Поделиться через


Изменение поведения драйвера 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.

См. также:

Компоненты собственного клиента SQL Server