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

驅動程式誤認為只要長度 * 2 即可將 CHAR 轉換成 WCHAR。

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 功能