diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index bf5760ab..a0e80471 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,24 @@ 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 { + // See getString implementation + (void)getBytes(); + auto data = static_cast(getBlob()); + if (data == nullptr) + return {}; + return std::string_view(data, getBytes()); + } +#endif // c++17 /** * @brief Return the type of the value of the column using sqlite3_column_type() @@ -227,6 +248,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..5ac1ba0c 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; @@ -152,6 +155,18 @@ 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 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) + * + * @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) + { + 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) * @@ -185,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) * @@ -252,6 +280,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 +334,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 +434,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 std::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 +488,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) * @@ -860,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/Statement.cpp b/src/Statement.cpp index 68293c8b..6bda2a8b 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -444,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 diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp index f4911397..c1a86ffb 100644 --- a/tests/Column_test.cpp +++ b/tests/Column_test.cpp @@ -75,6 +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*() @@ -97,6 +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)); @@ -112,26 +118,38 @@ 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(); +#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()... 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