fix for right truncation error when precision of the column definition and input is not aligned
This commit is contained in:
parent
def61df42c
commit
02e145d988
|
@ -459,8 +459,8 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
|||
if ( stmt->conn->ce_option.enabled && ( sql_type == SQL_UNKNOWN_TYPE || column_size == SQLSRV_UNKNOWN_SIZE )) {
|
||||
ae_get_sql_type_info( stmt, param_num, direction, param_z, encoding, sql_type, column_size, decimal_digits TSRMLS_CC );
|
||||
// change long to double if the sql type is decimal
|
||||
if ( sql_type == SQL_DECIMAL && Z_TYPE_P( param_z ) == IS_LONG )
|
||||
convert_to_double( param_z );
|
||||
if (( sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC ) && Z_TYPE_P(param_z) == IS_LONG )
|
||||
convert_to_double( param_z );
|
||||
}
|
||||
else {
|
||||
// if the sql type is unknown, then set the default based on the PHP type passed in
|
||||
|
@ -491,8 +491,8 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
|||
case IS_FALSE:
|
||||
case IS_LONG:
|
||||
{
|
||||
// if it is boolean, set the lval to 0 or 1
|
||||
convert_to_long( param_z );
|
||||
// if it is boolean, set the lval to 0 or 1
|
||||
convert_to_long( param_z );
|
||||
buffer = ¶m_z->value;
|
||||
buffer_len = sizeof( Z_LVAL_P( param_z ));
|
||||
ind_ptr = buffer_len;
|
||||
|
@ -516,82 +516,115 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
|||
}
|
||||
break;
|
||||
case IS_STRING:
|
||||
buffer = Z_STRVAL_P( param_z );
|
||||
buffer_len = Z_STRLEN_P( param_z );
|
||||
// if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted)
|
||||
if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) {
|
||||
|
||||
zval wbuffer_z;
|
||||
ZVAL_NULL( &wbuffer_z );
|
||||
|
||||
bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z );
|
||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
|
||||
param_num + 1, get_last_error_message() ) {
|
||||
throw core::CoreException();
|
||||
{
|
||||
buffer = Z_STRVAL_P( param_z );
|
||||
buffer_len = Z_STRLEN_P( param_z );
|
||||
|
||||
if( stmt->conn->ce_option.enabled && ( sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC )) {
|
||||
// get the double value
|
||||
double dval = zend_strtod( ZSTR_VAL( Z_STR_P( param_z )), NULL );
|
||||
// find the precision: number of digits before decimal place + decimal_digits
|
||||
size_t numDigitsBeforeDec = 0;
|
||||
double temp = dval;
|
||||
if ( abs( dval ) < 1) {
|
||||
numDigitsBeforeDec = 0;
|
||||
}
|
||||
else {
|
||||
while ( abs( temp ) > 1 ) {
|
||||
temp /= 10;
|
||||
numDigitsBeforeDec++;
|
||||
}
|
||||
}
|
||||
size_t precision = numDigitsBeforeDec + decimal_digits;
|
||||
// when passing a precision 0 to strppintf, it still returns a string with precision of 1
|
||||
// work around it by manually rounding the zval
|
||||
if ( precision == 0 && dval < 1 ) {
|
||||
if ( dval < 0.5 )
|
||||
dval = 0;
|
||||
else
|
||||
dval = 1;
|
||||
}
|
||||
// reformat it with the right number of decimal digits
|
||||
zend_string *str = strpprintf(0, "%.*G", ( int )precision, dval);
|
||||
zend_string_release( Z_STR_P( param_z ));
|
||||
ZVAL_NEW_STR( param_z, str );
|
||||
}
|
||||
buffer = Z_STRVAL_P( &wbuffer_z );
|
||||
buffer_len = Z_STRLEN_P( &wbuffer_z );
|
||||
core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC);
|
||||
}
|
||||
ind_ptr = buffer_len;
|
||||
if( direction != SQL_PARAM_INPUT ) {
|
||||
// PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion,
|
||||
// we reallocate the string if it's interned
|
||||
if ( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) {
|
||||
core::sqlsrv_zval_stringl( param_z, static_cast<const char*>(buffer), buffer_len );
|
||||
buffer = Z_STRVAL_P( param_z );
|
||||
buffer_len = Z_STRLEN_P( param_z );
|
||||
}
|
||||
|
||||
// if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
|
||||
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type,
|
||||
// convert it to wchar first
|
||||
if( direction == SQL_PARAM_INPUT_OUTPUT &&
|
||||
( c_type == SQL_C_WCHAR ||
|
||||
( c_type == SQL_C_BINARY &&
|
||||
( sql_type == SQL_WCHAR ||
|
||||
sql_type == SQL_WVARCHAR ||
|
||||
sql_type == SQL_WLONGVARCHAR )))) {
|
||||
|
||||
// if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted)
|
||||
if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) {
|
||||
|
||||
bool converted = convert_input_param_to_utf16( param_z, param_z );
|
||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
|
||||
zval wbuffer_z;
|
||||
ZVAL_NULL( &wbuffer_z );
|
||||
|
||||
bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z );
|
||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
|
||||
param_num + 1, get_last_error_message() ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
buffer = Z_STRVAL_P( param_z );
|
||||
buffer_len = Z_STRLEN_P( param_z );
|
||||
ind_ptr = buffer_len;
|
||||
buffer = Z_STRVAL_P( &wbuffer_z );
|
||||
buffer_len = Z_STRLEN_P( &wbuffer_z );
|
||||
core::sqlsrv_add_index_zval( *stmt, &( stmt->param_input_strings ), param_num, &wbuffer_z TSRMLS_CC );
|
||||
}
|
||||
ind_ptr = buffer_len;
|
||||
if( direction != SQL_PARAM_INPUT ) {
|
||||
// PHP 5.4 added interned strings, so since we obviously want to change that string here in some fashion,
|
||||
// we reallocate the string if it's interned
|
||||
if( ZSTR_IS_INTERNED( Z_STR_P( param_z ))) {
|
||||
core::sqlsrv_zval_stringl( param_z, static_cast<const char*>(buffer), buffer_len );
|
||||
buffer = Z_STRVAL_P( param_z );
|
||||
buffer_len = Z_STRLEN_P( param_z );
|
||||
}
|
||||
|
||||
// since this is an output string, assure there is enough space to hold the requested size and
|
||||
// set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr)
|
||||
resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, decimal_digits,
|
||||
buffer, buffer_len TSRMLS_CC );
|
||||
// if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
|
||||
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type,
|
||||
// convert it to wchar first
|
||||
if( direction == SQL_PARAM_INPUT_OUTPUT &&
|
||||
( c_type == SQL_C_WCHAR ||
|
||||
( c_type == SQL_C_BINARY &&
|
||||
( sql_type == SQL_WCHAR ||
|
||||
sql_type == SQL_WVARCHAR ||
|
||||
sql_type == SQL_WLONGVARCHAR )))) {
|
||||
|
||||
// save the parameter to be adjusted and/or converted after the results are processed
|
||||
sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast<SQLUINTEGER>( buffer_len ), zval_was_long );
|
||||
bool converted = convert_input_param_to_utf16( param_z, param_z );
|
||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
|
||||
param_num + 1, get_last_error_message() ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
buffer = Z_STRVAL_P( param_z );
|
||||
buffer_len = Z_STRLEN_P( param_z );
|
||||
ind_ptr = buffer_len;
|
||||
}
|
||||
|
||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||
// since this is an output string, assure there is enough space to hold the requested size and
|
||||
// set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr)
|
||||
resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, decimal_digits,
|
||||
buffer, buffer_len TSRMLS_CC );
|
||||
|
||||
// For output parameters, if we set the column_size to be same as the buffer_len,
|
||||
// then if there is a truncation due to the data coming from the server being
|
||||
// greater than the column_size, we don't get any truncation error. In order to
|
||||
// avoid this silent truncation, we set the column_size to be "MAX" size for
|
||||
// string types. This will guarantee that there is no silent truncation for
|
||||
// output parameters.
|
||||
// if column encryption is enabled, at this point the correct column size has been set by SQLDescribeParam
|
||||
if( direction == SQL_PARAM_OUTPUT && !stmt->conn->ce_option.enabled ) {
|
||||
|
||||
switch( sql_type ) {
|
||||
// save the parameter to be adjusted and/or converted after the results are processed
|
||||
sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast<SQLUINTEGER>( buffer_len ), zval_was_long );
|
||||
|
||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||
|
||||
// For output parameters, if we set the column_size to be same as the buffer_len,
|
||||
// then if there is a truncation due to the data coming from the server being
|
||||
// greater than the column_size, we don't get any truncation error. In order to
|
||||
// avoid this silent truncation, we set the column_size to be "MAX" size for
|
||||
// string types. This will guarantee that there is no silent truncation for
|
||||
// output parameters.
|
||||
// if column encryption is enabled, at this point the correct column size has been set by SQLDescribeParam
|
||||
if ( direction == SQL_PARAM_OUTPUT && !stmt->conn->ce_option.enabled ) {
|
||||
|
||||
switch ( sql_type ) {
|
||||
|
||||
case SQL_VARBINARY:
|
||||
case SQL_VARCHAR:
|
||||
case SQL_WVARCHAR:
|
||||
case SQL_WVARCHAR:
|
||||
column_size = SQL_SS_LENGTH_UNLIMITED;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
64
test/functional/pdo_sqlsrv/pdo_decimal_precision.phpt
Normal file
64
test/functional/pdo_sqlsrv/pdo_decimal_precision.phpt
Normal file
|
@ -0,0 +1,64 @@
|
|||
--TEST--
|
||||
prepare with emulate prepare and binding integer
|
||||
--SKIPIF--
|
||||
<?php require('skipif_mid-refactor.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once("MsCommon_mid-refactor.inc");
|
||||
|
||||
try {
|
||||
$conn = connect();
|
||||
|
||||
$tableName = "decimal_table";
|
||||
|
||||
createTable($conn, $tableName, array("c1_decimal0" => "decimal", "c2_decimal4" => "decimal(19,4)"));
|
||||
|
||||
insertRow($conn, $tableName, array("c1_decimal0" => 0.9, "c2_decimal4" => 0.9));
|
||||
insertRow($conn, $tableName, array("c1_decimal0" => 9.9, "c2_decimal4" => 9.9));
|
||||
insertRow($conn, $tableName, array("c1_decimal0" => 999.999, "c2_decimal4" => 999.999));
|
||||
insertRow($conn, $tableName, array("c1_decimal0" => 99999.99999, "c2_decimal4" => 99999.99999));
|
||||
|
||||
$query = "SELECT * FROM $tableName";
|
||||
$stmt = $conn->query($query);
|
||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
var_dump($row);
|
||||
|
||||
dropTable($conn, $tableName);
|
||||
unset($stmt);
|
||||
unset($conn);
|
||||
} catch (PDOException $e) {
|
||||
var_dump($e->errorInfo);
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
array(4) {
|
||||
[0]=>
|
||||
array(2) {
|
||||
["c1_decimal0"]=>
|
||||
string(1) "1"
|
||||
["c2_decimal4"]=>
|
||||
string(5) ".9000"
|
||||
}
|
||||
[1]=>
|
||||
array(2) {
|
||||
["c1_decimal0"]=>
|
||||
string(2) "10"
|
||||
["c2_decimal4"]=>
|
||||
string(6) "9.9000"
|
||||
}
|
||||
[2]=>
|
||||
array(2) {
|
||||
["c1_decimal0"]=>
|
||||
string(4) "1000"
|
||||
["c2_decimal4"]=>
|
||||
string(8) "999.9990"
|
||||
}
|
||||
[3]=>
|
||||
array(2) {
|
||||
["c1_decimal0"]=>
|
||||
string(6) "100000"
|
||||
["c2_decimal4"]=>
|
||||
string(11) "100000.0000"
|
||||
}
|
||||
}
|
55
test/functional/sqlsrv/sqlsrv_decimal_precision.phpt
Normal file
55
test/functional/sqlsrv/sqlsrv_decimal_precision.phpt
Normal file
|
@ -0,0 +1,55 @@
|
|||
--TEST--
|
||||
prepare with emulate prepare and binding integer
|
||||
--SKIPIF--
|
||||
|
||||
--FILE--
|
||||
<?php
|
||||
require_once("MsHelper.inc");
|
||||
|
||||
$conn = AE\connect();
|
||||
|
||||
$tableName = "decimal_table";
|
||||
AE\createTable($conn, $tableName, array(new AE\ColumnMeta("decimal", "c1_decimal0"), new AE\ColumnMeta("decimal(19,4)", "c2_decimal4")));
|
||||
|
||||
AE\insertRow($conn, $tableName, array("c1_decimal0" => 0.9, "c2_decimal4" => 0.9));
|
||||
AE\insertRow($conn, $tableName, array("c1_decimal0" => 9.9, "c2_decimal4" => 9.9));
|
||||
AE\insertRow($conn, $tableName, array("c1_decimal0" => 999.999, "c2_decimal4" => 999.999));
|
||||
AE\insertRow($conn, $tableName, array("c1_decimal0" => 99999.99999, "c2_decimal4" => 99999.99999));
|
||||
|
||||
$query = "SELECT * FROM $tableName";
|
||||
$stmt = sqlsrv_query($conn, $query);
|
||||
while (($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) != NULL) {
|
||||
//print_r($row);
|
||||
var_dump($row);
|
||||
}
|
||||
|
||||
dropTable($conn, $tableName);
|
||||
sqlsrv_free_stmt($stmt);
|
||||
sqlsrv_close($conn);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
array(2) {
|
||||
["c1_decimal0"]=>
|
||||
string(1) "1"
|
||||
["c2_decimal4"]=>
|
||||
string(5) ".9000"
|
||||
}
|
||||
array(2) {
|
||||
["c1_decimal0"]=>
|
||||
string(2) "10"
|
||||
["c2_decimal4"]=>
|
||||
string(6) "9.9000"
|
||||
}
|
||||
array(2) {
|
||||
["c1_decimal0"]=>
|
||||
string(4) "1000"
|
||||
["c2_decimal4"]=>
|
||||
string(8) "999.9990"
|
||||
}
|
||||
array(2) {
|
||||
["c1_decimal0"]=>
|
||||
string(6) "100000"
|
||||
["c2_decimal4"]=>
|
||||
string(11) "100000.0000"
|
||||
}
|
Loading…
Reference in a new issue