Alteração de comportamento do driver ODBC ao lidar com conversões de caracteres

Aplica-se a:SQL ServerBase de Dados SQL do AzureInstância Gerida do Azure SQLAzure Synapse Analytics

Importante

SQL Server Native Client (SNAC) não é fornecido com:

  • SQL Server 2022 (16.x) e versões posteriores
  • SQL Server Management Studio 19 e versões posteriores

O SQL Server Native Client (SQLNCLI ou SQLNCLI11) e o Microsoft OLE DB Provider for SQL Server (SQLOLEDB) herdado não são recomendados para o desenvolvimento de novos aplicativos.

Para novos projetos, use um dos seguintes drivers:

Para o SQLNCLI fornecido como componente do Mecanismo de Base de Dados do SQL Server (versões de 2012 a 2019), consulte esta exceção ao Ciclo de Vida de Suporte .

O Driver ODBC de Cliente Nativo de SQL Server 2012 (11.x) (SQLNCLI11.dll) alterou a forma como executa as conversões SQL_WCHAR* (NCHAR/NVARCHAR/NVARCHAR(MAX)) e SQL_CHAR* (CHAR/VARCHAR/NARCHAR(MAX)). Funções ODBC, como SQLGetData, SQLBindCol, SQLBindParameter, return (-4) SQL_NO_TOTAL como parâmetro de comprimento/indicador ao utilizar o driver ODBC do SQL Server Native Client 2012. Versões anteriores do driver ODBC do SQL Server Native Client devolviam um valor de comprimento, que pode estar incorreto.

Comportamento do SQLGetData

Muitas funções do Windows permitem especificar um tamanho de buffer de 0 e o comprimento devolvido é o tamanho dos dados devolvidos. O seguinte padrão é comum entre programadores de 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  

No entanto, o SQLGetData não deve ser utilizado neste cenário. O seguinte padrão não deve ser utilizado:

// 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  

O SQLGetData só pode ser chamado para recuperar pedaços de dados reais. Usar SQLGetData para obter o tamanho dos dados não é desnecessário.

O que se segue mostra o impacto da mudança do driver quando se usa o padrão incorreto. Esta aplicação consulta uma coluna varchar e a ligação como Unicode (SQL_UNICODE/SQL_WCHAR):

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

SQLGetData(hstmt, SQL_WCHAR, ....., (SQLPOINTER*) 0x1, 0 , &iSize);   // Attempting to determine storage size needed  
Versão do Driver ODBC do SQL Server Native Client Duração ou Resultado Indicador Description
SQL Server 2008 R2 (10.50.x) Cliente Nativo ou anterior 6 O maquinista assumiu incorretamente que converter CHAR para WCHAR poderia ser feito com o comprimento * 2.
SQL Server 2012 (11.x) Cliente Nativo (versão 11.0.2100.60) ou posterior -4 (SQL_NO_TOTAL) O condutor já não assume que converter de CHAR para WCHAR ou de WCHAR para CHAR é uma ação (multiplicar) *2 ou (dividir)/2.

Chamar SQLGetData já não retorna o comprimento da conversão esperada. O condutor deteta a conversão para ou a partir de CHAR e WCHAR e devolve (-4) SQL_NO_TOTAL em vez de *2 ou /2 comportamentos que podem estar incorretos.

Usa SQLGetData para recuperar os pedaços dos dados. (Pseudocódigo mostrado:)

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

Comportamento do SQLBindCol

Consulta: select convert(varchar(36), '1234567890')

SQLBindCol(... SQL_W_CHAR, ...)   // Only bound a buffer of WCHAR[4] - Expecting String Data Right Truncation behavior  
Versão do Driver ODBC do SQL Server Native Client Duração ou Resultado Indicador Description
SQL Server 2008 R2 (10.50.x) Cliente Nativo ou anterior 20 O SQLFetch reporta que existe um truncamento no lado direito dos dados.

O comprimento é o comprimento dos dados devolvidos, não o que foi armazenado (assume *2 conversão de CHAR para WCHAR, o que pode estar incorreto para glifos).

Os dados armazenados no buffer são 123/0. O buffer é garantido como terminado por NULL.
SQL Server 2012 (11.x) Cliente Nativo (versão 11.0.2100.60) ou posterior -4 (SQL_NO_TOTAL) O SQLFetch reporta que existe um truncamento no lado direito dos dados.

O comprimento indica -4 (SQL_NO_TOTAL) porque o resto dos dados não foi convertido.

Os dados armazenados no buffer são 123\0. - O buffer é garantido como terminado por NULL.

SQLBindParameter (Comportamento do Parâmetro OUTPUT)

Consulta: create procedure spTest @p1 varchar(max) OUTPUT

select @p1 = replicate('B', 1234)

SQLBindParameter(... SQL_W_CHAR, ...)   // Only bind up to first 64 characters  
Versão do Driver ODBC do SQL Server Native Client Duração ou Resultado Indicador Description
SQL Server 2008 R2 (10.50.x) Cliente Nativo ou anterior 2468 O SQLFetch não retorna mais dados disponíveis.

O SQLMoreResults não retorna mais dados disponíveis.

O comprimento indica o tamanho dos dados devolvidos pelo servidor, não armazenados em buffer.

O buffer original contém 63 bytes e um terminador NULL. O buffer é garantido como terminado por NULL.
SQL Server 2012 (11.x) Cliente Nativo (versão 11.0.2100.60) ou posterior -4 (SQL_NO_TOTAL) O SQLFetch não retorna mais dados disponíveis.

O SQLMoreResults não retorna mais dados disponíveis.

O comprimento indica (-4) SQL_NO_TOTAL porque o resto dos dados não foi convertido.

O buffer original contém 63 bytes e um terminador NULL. O buffer é garantido como terminado por NULL.

Realização de conversões CHAR e WCHAR

O driver SQL Server 2012 (11.x) Native Client ODBC oferece várias formas de realizar conversões CHAR e WCHAR. A lógica é semelhante à manipulação de blobs (varchar(max), nvarchar(max), ...):

  • Os dados são guardados ou truncados no buffer especificado quando ligados com SQLBindCol ou SQLBindParameter.

  • Se não fizeres bind, podes recuperar os dados em blocos usando SQLGetData e SQLParamData.