文字変換処理での ODBC ドライバーの動作の変更
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 ドライバーを使用する場合、長さ/インジケーター パラメーターとして (-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
ODBC ドライバーのバージョンをSQL Server Native Clientする | 長さまたはインジケーターの結果 | 説明 |
---|---|---|
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
ODBC ドライバーのバージョンをSQL Server Native Clientする | 長さまたはインジケーターの結果 | 説明 |
---|---|---|
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 (出力パラメーターの動作)
クエリ: create procedure spTest @p1 varchar(max) OUTPUT
select @p1 = replicate('B', 1234)
SQLBindParameter(... SQL_W_CHAR, ...) // Only bind up to first 64 characters
ODBC ドライバーのバージョンをSQL Server Native Clientする | 長さまたはインジケーターの結果 | 説明 |
---|---|---|
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 を使用してチャンク単位でデータ を取得できます。