From 10933e32ee9669576f75a2efaecefe7324736c95 Mon Sep 17 00:00:00 2001 From: dvtate Date: Mon, 21 Sep 2026 07:23:37 -0500 Subject: [PATCH 1/5] Add std::string_view support --- include/SQLiteCpp/Column.h | 29 +++++++++++++++ include/SQLiteCpp/Statement.h | 69 +++++++++++++++++++++++++++++++++++ src/Column.cpp | 16 ++++++++ src/Statement.cpp | 20 ++++++++++ 4 files changed, 134 insertions(+) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index bf5760ab..38888a3b 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -16,6 +16,9 @@ #include #include +#if __cplusplus >= 201703L // c++17 +#include +#endif // Forward declarations to avoid inclusion of in a header struct sqlite3_stmt; @@ -102,6 +105,17 @@ class SQLITECPP_API Column * Note this correctly handles strings that contain null bytes. */ std::string getString() const; +#if __cplusplus >= 201703L + /** + * @brief Return a std::string_view for a TEXT or BLOB column. + * + * Note this correctly handles strings that contain null bytes. + * + * @warning returned string_view is only valid until there is a type + * conversion or the statement is stepped or reset. + */ + std::string_view getStringView() const; +#endif // c++17 /** * @brief Return the type of the value of the column using sqlite3_column_type() @@ -227,6 +241,21 @@ class SQLITECPP_API Column return getString(); } +#if __cplusplus >= 201703L + + /** + * @brief Inline cast operator to std::string_view + * + * Handles BLOB or TEXT, which may contain null bytes within + * + * @see getStringView + */ + operator std::string_view() const + { + return getStringView(); + } +#endif // c++17 + private: Statement::TStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object int mIndex; ///< Index of the column in the row of result, starting at 0 diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index e69930bf..789a5e34 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -19,6 +19,9 @@ #include #include #include +#if __cplusplus >= 201703L +#include +#endif // Forward declarations to avoid inclusion of in a header struct sqlite3; @@ -146,6 +149,14 @@ class SQLITECPP_API Statement * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ void bind(const int aIndex, const std::string& aValue); +#if __cplusplus >= 201703L + /** + * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bind(const int aIndex, const std::string_view aValue); +#endif // c++17 /** * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -177,6 +188,16 @@ class SQLITECPP_API Statement * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. */ void bindNoCopy(const int aIndex, const std::string& aValue); +#if __cplusplus >= 201703L + /** + * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). + * + * The string can contain null characters as it is binded using its size. + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + void bindNoCopy(const int aIndex, const std::string_view aValue); +#endif // c++17 /** * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -252,6 +273,17 @@ class SQLITECPP_API Statement { bind(getIndex(apName), aValue); } +#if __cplusplus >= 201703L + /** + * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bind(const char* apName, const std::string_view aValue) + { + bind(getIndex(apName), aValue); + } +#endif // c++17 /** * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -295,6 +327,19 @@ class SQLITECPP_API Statement { bindNoCopy(getIndex(apName), aValue); } +#if __cplusplus >= 201703L + /** + * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * The string can contain null characters as it is binded using its size. + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + void bindNoCopy(const char* apName, const std::string_view aValue) + { + bindNoCopy(getIndex(apName), aValue); + } +#endif /** * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -382,6 +427,17 @@ class SQLITECPP_API Statement { bind(aName.c_str(), aValue); } +#if __cplusplus >= 201703L + /** + * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bind(const std::string& aName, const string_view aValue ) + { + bind(aName.c_str(), aValue); + } +#endif /** * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -425,6 +481,19 @@ class SQLITECPP_API Statement { bindNoCopy(aName.c_str(), aValue); } +#if __cplusplus >= 201703L + /** + * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * The string can contain null characters as it is binded using its size. + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + void bindNoCopy(const std::string& aName, const std::string_view aValue) + { + bindNoCopy(aName.c_str(), aValue); + } +#endif // c++17 /** * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * diff --git a/src/Column.cpp b/src/Column.cpp index 2485a735..d106c9a5 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -101,6 +101,22 @@ std::string Column::getString() const return std::string(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); } +#if __cplusplus >= 201703L +std::string_view Column::getStringView() const +{ + // Note: using sqlite3_column_blob and not sqlite3_column_text + // - no need for sqlite3_column_text to add a \0 on the end, as we're getting the bytes length directly + // however, we need to call sqlite3_column_bytes() to ensure correct format. It's a noop on a BLOB + // or a TEXT value with the correct encoding (UTF-8). Otherwise it'll do a conversion to TEXT (UTF-8). + (void)sqlite3_column_bytes(mStmtPtr.get(), mIndex); + auto data = static_cast(sqlite3_column_blob(mStmtPtr.get(), mIndex)); + + // SQLite docs: "The safest policy is to invoke… sqlite3_column_blob() followed by sqlite3_column_bytes()" + // Note: std::string_view is ok to pass nullptr as first arg, if length is 0 + return std::string_view(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); +} +#endif // c++17 + // Return the type of the value of the column int Column::getType() const noexcept { diff --git a/src/Statement.cpp b/src/Statement.cpp index 68293c8b..a449ef84 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -116,6 +116,16 @@ void Statement::bind(const int aIndex, const std::string& aValue) check(ret); } +#if __cplusplus >= 201703L +// Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement +void Statement::bind(const int aIndex, const std::string_view aValue) +{ + const int ret = sqlite3_bind_text64(getPreparedStatement(), aIndex, apValue.data(), + static_cast(aValue.size()), SQLITE_TRANSIENT, SQLITE_UTF8); + check(ret); +} +#endif // c++17 + // Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const char* apValue) { @@ -146,6 +156,16 @@ void Statement::bindNoCopy(const int aIndex, const std::string& aValue) check(ret); } +#if __cplusplus >= 201703L +// Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement +void Statement::bindNoCopy(const int aIndex, const std::string_view aValue) +{ + const int ret = sqlite3_bind_text64(getPreparedStatement(), aIndex, aValue.data(), + static_cast(aValue.size()), SQLITE_STATIC, SQLITE_UTF8); + check(ret); +} +#endif // c++17 + // Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bindNoCopy(const int aIndex, const char* apValue) { From 8e0a4d134a68264d9df2bb4efdab7d2e56d18fb9 Mon Sep 17 00:00:00 2001 From: dvtate Date: Mon, 21 Sep 2026 09:16:08 -0500 Subject: [PATCH 2/5] Isolate string_view to headers --- include/SQLiteCpp/Column.h | 6 +++- include/SQLiteCpp/Statement.h | 57 ++++++++++++++++++++++++----------- src/Column.cpp | 16 ---------- src/Statement.cpp | 36 ++++++++++------------ 4 files changed, 61 insertions(+), 54 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 38888a3b..b5623fa8 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -114,7 +114,11 @@ class SQLITECPP_API Column * @warning returned string_view is only valid until there is a type * conversion or the statement is stepped or reset. */ - std::string_view getStringView() const; + std::string_view getStringView() const { + // See getString implementation + (void)getBytes(); + return std::string_view(static_cast(getBlob()), getBytes()); + } #endif // c++17 /** diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 789a5e34..5ac1ba0c 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -149,20 +149,24 @@ class SQLITECPP_API Statement * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ void bind(const int aIndex, const std::string& aValue); -#if __cplusplus >= 201703L /** - * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ - void bind(const int aIndex, const std::string_view aValue); -#endif // c++17 + void bind(const int aIndex, const char* apValue); +#if __cplusplus >= 201703L /** - * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ - void bind(const int aIndex, const char* apValue); + void bind(const int aIndex, const std::string_view aValue) + { + bindText64(aIndex, aValue.data(), aValue.size()); + } +#endif // c++17 + /** * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -188,16 +192,6 @@ class SQLITECPP_API Statement * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. */ void bindNoCopy(const int aIndex, const std::string& aValue); -#if __cplusplus >= 201703L - /** - * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). - * - * The string can contain null characters as it is binded using its size. - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const int aIndex, const std::string_view aValue); -#endif // c++17 /** * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -206,6 +200,19 @@ class SQLITECPP_API Statement * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. */ void bindNoCopy(const int aIndex, const char* apValue); +#if __cplusplus >= 201703L + /** + * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). + * + * The string can contain null characters as it is bound using its size. + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + void bindNoCopy(const int aIndex, const std::string_view aValue) + { + bindTextNoCopy64(aIndex, aValue.data(), aValue.size()); + } +#endif // c++17 /** * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -433,7 +440,7 @@ class SQLITECPP_API Statement * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ - void bind(const std::string& aName, const string_view aValue ) + void bind(const std::string& aName, const std::string_view aValue ) { bind(aName.c_str(), aValue); } @@ -929,6 +936,22 @@ class SQLITECPP_API Statement */ sqlite3_stmt* getPreparedStatement() const; + /** + * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bindText64(const int aIndex, const char* apValue, const uint64_t aSize); + + /** + * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * The text can contain null characters as it is bound using its size + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + void bindTextNoCopy64(const int aIndex, const char* apValue, const uint64_t aSize); + std::string mQuery; //!< UTF-8 SQL Query sqlite3* mpSQLite; //!< Pointer to SQLite Database Connection Handle TStatementPtr mpPreparedStatement; //!< Shared Pointer to the prepared SQLite Statement Object diff --git a/src/Column.cpp b/src/Column.cpp index d106c9a5..2485a735 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -101,22 +101,6 @@ std::string Column::getString() const return std::string(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); } -#if __cplusplus >= 201703L -std::string_view Column::getStringView() const -{ - // Note: using sqlite3_column_blob and not sqlite3_column_text - // - no need for sqlite3_column_text to add a \0 on the end, as we're getting the bytes length directly - // however, we need to call sqlite3_column_bytes() to ensure correct format. It's a noop on a BLOB - // or a TEXT value with the correct encoding (UTF-8). Otherwise it'll do a conversion to TEXT (UTF-8). - (void)sqlite3_column_bytes(mStmtPtr.get(), mIndex); - auto data = static_cast(sqlite3_column_blob(mStmtPtr.get(), mIndex)); - - // SQLite docs: "The safest policy is to invoke… sqlite3_column_blob() followed by sqlite3_column_bytes()" - // Note: std::string_view is ok to pass nullptr as first arg, if length is 0 - return std::string_view(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); -} -#endif // c++17 - // Return the type of the value of the column int Column::getType() const noexcept { diff --git a/src/Statement.cpp b/src/Statement.cpp index a449ef84..6bda2a8b 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -116,16 +116,6 @@ void Statement::bind(const int aIndex, const std::string& aValue) check(ret); } -#if __cplusplus >= 201703L -// Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bind(const int aIndex, const std::string_view aValue) -{ - const int ret = sqlite3_bind_text64(getPreparedStatement(), aIndex, apValue.data(), - static_cast(aValue.size()), SQLITE_TRANSIENT, SQLITE_UTF8); - check(ret); -} -#endif // c++17 - // Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const char* apValue) { @@ -156,16 +146,6 @@ void Statement::bindNoCopy(const int aIndex, const std::string& aValue) check(ret); } -#if __cplusplus >= 201703L -// Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement -void Statement::bindNoCopy(const int aIndex, const std::string_view aValue) -{ - const int ret = sqlite3_bind_text64(getPreparedStatement(), aIndex, aValue.data(), - static_cast(aValue.size()), SQLITE_STATIC, SQLITE_UTF8); - check(ret); -} -#endif // c++17 - // Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bindNoCopy(const int aIndex, const char* apValue) { @@ -464,4 +444,20 @@ sqlite3_stmt* Statement::getPreparedStatement() const throw SQLite::Exception("Statement was not prepared."); } +// Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement +void Statement::bindText64(const int aIndex, const char* apValue, const uint64_t aSize) +{ + const int ret = sqlite3_bind_text64(getPreparedStatement(), aIndex, apValue, + static_cast(aSize), SQLITE_TRANSIENT, SQLITE_UTF8); + check(ret); +} + +// Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement +void Statement::bindTextNoCopy64(const int aIndex, const char* apValue, const uint64_t aSize) +{ + const int ret = sqlite3_bind_text64(getPreparedStatement(), aIndex, apValue, + static_cast(aSize), SQLITE_STATIC, SQLITE_UTF8); + check(ret); +} + } // namespace SQLite From 0699bfc08ff53224cad6c21e5c5c87aa5092c4b5 Mon Sep 17 00:00:00 2001 From: dvtate Date: Mon, 21 Sep 2026 09:16:17 -0500 Subject: [PATCH 3/5] unit tests --- tests/Column_test.cpp | 26 ++++++++++++++--------- tests/Statement_test.cpp | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp index f4911397..3f38db34 100644 --- a/tests/Column_test.cpp +++ b/tests/Column_test.cpp @@ -75,6 +75,7 @@ static void test_column_basis(bool utf16) const unsigned short uint4 = query.getColumn(0); // operator unsigned short() const char* ptxt = query.getColumn(1); // operator const char*() const std::string msg = query.getColumn(1); // operator std::string() (or const char* with MSVC) + const std::string_view sv = query.getColumn(1); // operator std::string() (or const char* with MSVC) const int integer = query.getColumn(2); // operator int() const double real = query.getColumn(3); // operator double() const void* pblob = query.getColumn(4); // operator void*() @@ -97,6 +98,7 @@ static void test_column_basis(bool utf16) EXPECT_EQ(1U, uint4); EXPECT_STREQ("first", ptxt); EXPECT_EQ("first", msg); + EXPECT_EQ("first", sv); EXPECT_EQ(-123, integer); EXPECT_DOUBLE_EQ(0.123, real); EXPECT_EQ(0, memcmp("bl\0b", pblob, size)); @@ -112,26 +114,30 @@ static void test_column_basis(bool utf16) // validates every variant of explicit getters { - int64_t id = query.getColumn(0).getInt64(); - const unsigned int uint1 = query.getColumn(0).getUInt(); - const uint32_t uint2 = query.getColumn(0).getUInt(); - const std::string msg1 = query.getColumn(1).getString(); - const char* ptxt = query.getColumn(1).getText(); - const std::string msg2 = query.getColumn(1).getText(); - const int integer = query.getColumn(2).getInt(); - const double real = query.getColumn(3).getDouble(); - const void* pblob = query.getColumn(4).getBlob(); - const std::string sblob = query.getColumn(4).getString(); + int64_t id = query.getColumn(0).getInt64(); + const unsigned int uint1 = query.getColumn(0).getUInt(); + const uint32_t uint2 = query.getColumn(0).getUInt(); + const std::string msg1 = query.getColumn(1).getString(); + const char* ptxt = query.getColumn(1).getText(); + const std::string msg2 = query.getColumn(1).getText(); + const std::string_view sv = query.getColumn(1).getStringView(); + const int integer = query.getColumn(2).getInt(); + const double real = query.getColumn(3).getDouble(); + const void* pblob = query.getColumn(4).getBlob(); + const std::string sblob = query.getColumn(4).getString(); + const std::string_view svblob = query.getColumn(4).getStringView(); EXPECT_EQ(1, id); EXPECT_EQ(1U, uint1); EXPECT_EQ(1U, uint2); EXPECT_STREQ("first", ptxt); EXPECT_EQ("first", msg1); EXPECT_EQ("first", msg2); + EXPECT_EQ("first", sv); EXPECT_EQ(-123, integer); EXPECT_DOUBLE_EQ(0.123, real); EXPECT_EQ(0, memcmp("bl\0b", pblob, 4)); EXPECT_EQ(0, memcmp("bl\0b", &sblob[0], 4)); + EXPECT_EQ(0, memcmp("bl\0b", svblob.data(), 4)); } // Validate getBytes(), getType(), isInteger(), isNull()... diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 78927d57..4691c456 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -1241,3 +1241,49 @@ TEST(Statement, getChanges) EXPECT_EQ(2, update.exec()); EXPECT_EQ(2, update.getChanges()); } + +// Test support for std::string_view which requries C++17 +#if __cplusplus >= 201703L +TEST(Statement, stringViewSupport) +{ + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + + // Create a new table + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, txt1 TEXT, txt2 TEXT, txt3 TEXT, txt4 TEXT)")); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + + // Insertion with bindable parameters + SQLite::Statement insert(db, "INSERT INTO test VALUES (NULL, ?, ?, ?, ?)"); + + // Compile a SQL query to check the results + SQLite::Statement query(db, "SELECT * FROM test"); + EXPECT_STREQ("SELECT * FROM test", query.getQuery().c_str()); + EXPECT_EQ(5, query.getColumnCount()); + + // Insert one row with all string_view variants of bind/bindNoCopy + { + const char* text = "123\0test"; + const std::string_view sv1{text, 8}; + std::string_view sv2{"abcd"}; + insert.bindNoCopy(1, sv1); + insert.bindNoCopy(2, sv2); + insert.bind(3, sv1); + insert.bind(4, sv2); + EXPECT_EQ(1, insert.exec()); + EXPECT_EQ(SQLITE_DONE, db.getErrorCode()); + + // Check the result + query.executeStep(); + EXPECT_TRUE(query.hasRow()); + EXPECT_FALSE(query.isDone()); + EXPECT_EQ(1, query.getColumn(0).getInt64()); + + EXPECT_EQ(sv1, query.getColumn(1).getStringView()); + EXPECT_EQ(sv2, query.getColumn(2).getStringView()); + EXPECT_EQ(sv1, query.getColumn(3).getStringView()); + EXPECT_EQ(sv2, query.getColumn(4).getStringView()); + } +} +#endif // c++17 \ No newline at end of file From ad45d7399a78ba2b31bfcfef8bf958602c765a7b Mon Sep 17 00:00:00 2001 From: dvtate Date: Mon, 21 Sep 2026 09:31:02 -0500 Subject: [PATCH 4/5] Fix UB --- include/SQLiteCpp/Column.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index b5623fa8..a0e80471 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -117,7 +117,10 @@ class SQLITECPP_API Column std::string_view getStringView() const { // See getString implementation (void)getBytes(); - return std::string_view(static_cast(getBlob()), getBytes()); + auto data = static_cast(getBlob()); + if (data == nullptr) + return {}; + return std::string_view(data, getBytes()); } #endif // c++17 From a18eed7c1f263a876df11049886bc296c0f686a0 Mon Sep 17 00:00:00 2001 From: Dustin Van Tate Testa Date: Wed, 23 Sep 2026 11:31:43 -0500 Subject: [PATCH 5/5] Add c++17 macros to column test --- tests/Column_test.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp index 3f38db34..c1a86ffb 100644 --- a/tests/Column_test.cpp +++ b/tests/Column_test.cpp @@ -75,7 +75,9 @@ static void test_column_basis(bool utf16) const unsigned short uint4 = query.getColumn(0); // operator unsigned short() const char* ptxt = query.getColumn(1); // operator const char*() const std::string msg = query.getColumn(1); // operator std::string() (or const char* with MSVC) +#if __cplusplus >= 201703L const std::string_view sv = query.getColumn(1); // operator std::string() (or const char* with MSVC) +#endif const int integer = query.getColumn(2); // operator int() const double real = query.getColumn(3); // operator double() const void* pblob = query.getColumn(4); // operator void*() @@ -98,7 +100,9 @@ static void test_column_basis(bool utf16) EXPECT_EQ(1U, uint4); EXPECT_STREQ("first", ptxt); EXPECT_EQ("first", msg); +#if __cplusplus >= 201703L EXPECT_EQ("first", sv); +#endif EXPECT_EQ(-123, integer); EXPECT_DOUBLE_EQ(0.123, real); EXPECT_EQ(0, memcmp("bl\0b", pblob, size)); @@ -120,24 +124,32 @@ static void test_column_basis(bool utf16) const std::string msg1 = query.getColumn(1).getString(); const char* ptxt = query.getColumn(1).getText(); const std::string msg2 = query.getColumn(1).getText(); +#if __cplusplus >= 201703L const std::string_view sv = query.getColumn(1).getStringView(); +#endif const int integer = query.getColumn(2).getInt(); const double real = query.getColumn(3).getDouble(); const void* pblob = query.getColumn(4).getBlob(); const std::string sblob = query.getColumn(4).getString(); +#if __cplusplus >= 201703L const std::string_view svblob = query.getColumn(4).getStringView(); +#endif EXPECT_EQ(1, id); EXPECT_EQ(1U, uint1); EXPECT_EQ(1U, uint2); EXPECT_STREQ("first", ptxt); EXPECT_EQ("first", msg1); EXPECT_EQ("first", msg2); +#if __cplusplus >= 201703L EXPECT_EQ("first", sv); +#endif EXPECT_EQ(-123, integer); EXPECT_DOUBLE_EQ(0.123, real); EXPECT_EQ(0, memcmp("bl\0b", pblob, 4)); EXPECT_EQ(0, memcmp("bl\0b", &sblob[0], 4)); +#if __cplusplus >= 201703L EXPECT_EQ(0, memcmp("bl\0b", svblob.data(), 4)); +#endif } // Validate getBytes(), getType(), isInteger(), isNull()...