Simplied conversions from strings to numbers (#1146)

This commit is contained in:
Jenny Tam 2020-06-26 15:56:51 -07:00 committed by GitHub
parent d5e1d8cfbc
commit ecbd53f712
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 183 additions and 146 deletions

View file

@ -262,64 +262,6 @@ std::string getUTF8StringFromString( _In_z_ const char* source )
#endif // !_WIN32
template <typename Number, typename Char>
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<Number*>( buffer );
#ifdef _WIN32
std::locale loc; // default locale should match system
std::basic_istringstream<Char> 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<Char>::_Iter( is.rdbuf()), std::basic_istream<Char>::_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<std::num_get<char>>( is.getloc() );
std::istreambuf_iterator<char> beg( is ), end;
std::ios_base::iostate err = std::ios_base::goodbit;
if ( std::is_integral<Number>::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<double*>( &row[meta[field_index].offset] );
SQLRETURN r = SQL_SUCCESS;
#ifdef _WIN32
r = number_to_string<WCHAR>( double_data, buffer, buffer_length, out_buffer_length, last_error );
#else
r = number_to_string<char16_t, double>( 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<LONG*>( &row[meta[field_index].offset] );
SQLRETURN r = SQL_SUCCESS;
#ifdef _WIN32
r = number_to_string<WCHAR>( long_data, buffer, buffer_length, out_buffer_length, last_error );
#else
r = number_to_string<char16_t, LONG>( 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<char*>( &row[meta[field_index].offset] ) + sizeof( SQLULEN );
return string_to_number<double>( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error );
double* number_data = reinterpret_cast<double*>(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<SQLWCHAR*>( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
return string_to_number<double>( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error );
double* number_data = reinterpret_cast<double*>(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<char*>( &row[meta[field_index].offset] ) + sizeof( SQLULEN );
return string_to_number<LONG>( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error );
LONG* number_data = reinterpret_cast<LONG*>(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<SQLWCHAR*>( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
return string_to_number<LONG>( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error );
LONG* number_data = reinterpret_cast<LONG*>(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,

View file

@ -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";

View file

@ -8,20 +8,17 @@ PHPT_EXEC=true
<?php
require_once("MsCommon_mid-refactor.inc");
try {
$conn1 = connect();
function runTests($conn, $tableName, $buffered)
{
$tsql = "SELECT val FROM $tableName ORDER BY id";
$options = array(PDO::ATTR_CURSOR => 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";

View file

@ -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);