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:
Jenny Tam 2018-05-08 13:13:14 -07:00 committed by GitHub
commit 5a842c9b32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 182 additions and 28 deletions

View file

@ -406,8 +406,8 @@ pdo_error PDO_ERRORS[] = {
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -83, false }
},
{
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
{ IMSSP, (SQLCHAR*) "Connection with Column Encryption enabled does not support fetching stream. Please fetch the data as a string.", -84, false }
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*) "Error converting a double (value out of range) to an integer.", -84, false }
},
{ UINT_MAX, {} }
};

View file

@ -1311,23 +1311,25 @@ struct sqlsrv_output_param {
zval* param_z;
SQLSRV_ENCODING encoding;
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
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
SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary
bool is_bool;
// 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 )
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
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 ),
encoding( SQLSRV_ENCODING_INVALID ),
param_num( num ),
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_INVALID_BUFFER_LIMIT,
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
// Driver specific error codes starts from here.
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,

View file

@ -491,7 +491,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
ind_ptr = buffer_len;
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
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 );
}
}
@ -503,7 +503,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
ind_ptr = buffer_len;
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
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 );
}
}
@ -2149,8 +2149,23 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
break;
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
if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) {
ZVAL_NULL( value_z );
if (stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA) {
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;
default:

View file

@ -397,8 +397,8 @@ ss_error SS_ERRORS[] = {
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -108, false }
},
{
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
{ IMSSP, (SQLCHAR*) "Connection with Column Encryption enabled does not support fetching stream. Please fetch the data as a string.", -109, false }
SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED,
{ IMSSP, (SQLCHAR*)"Error converting a double (value out of range) to an integer.", -109, false }
},
// terminate the list of errors/warnings

View 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

View file

@ -50,23 +50,15 @@ function compareFloats($actual, $expected)
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
if (is_string($det)) {
return (!compareFloats(floatval($det), $inputValues[0])
&& !compareFloats(floatval($rand), $inputValues[1]));
} else {
// if $pdoParamType is PDO::PARAM_BOOL,
// expect bool(true) or bool(false) depending on the rounded input values
// But with AE enabled (aforementioned GitHub issue), the fetched values
// are floats instead, which should be fixed
// if $pdoParamType is PDO::PARAM_BOOL, expect bool(true) or bool(false)
// depending on the rounded input values
$input0 = floor($inputValues[0]); // the positive 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));
}
@ -190,10 +182,14 @@ function testOutputDecimals($inout)
if ($found === false) {
printValues($errMsg, $det, $rand, $inputValues);
}
} elseif (!isAEConnected() && $precision >= 16) {
// When not AE enabled, large numbers are expected to
// fail when converting to booleans / integers
$error = "Error converting data type $dataType to int";
} elseif ($precision >= 16) {
// Large numbers are expected to fail when
// converting to booleans / integers
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);
if ($found === false) {
printValues($errMsg, $det, $rand, $inputValues);