From 832a7965fdc510087dbd48089b3ec9cefd6c46d6 Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Wed, 5 Apr 2023 01:26:33 +0700 Subject: [PATCH 01/10] Added UTF-16 support to session, statement and standard-into-type sources --- CMakeLists.txt | 1 + cmake/SociBackend.cmake | 8 +++ include/soci/odbc/soci-odbc.h | 74 +++++++++++++++++++-- src/backends/odbc/session.cpp | 82 ++++++++++++++++++------ src/backends/odbc/standard-into-type.cpp | 56 +++++++++++++--- src/backends/odbc/standard-use-type.cpp | 60 +++++++++++++---- src/backends/odbc/statement.cpp | 24 ++++++- src/backends/odbc/vector-into-type.cpp | 70 ++++++++++++++++++-- src/backends/odbc/vector-use-type.cpp | 78 +++++++++++++++++----- tests/common-tests.h | 9 ++- tests/odbc/test-odbc-mssql.cpp | 32 +++++++++ 11 files changed, 423 insertions(+), 71 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 380192654..f22e88fe3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ option(SOCI_TESTS "Enable build of collection of SOCI tests" ON) option(SOCI_ASAN "Enable address sanitizer on GCC v4.8+/Clang v 3.1+" OFF) option(SOCI_LTO "Enable link time optimization" OFF) option(SOCI_VISIBILITY "Enable hiding private symbol using ELF visibility if supported by the platform" ON) +option(SOCI_ENABLE_UNICODE "Enable Unicode support for ODBC backend" OFF) if (SOCI_LTO) cmake_minimum_required(VERSION 3.9) diff --git a/cmake/SociBackend.cmake b/cmake/SociBackend.cmake index 0a664667e..b68b2179a 100644 --- a/cmake/SociBackend.cmake +++ b/cmake/SociBackend.cmake @@ -184,6 +184,10 @@ macro(soci_backend NAME) VERSION ${${PROJECT_NAME}_VERSION} CLEAN_DIRECT_OUTPUT 1) endif() + + if(SOCI_ENABLE_UNICODE) + target_compile_definitions(${THIS_BACKEND_TARGET} PRIVATE SOCI_ODBC_WIDE UNICODE) + endif() # Static library target if(SOCI_STATIC) @@ -345,6 +349,10 @@ macro(soci_backend_test) ${THIS_TEST_DEPENDS_LIBRARIES} soci_core soci_${BACKENDL}) + + if(SOCI_ENABLE_UNICODE) + target_compile_definitions(${TEST_TARGET} PRIVATE SOCI_ODBC_WIDE UNICODE) + endif() add_test(${TEST_TARGET} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TEST_TARGET} diff --git a/include/soci/odbc/soci-odbc.h b/include/soci/odbc/soci-odbc.h index 4c32ef211..05f796aad 100644 --- a/include/soci/odbc/soci-odbc.h +++ b/include/soci/odbc/soci-odbc.h @@ -17,12 +17,18 @@ #endif #include +#include #include #include +#include +#include #if defined(_MSC_VER) || defined(__MINGW32__) #include #endif #include // ODBC +#ifdef SOCI_ODBC_WIDE +#include +#endif #include // strcpy() namespace soci @@ -39,10 +45,67 @@ namespace details // This cast is only used to avoid compiler warnings when passing strings // to ODBC functions, the returned string may *not* be really modified. + inline SQLCHAR* sqlchar_cast(std::string const& s) { return reinterpret_cast(const_cast(s.c_str())); } + + inline char* sqlchar_cast(SQLCHAR* s) + { + return reinterpret_cast(s); + } + + inline const char* sqlchar_cast(const SQLCHAR* s) + { + return reinterpret_cast(s); + } + + inline SQLWCHAR* sqlchar_cast(std::wstring const& s) + { + return reinterpret_cast(const_cast(s.c_str())); + } + + inline std::string toUtf8(std::wstring const& s) + { + static std::wstring_convert> converter; + return converter.to_bytes(s); + } + + inline std::string toUtf8(const wchar_t* s) + { + static std::wstring_convert> converter; + return converter.to_bytes(s); + } + + inline std::wstring toUtf16(std::string const& s) + { + static std::wstring_convert> converter; + return converter.from_bytes(s); + } + + inline std::wstring toUtf16(const char* s) + { + static std::wstring_convert> converter; + return converter.from_bytes(s); + } + + // convert single wchar_t to char + inline char toUtf8(wchar_t c) + { + static std::wstring_convert> converter; + return converter.to_bytes(c)[0]; + } + + // convert single char to wchar_t + inline wchar_t toUtf16(char c) + { + static std::wstring_convert> converter; + return converter.from_bytes(&c, &c + 1)[0]; + } + + + } // Option allowing to specify the "driver completion" parameter of @@ -88,7 +151,7 @@ struct odbc_standard_into_type_backend : details::standard_into_type_backend, private odbc_standard_type_backend_base { odbc_standard_into_type_backend(odbc_statement_backend &st) - : odbc_standard_type_backend_base(st), buf_(0) + : odbc_standard_type_backend_base(st), buf_(nullptr) {} void define_by_pos(int &position, @@ -99,8 +162,8 @@ struct odbc_standard_into_type_backend : details::standard_into_type_backend, indicator *ind) override; void clean_up() override; - - char *buf_; // generic buffer + + char* buf_; // generic buffer void *data_; details::exchange_type type_; int position_; @@ -156,7 +219,7 @@ struct odbc_standard_use_type_backend : details::standard_use_type_backend, { odbc_standard_use_type_backend(odbc_statement_backend &st) : odbc_standard_type_backend_base(st), - position_(-1), data_(0), buf_(0), indHolder_(0) {} + position_(-1), data_(0), buf_(nullptr), indHolder_(0) {} void bind_by_pos(int &position, void *data, details::exchange_type type, bool readOnly) override; @@ -225,7 +288,8 @@ struct odbc_vector_use_type_backend : details::vector_use_type_backend, void *data_; details::exchange_type type_; int position_; - char *buf_; // generic buffer + //details::odbc_char_type *buf_; // generic buffer + char* buf_; // generic buffer std::size_t colSize_; // size of the string column (used for strings) // used for strings only std::size_t maxSize_; diff --git a/src/backends/odbc/session.cpp b/src/backends/odbc/session.cpp index f79dff0bc..0192c1da9 100644 --- a/src/backends/odbc/session.cpp +++ b/src/backends/odbc/session.cpp @@ -47,7 +47,11 @@ odbc_session_backend::odbc_session_backend( "allocating connection handle"); } +#ifdef SOCI_ODBC_WIDE + SQLWCHAR outConnString[1024]; +#else SQLCHAR outConnString[1024]; +#endif // SOCI_ODBC_WIDE SQLSMALLINT strLength = 0; // Prompt the user for any missing information (typically UID/PWD) in the @@ -83,7 +87,11 @@ odbc_session_backend::odbc_session_backend( hwnd_for_prompt = ::GetDesktopWindow(); #endif // _WIN32 - std::string const & connectString = parameters.get_connect_string(); +#ifdef SOCI_ODBC_WIDE + std::wstring const& connectString = toUtf16(parameters.get_connect_string()); +#else + std::string const& connectString = parameters.get_connect_string(); +#endif // SOCI_ODBC_WIDE // This "infinite" loop can be executed at most twice. std::string errContext; @@ -136,7 +144,12 @@ odbc_session_backend::odbc_session_backend( break; } +#ifdef SOCI_ODBC_WIDE + const std::string outConnStringUtf8 = toUtf8((const wchar_t*)outConnString); + connection_string_.assign(outConnStringUtf8.c_str(), outConnStringUtf8.size()); +#else connection_string_.assign((const char*)outConnString, strLength); +#endif reset_transaction(); @@ -151,9 +164,14 @@ void odbc_session_backend::configure_connection() // ensure that the conversions to/from text round trip correctly, which // is not the case with the default value of 0. Use the maximal // supported value, which was 2 until 9.x and is 3 since it. - +#ifdef SOCI_ODBC_WIDE + SQLWCHAR product_ver[1024]; +#else char product_ver[1024]; +#endif // SOCI_ODBC_WIDE + SQLSMALLINT len = sizeof(product_ver); + // In case UNICODE is defined, SQLGetInfoW is called SQLRETURN rc = SQLGetInfo(hdbc_, SQL_DBMS_VER, product_ver, len, &len); if (is_odbc_error(rc)) { @@ -165,16 +183,25 @@ void odbc_session_backend::configure_connection() // need to parse it fully, we just need the major version which, // conveniently, comes first. unsigned major_ver = 0; - if (std::sscanf(product_ver, "%u", &major_ver) != 1) +#ifdef SOCI_ODBC_WIDE + const std::string product_ver_utf8(toUtf8(product_ver)); +#else + const std::string product_ver_utf8(product_ver); +#endif // SOCI_ODBC_WIDE + if (std::sscanf(product_ver_utf8.c_str(), "%u", &major_ver) != 1) { - throw soci_error("DBMS version \"" + std::string(product_ver) + + throw soci_error("DBMS version \"" + std::string(product_ver_utf8) + "\" in unrecognizable format."); } - details::auto_statement st(*this); +#ifdef SOCI_ODBC_WIDE + std::wstring const q(major_ver >= 9 ? L"SET extra_float_digits = 3" + : L"SET extra_float_digits = 2"); +#else std::string const q(major_ver >= 9 ? "SET extra_float_digits = 3" : "SET extra_float_digits = 2"); +#endif // SOCI_ODBC_WIDE rc = SQLExecDirect(st.hstmt_, sqlchar_cast(q), static_cast(q.size())); if (is_odbc_error(rc)) @@ -192,6 +219,7 @@ void odbc_session_backend::configure_connection() // Also configure the driver to handle unknown types, such as "xml", // that we use for x_xmltype, as long varchar instead of limiting them // to 256 characters (by default). + // In case UNICODE is defined, SQLSetConnectAttrW is called rc = SQLSetConnectAttr(hdbc_, SQL_ATTR_PGOPT_UNKNOWNSASLONGVARCHAR, (SQLPOINTER)1, 0); // Ignore the error from this one, failure to set it is not fatal and @@ -213,10 +241,16 @@ bool odbc_session_backend::is_connected() // The name of the table we check for is irrelevant, as long as we have a // working connection, it should still find (or, hopefully, not) something. + +#ifdef SOCI_ODBC_WIDE + SQLWCHAR* dummyText = L"bloordyblop"; +#else + SQLCHAR* dummyText = sqlchar_cast("bloordyblop"); +#endif // SOCI_ODBC_WIDE return !is_odbc_error(SQLTables(st.hstmt_, NULL, SQL_NTS, NULL, SQL_NTS, - sqlchar_cast("bloordyblop"), SQL_NTS, + dummyText, SQL_NTS, NULL, SQL_NTS)); } @@ -451,7 +485,11 @@ odbc_session_backend::get_database_product() const if (product_ != prod_uninitialized) return product_; +#ifdef SOCI_ODBC_WIDE + SQLWCHAR product_name[1024]; +#else char product_name[1024]; +#endif // SOCI_ODBC_WIDE SQLSMALLINT len = sizeof(product_name); SQLRETURN rc = SQLGetInfo(hdbc_, SQL_DBMS_NAME, product_name, len, &len); if (is_odbc_error(rc)) @@ -460,19 +498,25 @@ odbc_session_backend::get_database_product() const "getting ODBC driver name"); } - if (strcmp(product_name, "Firebird") == 0) - product_ = prod_firebird; - else if (strcmp(product_name, "Microsoft SQL Server") == 0) - product_ = prod_mssql; - else if (strcmp(product_name, "MySQL") == 0) - product_ = prod_mysql; - else if (strcmp(product_name, "Oracle") == 0) - product_ = prod_oracle; - else if (strcmp(product_name, "PostgreSQL") == 0) - product_ = prod_postgresql; - else if (strcmp(product_name, "SQLite") == 0) - product_ = prod_sqlite; - else if (strstr(product_name, "DB2") == product_name) // "DB2/LINUXX8664" +#ifdef SOCI_ODBC_WIDE + const std::string product_name_str(toUtf8(product_name)); +#else + const std::string product_name_str(product_name); +#endif + + if (product_name_str == "Firebird") + product_ = prod_firebird; + else if (product_name_str == "Microsoft SQL Server") + product_ = prod_mssql; + else if (product_name_str == "MySQL") + product_ = prod_mysql; + else if (product_name_str == "Oracle") + product_ = prod_oracle; + else if (product_name_str == "PostgreSQL") + product_ = prod_postgresql; + else if (product_name_str == "SQLite") + product_ = prod_sqlite; + else if(product_name_str.find("DB2") == 0) // "DB2/LINUXX8664" product_ = prod_db2; else product_ = prod_unknown; diff --git a/src/backends/odbc/standard-into-type.cpp b/src/backends/odbc/standard-into-type.cpp index 26a4b41be..1ba3074ca 100644 --- a/src/backends/odbc/standard-into-type.cpp +++ b/src/backends/odbc/standard-into-type.cpp @@ -28,24 +28,39 @@ void odbc_standard_into_type_backend::define_by_pos( switch (type_) { case x_char: +#ifdef SOCI_ODBC_WIDE + odbcType_ = SQL_C_WCHAR; + size = sizeof(SQLWCHAR) * 2; +#else odbcType_ = SQL_C_CHAR; - size = sizeof(char) + 1; + size = sizeof(char) * 2; +#endif // SOCI_ODBC_WIDE buf_ = new char[size]; data = buf_; break; case x_stdstring: case x_longstring: case x_xmltype: +#ifdef SOCI_ODBC_WIDE + odbcType_ = SQL_C_WCHAR; +#else odbcType_ = SQL_C_CHAR; +#endif // SOCI_ODBC_WIDE // For LONGVARCHAR fields the returned size is ODBC_MAX_COL_SIZE // (or 0 for some backends), but this doesn't correspond to the actual // field size, which can be (much) greater. For now we just used // a buffer of huge (100MiB) hardcoded size, which is clearly not // ideal, but changing this would require using SQLGetData() and is // not trivial, so for now we're stuck with this suboptimal solution. - size = static_cast(statement_.column_size(position_)); +#ifdef SOCI_ODBC_WIDE + size = static_cast(statement_.column_size(position_) * sizeof(SQLWCHAR)); size = (size >= ODBC_MAX_COL_SIZE || size == 0) ? odbc_max_buffer_length : size; - size++; + size += sizeof(SQLWCHAR); +#else + size = static_cast(statement_.column_size(position_) * sizeof(char)); + size = (size >= ODBC_MAX_COL_SIZE || size == 0) ? odbc_max_buffer_length : size; + size += sizeof(char); +#endif // SOCI_ODBC_WIDE buf_ = new char[size]; data = buf_; break; @@ -60,7 +75,7 @@ void odbc_standard_into_type_backend::define_by_pos( case x_long_long: if (use_string_for_bigint()) { - odbcType_ = SQL_C_CHAR; + odbcType_ = SQL_CHAR; size = max_bigint_length; buf_ = new char[size]; data = buf_; @@ -74,7 +89,7 @@ void odbc_standard_into_type_backend::define_by_pos( case x_unsigned_long_long: if (use_string_for_bigint()) { - odbcType_ = SQL_C_CHAR; + odbcType_ = SQL_CHAR; size = max_bigint_length; buf_ = new char[size]; data = buf_; @@ -155,12 +170,22 @@ void odbc_standard_into_type_backend::post_fetch( // only std::string and std::tm need special handling if (type_ == x_char) { +#ifdef SOCI_ODBC_WIDE + SQLWCHAR* wBuf = reinterpret_cast(buf_); + exchange_type_cast(data_) = toUtf8(wBuf)[0]; +#else exchange_type_cast(data_) = buf_[0]; +#endif // SOCI_ODBC_WIDE } else if (type_ == x_stdstring) { std::string& s = exchange_type_cast(data_); +#ifdef SOCI_ODBC_WIDE + SQLWCHAR* wBuf = reinterpret_cast(buf_); + s = toUtf8(wBuf); +#else s = buf_; +#endif // SOCI_ODBC_WIDE if (s.size() >= (odbc_max_buffer_length - 1)) { throw soci_error("Buffer size overflow; maybe got too large string"); @@ -168,11 +193,26 @@ void odbc_standard_into_type_backend::post_fetch( } else if (type_ == x_longstring) { - exchange_type_cast(data_).value = buf_; + std::string& s = exchange_type_cast(data_).value; +#ifdef SOCI_ODBC_WIDE + SQLWCHAR* wBuf = reinterpret_cast(buf_); + s = toUtf8(wBuf); +#else + s = buf_; +#endif // SOCI_ODBC_WIDE + if (s.size() >= (odbc_max_buffer_length - 1)) + { + throw soci_error("Buffer size overflow; maybe got too large string"); + } } else if (type_ == x_xmltype) { +#ifdef SOCI_ODBC_WIDE + SQLWCHAR* wBuf = reinterpret_cast(buf_); + exchange_type_cast(data_).value = toUtf8(wBuf); +#else exchange_type_cast(data_).value = buf_; +#endif // SOCI_ODBC_WIDE } else if (type_ == x_stdtm) { @@ -214,7 +254,7 @@ void odbc_standard_into_type_backend::clean_up() { if (buf_) { - delete [] buf_; - buf_ = 0; + delete[] buf_; + buf_ = nullptr; } } diff --git a/src/backends/odbc/standard-use-type.cpp b/src/backends/odbc/standard-use-type.cpp index 60ec149e7..35a08bafd 100644 --- a/src/backends/odbc/standard-use-type.cpp +++ b/src/backends/odbc/standard-use-type.cpp @@ -20,6 +20,11 @@ using namespace soci::details; void* odbc_standard_use_type_backend::prepare_for_bind( SQLLEN &size, SQLSMALLINT &sqlType, SQLSMALLINT &cType) { + +#ifdef SOCI_ODBC_WIDE + SQLWCHAR* buf = nullptr; +#endif // SOCI_ODBC_WIDE + switch (type_) { // simple cases @@ -36,12 +41,13 @@ void* odbc_standard_use_type_backend::prepare_for_bind( case x_long_long: if (use_string_for_bigint()) { + sqlType = SQL_NUMERIC; cType = SQL_C_CHAR; size = max_bigint_length; buf_ = new char[size]; - snprintf(buf_, size, "%" LL_FMT_FLAGS "d", - exchange_type_cast(data_)); + snprintf(reinterpret_cast(buf_), size, "%" LL_FMT_FLAGS "d", + exchange_type_cast(data_)); indHolder_ = SQL_NTS; } else // Normal case, use ODBC support. @@ -58,8 +64,8 @@ void* odbc_standard_use_type_backend::prepare_for_bind( cType = SQL_C_CHAR; size = max_bigint_length; buf_ = new char[size]; - snprintf(buf_, size, "%" LL_FMT_FLAGS "u", - exchange_type_cast(data_)); + snprintf(reinterpret_cast(buf_), size, "%" LL_FMT_FLAGS "u", + exchange_type_cast(data_)); indHolder_ = SQL_NTS; } else // Normal case, use ODBC support. @@ -76,12 +82,22 @@ void* odbc_standard_use_type_backend::prepare_for_bind( break; case x_char: +#ifdef SOCI_ODBC_WIDE + sqlType = SQL_WCHAR; + cType = SQL_C_WCHAR; + size = sizeof(SQLWCHAR) * 2; + buf_ = new char[size]; + buf = reinterpret_cast(buf_); + buf[0] = toUtf16(exchange_type_cast(data_)); + buf[1] = L'\0'; +#else sqlType = SQL_CHAR; cType = SQL_C_CHAR; size = 2; buf_ = new char[size]; buf_[0] = exchange_type_cast(data_); buf_[1] = '\0'; +#endif // SOCI_ODBC_WIDE indHolder_ = SQL_NTS; break; case x_stdstring: @@ -120,9 +136,15 @@ void* odbc_standard_use_type_backend::prepare_for_bind( break; case x_longstring: - copy_from_string(exchange_type_cast(data_).value, - size, sqlType, cType); - break; + { + std::string const& ls = exchange_type_cast(data_).value; + + copy_from_string(ls, size, sqlType, cType); + + // copy_from_string(exchange_type_cast(data_).value, + // size, sqlType, cType); + } + break; case x_xmltype: copy_from_string(exchange_type_cast(data_).value, size, sqlType, cType); @@ -145,12 +167,24 @@ void odbc_standard_use_type_backend::copy_from_string( SQLSMALLINT& cType ) { +#ifdef SOCI_ODBC_WIDE + std::wstring ws = toUtf16(s); + size = ws.size(); + sqlType = size >= ODBC_MAX_COL_SIZE ? SQL_WLONGVARCHAR : SQL_WVARCHAR; + cType = SQL_C_WCHAR; + std::size_t const bufSize = (size + 1) * sizeof(SQLWCHAR); + buf_ = new char[bufSize]; + SQLWCHAR* const buf = reinterpret_cast(buf_); + std::copy(ws.begin(), ws.end(), buf); + buf[size] = L'\0'; +#else size = s.size(); sqlType = size >= ODBC_MAX_COL_SIZE ? SQL_LONGVARCHAR : SQL_VARCHAR; cType = SQL_C_CHAR; - buf_ = new char[size+1]; - memcpy(buf_, s.c_str(), size); - buf_[size++] = '\0'; + buf_ = new char[size + 1]; + std::copy(s.begin(), s.end(), buf_); + buf_[size] = '\0'; +#endif // SOCI_ODBC_WIDE indHolder_ = SQL_NTS; } @@ -269,9 +303,9 @@ void odbc_standard_use_type_backend::post_use(bool gotData, indicator *ind) void odbc_standard_use_type_backend::clean_up() { - if (buf_ != NULL) + if (buf_) { - delete [] buf_; - buf_ = NULL; + delete[] buf_; + buf_ = nullptr; } } diff --git a/src/backends/odbc/statement.cpp b/src/backends/odbc/statement.cpp index 54711a5ed..f75ae1597 100644 --- a/src/backends/odbc/statement.cpp +++ b/src/backends/odbc/statement.cpp @@ -124,7 +124,13 @@ void odbc_statement_backend::prepare(std::string const & query, query_ += "?"; } - SQLRETURN rc = SQLPrepare(hstmt_, sqlchar_cast(query_), (SQLINTEGER)query_.size()); +#ifndef SOCI_ODBC_WIDE + const std::string & query_str(query_); +#else + const std::wstring query_str(toUtf16(query_)); +#endif // SOCI_ODBC_WIDE + + SQLRETURN rc = SQLPrepare(hstmt_, sqlchar_cast(query_str), (SQLINTEGER)query_str.size()); if (is_odbc_error(rc)) { std::ostringstream ss; @@ -330,7 +336,11 @@ int odbc_statement_backend::prepare_for_describe() void odbc_statement_backend::describe_column(int colNum, data_type & type, std::string & columnName) { +#ifdef SOCI_ODBC_WIDE + SQLWCHAR colNameBuffer[2048]; +#else SQLCHAR colNameBuffer[2048]; +#endif // SOCI_ODBC_WIDE SQLSMALLINT colNameBufferOverflow; SQLSMALLINT dataType; SQLULEN colSize; @@ -349,8 +359,14 @@ void odbc_statement_backend::describe_column(int colNum, data_type & type, throw odbc_soci_error(SQL_HANDLE_STMT, hstmt_, ss.str()); } - char const *name = reinterpret_cast(colNameBuffer); + +#ifdef SOCI_ODBC_WIDE + SQLWCHAR const* name = colNameBuffer; + columnName = toUtf8(name); +#else + char const* name = reinterpret_cast(colNameBuffer); columnName.assign(name, std::strlen(name)); +#endif // SOCI_ODBC_WIDE switch (dataType) { @@ -385,7 +401,11 @@ void odbc_statement_backend::describe_column(int colNum, data_type & type, std::size_t odbc_statement_backend::column_size(int colNum) { +#ifdef SOCI_ODBC_WIDE + SQLWCHAR colNameBuffer[2048]; +#else SQLCHAR colNameBuffer[2048]; +#endif // SOCI_ODBC_WIDE SQLSMALLINT colNameBufferOverflow; SQLSMALLINT dataType; SQLULEN colSize; diff --git a/src/backends/odbc/vector-into-type.cpp b/src/backends/odbc/vector-into-type.cpp index 84863542a..2ed091fe3 100644 --- a/src/backends/odbc/vector-into-type.cpp +++ b/src/backends/odbc/vector-into-type.cpp @@ -81,16 +81,24 @@ void odbc_vector_into_type_backend::define_by_pos( // cases that require adjustments and buffer management case x_char: +#ifdef SOCI_ODBC_WIDE + odbcType_ = SQL_C_WCHAR; + colSize_ = sizeof(SQLWCHAR) * 2; +#else odbcType_ = SQL_C_CHAR; - - colSize_ = sizeof(char) * 2; + colSize_ = 2; +#endif // SOCI_ODBC_WIDE buf_ = new char[colSize_ * vectorSize]; break; case x_stdstring: case x_xmltype: case x_longstring: { +#ifdef SOCI_ODBC_WIDE + odbcType_ = SQL_C_WCHAR; +#else odbcType_ = SQL_C_CHAR; +#endif // SOCI_ODBC_WIDE colSize_ = static_cast(get_sqllen_from_value(statement_.column_size(position))); if (colSize_ >= ODBC_MAX_COL_SIZE || colSize_ == 0) @@ -104,13 +112,20 @@ void odbc_vector_into_type_backend::define_by_pos( statement_.fetchVectorByRows_ = true; } +#ifdef SOCI_ODBC_WIDE + colSize_ += sizeof(SQLWCHAR); +#else colSize_++; +#endif // SOCI_ODBC_WIDE // If we are fetching by a single row, allocate the buffer only for // one value. - const std::size_t elementsCount - = statement_.fetchVectorByRows_ ? 1 : vectorSize; + const std::size_t elementsCount = statement_.fetchVectorByRows_ ? 1 : vectorSize; +#ifdef SOCI_ODBC_WIDE + buf_ = new char[colSize_ * elementsCount * sizeof(SQLWCHAR)]; +#else buf_ = new char[colSize_ * elementsCount]; +#endif // SOCI_ODBC_WIDE } break; case x_stdtm: @@ -209,21 +224,39 @@ void odbc_vector_into_type_backend::do_post_fetch_rows( { std::vector *vp = static_cast *>(data_); - std::vector &v(*vp); + +#ifdef SOCI_ODBC_WIDE + SQLWCHAR *pos = reinterpret_cast(buf_); +#else char *pos = buf_; +#endif // SOCI_ODBC_WIDE + for (std::size_t i = beginRow; i != endRow; ++i) { +#ifdef SOCI_ODBC_WIDE + v[i] = toUtf8(*pos); + pos += colSize_ / sizeof(SQLWCHAR); +#else v[i] = *pos; pos += colSize_; +#endif // SOCI_ODBC_WIDE } } if (type_ == x_stdstring || type_ == x_xmltype || type_ == x_longstring) { +#ifdef SOCI_ODBC_WIDE + const SQLWCHAR *pos = reinterpret_cast(buf_); + std::size_t const colSize = colSize_ / sizeof(SQLWCHAR); +#else const char *pos = buf_; - for (std::size_t i = beginRow; i != endRow; ++i, pos += colSize_) + std::size_t const colSize = colSize_; +#endif // SOCI_ODBC_WIDE + + for (std::size_t i = beginRow; i != endRow; ++i, pos += colSize) { - SQLLEN const len = get_sqllen_from_vector_at(i); + + SQLLEN len = get_sqllen_from_vector_at(i); std::string& value = vector_string_value(type_, data_, i); if (len == -1) @@ -232,6 +265,12 @@ void odbc_vector_into_type_backend::do_post_fetch_rows( value.clear(); continue; } +#ifdef SOCI_ODBC_WIDE + else + { + len = len / sizeof(SQLWCHAR); + } +#endif // SOCI_ODBC_WIDE // Find the actual length of the string: for a VARCHAR(N) // column, it may be right-padded with spaces up to the length @@ -243,11 +282,19 @@ void odbc_vector_into_type_backend::do_post_fetch_rows( // // So deal with this generically by just trimming all the // spaces from the right hand-side. +#ifdef SOCI_ODBC_WIDE + const SQLWCHAR *end = pos + len; +#else const char* end = pos + len; +#endif // SOCI_ODBC_WIDE while (end != pos) { // Pre-decrement as "end" is one past the end, as usual. +#ifdef SOCI_ODBC_WIDE + if (*--end != L' ') +#else if (*--end != ' ') +#endif // SOCI_ODBC_WIDE { // We must count the last non-space character. ++end; @@ -255,7 +302,14 @@ void odbc_vector_into_type_backend::do_post_fetch_rows( } } +#ifdef SOCI_ODBC_WIDE + std::wstring wstr; + wstr.assign(pos, end - pos); + value.assign(details::toUtf8(wstr)); +#else value.assign(pos, end - pos); +#endif // SOCI_ODBC_WIDE + } } else if (type_ == x_stdtm) @@ -286,6 +340,7 @@ void odbc_vector_into_type_backend::do_post_fetch_rows( = static_cast *>(data_); std::vector &v(*vp); char *pos = buf_; + for (std::size_t i = beginRow; i != endRow; ++i) { if (!cstring_to_integer(v[i], pos)) @@ -301,6 +356,7 @@ void odbc_vector_into_type_backend::do_post_fetch_rows( = static_cast *>(data_); std::vector &v(*vp); char *pos = buf_; + for (std::size_t i = beginRow; i != endRow; ++i) { if (!cstring_to_unsigned(v[i], pos)) diff --git a/src/backends/odbc/vector-use-type.cpp b/src/backends/odbc/vector-use-type.cpp index 90ab74e54..86dc099f6 100644 --- a/src/backends/odbc/vector-use-type.cpp +++ b/src/backends/odbc/vector-use-type.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #ifdef _MSC_VER // disables the warning about converting int to void*. This is a 64 bit compatibility @@ -130,25 +131,43 @@ void* odbc_vector_use_type_backend::prepare_for_bind(SQLUINTEGER &size, // cases that require adjustments and buffer management case x_char: { - std::vector *vp - = static_cast *>(data_); + std::vector *vp = static_cast *>(data_); std::size_t const vsize = vp->size(); prepare_indicators(vsize); - size = sizeof(char) * 2; +#ifdef SOCI_ODBC_WIDE + size = sizeof(SQLWCHAR) * 2; // 1 wchar + 1 null terminator +#else + size = sizeof(char) * 2; // 1 char + 1 null terminator +#endif // SOCI_ODBC_WIDE + buf_ = new char[size * vsize]; +#ifdef SOCI_ODBC_WIDE + SQLWCHAR* pos = reinterpret_cast(buf_); + + for (std::size_t i = 0; i != vsize; ++i) + { + *pos++ = details::toUtf16((*vp)[i]); + } + *pos = 0; // Null-terminate the string once, after all characters are copied + + sqlType = SQL_WCHAR; + cType = SQL_C_WCHAR; +#else char *pos = buf_; for (std::size_t i = 0; i != vsize; ++i) { *pos++ = (*vp)[i]; - *pos++ = 0; } + *pos = 0; // Null-terminate the string once, after all characters are copied sqlType = SQL_CHAR; cType = SQL_C_CHAR; +#endif // SOCI_ODBC_WIDE + data = buf_; } break; @@ -159,37 +178,65 @@ void* odbc_vector_use_type_backend::prepare_for_bind(SQLUINTEGER &size, std::size_t maxSize = 0; std::size_t const vecSize = get_vector_size(type_, data_); prepare_indicators(vecSize); - for (std::size_t i = 0; i != vecSize; ++i) + for (std::size_t i = 0; i < vecSize; ++i) { std::size_t sz = vector_string_value(type_, data_, i).length(); + +#ifdef SOCI_ODBC_WIDE + set_sqllen_from_vector_at(i, static_cast(sz * sizeof(SQLWCHAR))); +#else set_sqllen_from_vector_at(i, static_cast(sz)); +#endif // SOCI_ODBC_WIDE + maxSize = sz > maxSize ? sz : maxSize; } maxSize++; // For terminating nul. - buf_ = new char[maxSize * vecSize]; - memset(buf_, 0, maxSize * vecSize); +#ifdef SOCI_ODBC_WIDE + const std::size_t bufSize = maxSize * vecSize * sizeof(SQLWCHAR); +#else + const std::size_t bufSize = maxSize * vecSize; +#endif // SOCI_ODBC_WIDE + + buf_ = new char[bufSize]; + memset(buf_, 0, bufSize); +#ifdef SOCI_ODBC_WIDE + SQLWCHAR*pos = reinterpret_cast(buf_); +#else char *pos = buf_; +#endif // SOCI_ODBC_WIDE for (std::size_t i = 0; i != vecSize; ++i) { std::string& value = vector_string_value(type_, data_, i); + +#ifdef SOCI_ODBC_WIDE + const std::wstring wValue = toUtf16(value); + wmemcpy(pos, wValue.c_str(), wValue.length()); +#else memcpy(pos, value.c_str(), value.length()); +#endif // SOCI_ODBC_WIDE + pos += maxSize; } data = buf_; - size = static_cast(maxSize); +#ifdef SOCI_ODBC_WIDE + size = static_cast(maxSize * sizeof(SQLWCHAR)); + sqlType = size >= ODBC_MAX_COL_SIZE / sizeof(SQLWCHAR) ? SQL_WLONGVARCHAR : SQL_WVARCHAR; + cType = SQL_C_WCHAR; +#else + size = static_cast(maxSize); sqlType = size >= ODBC_MAX_COL_SIZE ? SQL_LONGVARCHAR : SQL_VARCHAR; cType = SQL_C_CHAR; +#endif // SOCI_ODBC_WIDE } break; case x_stdtm: { - std::vector *vp - = static_cast *>(data_); + std::vector *vp = static_cast *>(data_); prepare_indicators(vp->size()); @@ -330,15 +377,14 @@ void odbc_vector_use_type_backend::pre_use(indicator const *ind) case x_long_long: if (use_string_for_bigint()) { - std::vector *vp - = static_cast *>(data_); + std::vector *vp = static_cast *>(data_); std::vector &v(*vp); char *pos = buf_; std::size_t const vsize = v.size(); for (std::size_t i = 0; i != vsize; ++i) { - snprintf(pos, max_bigint_length, "%" LL_FMT_FLAGS "d", v[i]); + snprintf(reinterpret_cast(pos), max_bigint_length, "%" LL_FMT_FLAGS "d", v[i]); pos += max_bigint_length; } @@ -357,7 +403,7 @@ void odbc_vector_use_type_backend::pre_use(indicator const *ind) std::size_t const vsize = v.size(); for (std::size_t i = 0; i != vsize; ++i) { - snprintf(pos, max_bigint_length, "%" LL_FMT_FLAGS "u", v[i]); + snprintf(reinterpret_cast(pos), max_bigint_length, "%" LL_FMT_FLAGS "u", v[i]); pos += max_bigint_length; } @@ -430,9 +476,9 @@ std::size_t odbc_vector_use_type_backend::size() void odbc_vector_use_type_backend::clean_up() { - if (buf_ != NULL) + if (buf_ != nullptr) { delete [] buf_; - buf_ = NULL; + buf_ = nullptr; } } diff --git a/tests/common-tests.h b/tests/common-tests.h index 52f4b1b2c..0e0425fff 100644 --- a/tests/common-tests.h +++ b/tests/common-tests.h @@ -5428,8 +5428,11 @@ TEST_CASE_METHOD(common_tests, "CLOB", "[core][clob]") sql << "select s from soci_test where id = 1", into(s2); CHECK(s2.value.size() == 0); - +#ifdef SOCI_ODBC_WIDE + s1.value = make_long_xml_string(3000); +#else s1.value = make_long_xml_string(); +#endif sql << "update soci_test set s = :s where id = 1", use(s1); @@ -5495,7 +5498,11 @@ TEST_CASE_METHOD(common_tests, "XML", "[core][xml]") int id = 1; xml_type xml; +#ifdef SOCI_ODBC_WIDE + xml.value = make_long_xml_string(3000); +#else xml.value = make_long_xml_string(); +#endif sql << "insert into soci_test (id, x) values (:1, " << tc_.to_xml(":2") diff --git a/tests/odbc/test-odbc-mssql.cpp b/tests/odbc/test-odbc-mssql.cpp index 252ac17fe..b84d599ba 100644 --- a/tests/odbc/test-odbc-mssql.cpp +++ b/tests/odbc/test-odbc-mssql.cpp @@ -75,6 +75,38 @@ TEST_CASE("MS SQL long string", "[odbc][mssql][long]") ); } +// #ifdef SOCI_ODBC_WIDE + +TEST_CASE("MS SQL unicode string", "[odbc][mssql][unicode]") +{ + soci::session sql(backEnd, connectString); + + struct unicode_table_creator : public table_creator_base + { + explicit unicode_table_creator(soci::session& sql) + : table_creator_base(sql) + { + sql << "create table soci_test (" + "str nvarchar(255) null" + ")"; + } + } unicode_table_creator(sql); + + // std::string str_in { "สวัสดีชาวโลก!" }; + std::string str_in { u8"\u0E2A\u0E27\u0E31\u0E2A\u0E14\u0E35\u0E0A\u0E32\u0E27\u0E42\u0E25\u0E01!" }; + + CHECK_NOTHROW(( + sql << "insert into soci_test(str) values(N'" + str_in + "')" + )); + + std::string str_out; + sql << "select str from soci_test", into(str_out); + + CHECK(str_out == str_in); +} + +// #endif // SOCI_ODBC_WIDE + // DDL Creation objects for common tests struct table_creator_one : public table_creator_base { From d0694ccbbc0aa2551aff96622ad62ba613a6f488 Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Sun, 17 Mar 2024 20:49:33 +0700 Subject: [PATCH 02/10] stack-use-after-scope --- src/backends/odbc/session.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/backends/odbc/session.cpp b/src/backends/odbc/session.cpp index 0192c1da9..d57440bd4 100644 --- a/src/backends/odbc/session.cpp +++ b/src/backends/odbc/session.cpp @@ -242,15 +242,14 @@ bool odbc_session_backend::is_connected() // The name of the table we check for is irrelevant, as long as we have a // working connection, it should still find (or, hopefully, not) something. -#ifdef SOCI_ODBC_WIDE - SQLWCHAR* dummyText = L"bloordyblop"; -#else - SQLCHAR* dummyText = sqlchar_cast("bloordyblop"); -#endif // SOCI_ODBC_WIDE return !is_odbc_error(SQLTables(st.hstmt_, NULL, SQL_NTS, NULL, SQL_NTS, - dummyText, SQL_NTS, +#ifdef SOCI_ODBC_WIDE + L"bloordyblop", SQL_NTS, +#else + sqlchar_cast("bloordyblop"), SQL_NTS, +#endif // SOCI_ODBC_WIDE NULL, SQL_NTS)); } From 5c300dd14f3265ff79b97897a2c51f344c2a2dc7 Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Sun, 17 Mar 2024 23:02:26 +0700 Subject: [PATCH 03/10] removed #ifdef SOCI_ODBC_WIDE from test-odbc-mssql.cpp --- tests/odbc/test-odbc-mssql.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/odbc/test-odbc-mssql.cpp b/tests/odbc/test-odbc-mssql.cpp index fc2d5694b..20f2417c6 100644 --- a/tests/odbc/test-odbc-mssql.cpp +++ b/tests/odbc/test-odbc-mssql.cpp @@ -75,8 +75,6 @@ TEST_CASE("MS SQL long string", "[odbc][mssql][long]") ); } -// #ifdef SOCI_ODBC_WIDE - TEST_CASE("MS SQL unicode string", "[odbc][mssql][unicode]") { soci::session sql(backEnd, connectString); @@ -105,8 +103,6 @@ TEST_CASE("MS SQL unicode string", "[odbc][mssql][unicode]") CHECK(str_out == str_in); } -// #endif // SOCI_ODBC_WIDE - // DDL Creation objects for common tests struct table_creator_one : public table_creator_base { From fd58e16b6953148efcc6493c5acd744632acdc11 Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Mon, 18 Mar 2024 01:06:42 +0700 Subject: [PATCH 04/10] Fixed unit test --- tests/common-tests.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/common-tests.h b/tests/common-tests.h index 5484e81f5..7a9d74ee8 100644 --- a/tests/common-tests.h +++ b/tests/common-tests.h @@ -6017,7 +6017,11 @@ TEST_CASE_METHOD(common_tests, "String length", "[core][string][length]") std::vector v; v.push_back("Hello"); v.push_back(""); +#ifdef SOCI_ODBC_WIDE + v.push_back("varchar 20"); // 10 characters, because of wide characters +#else v.push_back("whole of varchar(20)"); +#endif // SOCI_ODBC_WIDE REQUIRE_NOTHROW(( sql << "insert into soci_test(str) values(:s)", use(v) @@ -6045,8 +6049,13 @@ TEST_CASE_METHOD(common_tests, "String length", "[core][string][length]") CHECK(vlen[1] == 5); CHECK(vout[1].length() == 5); +#ifdef SOCI_ODBC_WIDE + CHECK(vlen[2] == 10); + CHECK(vout[2].length() == 10); +#else CHECK(vlen[2] == 20); CHECK(vout[2].length() == 20); +#endif // SOCI_ODBC_WIDE } // Helper function used in some tests below. Generates an XML sample about From f862821f25e29e609f7632e7ed426cfe3c605c7e Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Mon, 18 Mar 2024 16:06:59 +0700 Subject: [PATCH 05/10] cleanup --- include/soci/odbc/soci-odbc.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/soci/odbc/soci-odbc.h b/include/soci/odbc/soci-odbc.h index bd4ddc2cb..dff2f27e5 100644 --- a/include/soci/odbc/soci-odbc.h +++ b/include/soci/odbc/soci-odbc.h @@ -164,7 +164,7 @@ struct odbc_standard_into_type_backend : details::standard_into_type_backend, indicator *ind) override; void clean_up() override; - + char* buf_; // generic buffer void *data_; details::exchange_type type_; @@ -290,7 +290,6 @@ struct odbc_vector_use_type_backend : details::vector_use_type_backend, void *data_; details::exchange_type type_; int position_; - //details::odbc_char_type *buf_; // generic buffer char* buf_; // generic buffer std::size_t colSize_; // size of the string column (used for strings) // used for strings only From 5d69724863f9f3928fd95344257488c04384a77d Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Wed, 20 Mar 2024 01:18:12 +0700 Subject: [PATCH 06/10] Update src/backends/odbc/vector-use-type.cpp Co-authored-by: VZ --- src/backends/odbc/vector-use-type.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/odbc/vector-use-type.cpp b/src/backends/odbc/vector-use-type.cpp index 9b8876ceb..1e20b6ec5 100644 --- a/src/backends/odbc/vector-use-type.cpp +++ b/src/backends/odbc/vector-use-type.cpp @@ -207,7 +207,7 @@ void* odbc_vector_use_type_backend::prepare_for_bind(SQLUINTEGER &size, for (std::size_t i = 0; i != vsize; ++i) { *pos++ = (*vp)[i]; - *pos++ = 0; + *pos++ = 0; } sqlType = SQL_CHAR; From d281bb8b62aef3cd92b0478b87440f40cd8c2a25 Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Wed, 20 Mar 2024 02:13:15 +0700 Subject: [PATCH 07/10] implemented requested changes and changed CMAKE option name --- CMakeLists.txt | 2 +- cmake/SociBackend.cmake | 4 ++-- src/backends/odbc/standard-into-type.cpp | 13 +++---------- src/backends/odbc/vector-into-type.cpp | 22 ++++++---------------- src/backends/odbc/vector-use-type.cpp | 23 +++++------------------ tests/common-tests.h | 13 +++++-------- 6 files changed, 22 insertions(+), 55 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5432a1f2b..d9707c2cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ option(SOCI_TESTS "Enable build of collection of SOCI tests" ON) option(SOCI_ASAN "Enable address sanitizer on GCC v4.8+/Clang v 3.1+" OFF) option(SOCI_LTO "Enable link time optimization" OFF) option(SOCI_VISIBILITY "Enable hiding private symbol using ELF visibility if supported by the platform" ON) -option(SOCI_ENABLE_UNICODE "Enable Unicode support for ODBC backend" OFF) +option(SOCI_ODBC_UNICODE "Enable Unicode support for ODBC backend" OFF) if (SOCI_LTO) cmake_minimum_required(VERSION 3.9) diff --git a/cmake/SociBackend.cmake b/cmake/SociBackend.cmake index b68b2179a..f00df875a 100644 --- a/cmake/SociBackend.cmake +++ b/cmake/SociBackend.cmake @@ -185,7 +185,7 @@ macro(soci_backend NAME) CLEAN_DIRECT_OUTPUT 1) endif() - if(SOCI_ENABLE_UNICODE) + if(SOCI_ODBC_UNICODE) target_compile_definitions(${THIS_BACKEND_TARGET} PRIVATE SOCI_ODBC_WIDE UNICODE) endif() @@ -350,7 +350,7 @@ macro(soci_backend_test) soci_core soci_${BACKENDL}) - if(SOCI_ENABLE_UNICODE) + if(SOCI_ODBC_UNICODE) target_compile_definitions(${TEST_TARGET} PRIVATE SOCI_ODBC_WIDE UNICODE) endif() diff --git a/src/backends/odbc/standard-into-type.cpp b/src/backends/odbc/standard-into-type.cpp index 26ab0f74b..ec1a17094 100644 --- a/src/backends/odbc/standard-into-type.cpp +++ b/src/backends/odbc/standard-into-type.cpp @@ -31,11 +31,10 @@ void odbc_standard_into_type_backend::define_by_pos( case x_char: #ifdef SOCI_ODBC_WIDE odbcType_ = SQL_C_WCHAR; - size = sizeof(SQLWCHAR) * 2; #else odbcType_ = SQL_C_CHAR; - size = sizeof(char) * 2; #endif // SOCI_ODBC_WIDE + size = sizeof(SQLTCHAR) * 2; buf_ = new char[size]; data = buf_; break; @@ -53,15 +52,9 @@ void odbc_standard_into_type_backend::define_by_pos( // a buffer of huge (100MiB) hardcoded size, which is clearly not // ideal, but changing this would require using SQLGetData() and is // not trivial, so for now we're stuck with this suboptimal solution. -#ifdef SOCI_ODBC_WIDE - size = static_cast(statement_.column_size(position_) * sizeof(SQLWCHAR)); - size = (size >= ODBC_MAX_COL_SIZE || size == 0) ? odbc_max_buffer_length : size; - size += sizeof(SQLWCHAR); -#else - size = static_cast(statement_.column_size(position_) * sizeof(char)); + size = static_cast(statement_.column_size(position_) * sizeof(SQLTCHAR)); size = (size >= ODBC_MAX_COL_SIZE || size == 0) ? odbc_max_buffer_length : size; - size += sizeof(char); -#endif // SOCI_ODBC_WIDE + size += sizeof(SQLTCHAR); buf_ = new char[size]; data = buf_; break; diff --git a/src/backends/odbc/vector-into-type.cpp b/src/backends/odbc/vector-into-type.cpp index 77eb95cb7..97d159263 100644 --- a/src/backends/odbc/vector-into-type.cpp +++ b/src/backends/odbc/vector-into-type.cpp @@ -96,11 +96,10 @@ void odbc_vector_into_type_backend::define_by_pos( case x_char: #ifdef SOCI_ODBC_WIDE odbcType_ = SQL_C_WCHAR; - colSize_ = sizeof(SQLWCHAR) * 2; #else odbcType_ = SQL_C_CHAR; - colSize_ = 2; #endif // SOCI_ODBC_WIDE + colSize_ = sizeof(SQLTCHAR) * 2; buf_ = new char[colSize_ * vectorSize]; break; case x_stdstring: @@ -125,20 +124,13 @@ void odbc_vector_into_type_backend::define_by_pos( statement_.fetchVectorByRows_ = true; } -#ifdef SOCI_ODBC_WIDE - colSize_ += sizeof(SQLWCHAR); -#else - colSize_++; -#endif // SOCI_ODBC_WIDE + colSize_ += sizeof(SQLTCHAR); // If we are fetching by a single row, allocate the buffer only for // one value. const std::size_t elementsCount = statement_.fetchVectorByRows_ ? 1 : vectorSize; -#ifdef SOCI_ODBC_WIDE - buf_ = new char[colSize_ * elementsCount * sizeof(SQLWCHAR)]; -#else - buf_ = new char[colSize_ * elementsCount]; -#endif // SOCI_ODBC_WIDE + + buf_ = new char[colSize_ * elementsCount * sizeof(SQLTCHAR)]; } break; case x_stdtm: @@ -265,22 +257,20 @@ void odbc_vector_into_type_backend::do_post_fetch_rows( { #ifdef SOCI_ODBC_WIDE v[i] = toUtf8(*pos); - pos += colSize_ / sizeof(SQLWCHAR); #else v[i] = *pos; - pos += colSize_; #endif // SOCI_ODBC_WIDE + pos += colSize_ / sizeof(SQLTCHAR); } } if (type_ == x_stdstring || type_ == x_xmltype || type_ == x_longstring) { #ifdef SOCI_ODBC_WIDE const SQLWCHAR *pos = reinterpret_cast(buf_); - std::size_t const colSize = colSize_ / sizeof(SQLWCHAR); #else const char *pos = buf_; - std::size_t const colSize = colSize_; #endif // SOCI_ODBC_WIDE + std::size_t const colSize = colSize_ / sizeof(SQLTCHAR); for (std::size_t i = beginRow; i != endRow; ++i, pos += colSize) { diff --git a/src/backends/odbc/vector-use-type.cpp b/src/backends/odbc/vector-use-type.cpp index 1e20b6ec5..baba86e08 100644 --- a/src/backends/odbc/vector-use-type.cpp +++ b/src/backends/odbc/vector-use-type.cpp @@ -181,11 +181,7 @@ void* odbc_vector_use_type_backend::prepare_for_bind(SQLUINTEGER &size, prepare_indicators(vsize); -#ifdef SOCI_ODBC_WIDE - size = sizeof(SQLWCHAR) * 2; // 1 wchar + 1 null terminator -#else - size = sizeof(char) * 2; // 1 char + 1 null terminator -#endif // SOCI_ODBC_WIDE + size = sizeof(SQLTCHAR) * 2; // 1 char + 1 null terminator buf_ = new char[size * vsize]; @@ -228,22 +224,14 @@ void* odbc_vector_use_type_backend::prepare_for_bind(SQLUINTEGER &size, { std::size_t sz = vector_string_value(type_, data_, i).length(); -#ifdef SOCI_ODBC_WIDE - set_sqllen_from_vector_at(i, static_cast(sz * sizeof(SQLWCHAR))); -#else - set_sqllen_from_vector_at(i, static_cast(sz)); -#endif // SOCI_ODBC_WIDE + set_sqllen_from_vector_at(i, static_cast(sz * sizeof(SQLTCHAR))); maxSize = sz > maxSize ? sz : maxSize; } maxSize++; // For terminating nul. -#ifdef SOCI_ODBC_WIDE - const std::size_t bufSize = maxSize * vecSize * sizeof(SQLWCHAR); -#else - const std::size_t bufSize = maxSize * vecSize; -#endif // SOCI_ODBC_WIDE + const std::size_t bufSize = maxSize * vecSize * sizeof(SQLTCHAR); buf_ = new char[bufSize]; memset(buf_, 0, bufSize); @@ -269,12 +257,11 @@ void* odbc_vector_use_type_backend::prepare_for_bind(SQLUINTEGER &size, data = buf_; -#ifdef SOCI_ODBC_WIDE - size = static_cast(maxSize * sizeof(SQLWCHAR)); + size = static_cast(maxSize * sizeof(SQLTCHAR)); +#ifdef SOCI_ODBC_WIDE sqlType = size >= ODBC_MAX_COL_SIZE / sizeof(SQLWCHAR) ? SQL_WLONGVARCHAR : SQL_WVARCHAR; cType = SQL_C_WCHAR; #else - size = static_cast(maxSize); sqlType = size >= ODBC_MAX_COL_SIZE ? SQL_LONGVARCHAR : SQL_VARCHAR; cType = SQL_C_CHAR; #endif // SOCI_ODBC_WIDE diff --git a/tests/common-tests.h b/tests/common-tests.h index 7a9d74ee8..f16c52c6b 100644 --- a/tests/common-tests.h +++ b/tests/common-tests.h @@ -6064,7 +6064,12 @@ static std::string make_long_xml_string(int approximateSize = 5000) { const int tagsSize = 6 + 7; const int patternSize = 26; + +#ifdef SOCI_ODBC_WIDE + const int patternsCount = approximateSize / 2 / patternSize + 1; +#else const int patternsCount = approximateSize / patternSize + 1; +#endif // SOCI_ODBC_WIDE std::string s; s.reserve(tagsSize + patternsCount * patternSize); @@ -6115,11 +6120,7 @@ TEST_CASE_METHOD(common_tests, "CLOB", "[core][clob]") sql << "select s from soci_test where id = 1", into(s2); CHECK(s2.value.size() == 0); -#ifdef SOCI_ODBC_WIDE - s1.value = make_long_xml_string(3000); -#else s1.value = make_long_xml_string(); -#endif sql << "update soci_test set s = :s where id = 1", use(s1); @@ -6185,11 +6186,7 @@ TEST_CASE_METHOD(common_tests, "XML", "[core][xml]") int id = 1; xml_type xml; -#ifdef SOCI_ODBC_WIDE - xml.value = make_long_xml_string(3000); -#else xml.value = make_long_xml_string(); -#endif sql << "insert into soci_test (id, x) values (:1, " << tc_.to_xml(":2") From 0dd10660e7133f3d5131557e1bbd33a7fc4fb531 Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Wed, 20 Mar 2024 02:20:10 +0700 Subject: [PATCH 08/10] Added documentation --- docs/installation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/installation.md b/docs/installation.md index a06738d7b..7e0c064f0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -141,6 +141,7 @@ Some other build options: * `ODBC_LIBRARIES` - string - Full paths to libraries to link SOCI against to enable the backend support. * `SOCI_ODBC` - boolean - Requests to build [ODBC](backends/odbc.md) backend. Automatically switched on, if `WITH_ODBC` is set to ON. * `SOCI_ODBC_TEST_{database}_CONNSTR` - string - ODBC Data Source Name (DSN) or ODBC File Data Source Name (FILEDSN) to test database: Microsoft Access (.mdb), Microsoft SQL Server, MySQL, PostgreSQL or any other ODBC SQL data source. {database} is placeholder for name of database driver ACCESS, MYSQL, POSTGRESQL, etc. See [ODBC](backends/odbc.md) backend reference for details. Example: `-DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="FILEDSN=/home/mloskot/soci/build/test-postgresql.dsn"` +* `SOCI_ODBC_UNICODE` - boolean - Enables Unicode support for the ODBC backend. This option configures SOCI to use UTF-16 encoding internally for ODBC operations, allowing for the handling of Unicode data. This feature requires the use of UTF-8 encoded std::string in the user-facing API, which SOCI will implicityly convert to and from UTF-16 as needed. Default is OFF. #### Oracle From 29f592880a646441ec55217b5c802b7889ad6581 Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Wed, 20 Mar 2024 02:20:10 +0700 Subject: [PATCH 09/10] Added documentation --- docs/installation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/installation.md b/docs/installation.md index a06738d7b..1d3d86e49 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -141,6 +141,7 @@ Some other build options: * `ODBC_LIBRARIES` - string - Full paths to libraries to link SOCI against to enable the backend support. * `SOCI_ODBC` - boolean - Requests to build [ODBC](backends/odbc.md) backend. Automatically switched on, if `WITH_ODBC` is set to ON. * `SOCI_ODBC_TEST_{database}_CONNSTR` - string - ODBC Data Source Name (DSN) or ODBC File Data Source Name (FILEDSN) to test database: Microsoft Access (.mdb), Microsoft SQL Server, MySQL, PostgreSQL or any other ODBC SQL data source. {database} is placeholder for name of database driver ACCESS, MYSQL, POSTGRESQL, etc. See [ODBC](backends/odbc.md) backend reference for details. Example: `-DSOCI_ODBC_TEST_POSTGRESQL_CONNSTR="FILEDSN=/home/mloskot/soci/build/test-postgresql.dsn"` +* `SOCI_ODBC_UNICODE` - boolean - Enables Unicode support for the ODBC backend. This option configures SOCI to use UTF-16 encoding internally for ODBC operations, allowing for the handling of Unicode data. UTF-8 strings will implicitly be converted to and from UTF-16 as needed. Default is OFF. #### Oracle From a0d06861e1ded4d7f192db9ec2e427eca96bb550 Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Wed, 20 Mar 2024 02:53:50 +0700 Subject: [PATCH 10/10] removed u8 --- tests/odbc/test-odbc-mssql.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/odbc/test-odbc-mssql.cpp b/tests/odbc/test-odbc-mssql.cpp index 20f2417c6..50784f103 100644 --- a/tests/odbc/test-odbc-mssql.cpp +++ b/tests/odbc/test-odbc-mssql.cpp @@ -91,7 +91,7 @@ TEST_CASE("MS SQL unicode string", "[odbc][mssql][unicode]") } unicode_table_creator(sql); // std::string str_in { "สวัสดีชาวโลก!" }; - std::string str_in { u8"\u0E2A\u0E27\u0E31\u0E2A\u0E14\u0E35\u0E0A\u0E32\u0E27\u0E42\u0E25\u0E01!" }; + std::string str_in { "\u0E2A\u0E27\u0E31\u0E2A\u0E14\u0E35\u0E0A\u0E32\u0E27\u0E42\u0E25\u0E01!" }; CHECK_NOTHROW(( sql << "insert into soci_test(str) values(N'" + str_in + "')"