SQL Server 2012 Native Client ODBC Driver (SQLNCLI11.dll) は、SQL_WCHAR* (NCHAR/NVARCHAR/NVARCHAR(MAX)) および SQL_CHAR* (CHAR/VARCHAR/NARCHAR(MAX)) 変換の処理方法を変更しました。 SQLGetData、SQLBindCol、SQLBindParameter などの ODBC 関数は、SQL Server 2012 Native Client ODBC ドライバーを使用する場合、長さ/インジケーター パラメーターとしてSQL_NO_TOTAL (-4) を返します。 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 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 との間の変換を検出し、正しくない可能性がある *2 または /2 の動作の代わりに (-4) SQL_NO_TOTALを返します。 |
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 Native Client 以前 | 20 |
-
SQLFetch は、データの右側に切り捨てがあることを報告します。 - Length は、格納されたデータではなく、返されるデータの長さです (グリフに対して正しくない可能性がある *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 Driver のバージョン | 長さまたはインジケーターの結果 | 説明 |
|---|---|---|
| SQL Server 2008 R2 Native Client 以前 | 2468 |
-
SQLFetch は、使用できるデータをそれ以上返しません。 - SQLMoreResults は 、使用できるデータを返しません。 - Length は、バッファーに格納されず、サーバーから返されるデータのサイズを示します。 - 元のバッファーには、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)、...) の操作に似ています。
SQLBindCol または SQLBindParameter を使用してバインドすると、指定したバッファーにデータが保存または切り捨てられます。
バインドしない場合は、 SQLGetData と SQLParamData を使用してチャンク単位でデータ を取得できます。