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

SRombauts / SQLiteCpp / 28404751507

29 Jun 2026 09:45PM UTC coverage: 99.708%. Remained the same
28404751507

Pull #552

github

SRombauts
Return empty string instead of throwing on null expanded SQL

Keeps the null guard that fixes the UB, but drops the explicit throw. The throw left two lines uncovered (the throw itself and the function's exception-unwind landing brace), neither reachable from a unit test, which tripped the Coveralls no-decrease gate. The empty-string fallback is the alternative noted in the review and adds no new lines.
Pull Request #552: Guard null C pointers in Exception, Database, and Statement against UB

1 of 1 new or added line in 1 file covered. (100.0%)

2 existing lines in 2 files now uncovered.

683 of 685 relevant lines covered (99.71%)

30.48 hits per line

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

99.46
/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
2✔
51
{
52
    return sqlite3_libversion();
2✔
53
}
54

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

61

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

421
    return h;
4✔
422
}
423

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

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

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

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

440
}  // namespace SQLite
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