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 Driver 版本 | 長度或指標結果 | 描述 |
---|---|---|
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 轉換為 CHAR 是 *2 或 (divide)/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 Driver 版本 | 長度或指標結果 | 描述 |
---|---|---|
SQL Server 2008 R2 (10.50.x) Native Client 或更早版本 | 20 | SQLFetch 會報告數據右側有截斷。 Length 是傳回的數據長度,而不是儲存的數據長度(假設 *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 Driver 版本 | 長度或指標結果 | 描述 |
---|---|---|
SQL Server 2008 R2 (10.50.x) Native Client 或更早版本 | 2468 | SQLFetch 不會傳回更多可用的數據。 SQLMoreResults 不會傳回更多可用的數據。 Length 表示從伺服器傳回的數據大小,而不是儲存在緩衝區中。 原始緩衝區包含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), ...):
使用 SQLBindCol 或 SQLBindParameter 系結時,數據會儲存或截斷到指定的緩衝區。
如果您未系結,您可以使用 SQLGetData 和 SQLParamData 來擷取區塊中的數據。