Modifica del comportamento del driver ODBC quando si gestiscono le conversioni di caratteri
Il driver ODBC SQL Server 2012 Native Client (SQLNCLI11.dll) ha modificato le modalità di SQL_WCHAR* (NCHAR/NVARCHAR/NVARCHAR(MAX)) e SQL_CHAR* (CHAR/VARCHAR/NARCHAR(MAX)). Le funzioni ODBC, ad esempio SQLGetData, SQLBindCol, SQLBindParameter, restituiscono (-4) SQL_NO_TOTAL come parametro length/indicator quando si usa il driver ODBC SQL Server 2012 Native Client. Le versioni precedenti del driver ODBC SQL Server Native Client hanno restituito un valore di lunghezza, che può non essere corretto.
Comportamento di SQLGetData
Tramite le numerose funzioni di Windows è possibile specificare dimensioni del buffer pari a 0 e la lunghezza restituita corrisponde alle dimensioni dei dati restituiti. Di seguito è riportato un modello comune per i programmatori di 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
Tuttavia, SQLGetData non deve essere usato in questo scenario. Non è consigliabile utilizzare il modello seguente:
// 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 può essere chiamato solo per recuperare blocchi di dati effettivi. L'uso di SQLGetData per ottenere le dimensioni dei dati non è supportato.
Di seguito viene illustrato l'impatto della modifica del driver quando si utilizza il modello errato. Tramite questa applicazione viene eseguita una query su una colonna varchar
e su un'associazione come Unicode (SQL_UNICODE/SQL_WCHAR):
Query: select convert(varchar(36), '123')
SQLGetData(hstmt, SQL_WCHAR, ....., (SQLPOINTER*) 0x1, 0 , &iSize); // Attempting to determine storage size needed
SQL Server Native Client versione del driver ODBC | Risultato della lunghezza o dell'indicatore | Descrizione |
---|---|---|
SQL Server 2008 R2 Native Client o versioni precedenti | 6 | È stato erroneamente presupposto dal driver che la conversione di CHAR in WCHAR potesse essere effettuata come lunghezza * 2. |
SQL Server 2012 Native Client (versione 11.0.2100.60) o successiva | -4 (SQL_NO_TOTAL) | Tramite il driver non viene più presupposto che la conversione da CHAR a WCHAR o da WCHAR a CHAR sia un'azione di (moltiplicazione) *2 o (divisione)/2. La chiamata a SQLGetData non restituisce più la lunghezza della conversione prevista. Tramite il driver viene rilevata la conversione a o da CHAR e WCHAR e viene restituito (-4) SQL_NO_TOTAL anziché il comportamento *2 o /2 che potrebbe essere errato. |
Usare SQLGetData per recuperare i blocchi dei dati. (di seguito è riportato uno pseudocodice):
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
}
}
}
Comportamento di SQLBindCol
Query: 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 versione del driver ODBC | Risultato della lunghezza o dell'indicatore | Descrizione |
---|---|---|
SQL Server 2008 R2 Native Client o versioni precedenti | 20 | - SQLFetch segnala che è presente un troncamento sul lato destro dei dati. - Lunghezza è la lunghezza dei dati restituiti, non di quello archiviato (presuppone la conversione da *2 CHAR a WCHAR che può non essere corretta per i glifi). - I dati archiviati nel buffer sono 123\0. Viene garantito che la terminazione del buffer sia NULL. |
SQL Server 2012 Native Client (versione 11.0.2100.60) o successiva | -4 (SQL_NO_TOTAL) | - SQLFetch segnala che è presente un troncamento sul lato destro dei dati. - Lunghezza indica -4 (SQL_NO_TOTAL) perché il resto dei dati non è stato convertito. - I dati archiviati nel buffer sono 123\0. - Viene garantito che la terminazione del buffer sia NULL. |
SQLBindParameter (comportamento del parametro OUTPUT)
Query: 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 versione del driver ODBC | Risultato della lunghezza o dell'indicatore | Descrizione |
---|---|---|
SQL Server 2008 R2 Native Client o versioni precedenti | 2468 | - SQLFetch non restituisce più dati disponibili. - SQLMoreResults restituisce non più dati disponibili. - Lunghezza indica le dimensioni dei dati restituiti dal server, non archiviati nel buffer. - Il buffer originale contiene 63 byte e un carattere di terminazione NULL. Viene garantito che la terminazione del buffer sia NULL. |
SQL Server 2012 Native Client (versione 11.0.2100.60) o successiva | -4 (SQL_NO_TOTAL) | - SQLFetch non restituisce più dati disponibili. - SQLMoreResults restituisce non più dati disponibili. - Lunghezza indica (-4) SQL_NO_TOTAL perché il resto dei dati non è stato convertito. - Il buffer originale contiene 63 byte e un carattere di terminazione NULL. Viene garantito che la terminazione del buffer sia NULL. |
Esecuzione delle conversioni CHAR e WCHAR
Il driver ODBC SQL Server 2012 Native Client offre diversi modi per eseguire conversioni CHAR e WCHAR. La logica è simile alla modifica dei BLOB (varchar(max), nvarchar(max), ...):
I dati vengono salvati o troncati nel buffer specificato durante l'associazione con SQLBindCol o SQLBindParameter.
Se non si esegue l'associazione, è possibile recuperare i dati in blocchi usando SQLGetData e SQLParamData.