Zmiana zachowania sterownika ODBC podczas obsługi konwersji znaków

Dotyczy:sql ServerAzure SQL DatabaseAzure SQL Managed InstanceAzure Synapse Analytics

Ważne

Klient natywny SQL Servera (SNAC) nie jest dostarczany z:

  • SQL Server 2022 (16.x) i nowsze wersje
  • SQL Server Management Studio 19 i nowsze wersje

Program SQL Server Native Client (SQLNCLI lub SQLNCLI11) oraz starszy dostawca microsoft OLE DB dla programu SQL Server (SQLOLEDB) nie są zalecane w przypadku tworzenia nowych aplikacji.

W przypadku nowych projektów użyj jednego z następujących sterowników:

W przypadku programu SQLNCLI dostarczanego jako składnik aparatu bazy danych programu SQL Server (wersje 2012–2019), proszę sprawdzić wyjątek cyklu wsparcia .

Sterownik ODBC z natywnego klienta z SQL Server 2012 (11.x) (SQLNCLI11.dll) zmienił sposób realizacji konwersji SQL_WCHAR* (NCHAR/NVARCHAR/NVARCHAR(MAX)) i SQL_CHAR* (CHAR/VARCHAR/NARCHAR(MAX)). Funkcje ODBC, takie jak SQLGetData, SQLBindCol, SQLBindParameter, return (-4) SQL_NO_TOTAL jako parametr długości/wskaźnika podczas korzystania z sterownika ODBC Native Client SQL Server 2012. Poprzednie wersje sterownika ODBC dla natywnego klienta SQL Server zwracały wartość długości, która może być nieprawidłowa.

Zachowanie SQLGetData

Wiele funkcji Windows pozwala określić rozmiar bufora 0, a zwrócona długość to rozmiar zwróconych danych. Następujący wzorzec jest powszechny wśród programistów 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  

Jednak SQLGetData nie powinno być używane w tym scenariuszu. Nie powinno się stosować następującego wzoru:

// 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 można wywołać tylko w celu pobrania fragmentów rzeczywistych danych. Używanie SQLGetData do określania rozmiaru danych nie jest nieobsługiwane.

Poniżej przedstawiono wpływ zmiany sterownika przy użyciu nieprawidłowego wzoru. Ta aplikacja zapytuje kolumnę varchar i powiązanie jako Unicode (SQL_UNICODE/SQL_WCHAR):

Zapytanie: select convert(varchar(36), '123')

SQLGetData(hstmt, SQL_WCHAR, ....., (SQLPOINTER*) 0x1, 0 , &iSize);   // Attempting to determine storage size needed  
SQL Server Native Client ODBC Driver version Długość lub wskaźnik wynik Description
SQL Server 2008 R2 (10.50.x) Natywny klient lub wcześniejszy 6 Maszynista błędnie założył, że konwersja CHAR na WCHAR może być wykonana jako długość * 2.
SQL Server 2012 (11.x) Natywny klient (wersja 11.0.2100.60) lub nowszy -4 (SQL_NO_TOTAL) Kierowca nie zakłada już, że konwersja z CHAR na WCHAR lub WCHAR na CHAR to akcja (mnoż) *2 lub (dziel)/2.

Wywołanie SQLGetData nie zwraca już długości oczekiwanej konwersji. Sterownik wykrywa konwersję do lub z CHAR i WCHAR i zwraca (-4) SQL_NO_TOTAL zamiast *2 lub /2, które może być nieprawidłowe.

Użyj SQLGetData do pobrania fragmentów danych. (Pokazano pseudokod:)

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 RETRIEVED (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  
      }  
   }  
}  

Zachowanie SQLBindCol

Zapytanie: 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 Driver version Długość lub wskaźnik wynik Description
SQL Server 2008 R2 (10.50.x) Natywny klient lub wcześniejszy 20 SQLFetch informuje, że po prawej stronie danych jest obcięcie.

Długość to długość zwróconych danych, a nie to, co zostało zapisane (zakłada konwersję *2 CHAR na WCHAR, co może być błędne dla glifów).

Dane przechowywane w buforze mają 123\0. Bufor jest gwarantowany zakończeniem NULL.
SQL Server 2012 (11.x) Natywny klient (wersja 11.0.2100.60) lub nowszy -4 (SQL_NO_TOTAL) SQLFetch informuje, że po prawej stronie danych jest obcięcie.

Długość wskazuje na -4 (SQL_NO_TOTAL), ponieważ pozostałe dane nie zostały przekonwertowane.

Dane przechowywane w buforze to 123\0. - Bufor jest gwarantowany zakończenie NULL.

SQLBindParameter (Zachowanie parametrów wyjściowych)

Zapytanie: 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 Driver version Długość lub wskaźnik wynik Description
SQL Server 2008 R2 (10.50.x) Natywny klient lub wcześniejszy 2468 SQLFetch nie zwraca już dostępnych danych.

SQLMoreResults nie zwraca już dostępnych danych.

Długość wskazuje rozmiar danych zwracanych z serwera, które nie są przechowywane w buforze.

Oryginalny bufor zawiera 63 bajty oraz terminator NULL. Bufor jest gwarantowany zakończeniem NULL.
SQL Server 2012 (11.x) Natywny klient (wersja 11.0.2100.60) lub nowszy -4 (SQL_NO_TOTAL) SQLFetch nie zwraca już dostępnych danych.

SQLMoreResults nie zwraca już dostępnych danych.

Długość wskazuje (-4) SQL_NO_TOTAL, ponieważ pozostałe dane nie zostały przekonwertowane.

Oryginalny bufor zawiera 63 bajty oraz terminator NULL. Bufor jest gwarantowany zakończeniem NULL.

Przeprowadzanie konwersji CHAR i WCHAR

Natywny sterownik ODBC klienta SQL Server 2012 (11.x) oferuje kilka sposobów wykonywania konwersji CHAR i WCHAR. Logika jest podobna do manipulowania plamami (varchar(max), nvarchar(max), ...):

  • Dane są zapisywane lub obcinane do określonego bufora podczas wiązania z SQLBindCol lub SQLBindParametrem.

  • Jeśli nie masz bindu, możesz pobierać dane w kawałkach, używając SQLGetData i SQLParamData.