diff --git a/CHANGELOG.md b/CHANGELOG.md index 552edd30..3c0864c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -307,7 +307,6 @@ Version 3.4.0 - 2026 ??? - Fix Database::isUnencrypted() to compare the full 16-byte header in binary mode (#553) - Fix execute_many() to clear stale bindings between parameter sets (#554) - Fix Column::operator<< to stream the exact column bytes via getString() (#556) -- Fix large `std::string` binding sizes (#563) - Restore the Coverity Scan static analysis as a GitHub Actions workflow, replacing the old Travis CI job - Fix Database::getHeaderInfo() signed-shift UB and use fixed-width types for the Header struct (#558) - Fix Savepoint destructor to catch all exceptions and track rollback state to avoid std::terminate (#559) @@ -315,3 +314,5 @@ Version 3.4.0 - 2026 ??? - Fix the Meson build when the SQLITECPP_DISABLE_STD_FILESYSTEM option is enabled (#560) - Add Statement::RowIterator to support range-based for loops over query results (#181) - Add unit tests for Statement::RowIterator to fix coverage regression (#562) +- Fix large `std::string` binding sizes (#563) +- Add 64-bit BLOB binding methods (#564) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 61fccb95..e69930bf 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -158,6 +158,17 @@ 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); + /** + * @brief Bind a binary blob using a 64-bit size. + * + * @param[in] aIndex Index of the parameter to bind (aIndex >= 1) + * @param[in] apValue Pointer to the binary data + * @param[in] aSize Size of the binary data in bytes + * @throw SQLite::Exception in case of error + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bind64(const int aIndex, const void* apValue, const uint64_t aSize); /** * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). * @@ -180,6 +191,18 @@ 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 void* apValue, const int aSize); + /** + * @brief Bind a binary blob using a 64-bit size without copying it. + * + * @param[in] aIndex Index of the parameter to bind (aIndex >= 1) + * @param[in] apValue Pointer to the binary data + * @param[in] aSize Size of the binary data in bytes + * @throw SQLite::Exception in case of error + * + * @warning Uses the SQLITE_STATIC flag. The data must remain valid and unchanged until the parameter is + * rebound or the statement is finalized. Resetting the statement does not clear the binding. + */ + void bindNoCopy64(const int aIndex, const void* apValue, const uint64_t aSize); /** * @brief Deleted, because the value's lifetime could not be guaranteed. Use bind(). */ @@ -247,6 +270,20 @@ class SQLITECPP_API Statement { bind(getIndex(apName), apValue, aSize); } + /** + * @brief Bind a binary blob to a named parameter using a 64-bit size. + * + * @param[in] apName Name of the parameter to bind + * @param[in] apValue Pointer to the binary data + * @param[in] aSize Size of the binary data in bytes + * @throw SQLite::Exception in case of error + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bind64(const char* apName, const void* apValue, const uint64_t aSize) + { + bind64(getIndex(apName), apValue, aSize); + } /** * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -278,6 +315,21 @@ class SQLITECPP_API Statement { bindNoCopy(getIndex(apName), apValue, aSize); } + /** + * @brief Bind a binary blob to a named parameter using a 64-bit size without copying it. + * + * @param[in] apName Name of the parameter to bind + * @param[in] apValue Pointer to the binary data + * @param[in] aSize Size of the binary data in bytes + * @throw SQLite::Exception in case of error + * + * @warning Uses the SQLITE_STATIC flag. The data must remain valid and unchanged until the parameter is + * rebound or the statement is finalized. Resetting the statement does not clear the binding. + */ + void bindNoCopy64(const char* apName, const void* apValue, const uint64_t aSize) + { + bindNoCopy64(getIndex(apName), apValue, aSize); + } /** * @brief Deleted, because the value's lifetime could not be guaranteed. Use bind(). */ @@ -348,6 +400,20 @@ class SQLITECPP_API Statement { bind(aName.c_str(), apValue, aSize); } + /** + * @brief Bind a binary blob to a named parameter using a 64-bit size. + * + * @param[in] aName Name of the parameter to bind + * @param[in] apValue Pointer to the binary data + * @param[in] aSize Size of the binary data in bytes + * @throw SQLite::Exception in case of error + * + * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use + */ + void bind64(const std::string& aName, const void* apValue, const uint64_t aSize) + { + bind64(aName.c_str(), apValue, aSize); + } /** * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -379,6 +445,21 @@ class SQLITECPP_API Statement { bindNoCopy(aName.c_str(), apValue, aSize); } + /** + * @brief Bind a binary blob to a named parameter using a 64-bit size without copying it. + * + * @param[in] aName Name of the parameter to bind + * @param[in] apValue Pointer to the binary data + * @param[in] aSize Size of the binary data in bytes + * @throw SQLite::Exception in case of error + * + * @warning Uses the SQLITE_STATIC flag. The data must remain valid and unchanged until the parameter is + * rebound or the statement is finalized. Resetting the statement does not clear the binding. + */ + void bindNoCopy64(const std::string& aName, const void* apValue, const uint64_t aSize) + { + bindNoCopy64(aName.c_str(), apValue, aSize); + } /** * @brief Deleted, because the value's lifetime could not be guaranteed. Use bind(). */ diff --git a/src/Statement.cpp b/src/Statement.cpp index 69f3ec8b..68293c8b 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -130,6 +130,14 @@ void Statement::bind(const int aIndex, const void* apValue, const int aSize) check(ret); } +// Bind a binary blob using a 64-bit size and SQLITE_TRANSIENT +void Statement::bind64(const int aIndex, const void* apValue, const uint64_t aSize) +{ + const int ret = sqlite3_bind_blob64(getPreparedStatement(), aIndex, apValue, + static_cast(aSize), SQLITE_TRANSIENT); + check(ret); +} + // 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) { @@ -152,6 +160,14 @@ void Statement::bindNoCopy(const int aIndex, const void* apValue, const int aSiz check(ret); } +// Bind a binary blob using a 64-bit size and SQLITE_STATIC +void Statement::bindNoCopy64(const int aIndex, const void* apValue, const uint64_t aSize) +{ + const int ret = sqlite3_bind_blob64(getPreparedStatement(), aIndex, apValue, + static_cast(aSize), SQLITE_STATIC); + check(ret); +} + // Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex) { diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 94f1376d..78927d57 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -455,6 +455,41 @@ TEST(Statement, bindNoCopy) } } +TEST(Statement, bind64) +{ + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + SQLite::Statement query(db, "SELECT :copyIndex, :copyName, :copyStringName, :noCopyIndex, :noCopyName"); + + const char copyIndex[] = {'c', '\0', 'i'}; + const char copyName[] = {'c', '\0', 'n'}; + const char copyStringName[] = {'c', '\0', 's'}; + const char noCopyIndex[] = {'n', '\0', 'i'}; + const char noCopyName[] = {'n', '\0', 'n'}; + + query.bind64(1, copyIndex, static_cast(sizeof(copyIndex))); + query.bind64(":copyName", copyName, static_cast(sizeof(copyName))); + query.bind64(std::string(":copyStringName"), copyStringName, static_cast(sizeof(copyStringName))); + query.bindNoCopy64(4, noCopyIndex, static_cast(sizeof(noCopyIndex))); + query.bindNoCopy64(std::string(":noCopyName"), noCopyName, static_cast(sizeof(noCopyName))); + + ASSERT_TRUE(query.executeStep()); + ASSERT_EQ(static_cast(sizeof(copyIndex)), query.getColumn(0).getBytes()); + ASSERT_EQ(static_cast(sizeof(copyName)), query.getColumn(1).getBytes()); + ASSERT_EQ(static_cast(sizeof(copyStringName)), query.getColumn(2).getBytes()); + ASSERT_EQ(static_cast(sizeof(noCopyIndex)), query.getColumn(3).getBytes()); + ASSERT_EQ(static_cast(sizeof(noCopyName)), query.getColumn(4).getBytes()); + EXPECT_EQ(0, memcmp(copyIndex, query.getColumn(0).getBlob(), sizeof(copyIndex))); + EXPECT_EQ(0, memcmp(copyName, query.getColumn(1).getBlob(), sizeof(copyName))); + EXPECT_EQ(0, memcmp(copyStringName, query.getColumn(2).getBlob(), sizeof(copyStringName))); + EXPECT_EQ(0, memcmp(noCopyIndex, query.getColumn(3).getBlob(), sizeof(noCopyIndex))); + EXPECT_EQ(0, memcmp(noCopyName, query.getColumn(4).getBlob(), sizeof(noCopyName))); + + SQLite::Statement oversized(db, "SELECT ?"); + const uint64_t oversizedLength = static_cast(INT_MAX) + 1U; + EXPECT_THROW(oversized.bind64(1, copyIndex, oversizedLength), SQLite::Exception); + EXPECT_THROW(oversized.bindNoCopy64(1, noCopyIndex, oversizedLength), SQLite::Exception); +} + TEST(Statement, bindByName) { // Create a new database