• 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.41
/src/Statement.cpp
1
/**
2
 * @file    Statement.cpp
3
 * @ingroup SQLiteCpp
4
 * @brief   A prepared SQLite Statement is a compiled SQL query ready to be executed, pointing to a row of result.
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/Statement.h>
12

13
#include <SQLiteCpp/Database.h>
14
#include <SQLiteCpp/Column.h>
15
#include <SQLiteCpp/Assertion.h>
16
#include <SQLiteCpp/Exception.h>
17

18
#include <sqlite3.h>
19

20
// check for if SQLite3 version >= 3.14.0
21
#if SQLITE_VERSION_NUMBER < 3014000
22
    #warning "SQLite3 version is less than 3.14.0, so expanded SQL is not available"
23
    #warning "To use expanded SQL, please upgrade to SQLite3 version 3.14.0 or later"
24
    #warning "If you want to disable this warning, define SQLITECPP_DISABLE_SQLITE3_EXPANDED_SQL"
25
    #warning "or use the specific project option in your build system"
26
    #warning "disabling expanded SQL support"
27
    #define SQLITECPP_DISABLE_SQLITE3_EXPANDED_SQL
28
#endif
29

30

31
namespace SQLite
32
{
33

34
Statement::Statement(const Database& aDatabase, const char* apQuery) :
107✔
35
    mQuery(apQuery),
214✔
36
    mpSQLite(aDatabase.getHandle()),
107✔
37
    mpPreparedStatement(prepareStatement()) // prepare the SQL query (needs Database friendship)
107✔
38
{
39
    mColumnCount = sqlite3_column_count(mpPreparedStatement.get());
106✔
40
}
107✔
41

42
Statement::Statement(Statement&& aStatement) noexcept :
2✔
43
    mQuery(std::move(aStatement.mQuery)),
2✔
44
    mpSQLite(aStatement.mpSQLite),
2✔
45
    mpPreparedStatement(std::move(aStatement.mpPreparedStatement)),
2✔
46
    mColumnCount(aStatement.mColumnCount),
2✔
47
    mbHasRow(aStatement.mbHasRow),
2✔
48
    mbDone(aStatement.mbDone),
2✔
49
    mColumnNames(std::move(aStatement.mColumnNames))
2✔
50
{
51
    aStatement.mpSQLite = nullptr;
2✔
52
    aStatement.mColumnCount = 0;
2✔
53
    aStatement.mbHasRow = false;
2✔
54
    aStatement.mbDone = false;
2✔
55
}
2✔
56

57
// Reset the statement to make it ready for a new execution (see also #clearBindings() below)
58
void Statement::reset()
36✔
59
{
60
    const int ret = tryReset();
36✔
61
    check(ret);
36✔
62
}
35✔
63

64
int Statement::tryReset() noexcept
37✔
65
{
66
    mbHasRow = false;
37✔
67
    mbDone = false;
37✔
68
    return sqlite3_reset(mpPreparedStatement.get());
37✔
69
}
70

71
// Clears away all the bindings of a prepared statement (can be associated with #reset() above).
72
void Statement::clearBindings()
4✔
73
{
74
    const int ret = sqlite3_clear_bindings(getPreparedStatement());
4✔
75
    check(ret);
4✔
76
}
4✔
77

78
int Statement::getIndex(const char * const apName) const
33✔
79
{
80
    return sqlite3_bind_parameter_index(getPreparedStatement(), apName);
33✔
81
}
82

83
// Bind an 32bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
84
void Statement::bind(const int aIndex, const int32_t aValue)
26✔
85
{
86
    const int ret = sqlite3_bind_int(getPreparedStatement(), aIndex, aValue);
26✔
87
    check(ret);
25✔
88
}
19✔
89

90
// Bind a 32bits unsigned int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
91
void Statement::bind(const int aIndex, const uint32_t aValue)
3✔
92
{
93
    const int ret = sqlite3_bind_int64(getPreparedStatement(), aIndex, aValue);
3✔
94
    check(ret);
3✔
95
}
3✔
96

97
// Bind a 64bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
98
void Statement::bind(const int aIndex, const int64_t aValue)
8✔
99
{
100
    const int ret = sqlite3_bind_int64(getPreparedStatement(), aIndex, aValue);
8✔
101
    check(ret);
8✔
102
}
8✔
103

104
// Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
105
void Statement::bind(const int aIndex, const double aValue)
6✔
106
{
107
    const int ret = sqlite3_bind_double(getPreparedStatement(), aIndex, aValue);
6✔
108
    check(ret);
6✔
109
}
6✔
110

111
// Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
112
void Statement::bind(const int aIndex, const std::string& aValue)
9✔
113
{
114
    const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, aValue.c_str(),
9✔
115
                                      static_cast<int>(aValue.size()), SQLITE_TRANSIENT);
9✔
116
    check(ret);
9✔
117
}
9✔
118

119
// Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
120
void Statement::bind(const int aIndex, const char* apValue)
42✔
121
{
122
    const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, apValue, -1, SQLITE_TRANSIENT);
42✔
123
    check(ret);
42✔
124
}
41✔
125

126
// Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
127
void Statement::bind(const int aIndex, const void* apValue, const int aSize)
6✔
128
{
129
    const int ret = sqlite3_bind_blob(getPreparedStatement(), aIndex, apValue, aSize, SQLITE_TRANSIENT);
6✔
130
    check(ret);
6✔
131
}
6✔
132

133
// Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
134
void Statement::bindNoCopy(const int aIndex, const std::string& aValue)
3✔
135
{
136
    const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, aValue.c_str(),
3✔
137
                                      static_cast<int>(aValue.size()), SQLITE_STATIC);
3✔
138
    check(ret);
3✔
139
}
3✔
140

141
// Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
142
void Statement::bindNoCopy(const int aIndex, const char* apValue)
3✔
143
{
144
    const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, apValue, -1, SQLITE_STATIC);
3✔
145
    check(ret);
3✔
146
}
3✔
147

148
// Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
149
void Statement::bindNoCopy(const int aIndex, const void* apValue, const int aSize)
3✔
150
{
151
    const int ret = sqlite3_bind_blob(getPreparedStatement(), aIndex, apValue, aSize, SQLITE_STATIC);
3✔
152
    check(ret);
3✔
153
}
3✔
154

155
// Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement
156
void Statement::bind(const int aIndex)
4✔
157
{
158
    const int ret = sqlite3_bind_null(getPreparedStatement(), aIndex);
4✔
159
    check(ret);
4✔
160
}
3✔
161

162

163
// Execute a step of the query to fetch one row of results
164
bool Statement::executeStep()
160✔
165
{
166
    const int ret = tryExecuteStep();
160✔
167
    if ((SQLITE_ROW != ret) && (SQLITE_DONE != ret)) // on row or no (more) row ready, else it's a problem
160✔
168
    {
169
        if (ret == sqlite3_errcode(mpSQLite))
2✔
170
        {
171
            throw SQLite::Exception(mpSQLite, ret);
1✔
172
        }
173
        else
174
        {
175
            throw SQLite::Exception("Statement needs to be reseted", ret);
1✔
176
        }
177
    }
178

179
    return mbHasRow; // true only if one row is accessible by getColumn(N)
158✔
180
}
181

182
// Execute a one-step query with no expected result, and return the number of changes.
183
int Statement::exec()
37✔
184
{
185
    const int ret = tryExecuteStep();
37✔
186
    if (SQLITE_DONE != ret) // the statement has finished executing successfully
37✔
187
    {
188
        if (SQLITE_ROW == ret)
5✔
189
        {
190
            throw SQLite::Exception("exec() does not expect results. Use executeStep.");
1✔
191
        }
192
        else if (ret == sqlite3_errcode(mpSQLite))
4✔
193
        {
194
            throw SQLite::Exception(mpSQLite, ret);
1✔
195
        }
196
        else
197
        {
198
            throw SQLite::Exception("Statement needs to be reseted", ret);
3✔
199
        }
200
    }
201

202
    // Return the number of rows modified by those SQL statements (INSERT, UPDATE or DELETE)
203
    return sqlite3_changes(mpSQLite);
32✔
204
}
205

206
int Statement::tryExecuteStep() noexcept
200✔
207
{
208
    if (mbDone)
200✔
209
    {
210
        return SQLITE_MISUSE; // Statement needs to be reseted !
4✔
211
    }
212

213
    const int ret = sqlite3_step(mpPreparedStatement.get());
196✔
214
    if (SQLITE_ROW == ret) // one row is ready : call getColumn(N) to access it
196✔
215
    {
216
        mbHasRow = true;
137✔
217
    }
218
    else
219
    {
220
        mbHasRow = false;
59✔
221
        mbDone = SQLITE_DONE == ret; // check if the query has finished executing
59✔
222
    }
223
    return ret;
196✔
224
}
225

226

227
// Return a copy of the column data specified by its index starting at 0
228
// (use the Column copy-constructor)
229
Column Statement::getColumn(const int aIndex) const
411✔
230
{
231
    checkRow();
411✔
232
    checkIndex(aIndex);
403✔
233

234
    // Share the Statement Object handle with the new Column created
235
    return Column(mpPreparedStatement, aIndex);
403✔
236
}
237

238
// Return a copy of the column data specified by its column name starting at 0
239
// (use the Column copy-constructor)
240
Column Statement::getColumn(const char* apName) const
17✔
241
{
242
    checkRow();
17✔
243
    const int index = getColumnIndex(apName);
17✔
244

245
    // Share the Statement Object handle with the new Column created
246
    return Column(mpPreparedStatement, index);
15✔
247
}
248

249
// Test if the column is NULL
250
bool Statement::isColumnNull(const int aIndex) const
36✔
251
{
252
    checkRow();
36✔
253
    checkIndex(aIndex);
31✔
254
    return (SQLITE_NULL == sqlite3_column_type(getPreparedStatement(), aIndex));
19✔
255
}
256

257
bool Statement::isColumnNull(const char* apName) const
15✔
258
{
259
    checkRow();
15✔
260
    const int index = getColumnIndex(apName);
15✔
261
    return (SQLITE_NULL == sqlite3_column_type(getPreparedStatement(), index));
11✔
262
}
263

264
// Return the named assigned to the specified result column (potentially aliased)
265
const char* Statement::getColumnName(const int aIndex) const
5✔
266
{
267
    checkIndex(aIndex);
5✔
268
    return sqlite3_column_name(getPreparedStatement(), aIndex);
5✔
269
}
270

271
#ifdef SQLITE_ENABLE_COLUMN_METADATA
272
// Return the named assigned to the specified result column (potentially aliased)
273
const char* Statement::getColumnOriginName(const int aIndex) const
5✔
274
{
275
    checkIndex(aIndex);
5✔
276
    return sqlite3_column_origin_name(getPreparedStatement(), aIndex);
5✔
277
}
278
#endif
279

280
// Return the index of the specified (potentially aliased) column name
281
int Statement::getColumnIndex(const char* apName) const
34✔
282
{
283
    // Build the map of column index by name on first call
284
    if (mColumnNames.empty())
34✔
285
    {
286
        for (int i = 0; i < mColumnCount; ++i)
17✔
287
        {
288
            const char* pName = sqlite3_column_name(getPreparedStatement(), i);
12✔
289
            // pName can be NULL if a memory allocation fails inside SQLite; such a column
290
            // cannot be matched by a caller-supplied name anyway, so skip it.
291
            if (pName)
12✔
292
            {
293
                mColumnNames[pName] = i;
24✔
294
            }
295
        }
296
    }
297

298
    const auto iIndex = mColumnNames.find(apName);
68✔
299
    if (iIndex == mColumnNames.end())
34✔
300
    {
301
        throw SQLite::Exception("Unknown column name.");
7✔
302
    }
303

304
    return iIndex->second;
54✔
305
}
306

307
const char* Statement::getColumnDeclaredType(const int aIndex) const
6✔
308
{
309
    checkIndex(aIndex);
6✔
310
    const char * result = sqlite3_column_decltype(getPreparedStatement(), aIndex);
5✔
311
    if (!result)
5✔
312
    {
313
        throw SQLite::Exception("Could not determine declared column type.");
2✔
314
    }
315
    else
316
    {
317
        return result;
3✔
318
    }
319
}
320

321
// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table).
322
int Statement::getChanges() const noexcept
3✔
323
{
324
    return sqlite3_changes(mpSQLite);
3✔
325
}
326

327
int Statement::getBindParameterCount() const noexcept
3✔
328
{
329
    return sqlite3_bind_parameter_count(mpPreparedStatement.get());
3✔
330
}
331

332
// Return the numeric result code for the most recent failed API call (if any).
333
int Statement::getErrorCode() const noexcept
2✔
334
{
335
    return sqlite3_errcode(mpSQLite);
2✔
336
}
337

338
// Return the extended numeric result code for the most recent failed API call (if any).
339
int Statement::getExtendedErrorCode() const noexcept
2✔
340
{
341
    return sqlite3_extended_errcode(mpSQLite);
2✔
342
}
343

344
// Return UTF-8 encoded English language explanation of the most recent failed API call (if any).
345
const char* Statement::getErrorMsg() const noexcept
1✔
346
{
347
    return sqlite3_errmsg(mpSQLite);
1✔
348
}
349

350

351
// Return a UTF-8 string containing the SQL text of prepared statement with bound parameters expanded.
352
std::string Statement::getExpandedSQL() const {
1✔
353
    #ifdef SQLITECPP_DISABLE_SQLITE3_EXPANDED_SQL
354
    throw SQLite::Exception("this version of SQLiteCpp does not support expanded SQL");
355
    #else
356
    char* expanded = sqlite3_expanded_sql(getPreparedStatement());
1✔
357
    // sqlite3_expanded_sql() returns NULL on out of memory, when the result would exceed
358
    // SQLITE_LIMIT_LENGTH, or in SQLITE_OMIT_TRACE builds. Building a std::string from NULL
359
    // is undefined behavior, so fall back to an empty string in that case.
360
    std::string expandedString(expanded ? expanded : "");
1✔
361
    sqlite3_free(expanded);
1✔
362
    return expandedString;
1✔
363
    #endif
UNCOV
364
}
×
365

366

367
// Prepare SQLite statement object and return shared pointer to this object
368
Statement::TStatementPtr Statement::prepareStatement()
107✔
369
{
370
    sqlite3_stmt* statement;
371
    const int ret = sqlite3_prepare_v2(mpSQLite, mQuery.c_str(), static_cast<int>(mQuery.size()), &statement, nullptr);
107✔
372
    if (SQLITE_OK != ret)
107✔
373
    {
374
        throw SQLite::Exception(mpSQLite, ret);
1✔
375
    }
376
    return Statement::TStatementPtr(statement, [](sqlite3_stmt* stmt)
106✔
377
        {
378
            sqlite3_finalize(stmt);
106✔
379
        });
212✔
380
}
381

382
// Return prepered statement object or throw
383
sqlite3_stmt* Statement::getPreparedStatement() const
208✔
384
{
385
    sqlite3_stmt* ret = mpPreparedStatement.get();
208✔
386
    if (ret)
208✔
387
    {
388
        return ret;
207✔
389
    }
390
    throw SQLite::Exception("Statement was not prepared.");
1✔
391
}
392

393
}  // 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