diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 5d6a73c0..61322600 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -171,7 +171,16 @@ class Statement * * @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 char* apValue, const int aSize); + /** + * @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 char* apValue) + { + bind(aIndex, apValue, -1); + } /** * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -186,6 +195,14 @@ class 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); + /** + * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * Main usage is with text which might not be null-terminated. + * + * @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, const int aSize); /** * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -193,7 +210,10 @@ class 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); + inline void bindNoCopy(const int aIndex, const char* apValue) + { + bindNoCopy(aIndex, apValue, -1); + } /** * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -262,6 +282,15 @@ class Statement { bind(getIndex(apName), aValue); } + /** + * @brief Bind a text 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 char* apValue, const int aSize) + { + bind(getIndex(apName), apValue, aSize); + } /** * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -269,7 +298,7 @@ class Statement */ void bind(const char* apName, const char* apValue) { - bind(getIndex(apName), apValue); + bind(getIndex(apName), apValue, -1); } /** * @brief Bind a binary blob value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) @@ -291,6 +320,17 @@ class Statement { bindNoCopy(getIndex(apName), aValue); } + /** + * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * Main usage is with text which might not be null-terminated + * + * @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 char* apValue, const int aSize) + { + bindNoCopy(getIndex(apName), apValue, aSize); + } /** * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -300,7 +340,7 @@ class Statement */ void bindNoCopy(const char* apName, const char* apValue) { - bindNoCopy(getIndex(apName), apValue); + bindNoCopy(getIndex(apName), apValue, -1); } /** * @brief Bind a binary blob value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) @@ -384,7 +424,16 @@ class Statement */ void bind(const std::string& aName, const char* apValue) { - bind(aName.c_str(), apValue); + bind(aName.c_str(), apValue, -1); + } + /** + * @brief Bind a text 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 + */ + inline void bind(const std::string& aName, const char* apValue, const int aSize) + { + bind(aName.c_str(), apValue, aSize); } /** * @brief Bind a binary blob value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) @@ -415,7 +464,18 @@ class Statement */ void bindNoCopy(const std::string& aName, const char* apValue) { - bindNoCopy(aName.c_str(), apValue); + bindNoCopy(aName.c_str(), apValue, -1); + } + /** + * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * Main usage is with null-terminated literal text (aka in code static strings) + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + inline void bindNoCopy(const std::string& aName, const char* apValue, const int aSize) + { + bindNoCopy(aName.c_str(), apValue, aSize); } /** * @brief Bind a binary blob value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) @@ -436,6 +496,70 @@ class Statement bind(aName.c_str()); } + // Provide bindings for std::string_view if using at least c++17 +#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 + */ + inline void bind(const int aIndex, std::string_view aValue) + { + bind(aIndex, aValue.data(), static_cast(aValue.size())); + } + /** + * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) + * + * Main usage is with null-terminated literal text (aka in code static strings) + * + * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. + */ + inline void bindNoCopy(const int aIndex, std::string_view aValue) + { + bindNoCopy(aIndex, aValue.data(), static_cast(aValue.size())); + } + /** + * @brief Bind a string_view 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 + */ + inline void bind(const char* apName, std::string_view aValue) + { + bind(apName, aValue.data(), static_cast(aValue.size())); + } + /** + * @brief Bind a string_view 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. + */ + inline void bindNoCopy(const char* apName, std::string_view aValue) + { + bind(apName, aValue.data(), static_cast(aValue.size())); + } + /** + * @brief Bind a string_view 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 + */ + inline void bind(const std::string& aName, std::string_view aValue) + { + bind(aName.c_str(), aValue.data(), static_cast(aValue.size())); + } + /** + * @brief Bind a string_view 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. + */ + inline void bindNoCopy(const std::string& aName, std::string_view aValue) + { + bindNoCopy(aName.c_str(), aValue.data(), static_cast(aValue.size())); + } +#endif + //////////////////////////////////////////////////////////////////////////// /** diff --git a/src/Statement.cpp b/src/Statement.cpp index c981ccb3..45baae18 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -105,9 +105,9 @@ void Statement::bind(const int aIndex, const std::string& aValue) } // 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) +void Statement::bind(const int aIndex, const char* apValue, const int aSize) { - const int ret = sqlite3_bind_text(mStmtPtr, aIndex, apValue, -1, SQLITE_TRANSIENT); + const int ret = sqlite3_bind_text(mStmtPtr, aIndex, apValue, aSize, SQLITE_TRANSIENT); check(ret); } @@ -127,9 +127,9 @@ void Statement::bindNoCopy(const int aIndex, const std::string& aValue) } // 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) +void Statement::bindNoCopy(const int aIndex, const char* apValue, const int aSize) { - const int ret = sqlite3_bind_text(mStmtPtr, aIndex, apValue, -1, SQLITE_STATIC); + const int ret = sqlite3_bind_text(mStmtPtr, aIndex, apValue, aSize, SQLITE_STATIC); check(ret); } diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index b80b35d8..fb7020ca 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -341,7 +341,7 @@ TEST(Statement, bindings) // Fifth row with binary buffer and a null parameter { - const char buffer[] = "binary"; + const unsigned char buffer[] = "binary"; insert.bind(1, buffer, sizeof(buffer)); insert.bind(2); EXPECT_EQ(1, insert.exec()); @@ -351,7 +351,7 @@ TEST(Statement, bindings) EXPECT_TRUE (query.hasRow()); EXPECT_FALSE(query.isDone()); EXPECT_EQ(5, query.getColumn(0).getInt64()); - EXPECT_STREQ(buffer, query.getColumn(1).getText()); + EXPECT_EQ(0, memcmp(buffer, &query.getColumn(1).getString()[0], sizeof(buffer))); EXPECT_TRUE (query.isColumnNull(2)); EXPECT_EQ(0, query.getColumn(2).getInt()); EXPECT_EQ(0.234f, query.getColumn(3).getDouble()); @@ -406,25 +406,26 @@ TEST(Statement, bindNoCopy) 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, binary BLOB)")); + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, txt1 TEXT, txt2 TEXT, txt3 TEXT, binary BLOB)")); EXPECT_EQ(SQLite::OK, db.getErrorCode()); // Insertion with bindable parameters - SQLite::Statement insert(db, "INSERT INTO test VALUES (NULL, ?, ?, ?)"); + 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(4, query.getColumnCount()); + EXPECT_EQ(5, query.getColumnCount()); // Insert one row with all variants of bindNoCopy() { const char* txt1 = "first"; const std::string txt2 = "sec\0nd"; - const char blob[] = {'b','l','\0','b'}; + const unsigned char blob[] = {'b','l','\0','b'}; insert.bindNoCopy(1, txt1); insert.bindNoCopy(2, txt2); - insert.bindNoCopy(3, blob, sizeof(blob)); + insert.bindNoCopy(3, txt2.c_str(), static_cast(txt2.size())); + insert.bindNoCopy(4, blob, sizeof(blob)); EXPECT_EQ(1, insert.exec()); EXPECT_EQ(SQLITE_DONE, db.getErrorCode()); @@ -435,7 +436,8 @@ TEST(Statement, bindNoCopy) EXPECT_EQ(1, query.getColumn(0).getInt64()); EXPECT_STREQ(txt1, query.getColumn(1).getText()); EXPECT_EQ(0, memcmp(&txt2[0], &query.getColumn(2).getString()[0], txt2.size())); - EXPECT_EQ(0, memcmp(blob, &query.getColumn(3).getString()[0], sizeof(blob))); + EXPECT_EQ(0, memcmp(&txt2[0], &query.getColumn(3).getString()[0], txt2.size())); + EXPECT_EQ(0, memcmp(blob, &query.getColumn(4).getString()[0], sizeof(blob))); } } @@ -621,7 +623,7 @@ TEST(Statement, bindByNameString) // Third row with binary buffer and a null parameter { - const char buffer[] = "binary"; + const unsigned char buffer[] = "binary"; insert.bind(amsg, buffer, sizeof(buffer)); insert.bind(aint); EXPECT_EQ(1, insert.exec()); @@ -631,7 +633,7 @@ TEST(Statement, bindByNameString) EXPECT_TRUE(query.hasRow()); EXPECT_FALSE(query.isDone()); EXPECT_EQ(3, query.getColumn(0).getInt64()); - EXPECT_STREQ(buffer, query.getColumn(1).getText()); + EXPECT_EQ(0, memcmp(buffer, query.getColumn(1).getText(), 6)); EXPECT_TRUE(query.isColumnNull(2)); EXPECT_EQ(0, query.getColumn(2).getInt()); EXPECT_EQ(0.234f, query.getColumn(3).getDouble()); @@ -657,6 +659,23 @@ TEST(Statement, bindByNameString) EXPECT_EQ(4294967295U, query.getColumn(2).getUInt()); EXPECT_EQ(12345678900000LL, query.getColumn(4).getInt64()); } + + // reset() without clearbindings() + insert.reset(); + + // Fifth row with string + { + const std::string first = "fir\0st"; + insert.bind(amsg, first.c_str(), static_cast(first.size())); + 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(first, query.getColumn(1).getString()); + } } TEST(Statement, bindNoCopyByName) @@ -709,9 +728,9 @@ TEST(Statement, bindNoCopyByName) const std::string ablob = "@blob"; const char* txt1 = "first2"; const std::string txt2 = "sec\0nd2"; - const char blob[] = { 'b','l','\0','b','2' }; + const unsigned char blob[] = { 'b','l','\0','b','2' }; insert.bindNoCopy(atxt1, txt1); - insert.bindNoCopy(atxt2, txt2); + insert.bindNoCopy(atxt2, txt2.c_str(), static_cast(txt2.size())); insert.bindNoCopy(ablob, blob, sizeof(blob)); EXPECT_EQ(1, insert.exec()); EXPECT_EQ(2, db.getLastInsertRowid()); @@ -727,6 +746,28 @@ TEST(Statement, bindNoCopyByName) EXPECT_EQ(0, memcmp(&txt2[0], &query.getColumn(2).getString()[0], txt2.size())); EXPECT_EQ(0, memcmp(blob, &query.getColumn(3).getString()[0], sizeof(blob))); } + + insert.reset(); + insert.clearBindings(); + query.reset(); + + // Insert a third row with some more variants of bindNoCopy() using std::string names + { + const std::string atxt1 = "@txt1"; + const std::string txt1 = "fir\0st"; + insert.bindNoCopy(atxt1, txt1); + EXPECT_EQ(1, insert.exec()); + EXPECT_EQ(3, db.getLastInsertRowid()); + EXPECT_EQ(SQLITE_DONE, db.getErrorCode()); + + // Check the result + query.executeStep(); // pass on the first row + query.executeStep(); // pass on the second row as well + query.executeStep(); + EXPECT_TRUE(query.hasRow()); + EXPECT_FALSE(query.isDone()); + EXPECT_EQ(txt1, query.getColumn(1).getString()); + } } TEST(Statement, isColumnNull)