diff --git a/appveyor.yml b/appveyor.yml index b4eb35d3..e7d4af30 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,6 +18,15 @@ environment: PHP_DEPSVER: 7.0 PHP_SDK: c:\projects\php matrix: + - BUILD_PLATFORM: x86 + TEST_PHP_SQL_SERVER: (local)\SQL2016 + SQL_INSTANCE: SQL2016 + PHP_VC: 14 + PHP_MAJOR_VER: 7.1 + PHP_MINOR_VER: latest + PHP_SDK_DIR: c:\projects\php\x86 + PHP_INSTALL_DIR: c:\projects\php\x86\bin + platform: x86 - BUILD_PLATFORM: x64 TEST_PHP_SQL_SERVER: (local)\SQL2012SP1 SQL_INSTANCE: SQL2012SP1 @@ -37,15 +46,6 @@ environment: PHP_SDK_DIR: c:\projects\php\x86 PHP_INSTALL_DIR: c:\projects\php\x86\bin platform: x86 - - BUILD_PLATFORM: x64 - TEST_PHP_SQL_SERVER: (local)\SQL2016 - SQL_INSTANCE: SQL2016 - PHP_VC: 14 - PHP_MAJOR_VER: 7.1 - PHP_MINOR_VER: latest - PHP_SDK_DIR: c:\projects\php\x64 - PHP_INSTALL_DIR: c:\projects\php\x64\bin - platform: x64 - BUILD_PLATFORM: x86 TEST_PHP_SQL_SERVER: (local)\SQL2008R2SP2 SQL_INSTANCE: SQL2008R2SP2 @@ -190,8 +190,8 @@ test_script: - ps: $outfiles = Get-ChildItem pdo_sqlsrv\*.out - ps: foreach($file in $difffiles){ls $file; more $file} - ps: foreach($file in $outfiles){ls $file; more $file} - - cd %PHP_INSTALL_DIR% - - OpenCppCoverage.exe --sources %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv --sources %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv --modules *sqlsrv*.dll --export_type=cobertura:.\coverage.xml --cover_children --quiet --optimized_build -- .\php.exe .\run-tests.php -P %APPVEYOR_BUILD_FOLDER%\test\functional\ + - cd %PHP_INSTALL_DIR% + - OpenCppCoverage.exe --sources %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\pdo_sqlsrv --sources %PHP_SDK_DIR%\php-%PHP_VERSION%-src\ext\sqlsrv --modules *sqlsrv*.dll --export_type=cobertura:.\coverage.xml --cover_children --quiet --continue_after_cpp_exception --optimized_build -- .\php.exe .\run-tests.php -P %APPVEYOR_BUILD_FOLDER%\test\functional\ - ls - python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\cleanup_dbs.py -dbname %SQLSRV_DBNAME% - python %APPVEYOR_BUILD_FOLDER%\test\functional\setup\cleanup_dbs.py -dbname %PDOSQLSRV_DBNAME% diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 0c909b44..16ca5711 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1322,10 +1322,11 @@ struct sqlsrv_output_param { SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer bool is_bool; + bool is_long; // string output param constructor - sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len ) : - param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ) + sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len, _In_ bool is_long ) : + param_z( p_z ), encoding( enc ), param_num( num ), original_buffer_len( buffer_len ), is_bool( false ), is_long( is_long ) { } @@ -1335,7 +1336,8 @@ struct sqlsrv_output_param { encoding( SQLSRV_ENCODING_INVALID ), param_num( num ), original_buffer_len( -1 ), - is_bool( is_bool ) + is_bool( is_bool ), + is_long( false ) { } }; @@ -1431,7 +1433,7 @@ typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, err sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht, _In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver TSRMLS_DC ); void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z, - _In_ SQLSRV_PHPTYPE php_out_type, _In_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size, + _In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size, _Inout_ SQLSMALLINT decimal_digits TSRMLS_DC ); SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_reads_bytes_(sql_len) const char* sql = NULL, _In_ int sql_len = 0 ); field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno TSRMLS_DC ); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 29255029..957389e4 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -342,7 +342,7 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm // The sql type is given as a hint if the driver provides it. void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z, - _In_ SQLSRV_PHPTYPE php_out_type, _In_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size, + _In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size, _Inout_ SQLSMALLINT decimal_digits TSRMLS_DC ) { SQLSMALLINT c_type; @@ -373,6 +373,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ } bool zval_was_null = ( Z_TYPE_P( param_z ) == IS_NULL ); bool zval_was_bool = ( Z_TYPE_P( param_z ) == IS_TRUE || Z_TYPE_P( param_z ) == IS_FALSE ); + bool zval_was_long = ( Z_TYPE_P( param_z ) == IS_LONG && php_out_type == SQLSRV_PHPTYPE_INT && (sql_type == SQL_BIGINT || sql_type == SQL_UNKNOWN_TYPE )); // if the user asks for for a specific type for input and output, make sure the data type we send matches the data we // type we expect back, since we can only send and receive the same type. Anything can be converted to a string, so // we always let that match if they want a string back. @@ -383,7 +384,16 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ if( zval_was_null || zval_was_bool ) { convert_to_long( param_z ); } - match = Z_TYPE_P( param_z ) == IS_LONG; + if( zval_was_long ){ + convert_to_string( param_z ); + if ( encoding != SQLSRV_ENCODING_SYSTEM && encoding != SQLSRV_ENCODING_UTF8 && encoding != SQLSRV_ENCODING_BINARY ) { + encoding = SQLSRV_ENCODING_SYSTEM; + } + match = Z_TYPE_P( param_z ) == IS_STRING; + } + else { + match = Z_TYPE_P( param_z ) == IS_LONG; + } break; case SQLSRV_PHPTYPE_FLOAT: if( zval_was_null ) { @@ -415,7 +425,15 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ if( direction == SQL_PARAM_OUTPUT ) { switch( php_out_type ) { case SQLSRV_PHPTYPE_INT: - convert_to_long( param_z ); + if( zval_was_long ){ + convert_to_string( param_z ); + if ( encoding != SQLSRV_ENCODING_SYSTEM && encoding != SQLSRV_ENCODING_UTF8 && encoding != SQLSRV_ENCODING_BINARY ) { + encoding = SQLSRV_ENCODING_SYSTEM; + } + } + else { + convert_to_long( param_z ); + } break; case SQLSRV_PHPTYPE_FLOAT: convert_to_double( param_z ); @@ -551,7 +569,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ buffer, buffer_len TSRMLS_CC ); // 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( buffer_len )); + sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len ), zval_was_long ); save_output_param_for_later( stmt, output_param TSRMLS_CC ); @@ -2127,6 +2145,15 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) else { core::sqlsrv_zval_stringl(value_z, str, str_len); } + if ( output_param->is_long ) { + zval* value_z_temp = ( zval * )sqlsrv_malloc( sizeof( zval )); + ZVAL_COPY( value_z_temp, value_z ); + convert_to_double( value_z_temp ); + if ( Z_DVAL_P( value_z_temp ) > INT_MIN && Z_DVAL_P( value_z_temp ) < INT_MAX ) { + convert_to_long( value_z ); + } + sqlsrv_free( value_z_temp ); + } } break; case IS_LONG: diff --git a/test/functional/pdo_sqlsrv/pdo_bigint_outparam.phpt b/test/functional/pdo_sqlsrv/pdo_bigint_outparam.phpt new file mode 100644 index 00000000..15234ff5 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_bigint_outparam.phpt @@ -0,0 +1,82 @@ +--TEST-- +Test for binding bigint output and inout parameters +--SKIPIF-- + +--FILE-- + "bigint")); + +// Create a Stored Procedure +$spname = "selectBigint"; +$spSql = "CREATE PROCEDURE $spname (@c1_bigint bigint OUTPUT) AS + SELECT @c1_bigint = c1_bigint FROM $tbname"; +$conn->query($spSql); + +// Insert a large bigint +insertRow($conn, $tbname, array("c1_bigint" => 922337203685479936)); + +// Call stored procedure with output +$outSql = "{CALL $spname (?)}"; +$bigintOut = 0; +$stmt = $conn->prepare($outSql); +$stmt->bindParam(1, $bigintOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); +$stmt->execute(); +printf("Large bigint output:\n" ); +var_dump($bigintOut); +printf("\n"); + +// Call stored procedure with inout +$bigintOut = 0; +$stmt = $conn->prepare($outSql); +$stmt->bindParam(1, $bigintOut, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); +$stmt->execute(); +printf("Large bigint inout:\n" ); +var_dump($bigintOut); +printf("\n"); + +$conn->exec("TRUNCATE TABLE $tbname"); + +// Insert a random small value truncated from the bigint input +insertRow($conn, $tbname, array("c1_bigint" => 922337203)); + +// Call stored procedure with output +$bigintOut = 0; +$stmt = $conn->prepare($outSql); +$stmt->bindParam(1, $bigintOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); +$stmt->execute(); +printf("Small bigint output:\n" ); +var_dump($bigintOut); +printf("\n"); + +// Call stored procedure with inout +$bigintOut = 0; +$stmt = $conn->prepare($outSql); +$stmt->bindParam(1, $bigintOut, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); +$stmt->execute(); +printf("Small bigint inout:\n" ); +var_dump($bigintOut); +printf("\n"); + +dropProc($conn, $spname); +dropTable($conn, $tbname); +unset($stmt); +unset($conn); +?> +--EXPECT-- +Large bigint output: +string(18) "922337203685479936" + +Large bigint inout: +string(18) "922337203685479936" + +Small bigint output: +int(922337203) + +Small bigint inout: +int(922337203) diff --git a/test/functional/pdo_sqlsrv/pdo_bool_outparam.phpt b/test/functional/pdo_sqlsrv/pdo_bool_outparam.phpt new file mode 100644 index 00000000..2048e496 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_bool_outparam.phpt @@ -0,0 +1,82 @@ +--TEST-- +Test for binding boolean output and inout parameters +--SKIPIF-- + +--FILE-- + "int")); + +// Create a Stored Procedure +$spname = "selectBool"; +$spSql = "CREATE PROCEDURE $spname (@c1_bool int OUTPUT) AS + SELECT @c1_bool = c1_bool FROM $tbname"; +$conn->query($spSql); + +// Insert 1 +insertRow($conn, $tbname, array("c1_bool" => 1)); + +// Call stored procedure with output +$outSql = "{CALL $spname (?)}"; +$boolOut = false; +$stmt = $conn->prepare($outSql); +$stmt->bindParam(1, $boolOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); +$stmt->execute(); +printf("True bool output:\n" ); +var_dump($boolOut); +printf("\n"); + +// Call stored procedure with inout +$boolOut = false; +$stmt = $conn->prepare($outSql); +$stmt->bindParam(1, $boolOut, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); +$stmt->execute(); +printf("True bool inout:\n" ); +var_dump($boolOut); +printf("\n"); + +$conn->exec("TRUNCATE TABLE $tbname"); + +// Insert 0 +insertRow($conn, $tbname, array("c1_bool" => 0)); + +// Call stored procedure with output +$boolOut = true; +$stmt = $conn->prepare($outSql); +$stmt->bindParam(1, $boolOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); +$stmt->execute(); +printf("True bool output:\n" ); +var_dump($boolOut); +printf("\n"); + +// Call stored procedure with inout +$boolOut = true; +$stmt = $conn->prepare($outSql); +$stmt->bindParam(1, $boolOut, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); +$stmt->execute(); +printf("True bool inout:\n" ); +var_dump($boolOut); +printf("\n"); + +dropProc($conn, $spname); +dropTable($conn, $tbname); +unset($stmt); +unset($conn); +?> +--EXPECT-- +True bool output: +int(1) + +True bool inout: +int(1) + +True bool output: +int(0) + +True bool inout: +int(0) diff --git a/test/functional/sqlsrv/MsHelper.inc b/test/functional/sqlsrv/MsHelper.inc index c6fa7173..7cf5f3f8 100644 --- a/test/functional/sqlsrv/MsHelper.inc +++ b/test/functional/sqlsrv/MsHelper.inc @@ -328,6 +328,7 @@ function connect($options = array(), $disableCE = false) */ function createTable($conn, $tbname, $columnMetaArr) { + require_once("MsCommon.inc"); dropTable($conn, $tbname); $colDef = ""; foreach ($columnMetaArr as $meta) { diff --git a/test/functional/sqlsrv/sqlsrv_bigint_outparam.phpt b/test/functional/sqlsrv/sqlsrv_bigint_outparam.phpt new file mode 100644 index 00000000..f305ecee --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_bigint_outparam.phpt @@ -0,0 +1,79 @@ +--TEST-- +Test for binding bigint output and inout parameters +--SKIPIF-- + +--FILE-- + 922337203685479936)); + +// Call stored procedure with SQLSRV_PARAM_OUT +$outSql = "{CALL $spname (?)}"; +$bigintOut = 0; +$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$bigintOut, SQLSRV_PARAM_OUT))); +sqlsrv_execute($stmt); +printf("Large bigint output:\n"); +var_dump($bigintOut); +printf("\n"); + +// Call stored procedure with SQLSRV_PARAM_INOUT +$bigintOut = 0; +$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$bigintOut, SQLSRV_PARAM_INOUT))); +sqlsrv_execute($stmt); +printf("Large bigint inout:\n"); +var_dump($bigintOut); +printf("\n"); +sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); + +// Insert a random small value truncated from the bigint input +AE\insertRow($conn, $tbname, array("c1_bigint" => 922337203)); + +// Call stored procedure with SQLSRV_PARAM_OUT +$bigintOut = 0; +$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$bigintOut, SQLSRV_PARAM_OUT))); +sqlsrv_execute($stmt); +printf("Small bigint output:\n"); +var_dump($bigintOut); +printf("\n"); + +// Call stored procedure with SQLSRV_PARAM_INOUT +$bigintOut = 0; +$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$bigintOut, SQLSRV_PARAM_INOUT))); +sqlsrv_execute($stmt); +printf("Small bigint inout:\n"); +var_dump($bigintOut); +printf("\n"); + +dropProc($conn, $spname); +dropTable($conn, $tbname); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +Large bigint output: +string(18) "922337203685479936" + +Large bigint inout: +string(18) "922337203685479936" + +Small bigint output: +int(922337203) + +Small bigint inout: +int(922337203) diff --git a/test/functional/sqlsrv/sqlsrv_bool_outparam.phpt b/test/functional/sqlsrv/sqlsrv_bool_outparam.phpt new file mode 100644 index 00000000..45ba3fed --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_bool_outparam.phpt @@ -0,0 +1,79 @@ +--TEST-- +Test for binding boolean output and inout parameters +--SKIPIF-- + +--FILE-- + 1)); + +// Call stored procedure with SQLSRV_PARAM_OUT +$outSql = "{CALL $spname (?)}"; +$boolOut = false; +$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$boolOut, SQLSRV_PARAM_OUT, SQLSRV_PHPTYPE_INT))); +sqlsrv_execute($stmt); +printf("True bool output:\n"); +var_dump($boolOut); +printf("\n"); + +// Call stored procedure with SQLSRV_PARAM_INOUT +$boolOut = false; +$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$boolOut, SQLSRV_PARAM_INOUT, SQLSRV_PHPTYPE_INT))); +sqlsrv_execute($stmt); +printf("True bool inout:\n"); +var_dump($boolOut); +printf("\n"); +sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); + +// Insert 0 +AE\insertRow($conn, $tbname, array("c1_bool" => 0)); + +// Call stored procedure with SQLSRV_PARAM_OUT +$boolOut = true; +$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$boolOut, SQLSRV_PARAM_OUT, SQLSRV_PHPTYPE_INT))); +sqlsrv_execute($stmt); +printf("False bool output:\n"); +var_dump($boolOut); +printf("\n"); + +// Call stored procedure with SQLSRV_PARAM_INOUT +$boolOut = true; +$stmt = sqlsrv_prepare($conn, $outSql, array(array(&$boolOut, SQLSRV_PARAM_INOUT, SQLSRV_PHPTYPE_INT))); +sqlsrv_execute($stmt); +printf("False bool inout:\n"); +var_dump($boolOut); +printf("\n"); + +dropProc($conn, $spname); +dropTable($conn, $tbname); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +True bool output: +bool(true) + +True bool inout: +bool(true) + +False bool output: +bool(false) + +False bool inout: +bool(false)