Skip to content

Commit

Permalink
fix(ODBC): Poco:Data::ODBC - MSSQL (n)varchar(max) length issue #4324
Browse files Browse the repository at this point in the history
  • Loading branch information
aleks-f committed Oct 15, 2024
1 parent 71a085c commit 26c3d78
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 183 deletions.
10 changes: 5 additions & 5 deletions Data/DataTest/src/SQLExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2426,14 +2426,14 @@ void SQLExecutor::blob(int bigSize, const std::string& blobPlaceholder)
catch(DataException& ce)
{
std::cout << ce.displayText() << std::endl;
fail (__func__, __LINE__, __FILE__);
failmsg (__func__);
}

try { session() << "SELECT COUNT(*) FROM Person", into(count), now; }
catch(DataException& ce)
{
std::cout << ce.displayText() << std::endl;
fail (__func__, __LINE__, __FILE__);
failmsg (__func__);
}

assertTrue (count == 1);
Expand All @@ -2444,7 +2444,7 @@ void SQLExecutor::blob(int bigSize, const std::string& blobPlaceholder)
catch(DataException& ce)
{
std::cout << ce.displayText() << std::endl;
fail (__func__, __LINE__, __FILE__);
failmsg (__func__);
}

assertTrue (res == img);
Expand All @@ -2459,7 +2459,7 @@ void SQLExecutor::blob(int bigSize, const std::string& blobPlaceholder)
catch(DataException& ce)
{
std::cout << ce.displayText() << std::endl;
fail (__func__, __LINE__, __FILE__);
failmsg (__func__);
}

try
Expand All @@ -2470,7 +2470,7 @@ void SQLExecutor::blob(int bigSize, const std::string& blobPlaceholder)
catch(DataException& ce)
{
std::cout << ce.displayText() << std::endl;
fail (__func__, __LINE__, __FILE__);
failmsg (__func__);
}

// sometimes throws (intentionally, caught in caller)
Expand Down
152 changes: 79 additions & 73 deletions Data/ODBC/ODBC_vs170.vcxproj

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions Data/ODBC/include/Poco/Data/ODBC/ODBC.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
#if __has_include(<msodbcsql.h>)
#include <msodbcsql.h>
#define POCO_DATA_ODBC_HAVE_SQL_SERVER_EXT

// To disable varchar(max) > 8000 bytes, set to 0.
// Note that this setting works in conjunction with
// the session "maxFieldSize" property, which ultimately
// determines the max string length available.
#define POCO_DATA_SQL_SERVER_BIG_STRINGS 1
#endif
#endif

Expand Down
47 changes: 38 additions & 9 deletions Data/ODBC/src/Binder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,11 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir)
{
std::string tcVal;
transcode(val, tcVal);
size = (SQLINTEGER)tcVal.size();
size = static_cast<SQLINTEGER>(tcVal.size());
pTCVal = reinterpret_cast<char*>(std::calloc((size_t)size+1, 1));
std::memcpy(pTCVal, tcVal.data(), size);
}
else size = (SQLINTEGER)val.size();
else size = static_cast<SQLINTEGER>(val.size());
SQLPOINTER pVal = 0;
SQLINTEGER colSize = 0;
SQLSMALLINT decDigits = 0;
Expand All @@ -134,7 +134,7 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir)
{
getColumnOrParameterSize(pos, size);
char* pChar = (char*) std::calloc(size, sizeof(char));
pVal = (SQLPOINTER) pChar;
pVal = static_cast<SQLPOINTER>(pChar);
_outParams.insert(ParamMap::value_type(pVal, size));
_strings.insert(StringMap::value_type(pChar, const_cast<std::string*>(&val)));
}
Expand All @@ -161,16 +161,22 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir)

_lengthIndicator.push_back(pLenIn);

if (Utility::isError(SQLBindParameter(_rStmt,
(SQLUSMALLINT) pos + 1,
int rc = SQLBindParameter(_rStmt,
(SQLUSMALLINT)pos + 1,
toODBCDirection(dir),
SQL_C_CHAR,
Utility::sqlDataType(SQL_C_CHAR),
(SQLUINTEGER) colSize,
#if defined(POCO_DATA_ODBC_HAVE_SQL_SERVER_EXT) && POCO_DATA_SQL_SERVER_BIG_STRINGS
SQL_SS_LENGTH_UNLIMITED,
#else
(SQLUINTEGER)colSize,
#endif
0,
pVal,
(SQLINTEGER) size,
_lengthIndicator.back())))
(SQLINTEGER)size,
_lengthIndicator.back());

if (Utility::isError(rc))
{
throw StatementException(_rStmt, "ODBC::Binder::bind(string):SQLBindParameter(std::string)");
}
Expand Down Expand Up @@ -217,7 +223,11 @@ void Binder::bind(std::size_t pos, const UTF16String& val, Direction dir)
toODBCDirection(dir),
SQL_C_WCHAR,
Utility::sqlDataType(SQL_C_WCHAR),
#if defined(POCO_DATA_ODBC_HAVE_SQL_SERVER_EXT) && POCO_DATA_SQL_SERVER_BIG_STRINGS
SQL_SS_LENGTH_UNLIMITED,
#else
(SQLUINTEGER)colSize,
#endif
0,
pVal,
(SQLINTEGER)size,
Expand Down Expand Up @@ -521,16 +531,35 @@ void Binder::getColSizeAndPrecision(std::size_t pos,
Dynamic::Var tmp;
bool foundSize(false);
bool foundPrec(false);

#ifdef POCO_DATA_ODBC_HAVE_SQL_SERVER_EXT
bool isVarchar(false);
switch (sqlDataType)
{
case SQL_VARCHAR:
case SQL_WVARCHAR:
case SQL_WLONGVARCHAR:
isVarchar = true;
break;
default: break;
}
#endif // POCO_DATA_ODBC_HAVE_SQL_SERVER_EXT

foundSize = _pTypeInfo->tryGetInfo(cDataType, "COLUMN_SIZE", tmp);
if (foundSize) colSize = tmp;
else foundSize = _pTypeInfo->tryGetInfo(sqlDataType, "COLUMN_SIZE", tmp);
if (foundSize) colSize = tmp;

if (actualSize > static_cast<std::size_t>(colSize))
if (actualSize > static_cast<std::size_t>(colSize)
#ifdef POCO_DATA_ODBC_HAVE_SQL_SERVER_EXT
&& !isVarchar
#endif
)
{
throw LengthExceededException(Poco::format("ODBC::Binder::getColSizeAndPrecision();%d: Error binding column %z size=%z, max size=%ld)",
__LINE__, pos, actualSize, static_cast<long>(colSize)));
}

foundPrec = _pTypeInfo->tryGetInfo(cDataType, "MAXIMUM_SCALE", tmp);
if (foundPrec) decDigits = tmp;
else foundPrec = _pTypeInfo->tryGetInfo(sqlDataType, "MAXIMUM_SCALE", tmp);
Expand Down
73 changes: 45 additions & 28 deletions Data/ODBC/src/Extractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,7 @@ bool Extractor::extractManualImpl<std::string>(std::size_t pos, std::string& val
std::size_t totalSize = 0;

SQLLEN len;
const int bufSize = CHUNK_SIZE;
Poco::Buffer<char> apChar(bufSize);
Poco::Buffer<char> apChar(CHUNK_SIZE);
char* pChar = apChar.begin();
SQLRETURN rc = 0;

Expand All @@ -291,15 +290,29 @@ bool Extractor::extractManualImpl<std::string>(std::size_t pos, std::string& val

do
{
std::memset(pChar, 0, bufSize);
std::memset(pChar, 0, CHUNK_SIZE);
len = 0;
rc = SQLGetData(_rStmt,
(SQLUSMALLINT) pos + 1,
cType, //C data type
pChar, //returned value
bufSize, //buffer length
CHUNK_SIZE, //buffer length
&len); //length indicator

if (SQL_SUCCESS_WITH_INFO == rc)
{
StatementDiagnostics d(_rStmt);
std::size_t fieldCount = d.fields().size();
for (int i = 0; i < fieldCount; ++i)
{
if (d.sqlState(i) == "01004"s)
{
if (len == SQL_NO_TOTAL || len > CHUNK_SIZE) // only part of data was returned
len = CHUNK_SIZE-1; // SQLGetData terminates the returned string
}
}
}

if (SQL_NO_DATA != rc && Utility::isError(rc))
throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl(string):SQLGetData()");

Expand All @@ -316,12 +329,11 @@ bool Extractor::extractManualImpl<std::string>(std::size_t pos, std::string& val
break;

_lengths[pos] += len;
fetchedSize = _lengths[pos] > CHUNK_SIZE ? CHUNK_SIZE : _lengths[pos];
totalSize += fetchedSize;
if (totalSize <= maxSize)
val.append(pChar, fetchedSize);
if (_lengths[pos] <= maxSize)
val.append(pChar, len);
else
throw DataException(format(FLD_SIZE_EXCEEDED_FMT, fetchedSize, maxSize));
throw DataException(format(FLD_SIZE_EXCEEDED_FMT, static_cast<std::size_t>(_lengths[pos]), maxSize));

}while (true);

return true;
Expand All @@ -332,12 +344,8 @@ template<>
bool Extractor::extractManualImpl<UTF16String>(std::size_t pos, UTF16String& val, SQLSMALLINT cType)
{
std::size_t maxSize = _pPreparator->getMaxFieldSize();
std::size_t fetchedSize = 0;
std::size_t totalSize = 0;

SQLLEN len;
const int bufSize = CHUNK_SIZE;
Poco::Buffer<UTF16String::value_type> apChar(bufSize);
Poco::Buffer<UTF16String::value_type> apChar(CHUNK_SIZE);
UTF16String::value_type* pChar = apChar.begin();
SQLRETURN rc = 0;

Expand All @@ -346,15 +354,29 @@ bool Extractor::extractManualImpl<UTF16String>(std::size_t pos, UTF16String& val

do
{
std::memset(pChar, 0, bufSize);
std::memset(pChar, 0, CHUNK_SIZE);
len = 0;
rc = SQLGetData(_rStmt,
(SQLUSMALLINT)pos + 1,
cType, //C data type
pChar, //returned value
bufSize, //buffer length
CHUNK_SIZE, //buffer length
&len); //length indicator

if (SQL_SUCCESS_WITH_INFO == rc)
{
StatementDiagnostics d(_rStmt);
std::size_t fieldCount = d.fields().size();
for (int i = 0; i < fieldCount; ++i)
{
if (d.sqlState(i) == "01004"s)
{
if (len == SQL_NO_TOTAL || len > CHUNK_SIZE) // only part of data was returned
len = CHUNK_SIZE;
}
}
}

if (SQL_NO_DATA != rc && Utility::isError(rc))
throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl(UTF16String):SQLGetData()");

Expand All @@ -371,12 +393,10 @@ bool Extractor::extractManualImpl<UTF16String>(std::size_t pos, UTF16String& val
break;

_lengths[pos] += len;
fetchedSize = _lengths[pos] > CHUNK_SIZE ? CHUNK_SIZE : _lengths[pos];
totalSize += fetchedSize;
if (totalSize <= maxSize)
val.append(pChar, fetchedSize / sizeof(UTF16Char));
if (_lengths[pos] <= maxSize)
val.append(pChar, len / sizeof(UTF16Char));
else
throw DataException(format(FLD_SIZE_EXCEEDED_FMT, fetchedSize, maxSize));
throw DataException(format(FLD_SIZE_EXCEEDED_FMT, static_cast<std::size_t>(_lengths[pos]), maxSize));
} while (true);

return true;
Expand All @@ -390,7 +410,6 @@ bool Extractor::extractManualImpl<Poco::Data::CLOB>(std::size_t pos,
{
std::size_t maxSize = _pPreparator->getMaxFieldSize();
std::size_t fetchedSize = 0;
std::size_t totalSize = 0;

SQLLEN len;
const int bufSize = CHUNK_SIZE;
Expand All @@ -406,14 +425,12 @@ bool Extractor::extractManualImpl<Poco::Data::CLOB>(std::size_t pos,
std::memset(pChar, 0, bufSize);
len = 0;
rc = SQLGetData(_rStmt,
(SQLUSMALLINT) pos + 1,
(SQLUSMALLINT)pos + 1,
cType, //C data type
pChar, //returned value
bufSize, //buffer length
&len); //length indicator

_lengths[pos] += len;

if (SQL_NO_DATA != rc && Utility::isError(rc))
throw StatementException(_rStmt, "ODBC::Extractor::extractManualImpl(CLOB):SQLGetData()");

Expand All @@ -427,13 +444,13 @@ bool Extractor::extractManualImpl<Poco::Data::CLOB>(std::size_t pos,
break;

fetchedSize = len > CHUNK_SIZE ? CHUNK_SIZE : len;
totalSize += fetchedSize;
if (totalSize <= maxSize)
_lengths[pos] += fetchedSize;
if (_lengths[pos] <= maxSize)
val.appendRaw(pChar, fetchedSize);
else
throw DataException(format(FLD_SIZE_EXCEEDED_FMT, fetchedSize, maxSize));

}while (true);
} while (true);

return true;
}
Expand Down
Loading

0 comments on commit 26c3d78

Please sign in to comment.