Merge pull request #761 from yitam/vso2823
Made changes to output param handling code to convert doubles to ints, if necessary
This commit is contained in:
commit
5a842c9b32
|
@ -406,8 +406,8 @@ pdo_error PDO_ERRORS[] = {
|
||||||
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -83, false }
|
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -83, false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
|
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
|
||||||
{ IMSSP, (SQLCHAR*) "Connection with Column Encryption enabled does not support fetching stream. Please fetch the data as a string.", -84, false }
|
{ IMSSP, (SQLCHAR*) "Error converting a double (value out of range) to an integer.", -84, false }
|
||||||
},
|
},
|
||||||
{ UINT_MAX, {} }
|
{ UINT_MAX, {} }
|
||||||
};
|
};
|
||||||
|
|
|
@ -1311,23 +1311,25 @@ struct sqlsrv_output_param {
|
||||||
|
|
||||||
zval* param_z;
|
zval* param_z;
|
||||||
SQLSRV_ENCODING encoding;
|
SQLSRV_ENCODING encoding;
|
||||||
SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement
|
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
|
SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer
|
||||||
|
SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary
|
||||||
bool is_bool;
|
bool is_bool;
|
||||||
|
|
||||||
// string output param constructor
|
// string output param constructor
|
||||||
sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len ) :
|
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 )
|
param_z(p_z), encoding(enc), param_num(num), original_buffer_len(buffer_len), is_bool(false), php_out_type(SQLSRV_PHPTYPE_INVALID)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// every other type output parameter constructor
|
// every other type output parameter constructor
|
||||||
sqlsrv_output_param( _In_ zval* p_z, _In_ int num, _In_ bool is_bool ) :
|
sqlsrv_output_param( _In_ zval* p_z, _In_ int num, _In_ bool is_bool, _In_ SQLSRV_PHPTYPE php_out_type) :
|
||||||
param_z( p_z ),
|
param_z( p_z ),
|
||||||
encoding( SQLSRV_ENCODING_INVALID ),
|
encoding( SQLSRV_ENCODING_INVALID ),
|
||||||
param_num( num ),
|
param_num( num ),
|
||||||
original_buffer_len( -1 ),
|
original_buffer_len( -1 ),
|
||||||
is_bool( is_bool )
|
is_bool( is_bool ),
|
||||||
|
php_out_type(php_out_type)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1699,7 +1701,7 @@ enum SQLSRV_ERROR_CODES {
|
||||||
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
|
SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED,
|
||||||
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
|
SQLSRV_ERROR_INVALID_BUFFER_LIMIT,
|
||||||
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
|
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
|
||||||
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
|
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
|
||||||
|
|
||||||
// Driver specific error codes starts from here.
|
// Driver specific error codes starts from here.
|
||||||
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
|
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
|
||||||
|
|
|
@ -491,7 +491,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
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, php_out_type);
|
||||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -503,7 +503,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
||||||
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 ), zval_was_bool, php_out_type);
|
||||||
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
save_output_param_for_later( stmt, output_param TSRMLS_CC );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2149,8 +2149,23 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
||||||
break;
|
break;
|
||||||
case IS_DOUBLE:
|
case IS_DOUBLE:
|
||||||
// for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so
|
// for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so
|
||||||
if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) {
|
if (stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA) {
|
||||||
ZVAL_NULL( value_z );
|
ZVAL_NULL(value_z);
|
||||||
|
}
|
||||||
|
else if (output_param->php_out_type == SQLSRV_PHPTYPE_INT) {
|
||||||
|
// first check if its value is out of range
|
||||||
|
double dval = Z_DVAL_P(value_z);
|
||||||
|
if (dval > INT_MAX || dval < INT_MIN) {
|
||||||
|
CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED) {
|
||||||
|
throw core::CoreException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the output param is a boolean, still convert to
|
||||||
|
// a long integer first to take care of rounding
|
||||||
|
convert_to_long(value_z);
|
||||||
|
if (output_param->is_bool) {
|
||||||
|
convert_to_boolean(value_z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -397,8 +397,8 @@ ss_error SS_ERRORS[] = {
|
||||||
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -108, false }
|
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -108, false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
|
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
|
||||||
{ IMSSP, (SQLCHAR*) "Connection with Column Encryption enabled does not support fetching stream. Please fetch the data as a string.", -109, false }
|
{ IMSSP, (SQLCHAR*)"Error converting a double (value out of range) to an integer.", -109, false }
|
||||||
},
|
},
|
||||||
|
|
||||||
// terminate the list of errors/warnings
|
// terminate the list of errors/warnings
|
||||||
|
|
141
test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt
Normal file
141
test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
--TEST--
|
||||||
|
GitHub issue 707 - binding decimals/numerics to integers or booleans with ColumnEncryption
|
||||||
|
--DESCRIPTION--
|
||||||
|
Verifies that the double values will be rounded as integers or returned as booleans
|
||||||
|
The key of this test is to connect with ColumnEncryption enabled, and the table columns
|
||||||
|
do not need to be encrypted
|
||||||
|
--ENV--
|
||||||
|
PHPT_EXEC=true
|
||||||
|
--SKIPIF--
|
||||||
|
<?php require('skipif_unix.inc'); ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
require_once("MsSetup.inc");
|
||||||
|
require_once("MsCommon_mid-refactor.inc");
|
||||||
|
|
||||||
|
$error = "Error converting a double (value out of range) to an integer";
|
||||||
|
|
||||||
|
function getOutputs($stmt, $outSql, $id, $pdoParamType, $inout = false)
|
||||||
|
{
|
||||||
|
$dec = $num = 0;
|
||||||
|
|
||||||
|
if ($inout) {
|
||||||
|
$paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT;
|
||||||
|
} else {
|
||||||
|
$paramType = $pdoParamType;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->bindParam(1, $id, PDO::PARAM_INT);
|
||||||
|
$stmt->bindParam(2, $dec, $paramType, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
|
||||||
|
$stmt->bindParam(3, $num, $paramType, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE);
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($pdoParamType == PDO::PARAM_BOOL) {
|
||||||
|
if (!$dec || !$num) {
|
||||||
|
echo "The returned booleans ($dec, $num) were unexpected!\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($dec != 100 || $num != 200) {
|
||||||
|
echo "The returned integers ($dec, $num) were unexpected!\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOutputsWithException($stmt, $outSql, $id, $pdoParamType, $inout = false)
|
||||||
|
{
|
||||||
|
global $error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
getOutputs($stmt, $outSql, $id, $pdoParamType, $inout);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$message = $e->getMessage();
|
||||||
|
$found = strpos($message, $error);
|
||||||
|
if ($found === false) {
|
||||||
|
echo "Exception message unexpected!\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSmallNumbers($conn, $outSql)
|
||||||
|
{
|
||||||
|
$stmt = $conn->prepare($outSql);
|
||||||
|
getOutputs($stmt, $outSql, 1, PDO::PARAM_BOOL);
|
||||||
|
getOutputs($stmt, $outSql, 1, PDO::PARAM_INT);
|
||||||
|
|
||||||
|
getOutputs($stmt, $outSql, 1, PDO::PARAM_BOOL, true);
|
||||||
|
getOutputs($stmt, $outSql, 1, PDO::PARAM_INT, true);
|
||||||
|
|
||||||
|
unset($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHugeNumbers($conn, $outSql)
|
||||||
|
{
|
||||||
|
// Expects an exception for each call
|
||||||
|
$stmt = $conn->prepare($outSql);
|
||||||
|
|
||||||
|
getOutputsWithException($stmt, $outSql, 2, PDO::PARAM_BOOL);
|
||||||
|
getOutputsWithException($stmt, $outSql, 2, PDO::PARAM_INT);
|
||||||
|
|
||||||
|
getOutputsWithException($stmt, $outSql, 2, PDO::PARAM_BOOL, true);
|
||||||
|
getOutputsWithException($stmt, $outSql, 2, PDO::PARAM_INT, true);
|
||||||
|
|
||||||
|
unset($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check eligibility
|
||||||
|
$conn = new PDO( "sqlsrv:server = $server", $uid, $pwd );
|
||||||
|
if (!isAEQualified($conn)) {
|
||||||
|
echo "Done\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unset($conn);
|
||||||
|
|
||||||
|
// Connection with column encryption enabled
|
||||||
|
$connectionInfo = "ColumnEncryption = Enabled;";
|
||||||
|
$conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd);
|
||||||
|
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
$tableName = "test_707_decimals";
|
||||||
|
$procName = "sp_test_707_decimals";
|
||||||
|
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
dropProc($conn, $procName);
|
||||||
|
|
||||||
|
// Create a test table
|
||||||
|
$tsql = "CREATE TABLE $tableName (id int identity(1,1), c1_decimal decimal(19,4), c2_numeric numeric(20, 6))";
|
||||||
|
$stmt = $conn->query($tsql);
|
||||||
|
unset($stmt);
|
||||||
|
|
||||||
|
// Insert two rows
|
||||||
|
$tsql = "INSERT INTO $tableName (c1_decimal, c2_numeric) VALUES (100.078, 200.034)";
|
||||||
|
$stmt = $conn->query($tsql);
|
||||||
|
unset($stmt);
|
||||||
|
|
||||||
|
$tsql = "INSERT INTO $tableName (c1_decimal, c2_numeric) VALUES (199999999999.0123, 999243876923.09887)";
|
||||||
|
$stmt = $conn->query($tsql);
|
||||||
|
unset($stmt);
|
||||||
|
|
||||||
|
// Create a stored procedure
|
||||||
|
$procArgs = "@id int, @c_decimal decimal(19,4) OUTPUT, @c_numeric numeric(20, 6) OUTPUT";
|
||||||
|
$procCode = "SELECT @c_decimal = c1_decimal, @c_numeric = c2_numeric FROM $tableName WHERE id = @id";
|
||||||
|
createProc($conn, $procName, $procArgs, $procCode);
|
||||||
|
|
||||||
|
// Read them back by calling the stored procedure
|
||||||
|
$outSql = getCallProcSqlPlaceholders($procName, 3);
|
||||||
|
getSmallNumbers($conn, $outSql);
|
||||||
|
getHugeNumbers($conn, $outSql);
|
||||||
|
|
||||||
|
dropProc($conn, $procName);
|
||||||
|
dropTable($conn, $tableName);
|
||||||
|
|
||||||
|
unset($conn);
|
||||||
|
echo "Done\n";
|
||||||
|
} catch( PDOException $e ) {
|
||||||
|
print_r( $e->getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Done
|
|
@ -50,23 +50,15 @@ function compareFloats($actual, $expected)
|
||||||
function compareIntegers($det, $rand, $inputValues, $pdoParamType)
|
function compareIntegers($det, $rand, $inputValues, $pdoParamType)
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////
|
||||||
// See GitHub issue 707 - Fix this method when the problem is addressed
|
|
||||||
//
|
|
||||||
// Assume $pdoParamType is PDO::PARAM_BOOL or PDO::PARAM_INT
|
// Assume $pdoParamType is PDO::PARAM_BOOL or PDO::PARAM_INT
|
||||||
if (is_string($det)) {
|
if (is_string($det)) {
|
||||||
return (!compareFloats(floatval($det), $inputValues[0])
|
return (!compareFloats(floatval($det), $inputValues[0])
|
||||||
&& !compareFloats(floatval($rand), $inputValues[1]));
|
&& !compareFloats(floatval($rand), $inputValues[1]));
|
||||||
} else {
|
} else {
|
||||||
// if $pdoParamType is PDO::PARAM_BOOL,
|
// if $pdoParamType is PDO::PARAM_BOOL, expect bool(true) or bool(false)
|
||||||
// expect bool(true) or bool(false) depending on the rounded input values
|
// depending on the rounded input values
|
||||||
// But with AE enabled (aforementioned GitHub issue), the fetched values
|
|
||||||
// are floats instead, which should be fixed
|
|
||||||
$input0 = floor($inputValues[0]); // the positive float
|
$input0 = floor($inputValues[0]); // the positive float
|
||||||
$input1 = ceil($inputValues[1]); // the negative float
|
$input1 = ceil($inputValues[1]); // the negative float
|
||||||
if (isAEConnected()) {
|
|
||||||
$det = boolval(floor($det));
|
|
||||||
$rand = boolval(ceil($rand));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ($det == boolval($input0) && $rand == boolval($input1));
|
return ($det == boolval($input0) && $rand == boolval($input1));
|
||||||
}
|
}
|
||||||
|
@ -190,10 +182,14 @@ function testOutputDecimals($inout)
|
||||||
if ($found === false) {
|
if ($found === false) {
|
||||||
printValues($errMsg, $det, $rand, $inputValues);
|
printValues($errMsg, $det, $rand, $inputValues);
|
||||||
}
|
}
|
||||||
} elseif (!isAEConnected() && $precision >= 16) {
|
} elseif ($precision >= 16) {
|
||||||
// When not AE enabled, large numbers are expected to
|
// Large numbers are expected to fail when
|
||||||
// fail when converting to booleans / integers
|
// converting to booleans / integers
|
||||||
$error = "Error converting data type $dataType to int";
|
if (isAEConnected()) {
|
||||||
|
$error = "Error converting a double (value out of range) to an integer";
|
||||||
|
} else {
|
||||||
|
$error = "Error converting data type $dataType to int";
|
||||||
|
}
|
||||||
$found = strpos($message, $error);
|
$found = strpos($message, $error);
|
||||||
if ($found === false) {
|
if ($found === false) {
|
||||||
printValues($errMsg, $det, $rand, $inputValues);
|
printValues($errMsg, $det, $rand, $inputValues);
|
||||||
|
|
Loading…
Reference in a new issue