From ecbd53f712249f68cfa0e1eb3fa5eb9348576f5b Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 26 Jun 2020 15:56:51 -0700 Subject: [PATCH] Simplied conversions from strings to numbers (#1146) --- source/shared/core_results.cpp | 146 ++++++------------ .../pdo_sqlsrv/pdo_buffered_fetch_types.phpt | 70 +++++++-- .../pdostatement_fetch_orientation.phpt | 51 +++--- .../sqlsrv/sqlsrv_buffered_fetch_types.phpt | 62 +++++--- 4 files changed, 183 insertions(+), 146 deletions(-) diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index fd55c294..64c3e6e5 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -262,64 +262,6 @@ std::string getUTF8StringFromString( _In_z_ const char* source ) #endif // !_WIN32 -template -SQLRETURN string_to_number( _In_z_ Char* string_data, SQLLEN str_len, _Out_writes_bytes_(*out_buffer_length) void* buffer, SQLLEN buffer_length, - _Inout_ SQLLEN* out_buffer_length, _Inout_ sqlsrv_error_auto_ptr& last_error ) -{ - Number* number_data = reinterpret_cast( buffer ); -#ifdef _WIN32 - std::locale loc; // default locale should match system - std::basic_istringstream is; - is.str( string_data ); - is.imbue( loc ); - std::ios_base::iostate st = 0; - - std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf()), std::basic_istream::_Iter( 0 ), is, st, *number_data ); - - if ( st & std::ios_base::failbit ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "22003", ( SQLCHAR* ) "Numeric value out of range", 103 ); - return SQL_ERROR; - } - - *out_buffer_length = sizeof( Number ); -#else - std::string str = getUTF8StringFromString( string_data ); - - std::istringstream is( str ); - std::locale loc; // default locale should match system - is.imbue( loc ); - - auto& facet = std::use_facet>( is.getloc() ); - std::istreambuf_iterator beg( is ), end; - std::ios_base::iostate err = std::ios_base::goodbit; - - if ( std::is_integral::value ) - { - long number; - facet.get( beg, end, is, err, number ); - - *number_data = number; - } - else - { - double number; - facet.get( beg, end, is, err, number ); - - *number_data = number; - } - - *out_buffer_length = sizeof( Number ); - - if ( is.fail() ) - { - last_error = new ( sqlsrv_malloc(sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "22003", ( SQLCHAR* ) "Numeric value out of range", 103 ); - return SQL_ERROR; - } -#endif // _WIN32 - return SQL_SUCCESS; - -} - // "closure" for the hash table destructor struct row_dtor_closure { @@ -851,7 +793,6 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ 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; } @@ -862,7 +803,6 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ 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; } @@ -1082,23 +1022,6 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( _In_ SQLSMALLINT return r; } -SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, - _Inout_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); - SQLRETURN r = SQL_SUCCESS; -#ifdef _WIN32 - r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -#else - r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -#endif // _WIN32 - return r; -} - SQLRETURN sqlsrv_buffered_result_set::long_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ) { @@ -1131,23 +1054,6 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( _In_ SQLSMALLINT fi return r; } -SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, - _Inout_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" ); - - unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); - SQLRETURN r = SQL_SUCCESS; -#ifdef _WIN32 - r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -#else - r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -#endif // _WIN32 - return r; -} - SQLRETURN sqlsrv_buffered_result_set::string_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { @@ -1157,7 +1063,16 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_double( _In_ SQLSMALLINT field_i unsigned char* row = get_row(); char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); - return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); + double* number_data = reinterpret_cast(buffer); + try { + *number_data = std::stod(std::string(string_data)); + } catch (const std::logic_error& err) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); + return SQL_ERROR; + } + + *out_buffer_length = sizeof(double); + return SQL_SUCCESS; } SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, @@ -1169,7 +1084,20 @@ SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( _In_ SQLSMALLINT field_ unsigned char* row = get_row(); SQLWCHAR* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); + double* number_data = reinterpret_cast(buffer); + try { +#ifdef _WIN32 + *number_data = std::stod(std::wstring(string_data)); +#else + *number_data = std::stod(getUTF8StringFromString(string_data)); +#endif // _WIN32 + } catch (const std::logic_error& err) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); + return SQL_ERROR; + } + + *out_buffer_length = sizeof(double); + return SQL_SUCCESS; } SQLRETURN sqlsrv_buffered_result_set::string_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, @@ -1181,7 +1109,16 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_long( _In_ SQLSMALLINT field_ind unsigned char* row = get_row(); char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); - return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); + LONG* number_data = reinterpret_cast(buffer); + try { + *number_data = std::stol(std::string(string_data)); + } catch (const std::logic_error& err) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); + return SQL_ERROR; + } + + *out_buffer_length = sizeof(LONG); + return SQL_SUCCESS; } SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, @@ -1193,7 +1130,20 @@ SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( _In_ SQLSMALLINT field_in unsigned char* row = get_row(); SQLWCHAR* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); + LONG* number_data = reinterpret_cast(buffer); + try { +#ifdef _WIN32 + *number_data = std::stol(std::wstring(string_data)); +#else + *number_data = std::stol(getUTF8StringFromString(string_data)); +#endif // _WIN32 + } catch (const std::logic_error& err) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); + return SQL_ERROR; + } + + *out_buffer_length = sizeof(LONG); + return SQL_SUCCESS; } SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, diff --git a/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt index f2d45d47..ebf19765 100644 --- a/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt +++ b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt @@ -14,12 +14,40 @@ $outOfRange = 'Numeric value out of range'; $truncation = 'Fractional truncation'; $epsilon = 0.00001; +function fetchAsChar($conn, $tableName, $inputs) +{ + $query = "SELECT c1, c2, c3, c4, c5, c6 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->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_SYSTEM); + + // Fetch all fields as strings - no conversion + for ($i = 0; $i < count($inputs) - 1; $i++) { + $stmt->execute(); + $f = $stmt->fetchColumn($i); + + if ($i == 2) { + if (!compareFloats(floatval($inputs[$i]), floatval($f))) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump($f); + } + } elseif ($f !== $inputs[$i]) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump($f); + } + } + } catch (PdoException $e) { + echo "Caught exception in fetchAsChar:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + function fetchAsUTF8($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)); - + // Fetch all fields as UTF-8 strings for ($i = 0; $i < count($inputs); $i++) { $stmt->execute(); @@ -145,14 +173,7 @@ function fetchCharAsInt($conn, $tableName, $column) $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); - } - } + echo "in fetchCharAsInt: exception should have been thrown!\n"; } catch (PdoException $e) { // The (n)varchar field - expect the outOfRange error if (strpos($e->getMessage(), $outOfRange) === false) { @@ -194,6 +215,35 @@ function fetchAsNumerics($conn, $tableName, $inputs) } } +function fetchNumbers($conn, $tableName, $inputs) +{ + // Fetch integers and floats as numbers, not strings + try { + $query = "SELECT c2, c3, 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->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + $stmt->execute(); + + $row = $stmt->fetch(PDO::FETCH_NUM); + if ($row[0] !== intval($inputs[1])) { + var_dump($row[0]); + } + $expected = floatval($inputs[2]); + if (!compareFloats($expected, $row[1])) { + echo "in fetchNumbers: expected $expected but got: "; + var_dump($row[1]); + } + if ($row[2] !== $inputs[3]) { + var_dump($row[2]); + } + } catch (PdoException $e) { + echo "Caught exception in fetchAsNumerics:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + try { $conn = connect(); $conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); @@ -226,11 +276,13 @@ try { unset($stmt); // Starting fetching using client buffers + fetchAsChar($conn, $tableName, $inputs); fetchAsUTF8($conn, $tableName, $inputs); fetchArray($conn, $tableName, $inputs); fetchBinaryAsNumber($conn, $tableName, $inputs); fetchBinaryAsBinary($conn, $tableName, $inputs); fetchAsNumerics($conn, $tableName, $inputs); + fetchNumbers($conn, $tableName, $inputs); // dropTable($conn, $tableName); echo "Done\n"; diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt index a14d6dde..6115965c 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt @@ -8,20 +8,17 @@ PHPT_EXEC=true PDO::CURSOR_SCROLL); + if ($buffered) { + $options = array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED); + } - // Prepare test table - $tableName = "pdo_test_table"; - createTable($conn1, $tableName, array(new ColumnMeta("int", "id", "NOT NULL PRIMARY KEY", "none"), "val" => "varchar(10)")); - insertRow($conn1, $tableName, array("id" => 1, "val" => "A")); - insertRow($conn1, $tableName, array("id" => 2, "val" => "B")); - insertRow($conn1, $tableName, array("id" => 3, "val" => "C")); + $stmt1 = $conn->prepare($tsql, $options); + $stmt1->execute(); - // Query table and retrieve data - $stmt1 = $conn1->prepare( "SELECT val FROM $tableName ORDER BY id", array( PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL )); - - $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_LAST ); if( $row[ 'val' ] != "C" ) { throw new Exception( "Not C" ); @@ -38,7 +35,7 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } - + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_LAST ); if( $row[ 'val' ] != "C" ) { @@ -57,7 +54,7 @@ try { throw new Exception( "Not false" ); } - $stmt1->execute(); + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_LAST ); if( $row[ 'val' ] != "C" ) { throw new Exception( "Not C" ); @@ -66,8 +63,8 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } - - $stmt1->execute(); + + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_LAST ); if( $row[ 'val' ] != "C" ) { throw new Exception( "Not C" ); @@ -76,7 +73,7 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } - + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_ABS, 2 ); if( $row[ 'val' ] != "C" ) { @@ -138,7 +135,7 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } - + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_FIRST ); if( $row[ 'val' ] != "A" ) { @@ -148,10 +145,26 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } + + unset($stmt1); +} + +try { + $conn1 = connect(); + + // Prepare test table + $tableName = "pdo_test_table"; + createTable($conn1, $tableName, array(new ColumnMeta("int", "id", "NOT NULL PRIMARY KEY", "none"), "val" => "varchar(10)")); + insertRow($conn1, $tableName, array("id" => 1, "val" => "A")); + insertRow($conn1, $tableName, array("id" => 2, "val" => "B")); + insertRow($conn1, $tableName, array("id" => 3, "val" => "C")); + + // Query table and retrieve data + runTests($conn1, $tableName, false); + runTests($conn1, $tableName, true); // Cleanup dropTable($conn1, $tableName); - unset($stmt1); unset($conn1); echo "Test 'PDO Statement - Fetch Scrollable' completed successfully.\n"; diff --git a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt index 3351f201..b2181be7 100644 --- a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt +++ b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt @@ -20,6 +20,41 @@ function compareFloats($expected, $actual) return ($diff < $epsilon); } +function fetchAsChar($conn, $tableName, $inputs) +{ + $query = "SELECT c_varbinary, c_int, c_float, c_decimal, c_datetime2, c_varchar FROM $tableName"; + + $stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED)); + if (!$stmt) { + fatalError("In fetchAsChar: failed to run query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsChar: failed to fetch the row from $tableName!"); + } + + // Fetch all fields as strings - no conversion + for ($i = 0; $i < count($inputs) - 1; $i++) { + $f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); + if ($i == 0) { + if ($inputs[$i] !== hex2bin($f)) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump(hex2bin($f)); + } + } elseif ($i == 2) { + if (!compareFloats(floatval($inputs[$i]), floatval($f))) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump($f); + } + } else { + if ($f !== $inputs[$i]) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump($f); + } + } + } +} + function fetchAsUTF8($conn, $tableName, $inputs) { $query = "SELECT * FROM $tableName"; @@ -132,16 +167,9 @@ function fetchAsFloats($conn, $tableName, $inputs) } } 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); - } + if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { + var_dump($f); + fatalError("in fetchAsFloats: expected $outOfRange for column $i\n"); } } } @@ -179,16 +207,9 @@ function fetchAsInts($conn, $tableName, $inputs) } } 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); - } + if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { + var_dump($f); + fatalError("in fetchAsInts: expected $outOfRange for column $i\n"); } } else { $expected = floor($inputs[$i]); @@ -284,6 +305,7 @@ if ($stmt) { } // Starting fetching using client buffers +fetchAsChar($conn, $tableName, $inputs); fetchAsUTF8($conn, $tableName, $inputs); fetchArray($conn, $tableName, $inputs); fetchAsFloats($conn, $tableName, $inputs);