From 18374bb6db020327a4d9cae4be97e2cdfb563407 Mon Sep 17 00:00:00 2001 From: Dustin Van Tate Testa Date: Sun, 3 Aug 2025 02:14:46 +0000 Subject: [PATCH 1/4] string_view support --- include/SQLiteCpp/Column.h | 15 +++++++++++++++ include/SQLiteCpp/Statement.h | 23 +++++++++++++++++++++++ src/Column.cpp | 17 +++++++++++++++++ src/Statement.cpp | 24 ++++++++++++++++++++++++ 4 files changed, 79 insertions(+) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index bf5760ab..a6b20998 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; @@ -103,6 +106,18 @@ class SQLITECPP_API Column */ std::string getString() const; +#if __cplusplus >= 201703L // C++17 + /** + * @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 + /** * @brief Return the type of the value of the column using sqlite3_column_type() * diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 3b9b0a70..840d691e 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -18,6 +18,9 @@ #include #include #include +#if __cplusplus >= 201703L // C++17 +#include +#endif // Forward declarations to avoid inclusion of in a header struct sqlite3; @@ -139,6 +142,16 @@ class SQLITECPP_API Statement * @brief Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) */ void bind(const int aIndex, const double aValue); + +#if __cplusplus >= 201703L // C++17 + /** + * @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 + /** * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -157,6 +170,16 @@ 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 void* apValue, const int aSize); +#if __cplusplus >= 201703L // C++17 + /** + * @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 /** * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). * diff --git a/src/Column.cpp b/src/Column.cpp index c7d18c51..151e16fe 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -101,6 +101,23 @@ std::string Column::getString() const return std::string(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); } +#if __cplusplus >= 201703L // C++17 +// Return a std::string_view to a TEXT or BLOB column +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 is ok to pass nullptr as first arg, if length is 0 + return std::string_view(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); +} +#endif + // 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 e4a264f4..48c3e7d7 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -108,6 +108,20 @@ void Statement::bind(const int aIndex, const double aValue) check(ret); } +#if __cplusplus >= 201703L // c++17 +/** + * @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 Statement::bind(const int aIndex, const std::string_view& aValue) { + const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, aValue.data(), + static_cast(aValue.size()), SQLITE_TRANSIENT); + check(ret); +} +#endif + + // 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& aValue) { @@ -130,6 +144,16 @@ void Statement::bind(const int aIndex, const void* apValue, const int aSize) check(ret); } +#if __cplusplus >= 201703L // C++17 +// 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_text(getPreparedStatement(), aIndex, aValue.data(), + static_cast(aValue.size()), SQLITE_STATIC); + check(ret); +} +#endif + // 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& aValue) { From 0c823761d5405ea9de002787984b837927779a21 Mon Sep 17 00:00:00 2001 From: Dustin Van Tate Testa Date: Mon, 4 Aug 2025 20:17:33 +0000 Subject: [PATCH 2/4] named parameter overloads, pass string_view by value --- include/SQLiteCpp/Statement.h | 55 +++++++++++++++++++++++++++++++++-- src/Statement.cpp | 4 +-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 840d691e..1fc6a567 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -149,7 +149,7 @@ 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_view& aValue); + void bind(const int aIndex, const std::string_view aValue); #endif /** @@ -178,7 +178,7 @@ 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_view& aValue); + void bindNoCopy(const int aIndex, const std::string_view aValue); #endif /** * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). @@ -242,6 +242,17 @@ class SQLITECPP_API Statement { bind(getIndex(apName), aValue); } +#if __cplusplus >= 201703L // C++17 + /** + * @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 /** * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -269,6 +280,19 @@ class SQLITECPP_API Statement { bind(getIndex(apName), apValue, aSize); } +#if __cplusplus >= 201703L // C++17 + /** + * @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 string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -343,6 +367,18 @@ class SQLITECPP_API Statement { bind(aName.c_str(), aValue); } + +#if __cplusplus >= 201703L // C++17 + /** + * @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 std::string_view aValue) + { + bind(aName.c_str(), aValue); + } +#endif /** * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -370,6 +406,21 @@ class SQLITECPP_API Statement { bind(aName.c_str(), apValue, aSize); } + +#if __cplusplus >= 201703L // C++17 + /** + * @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& aValue) + { + bindNoCopy(aName.c_str(), aValue); + } +#endif + /** * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * diff --git a/src/Statement.cpp b/src/Statement.cpp index 48c3e7d7..68fc23d4 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -114,7 +114,7 @@ void Statement::bind(const int aIndex, const double aValue) * * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use */ -void Statement::bind(const int aIndex, const std::string_view& aValue) { +void Statement::bind(const int aIndex, const std::string_view aValue) { const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, aValue.data(), static_cast(aValue.size()), SQLITE_TRANSIENT); check(ret); @@ -146,7 +146,7 @@ void Statement::bind(const int aIndex, const void* apValue, const int aSize) #if __cplusplus >= 201703L // C++17 // 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) +void Statement::bindNoCopy(const int aIndex, const std::string_view aValue) { const int ret = sqlite3_bind_text(getPreparedStatement(), aIndex, aValue.data(), static_cast(aValue.size()), SQLITE_STATIC); From 483dd460542d3cca316e80ce2b63210fc759377b Mon Sep 17 00:00:00 2001 From: dvtate Date: Tue, 10 Feb 2026 22:36:27 -0600 Subject: [PATCH 3/4] fix #533 : string_view for key/rekey --- include/SQLiteCpp/Database.h | 9 +++++++++ include/SQLiteCpp/Statement.h | 2 +- src/Database.cpp | 12 ++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index e92e2898..4780fbec 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -18,6 +18,7 @@ // c++17: macOS unless targetting compatibility with macOS < 10.15 #ifndef SQLITECPP_HAVE_STD_EXPERIMENTAL_FILESYSTEM #if __cplusplus >= 201703L + #include #if defined(__MINGW32__) || defined(__MINGW64__) #if __GNUC__ > 8 // MinGW requires GCC version > 8 for std::filesystem #define SQLITECPP_HAVE_STD_FILESYSTEM @@ -533,7 +534,11 @@ class SQLITECPP_API Database * * @throw SQLite::Exception in case of error */ +#if __cplusplus >= 201703L // C++17 + void key(const std::string_view aKey) const; +#else void key(const std::string& aKey) const; +#endif /** * @brief Reset the key for the current sqlite database instance. @@ -549,7 +554,11 @@ class SQLITECPP_API Database * * @throw SQLite::Exception in case of error */ +#if __cplusplus >= 201703L // C++17 + void rekey(const std::string_view aNewKey) const; +#else void rekey(const std::string& aNewKey) const; +#endif /** * @brief Test if a file contains an unencrypted database. diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 1fc6a567..a6562ab6 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -415,7 +415,7 @@ 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 std::string& aName, const std::string& aValue) + void bindNoCopy(const std::string& aName, const std::string_view aValue) { bindNoCopy(aName.c_str(), aValue); } diff --git a/src/Database.cpp b/src/Database.cpp index 30f71158..88d792b3 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -224,13 +224,17 @@ void Database::loadExtension(const char* apExtensionName, const char *apEntryPoi } // Set the key for the current sqlite database instance. +#if __cplusplus >= 201703L // C++17 +void Database::key(const std::string_view aKey) const +#else void Database::key(const std::string& aKey) const +#endif { int passLen = static_cast(aKey.length()); #ifdef SQLITE_HAS_CODEC if (passLen > 0) { - const int ret = sqlite3_key(getHandle(), aKey.c_str(), passLen); + const int ret = sqlite3_key(getHandle(), aKey.data(), passLen); check(ret); } #else // SQLITE_HAS_CODEC @@ -242,13 +246,17 @@ void Database::key(const std::string& aKey) const } // Reset the key for the current sqlite database instance. +#if __cplusplus >= 201703L // C++17 +void Database::rekey(const std::string_view aNewKey) const +#else void Database::rekey(const std::string& aNewKey) const +#endif { #ifdef SQLITE_HAS_CODEC int passLen = aNewKey.length(); if (passLen > 0) { - const int ret = sqlite3_rekey(getHandle(), aNewKey.c_str(), passLen); + const int ret = sqlite3_rekey(getHandle(), aNewKey.data(), passLen); check(ret); } else From 0534942cf1ec185a3bb03ef2519246ab37d8197b Mon Sep 17 00:00:00 2001 From: dvtate Date: Thu, 19 Feb 2026 10:39:01 -0600 Subject: [PATCH 4/4] Don't break ABI explanation: https://github.com/SRombauts/SQLiteCpp/pull/518#issuecomment-3927335687 Also I fixed indentation on some documentation comments. --- include/SQLiteCpp/Database.h | 145 +++++++++++++++++++++-------------- src/Database.cpp | 31 ++++---- 2 files changed, 99 insertions(+), 77 deletions(-) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index 4780fbec..817db9e1 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -18,7 +18,6 @@ // c++17: macOS unless targetting compatibility with macOS < 10.15 #ifndef SQLITECPP_HAVE_STD_EXPERIMENTAL_FILESYSTEM #if __cplusplus >= 201703L - #include #if defined(__MINGW32__) || defined(__MINGW64__) #if __GNUC__ > 8 // MinGW requires GCC version > 8 for std::filesystem #define SQLITECPP_HAVE_STD_FILESYSTEM @@ -524,71 +523,99 @@ class SQLITECPP_API Database void loadExtension(const char* apExtensionName, const char* apEntryPointName); /** - * @brief Set the key for the current sqlite database instance. - * - * This is the equivalent of the sqlite3_key call and should thus be called - * directly after opening the database. - * Open encrypted database -> call db.key("secret") -> database ready - * - * @param[in] aKey Key to decode/encode the database - * - * @throw SQLite::Exception in case of error - */ -#if __cplusplus >= 201703L // C++17 - void key(const std::string_view aKey) const; -#else - void key(const std::string& aKey) const; -#endif + * @brief Set the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_key call and should thus be called + * directly after opening the database. + * Open encrypted database -> call db.key("secret") -> database ready + * + * @param[in] aKey Key to decode/encode the database + * + * @throw SQLite::Exception in case of error + */ + void key(const char* apKey, const int aSize ) const; /** - * @brief Reset the key for the current sqlite database instance. - * - * This is the equivalent of the sqlite3_rekey call and should thus be called - * after the database has been opened with a valid key. To decrypt a - * database, call this method with an empty string. - * Open normal database -> call db.rekey("secret") -> encrypted database, database ready - * Open encrypted database -> call db.key("secret") -> call db.rekey("newsecret") -> change key, database ready - * Open encrypted database -> call db.key("secret") -> call db.rekey("") -> decrypted database, database ready - * - * @param[in] aNewKey New key to encode the database - * - * @throw SQLite::Exception in case of error - */ -#if __cplusplus >= 201703L // C++17 - void rekey(const std::string_view aNewKey) const; -#else - void rekey(const std::string& aNewKey) const; -#endif + * @brief Set the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_key call and should thus be called + * directly after opening the database. + * Open encrypted database -> call db.key("secret") -> database ready + * + * @param[in] aKey Key to decode/encode the database + * + * @throw SQLite::Exception in case of error + */ + void key(const std::string& aKey) const + { + key(aKey.data(), aKey.size()); + } + + /** + * @brief Reset the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_rekey call and should thus be called + * after the database has been opened with a valid key. To decrypt a + * database, call this method with an empty string. + * Open normal database -> call db.rekey("secret") -> encrypted database, database ready + * Open encrypted database -> call db.key("secret") -> call db.rekey("newsecret") -> change key, database ready + * Open encrypted database -> call db.key("secret") -> call db.rekey("") -> decrypted database, database ready + * + * @param[in] apNewKey New key to encode the database + * @param[in] aSize Length of apNewKey + * + * @throw SQLite::Exception in case of error + */ + void rekey(const char* apNewKey, const int aSize ) const; /** - * @brief Test if a file contains an unencrypted database. - * - * This is a simple test that reads the first bytes of a database file and - * compares them to the standard header for unencrypted databases. If the - * header does not match the standard string, we assume that we have an - * encrypted file. - * - * @param[in] aFilename path/uri to a file - * - * @return true if the database has the standard header. - * - * @throw SQLite::Exception in case of error - */ + * @brief Reset the key for the current sqlite database instance. + * + * This is the equivalent of the sqlite3_rekey call and should thus be called + * after the database has been opened with a valid key. To decrypt a + * database, call this method with an empty string. + * Open normal database -> call db.rekey("secret") -> encrypted database, database ready + * Open encrypted database -> call db.key("secret") -> call db.rekey("newsecret") -> change key, database ready + * Open encrypted database -> call db.key("secret") -> call db.rekey("") -> decrypted database, database ready + * + * @param[in] aNewKey New key to encode the database + * + * @throw SQLite::Exception in case of error + */ + void rekey(const std::string& aNewKey) const + { + rekey(aNewKey.data(), aNewKey.size()); + } + + /** + * @brief Test if a file contains an unencrypted database. + * + * This is a simple test that reads the first bytes of a database file and + * compares them to the standard header for unencrypted databases. If the + * header does not match the standard string, we assume that we have an + * encrypted file. + * + * @param[in] aFilename path/uri to a file + * + * @return true if the database has the standard header. + * + * @throw SQLite::Exception in case of error + */ static bool isUnencrypted(const std::string& aFilename); /** - * @brief Parse SQLite header data from a database file. - * - * This function reads the first 100 bytes of a SQLite database file - * and reconstructs groups of individual bytes into the associated fields - * in a Header object. - * - * @param[in] aFilename path/uri to a file - * - * @return Header object containing file data - * - * @throw SQLite::Exception in case of error - */ + * @brief Parse SQLite header data from a database file. + * + * This function reads the first 100 bytes of a SQLite database file + * and reconstructs groups of individual bytes into the associated fields + * in a Header object. + * + * @param[in] aFilename path/uri to a file + * + * @return Header object containing file data + * + * @throw SQLite::Exception in case of error + */ static Header getHeaderInfo(const std::string& aFilename); // Parse SQLite header data from a database file. diff --git a/src/Database.cpp b/src/Database.cpp index 88d792b3..0b0fdfef 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -224,21 +224,18 @@ void Database::loadExtension(const char* apExtensionName, const char *apEntryPoi } // Set the key for the current sqlite database instance. -#if __cplusplus >= 201703L // C++17 -void Database::key(const std::string_view aKey) const -#else -void Database::key(const std::string& aKey) const -#endif +void Database::key(const char* apKey, const int aSize) const { - int passLen = static_cast(aKey.length()); #ifdef SQLITE_HAS_CODEC - if (passLen > 0) + if (aSize > 0) { - const int ret = sqlite3_key(getHandle(), aKey.data(), passLen); + const int ret = sqlite3_key(getHandle(), apKey, aSize); check(ret); } #else // SQLITE_HAS_CODEC - if (passLen > 0) + // Silence unused parameter warning + static_cast(apKey); + if (aSize > 0) { throw SQLite::Exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable."); } @@ -246,17 +243,12 @@ void Database::key(const std::string& aKey) const } // Reset the key for the current sqlite database instance. -#if __cplusplus >= 201703L // C++17 -void Database::rekey(const std::string_view aNewKey) const -#else -void Database::rekey(const std::string& aNewKey) const -#endif +void Database::rekey(const char* apNewKey, const int aSize) const { #ifdef SQLITE_HAS_CODEC - int passLen = aNewKey.length(); - if (passLen > 0) + if (aSize > 0) { - const int ret = sqlite3_rekey(getHandle(), aNewKey.data(), passLen); + const int ret = sqlite3_rekey(getHandle(), apNewKey, aSize); check(ret); } else @@ -265,7 +257,10 @@ void Database::rekey(const std::string& aNewKey) const check(ret); } #else // SQLITE_HAS_CODEC - static_cast(aNewKey); // silence unused parameter warning + // Silence unused parameter warnings + static_cast(apNewKey); + static_cast(aSize); + throw SQLite::Exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable."); #endif // SQLITE_HAS_CODEC }