Feature request 415 for pdo_sqlsrv (#873)
This commit is contained in:
parent
2a9398f7e0
commit
f4ad2ae1d4
|
@ -80,7 +80,8 @@ enum PDO_STMT_OPTIONS {
|
|||
PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE,
|
||||
PDO_STMT_OPTION_EMULATE_PREPARES,
|
||||
PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE,
|
||||
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE
|
||||
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE,
|
||||
PDO_STMT_OPTION_FORMAT_DECIMALS
|
||||
};
|
||||
|
||||
// List of all the statement options supported by this driver.
|
||||
|
@ -95,6 +96,7 @@ const stmt_option PDO_STMT_OPTS[] = {
|
|||
{ NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr<stmt_option_emulate_prepares>( new stmt_option_emulate_prepares ) },
|
||||
{ NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr<stmt_option_fetch_numeric>( new stmt_option_fetch_numeric ) },
|
||||
{ NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr<stmt_option_fetch_datetime>( new stmt_option_fetch_datetime ) },
|
||||
{ NULL, 0, PDO_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr<stmt_option_format_decimals>( new stmt_option_format_decimals ) },
|
||||
|
||||
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
|
||||
};
|
||||
|
@ -1095,6 +1097,7 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
|
|||
case PDO_ATTR_EMULATE_PREPARES:
|
||||
case PDO_ATTR_CURSOR:
|
||||
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
|
||||
case SQLSRV_ATTR_FORMAT_DECIMALS:
|
||||
{
|
||||
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
|
||||
}
|
||||
|
@ -1153,6 +1156,7 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
|
|||
case PDO_ATTR_EMULATE_PREPARES:
|
||||
case PDO_ATTR_CURSOR:
|
||||
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
|
||||
case SQLSRV_ATTR_FORMAT_DECIMALS:
|
||||
{
|
||||
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
|
||||
}
|
||||
|
@ -1586,6 +1590,10 @@ void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_
|
|||
option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE;
|
||||
break;
|
||||
|
||||
case SQLSRV_ATTR_FORMAT_DECIMALS:
|
||||
option_key = PDO_STMT_OPTION_FORMAT_DECIMALS;
|
||||
break;
|
||||
|
||||
default:
|
||||
CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) {
|
||||
throw core::CoreException();
|
||||
|
|
|
@ -286,6 +286,7 @@ namespace {
|
|||
{ "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE },
|
||||
{ "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE },
|
||||
{ "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE },
|
||||
{ "SQLSRV_ATTR_FORMAT_DECIMALS" , SQLSRV_ATTR_FORMAT_DECIMALS },
|
||||
|
||||
// used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int,
|
||||
// PDO::PARAM_STR uses the size of the string in the variable
|
||||
|
|
|
@ -882,6 +882,10 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In
|
|||
driver_stmt->fetch_datetime = ( zend_is_true( val )) ? true : false;
|
||||
break;
|
||||
|
||||
case SQLSRV_ATTR_FORMAT_DECIMALS:
|
||||
core_sqlsrv_set_format_decimals(driver_stmt, val TSRMLS_CC);
|
||||
break;
|
||||
|
||||
default:
|
||||
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
|
||||
break;
|
||||
|
|
|
@ -437,6 +437,14 @@ pdo_error PDO_ERRORS[] = {
|
|||
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
|
||||
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false}
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_INVALID_FORMAT_DECIMALS,
|
||||
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -92, false}
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE,
|
||||
{ IMSSP, (SQLCHAR*) "For formatting decimal data values, %1!d! is out of range. Expected an integer from 0 to 38, inclusive.", -93, true}
|
||||
},
|
||||
|
||||
{ UINT_MAX, {} }
|
||||
};
|
||||
|
|
|
@ -41,14 +41,15 @@ extern "C" {
|
|||
// sqlsrv driver specific PDO attributes
|
||||
enum PDO_SQLSRV_ATTR {
|
||||
|
||||
// Currently there are only three custom attributes for this driver.
|
||||
// The custom attributes for this driver:
|
||||
SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC,
|
||||
SQLSRV_ATTR_QUERY_TIMEOUT,
|
||||
SQLSRV_ATTR_DIRECT_QUERY,
|
||||
SQLSRV_ATTR_CURSOR_SCROLL_TYPE,
|
||||
SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE,
|
||||
SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,
|
||||
SQLSRV_ATTR_FETCHES_DATETIME_TYPE
|
||||
SQLSRV_ATTR_FETCHES_DATETIME_TYPE,
|
||||
SQLSRV_ATTR_FORMAT_DECIMALS
|
||||
};
|
||||
|
||||
// valid set of values for TransactionIsolation connection option
|
||||
|
|
|
@ -1527,7 +1527,7 @@ void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z
|
|||
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
|
||||
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC );
|
||||
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit TSRMLS_DC );
|
||||
|
||||
void core_sqlsrv_set_format_decimals(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC);
|
||||
|
||||
//*********************************************************************************************************************************
|
||||
// Result Set
|
||||
|
@ -1707,7 +1707,6 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set {
|
|||
|
||||
// utility functions shared by multiple callers across files
|
||||
bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _Inout_ SQLLEN& len);
|
||||
bool convert_zval_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _Inout_ zval* value_z, _Inout_ SQLLEN& len);
|
||||
bool validate_string( _In_ char* string, _In_ SQLLEN& len);
|
||||
bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(cchInLen) const SQLWCHAR* inString, _In_ SQLINTEGER cchInLen, _Inout_updates_bytes_(cchOutLen) char** outString, _Out_ SQLLEN& cchOutLen );
|
||||
SQLWCHAR* utf16_string_from_mbcs_string( _In_ SQLSRV_ENCODING php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len );
|
||||
|
|
|
@ -1258,6 +1258,26 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout
|
|||
}
|
||||
}
|
||||
|
||||
void core_sqlsrv_set_format_decimals(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC)
|
||||
{
|
||||
try {
|
||||
// first check if the input is an integer
|
||||
CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_FORMAT_DECIMALS) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
zend_long format_decimals = Z_LVAL_P(value_z);
|
||||
CHECK_CUSTOM_ERROR(format_decimals < 0 || format_decimals > SQL_SERVER_MAX_PRECISION, stmt, SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, format_decimals) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
stmt->num_decimals = static_cast<short>(format_decimals);
|
||||
}
|
||||
catch( core::CoreException& ) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC )
|
||||
{
|
||||
TSRMLS_C;
|
||||
|
@ -1427,17 +1447,7 @@ void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op
|
|||
|
||||
void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC )
|
||||
{
|
||||
// first check if the input is an integer
|
||||
CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_FORMAT_DECIMALS) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
zend_long format_decimals = Z_LVAL_P(value_z);
|
||||
CHECK_CUSTOM_ERROR(format_decimals < 0 || format_decimals > SQL_SERVER_MAX_PRECISION, stmt, SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, format_decimals) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
stmt->num_decimals = static_cast<short>(format_decimals);
|
||||
core_sqlsrv_set_format_decimals(stmt, value_z TSRMLS_CC);
|
||||
}
|
||||
|
||||
// internal function to release the active stream. Called by each main API function
|
||||
|
@ -2293,27 +2303,39 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
|
|||
str_len = output_param->original_buffer_len - null_size;
|
||||
}
|
||||
|
||||
// if it's not in the 8 bit encodings, then it's in UTF-16
|
||||
if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) {
|
||||
bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len);
|
||||
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
}
|
||||
else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) {
|
||||
if (output_param->encoding == SQLSRV_ENCODING_BINARY) {
|
||||
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
|
||||
// so we do that here if the length of the returned data is less than the original allocation. The
|
||||
// original allocation null terminates the buffer already.
|
||||
str[str_len] = '\0';
|
||||
if (str_len < output_param->original_buffer_len) {
|
||||
str[str_len] = '\0';
|
||||
}
|
||||
core::sqlsrv_zval_stringl(value_z, str, str_len);
|
||||
}
|
||||
else {
|
||||
SQLSMALLINT decimal_digits = output_param->getDecimalDigits();
|
||||
if (stmt->num_decimals >= 0 && decimal_digits >= 0) {
|
||||
format_decimal_numbers(stmt->num_decimals, decimal_digits, str, &str_len);
|
||||
}
|
||||
|
||||
core::sqlsrv_zval_stringl(value_z, str, str_len);
|
||||
if (output_param->encoding != SQLSRV_ENCODING_CHAR) {
|
||||
char* outString = NULL;
|
||||
SQLLEN outLen = 0;
|
||||
bool result = convert_string_from_utf16(output_param->encoding, reinterpret_cast<const SQLWCHAR*>(str), int(str_len / sizeof(SQLWCHAR)), &outString, outLen );
|
||||
CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
if (stmt->num_decimals >= 0 && decimal_digits >= 0) {
|
||||
format_decimal_numbers(stmt->num_decimals, decimal_digits, outString, &outLen);
|
||||
}
|
||||
core::sqlsrv_zval_stringl(value_z, outString, outLen);
|
||||
sqlsrv_free(outString);
|
||||
}
|
||||
else {
|
||||
if (stmt->num_decimals >= 0 && decimal_digits >= 0) {
|
||||
format_decimal_numbers(stmt->num_decimals, decimal_digits, str, &str_len);
|
||||
}
|
||||
|
||||
core::sqlsrv_zval_stringl(value_z, str, str_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -91,25 +91,6 @@ bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_up
|
|||
return result;
|
||||
}
|
||||
|
||||
bool convert_zval_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _Inout_ zval* value_z, _Inout_ SQLLEN& len)
|
||||
{
|
||||
char* string = Z_STRVAL_P(value_z);
|
||||
|
||||
if( validate_string(string, len)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char* outString = NULL;
|
||||
SQLLEN outLen = 0;
|
||||
bool result = convert_string_from_utf16( encoding, reinterpret_cast<const SQLWCHAR*>(string), int(len / sizeof(SQLWCHAR)), &outString, outLen );
|
||||
if( result ) {
|
||||
core::sqlsrv_zval_stringl( value_z, outString, outLen );
|
||||
sqlsrv_free( outString );
|
||||
len = outLen;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool validate_string( _In_ char* string, _In_ SQLLEN& len )
|
||||
{
|
||||
SQLSRV_ASSERT(string != NULL, "String must be specified");
|
||||
|
|
390
test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt
Normal file
390
test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt
Normal file
|
@ -0,0 +1,390 @@
|
|||
--TEST--
|
||||
Test statement attribute PDO::SQLSRV_ATTR_FORMAT_DECIMALS for decimal types
|
||||
--DESCRIPTION--
|
||||
Test statement attribute PDO::SQLSRV_ATTR_FORMAT_DECIMALS for decimal or
|
||||
money types (feature request issue 415), which are always fetched as strings
|
||||
to preserve accuracy and precision, unlike other primitive numeric types,
|
||||
where there is an option to retrieve them as numbers.
|
||||
|
||||
This attribute expects an integer value from the range [0,38], the money or
|
||||
decimal types in the fetched result set can be formatted.
|
||||
|
||||
No effect on other operations like insertion or update.
|
||||
|
||||
1. By default, data will be returned with the original precision and scale
|
||||
2. The data column original scale still takes precedence – for example, if the user
|
||||
specifies 3 decimal digits for a column of decimal(5,2), the result still shows only 2
|
||||
decimals to the right of the dot
|
||||
3. After formatting, the missing leading zeroes will be padded
|
||||
4. The underlying data will not be altered, but formatted results may likely be rounded
|
||||
up (e.g. .2954 will be displayed as 0.30 if the user wants only two decimals)
|
||||
5. Do not support output params
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif_mid-refactor.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once("MsCommon_mid-refactor.inc");
|
||||
|
||||
function checkException($exception, $expected)
|
||||
{
|
||||
if (strpos($exception->getMessage(), $expected) === false) {
|
||||
print_r($exception->getMessage());
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
function testPdoAttribute($conn, $setAttr)
|
||||
{
|
||||
// Expects exception because PDO::SQLSRV_ATTR_FORMAT_DECIMALS
|
||||
// is a statement level attribute
|
||||
try {
|
||||
$res = true;
|
||||
if ($setAttr) {
|
||||
$res = $conn->setAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS, 1);
|
||||
} else {
|
||||
$res = $conn->getAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS);
|
||||
}
|
||||
if ($res) {
|
||||
echo "setAttribute at PDO level should have failed!\n";
|
||||
}
|
||||
} catch (PdoException $e) {
|
||||
if ($setAttr) {
|
||||
$expected = 'The given attribute is only supported on the PDOStatement object.';
|
||||
} else {
|
||||
$expected = 'driver does not support that attribute';
|
||||
}
|
||||
|
||||
checkException($e, $expected);
|
||||
}
|
||||
}
|
||||
|
||||
function testErrorCases($conn)
|
||||
{
|
||||
$query = "SELECT 0.0001";
|
||||
|
||||
try {
|
||||
$options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => 0.9);
|
||||
$stmt = $conn->prepare($query, $options);
|
||||
} catch (PdoException $e) {
|
||||
$expected = 'Expected an integer to specify number of decimals to format the output values of decimal data types';
|
||||
checkException($e, $expected);
|
||||
}
|
||||
|
||||
try {
|
||||
$options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => 100);
|
||||
$stmt = $conn->prepare($query, $options);
|
||||
} catch (PdoException $e) {
|
||||
$expected = 'For formatting decimal data values, 100 is out of range. Expected an integer from 0 to 38, inclusive.';
|
||||
checkException($e, $expected);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyMoneyValues($conn, $query, $values, $numDigits)
|
||||
{
|
||||
$options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => $numDigits);
|
||||
$stmt = $conn->prepare($query, $options);
|
||||
$stmt->execute();
|
||||
$results = $stmt->fetch(PDO::FETCH_NUM);
|
||||
|
||||
trace("\nverifyMoneyValues:\n");
|
||||
for ($i = 0; $i < count($values); $i++) {
|
||||
$value = number_format($values[$i], $numDigits);
|
||||
trace("$results[$i], $value\n");
|
||||
|
||||
if ($value !== $results[$i]) {
|
||||
echo "testMoneyTypes: Expected $value but got $results[$i]\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testFloatTypes($conn)
|
||||
{
|
||||
// This test with the float types of various number of bits, which are retrieved
|
||||
// as numbers by default. When fetched as strings, no formatting is done even with
|
||||
// the statement option FormatDecimals set
|
||||
$epsilon = 0.001;
|
||||
$values = array();
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$n1 = rand(1, 100);
|
||||
$n2 = rand(1, 100);
|
||||
$neg = ($i % 2 == 0) ? -1 : 1;
|
||||
|
||||
$n = $neg * $n1 / $n2;
|
||||
array_push($values, $n);
|
||||
}
|
||||
|
||||
$query = "SELECT CONVERT(float(1), $values[0]),
|
||||
CONVERT(float(12), $values[1]),
|
||||
CONVERT(float(24), $values[2]),
|
||||
CONVERT(float(36), $values[3]),
|
||||
CONVERT(float(53), $values[4])";
|
||||
$stmt = $conn->query($query);
|
||||
$floats = $stmt->fetch(PDO::FETCH_NUM);
|
||||
unset($stmt);
|
||||
|
||||
// Set PDO::SQLSRV_ATTR_FORMAT_DECIMALS to 2 should
|
||||
// have no effect on floating point numbers
|
||||
$numDigits = 2;
|
||||
$options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => $numDigits);
|
||||
$stmt = $conn->prepare($query, $options);
|
||||
|
||||
// By default the floating point numbers are fetched as strings
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$stmt->execute();
|
||||
$floatStr = $stmt->fetchColumn($i);
|
||||
|
||||
$floatVal = floatVal($floats[$i]);
|
||||
$floatVal1 = floatval($floatStr);
|
||||
|
||||
trace("testFloatTypes: $floatVal1, $floatVal\n");
|
||||
|
||||
// Check if the numbers of decimal digits are the same
|
||||
// It is highly unlikely but not impossible
|
||||
$numbers = explode('.', $floatStr);
|
||||
$len = strlen($numbers[1]);
|
||||
if ($len == $numDigits && $floatVal1 != $floatVal) {
|
||||
echo "Expected $floatVal but $floatVal1 returned. \n";
|
||||
} else {
|
||||
$diff = abs($floatVal1 - $floatVal) / $floatVal;
|
||||
if ($diff > $epsilon) {
|
||||
echo "Expected $floatVal but $floatVal1 returned. \n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testMoneyTypes($conn)
|
||||
{
|
||||
// With money and smallmoney types, which are essentially decimal types
|
||||
// ODBC driver does not support Always Encrypted feature with money / smallmoney
|
||||
$values = array('24.559', '0', '-0.946', '0.2985', '-99.675', '79.995');
|
||||
$defaults = array('24.5590', '.0000', '-.9460', '.2985', '-99.6750', '79.9950');
|
||||
|
||||
$query = "SELECT CONVERT(smallmoney, $values[0]),
|
||||
CONVERT(money, $values[1]),
|
||||
CONVERT(smallmoney, $values[2]),
|
||||
CONVERT(money, $values[3]),
|
||||
CONVERT(smallmoney, $values[4]),
|
||||
CONVERT(money, $values[5])";
|
||||
|
||||
$stmt = $conn->query($query);
|
||||
$results = $stmt->fetch(PDO::FETCH_NUM);
|
||||
for ($i = 0; $i < count($values); $i++) {
|
||||
if ($defaults[$i] !== $results[$i]) {
|
||||
echo "testMoneyTypes: Expected $defaults[$i] but got $results[$i]\n";
|
||||
}
|
||||
}
|
||||
unset($stmt);
|
||||
|
||||
// Set PDO::SQLSRV_ATTR_FORMAT_DECIMALS to 0 then 2
|
||||
verifyMoneyValues($conn, $query, $values, 0);
|
||||
verifyMoneyValues($conn, $query, $values, 2);
|
||||
}
|
||||
|
||||
function compareNumbers($actual, $input, $column, $fieldScale, $formatDecimal = -1)
|
||||
{
|
||||
$matched = false;
|
||||
if ($actual === $input) {
|
||||
$matched = true;
|
||||
trace("$actual, $input\n");
|
||||
} else {
|
||||
// When $formatDecimal is negative, that means no formatting done
|
||||
// Otherwise, if $formatDecimal > $fieldScale, will show $fieldScale decimal digits
|
||||
if ($formatDecimal >= 0) {
|
||||
$numDecimals = ($formatDecimal > $fieldScale) ? $fieldScale : $formatDecimal;
|
||||
} else {
|
||||
$numDecimals = $fieldScale;
|
||||
}
|
||||
$expected = number_format($input, $numDecimals);
|
||||
trace("$actual, $expected\n");
|
||||
if ($actual === $expected) {
|
||||
$matched = true;
|
||||
} else {
|
||||
echo "For $column: expected $expected but the value is $actual\n";
|
||||
}
|
||||
}
|
||||
return $matched;
|
||||
}
|
||||
|
||||
function testNoOption($conn, $tableName, $inputs, $columns)
|
||||
{
|
||||
// Without the statement option, should return decimal values as they are
|
||||
$query = "SELECT * FROM $tableName";
|
||||
$stmt = $conn->query($query);
|
||||
|
||||
// Compare values
|
||||
$results = $stmt->fetch(PDO::FETCH_NUM);
|
||||
trace("\ntestNoOption:\n");
|
||||
for ($i = 0; $i < count($inputs); $i++) {
|
||||
compareNumbers($results[$i], $inputs[$i], $columns[$i], $i);
|
||||
}
|
||||
}
|
||||
|
||||
function testStmtOption($conn, $tableName, $inputs, $columns, $formatDecimal, $withBuffer)
|
||||
{
|
||||
// Decimal values should return decimal digits based on the valid statement
|
||||
// option PDO::SQLSRV_ATTR_FORMAT_DECIMALS
|
||||
$query = "SELECT * FROM $tableName";
|
||||
if ($withBuffer){
|
||||
$options = array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL,
|
||||
PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,
|
||||
PDO::SQLSRV_ATTR_FORMAT_DECIMALS => $formatDecimal);
|
||||
} else {
|
||||
$options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => $formatDecimal);
|
||||
}
|
||||
|
||||
$size = count($inputs);
|
||||
$stmt = $conn->prepare($query, $options);
|
||||
|
||||
// Fetch by getting one field at a time
|
||||
trace("\ntestStmtOption: $formatDecimal and buffered $withBuffer\n");
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$stmt->execute();
|
||||
|
||||
$stmt->bindColumn($columns[$i], $field);
|
||||
$result = $stmt->fetch(PDO::FETCH_BOUND);
|
||||
|
||||
compareNumbers($field, $inputs[$i], $columns[$i], $i, $formatDecimal);
|
||||
}
|
||||
}
|
||||
|
||||
function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $inout)
|
||||
{
|
||||
$outString = '';
|
||||
$numDigits = 2;
|
||||
|
||||
$outSql = getCallProcSqlPlaceholders($storedProcName, 1);
|
||||
|
||||
$options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => $numDigits);
|
||||
$stmt = $conn->prepare($outSql, $options);
|
||||
|
||||
$len = 1024;
|
||||
if ($inout) {
|
||||
$paramType = PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT;
|
||||
|
||||
// For inout parameters the input type should match the output one
|
||||
$outString = '0.0';
|
||||
} else {
|
||||
$paramType = PDO::PARAM_STR;
|
||||
}
|
||||
|
||||
$stmt->bindParam(1, $outString, $paramType, $len);
|
||||
$stmt->execute();
|
||||
|
||||
// The output param value should be the same as the input value,
|
||||
// unaffected by the statement attr PDO::SQLSRV_ATTR_FORMAT_DECIMALS,
|
||||
// unless ColumnEncryption is enabled, in which case the driver is able
|
||||
// to derive the decimal type
|
||||
if (isAEConnected()) {
|
||||
trace("\ngetOutputParam ($inout) with AE:\n");
|
||||
$column = 'outputParamAE';
|
||||
compareNumbers($outString, $inputValue, $column, $scale, $numDigits);
|
||||
} else {
|
||||
trace("\ngetOutputParam ($inout) without AE:\n");
|
||||
$column = 'outputParam';
|
||||
compareNumbers($outString, $inputValue, $column, $scale);
|
||||
}
|
||||
}
|
||||
|
||||
function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes, $inout = false)
|
||||
{
|
||||
for ($i = 0, $p = 3; $i < count($columns); $i++, $p++) {
|
||||
// Create the stored procedure first
|
||||
$storedProcName = "spFormatDecimals" . $i;
|
||||
$procArgs = "@col $dataTypes[$i] OUTPUT";
|
||||
$procCode = "SELECT @col = $columns[$i] FROM $tableName";
|
||||
createProc($conn, $storedProcName, $procArgs, $procCode);
|
||||
|
||||
// Call stored procedure to retrieve output param
|
||||
getOutputParam($conn, $storedProcName, $inputs[$i], $p, $i, $inout);
|
||||
|
||||
dropProc($conn, $storedProcName);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION
|
||||
$conn = connect();
|
||||
|
||||
// Test some error conditions
|
||||
testPdoAttribute($conn, true);
|
||||
testPdoAttribute($conn, false);
|
||||
testErrorCases($conn);
|
||||
|
||||
// First test with money types
|
||||
testMoneyTypes($conn);
|
||||
|
||||
// Also test using regular floats
|
||||
testFloatTypes($conn);
|
||||
|
||||
// Create the test table of decimal / numeric data columns
|
||||
$tableName = 'pdoFormatDecimals';
|
||||
|
||||
$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6');
|
||||
$dataTypes = array('decimal(3,0)', 'decimal(4,1)', 'decimal(5,2)', 'numeric(6,3)', 'numeric(7,4)', 'numeric(8, 5)');
|
||||
|
||||
$colMeta = array(new ColumnMeta($dataTypes[0], $columns[0]),
|
||||
new ColumnMeta($dataTypes[1], $columns[1]),
|
||||
new ColumnMeta($dataTypes[2], $columns[2]),
|
||||
new ColumnMeta($dataTypes[3], $columns[3]),
|
||||
new ColumnMeta($dataTypes[4], $columns[4]),
|
||||
new ColumnMeta($dataTypes[5], $columns[5]));
|
||||
createTable($conn, $tableName, $colMeta);
|
||||
|
||||
// Generate random input values based on precision and scale
|
||||
trace("\nGenerating random input values: \n");
|
||||
$values = array();
|
||||
$max = 1;
|
||||
for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) {
|
||||
// First get a random number
|
||||
$n = rand(1, 6);
|
||||
$neg = ($n % 2 == 0) ? -1 : 1;
|
||||
|
||||
// $n1 may or may not be negative
|
||||
$n1 = rand(0, 1000) * $neg;
|
||||
|
||||
if ($s > 0) {
|
||||
$max *= 10;
|
||||
$n2 = rand(0, $max);
|
||||
$number = sprintf("%d.%d", $n1, $n2);
|
||||
} else {
|
||||
$number = sprintf("%d", $n1);
|
||||
}
|
||||
|
||||
trace("$s: $number\n");
|
||||
array_push($values, $number);
|
||||
}
|
||||
|
||||
$query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?)";
|
||||
$stmt = $conn->prepare($query);
|
||||
for ($i = 0; $i < count($columns); $i++) {
|
||||
$stmt->bindParam($i+1, $values[$i]);
|
||||
}
|
||||
$stmt->execute();
|
||||
|
||||
testNoOption($conn, $tableName, $values, $columns, true);
|
||||
|
||||
// Now try with setting number decimals to 3 then 2
|
||||
testStmtOption($conn, $tableName, $values, $columns, 3, false);
|
||||
testStmtOption($conn, $tableName, $values, $columns, 3, true);
|
||||
|
||||
testStmtOption($conn, $tableName, $values, $columns, 2, false);
|
||||
testStmtOption($conn, $tableName, $values, $columns, 2, true);
|
||||
|
||||
// Test output parameters
|
||||
testOutputParam($conn, $tableName, $values, $columns, $dataTypes);
|
||||
testOutputParam($conn, $tableName, $values, $columns, $dataTypes, true);
|
||||
|
||||
dropTable($conn, $tableName);
|
||||
echo "Done\n";
|
||||
|
||||
unset($stmt);
|
||||
unset($conn);
|
||||
} catch (PdoException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Done
|
|
@ -0,0 +1,248 @@
|
|||
--TEST--
|
||||
Test various precisions of formatting decimal data output values (feature request issue 415)
|
||||
--DESCRIPTION--
|
||||
In SQL Server, the maximum allowed precision is 38. The scale can range from 0 up to the
|
||||
defined precision. Generate a long numeric string and get rid of the last digit to make it a
|
||||
39-digit-string. Then replace one digit at a time with a dot '.' to make it a decimal
|
||||
input string for testing with various scales.
|
||||
For example,
|
||||
string(39) ".23456789012345678901234567890123456789"
|
||||
string(39) "1.3456789012345678901234567890123456789"
|
||||
string(39) "12.456789012345678901234567890123456789"
|
||||
string(39) "123.56789012345678901234567890123456789"
|
||||
string(39) "1234.6789012345678901234567890123456789"
|
||||
string(39) "12345.789012345678901234567890123456789"
|
||||
... ...
|
||||
string(39) "1234567890123456789012345678901234.6789"
|
||||
string(39) "12345678901234567890123456789012345.789"
|
||||
string(39) "123456789012345678901234567890123456.89"
|
||||
string(39) "1234567890123456789012345678901234567.9"
|
||||
string(38) "12345678901234567890123456789012345678"
|
||||
|
||||
Note: PHP number_format() will not be used for verification in this test
|
||||
because the function starts losing accuracy with large number of precisions / scales.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif_mid-refactor.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once("MsCommon_mid-refactor.inc");
|
||||
|
||||
$prec = 38;
|
||||
$dot = '.';
|
||||
|
||||
function createTestTable($conn)
|
||||
{
|
||||
global $prec;
|
||||
|
||||
// Create the test table of one decimal column
|
||||
$tableName = "pdoFormatDecimalScales";
|
||||
$colMeta = array();
|
||||
|
||||
$max = $prec + 1;
|
||||
for ($i = 0; $i < $max; $i++) {
|
||||
$scale = $prec - $i;
|
||||
|
||||
$column = "col_$scale";
|
||||
$dataType = "decimal($prec, $scale)";
|
||||
|
||||
array_push($colMeta, new ColumnMeta($dataType, $column));
|
||||
}
|
||||
createTable($conn, $tableName, $colMeta);
|
||||
|
||||
return $tableName;
|
||||
}
|
||||
|
||||
function insertTestData($conn, $tableName)
|
||||
{
|
||||
global $prec, $dot;
|
||||
|
||||
$temp = str_repeat('1234567890', 4);
|
||||
$digits = substr($temp, 0, $prec + 1);
|
||||
|
||||
$inputData = array();
|
||||
$max = $prec + 1;
|
||||
|
||||
// Generate input strings - replace the $i-th digit with a dot '.'
|
||||
for ($i = 0; $i < $max; $i++) {
|
||||
$d = $digits[$i];
|
||||
$digits[$i] = $dot;
|
||||
|
||||
if ($i == $prec) {
|
||||
$digits = substr($temp, 0, $prec);
|
||||
}
|
||||
|
||||
$scale = $prec - $i;
|
||||
|
||||
$column = "col_$scale";
|
||||
$inputData = array_merge($inputData, array($column => $digits));
|
||||
|
||||
// Restore the $i-th digit with its original digit
|
||||
$digits[$i] = $d;
|
||||
}
|
||||
|
||||
$stmt = insertRow($conn, $tableName, $inputData);
|
||||
unset($stmt);
|
||||
|
||||
return $inputData;
|
||||
}
|
||||
|
||||
function verifyNoDecimals($value, $input, $round)
|
||||
{
|
||||
global $prec, $dot;
|
||||
|
||||
// Use PHP explode() to separate the input string into an array
|
||||
$parts = explode($dot, $input);
|
||||
$len = strlen($parts[0]);
|
||||
if ($len == 0) {
|
||||
// The original input string is missing a leading zero
|
||||
$parts[0] = '0';
|
||||
}
|
||||
|
||||
// No need to worry about carry over for the input data of this test
|
||||
// Check the first digit of $parts[1]
|
||||
if ($len < $prec) {
|
||||
// Only need to round up when $len < $prec
|
||||
$ch = $parts[1][0];
|
||||
|
||||
// Round the last digit of $parts[0] if $ch is '5' or above
|
||||
if ($ch >= '5') {
|
||||
$len = strlen($parts[0]);
|
||||
$parts[0][$len-1] = $parts[0][$len-1] + 1 + '0';
|
||||
}
|
||||
}
|
||||
|
||||
// No decimal digits left in the expected string
|
||||
$expected = $parts[0];
|
||||
if ($value !== $expected) {
|
||||
echo "Round $round scale 0: expected $expected but returned $value\n";
|
||||
}
|
||||
}
|
||||
|
||||
function verifyWithDecimals($value, $input, $round, $scale)
|
||||
{
|
||||
global $dot;
|
||||
|
||||
// Use PHP explode() to separate the input string into an array
|
||||
$parts = explode($dot, $input);
|
||||
if (strlen($parts[0]) == 0) {
|
||||
// The original input string is missing a leading zero
|
||||
$parts[0] = '0';
|
||||
}
|
||||
|
||||
// No need to worry about carry over for the input data of this test
|
||||
// Check the digit at the position $scale of $parts[1]
|
||||
$len = strlen($parts[1]);
|
||||
if ($scale < $len) {
|
||||
// Only need to round up when $scale < $len
|
||||
$ch = $parts[1][$scale];
|
||||
|
||||
// Round the previous digit if $ch is '5' or above
|
||||
if ($ch >= '5') {
|
||||
$parts[1][$scale-1] = $parts[1][$scale-1] + 1 + '0';
|
||||
}
|
||||
}
|
||||
|
||||
// Use substr() to get up to $scale
|
||||
$parts[1] = substr($parts[1], 0, $scale);
|
||||
|
||||
// Join the array elements together
|
||||
$expected = implode($dot, $parts);
|
||||
if ($value !== $expected) {
|
||||
echo "Round $round scale $scale: expected $expected but returned $value\n";
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
The function testVariousScales() will fetch one column at a time, using scale from
|
||||
0 up to the maximum scale allowed for that column type.
|
||||
|
||||
For example, for column of type decimal(38,4), the input string is
|
||||
1234567890123456789012345678901234.6789
|
||||
|
||||
When fetching data, using scale from 0 to 4, the following values are expected to return:
|
||||
1234567890123456789012345678901235
|
||||
1234567890123456789012345678901234.7
|
||||
1234567890123456789012345678901234.68
|
||||
1234567890123456789012345678901234.679
|
||||
1234567890123456789012345678901234.6789
|
||||
|
||||
For example, for column of type decimal(38,6), the input string is
|
||||
12345678901234567890123456789012.456789
|
||||
|
||||
When fetching data, using scale from 0 to 6, the following values are expected to return:
|
||||
12345678901234567890123456789012
|
||||
12345678901234567890123456789012.5
|
||||
12345678901234567890123456789012.46
|
||||
12345678901234567890123456789012.457
|
||||
12345678901234567890123456789012.4568
|
||||
12345678901234567890123456789012.45679
|
||||
12345678901234567890123456789012.456789
|
||||
|
||||
etc.
|
||||
****/
|
||||
function testVariousScales($conn, $tableName, $inputData)
|
||||
{
|
||||
global $prec;
|
||||
$max = $prec + 1;
|
||||
|
||||
for ($i = 0; $i < $max; $i++) {
|
||||
$scale = $prec - $i;
|
||||
$column = "col_$scale";
|
||||
|
||||
$query = "SELECT $column as col1 FROM $tableName";
|
||||
$input = $inputData[$column];
|
||||
|
||||
// Default case: the fetched value should be the same as the corresponding input
|
||||
$stmt = $conn->query($query);
|
||||
if ($obj = $stmt->fetchObject()) {
|
||||
trace("\n$obj->col1\n");
|
||||
if ($obj->col1 !== $input) {
|
||||
echo "default case: expected $input but returned $obj->col1\n";
|
||||
}
|
||||
} else {
|
||||
echo "In testVariousScales: fetchObject failed\n";
|
||||
}
|
||||
|
||||
// Next, format how many decimal digits to be displayed
|
||||
$query = "SELECT $column FROM $tableName";
|
||||
for ($j = 0; $j <= $scale; $j++) {
|
||||
$options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => $j);
|
||||
$stmt = $conn->prepare($query, $options);
|
||||
$stmt->execute();
|
||||
|
||||
$stmt->bindColumn($column, $value);
|
||||
if ($stmt->fetch(PDO::FETCH_BOUND)) {
|
||||
trace("$value\n");
|
||||
if ($j == 0) {
|
||||
verifyNoDecimals($value, $input, $i);
|
||||
} else {
|
||||
verifyWithDecimals($value, $input, $i, $j);
|
||||
}
|
||||
} else {
|
||||
echo "Round $i scale $j: fetch failed\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION
|
||||
$conn = connect();
|
||||
|
||||
$tableName = createTestTable($conn);
|
||||
$inputData = insertTestData($conn, $tableName);
|
||||
testVariousScales($conn, $tableName, $inputData);
|
||||
|
||||
dropTable($conn, $tableName);
|
||||
|
||||
echo "Done\n";
|
||||
|
||||
unset($conn);
|
||||
} catch (PdoException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Done
|
|
@ -107,17 +107,21 @@ function testFloatTypes($conn)
|
|||
if (sqlsrv_fetch($stmt)) {
|
||||
for ($i = 0; $i < count($values); $i++) {
|
||||
$floatStr = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
|
||||
$floatVal = floatval($floatStr);
|
||||
|
||||
// Check if the numbers of decimal digits are the same
|
||||
// It is highly unlikely but not impossible
|
||||
$numbers = explode('.', $floatStr);
|
||||
$len = strlen($numbers[1]);
|
||||
if ($len == $numDigits) {
|
||||
// This is highly unlikely
|
||||
var_dump($floatStr);
|
||||
}
|
||||
$floatVal = floatval($floatStr);
|
||||
$diff = abs($floatVal - $floats[$i]) / $floats[$i];
|
||||
if ($diff > $epsilon) {
|
||||
var_dump($diff);
|
||||
if ($len == $numDigits && $floatVal != $floats[$i]) {
|
||||
echo "Expected $floats[$i] but returned ";
|
||||
var_dump($floatVal);
|
||||
} else {
|
||||
$diff = abs($floatVal - $floats[$i]) / $floats[$i];
|
||||
if ($diff > $epsilon) {
|
||||
echo "Expected $floats[$i] but returned ";
|
||||
var_dump($floatVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -216,17 +220,31 @@ function testStmtOption($conn, $tableName, $inputs, $columns, $formatDecimal, $w
|
|||
}
|
||||
}
|
||||
|
||||
function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale)
|
||||
function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $inout)
|
||||
{
|
||||
$outString = '';
|
||||
$numDigits = 2;
|
||||
$dir = SQLSRV_PARAM_OUT;
|
||||
|
||||
// Derive the sqlsrv type SQLSRV_SQLTYPE_DECIMAL($prec, $scale)
|
||||
$sqlType = call_user_func('SQLSRV_SQLTYPE_DECIMAL', $prec, $scale);
|
||||
// The output param value should be the same as the input value,
|
||||
// unaffected by the statement attr FormatDecimals, unless
|
||||
// ColumnEncryption is enabled, in which case the driver is able
|
||||
// to derive the decimal type. Another workaround is to specify
|
||||
// the SQLSRV_SQLTYPE_DECIMAL type with the correct precision and scale
|
||||
$sqlType = null;
|
||||
if (!AE\isColEncrypted()) {
|
||||
$sqlType = call_user_func('SQLSRV_SQLTYPE_DECIMAL', $prec, $scale);
|
||||
}
|
||||
|
||||
// For inout parameters the input type should match the output one
|
||||
if ($inout) {
|
||||
$dir = SQLSRV_PARAM_INOUT;
|
||||
$outString = '0.0';
|
||||
}
|
||||
|
||||
$outSql = AE\getCallProcSqlPlaceholders($storedProcName, 1);
|
||||
$stmt = sqlsrv_prepare($conn, $outSql,
|
||||
array(array(&$outString, SQLSRV_PARAM_OUT, null, $sqlType)),
|
||||
array(array(&$outString, $dir, null, $sqlType)),
|
||||
array('FormatDecimals' => $numDigits));
|
||||
if (!$stmt) {
|
||||
fatalError("getOutputParam: failed when preparing to call $storedProcName");
|
||||
|
@ -242,14 +260,11 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale)
|
|||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
if (!AE\isColEncrypted()) {
|
||||
// Get output param without specifying sqlsrv type, and the returned value will
|
||||
// be a regular string -- its value should be the same as the input value,
|
||||
// unaffected by the statement option FormatDecimals
|
||||
// With ColumnEncryption enabled, the driver is able to derive the decimal type,
|
||||
// so skip this part of the test
|
||||
$outString2 = '';
|
||||
$outString2 = $inout ? '0.0' : '';
|
||||
$stmt = sqlsrv_prepare($conn, $outSql,
|
||||
array(array(&$outString2, SQLSRV_PARAM_OUT)),
|
||||
array(array(&$outString2, $dir)),
|
||||
array('FormatDecimals' => $numDigits));
|
||||
if (!$stmt) {
|
||||
fatalError("getOutputParam2: failed when preparing to call $storedProcName");
|
||||
|
@ -264,7 +279,7 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale)
|
|||
}
|
||||
}
|
||||
|
||||
function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes)
|
||||
function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes, $inout = false)
|
||||
{
|
||||
for ($i = 0, $p = 3; $i < count($columns); $i++, $p++) {
|
||||
// Create the stored procedure first
|
||||
|
@ -274,7 +289,7 @@ function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes)
|
|||
createProc($conn, $storedProcName, $procArgs, $procCode);
|
||||
|
||||
// Call stored procedure to retrieve output param
|
||||
getOutputParam($conn, $storedProcName, $inputs[$i], $p, $i);
|
||||
getOutputParam($conn, $storedProcName, $inputs[$i], $p, $i, $inout);
|
||||
|
||||
dropProc($conn, $storedProcName);
|
||||
}
|
||||
|
@ -360,6 +375,7 @@ testStmtOption($conn, $tableName, $values, $columns, 2, true);
|
|||
|
||||
// Test output parameters
|
||||
testOutputParam($conn, $tableName, $values, $columns, $dataTypes);
|
||||
testOutputParam($conn, $tableName, $values, $columns, $dataTypes, true);
|
||||
|
||||
dropTable($conn, $tableName);
|
||||
sqlsrv_close($conn);
|
||||
|
|
Loading…
Reference in a new issue