From 8bb6cef33c22e3923117b4d73cb0a39d396f0fc1 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 2 Mar 2020 09:51:02 -0800 Subject: [PATCH] Revised a few existing tests that are flawed (#1103) --- azure-pipelines.yml | 2 +- test/functional/sqlsrv/AEData.inc | 31 +- .../sqlsrv_ae_insert_sqltype_numeric.phpt | 2 +- ...lsrv_ae_output_param_sqltype_datetime.phpt | 243 ++++++++------ ...qlsrv_ae_output_param_sqltype_numeric.phpt | 306 ++++++++++-------- ...sqlsrv_ae_output_param_sqltype_string.phpt | 256 ++++++++------- 6 files changed, 488 insertions(+), 352 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 68c85525..a5d636ad 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,7 +16,7 @@ trigger: jobs: - job: macOS pool: - vmImage: 'macOS-10.13' + vmImage: 'macOS-10.14' steps: - checkout: self clean: true diff --git a/test/functional/sqlsrv/AEData.inc b/test/functional/sqlsrv/AEData.inc index 3879efce..46dfc6c5 100644 --- a/test/functional/sqlsrv/AEData.inc +++ b/test/functional/sqlsrv/AEData.inc @@ -84,11 +84,9 @@ $sqlTypes = array( function is_incompatible_types_error( $dataType, $sqlType ) { $errors = sqlsrv_errors(); - foreach ( $errors as $error ) - { + foreach ($errors as $error) { // 22018 is the SQLSTATE for the operand crash error for incompatible types - if ( $error['SQLSTATE'] == 22018 ) - { + if ($error['SQLSTATE'] == '22018') { echo "Encrypted $sqlType is incompatible with encrypted $dataType\n"; } } @@ -109,7 +107,6 @@ function get_sqlType_constant( $sqlType ) { switch ( $sqlType ) { case 'SQLSRV_SQLTYPE_BIGINT': - case 'SQLSRV_SQLTYPE_BINARY': case 'SQLSRV_SQLTYPE_BIT': case 'SQLSRV_SQLTYPE_DATE': case 'SQLSRV_SQLTYPE_DATETIME': @@ -135,6 +132,10 @@ function get_sqlType_constant( $sqlType ) case 'SQLSRV_SQLTYPE_XML': return constant( $sqlType ); break; + case 'SQLSRV_SQLTYPE_BINARY': + // our tests always use precision 5 for SQLSRV_SQLTYPE_BINARY + return SQLSRV_SQLTYPE_BINARY(5); + break; case 'SQLSRV_SQLTYPE_CHAR': // our tests always use precision 5 for SQLSRV_SQLTYPE_CHAR return SQLSRV_SQLTYPE_CHAR(5); @@ -146,7 +147,7 @@ function get_sqlType_constant( $sqlType ) case 'SQLSRV_SQLTYPE_NCHAR': // our tests always use precision 5 for SQLSRV_SQLTYPE_NCHAR return SQLSRV_SQLTYPE_NCHAR(5); - break; + break; case 'SQLSRV_SQLTYPE_NUMERIC': // our tests always use precision 10 scale 5 for SQLSRV_SQLTYPE_NUMERIC return SQLSRV_SQLTYPE_NUMERIC(10, 5); @@ -157,7 +158,7 @@ function get_sqlType_constant( $sqlType ) } } -function isDateTimeType( $sqlType ) +function isDateTimeType($sqlType) { return ($sqlType == 'SQLSRV_SQLTYPE_DATE' || $sqlType == 'SQLSRV_SQLTYPE_DATETIME' || @@ -167,4 +168,20 @@ function isDateTimeType( $sqlType ) $sqlType == 'SQLSRV_SQLTYPE_TIME'); } +function isLOBType($sqlType) +{ + return ($sqlType == 'SQLSRV_SQLTYPE_TEXT' || $sqlType == 'SQLSRV_SQLTYPE_NTEXT' || $sqlType == 'SQLSRV_SQLTYPE_IMAGE'); +} + +function isCompatible($compatList, $dataType, $sqlType) +{ + foreach ($compatList[$dataType] as $compatType) { + if (stripos($compatType, $sqlType) !== false) { + return true; + } + } + + return false; +} + ?> diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_numeric.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_numeric.phpt index cb7aaf66..6fd5c3be 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_numeric.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_sqltype_numeric.phpt @@ -52,7 +52,7 @@ foreach ($dataTypes as $dataType) { } } // 22018 is the SQLSTATE for any incompatible conversion errors - if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018) { + if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == '22018') { echo "$sqlType should be compatible with $dataType\n"; $success = false; } diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt index 67c601b8..020dce75 100755 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt @@ -1,111 +1,154 @@ --TEST-- Test for inserting and retrieving encrypted data of datetime types --DESCRIPTION-- -Bind output params using sqlsrv_prepare with all sql_type +Bind output/inout params using sqlsrv_prepare with all sql_type --SKIPIF-- --FILE-- - array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), - "datetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), - "datetime2" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), - "smalldatetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), - "time" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), - "datetimeoffset" => array("SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIMEOFFSET") ); - -$conn = AE\connect(); - -foreach ($dataTypes as $dataType) { - echo "\nTesting $dataType:\n"; - $success = true; - - // create table - $tbname = GetTempTableName("", false); - $colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false)); - AE\createTable($conn, $tbname, $colMetaArr); - - if (AE\isColEncrypted()) { - // Create a Store Procedure - $spname = 'selectAllColumns'; - createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"); - } - + array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), + "datetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), + "datetime2" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), + "smalldatetime" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DATE", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), + "time" => array( "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_TIME", "SQLSRV_SQLTYPE_DATETIMEOFFSET", "SQLSRV_SQLTYPE_DATETIME2"), + "datetimeoffset" => array("SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIMEOFFSET") ); + +function testOutputParam($conn, $spname, $direction, $dataType, $sqlType) +{ + // The driver does not support these types as output params, simply return + if (isDateTimeType($sqlType) || isLOBType($sqlType)) { + return true; + } + + global $compatList; + + $sqlTypeConstant = get_sqlType_constant($sqlType); + + // Call store procedure + $outSql = AE\getCallProcSqlPlaceholders($spname, 2); + + // Set these to NULL such that the PHP type of each output parameter is inferred + // from the SQLSRV_SQLTYPE_* constant + $c_detOut = null; + $c_randOut = null; + $stmt = sqlsrv_prepare( + $conn, + $outSql, + array(array( &$c_detOut, $direction, null, $sqlTypeConstant), + array(&$c_randOut, $direction, null, $sqlTypeConstant )) + ); + if (!$stmt) { + die(print_r(sqlsrv_errors(), true)); + } + sqlsrv_execute($stmt); + + $success = false; + $errors = sqlsrv_errors(); + if (AE\IsDataEncrypted()) { + // With data encrypted, errors are totally expected + if (empty($errors)) { + echo "Encrypted data: $dataType should NOT be compatible with $sqlType\n"; + } else { + // This should return 22018, the SQLSTATE for any incompatible conversion, + // except the XML type + $success = ($errors[0]['SQLSTATE'] === '22018'); + if (!$success) { + if ($sqlType === 'SQLSRV_SQLTYPE_XML') { + $success = ($errors[0]['SQLSTATE'] === '42000'); + } else { + echo "Encrypted data: unexpected errors with SQL type: $sqlType\n"; + } + } + } + } else { + $compatible = isCompatible($compatList, $dataType, $sqlType); + if ($compatible) { + if (!empty($errors)) { + echo "$dataType should be compatible with $sqlType.\n"; + } else { + $success = true; + } + } else { + $implicitConv = 'Implicit conversion from data type '; + + // 22018 is the SQLSTATE for any incompatible conversion errors + if ($errors[0]['SQLSTATE'] === '22018') { + $success = true; + } elseif (strpos($errors[0]['message'], $implicitConv) !== false) { + $success = true; + } else { + echo "Failed with SQL type: $sqlType\n"; + } + } + } + return $success; +} + +//////////////////////////////////////////////////////////////////////////////////////// + +$conn = AE\connect(); + +foreach ($dataTypes as $dataType) { + echo "\nTesting $dataType:\n"; + $success = true; + + // create table + $tbname = GetTempTableName("", false); + $colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false)); + AE\createTable($conn, $tbname, $colMetaArr); + + // Create a Store Procedure + $spname = 'selectAllColumns'; + createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"); + // insert a row + // Take the second and third entres (some edge cases) from the various + // $[$dataType]_params in AEData.inc + // e.g. with $dataType = 'date', use $date_params[1] and $date_params[2] + // to form an array, namely ["0001-01-01", "9999-12-31"] $inputValues = array_slice(${explode("(", $dataType)[0] . "_params"}, 1, 2); - $r; - $stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r); - if ($r === false) { - is_incompatible_types_error($dataType, "default type"); - } - - foreach($directions as $direction) { - echo "Testing as $direction:\n"; - - // test each SQLSRV_SQLTYPE_ constants - foreach ($sqlTypes as $sqlType) { - if (!AE\isColEncrypted()) { - $isCompatible = false; - foreach ($compatList[$dataType] as $compatType) { - if (stripos($compatType, $sqlType) !== false) { - $isCompatible = true; - } - } - // 22018 is the SQLSTATE for any incompatible conversion errors - $errors = sqlsrv_errors(); - if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) { - echo "$sqlType should be compatible with $dataType\n"; - $success = false; - } - } else { - // skip unsupported datetime types - if (!isDateTimeType($sqlType)) { - $sqlTypeConstant = get_sqlType_constant($sqlType); - - // Call store procedure - $outSql = AE\getCallProcSqlPlaceholders($spname, 2); - $c_detOut = ''; - $c_randOut = ''; - $stmt = sqlsrv_prepare( $conn, $outSql, - array(array( &$c_detOut, SQLSRV_PARAM_OUT, null, $sqlTypeConstant), - array(&$c_randOut, SQLSRV_PARAM_OUT, null, $sqlTypeConstant ))); - if (!$stmt) { - die(print_r(sqlsrv_errors(), true)); - } - sqlsrv_execute($stmt); - $errors = sqlsrv_errors(); - if (empty($errors) && AE\IsDataEncrypted()) { - // SQLSRV_PHPTYPE_DATETIME not supported - echo "$dataType should not be compatible with any datetime type.\n"; - $success = false; - } - } - } - } - } - - // cleanup - sqlsrv_free_stmt($stmt); - sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); - - if ($success) { - echo "Test successfully done.\n"; - } - - if (AE\isColEncrypted()) { - dropProc($conn, $spname); - } - dropTable($conn, $tbname); -} - -sqlsrv_close($conn); + $r; + $stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r); + if ($r === false) { + fatalError("Failed to insert data of type $dataType\n"); + } + + foreach ($directions as $direction) { + $dir = ($direction == SQLSRV_PARAM_OUT) ? 'SQLSRV_PARAM_OUT' : 'SQLSRV_PARAM_INOUT'; + echo "Testing as $dir:\n"; + + // test each SQLSRV_SQLTYPE_* constants + foreach ($sqlTypes as $sqlType) { + $success = testOutputParam($conn, $spname, $direction, $dataType, $sqlType); + if (!$success) { + // No point to continue looping + echo("Test failed: $dataType as $sqlType\n"); + die(print_r(sqlsrv_errors(), true)); + } + } + } + + // cleanup + sqlsrv_free_stmt($stmt); + sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); + + dropProc($conn, $spname); + if ($success) { + echo "Test successfully done.\n"; + } + dropTable($conn, $tbname); +} + +sqlsrv_close($conn); ?> --EXPECT-- diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt index ea5285d8..1fd65641 100755 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt @@ -5,143 +5,181 @@ Bind output params using sqlsrv_prepare with all sql_type --SKIPIF-- --FILE-- - array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), - "tinyint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), - "smallint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), - "int" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), - "bigint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), - "decimal(18,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), - "numeric(10,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), - "float" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT"), - "real" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT")); -$epsilon = 0.0001; - -$conn = AE\connect(); - -foreach ($dataTypes as $dataType) { - echo "\nTesting $dataType:\n"; - $success = true; - - // create table - $tbname = GetTempTableName("", false); - $colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false)); - AE\createTable($conn, $tbname, $colMetaArr); - - // TODO: It's a good idea to test conversions between different datatypes when AE is off as well. - if (AE\isColEncrypted()) { - // Create a Store Procedure - $spname = 'selectAllColumns'; - createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"); - } - +$directions = array(SQLSRV_PARAM_OUT, SQLSRV_PARAM_INOUT); + +// this is a list of implicit datatype conversion that SQL Server allows (https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-conversion-database-engine) +$compatList = array("bit" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), + "tinyint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), + "smallint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), + "int" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), + "bigint" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP" ), + "decimal(18,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), + "numeric(10,5)" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT", "SQLSRV_SQLTYPE_TIMESTAMP"), + "float" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT"), + "real" => array( "SQLSRV_SQLTYPE_BINARY", "SQLSRV_SQLTYPE_VARBINARY", "SQLSRV_SQLTYPE_CHAR", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DATETIME", "SQLSRV_SQLTYPE_SMALLDATETIME", "SQLSRV_SQLTYPE_DECIMAL(18,5)", "SQLSRV_SQLTYPE_NUMERIC(10,5)", "SQLSRV_SQLTYPE_FLOAT", "SQLSRV_SQLTYPE_REAL", "SQLSRV_SQLTYPE_BIGINT", "SQLSRV_SQLTYPE_INT", "SQLSRV_SQLTYPE_SMALLINT", "SQLSRV_SQLTYPE_TINYINT", "SQLSRV_SQLTYPE_MONEY", "SQLSRV_SQLTYPE_SMALLMONEY", "SQLSRV_SQLTYPE_BIT")); + +function compareResults($dataType, $sqlType, $c_detOut, $c_randOut, $inputValues) +{ + $epsilon = 0.0001; + $success = true; + + if ($dataType == "float" || $dataType == "real") { + if (abs($c_detOut - $inputValues[0]) > $epsilon || abs($c_randOut - $inputValues[1]) > $epsilon) { + echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n"; + print(" c_det: " . $c_detOut . "\n"); + print(" c_rand: " . $c_randOut . "\n"); + $success = false; + } + } else { + if ($c_detOut != $inputValues[0] || $c_randOut != $inputValues[1]) { + echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n"; + print(" c_det: " . $c_detOut . "\n"); + print(" c_rand: " . $c_randOut . "\n"); + $success = false; + } + } + + return $success; +} + +function testOutputParam($conn, $spname, $direction, $dataType, $sqlType, $inputValues) +{ + // The driver does not support these types as output params, simply return + if (isDateTimeType($sqlType) || isLOBType($sqlType)) { + return true; + } + + global $compatList; + + $sqlTypeConstant = get_sqlType_constant($sqlType); + + // Call store procedure + $outSql = AE\getCallProcSqlPlaceholders($spname, 2); + + // Set these to NULL such that the PHP type of each output parameter is inferred + // from the SQLSRV_SQLTYPE_* constant + $c_detOut = null; + $c_randOut = null; + $stmt = sqlsrv_prepare( + $conn, + $outSql, + array(array( &$c_detOut, $direction, null, $sqlTypeConstant), + array(&$c_randOut, $direction, null, $sqlTypeConstant )) + ); + if (!$stmt) { + die(print_r(sqlsrv_errors(), true)); + } + sqlsrv_execute($stmt); + + $success = false; + $errors = sqlsrv_errors(); + if (AE\IsDataEncrypted()) { + if (empty($errors)) { + // With data encrypted, it's a lot stricter, so the results are expected + // to be numeric and comparable + $success = compareResults($dataType, $sqlType, $c_detOut, $c_randOut, $inputValues); + } else { + // This should return 22018, the SQLSTATE for any incompatible conversion, + // except the XML type + $success = ($errors[0]['SQLSTATE'] === '22018'); + if (!$success) { + if ($sqlType === 'SQLSRV_SQLTYPE_XML') { + $success = ($errors[0]['SQLSTATE'] === '42000'); + } else { + echo "Encrypted data: unexpected errors with SQL type: $sqlType\n"; + } + } + } + } else { + $compatible = isCompatible($compatList, $dataType, $sqlType); + if ($compatible && empty($errors)) { + $success = true; + } else { + // Even if $dataType is compatible with $sqlType sometimes + // we still get errors from the server -- if so, it might + // return either SQLSTATE '42000' or '22018' (operand type + // clash but only happens with some certain types) + // E.g. when converting a bigint to int or an int to numeric, + // SQLSTATE '42000' is returned, indicating an error when + // converting from one type to another. + // TODO 11559: investigate if SQLSTATE '42000' is indeed acceptable + $success = ($errors[0]['SQLSTATE'] === '42000' || ($errors[0]['SQLSTATE'] === '22018' && in_array($sqlType, ['SQLSRV_SQLTYPE_XML', 'SQLSRV_SQLTYPE_BINARY', 'SQLSRV_SQLTYPE_VARBINARY', 'SQLSRV_SQLTYPE_UNIQUEIDENTIFIER', 'SQLSRV_SQLTYPE_TIMESTAMP']))); + if (!$success) { + if ($compatible) { + echo "$dataType should be compatible with $sqlType.\n"; + } else { + echo "Failed with SQL type: $sqlType\n"; + } + } + } + } + + return $success; +} + +//////////////////////////////////////////////////////////////////////////////////////// + +$conn = AE\connect(); + +foreach ($dataTypes as $dataType) { + echo "\nTesting $dataType:\n"; + $success = true; + + // create table + $tbname = GetTempTableName("", false); + $colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false)); + AE\createTable($conn, $tbname, $colMetaArr); + + // Create a Store Procedure + $spname = 'selectAllColumns'; + createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"); + // insert a row + // Take the second and third entres (some edge cases) from the various + // $[$dataType]_params in AEData.inc + // e.g. with $dataType = 'decimal(18,5)', use $decimal_params[1] and $decimal_params[2] + // to form an array, namely [-9223372036854.80000, 9223372036854.80000] $inputValues = array_slice(${explode("(", $dataType)[0] . "_params"}, 1, 2); - $r; - // convert input values to strings for decimals and numerics - if ($dataTypes == "decimal(18,5)" || $dataTypes == "numeric(10,5)") { - $stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => (string) $inputValues[0], $colMetaArr[1]->colName => (string) $inputValues[1] ), $r); - } else { - $stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r); - } - if ($r === false) { - is_incompatible_types_error($dataType, "default type"); - } - - foreach($directions as $direction) { - echo "Testing as $direction:\n"; - - // test each SQLSRV_SQLTYPE_ constants - foreach ($sqlTypes as $sqlType) { - - if (!AE\isColEncrypted()) { - $isCompatible = false; - foreach ($compatList[$dataType] as $compatType) { - if (stripos($compatType, $sqlType) !== false) { - $isCompatible = true; - } - } - // 22018 is the SQLSTATE for any incompatible conversion errors - $errors = sqlsrv_errors(); - if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) { - echo "$sqlType should be compatible with $dataType\n"; - $success = false; - } - } else { - // skip unsupported datetime types - if (!isDateTimeType($sqlType)) { - $sqlTypeConstant = get_sqlType_constant($sqlType); - - // Call store procedure - $outSql = AE\getCallProcSqlPlaceholders($spname, 2); - if ($sqlType == 'SQLSRV_SQLTYPE_FLOAT' || $sqlType == 'SQLSRV_SQLTYPE_REAL') { - $c_detOut = 0.0; - $c_randOut = 0.0; - } else { - $c_detOut = 0; - $c_randOut = 0; - } - $stmt = sqlsrv_prepare($conn, $outSql, - array(array( &$c_detOut, constant($direction), null, $sqlTypeConstant), - array(&$c_randOut, constant($direction), null, $sqlTypeConstant))); - - if (!$stmt) { - die(print_r(sqlsrv_errors(), true)); - } - sqlsrv_execute($stmt); - $errors = sqlsrv_errors(); - - if (!empty($errors)) { - if (stripos("SQLSRV_SQLTYPE_" . $dataType, $sqlType) !== false) { - var_dump(sqlsrv_errors()); - $success = false; - } - } - else { - if (AE\IsDataEncrypted() || stripos("SQLSRV_SQLTYPE_" . $dataType, $sqlType) !== false) { - if ($dataType == "float" || $dataType == "real") { - if (abs($c_detOut - $inputValues[0]) > $epsilon || abs($c_randOut - $inputValues[1]) > $epsilon) { - echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n"; - print(" c_det: " . $c_detOut . "\n"); - print(" c_rand: " . $c_randOut . "\n"); - $success = false; - } - } else { - if ($c_detOut != $inputValues[0] || $c_randOut != $inputValues[1]) { - echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n"; - print(" c_det: " . $c_detOut . "\n"); - print(" c_rand: " . $c_randOut . "\n"); - $success = false; - } - } - } - } - - sqlsrv_free_stmt($stmt); - } - } - } - } - - if (AE\isColEncrypted()) { - dropProc($conn, $spname); - } - - if ($success) { - echo "Test successfully done.\n"; - } - - dropTable($conn, $tbname); -} - -sqlsrv_close($conn); + $r; + // convert input values to strings for decimals and numerics + if ($dataTypes == "decimal(18,5)" || $dataTypes == "numeric(10,5)") { + $stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => (string) $inputValues[0], $colMetaArr[1]->colName => (string) $inputValues[1] ), $r); + } else { + $stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r); + } + if ($r === false) { + fatalError("Failed to insert data of type $dataType\n"); + } + + foreach ($directions as $direction) { + $dir = ($direction == SQLSRV_PARAM_OUT) ? 'SQLSRV_PARAM_OUT' : 'SQLSRV_PARAM_INOUT'; + echo "Testing as $dir:\n"; + + // test each SQLSRV_SQLTYPE_ constants + foreach ($sqlTypes as $sqlType) { + $success = testOutputParam($conn, $spname, $direction, $dataType, $sqlType, $inputValues); + if (!$success) { + // No point to continue looping + echo("Test failed: $dataType as $sqlType\n"); + die(print_r(sqlsrv_errors(), true)); + } + } + } + + dropProc($conn, $spname); + if ($success) { + echo "Test successfully done.\n"; + } + + dropTable($conn, $tbname); +} + +sqlsrv_close($conn); ?> --EXPECT-- diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt index 8bbe9606..65919143 100755 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt @@ -5,117 +5,155 @@ Bind output params using sqlsrv_prepare with all sql_type --SKIPIF-- --FILE-- - array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"), - "varchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"), - "nchar(5)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"), - "nvarchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML")); - -$conn = AE\connect(); - -foreach ($dataTypes as $dataType) { - echo "\nTesting $dataType:\n"; - $success = true; - - // create table - $tbname = GetTempTableName("", false); - $colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false)); - AE\createTable($conn, $tbname, $colMetaArr); - - // TODO: It's a good idea to test conversions between different datatypes when AE is off as well. - if (AE\isColEncrypted()) { - // Create a Store Procedure - $spname = 'selectAllColumns'; - createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"); - } - - // insert a row - $inputValues = array_slice(${explode("(", $dataType)[0] . "_params"}, 1, 2); - $r; - $stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r); - if ($r === false) { - is_incompatible_types_error($dataType, "default type"); - } - - foreach($directions as $direction) { - echo "Testing as $direction:\n"; - - // test each SQLSRV_SQLTYPE_ constants - foreach ($sqlTypes as $sqlType) { - if (!AE\isColEncrypted()) { - $isCompatible = false; - foreach ($compatList[$dataType] as $compatType) { - if (stripos($compatType, $sqlType) !== false) { - $isCompatible = true; - } - } - // 22018 is the SQLSTATE for any incompatible conversion errors - $errors = sqlsrv_errors(); - if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) { - echo "$sqlType should be compatible with $dataType\n"; - $success = false; - } - } else { - // skip unsupported datetime types - if (!isDateTimeType($sqlType)) { - $sqlTypeConstant = get_sqlType_constant($sqlType); - - // Call store procedure - $outSql = AE\getCallProcSqlPlaceholders($spname, 2); - $c_detOut = ''; - $c_randOut = ''; - $stmt = sqlsrv_prepare($conn, $outSql, - array(array(&$c_detOut, SQLSRV_PARAM_INOUT, null, $sqlTypeConstant), - array(&$c_randOut, SQLSRV_PARAM_INOUT, null, $sqlTypeConstant))); - - if (!$stmt) { - die(print_r(sqlsrv_errors(), true)); - } - - sqlsrv_execute($stmt); - $errors = sqlsrv_errors(); - - if (!empty($errors) ) { - if (stripos("SQLSRV_SQLTYPE_" . $dataType, $sqlType) !== false) { - var_dump(sqlsrv_errors()); - $success = false; - } - } - else - { - if (AE\IsDataEncrypted() || stripos("SQLSRV_SQLTYPE_" . $dataType, $sqlType) !== false) { - if ($c_detOut != $inputValues[0] || $c_randOut != $inputValues[1]) { - echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n"; - print(" c_det: " . $c_detOut . "\n"); - print(" c_rand: " . $c_randOut . "\n"); - $success = false; - } - } - } - - sqlsrv_free_stmt($stmt); - } - } - } - } +$directions = array(SQLSRV_PARAM_OUT, SQLSRV_PARAM_INOUT); + +// this is a list of implicit datatype conversion that SQL Server allows (https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-conversion-database-engine) +$compatList = array("char(5)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"), + "varchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"), + "nchar(5)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML"), + "nvarchar(max)" => array( "SQLSRV_SQLTYPE_CHAR(5)", "SQLSRV_SQLTYPE_VARCHAR", "SQLSRV_SQLTYPE_NCHAR(5)", "SQLSRV_SQLTYPE_NVARCHAR", "SQLSRV_SQLTYPE_DECIMAL", "SQLSRV_SQLTYPE_NUMERIC", "SQLSRV_SQLTYPE_NTEXT", "SQLSRV_SQLTYPE_TEXT", "SQLSRV_SQLTYPE_XML")); + +$conn = AE\connect(); + +function compareResults($dataType, $sqlType, $c_detOut, $c_randOut, $inputValues) +{ + $success = true; + if ($c_detOut != $inputValues[0] || $c_randOut != $inputValues[1]) { + echo "Incorrect output retrieved for datatype $dataType and sqlType $sqlType:\n"; + print(" c_det: " . $c_detOut . "\n"); + print(" c_rand: " . $c_randOut . "\n"); + + $success = false; + } + + return $success; +} + +function testOutputParam($conn, $spname, $direction, $dataType, $sqlType, $inputValues) +{ + // The driver does not support these types as output params, simply return + if (isDateTimeType($sqlType) || isLOBType($sqlType)) { + return true; + } + + global $compatList; - if (AE\isColEncrypted()) { - dropProc($conn, $spname); - } - if ($success) { - echo "Test successfully done.\n"; - } - dropTable($conn, $tbname); -} - -sqlsrv_close($conn); + $sqlTypeConstant = get_sqlType_constant($sqlType); + + // Call store procedure + $outSql = AE\getCallProcSqlPlaceholders($spname, 2); + + // Set these to NULL such that the PHP type of each output parameter is inferred + // from the SQLSRV_SQLTYPE_* constant + $c_detOut = null; + $c_randOut = null; + + $stmt = sqlsrv_prepare( + $conn, + $outSql, + array(array(&$c_detOut, SQLSRV_PARAM_INOUT, null, $sqlTypeConstant), + array(&$c_randOut, SQLSRV_PARAM_INOUT, null, $sqlTypeConstant)) + ); + + if (!$stmt) { + die(print_r(sqlsrv_errors(), true)); + } + sqlsrv_execute($stmt); + + $success = false; + $errors = sqlsrv_errors(); + if (AE\IsDataEncrypted()) { + if (empty($errors)) { + // With data encrypted, it's a lot stricter, so the results are expected + // to be comparable + $success = compareResults($dataType, $sqlType, $c_detOut, $c_randOut, $inputValues); + } else { + // This should return 22018, the SQLSTATE for any incompatible conversion, + // except the XML type + $success = ($errors[0]['SQLSTATE'] === '22018'); + if (!$success) { + if ($sqlType === 'SQLSRV_SQLTYPE_XML') { + $success = ($errors[0]['SQLSTATE'] === '42000'); + } else { + echo "Encrypted data: unexpected errors with SQL type: $sqlType\n"; + } + } + } + } else { + $compatible = isCompatible($compatList, $dataType, $sqlType); + if ($compatible && empty($errors)) { + $success = true; + } else { + // Even if $dataType is compatible with $sqlType sometimes + // we still get errors from the server -- if so, it should + // return SQLSTATE '42000', indicating an error when + // converting from one type to another + // With data NOT encrypted, converting string types to other + // types will not return '22018' + $success = ($errors[0]['SQLSTATE'] === '42000'); + if (!$success) { + echo "Failed with SQL type: $sqlType\n"; + } + } + } + + return $success; +} + +//////////////////////////////////////////////////////////////////////////////////////// + +foreach ($dataTypes as $dataType) { + echo "\nTesting $dataType:\n"; + $success = true; + + // create table + $tbname = GetTempTableName("", false); + $colMetaArr = array(new AE\ColumnMeta($dataType, "c_det"), new AE\ColumnMeta($dataType, "c_rand", null, false)); + AE\createTable($conn, $tbname, $colMetaArr); + + // Create a Store Procedure + $spname = 'selectAllColumns'; + createProc($conn, $spname, "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT", "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"); + + // insert a row + // Take the second and third entres from the various $[$dataType]_params in AEData.inc + // e.g. with $dataType = 'varchar(max)', use $varchar_params[1] and $varchar_params[2] + // to form an array + $inputValues = array_slice(${explode("(", $dataType)[0] . "_params"}, 1, 2); + $r; + $stmt = AE\insertRow($conn, $tbname, array( $colMetaArr[0]->colName => $inputValues[0], $colMetaArr[1]->colName => $inputValues[1] ), $r); + if ($r === false) { + fatalError("Failed to insert data of type $dataType\n"); + } + + foreach ($directions as $direction) { + $dir = ($direction == SQLSRV_PARAM_OUT) ? 'SQLSRV_PARAM_OUT' : 'SQLSRV_PARAM_INOUT'; + echo "Testing as $dir:\n"; + + // test each SQLSRV_SQLTYPE_ constants + foreach ($sqlTypes as $sqlType) { + $success = testOutputParam($conn, $spname, $direction, $dataType, $sqlType, $inputValues); + if (!$success) { + // No point to continue looping + echo("Test failed: $dataType as $sqlType\n"); + die(print_r(sqlsrv_errors(), true)); + } + } + } + + dropProc($conn, $spname); + if ($success) { + echo "Test successfully done.\n"; + } + dropTable($conn, $tbname); +} + +sqlsrv_close($conn); ?> --EXPECT--