Изменение поведения драйвера ODBC при обработке преобразования символов
Драйвер ODBC для SQL Server 2012 native Client (SQLNCLI11.dll) изменил способ преобразования SQL_WCHAR* (NCHAR/NVARCHAR/NVARCHAR(MAX)) и SQL_CHAR* (CHAR/VARCHAR/NARCHAR(MAX)). Функции ODBC, такие как SQLGetData, SQLBindCol, SQLBindParameter, возвращают (-4) SQL_NO_TOTAL в качестве параметра длины или индикатора при использовании драйвера ODBC для собственного клиента SQL Server 2012. В предыдущих версиях драйвера ODBC SQL Server Native Client возвращалось значение длины, которое может быть неправильным.
Поведение SQLGetData
Многие функции Windows позволяют указывать нулевой размер буфера, при этом возвращаемая длина является размером возвращаемых данных. Следующий вариант является стандартным для программистов Windows:
int iSize = 0;
BYTE * pBuffer = NULL;
GetMyFavoriteAPI(pBuffer, &iSize); // Returns needed size in iSize
pBuffer = new BYTE[iSize]; // Allocate buffer
GetMyFavoriteAPI(pBuffer, &iSize); // Retrieve actual data
Однако в этом сценарии не следует использовать SQLGetData . Не следует использовать следующий вариант.
// bad
int iSize = 0;
WCHAR * pBuffer = NULL;
SQLGetData(hstmt, SQL_W_CHAR, ...., (SQLPOINTER*)0x1, 0, &iSize); // Get storage size needed
pBuffer = new WCHAR[(iSize/sizeof(WCHAR)) + 1]; // Allocate buffer
SQLGetData(hstmt, SQL_W_CHAR, ...., (SQLPOINTER*)pBuffer, iSize, &iSize); // Retrieve data
SQLGetData можно вызывать только для получения фрагментов фактических данных. Использование SQLGetData для получения размера данных не поддерживается.
Далее показано влияние изменения драйвера, которое проявляется при использовании неверного варианта. Это приложение запрашивает столбец и привязку varchar
как Юникод (SQL_UNICODE/SQL_WCHAR):
Запрос: select convert(varchar(36), '123')
SQLGetData(hstmt, SQL_WCHAR, ....., (SQLPOINTER*) 0x1, 0 , &iSize); // Attempting to determine storage size needed
SQL Server Native Client версия драйвера ODBC | Итоговая длина или индикатор | Описание |
---|---|---|
SQL Server 2008 R2 Native Client или более ранней версии | 6 | Драйвер ошибочно предположил, что преобразование CHAR в WCHAR можно было выполнить как умножение длины на 2. |
SQL Server 2012 Native Client (версия 11.0.2100.60) или более поздней версии | -4 (SQL_NO_TOTAL) | Драйвер больше не предполагает, что преобразование из CHAR в WCHAR или из WCHAR в CHAR является действием (умножения) *2 или (деления)/2. Вызов SQLGetData больше не возвращает длину ожидаемого преобразования. Драйвер обнаруживает преобразование из CHAR в WCHAR или обратное преобразование и возвращает (-4) SQL_NO_TOTAL вместо *2 или /2, что могло быть неверным. |
Используйте SQLGetData для получения блоков данных. (Показан псевдокод).
while( (SQL_SUCCESS or SQL_SUCCESS_WITH_INFO) == SQLFetch(...) ) {
SQLNumCols(...iTotalCols...)
for(int iCol = 1; iCol < iTotalCols; iCol++) {
WCHAR* pBufOrig, pBuffer = new WCHAR[100];
SQLGetData(.... iCol ... pBuffer, 100, &iSize); // Get original chunk
while(NOT ALL DATA RETREIVED (SQL_NO_TOTAL, ...) ) {
pBuffer += 50; // Advance buffer for data retrieved
// May need to realloc the buffer when you reach current size
SQLGetData(.... iCol ... pBuffer, 100, &iSize); // Get next chunk
}
}
}
Поведение функции SQLBindCol
Запрос: select convert(varchar(36), '1234567890')
SQLBindCol(... SQL_W_CHAR, ...) // Only bound a buffer of WCHAR[4] - Expecting String Data Right Truncation behavior
SQL Server Native Client версия драйвера ODBC | Итоговая длина или индикатор | Описание |
---|---|---|
SQL Server 2008 R2 Native Client или более ранней версии | 20 | - SQLFetch сообщает, что в правой части данных имеется усечение. — Длина — это длина возвращаемых данных, а не то, что было сохранено (предполагается преобразование *2 CHAR в WCHAR, которое может быть неправильным для глифов). — Данные, хранящиеся в буфере, — 123\0. Буфер гарантированно заканчивается на NULL. |
SQL Server 2012 Native Client (версия 11.0.2100.60) или более поздней версии | -4 (SQL_NO_TOTAL) | - SQLFetch сообщает, что в правой части данных имеется усечение. — Длина означает -4 (SQL_NO_TOTAL), так как остальные данные не были преобразованы. — Данные, хранящиеся в буфере, — 123\0. - Буфер гарантированно заканчивается на NULL. |
SQLBindParameter (поведение параметра OUTPUT)
Запрос: create procedure spTest @p1 varchar(max) OUTPUT
select @p1 = replicate('B', 1234)
SQLBindParameter(... SQL_W_CHAR, ...) // Only bind up to first 64 characters
SQL Server Native Client версия драйвера ODBC | Итоговая длина или индикатор | Описание |
---|---|---|
SQL Server 2008 R2 Native Client или более ранней версии | 2468 | - SQLFetch не возвращает больше доступных данных. - SQLMoreResults не возвращает больше доступных данных. — Длина указывает размер данных, возвращаемых с сервера, а не хранящихся в буфере. — Исходный буфер содержит 63 байта и признак конца NULL. Буфер гарантированно заканчивается на NULL. |
SQL Server 2012 Native Client (версия 11.0.2100.60) или более поздней версии | -4 (SQL_NO_TOTAL) | - SQLFetch не возвращает больше доступных данных. - SQLMoreResults не возвращает больше доступных данных. — Длина указывает на (-4) SQL_NO_TOTAL, так как остальные данные не были преобразованы. — Исходный буфер содержит 63 байта и признак конца NULL. Буфер гарантированно заканчивается на NULL. |
Выполнение преобразований CHAR и WCHAR
Драйвер ODBC для собственного клиента SQL Server 2012 предлагает несколько способов преобразования CHAR и WCHAR. Логика аналогична управлению большими двоичными объектами (varchar(max), nvarchar(max), ...):
Данные сохраняются или усекаются в указанный буфер при привязке с помощью SQLBindCol или SQLBindParameter.
Если привязка не выполняется, данные можно получить блоками с помощью SQLGetData и SQLParamData.