From 8b71316b12825720df9d25974a3f8811dc63e804 Mon Sep 17 00:00:00 2001 From: Doug Nazar Date: Sun, 4 Dec 2022 23:43:31 -0500 Subject: [PATCH 1/4] Add support to create Statements with various unicode encodings. Support for char16_t, u16string & u16string_view. Support for char8_t, u8string & u8string_view. Support for wchar_t, wstring & wstring_view (on 2-byte platforms). --- include/SQLiteCpp/Statement.h | 123 ++++++++++++++++++++++++++++++++++ src/Statement.cpp | 62 +++++++++++++++++ tests/Statement_test.cpp | 39 +++++++++++ 3 files changed, 224 insertions(+) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index e14fed09..6ff91085 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -16,6 +16,7 @@ #include #include #include +#include // Forward declarations to avoid inclusion of in a header struct sqlite3; @@ -74,6 +75,110 @@ class Statement Statement(aDatabase, aQuery.c_str()) {} +#ifdef __cpp_unicode_characters + /** + * @brief Compile and register the SQL query for the provided SQLite Database Connection + * + * @param[in] aDatabase the SQLite Database Connection + * @param[in] aQuery an UTF-16 encoded query string + * + * Exception is thrown in case of error, then the Statement object is NOT constructed. + */ + Statement(const Database& aDatabase, const std::u16string& aQuery); + +#if WCHAR_MAX == 0xffff + /** + * @brief Compile and register the SQL query for the provided SQLite Database Connection + * + * @param[in] aDatabase the SQLite Database Connection + * @param[in] aQuery an UTF-16 encoded query string + * + * Exception is thrown in case of error, then the Statement object is NOT constructed. + */ + Statement(const Database& aDatabase, const std::wstring& aQuery) : +#ifdef __cpp_lib_string_view + Statement(aDatabase, std::u16string_view(reinterpret_cast(aQuery.data()), aQuery.size())) +#else + Statement(aDatabase, std::u16string(reinterpret_cast(aQuery.data()), aQuery.size())) +#endif + {} +#endif // WCHAR_MAX == 0xffff +#endif // __cpp_unicode_characters + +#ifdef __cpp_lib_string_view + /** + * @brief Compile and register the SQL query for the provided SQLite Database Connection + * + * @param[in] aDatabase the SQLite Database Connection + * @param[in] aQuery an UTF-16 encoded query string + * + * Exception is thrown in case of error, then the Statement object is NOT constructed. + */ + Statement(const Database& aDatabase, const std::string_view aQuery); + +#ifdef __cpp_char8_t + /** + * @brief Compile and register the SQL query for the provided SQLite Database Connection + * + * @param[in] aDatabase the SQLite Database Connection + * @param[in] aQuery an UTF-8 encoded query string + * + * Exception is thrown in case of error, then the Statement object is NOT constructed. + */ + Statement(const Database& aDatabase, const std::u8string_view aQuery) : + Statement(aDatabase, std::string_view(reinterpret_cast(aQuery.data()), aQuery.size())) + {} +#endif // __cpp_char8_t + + /** + * @brief Compile and register the SQL query for the provided SQLite Database Connection + * + * @param[in] aDatabase the SQLite Database Connection + * @param[in] apQuery an UTF-16 encoded query string + * + * Exception is thrown in case of error, then the Statement object is NOT constructed. + */ + Statement(const Database& aDatabase, const char16_t* apQuery) : + Statement(aDatabase, std::u16string_view(apQuery)) + {} + + /** + * @brief Compile and register the SQL query for the provided SQLite Database Connection + * + * @param[in] aDatabase the SQLite Database Connection + * @param[in] aQuery an UTF-16 encoded query string + * + * Exception is thrown in case of error, then the Statement object is NOT constructed. + */ + Statement(const Database& aDatabase, std::u16string_view aQuery); + +#if WCHAR_MAX == 0xffff + /** + * @brief Compile and register the SQL query for the provided SQLite Database Connection + * + * @param[in] aDatabase the SQLite Database Connection + * @param[in] apQuery an UTF-16 encoded query string + * + * Exception is thrown in case of error, then the Statement object is NOT constructed. + */ + Statement(const Database& aDatabase, const wchar_t* apQuery) : + Statement(aDatabase, std::u16string_view(reinterpret_cast(apQuery))) + {} + + /** + * @brief Compile and register the SQL query for the provided SQLite Database Connection + * + * @param[in] aDatabase the SQLite Database Connection + * @param[in] aQuery an UTF-16 encoded query string + * + * Exception is thrown in case of error, then the Statement object is NOT constructed. + */ + Statement(const Database& aDatabase, const std::wstring_view aQuery) : + Statement(aDatabase, std::u16string_view(reinterpret_cast(aQuery.data()), aQuery.size())) + {} +#endif // WCHAR_MAX == 0xffff +#endif // __cpp_lib_string_view + // Statement is non-copyable Statement(const Statement&) = delete; Statement& operator=(const Statement&) = delete; @@ -690,6 +795,24 @@ class Statement */ TStatementPtr prepareStatement(); +#ifdef __cpp_unicode_characters + /** + * @brief Prepare statement object. + * + * @return Shared pointer to prepared statement object + */ + TStatementPtr prepareStatement(const std::u16string& query); +#endif // __cpp_unicode_characters + +#ifdef __cpp_lib_string_view + /** + * @brief Prepare statement object. + * + * @return Shared pointer to prepared statement object + */ + TStatementPtr prepareStatement(const std::u16string_view query); +#endif // __cpp_lib_string_view + /** * @brief Return a prepared statement object. * diff --git a/src/Statement.cpp b/src/Statement.cpp index caaf4d38..b746a371 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -39,6 +39,32 @@ Statement::Statement(const Database& aDatabase, const char* apQuery) : mColumnCount = sqlite3_column_count(mpPreparedStatement.get()); } +#ifdef __cpp_unicode_characters +Statement::Statement(const Database& aDatabase, const std::u16string& aQuery) : + mpSQLite(aDatabase.getHandle()), + mpPreparedStatement(prepareStatement(aQuery)) // prepare the SQL query (needs Database friendship) +{ + mColumnCount = sqlite3_column_count(mpPreparedStatement.get()); +} +#endif // __cpp_unicode_characters + +#ifdef __cpp_lib_string_view +Statement::Statement(const Database& aDatabase, const std::string_view aQuery) : + mQuery(aQuery), + mpSQLite(aDatabase.getHandle()), + mpPreparedStatement(prepareStatement()) // prepare the SQL query (needs Database friendship) +{ + mColumnCount = sqlite3_column_count(mpPreparedStatement.get()); +} + +Statement::Statement(const Database& aDatabase, const std::u16string_view aQuery) : + mpSQLite(aDatabase.getHandle()), + mpPreparedStatement(prepareStatement(aQuery)) // prepare the SQL query (needs Database friendship) +{ + mColumnCount = sqlite3_column_count(mpPreparedStatement.get()); +} +#endif // __cpp_lib_string_view + Statement::Statement(Statement&& aStatement) noexcept : mQuery(std::move(aStatement.mQuery)), mpSQLite(aStatement.mpSQLite), @@ -371,6 +397,42 @@ Statement::TStatementPtr Statement::prepareStatement() }); } +#ifdef __cpp_unicode_characters +Statement::TStatementPtr Statement::prepareStatement(const std::u16string& query) +{ + sqlite3_stmt* statement; + const int ret = sqlite3_prepare16_v2(mpSQLite, query.data(), + static_cast(query.size() * sizeof(char16_t)), &statement, nullptr); + if (SQLITE_OK != ret) + { + throw SQLite::Exception(mpSQLite, ret); + } + mQuery = sqlite3_sql(statement); + return Statement::TStatementPtr(statement, [](sqlite3_stmt* stmt) + { + sqlite3_finalize(stmt); + }); +} +#endif // __cpp_unicode_characters + +#ifdef __cpp_lib_string_view +Statement::TStatementPtr Statement::prepareStatement(const std::u16string_view query) +{ + sqlite3_stmt* statement; + const int ret = sqlite3_prepare16_v2(mpSQLite, query.data(), + static_cast(query.size() * sizeof(char16_t)), &statement, nullptr); + if (SQLITE_OK != ret) + { + throw SQLite::Exception(mpSQLite, ret); + } + mQuery = sqlite3_sql(statement); + return Statement::TStatementPtr(statement, [](sqlite3_stmt* stmt) + { + sqlite3_finalize(stmt); + }); +} +#endif // __cpp_lib_string_view + // Return prepered statement object or throw sqlite3_stmt* Statement::getPreparedStatement() const { diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 0976ce18..1237c79e 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -142,6 +142,45 @@ TEST(Statement, moveConstructor) #endif +#ifdef __cpp_unicode_characters +TEST(Statement, unicode) +{ + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); + + const std::string sql("SELECT * FROM test"); + const std::u16string sql_u16(u"SELECT * FROM test"); + + SQLite::Statement query_u16_char(db, sql_u16.c_str()); + EXPECT_EQ(sql, query_u16_char.getQuery()); + + SQLite::Statement query_u16_str(db, sql_u16); + EXPECT_EQ(sql, query_u16_str.getQuery()); + + EXPECT_THROW(SQLite::Statement(db, u"SELECT * FROM test2"), SQLite::Exception); + +#if WCHAR_MAX == 0xffff + const std::wstring sql_w(L"SELECT * FROM test"); + + SQLite::Statement query_w_char(db, sql_w.c_str()); + EXPECT_EQ(sql, query_w_char.getQuery()); + SQLite::Statement query_w_str(db, sql_w); + EXPECT_EQ(sql, query_w_str.getQuery()); +#endif + +#ifdef __cpp_char8_t + const std::u8string sql_u8(u8"SELECT * FROM test"); + + SQLite::Statement query_u8_char(db, sql_u8.c_str()); + EXPECT_EQ(sql, query_u8_char.getQuery()); + SQLite::Statement query_u8_str(db, sql_u8); + EXPECT_EQ(sql, query_u8_str.getQuery()); + EXPECT_THROW(SQLite::Statement(db, u8"SELECT * FROM test2"), SQLite::Exception); +#endif +} +#endif + TEST(Statement, executeStep) { // Create a new database From 883782f0c54b8b39929c7d3d88d39fee105a5aac Mon Sep 17 00:00:00 2001 From: Doug Nazar Date: Mon, 5 Dec 2022 15:37:01 -0500 Subject: [PATCH 2/4] Add support to bind parameters with various unicode encodings. Support for char16_t, u16string & u16string_view. Support for char8_t, u8string & u8string_view. Support for wchar_t, wstring & wstring_view (on 2-byte platforms). --- include/SQLiteCpp/Statement.h | 188 ++++++++++++++++++++++++++++++++++ src/Statement.cpp | 50 +++++++++ tests/Statement_test.cpp | 154 ++++++++++++++++++++++++++++ 3 files changed, 392 insertions(+) diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 6ff91085..2400e59b 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -256,6 +256,83 @@ 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); +#ifdef __cpp_unicode_characters + /** + * @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::u16string& aValue); +#if WCHAR_MAX == 0xffff + /** + * @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::wstring& aValue) + { +#ifdef __cpp_lib_string_view + bind(aIndex, std::u16string_view(reinterpret_cast(aValue.data()), aValue.size())); +#else + bind(aIndex, std::u16string(reinterpret_cast(aValue.data()), aValue.size())); +#endif + } +#endif +#endif +#ifdef __cpp_lib_string_view + /** + * @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); +#ifdef __cpp_char8_t + /** + * @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::u8string_view aValue) + { + bind(aIndex, std::string_view(reinterpret_cast(aValue.data()), aValue.size())); + } +#endif + /** + * @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::u16string_view aValue); + /** + * @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 char16_t* aValue) + { + bind(aIndex, std::u16string_view(aValue)); + } +#if WCHAR_MAX == 0xffff + /** + * @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 wchar_t* aValue) + { + bind(aIndex, std::u16string_view(reinterpret_cast(aValue))); + } + /** + * @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::wstring_view aValue) + { + bind(aIndex, std::u16string_view(reinterpret_cast(aValue.data()), aValue.size())); + } +#endif +#endif /** * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -278,6 +355,95 @@ 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); +#ifdef __cpp_unicode_characters + /** + * @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 bindNoCopy(const int aIndex, const std::u16string& aValue); +#if WCHAR_MAX == 0xffff + /** + * @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 bindNoCopy(const int aIndex, const std::wstring& aValue) + { +#if __cpp_lib_string_view + bindNoCopy(aIndex, std::u16string_view(reinterpret_cast(aValue.data()), aValue.size())); +#else + bindNoCopy(aIndex, std::u16string(reinterpret_cast(aValue.data()), aValue.size())); +#endif + } +#endif +#endif +#if __cpp_lib_string_view + /** + * @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); +#ifdef __cpp_char8_t + /** + * @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::u8string_view aValue) + { + bindNoCopy(aIndex, std::string_view(reinterpret_cast(aValue.data()), aValue.size())); + } +#endif + /** + * @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::u16string_view aValue); + /** + * @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 char16_t* aValue) + { + bindNoCopy(aIndex, std::u16string_view(aValue)); + } +#if WCHAR_MAX == 0xffff + /** + * @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 wchar_t* aValue) + { + bindNoCopy(aIndex, std::u16string_view(reinterpret_cast(aValue))); + } + /** + * @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::wstring_view aValue) + { + bindNoCopy(aIndex, std::u16string_view(reinterpret_cast(aValue.data()), aValue.size())); + } +#endif +#endif /** * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) * @@ -291,6 +457,28 @@ class Statement */ void bind(const int aIndex); + template + inline void bind(const char* apName, T&& aValue) + { + bind(getIndex(apName), std::forward(aValue)); + } + template + inline void bindNoCopy(const char* apName, T&& aValue) + { + bindNoCopy(getIndex(apName), std::forward(aValue)); + } + template + inline void bind(const std::string& aName, T&& aValue) + { + bind(getIndex(aName.c_str()), std::forward(aValue)); + } + template + inline void bindNoCopy(const std::string& aName, T&& aValue) + { + bindNoCopy(getIndex(aName.c_str()), std::forward(aValue)); + } + + /** * @brief Bind an int 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 b746a371..3d5fcc8c 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -149,6 +149,31 @@ void Statement::bind(const int aIndex, const char* apValue) check(ret); } +#ifdef __cpp_unicode_characters +void Statement::bind(const int aIndex, const std::u16string& aValue) +{ + const int ret = sqlite3_bind_text16(getPreparedStatement(), aIndex, aValue.data(), + static_cast(aValue.size() * sizeof(char16_t)), SQLITE_TRANSIENT); + check(ret); +} +#endif + +#ifdef __cpp_lib_string_view +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); +} + +void Statement::bind(const int aIndex, const std::u16string_view aValue) +{ + const int ret = sqlite3_bind_text16(getPreparedStatement(), aIndex, aValue.data(), + static_cast(aValue.size() * sizeof(char16_t)), SQLITE_TRANSIENT); + check(ret); +} +#endif + // Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bind(const int aIndex, const void* apValue, const int aSize) { @@ -171,6 +196,31 @@ void Statement::bindNoCopy(const int aIndex, const char* apValue) check(ret); } +#ifdef __cpp_unicode_characters +void Statement::bindNoCopy(const int aIndex, const std::u16string& aValue) +{ + const int ret = sqlite3_bind_text16(getPreparedStatement(), aIndex, aValue.data(), + static_cast(aValue.size() * sizeof(char16_t)), SQLITE_STATIC); + check(ret); +} +#endif + +#ifdef __cpp_lib_string_view +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); +} + +void Statement::bindNoCopy(const int aIndex, const std::u16string_view aValue) +{ + const int ret = sqlite3_bind_text16(getPreparedStatement(), aIndex, aValue.data(), + static_cast(aValue.size() * sizeof(char16_t)), SQLITE_STATIC); + check(ret); +} +#endif + // Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement void Statement::bindNoCopy(const int aIndex, const void* apValue, const int aSize) { diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index 1237c79e..b6b1f01d 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -159,6 +159,7 @@ TEST(Statement, unicode) EXPECT_EQ(sql, query_u16_str.getQuery()); EXPECT_THROW(SQLite::Statement(db, u"SELECT * FROM test2"), SQLite::Exception); + EXPECT_THROW(SQLite::Statement(db, std::u16string(u"SELECT * FROM test2")), SQLite::Exception); #if WCHAR_MAX == 0xffff const std::wstring sql_w(L"SELECT * FROM test"); @@ -446,6 +447,110 @@ TEST(Statement, bindings) EXPECT_EQ(7, query.getColumn(0).getInt64()); EXPECT_EQ(12345678900000LL, query.getColumn(2).getInt64()); } + +#ifdef __cpp_unicode_characters + // reset() without clearbindings() + insert.reset(); + + // Eighth row using UTF-16 text + { + insert.bind(1, u"u16 text"); + 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(8, query.getColumn(0).getInt64()); + EXPECT_STREQ("u16 text", query.getColumn(1).getText()); + } + + // reset() without clearbindings() + insert.reset(); + + // Nineth row using UTF-16 string + { + insert.bind(1, std::u16string(u"u16 string")); + 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(9, query.getColumn(0).getInt64()); + EXPECT_STREQ("u16 string", query.getColumn(1).getText()); + } + +#if WCHAR_MAX == 0xffff + // reset() without clearbindings() + insert.reset(); + + // wchar_t text + { + insert.bind(1, L"w text"); + 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_STREQ("w text", query.getColumn(1).getText()); + } + + // reset() without clearbindings() + insert.reset(); + + // wstring + { + insert.bind(1, std::wstring(L"w string")); + 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_STREQ("w string", query.getColumn(1).getText()); + } +#endif +#endif + +#ifdef __cpp_char8_t + // reset() without clearbindings() + insert.reset(); + + // u8 text + { + insert.bind(1, u8"u8 text"); + 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_STREQ("u8 text", query.getColumn(1).getText()); + } + + // reset() without clearbindings() + insert.reset(); + + // u8 string + { + insert.bind(1, std::u8string(u8"u8 string")); + 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_STREQ("u8 string", query.getColumn(1).getText()); + } +#endif } TEST(Statement, bindNoCopy) @@ -776,6 +881,55 @@ 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))); } + +#ifdef __cpp_unicode_characters + insert.reset(); + + // Insert a third row using u16 strings. + { + const std::string atxt1 = "@txt1"; + const std::string atxt2 = "@txt2"; + const auto txt1 = u"first3"; + const std::u16string txt2 = u"sec\0nd3"; + insert.bindNoCopy(atxt1, txt1); + insert.bindNoCopy(atxt2, txt2); + EXPECT_EQ(1, insert.exec()); + EXPECT_EQ(3, db.getLastInsertRowid()); + EXPECT_EQ(SQLITE_DONE, db.getErrorCode()); + + // Check the result + query.executeStep(); + EXPECT_TRUE(query.hasRow()); + EXPECT_FALSE(query.isDone()); + EXPECT_EQ(3, query.getColumn(0).getInt64()); + EXPECT_STREQ("first3", query.getColumn(1).getText()); +// EXPECT_EQ(0, memcmp(&txt2[0], &query.getColumn(2).getString()[0], txt2.size())); + } +#endif +#ifdef __cpp_char8_t + insert.reset(); + + // Insert a forth row using u8 strings. + { + const std::string atxt1 = "@txt1"; + const std::string atxt2 = "@txt2"; + const auto txt1 = u8"first4"; + const std::u8string txt2 = u8"sec\0nd4"; + insert.bindNoCopy(atxt1, txt1); + insert.bindNoCopy(atxt2, txt2); + EXPECT_EQ(1, insert.exec()); + EXPECT_EQ(4, db.getLastInsertRowid()); + EXPECT_EQ(SQLITE_DONE, db.getErrorCode()); + + // Check the result + query.executeStep(); + EXPECT_TRUE(query.hasRow()); + EXPECT_FALSE(query.isDone()); + EXPECT_EQ(4, query.getColumn(0).getInt64()); + EXPECT_STREQ("first4", query.getColumn(1).getText()); + EXPECT_EQ(0, memcmp(&txt2[0], &query.getColumn(2).getString()[0], txt2.size())); + } +#endif } TEST(Statement, isColumnNull) From 02410d4302dbe04c6b4b2c384f7af71d6b1f4ba6 Mon Sep 17 00:00:00 2001 From: Doug Nazar Date: Mon, 5 Dec 2022 20:00:44 -0500 Subject: [PATCH 3/4] Add support to column values with various unicode encodings. Support for char16_t, u16string & u16string_view. Support for char8_t, u8string & u8string_view. Support for wchar_t, wstring & wstring_view (on 2-byte platforms). --- include/SQLiteCpp/Column.h | 128 ++++++++++++++++++++++++++++++++++++- src/Column.cpp | 53 ++++++++++++++- tests/Column_test.cpp | 53 +++++++++++++++ 3 files changed, 232 insertions(+), 2 deletions(-) diff --git a/include/SQLiteCpp/Column.h b/include/SQLiteCpp/Column.h index 8fee0965..8ad538ea 100644 --- a/include/SQLiteCpp/Column.h +++ b/include/SQLiteCpp/Column.h @@ -102,6 +102,49 @@ class Column */ std::string getString() const; +#ifdef __cpp_unicode_characters + /** + * @brief Return a pointer to the text value (NULL terminated UTF-16 string) of the column. + * + * @warning The value pointed at is only valid while the statement is valid (ie. not finalized), + * thus you must copy it before using it beyond its scope (to a std::u16string for instance). + */ + const char16_t* getU16Text(const char16_t* apDefaultValue = u"") const noexcept; + /** + * @brief Return a std::u16string for a TEXT column. + * + * Note this correctly handles strings that contain null bytes. + */ + std::u16string getU16String() const; +#if WCHAR_MAX == 0xffff + /** + * @brief Return a pointer to the text value (NULL terminated UTF-16 string) of the column. + * + * @warning The value pointed at is only valid while the statement is valid (ie. not finalized), + * thus you must copy it before using it beyond its scope (to a std::wstring for instance). + */ + const wchar_t* getWText(const wchar_t* apDefaultValue = L"") const noexcept; + /** + * @brief Return a std::wstring for a TEXT column. + */ + std::wstring getWString() const; +#endif // WCHAR_MAX == 0xffff +#endif // __cpp_unicode_characters +#ifdef __cpp_char8_t + /** + * @brief Return a pointer to the text value (NULL terminated UTF-8 string) of the column. + * + * @warning The value pointed at is only valid while the statement is valid (ie. not finalized), + * thus you must copy it before using it beyond its scope (to a std::u8string for instance). + */ + const char8_t* getU8Text(const char8_t* apDefaultValue = u8"") const noexcept; + /** + * @brief Return a std::u8string for a TEXT or BLOB column. + * + * Note this correctly handles strings that contain null bytes. + */ + std::u8string getU8String() const; +#endif // __cpp_char8_t /** * @brief Return the type of the value of the column using sqlite3_column_type() * @@ -142,7 +185,10 @@ class Column } /** - * @brief Return the number of bytes used by the text (or blob) value of the column + * @brief Return the number of bytes used by the UTF-8 text (or blob) value of the column + * + * Can cause conversion to text and between UTF-8/UTF-16 encodings + * Be careful when using with getBytes16() and UTF-16 functions * * Return either : * - size in bytes (not in characters) of the string returned by getText() without the '\0' terminator @@ -152,6 +198,19 @@ class Column */ int getBytes() const noexcept; + /** + * @brief Return the number of bytes used by the UTF-16 text value of the column + * + * Can cause conversion to text and between UTF-8/UTF-16 encodings + * Be careful when using with getBytes() and UTF-8 functions + * + * Return either : + * - size in bytes (not in characters) of the string returned by getText() without the '\0' terminator + * - size in bytes of the string representation of the numerical value (integer or double) + * - 0 for a NULL value + */ + int getBytes16() const noexcept; + /// Alias returning the number of bytes used by the text (or blob) value of the column int size() const noexcept { @@ -226,6 +285,73 @@ class Column return getString(); } +#ifdef __cpp_unicode_characters + /** + * @brief Inline cast operator to char16_t* + * + * @see getU16String + */ + operator const char16_t* () const + { + return getU16Text(); + } + /** + * @brief Inline cast operator to std::u16string + * + * Handles UTF-16 TEXT + * + * @see getU16String + */ + operator std::u16string() const + { + return getU16String(); + } +#if WCHAR_MAX == 0xffff + /** + * @brief Inline cast operator to wchar_t* + * + * @see getWText + */ + operator const wchar_t* () const + { + return getWText(); + } + /** + * @brief Inline cast operator to std::wstring + * + * Handles UTF-16 TEXT + * + * @see getWString + */ + operator std::wstring() const + { + return getWString(); + } +#endif // WCHAR_MAX == 0xffff +#endif // __cpp_unicode_characters +#ifdef __cpp_char8_t + /** + * @brief Inline cast operator to char8_t* + * + * @see getU8Text + */ + operator const char8_t* () const + { + return getU8Text(); + } + /** + * @brief Inline cast operator to std::u8string + * + * Handles BLOB or TEXT, which may contain null bytes within + * + * @see getU8String + */ + operator std::u8string() const + { + return getU8String(); + } +#endif // __cpp_char8_t + 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/src/Column.cpp b/src/Column.cpp index 60b3c3b2..91bea83c 100644 --- a/src/Column.cpp +++ b/src/Column.cpp @@ -102,18 +102,69 @@ std::string Column::getString() const return std::string(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); } +#ifdef __cpp_unicode_characters +const char16_t* Column::getU16Text(const char16_t* apDefaultValue /* = u"" */) const noexcept +{ + auto pText = static_cast(sqlite3_column_text16(mStmtPtr.get(), mIndex)); + return (pText ? pText : apDefaultValue); +} +std::u16string Column::getU16String() const +{ + (void)sqlite3_column_bytes16(mStmtPtr.get(), mIndex); + auto data = static_cast(sqlite3_column_blob(mStmtPtr.get(), mIndex)); + return std::u16string(data, sqlite3_column_bytes16(mStmtPtr.get(), mIndex) / sizeof(char16_t)); +} +#if WCHAR_MAX == 0xffff +const wchar_t* Column::getWText(const wchar_t* apDefaultValue /* = L"" */) const noexcept +{ + auto pText = static_cast(sqlite3_column_text16(mStmtPtr.get(), mIndex)); + return (pText ? pText : apDefaultValue); +} +std::wstring Column::getWString() const +{ + (void)sqlite3_column_bytes16(mStmtPtr.get(), mIndex); + auto data = static_cast(sqlite3_column_blob(mStmtPtr.get(), mIndex)); + return std::wstring(data, sqlite3_column_bytes16(mStmtPtr.get(), mIndex) / sizeof(wchar_t)); +} +#endif // WCHAR_MAX == 0xffff +#endif // __cpp_unicode_characters +#ifdef __cpp_char8_t +const char8_t* Column::getU8Text(const char8_t* apDefaultValue /* = u8"" */) const noexcept +{ + auto pText = reinterpret_cast(sqlite3_column_text(mStmtPtr.get(), mIndex)); + return (pText ? pText : apDefaultValue); +} +std::u8string Column::getU8String() const +{ + (void)sqlite3_column_bytes(mStmtPtr.get(), mIndex); + auto data = static_cast(sqlite3_column_blob(mStmtPtr.get(), mIndex)); + return std::u8string(data, sqlite3_column_bytes(mStmtPtr.get(), mIndex)); +} +#endif // __cpp_char8_t + + // Return the type of the value of the column int Column::getType() const noexcept { return sqlite3_column_type(mStmtPtr.get(), mIndex); } -// Return the number of bytes used by the text value of the column +// Return the number of bytes used by the UTF-8 text value of the column +// Can cause conversion to text and between UTF-8/UTF-16 encodings +// Be careful when using with getBytes16() and UTF-16 functions int Column::getBytes() const noexcept { return sqlite3_column_bytes(mStmtPtr.get(), mIndex); } +// Return the number of bytes used by the UTF-16 text value of the column +// Can cause conversion to text and between UTF-8/UTF-16 encodings +// Be careful when using with getBytes() and UTF-8 functions +int Column::getBytes16() const noexcept +{ + return sqlite3_column_bytes16(mStmtPtr.get(), mIndex); +} + // Standard std::ostream inserter std::ostream& operator<<(std::ostream& aStream, const Column& aColumn) { diff --git a/tests/Column_test.cpp b/tests/Column_test.cpp index 3a41a273..97d42cb6 100644 --- a/tests/Column_test.cpp +++ b/tests/Column_test.cpp @@ -197,6 +197,59 @@ static void test_column_basis(bool utf16) const SQLite::Column dbl = query.getColumn(3); EXPECT_EQ(0.123, dbl.getDouble()); } + +#ifdef __cpp_unicode_characters + query.reset(); + query.executeStep(); + + { + const auto first_u16 = u"first"; + + const std::u16string str16 = query.getColumn(1); + const char16_t * txt16 = query.getColumn(1); + + EXPECT_EQ(5, str16.length()); + EXPECT_EQ(10, query.getColumn(1).getBytes16()); + EXPECT_EQ(0, memcmp(first_u16, str16.data(), str16.length() * sizeof(char16_t))); + EXPECT_EQ(5, std::char_traits::length(txt16)); + EXPECT_EQ(0, std::char_traits::compare(first_u16, txt16, 6)); + } + +#if WCHAR_MAX == 0xffff + query.reset(); + query.executeStep(); + + { + const auto first_w = L"first"; + + const std::wstring wstr = query.getColumn(1); + const wchar_t* wtxt = query.getColumn(1); + + EXPECT_EQ(5, wstr.length()); + EXPECT_EQ(0, memcmp(first_w, wstr.data(), wstr.length() * sizeof(wchar_t))); + EXPECT_EQ(5, std::char_traits::length(wtxt)); + EXPECT_EQ(0, std::char_traits::compare(first_w, wtxt, 6)); + } +#endif // WCHAR_MAX == 0xffff + +#endif // __cpp_unicode_characters + +#ifdef __cpp_char8_t + query.reset(); + query.executeStep(); + + { + const auto first_u8 = u8"first"; + + const std::u8string str8 = query.getColumn(1); + const char8_t* txt8 = query.getColumn(1); + + EXPECT_EQ(5, str8.length()); + EXPECT_EQ(0, memcmp(first_u8, str8.data(), str8.length() * sizeof(char8_t))); + EXPECT_EQ(5, std::char_traits::length(txt8)); + EXPECT_EQ(0, std::char_traits::compare(first_u8, txt8, 6)); + } +#endif // __cpp_char8_t } TEST(Column, basis) From 7096f6b8107bb99c023b6b2ec8d2a24d64466673 Mon Sep 17 00:00:00 2001 From: Doug Nazar Date: Mon, 5 Dec 2022 20:02:35 -0500 Subject: [PATCH 4/4] Add a couple tests to Statement for 100% coverage. --- tests/Statement_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Statement_test.cpp b/tests/Statement_test.cpp index b6b1f01d..2a828d1f 100644 --- a/tests/Statement_test.cpp +++ b/tests/Statement_test.cpp @@ -138,6 +138,7 @@ TEST(Statement, moveConstructor) // Moved statements should throw EXPECT_THROW(query.getColumnIndex("value"), SQLite::Exception); EXPECT_THROW(query.getColumn(index), SQLite::Exception); + EXPECT_THROW(query.getExpandedSQL(), SQLite::Exception); } #endif @@ -309,6 +310,7 @@ TEST(Statement, bindings) insert.bind(3, dbl); EXPECT_EQ(insert.getExpandedSQL(), "INSERT INTO test VALUES (NULL, 'first', -123, 0.123)"); EXPECT_EQ(1, insert.exec()); + EXPECT_EQ(1, insert.getChanges()); EXPECT_EQ(SQLITE_DONE, db.getErrorCode()); // Check the result