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

SQL Server 2012 Native Client ODBC 驱动程序 (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 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 转换

SQL Server 2012 Native Client ODBC 驱动程序提供几种执行 CHAR 和 WCHAR 转换的方式。 逻辑类似于处理 blob(varchar(max)、nvarchar(max)、…):

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

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

请参阅

其他资源

SQL Server Native Client 功能