处理字符转换时 ODBC 驱动程序行为的变化

适用于:SQL Server Azure SQL 数据库 Azure SQL 托管实例 Azure Synapse Analytics Analytics Platform System (PDW)

重要

SQL Server Native Client (SNAC) 未随附:

  • SQL Server 2022 (16.x) 及更高版本
  • SQL Server Management Studio 19 及更高版本

不建议使用 SQL Server Native Client(SQLNCLI 或 SQLNCLI11)和旧的 Microsoft OLE DB Provider for SQL Server (SQLOLEDB)进行新的应用程序开发。

对于新项目,请使用以下驱动程序之一:

对于作为 SQL Server 数据库引擎组件(版本 2012 到 2019)随附的 SQLNCLI,请参阅此支持生命周期特例

SQL Server 2012 (11.x) Native Client ODBC Driver (SQLNCLI11.dll) 更改了 SQL_WCHAR* (NCHAR/NVARCHAR/NVARCHAR(MAX))和 SQL_CHAR* (CHAR/VARCHAR/NARCHAR(MAX))转换的方式。 使用 SQL Server 2012 Native Client ODBC 驱动程序时,ODBC 函数(如 SQLGetData、SQLBindCol、SQLBindParameter)返回 (-4) SQL_NO_TOTAL作为长度/指示器参数。 SQL Server Native Client ODBC 驱动程序的早期版本返回长度值,该值可能不正确。

SQLGetData 行为

许多 Windows 函数允许指定缓冲区大小为 0,且返回的长度为返回的数据大小。 以下模式对于 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 列和绑定查询为 Unicode(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 (10.50.x) Native Client 或更早版本 6 驱动程序错误地假定将 CHAR 转换为 WCHAR 可以通过长度 * 2 来实现。
SQL Server 2012 (11.x) Native Client (版本 11.0.2100.60) 或更高版本 -4 (SQL_NO_TOTAL) 驱动程序不再假定从 CHAR 转换为 WCHAR 或 WCHAR 是 *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 (10.50.x) Native Client 或更早版本 20 SQLFetch 报告数据右侧存在截断。

长度为返回的数据长度而非存储的数据长度(假定 *2 CHAR 到 WCHAR 的转换对于符号可能不正确)。

存储在缓冲区中的数据为 123\0。 缓冲区被保证为以 NULL 结束。
SQL Server 2012 (11.x) 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 (10.50.x) Native Client 或更早版本 2468 SQLFetch 不会返回更多可用数据。

SQLMoreResults 不返回更多可用数据。

长度指示从服务器返回的数据大小而非缓冲区中存储的数据大小。

原始缓冲区包含 63 个字节和 NULL 结束符。 缓冲区被保证为以 NULL 结束。
SQL Server 2012 (11.x) Native Client (版本 11.0.2100.60) 或更高版本 -4 (SQL_NO_TOTAL) SQLFetch 不会返回更多可用数据。

SQLMoreResults 不返回更多可用数据。

长度指示 (-4) SQL_NO_TOTAL,因为数据的其余部分未转换。

原始缓冲区包含 63 个字节和 NULL 结束符。 缓冲区被保证为以 NULL 结束。

执行 CHAR 和 WCHAR 转换

SQL Server 2012 (11.x) Native Client ODBC 驱动程序提供了多种执行 CHAR 和 WCHAR 转换的方法。 逻辑类似于操作 blob(varchar(max)、nvarchar(max)、...):

  • 使用 SQLBindColSQLBindParameter 绑定时,数据将保存或截断到指定的缓冲区中。

  • 如果不绑定,可以使用 SQLGetDataSQLParamData 检索区块中的数据。

另请参阅

SQL Server Native Client 功能