• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SRombauts / SQLiteCpp / #613698365

22 Nov 2023 10:24AM UTC coverage: 98.238%. First build
#613698365

travis-ci

669 of 681 relevant lines covered (98.24%)

29.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.74
/src/Database.cpp
1
/**
2
 * @file    Database.cpp
3
 * @ingroup SQLiteCpp
4
 * @brief   Management of a SQLite Database Connection.
5
 *
6
 * Copyright (c) 2012-2025 Sebastien Rombauts (sebastien.rombauts@gmail.com)
7
 *
8
 * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
9
 * or copy at http://opensource.org/licenses/MIT)
10
 */
11
#include <SQLiteCpp/Database.h>
12

13
#include <SQLiteCpp/Assertion.h>
14
#include <SQLiteCpp/Backup.h>
15
#include <SQLiteCpp/Exception.h>
16
#include <SQLiteCpp/Statement.h>
17

18
#include <sqlite3.h>
19
#include <fstream>
20
#include <string.h>
21

22
#ifndef SQLITE_DETERMINISTIC
23
#define SQLITE_DETERMINISTIC 0x800
24
#endif // SQLITE_DETERMINISTIC
25

26
namespace SQLite
27
{
28

29
const int   OK                = SQLITE_OK;
30
const int   OPEN_READONLY     = SQLITE_OPEN_READONLY;
31
const int   OPEN_READWRITE    = SQLITE_OPEN_READWRITE;
32
const int   OPEN_CREATE       = SQLITE_OPEN_CREATE;
33
const int   OPEN_URI          = SQLITE_OPEN_URI;
34
const int   OPEN_MEMORY       = SQLITE_OPEN_MEMORY;
35
const int   OPEN_NOMUTEX      = SQLITE_OPEN_NOMUTEX;
36
const int   OPEN_FULLMUTEX    = SQLITE_OPEN_FULLMUTEX;
37
const int   OPEN_SHAREDCACHE  = SQLITE_OPEN_SHAREDCACHE;
38
const int   OPEN_PRIVATECACHE = SQLITE_OPEN_PRIVATECACHE;
39
// check if sqlite version is >= 3.31.0 and SQLITE_OPEN_NOFOLLOW is defined
40
#if SQLITE_VERSION_NUMBER >= 3031000 && defined(SQLITE_OPEN_NOFOLLOW)
41
const int   OPEN_NOFOLLOW     = SQLITE_OPEN_NOFOLLOW;
42
#else
43
const int   OPEN_NOFOLLOW     = 0;
44
#endif
45

46
const char* const VERSION        = SQLITE_VERSION;
47
const int         VERSION_NUMBER = SQLITE_VERSION_NUMBER;
48

49
// Return SQLite version string using runtime call to the compiled library
50
const char* getLibVersion() noexcept
51
{
52
    return sqlite3_libversion();
53
}
54

55
// Return SQLite version number using runtime call to the compiled library
1✔
56
int getLibVersionNumber() noexcept
57
{
1✔
58
    return sqlite3_libversion_number();
59
}
60

61

×
62
// Open the provided database UTF-8 filename with SQLite::OPEN_xxx provided flags.
63
Database::Database(const char* apFilename,
×
64
                   const int   aFlags         /* = SQLite::OPEN_READONLY*/,
65
                   const int   aBusyTimeoutMs /* = 0 */,
66
                   const char* apVfs          /* = nullptr*/) :
67
    mFilename(apFilename)
68
{
66✔
69
    sqlite3* handle;
70
    const int ret = sqlite3_open_v2(apFilename, &handle, aFlags, apVfs);
71
    mSQLitePtr.reset(handle);
66✔
72
    if (SQLITE_OK != ret)
69✔
73
    {
74
        throw SQLite::Exception(handle, ret);
75
    }
66✔
76
    if (aBusyTimeoutMs > 0)
66✔
77
    {
66✔
78
        setBusyTimeout(aBusyTimeoutMs);
79
    }
3✔
80
}
81

63✔
82
// Deleter functor to use with smart pointers to close the SQLite database connection in an RAII fashion.
83
void Database::Deleter::operator()(sqlite3* apSQLite)
2✔
84
{
85
    const int ret = sqlite3_close(apSQLite); // Calling sqlite3_close() with a nullptr argument is a harmless no-op.
63✔
86

87
    // Avoid unreferenced variable warning when build in release mode
88
    (void) ret;
66✔
89

90
    // Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized)
66✔
91
    // Never throw an exception in a destructor :
92
    SQLITECPP_ASSERT(SQLITE_OK == ret, "database is locked");  // See SQLITECPP_ENABLE_ASSERT_HANDLER
93
}
94

95
// Set a busy handler that sleeps for a specified amount of time when a table is locked.
96
void Database::setBusyTimeout(const int aBusyTimeoutMs)
97
{
66✔
98
    const int ret = sqlite3_busy_timeout(getHandle(), aBusyTimeoutMs);
66✔
99
    check(ret);
100
}
101

6✔
102
// Shortcut to execute one or multiple SQL statements without results (UPDATE, INSERT, ALTER, COMMIT, CREATE...).
103
// Return the number of changes.
6✔
104
int Database::exec(const char* apQueries)
6✔
105
{
6✔
106
    const int ret = tryExec(apQueries);
107
    check(ret);
108

109
    // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE only)
145✔
110
    return sqlite3_changes(getHandle());
111
}
145✔
112

145✔
113
int Database::tryExec(const char* apQueries) noexcept
114
{
115
    return sqlite3_exec(getHandle(), apQueries, nullptr, nullptr, nullptr);
137✔
116
}
117

118
// Shortcut to execute a one step query and fetch the first column of the result.
161✔
119
// WARNING: Be very careful with this dangerous method: you have to
120
// make a COPY OF THE result, else it will be destroy before the next line
161✔
121
// (when the underlying temporary Statement and Column objects are destroyed)
122
// this is an issue only for pointer type result (ie. char* and blob)
123
// (use the Column copy-constructor)
124
Column Database::execAndGet(const char* apQuery)
125
{
126
    Statement query(*this, apQuery);
127
    (void)query.executeStep(); // Can return false if no result, which will throw next line in getColumn()
128
    return query.getColumn(0);
129
}
14✔
130

131
// Shortcut to test if a table exists.
28✔
132
bool Database::tableExists(const char* apTableName) const
14✔
133
{
26✔
134
    Statement query(*this, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?");
135
    query.bind(1, apTableName);
136
    (void)query.executeStep(); // Cannot return false, as the above query always return a result
137
    return (1 == query.getColumn(0).getInt());
31✔
138
}
139

62✔
140
// Get the rowid of the most recent successful INSERT into the database from the current connection.
31✔
141
int64_t Database::getLastInsertRowid() const noexcept
31✔
142
{
62✔
143
    return sqlite3_last_insert_rowid(getHandle());
144
}
145

146
// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table).
40✔
147
int Database::getChanges() const noexcept
148
{
40✔
149
    return sqlite3_changes(getHandle());
150
}
151

152
// Get total number of rows modified by all INSERT, UPDATE or DELETE statement since connection.
19✔
153
int Database::getTotalChanges() const noexcept
154
{
19✔
155
    return sqlite3_total_changes(getHandle());
156
}
157

158
// Return the numeric result code for the most recent failed API call (if any).
27✔
159
int Database::getErrorCode() const noexcept
160
{
27✔
161
    return sqlite3_errcode(getHandle());
162
}
163

164
// Return the extended numeric result code for the most recent failed API call (if any).
58✔
165
int Database::getExtendedErrorCode() const noexcept
166
{
58✔
167
    return sqlite3_extended_errcode(getHandle());
168
}
169

170
// Return UTF-8 encoded English language explanation of the most recent failed API call (if any).
21✔
171
const char* Database::getErrorMsg() const noexcept
172
{
21✔
173
    return sqlite3_errmsg(getHandle());
174
}
175

176
// Attach a custom function to your sqlite database. Assumes UTF8 text representation.
10✔
177
// Parameter details can be found here: http://www.sqlite.org/c3ref/create_function.html
178
void Database::createFunction(const char*   apFuncName,
10✔
179
                              int           aNbArg,
180
                              bool          abDeterministic,
181
                              void*         apApp,
182
                              void        (*apFunc)(sqlite3_context *, int, sqlite3_value **),
183
                              void        (*apStep)(sqlite3_context *, int, sqlite3_value **) /* = nullptr */,
1✔
184
                              void        (*apFinal)(sqlite3_context *) /* = nullptr */, // NOLINT(readability/casting)
185
                              void        (*apDestroy)(void *) /* = nullptr */)
186
{
187
    int textRep = SQLITE_UTF8;
188
    // optimization if deterministic function (e.g. of nondeterministic function random())
189
    if (abDeterministic)
190
    {
191
        textRep = textRep | SQLITE_DETERMINISTIC;
192
    }
1✔
193
    const int ret = sqlite3_create_function_v2(getHandle(), apFuncName, aNbArg, textRep,
194
                                               apApp, apFunc, apStep, apFinal, apDestroy);
1✔
195
    check(ret);
196
}
1✔
197

198
// Load an extension into the sqlite database. Only affects the current connection.
1✔
199
// Parameter details can be found here: http://www.sqlite.org/c3ref/load_extension.html
1✔
200
void Database::loadExtension(const char* apExtensionName, const char *apEntryPointName)
1✔
201
{
1✔
202
#ifdef SQLITE_OMIT_LOAD_EXTENSION
203
    // Unused
204
    (void)apExtensionName;
205
    (void)apEntryPointName;
1✔
206

207
    throw SQLite::Exception("sqlite extensions are disabled");
208
#else
209
#ifdef SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION // Since SQLite 3.13 (2016-05-18):
210
    // Security warning:
211
    // It is recommended that the SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION method be used to enable only this interface.
212
    // The use of the sqlite3_enable_load_extension() interface should be avoided to keep the SQL load_extension()
213
    // disabled and prevent SQL injections from giving attackers access to extension loading capabilities.
214
    // (NOTE: not using nullptr: cannot pass object of non-POD type 'std::__1::nullptr_t' through variadic function)
215
    int ret = sqlite3_db_config(getHandle(), SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); // NOTE: not using nullptr
216
#else
217
    int ret = sqlite3_enable_load_extension(getHandle(), 1);
218
#endif
219
    check(ret);
220

1✔
221
    ret = sqlite3_load_extension(getHandle(), apExtensionName, apEntryPointName, 0);
222
    check(ret);
223
#endif
224
}
1✔
225

226
// Set the key for the current sqlite database instance.
1✔
227
void Database::key(const std::string& aKey) const
1✔
228
{
229
    int passLen = static_cast<int>(aKey.length());
230
#ifdef SQLITE_HAS_CODEC
231
    if (passLen > 0)
232
    {
×
233
        const int ret = sqlite3_key(getHandle(), aKey.c_str(), passLen);
×
234
        check(ret);
235
    }
236
#else // SQLITE_HAS_CODEC
237
    if (passLen > 0)
238
    {
239
        throw SQLite::Exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable.");
240
    }
241
#endif // SQLITE_HAS_CODEC
242
}
243

244
// Reset the key for the current sqlite database instance.
245
void Database::rekey(const std::string& aNewKey) const
246
{
247
#ifdef SQLITE_HAS_CODEC
248
    int passLen = aNewKey.length();
249
    if (passLen > 0)
250
    {
251
        const int ret = sqlite3_rekey(getHandle(), aNewKey.c_str(), passLen);
252
        check(ret);
253
    }
254
    else
255
    {
×
256
        const int ret = sqlite3_rekey(getHandle(), nullptr, 0);
257
        check(ret);
×
258
    }
259
#else // SQLITE_HAS_CODEC
260
    static_cast<void>(aNewKey); // silence unused parameter warning
261
    throw SQLite::Exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable.");
262
#endif // SQLITE_HAS_CODEC
263
}
264

1✔
265
// Test if a file contains an unencrypted database.
266
bool Database::isUnencrypted(const std::string& aFilename)
1✔
267
{
268
    if (aFilename.empty())
269
    {
270
        throw SQLite::Exception("Could not open database, the aFilename parameter was empty.");
271
    }
272

273
    std::ifstream fileBuffer(aFilename.c_str(), std::ios::in | std::ios::binary);
274
    char header[16];
1✔
275
    if (fileBuffer.is_open())
276
    {
1✔
277
        fileBuffer.seekg(0, std::ios::beg);
278
        fileBuffer.getline(header, 16);
279
        fileBuffer.close();
280
    }
281
    else
282
    {
1✔
283
        throw SQLite::Exception("Error opening file: " + aFilename);
284
    }
285

286
    return strncmp(header, "SQLite format 3\000", 16) == 0;
287
}
288

289
// Parse header data from a database.
290
Header Database::getHeaderInfo(const std::string& aFilename)
291
{
292
    Header h;
293
    unsigned char buf[100];
294
    char* pBuf = reinterpret_cast<char*>(&buf[0]);
295
    char* pHeaderStr = reinterpret_cast<char*>(&h.headerStr[0]);
296

297
    if (aFilename.empty())
298
    {
1✔
299
        throw SQLite::Exception("Filename parameter is empty");
300
    }
301

302
    {
303
        std::ifstream fileBuffer(aFilename.c_str(), std::ios::in | std::ios::binary);
3✔
304
        if (fileBuffer.is_open())
305
        {
3✔
306
            fileBuffer.seekg(0, std::ios::beg);
307
            fileBuffer.read(pBuf, 100);
1✔
308
            fileBuffer.close();
309
            if (fileBuffer.gcount() < 100)
310
            {
4✔
311
                throw SQLite::Exception("File " + aFilename + " is too short");
312
            }
2✔
313
        }
314
        else
1✔
315
        {
1✔
316
            throw SQLite::Exception("Error opening file " + aFilename);
1✔
317
        }
318
    }
319

320
    // If the "magic string" can't be found then header is invalid, corrupt or unreadable
1✔
321
    memcpy(pHeaderStr, pBuf, 16);
322
    pHeaderStr[15] = '\0';
323
    if (strncmp(pHeaderStr, "SQLite format 3", 15) != 0)
2✔
324
    {
325
        throw SQLite::Exception("Invalid or encrypted SQLite header in file " + aFilename);
326
    }
327

6✔
328
    h.pageSizeBytes = (buf[16] << 8) | buf[17];
329
    h.fileFormatWriteVersion = buf[18];
330
    h.fileFormatReadVersion = buf[19];
331
    h.reservedSpaceBytes = buf[20];
6✔
332
    h.maxEmbeddedPayloadFrac = buf[21];
6✔
333
    h.minEmbeddedPayloadFrac = buf[22];
334
    h.leafPayloadFrac = buf[23];
6✔
335

336
    h.fileChangeCounter =
1✔
337
        (buf[24] << 24) |
338
        (buf[25] << 16) |
339
        (buf[26] << 8)  |
340
        (buf[27] << 0);
10✔
341

5✔
342
    h.databaseSizePages =
343
        (buf[28] << 24) |
4✔
344
        (buf[29] << 16) |
4✔
345
        (buf[30] << 8)  |
4✔
346
        (buf[31] << 0);
4✔
347

348
    h.firstFreelistTrunkPage =
1✔
349
        (buf[32] << 24) |
350
        (buf[33] << 16) |
351
        (buf[34] << 8)  |
352
        (buf[35] << 0);
353

1✔
354
    h.totalFreelistPages =
355
        (buf[36] << 24) |
356
        (buf[37] << 16) |
357
        (buf[38] << 8)  |
358
        (buf[39] << 0);
3✔
359

3✔
360
    h.schemaCookie =
3✔
361
        (buf[40] << 24) |
362
        (buf[41] << 16) |
1✔
363
        (buf[42] << 8)  |
364
        (buf[43] << 0);
365

2✔
366
    h.schemaFormatNumber =
2✔
367
        (buf[44] << 24) |
2✔
368
        (buf[45] << 16) |
2✔
369
        (buf[46] << 8)  |
2✔
370
        (buf[47] << 0);
2✔
371

2✔
372
    h.defaultPageCacheSizeBytes =
373
        (buf[48] << 24) |
2✔
374
        (buf[49] << 16) |
4✔
375
        (buf[50] << 8)  |
4✔
376
        (buf[51] << 0);
4✔
377

2✔
378
    h.largestBTreePageNumber =
379
        (buf[52] << 24) |
2✔
380
        (buf[53] << 16) |
4✔
381
        (buf[54] << 8)  |
4✔
382
        (buf[55] << 0);
4✔
383

2✔
384
    h.databaseTextEncoding =
385
        (buf[56] << 24) |
2✔
386
        (buf[57] << 16) |
4✔
387
        (buf[58] << 8)  |
4✔
388
        (buf[59] << 0);
4✔
389

2✔
390
    h.userVersion =
391
        (buf[60] << 24) |
2✔
392
        (buf[61] << 16) |
4✔
393
        (buf[62] << 8)  |
4✔
394
        (buf[63] << 0);
4✔
395

2✔
396
    h.incrementalVaccumMode =
397
        (buf[64] << 24) |
2✔
398
        (buf[65] << 16) |
4✔
399
        (buf[66] << 8)  |
4✔
400
        (buf[67] << 0);
4✔
401

2✔
402
    h.applicationId =
403
        (buf[68] << 24) |
2✔
404
        (buf[69] << 16) |
4✔
405
        (buf[70] << 8)  |
4✔
406
        (buf[71] << 0);
4✔
407

2✔
408
    h.versionValidFor =
409
        (buf[92] << 24) |
2✔
410
        (buf[93] << 16) |
4✔
411
        (buf[94] << 8)  |
4✔
412
        (buf[95] << 0);
4✔
413

2✔
414
    h.sqliteVersion =
415
        (buf[96] << 24) |
2✔
416
        (buf[97] << 16) |
4✔
417
        (buf[98] << 8)  |
4✔
418
        (buf[99] << 0);
4✔
419

2✔
420
    return h;
421
}
2✔
422

4✔
423
void Database::backup(const char* apFilename, BackupType aType)
4✔
424
{
4✔
425
    // Open the database file identified by apFilename
2✔
426
    Database otherDatabase(apFilename, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
427

2✔
428
    // For a 'Save' operation, data is copied from the current Database to the other. A 'Load' is the reverse.
4✔
429
    Database& src = (aType == BackupType::Save ? *this : otherDatabase);
4✔
430
    Database& dest = (aType == BackupType::Save ? otherDatabase : *this);
4✔
431

2✔
432
    // Set up the backup procedure to copy between the "main" databases of each connection
433
    Backup bkp(dest, src);
2✔
434
    bkp.executeStep(); // Execute all steps at once
4✔
435

4✔
436
    // RAII Finish Backup an Close the other Database
4✔
437
}
2✔
438

439
}  // namespace SQLite
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc