Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,12 @@ 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)
- Fix Transaction destructor to catch all exceptions to avoid std::terminate (#559)
- 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)
81 changes: 81 additions & 0 deletions include/SQLiteCpp/Statement.h
Original file line number Diff line number Diff line change
Expand Up @@ -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).
*
Expand All @@ -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().
*/
Expand Down Expand Up @@ -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)
*
Expand Down Expand Up @@ -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().
*/
Expand Down Expand Up @@ -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)
*
Expand Down Expand Up @@ -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().
*/
Expand Down
16 changes: 16 additions & 0 deletions src/Statement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<sqlite3_uint64>(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)
{
Expand All @@ -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<sqlite3_uint64>(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)
{
Expand Down
35 changes: 35 additions & 0 deletions tests/Statement_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint64_t>(sizeof(copyIndex)));
query.bind64(":copyName", copyName, static_cast<uint64_t>(sizeof(copyName)));
query.bind64(std::string(":copyStringName"), copyStringName, static_cast<uint64_t>(sizeof(copyStringName)));
query.bindNoCopy64(4, noCopyIndex, static_cast<uint64_t>(sizeof(noCopyIndex)));
query.bindNoCopy64(std::string(":noCopyName"), noCopyName, static_cast<uint64_t>(sizeof(noCopyName)));

ASSERT_TRUE(query.executeStep());
ASSERT_EQ(static_cast<int>(sizeof(copyIndex)), query.getColumn(0).getBytes());
ASSERT_EQ(static_cast<int>(sizeof(copyName)), query.getColumn(1).getBytes());
ASSERT_EQ(static_cast<int>(sizeof(copyStringName)), query.getColumn(2).getBytes());
ASSERT_EQ(static_cast<int>(sizeof(noCopyIndex)), query.getColumn(3).getBytes());
ASSERT_EQ(static_cast<int>(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<uint64_t>(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
Expand Down
Loading