diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 0b9c8fd94..afa3bf56e 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -30,11 +30,6 @@ using namespace core; -// conversion matrix -// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported -// this is initialized the first time the buffered result set is created. -sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix; - namespace { // *** internal types *** @@ -454,34 +449,6 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm meta = static_cast( sqlsrv_malloc( col_count * sizeof( sqlsrv_buffered_result_set::meta_data ))); - // set up the conversion matrix if this is the first time we're called - if( conv_matrix.size() == 0 ) { - - conv_matrix[SQL_C_CHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[SQL_C_CHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::system_to_wide_string; - conv_matrix[SQL_C_CHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[SQL_C_CHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::string_to_double; - conv_matrix[SQL_C_CHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::string_to_long; - conv_matrix[SQL_C_WCHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[SQL_C_WCHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[SQL_C_WCHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::wide_to_system_string; - conv_matrix[SQL_C_WCHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::wstring_to_double; - conv_matrix[SQL_C_WCHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::wstring_to_long; - conv_matrix[SQL_C_BINARY][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[SQL_C_BINARY][SQL_C_CHAR] = &sqlsrv_buffered_result_set::binary_to_system_string; - conv_matrix[SQL_C_BINARY][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::binary_to_wide_string; - conv_matrix[SQL_C_LONG][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::long_to_double; - conv_matrix[SQL_C_LONG][SQL_C_LONG] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[SQL_C_LONG][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[SQL_C_LONG][SQL_C_CHAR] = &sqlsrv_buffered_result_set::long_to_system_string; - conv_matrix[SQL_C_LONG][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::long_to_wide_string; - conv_matrix[SQL_C_DOUBLE][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[SQL_C_DOUBLE][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[SQL_C_DOUBLE][SQL_C_CHAR] = &sqlsrv_buffered_result_set::double_to_system_string; - conv_matrix[SQL_C_DOUBLE][SQL_C_LONG] = &sqlsrv_buffered_result_set::double_to_long; - conv_matrix[SQL_C_DOUBLE][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::double_to_wide_string; - } - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); @@ -844,18 +811,70 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ *out_buffer_length = SQL_NULL_DATA; return SQL_SUCCESS; } - // check to make sure the conversion type is valid - conv_matrix_t::const_iterator conv_iter = conv_matrix.find( meta[field_index].c_type ); - if( conv_iter == conv_matrix.end() || conv_iter->second.find( target_type ) == conv_iter->second.end() ) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0 ); - return SQL_ERROR; + switch (meta[field_index].c_type) { + case SQL_C_CHAR: + switch (target_type) { + case SQL_C_CHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::system_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::string_to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_LONG: return sqlsrv_buffered_result_set::string_to_long(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + case SQL_C_WCHAR: + switch (target_type) { + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_CHAR: return sqlsrv_buffered_result_set::wide_to_system_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::wstring_to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_LONG: return sqlsrv_buffered_result_set::wstring_to_long(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + case SQL_C_BINARY: + switch (target_type) { + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_CHAR: return sqlsrv_buffered_result_set::binary_to_system_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::binary_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + case SQL_C_LONG: + switch (target_type) { + case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::long_to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_LONG: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_CHAR: return sqlsrv_buffered_result_set::long_to_system_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::long_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + case SQL_C_DOUBLE: + switch (target_type) { + case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_CHAR: return sqlsrv_buffered_result_set::double_to_system_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_LONG: return sqlsrv_buffered_result_set::double_to_long(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::double_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + default: + break; } - return (( this )->*( conv_matrix[meta[field_index].c_type][target_type] ))( field_index, buffer, buffer_length, - out_buffer_length ); + // Should not have reached here, return an error + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) + sqlsrv_error((SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0); + return SQL_ERROR; } SQLRETURN sqlsrv_buffered_result_set::get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index ae27400e8..d576993d9 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1746,13 +1746,6 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use SQLLEN temp_length; // number of bytes in the temp conversion buffer - typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, - _Inout_ SQLLEN* out_buffer_length ); - typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; - - // two dimentional sparse matrix that holds the [from][to] functions that do conversions - static conv_matrix_t conv_matrix; - // string conversion functions SQLRETURN binary_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 32b10ad58..7fac8e5b3 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1799,11 +1799,11 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i case SQLSRV_PHPTYPE_INT: { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( SQLLEN ))); *field_value_temp = 0; - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( SQLLEN ), field_len, true /*handle_warning*/ TSRMLS_CC ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { diff --git a/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt new file mode 100644 index 000000000..a10bf6c38 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt @@ -0,0 +1,229 @@ +--TEST-- +Prepare with cursor buffered and fetch a variety of types converted to different types +--DESCRIPTION-- +Test various conversion functionalites for buffered queries with PDO_SQLSRV. +--SKIPIF-- + +--FILE-- +prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + + // Fetch all fields as UTF-8 strings + for ($i = 0; $i < count($inputs); $i++) { + $stmt->execute(); + $f = $stmt->fetchColumn($i); + + if ($f !== $inputs[$i]) { + var_dump($f); + } + } + } catch (PdoException $e) { + echo "Caught exception in fetchAsUTF8:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +function fetchArray($conn, $tableName, $inputs) +{ + $query = "SELECT * FROM $tableName"; + try { + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + // By default, even numeric or datetime fields are fetched as strings + $result = $stmt->fetch(PDO::FETCH_NUM); + for ($i = 0; $i < count($inputs); $i++) { + if ($result[$i] !== $inputs[$i]) { + var_dump($f); + } + } + } catch (PdoException $e) { + echo "Caught exception in fetchArray:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +function fetchBinaryAsNumber($conn, $tableName, $inputs) +{ + global $violation; + + $query = "SELECT c1 FROM $tableName"; + + try { + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED, PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE=>true)); + $stmt->execute(); + + $stmt->bindColumn('c1', $binaryValue, PDO::PARAM_INT); + $row = $stmt->fetch(PDO::FETCH_BOUND); + echo "in fetchBinaryAsNumber: exception should have been thrown!\n"; + } catch (PdoException $e) { + // The varbinary field - expect the violation error + if (strpos($e->getMessage(), $violation) === false) { + echo "in fetchBinaryAsNumber: expected '$violation' but caught this:\n"; + echo $e->getMessage() . PHP_EOL; + } + } +} + +function fetchBinaryAsBinary($conn, $tableName, $inputs) +{ + try { + $query = "SELECT c1 FROM $tableName"; + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + $stmt->bindColumn('c1', $binaryValue, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $row = $stmt->fetch(PDO::FETCH_BOUND); + + if ($binaryValue !== $inputs[0]) { + echo "Fetched binary value unexpected: $binaryValue\n"; + } + } catch (PdoException $e) { + echo "Caught exception in fetchBinaryAsBinary:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +function fetchFloatAsInt($conn, $tableName) +{ + global $truncation; + + try { + $query = "SELECT c3 FROM $tableName"; + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + $stmt->bindColumn('c3', $floatValue, PDO::PARAM_INT); + $row = $stmt->fetch(PDO::FETCH_BOUND); + + // This should return SQL_SUCCESS_WITH_INFO with the truncation error + $info = $stmt->errorInfo(); + if ($info[0] != '01S07' || $info[2] !== $truncation) { + print_r($stmt->errorInfo()); + } + } catch (PdoException $e) { + echo "Caught exception in fetchFloatAsInt:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +function fetchCharAsInt($conn, $tableName, $column) +{ + global $outOfRange; + + try { + $query = "SELECT $column FROM $tableName"; + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + $stmt->bindColumn($column, $value, PDO::PARAM_INT); + $row = $stmt->fetch(PDO::FETCH_BOUND); + + // TODO 11297: fix this part outside Windows later + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + echo "in fetchCharAsInt: exception should have been thrown!\n"; + } else { + if ($value != 0) { + var_dump($value); + } + } + } catch (PdoException $e) { + // The (n)varchar field - expect the outOfRange error + if (strpos($e->getMessage(), $outOfRange) === false) { + echo "in fetchCharAsInt ($column): expected '$outOfRange' but caught this:\n"; + echo $e->getMessage() . PHP_EOL; + } + } +} + +function fetchAsNumerics($conn, $tableName, $inputs) +{ + // The following calls expect different errors + fetchFloatAsInt($conn, $tableName); + fetchCharAsInt($conn, $tableName, 'c6'); + fetchCharAsInt($conn, $tableName, 'c7'); + + // The following should work + try { + $query = "SELECT c2, c4 FROM $tableName"; + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + $stmt->bindColumn('c2', $intValue, PDO::PARAM_INT); + $stmt->bindColumn('c4', $decValue, PDO::PARAM_INT); + + $row = $stmt->fetch(PDO::FETCH_BOUND); + + if ($intValue !== intval($inputs[1])) { + var_dump($intValue); + } + if ($decValue !== intval($inputs[3])) { + var_dump($decValue); + } + } catch (PdoException $e) { + echo "Caught exception in fetchAsNumerics:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +try { + $conn = connect(); + $conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + + $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7'); + $types = array('varbinary(10)', 'int', 'float(53)', 'decimal(16, 6)', 'datetime2', 'varchar(50)', 'nvarchar(50)'); + $inputs = array('abcdefghij', '34567', '9876.5432', '123456789.012340', '2020-02-02 20:20:20.2220000', 'This is a test', 'Şơмė śäოрŀề'); + + // Create table + $colMeta = array(new ColumnMeta($types[0], $columns[0]), + new ColumnMeta($types[1], $columns[1]), + new ColumnMeta($types[2], $columns[2]), + new ColumnMeta($types[3], $columns[3]), + new ColumnMeta($types[4], $columns[4]), + new ColumnMeta($types[5], $columns[5]), + new ColumnMeta($types[6], $columns[6])); + createTable($conn, $tableName, $colMeta); + + // Prepare the input values and insert one row + $query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?, ?)"; + $stmt = $conn->prepare($query); + for ($i = 0; $i < count($columns); $i++) { + if ($i == 0) { + $stmt->bindParam($i+1, $inputs[$i], PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + } else { + $stmt->bindParam($i+1, $inputs[$i]); + } + } + $stmt->execute(); + unset($stmt); + + // Starting fetching using client buffers + fetchAsUTF8($conn, $tableName, $inputs); + fetchArray($conn, $tableName, $inputs); + fetchBinaryAsNumber($conn, $tableName, $inputs); + fetchBinaryAsBinary($conn, $tableName, $inputs); + fetchAsNumerics($conn, $tableName, $inputs); + + // dropTable($conn, $tableName); + echo "Done\n"; + unset($conn); +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt new file mode 100644 index 000000000..425d7f58f --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt @@ -0,0 +1,278 @@ +--TEST-- +Prepare with cursor buffered and fetch a variety of types converted to different types +--DESCRIPTION-- +Test various conversion functionalites for buffered queries with SQLSRV. +--SKIPIF-- + +--FILE-- +SQLSRV_CURSOR_CLIENT_BUFFERED)); + if (!$stmt) { + fatalError("In fetchAsUTF8: failed to run query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsUTF8: failed to fetch the row from $tableName!"); + } + + // Fetch all fields as UTF-8 strings + for ($i = 0; $i < count($inputs); $i++) { + $f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING('utf-8')); + if ($i == 0) { + if ($inputs[$i] !== hex2bin($f)) { + var_dump($f); + } + } else { + if ($f !== $inputs[$i]) { + var_dump($f); + } + } + } +} + +function fetchArray($conn, $tableName, $inputs) +{ + $query = "SELECT * FROM $tableName"; + + $stmt = sqlsrv_prepare($conn, $query, array(), array('Scrollable'=>SQLSRV_CURSOR_CLIENT_BUFFERED, 'ReturnDatesAsStrings' => true)); + if (!$stmt) { + fatalError("In fetchArray: failed to prepare query!"); + } + $res = sqlsrv_execute($stmt); + if (!$res) { + fatalError("In fetchArray: failed to execute query!"); + } + + // Fetch fields as an array + $results = sqlsrv_fetch_array($stmt); + if ($results === false) { + fatalError("In fetchArray: failed to fetch the row from $tableName!"); + } + + for ($i = 0; $i < count($inputs); $i++) { + if ($i == 1) { + $expected = intval($inputs[$i]); + } elseif ($i == 2) { + $expected = floatval($inputs[$i]); + } else { + $expected = $inputs[$i]; + } + + if ($results[$i] !== $expected) { + echo "in fetchArray: for column $i expected $expected but got: "; + var_dump($results[$i]); + } + } +} + +function fetchAsFloats($conn, $tableName, $inputs) +{ + global $violation, $outOfRange, $epsilon; + + $query = "SELECT * FROM $tableName"; + $stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED, 'ReturnDatesAsStrings' => true)); + if (!$stmt) { + fatalError("In fetchAsFloats: failed to run query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsFloats: failed to fetch the row from $tableName!"); + } + + // Fetch all fields as floats + for ($i = 0; $i < count($inputs); $i++) { + $f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_FLOAT); + + if ($i == 0) { + // The varbinary field - expect the violation error + if (strpos(sqlsrv_errors()[0]['message'], $violation) === false) { + var_dump($f); + fatalError("in fetchAsFloats: expected $violation for column $i\n"); + } + } elseif ($i < 5) { + $expected = floatval($inputs[$i]); + $diff = abs(($f - $expected) / $expected); + + if ($diff > $epsilon) { + echo "in fetchAsFloats: for column $i expected $expected but got: "; + var_dump($f); + } + } else { + // The char fields will get errors too + // TODO 11297: fix this part outside Windows later + if (isWindows()) { + if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { + var_dump($f); + fatalError("in fetchAsFloats: expected $outOfRange for column $i\n"); + } + } else { + if ($f != 0.0) { + var_dump($f); + } + } + } + } +} + +function fetchAsInts($conn, $tableName, $inputs) +{ + global $violation, $outOfRange, $truncation; + + $query = "SELECT * FROM $tableName"; + $stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED, 'ReturnDatesAsStrings' => true)); + if (!$stmt) { + fatalError("In fetchAsInts: failed to run query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsInts: failed to fetch the row from $tableName!"); + } + + // Fetch all fields as integers + for ($i = 0; $i < count($inputs); $i++) { + $f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_INT); + + if ($i == 0) { + // The varbinary field - expect the violation error + if (strpos(sqlsrv_errors()[0]['message'], $violation) === false) { + var_dump($f); + fatalError("in fetchAsInts: expected $violation for column $i\n"); + } + } elseif ($i == 2) { + // The float field - expect truncation + if (strpos(sqlsrv_errors()[0]['message'], $truncation) === false) { + var_dump($f); + fatalError("in fetchAsInts: expected $truncation for column $i\n"); + } + } elseif ($i >= 5) { + // The char fields will get errors too + // TODO 11297: fix this part outside Windows later + if (isWindows()) { + if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { + var_dump($f); + fatalError("in fetchAsInts: expected $outOfRange for column $i\n"); + } + } else { + if ($f != 0) { + var_dump($f); + } + } + } else { + $expected = floor($inputs[$i]); + if ($f != $expected) { + echo "in fetchAsInts: for column $i expected $expected but got: "; + var_dump($f); + } + } + } +} + +function fetchAsBinary($conn, $tableName, $inputs) +{ + $query = "SELECT c_varbinary FROM $tableName"; + + $stmt = sqlsrv_prepare($conn, $query, array(), array('Scrollable'=>SQLSRV_CURSOR_CLIENT_BUFFERED)); + if (!$stmt) { + fatalError("In fetchAsBinary: failed to prepare query!"); + } + $res = sqlsrv_execute($stmt); + if (!$res) { + fatalError("In fetchAsBinary: failed to execute query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsInts: failed to fetch the row from $tableName!"); + } + + // Fetch the varbinary field as is + $f = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STREAM("binary")); + if (gettype($f) !== 'resource') { + var_dump($f); + } + // Do not expect errors + $errs = sqlsrv_errors(); + if (!empty($errs)) { + var_dump($errs); + } + + // Check its value + while (!feof($f)) { + $str = fread($f, 80); + } + if (trim($str) !== $inputs[0]) { + echo "Fetched binary value unexpected: $str\n"; + } +} + +require_once('MsCommon.inc'); + +$conn = AE\connect(array('CharacterSet' => 'UTF-8')); +$tableName = 'srvFetchingClientBuffer'; + +// Create table +$names = array('c_varbinary', 'c_int', 'c_float', 'c_decimal', 'c_datetime2', 'c_varchar', 'c_nvarchar'); + +$columns = array(new AE\ColumnMeta('varbinary(10)', $names[0]), + new AE\ColumnMeta('int', $names[1]), + new AE\ColumnMeta('float(53)', $names[2]), + new AE\ColumnMeta('decimal(16, 6)', $names[3]), + new AE\ColumnMeta('datetime2', $names[4]), + new AE\ColumnMeta('varchar(50)', $names[5]), + new AE\ColumnMeta('nvarchar(50)', $names[6])); +$stmt = AE\createTable($conn, $tableName, $columns); +if (!$stmt) { + fatalError("Failed to create $tableName!"); +} + +// Prepare the input values +$inputs = array('abcdefghij', '34567', '9876.5432', '123456789.012340', '2020-02-02 20:20:20.2220000', 'This is a test', 'Şơмė śäოрŀề'); + +$params = array(array(bin2hex($inputs[0]), SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_BINARY(10)), + $inputs[1], $inputs[2], $inputs[3], $inputs[4], $inputs[5], + array($inputs[6], SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING('utf-8'))); + +// Form the insert query +$colStr = '('; +foreach ($names as $name) { + $colStr .= $name . ", "; +} +$colStr = rtrim($colStr, ", ") . ") "; +$insertSql = "INSERT INTO [$tableName] " . $colStr . 'VALUES (?,?,?,?,?,?,?)'; + +// Insert one row only +$stmt = sqlsrv_prepare($conn, $insertSql, $params); +if ($stmt) { + $res = sqlsrv_execute($stmt); + if (!$res) { + fatalError("Failed to execute insert statement to $tableName!"); + } +} else { + fatalError("Failed to prepare insert statement to $tableName!"); +} + +// Starting fetching using client buffers +fetchAsUTF8($conn, $tableName, $inputs); +fetchArray($conn, $tableName, $inputs); +fetchAsFloats($conn, $tableName, $inputs); +fetchAsInts($conn, $tableName, $inputs); +fetchAsBinary($conn, $tableName, $inputs); + +dropTable($conn, $tableName); + +echo "Done\n"; + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +Done