Merge pull request #571 from v-kaywon/AEdecimalprecision
fix for right truncation error when precision of the column definition and input is not aligned
This commit is contained in:
commit
779c39abc2
|
@ -27,16 +27,16 @@ environment:
|
||||||
PHP_SDK_DIR: c:\projects\php\x86
|
PHP_SDK_DIR: c:\projects\php\x86
|
||||||
PHP_INSTALL_DIR: c:\projects\php\x86\bin
|
PHP_INSTALL_DIR: c:\projects\php\x86\bin
|
||||||
platform: x86
|
platform: x86
|
||||||
- BUILD_PLATFORM: x64
|
- BUILD_PLATFORM: x86
|
||||||
TEST_PHP_SQL_SERVER: (local)\SQL2012SP1
|
TEST_PHP_SQL_SERVER: (local)\SQL2012SP1
|
||||||
SQL_INSTANCE: SQL2012SP1
|
SQL_INSTANCE: SQL2012SP1
|
||||||
PHP_VC: 14
|
PHP_VC: 14
|
||||||
PHP_MAJOR_VER: 7.0
|
PHP_MAJOR_VER: 7.0
|
||||||
PHP_MINOR_VER: latest
|
PHP_MINOR_VER: latest
|
||||||
PHP_SDK_DIR: c:\projects\php\x64
|
PHP_SDK_DIR: c:\projects\php\x86
|
||||||
PHP_INSTALL_DIR: c:\projects\php\x64\bin
|
PHP_INSTALL_DIR: c:\projects\php\x86\bin
|
||||||
PHP_ZTS: --disable-zts
|
PHP_ZTS: --disable-zts
|
||||||
platform: x64
|
platform: x86
|
||||||
- BUILD_PLATFORM: x86
|
- BUILD_PLATFORM: x86
|
||||||
TEST_PHP_SQL_SERVER: (local)\SQL2014
|
TEST_PHP_SQL_SERVER: (local)\SQL2014
|
||||||
SQL_INSTANCE: SQL2014
|
SQL_INSTANCE: SQL2014
|
||||||
|
|
|
@ -357,18 +357,18 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// check is only < because params are 0 based
|
// check is only < because params are 0 based
|
||||||
CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ) {
|
CHECK_CUSTOM_ERROR( param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1 ){
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// resize the statements array of int_ptrs if the parameter isn't already set.
|
// resize the statements array of int_ptrs if the parameter isn't already set.
|
||||||
if( stmt->param_ind_ptrs.size() < static_cast<size_t>(param_num + 1) ) {
|
if( stmt->param_ind_ptrs.size() < static_cast<size_t>( param_num + 1 )){
|
||||||
stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA );
|
stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA );
|
||||||
}
|
}
|
||||||
SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ];
|
SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ];
|
||||||
|
|
||||||
zval* param_ref = param_z;
|
zval* param_ref = param_z;
|
||||||
if ( Z_ISREF_P( param_z ) ) {
|
if( Z_ISREF_P( param_z )){
|
||||||
ZVAL_DEREF( param_z );
|
ZVAL_DEREF( param_z );
|
||||||
}
|
}
|
||||||
bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL );
|
bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL );
|
||||||
|
@ -379,24 +379,24 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
// we always let that match if they want a string back.
|
// we always let that match if they want a string back.
|
||||||
if( direction == SQL_PARAM_INPUT_OUTPUT ) {
|
if( direction == SQL_PARAM_INPUT_OUTPUT ) {
|
||||||
bool match = false;
|
bool match = false;
|
||||||
switch( php_out_type ) {
|
switch( php_out_type ){
|
||||||
case SQLSRV_PHPTYPE_INT:
|
case SQLSRV_PHPTYPE_INT:
|
||||||
if( zval_was_null || zval_was_bool ) {
|
if( zval_was_null || zval_was_bool ){
|
||||||
convert_to_long( param_z );
|
convert_to_long( param_z );
|
||||||
}
|
}
|
||||||
if( zval_was_long ){
|
if( zval_was_long ){
|
||||||
convert_to_string( param_z );
|
convert_to_string( param_z );
|
||||||
if ( encoding != SQLSRV_ENCODING_SYSTEM && encoding != SQLSRV_ENCODING_UTF8 && encoding != SQLSRV_ENCODING_BINARY ) {
|
if( encoding != SQLSRV_ENCODING_SYSTEM && encoding != SQLSRV_ENCODING_UTF8 && encoding != SQLSRV_ENCODING_BINARY ){
|
||||||
encoding = SQLSRV_ENCODING_SYSTEM;
|
encoding = SQLSRV_ENCODING_SYSTEM;
|
||||||
}
|
}
|
||||||
match = Z_TYPE_P( param_z ) == IS_STRING;
|
match = Z_TYPE_P( param_z ) == IS_STRING;
|
||||||
}
|
}
|
||||||
else {
|
else{
|
||||||
match = Z_TYPE_P( param_z ) == IS_LONG;
|
match = Z_TYPE_P( param_z ) == IS_LONG;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SQLSRV_PHPTYPE_FLOAT:
|
case SQLSRV_PHPTYPE_FLOAT:
|
||||||
if( zval_was_null ) {
|
if( zval_was_null ){
|
||||||
convert_to_double( param_z );
|
convert_to_double( param_z );
|
||||||
}
|
}
|
||||||
match = Z_TYPE_P( param_z ) == IS_DOUBLE;
|
match = Z_TYPE_P( param_z ) == IS_DOUBLE;
|
||||||
|
@ -415,23 +415,23 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." );
|
SQLSRV_ASSERT( false, "Unknown SQLSRV_PHPTYPE_* constant given." );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ) {
|
CHECK_CUSTOM_ERROR( !match, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1 ){
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so
|
// if it's an output parameter and the user asks for a certain type, we have to convert the zval to that type so
|
||||||
// when the buffer is filled, the type is correct
|
// when the buffer is filled, the type is correct
|
||||||
if( direction == SQL_PARAM_OUTPUT ) {
|
if( direction == SQL_PARAM_OUTPUT ){
|
||||||
switch( php_out_type ) {
|
switch( php_out_type ) {
|
||||||
case SQLSRV_PHPTYPE_INT:
|
case SQLSRV_PHPTYPE_INT:
|
||||||
if( zval_was_long ){
|
if( zval_was_long ){
|
||||||
convert_to_string( param_z );
|
convert_to_string( param_z );
|
||||||
if ( encoding != SQLSRV_ENCODING_SYSTEM && encoding != SQLSRV_ENCODING_UTF8 && encoding != SQLSRV_ENCODING_BINARY ) {
|
if( encoding != SQLSRV_ENCODING_SYSTEM && encoding != SQLSRV_ENCODING_UTF8 && encoding != SQLSRV_ENCODING_BINARY ){
|
||||||
encoding = SQLSRV_ENCODING_SYSTEM;
|
encoding = SQLSRV_ENCODING_SYSTEM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else{
|
||||||
convert_to_long( param_z );
|
convert_to_long( param_z );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -456,20 +456,20 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 ||
|
( encoding == SQLSRV_ENCODING_SYSTEM || encoding == SQLSRV_ENCODING_UTF8 ||
|
||||||
encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" );
|
encoding == SQLSRV_ENCODING_BINARY ), "core_sqlsrv_bind_param: invalid encoding" );
|
||||||
|
|
||||||
if ( stmt->conn->ce_option.enabled && ( sql_type == SQL_UNKNOWN_TYPE || column_size == SQLSRV_UNKNOWN_SIZE )) {
|
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 );
|
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
|
// change long to double if the sql type is decimal
|
||||||
if ( sql_type == SQL_DECIMAL && Z_TYPE_P( param_z ) == IS_LONG )
|
if(( sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC ) && Z_TYPE_P(param_z) == IS_LONG )
|
||||||
convert_to_double( param_z );
|
convert_to_double( param_z );
|
||||||
}
|
}
|
||||||
else {
|
else{
|
||||||
// if the sql type is unknown, then set the default based on the PHP type passed in
|
// if the sql type is unknown, then set the default based on the PHP type passed in
|
||||||
if ( sql_type == SQL_UNKNOWN_TYPE ) {
|
if( sql_type == SQL_UNKNOWN_TYPE ){
|
||||||
default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC );
|
default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC );
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the size is unknown, then set the default based on the PHP type passed in
|
// if the size is unknown, then set the default based on the PHP type passed in
|
||||||
if ( column_size == SQLSRV_UNKNOWN_SIZE ) {
|
if( column_size == SQLSRV_UNKNOWN_SIZE ){
|
||||||
default_sql_size_and_scale( stmt, static_cast<unsigned int>(param_num), param_z, encoding, column_size, decimal_digits TSRMLS_CC );
|
default_sql_size_and_scale( stmt, static_cast<unsigned int>(param_num), param_z, encoding, column_size, decimal_digits TSRMLS_CC );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -477,7 +477,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC );
|
c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC );
|
||||||
|
|
||||||
// set the buffer based on the PHP parameter type
|
// set the buffer based on the PHP parameter type
|
||||||
switch( Z_TYPE_P( param_z )) {
|
switch( Z_TYPE_P( param_z )){
|
||||||
|
|
||||||
case IS_NULL:
|
case IS_NULL:
|
||||||
{
|
{
|
||||||
|
@ -491,12 +491,12 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
case IS_FALSE:
|
case IS_FALSE:
|
||||||
case IS_LONG:
|
case IS_LONG:
|
||||||
{
|
{
|
||||||
// if it is boolean, set the lval to 0 or 1
|
// if it is boolean, set the lval to 0 or 1
|
||||||
convert_to_long( param_z );
|
convert_to_long( param_z );
|
||||||
buffer = ¶m_z->value;
|
buffer = ¶m_z->value;
|
||||||
buffer_len = sizeof( Z_LVAL_P( param_z ));
|
buffer_len = sizeof( Z_LVAL_P( param_z ));
|
||||||
ind_ptr = buffer_len;
|
ind_ptr = buffer_len;
|
||||||
if( direction != SQL_PARAM_INPUT ) {
|
if( direction != SQL_PARAM_INPUT ){
|
||||||
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
|
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
|
||||||
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), zval_was_bool );
|
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), zval_was_bool );
|
||||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||||
|
@ -508,7 +508,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
buffer = ¶m_z->value;
|
buffer = ¶m_z->value;
|
||||||
buffer_len = sizeof( Z_DVAL_P( param_z ));
|
buffer_len = sizeof( Z_DVAL_P( param_z ));
|
||||||
ind_ptr = buffer_len;
|
ind_ptr = buffer_len;
|
||||||
if( direction != SQL_PARAM_INPUT ) {
|
if( direction != SQL_PARAM_INPUT ){
|
||||||
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
|
// save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned
|
||||||
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), false );
|
sqlsrv_output_param output_param( param_ref, static_cast<int>( param_num ), false );
|
||||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||||
|
@ -516,82 +516,115 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case IS_STRING:
|
case IS_STRING:
|
||||||
buffer = Z_STRVAL_P( param_z );
|
{
|
||||||
buffer_len = Z_STRLEN_P( param_z );
|
buffer = Z_STRVAL_P( param_z );
|
||||||
// if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted)
|
buffer_len = Z_STRLEN_P( param_z );
|
||||||
if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ) {
|
|
||||||
|
if( stmt->conn->ce_option.enabled && ( sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC )){
|
||||||
zval wbuffer_z;
|
// get the double value
|
||||||
ZVAL_NULL( &wbuffer_z );
|
double dval = zend_strtod( ZSTR_VAL( Z_STR_P( param_z )), NULL );
|
||||||
|
// find the precision: number of digits before decimal place + decimal_digits
|
||||||
bool converted = convert_input_param_to_utf16( param_z, &wbuffer_z );
|
size_t numDigitsBeforeDec = 0;
|
||||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
|
double temp = dval;
|
||||||
param_num + 1, get_last_error_message() ) {
|
if( abs( dval ) < 1){
|
||||||
throw core::CoreException();
|
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 && abs( 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 );
|
// if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted)
|
||||||
core::sqlsrv_add_index_zval(*stmt, &(stmt->param_input_strings), param_num, &wbuffer_z TSRMLS_CC);
|
if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ){
|
||||||
}
|
|
||||||
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 )))) {
|
|
||||||
|
|
||||||
bool converted = convert_input_param_to_utf16( param_z, param_z );
|
zval wbuffer_z;
|
||||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE,
|
ZVAL_NULL( &wbuffer_z );
|
||||||
param_num + 1, get_last_error_message() ) {
|
|
||||||
|
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();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
buffer = Z_STRVAL_P( param_z );
|
buffer = Z_STRVAL_P( &wbuffer_z );
|
||||||
buffer_len = Z_STRLEN_P( param_z );
|
buffer_len = Z_STRLEN_P( &wbuffer_z );
|
||||||
ind_ptr = buffer_len;
|
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
|
// if it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
|
||||||
// set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr)
|
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type,
|
||||||
resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, decimal_digits,
|
// convert it to wchar first
|
||||||
buffer, buffer_len TSRMLS_CC );
|
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
|
bool converted = convert_input_param_to_utf16( param_z, param_z );
|
||||||
sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast<SQLUINTEGER>( buffer_len ), zval_was_long );
|
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,
|
// save the parameter to be adjusted and/or converted after the results are processed
|
||||||
// then if there is a truncation due to the data coming from the server being
|
sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast<SQLUINTEGER>( buffer_len ), zval_was_long );
|
||||||
// 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
|
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||||
// string types. This will guarantee that there is no silent truncation for
|
|
||||||
// output parameters.
|
// For output parameters, if we set the column_size to be same as the buffer_len,
|
||||||
// if column encryption is enabled, at this point the correct column size has been set by SQLDescribeParam
|
// then if there is a truncation due to the data coming from the server being
|
||||||
if( direction == SQL_PARAM_OUTPUT && !stmt->conn->ce_option.enabled ) {
|
// 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
|
||||||
switch( sql_type ) {
|
// 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_VARBINARY:
|
||||||
case SQL_VARCHAR:
|
case SQL_VARCHAR:
|
||||||
case SQL_WVARCHAR:
|
case SQL_WVARCHAR:
|
||||||
column_size = SQL_SS_LENGTH_UNLIMITED;
|
column_size = SQL_SS_LENGTH_UNLIMITED;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -624,36 +657,36 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
|
|
||||||
zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC );
|
zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC );
|
||||||
|
|
||||||
while( class_entry != NULL ) {
|
while( class_entry != NULL ){
|
||||||
SQLSRV_ASSERT( class_entry->name != NULL, "core_sqlsrv_bind_param: class_entry->name is NULL." );
|
SQLSRV_ASSERT( class_entry->name != NULL, "core_sqlsrv_bind_param: class_entry->name is NULL." );
|
||||||
if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL &&
|
if( class_entry->name->len == DateTime::DATETIME_CLASS_NAME_LEN && class_entry->name != NULL &&
|
||||||
stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ) {
|
stricmp( class_entry->name->val, DateTime::DATETIME_CLASS_NAME ) == 0 ){
|
||||||
valid_class_name_found = true;
|
valid_class_name_found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else{
|
||||||
|
|
||||||
// Check the parent
|
// Check the parent
|
||||||
class_entry = class_entry->parent;
|
class_entry = class_entry->parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
|
CHECK_CUSTOM_ERROR( !valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ){
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error'
|
// if the user specifies the 'date' sql type, giving it the normal format will cause a 'date overflow error'
|
||||||
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
|
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
|
||||||
// sql type, it lacks the timezone.
|
// sql type, it lacks the timezone.
|
||||||
if( sql_type == SQL_SS_TIMESTAMPOFFSET ) {
|
if( sql_type == SQL_SS_TIMESTAMPOFFSET ){
|
||||||
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATETIMEOFFSET_FORMAT ),
|
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATETIMEOFFSET_FORMAT ),
|
||||||
DateTime::DATETIMEOFFSET_FORMAT_LEN );
|
DateTime::DATETIMEOFFSET_FORMAT_LEN );
|
||||||
}
|
}
|
||||||
else if( sql_type == SQL_TYPE_DATE ) {
|
else if( sql_type == SQL_TYPE_DATE ){
|
||||||
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN );
|
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN );
|
||||||
}
|
}
|
||||||
else {
|
else{
|
||||||
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN );
|
core::sqlsrv_zval_stringl( &format_z, const_cast<char*>( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN );
|
||||||
}
|
}
|
||||||
// call the DateTime::format member function to convert the object to a string that SQL Server understands
|
// call the DateTime::format member function to convert the object to a string that SQL Server understands
|
||||||
|
@ -664,12 +697,12 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC );
|
int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC );
|
||||||
zend_string_release( Z_STR( format_z ));
|
zend_string_release( Z_STR( format_z ));
|
||||||
zend_string_release( Z_STR( function_z ));
|
zend_string_release( Z_STR( function_z ));
|
||||||
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
|
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ){
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
buffer = Z_STRVAL( buffer_z );
|
buffer = Z_STRVAL( buffer_z );
|
||||||
zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z );
|
zr = add_next_index_zval( &( stmt->param_datetime_buffers ), &buffer_z );
|
||||||
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ) {
|
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ){
|
||||||
throw core::CoreException();
|
throw core::CoreException();
|
||||||
}
|
}
|
||||||
buffer_len = Z_STRLEN( buffer_z ) - 1;
|
buffer_len = Z_STRLEN( buffer_z ) - 1;
|
||||||
|
@ -685,7 +718,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( zval_was_null ) {
|
if( zval_was_null ){
|
||||||
ind_ptr = SQL_NULL_DATA;
|
ind_ptr = SQL_NULL_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,13 +726,13 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC );
|
c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC );
|
||||||
if ( stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP )
|
if ( stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP )
|
||||||
{
|
{
|
||||||
if ( decimal_digits == 3 )
|
if( decimal_digits == 3 )
|
||||||
core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, 0 );
|
core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, 0 );
|
||||||
else if (decimal_digits == 0)
|
else if (decimal_digits == 0)
|
||||||
core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, 0 );
|
core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, 0 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( core::CoreException& e ) {
|
catch( core::CoreException& e ){
|
||||||
stmt->free_param_data( TSRMLS_C );
|
stmt->free_param_data( TSRMLS_C );
|
||||||
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
|
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
|
||||||
throw e;
|
throw e;
|
||||||
|
|
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--
|
||||||
|
Insert into decimal columns with inputs of various scale and precision
|
||||||
|
--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"
|
||||||
|
}
|
||||||
|
}
|
54
test/functional/sqlsrv/sqlsrv_decimal_precision.phpt
Normal file
54
test/functional/sqlsrv/sqlsrv_decimal_precision.phpt
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
--TEST--
|
||||||
|
Insert into decimal columns with inputs of various scale and precision
|
||||||
|
--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) {
|
||||||
|
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