diff --git a/CHANGES b/CHANGES index 21c09ed0e59deadefecbeb86ca866fb34bff360c..b66729305d1b9778a662c321ee36e5b834aed6f6 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,9 @@ Version 4.0.0 differs from 3.2.x in the following ways: -- Add SOCI_FIREBIRD_EMBEDDED option to allow building with embedded library. -- Throw an exception instead of truncating too long VARCHAR columns values. +- ODBC/MS SQL +-- Fix inserting strings of length greater than 8000 bytes into database. + - Oracle -- Use SQLT_BDOUBLE for floating point values instead of SQLT_FLT. diff --git a/src/backends/odbc/standard-use-type.cpp b/src/backends/odbc/standard-use-type.cpp index d32c5b749a0c5f85cfd43edfc00c23139ee75c39..f5b126ec0536702f9f69861f86424ba98dce9c02 100644 --- a/src/backends/odbc/standard-use-type.cpp +++ b/src/backends/odbc/standard-use-type.cpp @@ -99,6 +99,15 @@ void* odbc_standard_use_type_backend::prepare_for_bind( memcpy(buf_, s.c_str(), size); buf_[size++] = '\0'; indHolder_ = SQL_NTS; + + // Strings of greater length are silently truncated at 8000 limit by MS + // SQL unless SQL_SS_LENGTH_UNLIMITED (which is defined as 0, but not + // available in all headers) is used. + if (size > 8000) + { + sqlType = SQL_LONGVARCHAR; + size = 0 /* SQL_SS_LENGTH_UNLIMITED */; + } } break; case x_stdtm: diff --git a/tests/odbc/test-odbc-mssql.cpp b/tests/odbc/test-odbc-mssql.cpp index 479ffedf9783b5678ef74706e1bbf461cfe9f032..f4511c9ebe929d9fe8bb8819873fb71877e57870 100644 --- a/tests/odbc/test-odbc-mssql.cpp +++ b/tests/odbc/test-odbc-mssql.cpp @@ -19,6 +19,60 @@ using namespace soci::tests; std::string connectString; backend_factory const &backEnd = *soci::factory_odbc(); +// MS SQL-specific tests +TEST_CASE("MS SQL long string", "[odbc][mssql][long]") +{ + session sql(backEnd, connectString); + + struct long_text_table_creator : public table_creator_base + { + explicit long_text_table_creator(session& sql) + : table_creator_base(sql) + { + // Notice that 4000 is the maximal length of an nvarchar() column, + // at least when using FreeTDS ODBC driver. + sql << "create table soci_test (" + "long_text nvarchar(max) null, " + "fixed_text nvarchar(4000) null" + ")"; + } + } long_text_table_creator(sql); + + // Build a string at least 8000 characters long to test that it survives + // the round trip unscathed. + std::ostringstream os; + for ( int n = 0; n < 1000; ++n ) + { + os << "Line #" << n << "\n"; + } + + std::string const str_in = os.str(); + sql << "insert into soci_test(long_text) values(:str)", use(str_in); + + std::string str_out; + sql << "select long_text from soci_test", into(str_out); + + // Don't just compare the strings because the error message in case they + // differ is completely unreadable due to their size, so give a better + // error in the common failure case. + if (str_out.length() != str_in.length()) + { + FAIL("Read back string of length " << str_out.length() << + " instead of expected " << str_in.length()); + } + else + { + CHECK(str_out == str_in); + } + + // The long string should be truncated when inserting it into a fixed size + // column. + CHECK_THROWS_AS( + (sql << "insert into soci_test(fixed_text) values(:str)", use(str_in)), + soci_error + ); +} + // DDL Creation objects for common tests struct table_creator_one : public table_creator_base {