Revised a few existing tests that are flawed (#1103)

This commit is contained in:
Jenny Tam 2020-03-02 09:51:02 -08:00 committed by GitHub
parent cd64173f95
commit 8bb6cef33c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 488 additions and 352 deletions

View file

@ -16,7 +16,7 @@ trigger:
jobs:
- job: macOS
pool:
vmImage: 'macOS-10.13'
vmImage: 'macOS-10.14'
steps:
- checkout: self
clean: true

View file

@ -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;
}
?>

View file

@ -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;
}

View file

@ -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--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
date_default_timezone_set("Canada/Pacific");
$dataTypes = array("date", "datetime", "datetime2", "smalldatetime", "time", "datetimeoffset");
$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("date" => 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");
}
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
date_default_timezone_set("Canada/Pacific");
$dataTypes = array("date", "datetime", "datetime2", "smalldatetime", "time", "datetimeoffset");
$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("date" => 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--

View file

@ -5,143 +5,181 @@ Bind output params using sqlsrv_prepare with all sql_type
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
$dataTypes = array("bit", "tinyint", "smallint", "int", "bigint", "decimal(18,5)", "numeric(10,5)", "float", "real" );
$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"));
$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--

View file

@ -5,117 +5,155 @@ Bind output params using sqlsrv_prepare with all sql_type
--SKIPIF--
<?php require('skipif_versions_old.inc'); ?>
--FILE--
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
<?php
require_once('MsCommon.inc');
require_once('AEData.inc');
$dataTypes = array("char(5)", "varchar(max)", "nchar(5)", "nvarchar(max)");
$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();
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--