Skip to content

Commit

Permalink
Simplied conversions from strings to numbers (#1146)
Browse files Browse the repository at this point in the history
  • Loading branch information
yitam authored Jun 26, 2020
1 parent d5e1d8c commit ecbd53f
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 146 deletions.
146 changes: 48 additions & 98 deletions source/shared/core_results.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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 )
{
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
70 changes: 61 additions & 9 deletions test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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";
Expand Down
Loading

0 comments on commit ecbd53f

Please sign in to comment.