Fixed buffer length when fetching binary fields as streams
This commit is contained in:
parent
dff66a7982
commit
6b1923cde2
|
@ -882,7 +882,7 @@ SQLRETURN binary_to_string( _Inout_ SQLCHAR* field_data, _Inout_ SQLLEN& read_so
|
||||||
_In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length,
|
_In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length,
|
||||||
_Inout_ sqlsrv_error_auto_ptr& out_error )
|
_Inout_ sqlsrv_error_auto_ptr& out_error )
|
||||||
{
|
{
|
||||||
// hex characters for the conversion loop below
|
// The hex characters for the conversion loop below
|
||||||
static char hex_chars[] = "0123456789ABCDEF";
|
static char hex_chars[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
SQLSRV_ASSERT( out_error == 0, "Pending error for sqlsrv_buffered_results_set::binary_to_string" );
|
SQLSRV_ASSERT( out_error == 0, "Pending error for sqlsrv_buffered_results_set::binary_to_string" );
|
||||||
|
@ -892,18 +892,19 @@ SQLRETURN binary_to_string( _Inout_ SQLCHAR* field_data, _Inout_ SQLLEN& read_so
|
||||||
// Set the amount of space necessary for null characters at the end of the data.
|
// Set the amount of space necessary for null characters at the end of the data.
|
||||||
SQLSMALLINT extra = sizeof(Char);
|
SQLSMALLINT extra = sizeof(Char);
|
||||||
|
|
||||||
// TO convert a binary to a system string or a binary to a wide string, the buffer size minur extra
|
// TO convert a binary to a system string or a binary to a wide string, the buffer size minus
|
||||||
// is expected to be multiples of 2 or 4 (depending on Char), but calculating to_copy_hex below
|
// 'extra' is ideally multiples of 2 or 4 (depending on Char), but calculating to_copy_hex below
|
||||||
// takes care of this.
|
// takes care of this.
|
||||||
|
|
||||||
// all fields will be treated as ODBC returns varchar(max) fields:
|
// All fields will be treated as ODBC returns varchar(max) fields:
|
||||||
// the entire length of the string is returned the first
|
// the entire length of the string is returned the first
|
||||||
// call in out_buffer_len. Successive calls return how much is
|
// call in out_buffer_len. Successive calls return how much is
|
||||||
// left minus how much has already been read by previous reads
|
// left minus how much has already been read by previous reads
|
||||||
// *2 is for each byte to hex conversion and * extra is for either system or wide string allocation
|
// *2 is for each byte to hex conversion and * extra is for either system
|
||||||
|
// or wide string allocation
|
||||||
*out_buffer_length = (*reinterpret_cast<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra;
|
*out_buffer_length = (*reinterpret_cast<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra;
|
||||||
|
|
||||||
// copy as much as we can into the buffer
|
// Will copy as much as we can into the buffer
|
||||||
SQLLEN to_copy;
|
SQLLEN to_copy;
|
||||||
if( buffer_length < *out_buffer_length + extra ) {
|
if( buffer_length < *out_buffer_length + extra ) {
|
||||||
to_copy = (buffer_length - extra);
|
to_copy = (buffer_length - extra);
|
||||||
|
@ -916,13 +917,13 @@ SQLRETURN binary_to_string( _Inout_ SQLCHAR* field_data, _Inout_ SQLLEN& read_so
|
||||||
to_copy = *out_buffer_length;
|
to_copy = *out_buffer_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there are bytes to copy as hex
|
// If there are bytes to copy as hex
|
||||||
if( to_copy > 0 ) {
|
if( to_copy > 0 ) {
|
||||||
// quick hex conversion routine
|
// quick hex conversion routine
|
||||||
Char* h = reinterpret_cast<Char*>(buffer);
|
Char* h = reinterpret_cast<Char*>(buffer);
|
||||||
BYTE* b = reinterpret_cast<BYTE*>(field_data + read_so_far);
|
BYTE* b = reinterpret_cast<BYTE*>(field_data + read_so_far);
|
||||||
// to_copy contains the number of bytes to copy, so we divide the number in half (or quarter)
|
// to_copy contains the number of bytes to copy, so we divide the number in half (or quarter)
|
||||||
// to get the maximum number of hex digits we can copy
|
// to get the maximum number of hex digits to copy
|
||||||
SQLLEN to_copy_hex = static_cast<SQLLEN>(floor(to_copy / (2 * extra)));
|
SQLLEN to_copy_hex = static_cast<SQLLEN>(floor(to_copy / (2 * extra)));
|
||||||
for( SQLLEN i = 0; i < to_copy_hex; ++i ) {
|
for( SQLLEN i = 0; i < to_copy_hex; ++i ) {
|
||||||
*h = hex_chars[(*b & 0xf0) >> 4];
|
*h = hex_chars[(*b & 0xf0) >> 4];
|
||||||
|
|
|
@ -101,13 +101,18 @@ size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count)
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the stream returns either no data, NULL data, or returns data < than the count requested then
|
// If the stream returns no data or NULL data, mark the "end of the stream" and return
|
||||||
// we are at the "end of the stream" so we mark it
|
if( r == SQL_NO_DATA || read == SQL_NULL_DATA) {
|
||||||
if( r == SQL_NO_DATA || read == SQL_NULL_DATA || ( static_cast<size_t>( read ) <= count && read != SQL_NO_TOTAL )) {
|
stream->eof = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the stream returns data less than the count requested then we are at the "end of the stream" but continue processing
|
||||||
|
if (static_cast<size_t>(read) <= count && read != SQL_NO_TOTAL) {
|
||||||
stream->eof = 1;
|
stream->eof = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator
|
// If ODBC returns the 01004 (truncated string) warning, then we return the count minus the null terminator
|
||||||
// if it's not a binary encoded field
|
// if it's not a binary encoded field
|
||||||
if( r == SQL_SUCCESS_WITH_INFO ) {
|
if( r == SQL_SUCCESS_WITH_INFO ) {
|
||||||
|
|
||||||
|
@ -121,25 +126,41 @@ size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count)
|
||||||
"did not occur." );
|
"did not occur." );
|
||||||
}
|
}
|
||||||
|
|
||||||
// with unixODBC connection pooling enabled the truncated state may not be returned so check the actual length read
|
// As per SQLGetData documentation, if the length of character data exceeds the BufferLength,
|
||||||
|
// SQLGetData truncates the data to BufferLength less the length of null-termination character.
|
||||||
|
// But when fetching binary fields as chars (wide chars), each byte is represented as 2 hex characters,
|
||||||
|
// each takes the size of a char (wide char). Note that BufferLength may not be multiples of 2 or 4.
|
||||||
|
bool is_binary = (ss->sql_type == SQL_BINARY || ss->sql_type == SQL_VARBINARY || ss->sql_type == SQL_LONGVARBINARY);
|
||||||
|
|
||||||
|
// With unixODBC connection pooling enabled the truncated state may not be returned so check the actual length read
|
||||||
// with buffer length.
|
// with buffer length.
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
if( is_truncated_warning( state ) || count < read) {
|
if( is_truncated_warning( state ) || count < read) {
|
||||||
#else
|
#else
|
||||||
if( is_truncated_warning( state ) ) {
|
if( is_truncated_warning( state ) ) {
|
||||||
#endif // !_WIN32
|
#endif // !_WIN32
|
||||||
switch( c_type ) {
|
size_t char_size = sizeof(SQLCHAR);
|
||||||
|
|
||||||
// As per SQLGetData documentation, if the length of character data exceeds the BufferLength,
|
switch( c_type ) {
|
||||||
// SQLGetData truncates the data to BufferLength less the length of null-termination character.
|
|
||||||
case SQL_C_BINARY:
|
case SQL_C_BINARY:
|
||||||
read = count;
|
read = count;
|
||||||
break;
|
break;
|
||||||
case SQL_C_WCHAR:
|
case SQL_C_WCHAR:
|
||||||
|
char_size = sizeof(SQLWCHAR);
|
||||||
|
if (is_binary) {
|
||||||
|
// Each binary byte read will be 2 hex wide chars in the buffer
|
||||||
|
SQLLEN num_bytes_read = static_cast<SQLLEN>(floor((count - char_size) / (2 * char_size)));
|
||||||
|
read = num_bytes_read * char_size * 2 ;
|
||||||
|
} else {
|
||||||
read = (count % 2 == 0 ? count - 2 : count - 3);
|
read = (count % 2 == 0 ? count - 2 : count - 3);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case SQL_C_CHAR:
|
case SQL_C_CHAR:
|
||||||
|
if (is_binary) {
|
||||||
|
read = ((count - char_size) % 2 == 0 ? count - char_size : count - char_size - 1);
|
||||||
|
} else {
|
||||||
read = count - 1;
|
read = count - 1;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DIE( "sqlsrv_stream_read: should have never reached in this switch case.");
|
DIE( "sqlsrv_stream_read: should have never reached in this switch case.");
|
||||||
|
@ -151,10 +172,10 @@ size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the encoding is UTF-8
|
// If the encoding is UTF-8
|
||||||
if( c_type == SQL_C_WCHAR ) {
|
if( c_type == SQL_C_WCHAR ) {
|
||||||
count *= 2;
|
count *= 2;
|
||||||
// undo the shift to use the full buffer
|
// Undo the shift to use the full buffer
|
||||||
// flags set to 0 by default, which means that any invalid characters are dropped rather than causing
|
// flags set to 0 by default, which means that any invalid characters are dropped rather than causing
|
||||||
// an error. This happens only on XP.
|
// an error. This happens only on XP.
|
||||||
// convert to UTF-8
|
// convert to UTF-8
|
||||||
|
|
|
@ -18,6 +18,19 @@ function fetchNull($stmt, $sqltype, $message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fetchNullStream($stmt, $sqltype, $message)
|
||||||
|
{
|
||||||
|
$stream = sqlsrv_get_field($stmt, 1, $sqltype);
|
||||||
|
if ($stream !== false) {
|
||||||
|
$value = fread($stream, 8192);
|
||||||
|
fclose($stream);
|
||||||
|
|
||||||
|
if (!empty($value)) {
|
||||||
|
echo("$message: expected an empty value\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fetchStream($stmt, $test)
|
function fetchStream($stmt, $test)
|
||||||
{
|
{
|
||||||
global $binaryValue, $hexValue;
|
global $binaryValue, $hexValue;
|
||||||
|
@ -32,17 +45,17 @@ function fetchStream($stmt, $test)
|
||||||
switch ($test) {
|
switch ($test) {
|
||||||
case 1:
|
case 1:
|
||||||
$sqltype = SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR);
|
$sqltype = SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR);
|
||||||
trace("fetchStream (char string):\n");
|
$type = 'char string';
|
||||||
$expected = $hexValue;
|
$expected = $hexValue;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
$sqltype = SQLSRV_PHPTYPE_STREAM('UTF-8');
|
$sqltype = SQLSRV_PHPTYPE_STREAM('UTF-8');
|
||||||
trace("fetchStream (char string):\n");
|
$type = 'UTF-8 string';
|
||||||
$expected = $hexValue;
|
$expected = $hexValue;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
$sqltype = SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY);
|
$sqltype = SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY);
|
||||||
trace("fetchStream (binary string):\n");
|
$type = 'binary string';
|
||||||
$expected = $binaryValue;
|
$expected = $binaryValue;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -50,6 +63,7 @@ function fetchStream($stmt, $test)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace("fetchStream ($type):\n");
|
||||||
$stream = sqlsrv_get_field($stmt, 0, $sqltype);
|
$stream = sqlsrv_get_field($stmt, 0, $sqltype);
|
||||||
if ($stream !== false) {
|
if ($stream !== false) {
|
||||||
$value = '';
|
$value = '';
|
||||||
|
@ -58,11 +72,13 @@ function fetchStream($stmt, $test)
|
||||||
}
|
}
|
||||||
fclose($stream);
|
fclose($stream);
|
||||||
if (!checkData($value, $expected)) {
|
if (!checkData($value, $expected)) {
|
||||||
echo("Expected:\n$expected\nActual:\n$value\n");
|
echo("fetchStream ($type)\nExpected:\n$expected\nActual:\n$value\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fatalError("fetchStream ($test) failed");
|
fatalError("fetchStream ($type) failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchNullStream($stmt, $sqltype, "fetchStream ($type)\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchData($stmt, $test)
|
function fetchData($stmt, $test)
|
||||||
|
@ -79,17 +95,17 @@ function fetchData($stmt, $test)
|
||||||
switch ($test) {
|
switch ($test) {
|
||||||
case 1:
|
case 1:
|
||||||
$sqltype = SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR);
|
$sqltype = SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR);
|
||||||
trace("fetchData (char string):\n");
|
$type = 'char string';
|
||||||
$expected = $hexValue;
|
$expected = $hexValue;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
$sqltype = SQLSRV_PHPTYPE_STRING('UTF-8');
|
$sqltype = SQLSRV_PHPTYPE_STRING('UTF-8');
|
||||||
trace("fetchData (char string):\n");
|
$type = 'UTF-8 string';
|
||||||
$expected = $hexValue;
|
$expected = $hexValue;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
$sqltype = SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY);
|
$sqltype = SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY);
|
||||||
trace("fetchData (binary string):\n");
|
$type = 'binary string';
|
||||||
$expected = $binaryValue;
|
$expected = $binaryValue;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -97,12 +113,13 @@ function fetchData($stmt, $test)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace("fetchData ($type):\n");
|
||||||
$value = sqlsrv_get_field($stmt, 0, $sqltype);
|
$value = sqlsrv_get_field($stmt, 0, $sqltype);
|
||||||
if (!checkData($value, $expected)) {
|
if (!checkData($value, $expected)) {
|
||||||
echo("Expected:\n$expected\nActual:\n$value\n");
|
echo("fetchData ($type)\nExpected:\n$expected\nActual:\n$value\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchNull($stmt, $sqltype, "fetchData ($test)\n");
|
fetchNull($stmt, $sqltype, "fetchData ($type)\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function runTest($conn, $buffered)
|
function runTest($conn, $buffered)
|
||||||
|
@ -154,7 +171,7 @@ $columns = array(new AE\ColumnMeta("varbinary(max)", "varbinary_max_col"),
|
||||||
AE\createTable($conn, $tableName, $columns);
|
AE\createTable($conn, $tableName, $columns);
|
||||||
|
|
||||||
$bin = 'abcdefghijk';
|
$bin = 'abcdefghijk';
|
||||||
$binaryValue = str_repeat($bin, 40);
|
$binaryValue = str_repeat($bin, 400);
|
||||||
$hexValue = strtoupper(bin2hex($binaryValue));
|
$hexValue = strtoupper(bin2hex($binaryValue));
|
||||||
|
|
||||||
$insertSql = "INSERT INTO $tableName (varbinary_max_col, varbinary_null_col) VALUES (?, ?)";
|
$insertSql = "INSERT INTO $tableName (varbinary_max_col, varbinary_null_col) VALUES (?, ?)";
|
||||||
|
|
Loading…
Reference in a new issue