From 10ff344fca84f1393ba809c604395b6ad3006fbd Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 5 Jun 2018 13:09:51 -0700 Subject: [PATCH 001/249] Fixed the potential error reported by Prefast code analysis --- source/pdo_sqlsrv/pdo_dbh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 48ed3b123..60d03eeb1 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -1276,7 +1276,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, try { - char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ]; + char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ] = {'\0'}; if( name == NULL ) { strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY ); } @@ -1300,7 +1300,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, sqlsrv_malloc_auto_ptr wsql_string; unsigned int wsql_len; - wsql_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_CHAR, reinterpret_cast( last_insert_id_query ), static_cast( strnlen_s( last_insert_id_query )), &wsql_len ); + wsql_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_CHAR, reinterpret_cast( last_insert_id_query ), sizeof(last_insert_id_query), &wsql_len ); CHECK_CUSTOM_ERROR( wsql_string == 0, driver_stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE, get_last_error_message() ) { throw core::CoreException(); From 44d1bb39ba1a09ac730f11aca9c1e3c1c9fdb8ba Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 6 Jun 2018 13:18:58 -0700 Subject: [PATCH 002/249] Use SQLSRV_ASSERT for checking NULL ptrs --- source/shared/core_results.cpp | 3 ++- source/shared/core_stream.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index d5882f2c6..85a4ecdab 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -345,7 +345,8 @@ sqlsrv_error* odbc_get_diag_rec( _In_ sqlsrv_stmt* odbc, _In_ SQLSMALLINT record SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ]; SQLINTEGER native_code; SQLSMALLINT wnative_message_len = 0; - + + SQLSRV_ASSERT(odbc != NULL, "odbc_get_diag_rec: sqlsrv_stmt* odbc was null."); SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message, SQL_MAX_ERROR_MESSAGE_LENGTH + 1, &wnative_message_len ); if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 5309f0043..3167d1e83 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -26,7 +26,7 @@ namespace { int sqlsrv_stream_close( _Inout_ php_stream* stream, int /*close_handle*/ TSRMLS_DC ) { sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); + SQLSRV_ASSERT( ss != NULL && ss->stmt != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); // free the stream resources in the Zend engine php_stream_free( stream, PHP_STREAM_FREE_RELEASE_STREAM ); @@ -52,7 +52,7 @@ size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) sqlsrv_malloc_auto_ptr temp_buf; sqlsrv_stream* ss = static_cast( stream->abstract ); - SQLSRV_ASSERT( ss != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); + SQLSRV_ASSERT( ss != NULL && ss->stmt != NULL, "sqlsrv_stream_read: sqlsrv_stream* ss is NULL." ); try { From f6e450b40859b880430eb2b5790659bca83e9281 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 7 Jun 2018 13:54:35 -0700 Subject: [PATCH 003/249] For these AKV tests check env despite not AE connected --- .../pdo_ae_azure_key_vault_client_secret.phpt | 2 +- .../pdo_ae_azure_key_vault_keywords.phpt | 2 +- .../pdo_ae_azure_key_vault_username_password.phpt | 2 +- test/functional/pdo_sqlsrv/skipif_not_akv.inc | 15 +++++++++++++++ test/functional/sqlsrv/skipif_not_akv.inc | 15 +++++++++++++++ .../sqlsrv_ae_azure_key_vault_client_secret.phpt | 2 +- .../sqlsrv_ae_azure_key_vault_keywords.phpt | 2 +- ...lsrv_ae_azure_key_vault_username_password.phpt | 2 +- 8 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/skipif_not_akv.inc create mode 100644 test/functional/sqlsrv/skipif_not_akv.inc diff --git a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_client_secret.phpt b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_client_secret.phpt index d523c69bd..a3bae5210 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_client_secret.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_client_secret.phpt @@ -1,7 +1,7 @@ --TEST-- Test client ID/secret credentials for Azure Key Vault for Always Encrypted. --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_client_secret.phpt b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_client_secret.phpt index fd6a11151..0a8b52a8d 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_client_secret.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_client_secret.phpt @@ -1,7 +1,7 @@ --TEST-- Test client ID/secret credentials for Azure Key Vault for Always Encrypted. --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- Date: Fri, 8 Jun 2018 16:00:27 -0700 Subject: [PATCH 004/249] Added the driver option to run functional tests --- test/functional/pdo_sqlsrv/MsCommon.inc | 158 +----------------- .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 11 +- test/functional/pdo_sqlsrv/MsSetup.inc | 4 +- .../pdo_sqlsrv/PDO21_Connection.phpt | 2 +- .../pdo_sqlsrv/pdo_020_bind_params_array.phpt | 2 + .../pdo_sqlsrv/pdo_040_error_information.phpt | 2 + .../pdo_065_construct_persistent.phpt | 2 +- .../pdo_065_construct_prefetch.phpt | 2 +- .../pdo_sqlsrv/pdo_ae_output_param_all.phpt | 1 + test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt | 3 +- test/functional/pdo_sqlsrv/skipif_not_akv.inc | 5 + .../pdo_sqlsrv/test_ae_keys_setup.phpt | 83 +++++---- test/functional/sqlsrv/MsSetup.inc | 4 +- test/functional/sqlsrv/skipif_not_akv.inc | 6 + .../functional/sqlsrv/test_ae_keys_setup.phpt | 39 +++-- 15 files changed, 106 insertions(+), 218 deletions(-) diff --git a/test/functional/pdo_sqlsrv/MsCommon.inc b/test/functional/pdo_sqlsrv/MsCommon.inc index cddfdab9c..b1c479e95 100644 --- a/test/functional/pdo_sqlsrv/MsCommon.inc +++ b/test/functional/pdo_sqlsrv/MsCommon.inc @@ -8,30 +8,6 @@ */ -// -// looks like an additional file (in addition to pdo_test_base.inc) may be needed for these PHPTs -// to be runnable from the MSSQL teams' internal proprietary test running system -// - -function IsAEQualified($conn) -{ - $msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; - $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; - if ($msodbcsql_maj < 17) { - return false; - } - require 'MsSetup.inc'; - if ($daasMode) { - // running against Azure - return true; - } - // if not Azure, check the server version - $server_ver = $conn->getAttribute(PDO::ATTR_SERVER_VERSION); - if (explode('.', $server_ver)[0] < 13) - return false; - return true; -} - // TO BE DELETED function connect($options=array()) { @@ -40,7 +16,7 @@ function connect($options=array()) // simply use $databaseName from MsSetup.inc to facilitate testing in Azure, // which does not support switching databases require 'MsSetup.inc'; - $conn = new PDO( "sqlsrv:Server=$server;database=$databaseName;ConnectionPooling=false;" , $uid, $pwd, $options); + $conn = new PDO( "sqlsrv:Server=$server;database=$databaseName;Driver=$driver;ConnectionPooling=false;" , $uid, $pwd, $options); $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); return $conn; } @@ -58,138 +34,6 @@ function connect($options=array()) } } - -/** - * Connect to the database specified in MsSetup.inc; Column Encryption keywords automatically added when $keystore is not none - * @param string $keywords : string to append to the dsn string in PDO::_construct - * @param array $options : attributes to pass to PDO::_construct - * @param bool $disableCE : flag for disabling column encryption even when keystore is NOT none - * for testing fetching encrypted data when connection column encryption is off - * @return PDO connection object - */ -function ae_connect( $keywords='', $options=array(), $disableCE = false ) -{ - try - { - // simply use $databaseName from MsSetup.inc to facilitate testing in Azure, - // which does not support switching databases - require 'MsSetup.inc'; - $dsn = "sqlsrv:Server=$server;database=$databaseName;ConnectionPooling=false;"; - if ( $keystore != "none" && !$disableCE ) - { - $dsn .= "ColumnEncryption=Enabled;"; - } - if ( $keystore == "ksp" && !$disableCE ) - { - require( 'AE_Ksp.inc' ); - $ksp_path = getKSPPath(); - $dsn .= "CEKeystoreProvider=$ksp_path;CEKeystoreName=$ksp_name;CEKeystoreEncryptKey=$encrypt_key;"; - } - if ( $keywords ) - { - $dsn .= $keywords; - } - $conn = new PDO( $dsn, $uid, $pwd, $options ); - $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); - return $conn; - } - catch( PDOException $e ) - { - var_dump( $e ); - exit; - } - catch(Exception $e) - { - var_dump( $e ); - exit; - } -} - - -/** - * @return string CEK name depending on the connection keywords - */ -function getCekName() -{ - require 'MsSetup.inc'; - $cekName = ''; - switch ( $keystore ) { - case "none": - $cekName = ''; - break; - case "win": - $cekName = 'AEColumnKey'; - break; - case "ksp": - $cekName = 'CustomCEK'; - break; - case "akv": - $cekName = 'AKVColumnKey'; - break; - default: - echo "getCekName: Invalid keystore name.\n"; - } - return $cekName; -} - - -/** - * class for encapsulating column metadata needed for creating a table - */ -class columnMeta { - public $colName; - public $dataType; //a string that includes the size of the type if necessary (e.g., decimal(10,5)) - public $encType; //randomized or deterministic; default is deterministic - public $options; //a string that is null by default (e.g. NOT NULL Identity (1,1) ) - - function __construct( $dataType, $colName = null, $options = null, $encType = "deterministic" ) - { - if ( is_null( $colName )) - { - $this->colName = get_default_colname( $dataType ); - } - else - { - $this->colName = $colName; - } - $this->dataType = $dataType; - $this->encType = $encType; - $this->options = $options; - } - /** - * @return string column definition for creating a table - */ - function getColDef() - { - require 'MsSetup.inc'; - $append = " "; - - // an identity column is not encrypted because a select query with identity column as the where clause is often run and the user want to have to bind parameter every time - if ( $keystore != "none" && stripos( $this->options, "identity" ) === false ) - { - $cekName = getCekName(); - if ( stripos( $this->dataType, "char" ) !== false ) - $append .= "COLLATE Latin1_General_BIN2 "; - $append .= sprintf( "ENCRYPTED WITH (ENCRYPTION_TYPE = %s, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = $cekName) ", $this->encType ); - } - $append .= $this->options; - $colDef = "[" . $this->colName . "] " . $this->dataType . $append; - return $colDef; - } -} - - -/** - * @return string default column name when a name is not provided in the columnMeta class - */ -function get_default_colname( $dataType ) -{ - $colName = "c_" . str_replace( ",", "_", str_replace( "(", "_", $dataType )); - $colName = rtrim( $colName, ")" ); - return $colName; -} - - /** * Create a table * @param object $conn : PDO connection object diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index dd183c32a..7aa915a0b 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -13,10 +13,6 @@ // to be runnable from the MSSQL teams' internal proprietary test running system // -const KSP_NAME = 'MyCustomKSPName'; -const ENCRYPT_KEY = 'LPKCWVD07N3RG98J0MBLG4H2'; -const KSP_TEST_TABLE = 'CustomKSPTestTable'; - function isAEQualified($conn) { $msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; @@ -52,7 +48,7 @@ function connect($keywords = '', $options=array(), $errmode = PDO::ERRMODE_EXCEP // simply use $databaseName from MsSetup.inc to facilitate testing in Azure, // which does not support switching databases require("MsSetup.inc"); - $dsn = getDSN($server, $databaseName, $keywords, $disableCE); + $dsn = getDSN($server, $databaseName, $driver, $keywords, $disableCE); $conn = new PDO($dsn, $uid, $pwd, $options); if ($errmode == PDO::ERRMODE_EXCEPTION || $errmode == PDO::ERRMODE_WARNING || $errmode == PDO::ERRMODE_SILENT) { $conn->setAttribute(PDO::ATTR_ERRMODE, $errmode); @@ -76,7 +72,7 @@ function connect($keywords = '', $options=array(), $errmode = PDO::ERRMODE_EXCEP * @param bool $disableCE : flag for disabling column encryption even when keystore is NOT none * @return string dsn string used for PDO constructor */ -function getDSN($sqlsrvserver, $database, $keywords = '', $disableCE = false) +function getDSN($sqlsrvserver, $database, $driver, $keywords = '', $disableCE = false) { require("MsSetup.inc"); $dsn = ""; @@ -89,6 +85,9 @@ function getDSN($sqlsrvserver, $database, $keywords = '', $disableCE = false) if ($database) { $dsn .= "database=$database;"; } + if ($driver) { + $dsn .= "driver=$driver;"; + } if ($keystore != "none" && !$disableCE) { $dsn .= "ColumnEncryption=Enabled;"; } diff --git a/test/functional/pdo_sqlsrv/MsSetup.inc b/test/functional/pdo_sqlsrv/MsSetup.inc index e07c46a7d..823f283cc 100644 --- a/test/functional/pdo_sqlsrv/MsSetup.inc +++ b/test/functional/pdo_sqlsrv/MsSetup.inc @@ -18,7 +18,6 @@ if (isset($_ENV['MSSQL_SERVER']) || isset($_ENV['MSSQL_USER']) || isset($_ENV['M $uid = 'TARGET_USERNAME'; $pwd = 'TARGET_PASSWORD'; $databaseName = 'TARGET_DATABASE'; - $DriverName = "ODBC Driver 11 for SQL Server"; } $adServer = 'TARGET_AD_SERVER'; @@ -27,13 +26,12 @@ $adUser = 'TARGET_AD_USERNAME'; $adPassword = 'TARGET_AD_PASSWORD'; $driverType = true; -$PhpDriver = "ODBC Driver 11 for SQL Server"; +$driver = "ODBC Driver 17 for SQL Server"; $tableName = 'pdo_test_table'; $tableIndex = 'php_test_table_idx'; $procName = 'php_test_proc'; $fileName = 'php_test_file.dat'; -$dsn = "odbc:Driver={$DriverName};Server=$server"; $connectionOptions = array(); $daasMode = false; $marsMode = true; diff --git a/test/functional/pdo_sqlsrv/PDO21_Connection.phpt b/test/functional/pdo_sqlsrv/PDO21_Connection.phpt index f09899626..9498a3585 100644 --- a/test/functional/pdo_sqlsrv/PDO21_Connection.phpt +++ b/test/functional/pdo_sqlsrv/PDO21_Connection.phpt @@ -15,7 +15,7 @@ try { // Invalid connection attempt => errors are expected $serverName="InvalidServerName"; - $dsn = getDSN($serverName, $databaseName); + $dsn = getDSN($serverName, $databaseName, $driver); $conn1 = new PDO($dsn, $uid, $pwd, $connectionOptions); if ($conn1) { printf("Invalid connection attempt should have failed.\n"); diff --git a/test/functional/pdo_sqlsrv/pdo_020_bind_params_array.phpt b/test/functional/pdo_sqlsrv/pdo_020_bind_params_array.phpt index 577ec0c53..ce12443da 100644 --- a/test/functional/pdo_sqlsrv/pdo_020_bind_params_array.phpt +++ b/test/functional/pdo_sqlsrv/pdo_020_bind_params_array.phpt @@ -12,6 +12,8 @@ try { // Create table $tableName = 'bindParams'; + dropTable($conn, $tableName); + $sql = "CREATE TABLE $tableName (ID TINYINT, SID CHAR(5))"; $stmt = $conn->exec($sql); diff --git a/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt b/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt index 93b6e2fd4..c8424856d 100644 --- a/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt +++ b/test/functional/pdo_sqlsrv/pdo_040_error_information.phpt @@ -12,6 +12,8 @@ try { // Create table $tableName = 'pdo_040test'; + dropTable($conn, $tableName); + // common function insertRow() is not used here since the test deliberately // executes an invalid insertion statement // thus it's not necessary to create an encrypted column for testing column encryption diff --git a/test/functional/pdo_sqlsrv/pdo_065_construct_persistent.phpt b/test/functional/pdo_sqlsrv/pdo_065_construct_persistent.phpt index 883da0299..8f2d8e8f7 100644 --- a/test/functional/pdo_sqlsrv/pdo_065_construct_persistent.phpt +++ b/test/functional/pdo_sqlsrv/pdo_065_construct_persistent.phpt @@ -13,7 +13,7 @@ require_once("MsCommon_mid-refactor.inc"); try { echo "Testing a connection with ATTR_PERSISTENT...\n"; // setting PDO::ATTR_PERSISTENT in PDO constructor returns an exception - $dsn = getDSN($server, $databaseName); + $dsn = getDSN($server, $databaseName, $driver); $attr = array(PDO::ATTR_PERSISTENT => true); $conn = new PDO($dsn, $uid, $pwd, $attr); //free the connection diff --git a/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt b/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt index 463b7ac4d..88f47495f 100644 --- a/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt +++ b/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt @@ -10,7 +10,7 @@ require_once("MsSetup.inc"); require_once("MsCommon_mid-refactor.inc"); try { echo "Testing a connection with ATTR_PREFETCH before ERRMODE_EXCEPTION...\n"; - $dsn = getDSN($server, $databaseName); + $dsn = getDSN($server, $databaseName, $driver); $attr = array(PDO::ATTR_PREFETCH => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); $conn = new PDO($dsn, $uid, $pwd, $attr); diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt index 580bbdcf3..ea20d7ec8 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt @@ -29,6 +29,7 @@ $colMetaArr = array("c1_int" => "int", createTable($conn, $tbname, $colMetaArr); // Create a Store Procedure $spname = 'selectAllColumns'; +dropProc($conn, $spname); $spSql = "CREATE PROCEDURE $spname ( @c1_int int OUTPUT, @c2_smallint smallint OUTPUT, @c3_tinyint tinyint OUTPUT, @c4_bit bit OUTPUT, diff --git a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt index f51d3ba6b..a5c31e09e 100644 --- a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt +++ b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt @@ -4,6 +4,7 @@ UTF-8 connection strings --FILE-- ---FILE-- -query($query); - $master_key_row = $stmt->fetch(); - - $query = "SELECT name FROM sys.column_encryption_keys"; - $stmt = $conn->query($query); - $encryption_key_row = $stmt->fetch(); - - if ($master_key_row[0] == 'AEMasterKey' && $encryption_key_row[0] == 'AEColumnKey'){ - echo "Test Successfully done.\n"; - } - else { - die("Column Master Key and Column Encryption Key not created.\n"); - } - unset($stmt); -} -else { - echo "Test Successfully done.\n"; -} -unset($conn); -?> ---EXPECT-- +--TEST-- +Test the existence of Windows Always Encrypted keys generated in the database setup +--DESCRIPTION-- +This test iterates through the rows of sys.column_master_keys and/or +sys.column_encryption_keys to look for the specific column master key and +column encryption key generated in the database setup +--SKIPIF-- + +--FILE-- +query($query); + + // Do not assume the master key must be the first one created + $found = false; + while ($master_key_row = $stmt->fetch()) { + if ($master_key_row[0] == 'AEMasterKey') { + $found = true; + } + } + if (!$found) { + die("Windows Column Master Key not created.\n"); + } + + // Do not assume the encryption key must be the first one created + $query = "SELECT name FROM sys.column_encryption_keys"; + $stmt = $conn->query($query); + + $found = false; + while ($encryption_key_row = $stmt->fetch()) { + if ($encryption_key_row[0] == 'AEColumnKey') { + $found = true; + } + } + if (!$found) { + die("Windows Column Encryption Key not created.\n"); + } + unset($stmt); +} + +echo "Test Successfully done.\n"; +unset($conn); +?> +--EXPECT-- Test Successfully done. \ No newline at end of file diff --git a/test/functional/sqlsrv/MsSetup.inc b/test/functional/sqlsrv/MsSetup.inc index 1ec7ceef4..aec2b4bb1 100644 --- a/test/functional/sqlsrv/MsSetup.inc +++ b/test/functional/sqlsrv/MsSetup.inc @@ -18,7 +18,9 @@ $tableIndex = "php_test_table_index"; $procName = "php_test_proc"; $fileName = "php_test_file.dat"; -$connectionOptions = array("Database"=>$database, "UID"=>$userName, "PWD"=>$userPassword, "TraceOn"=>false); +$driver = "ODBC Driver 17 for SQL Server"; + +$connectionOptions = array("Database" => $database, "UID" => $userName, "PWD" => $userPassword, "TraceOn" => false, "Driver" => $driver); $daasMode = false; $marsMode = true; diff --git a/test/functional/sqlsrv/skipif_not_akv.inc b/test/functional/sqlsrv/skipif_not_akv.inc index f56a4522c..9746db712 100644 --- a/test/functional/sqlsrv/skipif_not_akv.inc +++ b/test/functional/sqlsrv/skipif_not_akv.inc @@ -4,6 +4,12 @@ if (! extension_loaded("sqlsrv")) { die("skip extension not loaded"); } +require_once("MsSetup.inc"); +if ($driver != "ODBC Driver 17 for SQL Server") { + // the testing is not set to use ODBC 17 + die("skip - AE feature not supported in the current environment."); +} + require_once('MsCommon.inc'); $conn = AE\connect(); diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt index e70257a29..53a2c0fa6 100644 --- a/test/functional/sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -1,5 +1,9 @@ --TEST-- -retrieval of names of column master key and column encryption key generated in the database setup +Test the existence of Windows Always Encrypted keys generated in the database setup +--DESCRIPTION-- +This test iterates through the rows of sys.column_master_keys and/or +sys.column_encryption_keys to look for the specific column master key and +column encryption key generated in the database setup --SKIPIF-- --FILE-- @@ -13,23 +17,34 @@ $conn = connect(); if (AE\IsQualified($conn)) { $query = "SELECT name FROM sys.column_master_keys"; $stmt = sqlsrv_query($conn, $query); - sqlsrv_fetch($stmt); - $master_key_name = sqlsrv_get_field($stmt, 0); + $found = false; + while (sqlsrv_fetch($stmt)) { + $master_key_name = sqlsrv_get_field($stmt, 0); + if ($master_key_name == 'AEMasterKey') { + $found = true; + } + } + // $master_key_name = sqlsrv_get_field($stmt, 0); + if (!$found) { + die("Windows Column Master Key not created.\n"); + } $query = "SELECT name FROM sys.column_encryption_keys"; $stmt = sqlsrv_query($conn, $query); - sqlsrv_fetch($stmt); - $encryption_key_name = sqlsrv_get_field($stmt, 0); - - if ($master_key_name == 'AEMasterKey' && $encryption_key_name == 'AEColumnKey') { - echo "Test Successfully done.\n"; - } else { - echo "Column Master Key and Column Encryption Key not created.\n"; + $found = false; + while (sqlsrv_fetch($stmt)) { + $encryption_key_name = sqlsrv_get_field($stmt, 0); + if ($encryption_key_name == 'AEColumnKey') { + $found = true; + } + } + if (!$found) { + die("Windows Column Encryption Key not created.\n"); } sqlsrv_free_stmt($stmt); -} else { - echo "Test Successfully done.\n"; } + +echo "Test Successfully done.\n"; sqlsrv_close($conn); ?> --EXPECT-- From 17fa64ac79c55ce1d52006a9dfc542d1b57f99f5 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 11 Jun 2018 11:36:22 -0700 Subject: [PATCH 005/249] Fixed connection pooling tests for more than one ODBC drivers --- test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt | 13 +++++++++++-- test/functional/sqlsrv/sqlsrv_ConnPool_Unix.phpt | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt b/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt index 05bf24a71..c68a46b14 100644 --- a/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt +++ b/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt @@ -6,6 +6,15 @@ This test assumes the default odbcinst.ini has not been modified. --FILE-- --FILE-- Date: Mon, 11 Jun 2018 12:10:33 -0700 Subject: [PATCH 006/249] added driver option to pdo isPooled.php --- test/functional/pdo_sqlsrv/isPooled.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/isPooled.php b/test/functional/pdo_sqlsrv/isPooled.php index b0be6e0ed..a25e3d1db 100644 --- a/test/functional/pdo_sqlsrv/isPooled.php +++ b/test/functional/pdo_sqlsrv/isPooled.php @@ -1,10 +1,10 @@ Date: Thu, 21 Jun 2018 14:29:09 -0700 Subject: [PATCH 007/249] Removed win32 ifdefs re connection resiliency (#802) --- source/pdo_sqlsrv/pdo_dbh.cpp | 6 ------ source/shared/core_sqlsrv.h | 4 ---- source/sqlsrv/conn.cpp | 6 ------ 3 files changed, 16 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 60d03eeb1..8154ac69f 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -43,10 +43,8 @@ const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; const char ColumnEncryption[] = "ColumnEncryption"; const char ConnectionPooling[] = "ConnectionPooling"; -#ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; -#endif // _WIN32 const char Database[] = "Database"; const char Driver[] = "Driver"; const char Encrypt[] = "Encrypt"; @@ -109,7 +107,6 @@ struct pdo_txn_isolation_conn_attr_func static void func( connection_option const* /*option*/, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ); }; -#ifdef _WIN32 struct pdo_int_conn_str_func { static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC ) @@ -125,7 +122,6 @@ struct pdo_int_conn_str_func { conn_str += "};"; } }; -#endif // _WIN32 template struct pdo_int_conn_attr_func { @@ -243,7 +239,6 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_STRING, column_encryption_set_func::func }, -#ifdef _WIN32 { PDOConnOptionNames::ConnectRetryCount, sizeof( PDOConnOptionNames::ConnectRetryCount ), @@ -262,7 +257,6 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_INT, pdo_int_conn_str_func::func }, -#endif // _WIN32 { PDOConnOptionNames::Database, sizeof( PDOConnOptionNames::Database ), diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 39e53b19a..8ef19e97e 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1112,10 +1112,8 @@ const char Driver[] = "Driver"; const char CharacterSet[] = "CharacterSet"; const char ConnectionPooling[] = "ConnectionPooling"; const char ColumnEncryption[] = "ColumnEncryption"; -#ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; -#endif // _WIN32 const char Database[] = "Database"; const char Encrypt[] = "Encrypt"; const char Failover_Partner[] = "Failover_Partner"; @@ -1168,10 +1166,8 @@ enum SQLSRV_CONN_OPTIONS { SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID, SQLSRV_CONN_OPTION_KEYSTORE_SECRET, SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION, -#ifdef _WIN32 SQLSRV_CONN_OPTION_CONN_RETRY_COUNT, SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL, -#endif // _WIN32 // Driver specific connection options SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 183ac3c55..c77a70de2 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -95,7 +95,6 @@ struct bool_conn_str_func { } }; -#ifdef _WIN32 struct int_conn_str_func { static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC ) @@ -111,7 +110,6 @@ struct int_conn_str_func { conn_str += "};"; } }; -#endif // _WIN32 template struct int_conn_attr_func { @@ -188,10 +186,8 @@ const char Authentication[] = "Authentication"; const char CharacterSet[] = "CharacterSet"; const char ColumnEncryption[] = "ColumnEncryption"; const char ConnectionPooling[] = "ConnectionPooling"; -#ifdef _WIN32 const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; -#endif // _WIN32 const char Database[] = "Database"; const char DateAsString[] = "ReturnDatesAsStrings"; const char Driver[] = "Driver"; @@ -324,7 +320,6 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_STRING, column_encryption_set_func::func }, -#ifdef _WIN32 { SSConnOptionNames::ConnectRetryCount, sizeof( SSConnOptionNames::ConnectRetryCount ), @@ -343,7 +338,6 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_INT, int_conn_str_func::func }, -#endif // _WIN32 { SSConnOptionNames::Database, sizeof( SSConnOptionNames::Database ), From eeea7878fb0a4724a1bc959ae0b46feb652ebe8c Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 26 Jun 2018 13:41:07 -0700 Subject: [PATCH 008/249] Set the driver argument for getDSN to null by default (#798) * Added the driver argument to getDSN * Dropped the driver argument but set to null as default * Removed the AE condition in locale support * Modified the AE condition for locale support --- test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc | 4 ++-- test/functional/sqlsrv/MsCommon.inc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index 7aa915a0b..cf2ff605c 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -72,7 +72,7 @@ function connect($keywords = '', $options=array(), $errmode = PDO::ERRMODE_EXCEP * @param bool $disableCE : flag for disabling column encryption even when keystore is NOT none * @return string dsn string used for PDO constructor */ -function getDSN($sqlsrvserver, $database, $driver, $keywords = '', $disableCE = false) +function getDSN($sqlsrvserver, $database, $driver = null, $keywords = '', $disableCE = false) { require("MsSetup.inc"); $dsn = ""; @@ -85,7 +85,7 @@ function getDSN($sqlsrvserver, $database, $driver, $keywords = '', $disableCE = if ($database) { $dsn .= "database=$database;"; } - if ($driver) { + if (!is_null($driver)) { $dsn .= "driver=$driver;"; } if ($keystore != "none" && !$disableCE) { diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index 13900e2ec..def2d0f50 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -472,7 +472,7 @@ function isLocaleSupported() if (isWindows()) { return true; } - if (AE\isColEncrypted()) { + if (AE\isDataEncrypted()) { return false; } // now check ODBC version From ae0b95b757f231726a88332a0fd11da6daf09322 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 27 Jun 2018 11:03:30 -0700 Subject: [PATCH 009/249] Changed int to SQLLEN to avoid infinite loop (#806) --- source/shared/core_results.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 85a4ecdab..fc9935d5f 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -964,7 +964,7 @@ SQLRETURN binary_to_string( _Inout_ SQLCHAR* field_data, _Inout_ SQLLEN& read_so // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter) // to get the number of hex digits we can copy SQLLEN to_copy_hex = to_copy / (2 * extra); - for( int i = 0; i < to_copy_hex; ++i ) { + for( SQLLEN i = 0; i < to_copy_hex; ++i ) { *h = hex_chars[ (*b & 0xf0) >> 4 ]; h++; *h = hex_chars[ (*b++ & 0x0f) ]; From 197489ab4fd171f87c7b6dbe82e632054413e262 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 27 Jun 2018 11:03:52 -0700 Subject: [PATCH 010/249] Version 5.3.0 (#803) * Version 5.3.0 * Fixed the wrong replacements * Added comments block to m4 files * Use dnl for comments --- LICENSE | 26 +++++++++++++------------- source/pdo_sqlsrv/config.m4 | 20 ++++++++++++++++++++ source/pdo_sqlsrv/config.w32 | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- source/pdo_sqlsrv/pdo_init.cpp | 2 +- source/pdo_sqlsrv/pdo_parser.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 +- source/pdo_sqlsrv/template.rc | 2 +- source/shared/FormattedPrint.cpp | 2 +- source/shared/FormattedPrint.h | 2 +- source/shared/StringFunctions.cpp | 2 +- source/shared/StringFunctions.h | 2 +- source/shared/core_conn.cpp | 2 +- source/shared/core_init.cpp | 2 +- source/shared/core_results.cpp | 2 +- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 2 +- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 2 +- source/shared/globalization.h | 2 +- source/shared/interlockedatomic.h | 2 +- source/shared/interlockedatomic_gcc.h | 2 +- source/shared/interlockedslist.h | 2 +- source/shared/localization.hpp | 2 +- source/shared/localizationimpl.cpp | 2 +- source/shared/msodbcsql.h | 2 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 8 ++++---- source/shared/xplat.h | 2 +- source/shared/xplat_intsafe.h | 2 +- source/shared/xplat_winerror.h | 2 +- source/shared/xplat_winnls.h | 2 +- source/sqlsrv/config.m4 | 20 ++++++++++++++++++++ source/sqlsrv/config.w32 | 2 +- source/sqlsrv/conn.cpp | 2 +- source/sqlsrv/init.cpp | 2 +- source/sqlsrv/php_sqlsrv.h | 2 +- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 2 +- 43 files changed, 96 insertions(+), 56 deletions(-) diff --git a/LICENSE b/LICENSE index b52d9d433..13fab6115 100644 --- a/LICENSE +++ b/LICENSE @@ -1,14 +1,14 @@ -Copyright(c) 2017 Microsoft Corporation -All rights reserved. - -MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), -to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +Copyright(c) 2018 Microsoft Corporation +All rights reserved. + +MIT License +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index 46e22960d..b1bfd6e48 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -1,3 +1,23 @@ +dnl ---------------------------------------------------------------------------------------------------------------------------------- +dnl File: config.m4 +dnl +dnl Contents: the code that will go into the configure script, indicating options, +dnl external libraries and includes, and what source files are to be compiled. +dnl +dnl Microsoft Drivers 5.3 for PHP for SQL Server +dnl Copyright(c) Microsoft Corporation +dnl All rights reserved. +dnl MIT License +dnl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +dnl to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +dnl and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +dnl The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +dnl THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +dnl FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +dnl LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +dnl IN THE SOFTWARE. +dnl --------------------------------------------------------------------------------------------------------------------------------- + PHP_ARG_WITH(pdo_sqlsrv, for pdo_sqlsrv support, [ --with-pdo_sqlsrv Include pdo_sqlsrv support]) diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index bd1eaf048..7066e6254 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 8154ac69f..330205565 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 0fa961c6e..65e7a0786 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 8c783b39f..baafeed73 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index fd65b9eac..d41dde3b7 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 343537dd6..a05e75471 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index ba7dd4b76..38e4cec42 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 248c99ccc..8b94ddfad 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index 6a19a4259..1b48c0e92 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -6,7 +6,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index 61080a424..87f15f463 100644 --- a/source/shared/FormattedPrint.h +++ b/source/shared/FormattedPrint.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp index 550c0d359..d5183fc52 100644 --- a/source/shared/StringFunctions.cpp +++ b/source/shared/StringFunctions.cpp @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.h b/source/shared/StringFunctions.h index a0b31b783..b12e789b7 100644 --- a/source/shared/StringFunctions.h +++ b/source/shared/StringFunctions.h @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 7f2d16eb5..b094e110b 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index d47080066..cda38fa55 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index fc9935d5f..62a757d86 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 8ef19e97e..5530a5540 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 2cdb7a003..6d4d4f613 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 3167d1e83..32780a5b2 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index c537f766e..d8b7b2445 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/globalization.h b/source/shared/globalization.h index 486a381d2..88e8d1a40 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index 39e615f48..cc0163f0f 100644 --- a/source/shared/interlockedatomic.h +++ b/source/shared/interlockedatomic.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h index 7f5a974aa..e8c8e5bb2 100644 --- a/source/shared/interlockedatomic_gcc.h +++ b/source/shared/interlockedatomic_gcc.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h index 6c88f0e59..bf2bc9ca2 100644 --- a/source/shared/interlockedslist.h +++ b/source/shared/interlockedslist.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, singly // linked list. // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index 136c5a519..2ec13e09d 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 92221e984..75251eb6d 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -5,7 +5,7 @@ // Must be included in one c/cpp file per binary // A build error will occur if this inclusion policy is not followed // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index d6b74a5b8..3a759252e 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -20,7 +20,7 @@ // pecuniary loss) arising out of the use of or inability to use // this SDK, even if Microsoft has been advised of the possibility // of such damages. -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index 7be9ba68a..78478eaa1 100644 --- a/source/shared/sal_def.h +++ b/source/shared/sal_def.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index 6daf7fcd6..dc3b4ca5b 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -1,7 +1,7 @@ //--------------------------------------------------------------------------------------------------------------------------------- // File: typedefs_for_linux.h // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/version.h b/source/shared/version.h index 140e24484..7d6554431 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -4,7 +4,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -26,12 +26,12 @@ // Increase Minor with backward compatible new functionalities and API changes. // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 -#define SQLVERSION_MINOR 2 -#define SQLVERSION_PATCH 1 +#define SQLVERSION_MINOR 3 +#define SQLVERSION_PATCH 0 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 -#define PREVIEW 1 +#define PREVIEW 0 #define SEMVER_PRERELEASE // Semantic versioning build metadata, build meta data is not counted in precedence order. diff --git a/source/shared/xplat.h b/source/shared/xplat.h index f768f112f..baa393e22 100644 --- a/source/shared/xplat.h +++ b/source/shared/xplat.h @@ -3,7 +3,7 @@ // // Contents: include for definition of Windows types for non-Windows platforms // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h index 68595afb9..1baa473a7 100644 --- a/source/shared/xplat_intsafe.h +++ b/source/shared/xplat_intsafe.h @@ -4,7 +4,7 @@ // Contents: This module defines helper functions to prevent // integer overflow bugs. // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h index 292960694..44eb7c844 100644 --- a/source/shared/xplat_winerror.h +++ b/source/shared/xplat_winerror.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h index 3b1cbc68b..36aceae16 100644 --- a/source/shared/xplat_winnls.h +++ b/source/shared/xplat_winnls.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index e03ab0f4c..6b4190665 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -1,3 +1,23 @@ +dnl ---------------------------------------------------------------------------------------------------------------------------------- +dnl File: config.m4 +dnl +dnl Contents: the code that will go into the configure script, indicating options, +dnl external libraries and includes, and what source files are to be compiled. +dnl +dnl Microsoft Drivers 5.3 for PHP for SQL Server +dnl Copyright(c) Microsoft Corporation +dnl All rights reserved. +dnl MIT License +dnl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +dnl to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +dnl and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +dnl The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +dnl THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +dnl FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +dnl LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +dnl IN THE SOFTWARE. +dnl --------------------------------------------------------------------------------------------------------------------------------- + PHP_ARG_ENABLE(sqlsrv, whether to enable sqlsrv functions, [ --disable-sqlsrv Disable sqlsrv functions], yes) diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 77d8c4d11..449789c4b 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index c77a70de2..7bf805089 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 7385dc6b1..bb8ab04b4 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 17175730a..6c5e7b015 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 96aeae8a0..ac334cb9b 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index 446f50d88..ffee85372 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 50b963e48..bc26658c0 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.2 for PHP for SQL Server +// Microsoft Drivers 5.3 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License From aef3830479d7065bcd9391ebbe6856147921c036 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 27 Jun 2018 13:14:10 -0700 Subject: [PATCH 011/249] Modified AE fetch phptypes test to insert only one row at a time and loop through php types (#801) * Modified AE fetch phptypes test to insert only one row at a time and loop through php types * Fixed formatting --- .../sqlsrv/sqlsrv_ae_fetch_phptypes.phpt | 174 ++++++++++-------- 1 file changed, 93 insertions(+), 81 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_ae_fetch_phptypes.phpt b/test/functional/sqlsrv/sqlsrv_ae_fetch_phptypes.phpt index 22c9f742f..59e184478 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_fetch_phptypes.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_fetch_phptypes.phpt @@ -1,16 +1,17 @@ --TEST-- Test insert data and fetch as all possible php types +--DESCRIPTION-- +Test insert data of most common column types and fetch them all as possible php types --SKIPIF-- --FILE-- s; - - if ($diff == 0) { - $value = $valueAE; - } + + $dataArray = sqlsrv_fetch_array($stmt2, SQLSRV_FETCH_NUMERIC); + for ($i = 0; $i < sizeof($SQLSRV_PHPTYPE_CONST); ++$i) { + if (!sqlsrv_execute($stmt)) { + fatalError("Execute failed for $SQLSRV_PHPTYPE_CONST[$i]\n"); + } + + if ($result = sqlsrv_fetch($stmt)) { + for ($j = 0; $j < $numFields; $j++) { + $value = sqlsrv_get_field($stmt, $j, $SQLSRV_PHPTYPE_CONST[$i]); + $valueFromArray = $dataArray[$j]; + + // PHPTYPE_STREAM returns a PHP resource, so check the type + if (is_resource($value)) { + $value = get_resource_type($value); } - - if ($valueAE != $value or $valueFromArrayAE != $valueFromArray) { - echo "Values do not match! PHPType $i Field $j\n"; - print_r($valueAE);echo "\n"; - print_r($value);echo "\n"; - print_r($valueFromArrayAE);echo "\n"; - print_r($valueFromArray);echo "\n"; - print_r(sqlsrv_errors()); - fatalError("Test failed, values do not match.\n"); + + // For each type, the AE values come first and non-AE values second + // So let's do the comparison every second field + if ($j%2 == 0) { + $valueAE = $value; + $valueFromArrayAE = $valueFromArray; + } elseif ($j%2 == 1) { + // If returning a DateTime PHP type from a date only SQL type, + // PHP adds the current timestamp to make a DateTime object, + // and in this case the AE and non-AE times may be off by a + // fraction of a second since they are retrieved at ever-so-slightly + // different times. This not a test-failing discrepancy, so + // below the DateTime objects are made equal again for the next if + // block. + if ($value instanceof DateTime) { + // date_diff returns a DateInterval object, and s is + // the difference in seconds. s should be zero because + // the difference should be just a fraction of a second. + $datediff = date_diff($value, $valueAE); + $diff = $datediff->s; + + if ($diff == 0) { + $value = $valueAE; + } + } + + if ($valueAE != $value or $valueFromArrayAE != $valueFromArray) { + $index = floor($j / 2); + echo "Values do not match! PHPType $i Field $dataTypes[$index]\n"; + print_r($valueAE); + echo "\n--------\n\n"; + print_r($value); + echo "\n--------\n\n"; + print_r($valueFromArrayAE); + echo "\n--------\n\n"; + print_r($valueFromArray); + echo "\n--------\n\n"; + print_r(sqlsrv_errors()); + echo("Test failed, values do not match.\n"); + } } } } - ++$i; } - + sqlsrv_free_stmt($stmt); sqlsrv_free_stmt($stmt2); - - $deleteQuery = "DELETE FROM $tableName"; + + $deleteQuery = "TRUNCATE TABLE $tableName"; $stmt = sqlsrv_query($conn, $deleteQuery); if ($stmt == false) { print_r(sqlsrv_errors()); - fatalError("Delete statement failed"); + fatalError("Truncate statement failed"); } - + sqlsrv_free_stmt($stmt); } From 5aa9be7e13c10745e8f3ca6a680d30197cd03800 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 3 Jul 2018 16:47:14 -0700 Subject: [PATCH 012/249] Streamlined two very similar large column name tests (#807) * Streamlined two very similar large column name tests * Changed the EOL --- .../pdo_sqlsrv/PDO101_LargeColumnName.phpt | 95 ++++++++++--------- ...O101_LargeColumnName_unicode_col_name.phpt | 65 ++++++------- 2 files changed, 81 insertions(+), 79 deletions(-) diff --git a/test/functional/pdo_sqlsrv/PDO101_LargeColumnName.phpt b/test/functional/pdo_sqlsrv/PDO101_LargeColumnName.phpt index b5019290d..be6234f2e 100644 --- a/test/functional/pdo_sqlsrv/PDO101_LargeColumnName.phpt +++ b/test/functional/pdo_sqlsrv/PDO101_LargeColumnName.phpt @@ -1,76 +1,77 @@ ---TEST-- -PDO - Large Column Name Test ---DESCRIPTION-- -Verifies that long column names are supported (up to 128 chars). ---ENV-- -PHPT_EXEC=true ---SKIPIF-- - ---FILE-- +--TEST-- +PDO - Large Column Name Test +--DESCRIPTION-- +Verifies that long column names are supported (up to 128 chars). +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- query("CREATE TABLE [$tableName] ([$columnName] int)"); - $conn->query("INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); $stmt = $conn->query("SELECT * from [$tableName]"); - if ( null == $stmt ) - { - if (!$expectfail) - FatalError("Possible regression: Unable to retrieve inserted value."); - } - - DropTable($conn, $tableName); - + dropTable($conn, $tableName); } //-------------------------------------------------------------------- -// Repro +// repro // //-------------------------------------------------------------------- -function Repro() +function repro() { - $testName = "PDO - Large Column Name Test"; - StartTest($testName); - - $columnName = "a"; - - try - { - for ($a = 1; $a <= 128; $a++) - { - LargeColumnNameTest($columnName, $a > 128); - $columnName .= "A"; - } - } - catch (Exception $e) - { - echo $e->getMessage(); + startTest($testName); + + // The maximum size of a column name is 128 characters + $maxlen = 128; + $columnName = str_repeat('A', $maxlen); + + try { + largeColumnNameTest($columnName); + } catch (Exception $e) { + echo "Possible regression: Unable to retrieve inserted value\n"; + print_r($e->getMessage()); + echo "\n"; } - - EndTest($testName); + + // Now add another character to the name + $columnName .= 'A'; + try { + largeColumnNameTest($columnName); + } catch (Exception $e) { + // Expects to fail + $expected = 'is too long. Maximum length is 128.'; + if (strpos($e->getMessage(), $expected) === false) { + print_r($e->getMessage()); + echo "\n"; + } + } + + endTest($testName); } -Repro(); -?> ---EXPECT-- -Test "PDO - Large Column Name Test" completed successfully. +repro(); +?> +--EXPECT-- +Test "PDO - Large Column Name Test" completed successfully. diff --git a/test/functional/pdo_sqlsrv/PDO101_LargeColumnName_unicode_col_name.phpt b/test/functional/pdo_sqlsrv/PDO101_LargeColumnName_unicode_col_name.phpt index 634dc5272..9ad27164e 100644 --- a/test/functional/pdo_sqlsrv/PDO101_LargeColumnName_unicode_col_name.phpt +++ b/test/functional/pdo_sqlsrv/PDO101_LargeColumnName_unicode_col_name.phpt @@ -8,69 +8,70 @@ PHPT_EXEC=true --FILE-- query("CREATE TABLE [$tableName] ([$columnName] int)"); - $conn->query("INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); $stmt = $conn->query("SELECT * from [$tableName]"); - if ( null == $stmt ) - { - if (!$expectfail) - FatalError("Possible regression: Unable to retrieve inserted value."); - } - - DropTable($conn, $tableName); - + dropTable($conn, $tableName); } //-------------------------------------------------------------------- -// Repro +// repro // //-------------------------------------------------------------------- -function Repro() +function repro() { - $testName = "PDO - Large Column Name Test"; - StartTest($testName); + startTest($testName); - $columnName = "是"; + // The maximum size of a column name is 128 characters + $maxlen = 128; + $columnName = str_repeat('是', $maxlen); - try - { - for ($a = 1; $a <= 128; $a++) - { - LargeColumnNameTest($columnName, $a > 128); - $columnName .= "是"; - } + try { + largeColumnNameTest($columnName); + } catch (Exception $e) { + echo "Possible regression: Unable to retrieve inserted value\n"; + print_r($e->getMessage()); + echo "\n"; } - catch (Exception $e) - { - echo $e->getMessage(); + + // Now add another character to the name + $columnName .= '是'; + try { + largeColumnNameTest($columnName); + } catch (Exception $e) { + // Expects to fail + $expected = 'is too long. Maximum length is 128.'; + if (strpos($e->getMessage(), $expected) === false) { + print_r($e->getMessage()); + echo "\n"; + } } - EndTest($testName); + endTest($testName); } -Repro(); +repro(); + ?> --EXPECT-- Test "PDO - Large Column Name Test" completed successfully. From bbfe6df4cdf5f4e6b6340fa7d8d32aa3eb54ac55 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 5 Jul 2018 10:58:04 -0700 Subject: [PATCH 013/249] Updates to change log and readme (#811) * Updates to change log and readme * Dropped support for Ubuntu 17 * Modified as per review comments --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ README.md | 20 +++++++------------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 765bb786c..a3a6be270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.3.0 - 2018-07-20 +Updated PECL release packages. Here is the list of updates: + +### Added +- Added support for Azure Key Vault for Always Encrypted for basic CRUD functionalities such that Always Encrypted feature is available to all supported Windows, Linux or macOS platforms +- Added support for macOS High Sierra (requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) +- Added support for Ubuntu 18.04 LTS (requires MS ODBC Driver 17.2) +- Added support for Connection Resiliency to make it available to Linux or macOS users as well (requires MS ODBC Driver 17.2) + +### Fixed +- Issue [#577](https://github.com/Microsoft/msphpsql/issues/577) - Idle Connection Resiliency doesn't work with Column Encryption enabled connection +- Issue [#678](https://github.com/Microsoft/msphpsql/issues/678) - Idle Connection Resiliency doesn't work with Connection Pooling bug +- Issue [#699](https://github.com/Microsoft/msphpsql/issues/699) - Binding output parameter failed when the query in the stored procedure returned no data. The test case has been added to the test lab. +- Issue [#705](https://github.com/Microsoft/msphpsql/issues/705) - Always Encrypted - Retrieving a negative decimal value (edge case) as output parameter causes truncation +- Issue [#706](https://github.com/Microsoft/msphpsql/issues/706) - Always Encrypted - Cannot insert double with precision and scale (38, 38) +- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - Always Encrypted - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers +- Issue [#735](https://github.com/Microsoft/msphpsql/issues/735) - Extended the buffer size for PDO lastInsertId such that data types other than integers can be supported +- Pull Request [#759](https://github.com/Microsoft/msphpsql/pull/759) - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY +- Pull Request [#775](https://github.com/Microsoft/msphpsql/pull/775) - Fixed the problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC + +### Limitations +- No support for inout / output params when using sql_variant type +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connection will not work +- Always Encrypted feature, which requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) + - only Windows Certificate Store and Azure Key Vault are supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted feature enabled, Named Parameters in Sub Queries are not supported + - [Always Encrypted limitations](https://docs.microsoft.com/en-us/sql/connect/php/using-always-encrypted-php-drivers?view=sql-server-2017#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Connection pooling on Linux or macOS not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.6 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostics information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) + ## 5.2.1-preview - 2018-06-01 Updated PECL release packages. Here is the list of updates: diff --git a/README.md b/README.md index b30a3fa0f..a0172193b 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ For full details on the system requirements for the drivers, see the [system req On the client machine: - PHP 7.0.x, 7.1.x, or 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows) - A Web server such as Internet Information Services (IIS) is required. Your Web server must be configured to run PHP -- [Microsoft ODBC Driver 17][odbc17], [Microsoft ODBC Driver 13][odbc13], or [Microsoft ODBC Driver 11][odbc11] +- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-2017) On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux. @@ -86,14 +86,14 @@ The version number may have trailing pre-release version identifiers to indicate - Build metadata may be denoted by a plus sign followed by 4 or 5 digits, such as `1.2.3-preview+5678` or `1.2.3+5678`. Build metadata does not figure into the precedence order. ## Future Plans -- Expand SQL Server 2016 feature support (example: Always Encrypted) +- Expand SQL Server 2016 feature support (example: Azure AD) - Add more verification/fundamental tests - Bug fixes ## Guidelines for Reporting Issues We appreciate you taking the time to test the driver, provide feedback and report any issues. It would be extremely helpful if you: -- First check the [FAQ](https://github.com/Microsoft/msphpsql/wiki/FAQ) +- First check the [FAQ](https://github.com/Microsoft/msphpsql/wiki/FAQ) for common problems - Report each issue as a new issue (but check first if it's already been reported) - Please address the questions in the new issue template and provide scripts, table schema, and/or any details that may help reproduce the problem(s) @@ -106,15 +106,15 @@ Thank you! **Q:** What's next? -**A:** On March 23, 2018 we released the production release version 5.2.0 of our PHP Driver. We will continue working on our future plans and releasing previews of upcoming releases frequently. +**A:** On July 20, 2018 we released the production release version 5.3.0 of our PHP Driver. We will continue working on our future plans and releasing previews of upcoming releases. **Q:** Is Microsoft taking pull requests for this project? -**A:** Yes. Please submit pull requests to the **dev** branch and not the **master** branch. +**A:** Yes. Please submit pull requests to the **dev** branch, not the **master** branch. ## License -The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details. +The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details. ## Code of conduct @@ -138,12 +138,6 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf [phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild -[phpdoc]: http://msdn.microsoft.com/library/dd903047%28SQL.11%29.aspx - -[odbc11]: https://www.microsoft.com/download/details.aspx?id=36434 - -[odbc13]: https://www.microsoft.com/download/details.aspx?id=50420 - -[odbc17]: https://www.microsoft.com/download/details.aspx?id=56567 +[phpdoc]: https://docs.microsoft.com/en-us/sql/connect/php/microsoft-php-driver-for-sql-server?view=sql-server-2017 [PHPMan]: http://php.net/manual/install.unix.php From af9f77e1d19ec1b72ffe70400b1e75302704339d Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 10 Jul 2018 17:07:03 -0700 Subject: [PATCH 014/249] Fixed connection resiliency tests for Unix, updated AppVeyor for ODBC 17.2 --- appveyor.yml | 6 ++-- .../pdo_707_ae_output_param_decimals.phpt | 2 +- .../pdo_azure_ad_authentication.phpt | 1 + .../pdo_sqlsrv/pdo_connect_encrypted.phpt | 2 +- .../pdo_sqlsrv/pdo_connection_resiliency.phpt | 30 +++++++++---------- .../pdo_connection_resiliency_keywords.phpt | 13 ++++---- ...onnection_resiliency_prepare_transact.phpt | 12 ++++---- .../pdo_connection_resiliency_timeouts.phpt | 6 ++-- .../pdo_sqlsrv/skipif_protocol_not_tcp.inc | 2 -- .../skipif_version_less_than_2k14.inc | 2 -- .../skipif_version_less_than_2k16.inc | 2 -- .../sqlsrv/connection_resiliency.phpt | 12 ++++---- .../connection_resiliency_keywords.phpt | 15 +++++----- ...onnection_resiliency_prepare_transact.phpt | 12 ++++---- .../connection_resiliency_timeouts.phpt | 12 ++++---- .../sqlsrv/skipif_protocol_not_tcp.inc | 2 -- .../sqlsrv/skipif_version_less_than_2k14.inc | 2 -- .../sqlsrv/skipif_version_less_than_2k16.inc | 2 -- .../sqlsrv_azure_ad_authentication.phpt | 1 + 19 files changed, 62 insertions(+), 74 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9d2cdeea1..4a0fcbddd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -81,10 +81,10 @@ install: } Else { $env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER; } - - echo Downloading MSODBCSQL 17.1 + - echo Downloading MSODBCSQL 17.2 # AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it - - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/msodbcsql_17.1.0.1_x64.msi', 'c:\projects\msodbcsql_17.1.0.1_x64.msi') - - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.1.0.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.2.0.1_x64.msi', 'c:\projects\msodbcsql_17.2.0.1_x64.msi') + - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.2.0.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL - echo Checking the version of MSODBCSQL - reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server" - dir %WINDIR%\System32\msodbcsql*.dll diff --git a/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt b/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt index 6b59c134e..8433d1940 100644 --- a/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt +++ b/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt @@ -7,7 +7,7 @@ do not need to be encrypted --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- --FILE-- + --FILE-- --EXPECTREGEX-- -Statement 1 successful. -16 rows in result set. -Statement 2 successful. -9 rows in result set. -Statement 3 successful. --1 rows in result set. -Statement 4 successful. --1 rows in result set. -Statement 5 successful. --1 rows in result set. -Error executing statement 6. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. -Statement 7 successful. -Error executing statement 8. -SQLSTATE\[IMSSP\]: The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. +Statement 1 successful\. +16 rows in result set\. +Statement 2 successful\. +9 rows in result set\. +Statement 3 successful\. +-1 rows in result set\. +Statement 4 successful\. +-1 rows in result set\. +Statement 5 successful\. +-1 rows in result set\. +Error executing statement 6\. +SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x20) +Statement 7 successful\. +Error executing statement 8\. +SQLSTATE\[IMSSP\]: The connection cannot process this operation because there is a statement with pending results\. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. diff --git a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_keywords.phpt index 1d7b066e9..b012eed98 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_keywords.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_keywords.phpt @@ -1,8 +1,7 @@ --TEST-- Test the connection resiliency keywords ConnectRetryCount and ConnectRetryInterval and their ranges of acceptable values --SKIPIF-- - + --FILE-- --EXPECTREGEX-- -Statement 1 prepared. -Statement 1 executed. -Transaction begun. -Transaction was committed. -Transaction begun. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. +Statement 1 prepared\. +Statement 1 executed\. +Transaction begun\. +Transaction was committed\. +Transaction begun\. +SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x20) SQLSTATE\[08S01\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure diff --git a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt index 4389b3a95..375ae6372 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt @@ -83,6 +83,6 @@ DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ); ?> --EXPECTREGEX-- -Error executing statement 1. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. -Query successfully executed. +Error executing statement 1\. +SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: [An existing connection was forcibly closed by the remote host\.|Error code 0x20] +Query successfully executed\. diff --git a/test/functional/pdo_sqlsrv/skipif_protocol_not_tcp.inc b/test/functional/pdo_sqlsrv/skipif_protocol_not_tcp.inc index 47a0d8d7d..e59d550b8 100644 --- a/test/functional/pdo_sqlsrv/skipif_protocol_not_tcp.inc +++ b/test/functional/pdo_sqlsrv/skipif_protocol_not_tcp.inc @@ -1,6 +1,4 @@ 08S01 \[SQLSTATE\] => 08S01 - \[1\] => 10054 - \[code\] => 10054 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. + \[1\] => (10054|104) + \[code\] => (10054|104) + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.Error code 0x68) - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.Error code 0x68) \) @@ -229,8 +229,8 @@ Array \( \[0\] => 08S01 \[SQLSTATE\] => 08S01 - \[1\] => 10054 - \[code\] => 10054 + \[1\] => (10054|104) + \[code\] => (10054|104) \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure \) diff --git a/test/functional/sqlsrv/connection_resiliency_keywords.phpt b/test/functional/sqlsrv/connection_resiliency_keywords.phpt index 0211b47c1..7dd50fb57 100644 --- a/test/functional/sqlsrv/connection_resiliency_keywords.phpt +++ b/test/functional/sqlsrv/connection_resiliency_keywords.phpt @@ -3,8 +3,7 @@ Test the connection resiliency keywords --DESCRIPTION-- Test the connection resiliency keywords ConnectRetryCount and ConnectRetryInterval and their ranges of acceptable values --SKIPIF-- - + --FILE-- 08001 \[1\] => 0 \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' + \[2\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' + \[message\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' \) \) @@ -90,8 +89,8 @@ Array \[SQLSTATE\] => 08001 \[1\] => 0 \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryInterval' - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryInterval' + \[2\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryInterval' + \[message\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryInterval' \) \) @@ -104,8 +103,8 @@ Array \[SQLSTATE\] => 08001 \[1\] => 0 \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' + \[2\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' + \[message\] => (\[unixODBC\]|)\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ConnectRetryCount' \) \) diff --git a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt index 3d713c2ee..49036d3b3 100644 --- a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt +++ b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt @@ -187,11 +187,11 @@ Array \( \[0\] => 08S02 \[SQLSTATE\] => 08S02 - \[1\] => 10054 - \[code\] => 10054 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. + \[1\] => (10054|-1) + \[code\] => (10054|-1) + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]) - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]) \) @@ -199,8 +199,8 @@ Array \( \[0\] => 08S02 \[SQLSTATE\] => 08S02 - \[1\] => 10054 - \[code\] => 10054 + \[1\] => (10054|-1) + \[code\] => (10054|-1) \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Unable to open a logical session \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Unable to open a logical session \) diff --git a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt index 06a8d28f3..c4d9ed13a 100644 --- a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt +++ b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt @@ -84,11 +84,11 @@ Array \( \[0\] => 08S01 \[SQLSTATE\] => 08S01 - \[1\] => 10054 - \[code\] => 10054 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. + \[1\] => (10054|104) + \[code\] => (10054|104) + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x68) - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: An existing connection was forcibly closed by the remote host. + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x68) \) @@ -96,8 +96,8 @@ Array \( \[0\] => 08S01 \[SQLSTATE\] => 08S01 - \[1\] => 10054 - \[code\] => 10054 + \[1\] => (10054|104) + \[code\] => (10054|104) \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure \) diff --git a/test/functional/sqlsrv/skipif_protocol_not_tcp.inc b/test/functional/sqlsrv/skipif_protocol_not_tcp.inc index 4dc4921e7..26d960ca3 100644 --- a/test/functional/sqlsrv/skipif_protocol_not_tcp.inc +++ b/test/functional/sqlsrv/skipif_protocol_not_tcp.inc @@ -1,6 +1,4 @@ --FILE-- Date: Tue, 10 Jul 2018 19:44:48 -0700 Subject: [PATCH 015/249] Fixed expected output --- test/functional/sqlsrv/connection_resiliency.phpt | 6 ++---- .../sqlsrv/connection_resiliency_prepare_transact.phpt | 6 ++---- test/functional/sqlsrv/connection_resiliency_timeouts.phpt | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/test/functional/sqlsrv/connection_resiliency.phpt b/test/functional/sqlsrv/connection_resiliency.phpt index ebde2ea5a..ad88e923c 100644 --- a/test/functional/sqlsrv/connection_resiliency.phpt +++ b/test/functional/sqlsrv/connection_resiliency.phpt @@ -219,10 +219,8 @@ Array \[SQLSTATE\] => 08S01 \[1\] => (10054|104) \[code\] => (10054|104) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.Error code 0x68) - - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.Error code 0x68) - + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68) + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68) \) \[1\] => Array diff --git a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt index 49036d3b3..8f5a3e1b9 100644 --- a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt +++ b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt @@ -189,10 +189,8 @@ Array \[SQLSTATE\] => 08S02 \[1\] => (10054|-1) \[code\] => (10054|-1) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]) - - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]) - + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]) + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]) \) \[1\] => Array diff --git a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt index c4d9ed13a..b810d02e0 100644 --- a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt +++ b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt @@ -86,10 +86,8 @@ Array \[SQLSTATE\] => 08S01 \[1\] => (10054|104) \[code\] => (10054|104) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x68) - - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x68) - + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68) + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68) \) \[1\] => Array From 82be8141a3c56dc071acfc8a72669c76d9c35c26 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 11 Jul 2018 11:29:34 -0700 Subject: [PATCH 016/249] Fixed output and skipifs --- .../pdo_707_ae_output_param_decimals.phpt | 2 +- .../pdo_sqlsrv/pdo_connect_encrypted.phpt | 2 +- .../pdo_sqlsrv/pdo_connection_resiliency.phpt | 3 ++- .../pdo_connection_resiliency_keywords.phpt | 3 ++- ...onnection_resiliency_prepare_transact.phpt | 3 ++- .../pdo_connection_resiliency_timeouts.phpt | 3 ++- .../skipif_version_less_than_2k14.inc | 21 +++++++++++-------- ...onnection_resiliency_prepare_transact.phpt | 4 ++-- 8 files changed, 24 insertions(+), 17 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt b/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt index 8433d1940..67c0f4fd0 100644 --- a/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt +++ b/test/functional/pdo_sqlsrv/pdo_707_ae_output_param_decimals.phpt @@ -7,7 +7,7 @@ do not need to be encrypted --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- + --FILE-- --FILE-- + --FILE-- --FILE-- --FILE-- query( "SELECT @@VERSION" ); -if ($stmt) { - $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; -} else { - die( "skip Could not fetch SQL Server version during SKIPIF."); -} +// Exclude this check if running on Azure +if (!$daasMode) { + $stmt = $conn->query( "SELECT @@VERSION" ); + if ($stmt) { + $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; + } else { + die( "skip Could not fetch SQL Server version during SKIPIF."); + } -$version = explode(' ', $ver_string); + $version = explode(' ', $ver_string); -if ($version[3] < '2014') { - die("skip Wrong version of SQL Server, 2014 or later required"); + if ($version[3] < '2014') { + die("skip Wrong version of SQL Server, 2014 or later required"); + } } ?> diff --git a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt index 8f5a3e1b9..5d30280e2 100644 --- a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt +++ b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt @@ -189,8 +189,8 @@ Array \[SQLSTATE\] => 08S02 \[1\] => (10054|-1) \[code\] => (10054|-1) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]) - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]) + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\])\. + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\])\. \) \[1\] => Array From 0495513c07ce5cdadd0cbf79f7483fe5c76db9b6 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 11 Jul 2018 16:27:03 -0700 Subject: [PATCH 017/249] Fixed skipifs and output --- .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 2 +- .../pdo_azure_ad_authentication.phpt | 1 - .../pdo_sqlsrv/pdo_connection_resiliency.phpt | 3 +- .../pdo_connection_resiliency_keywords.phpt | 3 +- ...onnection_resiliency_prepare_transact.phpt | 3 +- .../pdo_connection_resiliency_timeouts.phpt | 5 +-- .../skipif_version_less_than_2k14.inc | 16 ++++++++ .../skipif_version_less_than_2k16.inc | 21 ++++++----- ...onnection_resiliency_prepare_transact.phpt | 4 +- .../sqlsrv/skipif_version_less_than_2k14.inc | 37 ++++++++++++++----- .../sqlsrv/skipif_version_less_than_2k16.inc | 21 ++++++----- .../sqlsrv_azure_ad_authentication.phpt | 1 - 12 files changed, 76 insertions(+), 41 deletions(-) diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index cf2ff605c..24633ff8f 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -48,7 +48,7 @@ function connect($keywords = '', $options=array(), $errmode = PDO::ERRMODE_EXCEP // simply use $databaseName from MsSetup.inc to facilitate testing in Azure, // which does not support switching databases require("MsSetup.inc"); - $dsn = getDSN($server, $databaseName, $driver, $keywords, $disableCE); + $dsn = getDSN($server, $databaseName, $DriverName, $keywords, $disableCE); $conn = new PDO($dsn, $uid, $pwd, $options); if ($errmode == PDO::ERRMODE_EXCEPTION || $errmode == PDO::ERRMODE_WARNING || $errmode == PDO::ERRMODE_SILENT) { $conn->setAttribute(PDO::ATTR_ERRMODE, $errmode); diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt index 1e40da23b..0aea766f4 100644 --- a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt @@ -2,7 +2,6 @@ Test the Authentication keyword and three options: SqlPassword, ActiveDirectoryIntegrated, and ActiveDirectoryPassword. --SKIPIF-- --FILE-- --FILE-- + --FILE-- --FILE-- --FILE-- --EXPECTREGEX-- Error executing statement 1\. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: [An existing connection was forcibly closed by the remote host\.|Error code 0x20] +SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x20) Query successfully executed\. diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc index 52fc358a0..fefd0d347 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc @@ -1,8 +1,14 @@ getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + +if (!$is_win) { + if ($msodbcsql_maj < 17 or $msodbcslq_min < 2) { + die("skip Unsupported ODBC driver version"); + } +} + // Get SQL Server Version // Exclude this check if running on Azure if (!$daasMode) { diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc index c384d07e0..5a413420c 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc @@ -11,16 +11,19 @@ if ($conn === false) { } // Get SQL Server Version -$stmt = $conn->query( "SELECT @@VERSION" ); -if ($stmt) { - $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; -} else { - die( "skip Could not fetch SQL Server version during SKIPIF."); -} +// Exclude this check if running on Azure +if (!$daasMode) { + $stmt = $conn->query( "SELECT @@VERSION" ); + if ($stmt) { + $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; + } else { + die( "skip Could not fetch SQL Server version during SKIPIF."); + } -$version = explode(' ', $ver_string); + $version = explode(' ', $ver_string); -if ($version[3] < '2016') { - die("skip Wrong version of SQL Server, 2016 or later required"); + if ($version[3] < '2016') { + die("skip Wrong version of SQL Server, 2016 or later required"); + } } ?> diff --git a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt index 5d30280e2..98239aa89 100644 --- a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt +++ b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt @@ -189,8 +189,8 @@ Array \[SQLSTATE\] => 08S02 \[1\] => (10054|-1) \[code\] => (10054|-1) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\])\. - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\])\. + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]\. ) + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]\. ) \) \[1\] => Array diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc index b57c7fae6..431ef3853 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc @@ -1,8 +1,14 @@ $userName, "PWD"=>$userPassword ); @@ -12,17 +18,30 @@ if ($conn === false) { die( "skip Could not connect during SKIPIF." ); } -// Get SQL Server version -$stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); -if (sqlsrv_fetch($stmt)) { - $ver_string = sqlsrv_get_field( $stmt, 0 ); -} else { - die("skip Could not fetch SQL Server version."); +$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + +if (!$is_win) { + if ($msodbcsql_maj < 17 or $msodbcslq_min < 2) { + die("skip Unsupported ODBC driver version"); + } } -$version = explode(' ', $ver_string); +// Get SQL Server version +// Exclude this check if running on Azure +if (!$daasMode) { + $stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); + if (sqlsrv_fetch($stmt)) { + $ver_string = sqlsrv_get_field( $stmt, 0 ); + } else { + die("skip Could not fetch SQL Server version."); + } + + $version = explode(' ', $ver_string); -if ($version[3] < '2014') { - die("skip Wrong version of SQL Server, 2014 or later required"); + if ($version[3] < '2014') { + die("skip Wrong version of SQL Server, 2014 or later required"); + } } ?> diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc index 77b27f711..fdd4c11d5 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc @@ -13,16 +13,19 @@ if ($conn === false) { } // Get SQL Server version -$stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); -if (sqlsrv_fetch($stmt)) { - $ver_string = sqlsrv_get_field( $stmt, 0 ); -} else { - die("skip Could not fetch SQL Server version."); -} +// Exclude this check if running on Azure +if (!$daasMode) { + $stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); + if (sqlsrv_fetch($stmt)) { + $ver_string = sqlsrv_get_field( $stmt, 0 ); + } else { + die("skip Could not fetch SQL Server version."); + } -$version = explode(' ', $ver_string); + $version = explode(' ', $ver_string); -if ($version[3] < '2016') { - die("skip Wrong version of SQL Server, 2016 or later required"); + if ($version[3] < '2016') { + die("skip Wrong version of SQL Server, 2016 or later required"); + } } ?> diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt index 85845adc4..bf9031123 100644 --- a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt @@ -2,7 +2,6 @@ Test the Authentication keyword and three options: SqlPassword, ActiveDirectoryIntegrated, and ActiveDirectoryPassword. --SKIPIF-- --FILE-- Date: Thu, 12 Jul 2018 13:24:04 -0700 Subject: [PATCH 018/249] Fixed driver name --- .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 2 +- .../pdo_sqlsrv/skipif_version_less_than_2k16.inc | 16 ++++++++++++++++ .../sqlsrv/skipif_version_less_than_2k16.inc | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index 24633ff8f..cf2ff605c 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -48,7 +48,7 @@ function connect($keywords = '', $options=array(), $errmode = PDO::ERRMODE_EXCEP // simply use $databaseName from MsSetup.inc to facilitate testing in Azure, // which does not support switching databases require("MsSetup.inc"); - $dsn = getDSN($server, $databaseName, $DriverName, $keywords, $disableCE); + $dsn = getDSN($server, $databaseName, $driver, $keywords, $disableCE); $conn = new PDO($dsn, $uid, $pwd, $options); if ($errmode == PDO::ERRMODE_EXCEPTION || $errmode == PDO::ERRMODE_WARNING || $errmode == PDO::ERRMODE_SILENT) { $conn->setAttribute(PDO::ATTR_ERRMODE, $errmode); diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc index 5a413420c..72553974b 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc @@ -1,8 +1,14 @@ getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + +if (!$is_win) { + if ($msodbcsql_maj < 17) { + die("skip Unsupported ODBC driver version"); + } +} + // Get SQL Server Version // Exclude this check if running on Azure if (!$daasMode) { diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc index fdd4c11d5..ce06258a2 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc @@ -1,8 +1,14 @@ $userName, "PWD"=>$userPassword ); @@ -12,6 +18,15 @@ if ($conn === false) { die( "skip Could not connect during SKIPIF." ); } +$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; + +if (!$is_win) { + if ($msodbcsql_maj < 17) { + die("skip Unsupported ODBC driver version"); + } +} + // Get SQL Server version // Exclude this check if running on Azure if (!$daasMode) { From 79be2821fa9b0fcdb7687b4852872021fd5a0ceb Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 12 Jul 2018 15:16:28 -0700 Subject: [PATCH 019/249] Updated installation instructions and sample script (#813) * Updated instructions and sample test for 5.3.0 RTW * Fixed sample code to adhere to php coding standard * Fixed cases and spaces * Modified NOTE for UB 18.04 based on review comments * Added 'exit' * Modified change log and readme based on review to PR 811 * Applied review comments --- CHANGELOG.md | 8 ++--- Linux-mac-install.md | 70 ++++++++++++++++++++++++-------------------- README.md | 2 +- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3a6be270..d4867eb95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,18 +10,18 @@ Updated PECL release packages. Here is the list of updates: - Added support for Azure Key Vault for Always Encrypted for basic CRUD functionalities such that Always Encrypted feature is available to all supported Windows, Linux or macOS platforms - Added support for macOS High Sierra (requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) - Added support for Ubuntu 18.04 LTS (requires MS ODBC Driver 17.2) -- Added support for Connection Resiliency to make it available to Linux or macOS users as well (requires MS ODBC Driver 17.2) +- Added support for Linux and macOS to Connection Resiliency (requires MS ODBC Driver 17.2) ### Fixed -- Issue [#577](https://github.com/Microsoft/msphpsql/issues/577) - Idle Connection Resiliency doesn't work with Column Encryption enabled connection -- Issue [#678](https://github.com/Microsoft/msphpsql/issues/678) - Idle Connection Resiliency doesn't work with Connection Pooling bug +- Issue [#577](https://github.com/Microsoft/msphpsql/issues/577) - Idle Connection Resiliency doesn't work with Column Encryption enabled connection (fixed in MS ODBC Driver 17.1) +- Issue [#678](https://github.com/Microsoft/msphpsql/issues/678) - Idle Connection Resiliency doesn't work with Connection Pooling bug (fixed in MS ODBC Driver 17.1) - Issue [#699](https://github.com/Microsoft/msphpsql/issues/699) - Binding output parameter failed when the query in the stored procedure returned no data. The test case has been added to the test lab. - Issue [#705](https://github.com/Microsoft/msphpsql/issues/705) - Always Encrypted - Retrieving a negative decimal value (edge case) as output parameter causes truncation - Issue [#706](https://github.com/Microsoft/msphpsql/issues/706) - Always Encrypted - Cannot insert double with precision and scale (38, 38) - Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - Always Encrypted - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers - Issue [#735](https://github.com/Microsoft/msphpsql/issues/735) - Extended the buffer size for PDO lastInsertId such that data types other than integers can be supported - Pull Request [#759](https://github.com/Microsoft/msphpsql/pull/759) - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY -- Pull Request [#775](https://github.com/Microsoft/msphpsql/pull/775) - Fixed the problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC +- Pull Request [#775](https://github.com/Microsoft/msphpsql/pull/775) - Fixed the truncation problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC ### Limitations - No support for inout / output params when using sql_variant type diff --git a/Linux-mac-install.md b/Linux-mac-install.md index 6964ca0e2..d84eca655 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,20 +1,24 @@ -# PHP Linux and Mac Drivers Installation Tutorial -The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft drivers for PHP for Microsoft SQL Server on Ubuntu 16.04 and 17.10, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11 and 10.12. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for Microsoft SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for Microsoft SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver)). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup). +# Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server +The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft drivers for PHP for Microsoft SQL Server on Ubuntu 16.04, 17.10 and 18.04, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12 and 10.13. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for Microsoft SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for Microsoft SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver)). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup). -These instruction install PHP 7.2 by default -- see the notes at the beginning of each section to install PHP 7.0 or 7.1. +These instructions install PHP 7.2 by default -- see the notes at the beginning of each section to install PHP 7.0 or 7.1. ## Contents of this page: -- [Installing the drivers on Ubuntu 16.04 and 17.10](#installing-the-drivers-on-ubuntu-1604-and-1710) +- [Installing the drivers on Ubuntu 16.04, 17.10, and 18.04](#installing-the-drivers-on-ubuntu-1604-1710-and-1804) - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) - [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12) - [Installing the drivers on macOS El Capitan, Sierra and High Sierra](#installing-the-drivers-on-macos-el-capitan-sierra-and-high-sierra) -## Installing the drivers on Ubuntu 16.04 and 17.10 +## Installing the drivers on Ubuntu 16.04, 17.10 and 18.04 > [!NOTE] > To install PHP 7.0 or 7.1, replace 7.2 with 7.0 or 7.1 in the following commands. +> For Ubuntu 18.04, the step to add the ondrej repository is not required unless +> PHP 7.0 or 7.1 is needed. However, installing PHP 7.0 or 7.1 in Ubuntu 18.04 may +> not work as packages from the ondrej repository come with dependencies that may +> conflict with a base Ubuntu 18.04 install. ### Step 1. Install PHP ``` @@ -44,6 +48,7 @@ a2enmod mpm_prefork a2enmod php7.2 echo "extension=pdo_sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/30-pdo_sqlsrv.ini echo "extension=sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/20-sqlsrv.ini +exit ``` ### Step 5. Restart Apache and test the sample script ``` @@ -86,11 +91,11 @@ echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini file echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/20-sqlsrv.ini exit ``` -An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually: +An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually (similar steps for pdo_sqlsrv): ``` pecl download sqlsrv -tar xvzf sqlsrv-5.2.0.tgz -cd sqlsrv-5.2.0/ +tar xvzf sqlsrv-5.3.0.tgz +cd sqlsrv-5.3.0/ phpize ./configure --with-php-config=/usr/bin/php-config make @@ -196,6 +201,7 @@ zypper install apache2 apache2-mod_php7 a2enmod php7 echo "extension=sqlsrv.so" >> /etc/php7/apache2/php.ini echo "extension=pdo_sqlsrv.so" >> /etc/php7/apache2/php.ini +exit ``` ### Step 5. Restart Apache and test the sample script ``` @@ -266,47 +272,47 @@ To test this sample script, create a file called testsql.php in your system's do "yourDatabase", - "Uid" => "yourUsername", - "PWD" => "yourPassword" + "database" => "yourDatabase", + "uid" => "yourUsername", + "pwd" => "yourPassword" ); -//Establishes the connection +// Establishes the connection $conn = sqlsrv_connect($serverName, $connectionOptions); -if( $conn === false ) { - die( FormatErrors( sqlsrv_errors())); +if ($conn === false) { + die(formatErrors(sqlsrv_errors())); } -//Select Query -$tsql= "SELECT @@Version as SQL_VERSION"; +// Select Query +$tsql = "SELECT @@Version AS SQL_VERSION"; -//Executes the query -$getResults= sqlsrv_query($conn, $tsql); +// Executes the query +$stmt = sqlsrv_query($conn, $tsql); -//Error handling -if ($getResults == FALSE) - die(FormatErrors(sqlsrv_errors())); +// Error handling +if ($stmt === false) { + die(formatErrors(sqlsrv_errors())); +} ?>

Results :

"); +while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { + echo $row['SQL_VERSION'] . PHP_EOL; } -sqlsrv_free_stmt($getResults); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); -function FormatErrors( $errors ) +function formatErrors($errors) { - /* Display errors. */ + // Display errors echo "Error information:
"; - foreach ( $errors as $error ) - { - echo "SQLSTATE: ".$error['SQLSTATE']."
"; - echo "Code: ".$error['code']."
"; - echo "Message: ".$error['message']."
"; + foreach ($errors as $error) { + echo "SQLSTATE: ". $error['SQLSTATE'] . "
"; + echo "Code: ". $error['code'] . "
"; + echo "Message: ". $error['message'] . "
"; } } ?> diff --git a/README.md b/README.md index a0172193b..20e3c0e98 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The version number may have trailing pre-release version identifiers to indicate - Build metadata may be denoted by a plus sign followed by 4 or 5 digits, such as `1.2.3-preview+5678` or `1.2.3+5678`. Build metadata does not figure into the precedence order. ## Future Plans -- Expand SQL Server 2016 feature support (example: Azure AD) +- Expand SQL Server 2016 feature support (example: Azure Active Directory) - Add more verification/fundamental tests - Bug fixes From a18a59b5811288d3d25033feb2660021c42f4ffe Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 12 Jul 2018 16:28:41 -0700 Subject: [PATCH 020/249] build output to debug appveyor failure --- buildscripts/builddrivers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/buildscripts/builddrivers.py b/buildscripts/builddrivers.py index 4378ad1f1..3e62bf256 100644 --- a/buildscripts/builddrivers.py +++ b/buildscripts/builddrivers.py @@ -196,6 +196,7 @@ def build(self): print('Build Completed') except: print('Something went wrong, launching log file', logfile) + print(open(logfile, 'r').read()) # display log file only when not testing if not self.testing: os.startfile(os.path.join(root_dir, 'php-sdk', logfile)) From cb7897761b811c4f544c7f246c8c6af40cb581d5 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 12 Jul 2018 17:01:45 -0700 Subject: [PATCH 021/249] removed debug output --- buildscripts/builddrivers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/buildscripts/builddrivers.py b/buildscripts/builddrivers.py index 3e62bf256..4378ad1f1 100644 --- a/buildscripts/builddrivers.py +++ b/buildscripts/builddrivers.py @@ -196,7 +196,6 @@ def build(self): print('Build Completed') except: print('Something went wrong, launching log file', logfile) - print(open(logfile, 'r').read()) # display log file only when not testing if not self.testing: os.startfile(os.path.join(root_dir, 'php-sdk', logfile)) From 35631cffaebdb393648301793855813e712159b6 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 13 Jul 2018 16:11:35 -0700 Subject: [PATCH 022/249] Streamlined two very similar large column name tests (#815) * Streamlined two very similar large column name tests * Added random number of test table names to avoid operand clash issues * Replaced to with for based on review --- CHANGELOG.md | 2 +- test/functional/sqlsrv/TC52_StreamSend.phpt | 2 +- .../sqlsrv/TC54_StreamPrepared.phpt | 2 +- .../sqlsrv/TC84_LargeColumnName.phpt | 49 ++++++------ .../sqlsrv/TC84_LargeColumnName_unicode.phpt | 51 +++++++------ ...TC84_LargeColumnName_unicode_col_name.phpt | 75 ------------------- 6 files changed, 60 insertions(+), 121 deletions(-) delete mode 100644 test/functional/sqlsrv/TC84_LargeColumnName_unicode_col_name.phpt diff --git a/CHANGELOG.md b/CHANGELOG.md index d4867eb95..0794d8dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ Updated PECL release packages. Here is the list of updates: - Added support for Azure Key Vault for Always Encrypted for basic CRUD functionalities such that Always Encrypted feature is available to all supported Windows, Linux or macOS platforms - Added support for macOS High Sierra (requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) - Added support for Ubuntu 18.04 LTS (requires MS ODBC Driver 17.2) -- Added support for Linux and macOS to Connection Resiliency (requires MS ODBC Driver 17.2) +- Added support for Linux and macOS for Connection Resiliency (requires MS ODBC Driver 17.2) ### Fixed - Issue [#577](https://github.com/Microsoft/msphpsql/issues/577) - Idle Connection Resiliency doesn't work with Column Encryption enabled connection (fixed in MS ODBC Driver 17.1) diff --git a/test/functional/sqlsrv/TC52_StreamSend.phpt b/test/functional/sqlsrv/TC52_StreamSend.phpt index 648c23fdf..3779e4448 100644 --- a/test/functional/sqlsrv/TC52_StreamSend.phpt +++ b/test/functional/sqlsrv/TC52_StreamSend.phpt @@ -19,7 +19,7 @@ function sendStream($minType, $maxType, $atExec) startTest($testName); setup(); - $tableName = "TC52test"; + $tableName = "TC52test" . rand(0, 100); $fileName = "TC52test.dat"; $conn1 = AE\connect(); diff --git a/test/functional/sqlsrv/TC54_StreamPrepared.phpt b/test/functional/sqlsrv/TC54_StreamPrepared.phpt index 94a6bbe88..a22868f90 100644 --- a/test/functional/sqlsrv/TC54_StreamPrepared.phpt +++ b/test/functional/sqlsrv/TC54_StreamPrepared.phpt @@ -18,7 +18,7 @@ function sendStream($minType, $maxType) startTest($testName); setup(); - $tableName = "TC54test"; + $tableName = "TC54test" . rand(0, 100); $fileName = "TC53test.dat"; $conn1 = AE\connect(); diff --git a/test/functional/sqlsrv/TC84_LargeColumnName.phpt b/test/functional/sqlsrv/TC84_LargeColumnName.phpt index bebcfadc4..b7cc3d570 100644 --- a/test/functional/sqlsrv/TC84_LargeColumnName.phpt +++ b/test/functional/sqlsrv/TC84_LargeColumnName.phpt @@ -10,7 +10,7 @@ PHPT_EXEC=true 128); - $columnName .= "A"; - } - } catch (Exception $e) { - echo $e->getMessage(); - } + // The maximum size of a column name is 128 characters + $maxlen = 128; + $columnName = str_repeat('a', $maxlen); + largeColumnNameTest($columnName); + + // Now add another character to the name + $columnName .= "A"; + + largeColumnNameTest($columnName, true); endTest($testName); } diff --git a/test/functional/sqlsrv/TC84_LargeColumnName_unicode.phpt b/test/functional/sqlsrv/TC84_LargeColumnName_unicode.phpt index 48bdc11c9..7a0aca36f 100644 --- a/test/functional/sqlsrv/TC84_LargeColumnName_unicode.phpt +++ b/test/functional/sqlsrv/TC84_LargeColumnName_unicode.phpt @@ -10,35 +10,44 @@ PHPT_EXEC=true 'UTF-8' )); + $conn = connect(array('CharacterSet'=>'UTF-8')); $tableName = "LargeColumnNameTest"; dropTable($conn, $tableName); - sqlsrv_query($conn, "CREATE TABLE [$tableName] ([$columnName] int)"); - - sqlsrv_query($conn, "INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); + $stmt = sqlsrv_query($conn, "CREATE TABLE [$tableName] ([$columnName] int)"); + if ($stmt == null) { + if (!$expectFail) { + fatalError("Possible regression: Unable to create test $tableName."); + } else { + $expected = 'is too long. Maximum length is 128.'; + if (strpos(sqlsrv_errors()[0]['message'], $expected) === false) { + print_r(sqlsrv_errors()); + } + echo "$"; + echo "stmt = null"; + echo "\n"; + } + } else { + sqlsrv_query($conn, "INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); - $stmt = sqlsrv_query($conn, "SELECT * from [$tableName]"); + $stmt = sqlsrv_query($conn, "SELECT * from [$tableName]"); - if (null == $stmt) { - echo "$"; - echo "stmt = null"; - echo "\n"; - } else { if (null == sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { - if (!$expectfail) { + if (!$expectFail) { fatalError("Possible regression: Unable to retrieve inserted value."); } } sqlsrv_free_stmt($stmt); } + dropTable($conn, $tableName); + sqlsrv_close($conn); } @@ -53,16 +62,16 @@ function repro() startTest($testName); - $columnName = "银"; + // The maximum size of a column name is 128 characters + $maxlen = 128; + $columnName = str_repeat('银', $maxlen); - try { - for ($a = 1; $a <= 129; $a++) { - LargeColumnNameTest($columnName, $a > 128); - $columnName .= "银"; - } - } catch (Exception $e) { - echo $e->getMessage(); - } + largeColumnNameTest($columnName); + + // Now add another character to the name + $columnName .= "银"; + + largeColumnNameTest($columnName, true); endTest($testName); } diff --git a/test/functional/sqlsrv/TC84_LargeColumnName_unicode_col_name.phpt b/test/functional/sqlsrv/TC84_LargeColumnName_unicode_col_name.phpt deleted file mode 100644 index dc7a1eb2d..000000000 --- a/test/functional/sqlsrv/TC84_LargeColumnName_unicode_col_name.phpt +++ /dev/null @@ -1,75 +0,0 @@ ---TEST-- -PHP - Large Unicode Column Name Test ---DESCRIPTION-- -Verifies that long column names are supported (up to 128 chars). ---ENV-- -PHPT_EXEC=true ---SKIPIF-- - ---FILE-- -'UTF-8' )); - - $tableName = "LargeColumnNameTest"; - - dropTable($conn, $tableName); - - sqlsrv_query($conn, "CREATE TABLE [$tableName] ([$columnName] int)"); - - sqlsrv_query($conn, "INSERT INTO [$tableName] ([$columnName]) VALUES (5)"); - - $stmt = sqlsrv_query($conn, "SELECT * from [$tableName]"); - - if (null == $stmt) { - echo "$"; - echo "stmt = null"; - echo "\n"; - } else { - if (null == sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { - if (!$expectfail) { - fatalError("Possible regression: Unable to retrieve inserted value."); - } - } - sqlsrv_free_stmt($stmt); - } - - sqlsrv_close($conn); -} - - -//-------------------------------------------------------------------- -// repro -// -//-------------------------------------------------------------------- -function repro() -{ - $testName = "PHP - Large Unicode Column Name Test"; - - startTest($testName); - - $columnName = "银"; - - try { - for ($a = 1; $a <= 129; $a++) { - LargeColumnNameTest($columnName, $a > 128); - $columnName .= "银"; - } - } catch (Exception $e) { - echo $e->getMessage(); - } - - - endTest($testName); -} - -repro(); -?> ---EXPECT-- -$stmt = null -Test "PHP - Large Unicode Column Name Test" completed successfully. From 706c526664b0fe47d0b14982d3a887af3fc85da2 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 18 Jul 2018 16:45:10 -0700 Subject: [PATCH 023/249] Changelog updated --- CHANGELOG.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0794d8dfe..86d31a036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,36 +7,36 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) Updated PECL release packages. Here is the list of updates: ### Added -- Added support for Azure Key Vault for Always Encrypted for basic CRUD functionalities such that Always Encrypted feature is available to all supported Windows, Linux or macOS platforms -- Added support for macOS High Sierra (requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) -- Added support for Ubuntu 18.04 LTS (requires MS ODBC Driver 17.2) -- Added support for Linux and macOS for Connection Resiliency (requires MS ODBC Driver 17.2) +- Added support for Azure Key Vault for Always Encrypted functionality. Always Encrypted functionality is supported on Linux and macOS through Azure Key Vault +- Added support for connection resiliency on Linux and macOS (requires version 17.2 or higher of the [ODBC driver](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) +- Added support for macOS High Sierra (requires version 17 or higher of the [ODBC driver](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) +- Added support for Ubuntu 18.04 (requires version 17.2 or higher of the [ODBC driver](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) ### Fixed -- Issue [#577](https://github.com/Microsoft/msphpsql/issues/577) - Idle Connection Resiliency doesn't work with Column Encryption enabled connection (fixed in MS ODBC Driver 17.1) -- Issue [#678](https://github.com/Microsoft/msphpsql/issues/678) - Idle Connection Resiliency doesn't work with Connection Pooling bug (fixed in MS ODBC Driver 17.1) -- Issue [#699](https://github.com/Microsoft/msphpsql/issues/699) - Binding output parameter failed when the query in the stored procedure returned no data. The test case has been added to the test lab. -- Issue [#705](https://github.com/Microsoft/msphpsql/issues/705) - Always Encrypted - Retrieving a negative decimal value (edge case) as output parameter causes truncation -- Issue [#706](https://github.com/Microsoft/msphpsql/issues/706) - Always Encrypted - Cannot insert double with precision and scale (38, 38) -- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - Always Encrypted - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers -- Issue [#735](https://github.com/Microsoft/msphpsql/issues/735) - Extended the buffer size for PDO lastInsertId such that data types other than integers can be supported -- Pull Request [#759](https://github.com/Microsoft/msphpsql/pull/759) - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY -- Pull Request [#775](https://github.com/Microsoft/msphpsql/pull/775) - Fixed the truncation problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC +- Issue #577 - Idle Connection Resiliency doesn't work with Column Encryption enabled connections (fixed in MS ODBC Driver 17.1) +- Issue #678 - Idle Connection Resiliency doesn't work with Connection Pooling (fixed in MS ODBC Driver 17.1) +- Issue #699 - Binding output parameters fails when the query in the stored procedure returns no data. The test case has been added to the test lab. +- Issue #705 - Always Encrypted - Retrieving a negative decimal value (edge case) as output parameter causes truncation +- Issue #706 - Always Encrypted - Cannot insert double with precision and scale (38, 38) +- Issue #707 - Always Encrypted - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers +- Issue #735 - Extended the buffer size for PDO::lastInsertId so that data types other than integers can be supported +- Pull Request #759 - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY +- Pull Request #775 - Fixed the truncation problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC ### Limitations - No support for inout / output params when using sql_variant type -- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connection will not work -- Always Encrypted feature, which requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) - - only Windows Certificate Store and Azure Key Vault are supported - - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted feature enabled, Named Parameters in Sub Queries are not supported +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not supported + - Issue #716 - With Always Encrypted enabled, named parameters in subqueries are not supported - [Always Encrypted limitations](https://docs.microsoft.com/en-us/sql/connect/php/using-always-encrypted-php-drivers?view=sql-server-2017#limitations-of-the-php-drivers-when-using-always-encrypted) ### Known Issues -- Connection pooling on Linux or macOS not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.6 +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.6 - When pooling is enabled in Linux or macOS - - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostics information, such as error messages, warnings and informative messages + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) -- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue #674) ## 5.2.1-preview - 2018-06-01 Updated PECL release packages. Here is the list of updates: From 7b720e1f623eacfc8834c3de7cb2bace24ac7b6d Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 18 Jul 2018 17:01:54 -0700 Subject: [PATCH 024/249] changelog updated, test skipif changed to run on unix platforms --- CHANGELOG.md | 2 +- test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86d31a036..939a6e23d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ Updated PECL release packages. Here is the list of updates: - No support for inout / output params when using sql_variant type - In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work - Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) - - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not supported + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported - Issue #716 - With Always Encrypted enabled, named parameters in subqueries are not supported - [Always Encrypted limitations](https://docs.microsoft.com/en-us/sql/connect/php/using-always-encrypted-php-drivers?view=sql-server-2017#limitations-of-the-php-drivers-when-using-always-encrypted) diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt index 5a39140e1..e6a5bbb6d 100644 --- a/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt @@ -1,7 +1,7 @@ --TEST-- Test new connection keyword ColumnEncryption --SKIPIF-- - + --FILE-- Date: Wed, 18 Jul 2018 17:25:38 -0700 Subject: [PATCH 025/249] Fixed skipif typo --- test/functional/sqlsrv/skipif_version_less_than_2k14.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc index 431ef3853..74d041fff 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc @@ -23,7 +23,7 @@ $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; if (!$is_win) { - if ($msodbcsql_maj < 17 or $msodbcslq_min < 2) { + if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { die("skip Unsupported ODBC driver version"); } } From 6cd7dbc8b9de9899300da051ddd43918ab5e1011 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 19 Jul 2018 10:59:32 -0700 Subject: [PATCH 026/249] Fixed typo in skipif for pdo --- test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc index fefd0d347..10849b9b2 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc @@ -21,7 +21,7 @@ $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; if (!$is_win) { - if ($msodbcsql_maj < 17 or $msodbcslq_min < 2) { + if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { die("skip Unsupported ODBC driver version"); } } From 820bc3199e91b9e48c014fd072da086cd8364df2 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 19 Jul 2018 12:22:02 -0700 Subject: [PATCH 027/249] Fixed some output for Travis --- test/functional/pdo_sqlsrv/pdo_connection_resiliency.phpt | 3 +-- .../pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt | 3 +-- test/functional/sqlsrv/connection_resiliency.phpt | 4 ++-- test/functional/sqlsrv/connection_resiliency_timeouts.phpt | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_connection_resiliency.phpt b/test/functional/pdo_sqlsrv/pdo_connection_resiliency.phpt index 3eb8b4f60..806f899d6 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_resiliency.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_resiliency.phpt @@ -219,7 +219,6 @@ Statement 4 successful\. Statement 5 successful\. -1 rows in result set\. Error executing statement 6\. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x20) -Statement 7 successful\. +SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x20)Statement 7 successful\. Error executing statement 8\. SQLSTATE\[IMSSP\]: The connection cannot process this operation because there is a statement with pending results\. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. diff --git a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt index b79705cd2..08424edaf 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt @@ -84,5 +84,4 @@ DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ); ?> --EXPECTREGEX-- Error executing statement 1\. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x20) -Query successfully executed\. +SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x20 |SMux Provider: Physical connection is not usable \[xFFFFFFFF\]\. )Query successfully executed\. diff --git a/test/functional/sqlsrv/connection_resiliency.phpt b/test/functional/sqlsrv/connection_resiliency.phpt index ad88e923c..1f01f4826 100644 --- a/test/functional/sqlsrv/connection_resiliency.phpt +++ b/test/functional/sqlsrv/connection_resiliency.phpt @@ -219,8 +219,8 @@ Array \[SQLSTATE\] => 08S01 \[1\] => (10054|104) \[code\] => (10054|104) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68) - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68) + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68|Error code 0x2746) + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68|Error code 0x2746) \) \[1\] => Array diff --git a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt index b810d02e0..be370ad72 100644 --- a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt +++ b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt @@ -86,8 +86,8 @@ Array \[SQLSTATE\] => 08S01 \[1\] => (10054|104) \[code\] => (10054|104) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68) - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68) + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68|Error code 0x2746) + \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68|Error code 0x2746) \) \[1\] => Array From 0f66c4848bdad1e7e0fa2ed50b74851b892277ce Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 19 Jul 2018 13:25:07 -0700 Subject: [PATCH 028/249] Moved error checking inside pdo connres tests --- .../pdo_sqlsrv/pdo_connection_resiliency.phpt | 41 +++++++++++-------- ...onnection_resiliency_prepare_transact.phpt | 28 ++++++++----- .../pdo_connection_resiliency_timeouts.phpt | 12 ++++-- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_connection_resiliency.phpt b/test/functional/pdo_sqlsrv/pdo_connection_resiliency.phpt index 806f899d6..5c9fe7b23 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_resiliency.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_resiliency.phpt @@ -161,7 +161,11 @@ try { echo $rowcount." rows in result set.\n"; } catch (PDOException $e) { echo "Error executing statement 6.\n"; - print_r($e->getMessage()); + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[08S02]')===false or (strpos($err, 'TCP Provider')===false and strpos($err, 'SMux Provider')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } unset($conn); @@ -200,25 +204,28 @@ try { } } catch (PDOException $e) { echo "Error executing statement 8.\n"; - print_r($e->getMessage()); + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[IMSSP]')===false or strpos($err, 'The connection cannot process this operation because there is a statement with pending results')===false) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } unset($conn); unset($conn_break); ?> ---EXPECTREGEX-- -Statement 1 successful\. -16 rows in result set\. -Statement 2 successful\. -9 rows in result set\. -Statement 3 successful\. --1 rows in result set\. -Statement 4 successful\. --1 rows in result set\. -Statement 5 successful\. --1 rows in result set\. -Error executing statement 6\. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x20)Statement 7 successful\. -Error executing statement 8\. -SQLSTATE\[IMSSP\]: The connection cannot process this operation because there is a statement with pending results\. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. +--EXPECT-- +Statement 1 successful. +16 rows in result set. +Statement 2 successful. +9 rows in result set. +Statement 3 successful. +-1 rows in result set. +Statement 4 successful. +-1 rows in result set. +Statement 5 successful. +-1 rows in result set. +Error executing statement 6. +Statement 7 successful. +Error executing statement 8. diff --git a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_prepare_transact.phpt b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_prepare_transact.phpt index 95db2b5bd..5bd5a3482 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_prepare_transact.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_prepare_transact.phpt @@ -190,7 +190,12 @@ try } catch ( PDOException $e ) { - print_r( $e->getMessage() ); + echo "Transaction failed.\n"; + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[08S02]')===false or (strpos($err, 'TCP Provider')===false and strpos($err, 'SMux Provider')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } // This try catch block prevents an Uncaught PDOException error that occurs @@ -201,17 +206,20 @@ try } catch ( PDOException $e ) { - print_r( $e->getMessage() ); + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[08S01]')===false or strpos($err, 'Communication link failure')===false) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } $conn_break = null; ?> ---EXPECTREGEX-- -Statement 1 prepared\. -Statement 1 executed\. -Transaction begun\. -Transaction was committed\. -Transaction begun\. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.|Error code 0x20) -SQLSTATE\[08S01\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure +--EXPECT-- +Statement 1 prepared. +Statement 1 executed. +Transaction begun. +Transaction was committed. +Transaction begun. +Transaction failed. diff --git a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt index 08424edaf..279a7eb48 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_resiliency_timeouts.phpt @@ -40,7 +40,11 @@ try catch( PDOException $e ) { echo "Error executing statement 1.\n"; - print_r( $e->getMessage() ); + $err = $e->getMessage(); + if (strpos($err, 'SQLSTATE[08S02]')===false or (strpos($err, 'TCP Provider')===false and strpos($err, 'SMux Provider')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } $conn = null; @@ -82,6 +86,6 @@ $conn_break = null; DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ); ?> ---EXPECTREGEX-- -Error executing statement 1\. -SQLSTATE\[08S02\]: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x20 |SMux Provider: Physical connection is not usable \[xFFFFFFFF\]\. )Query successfully executed\. +--EXPECT-- +Error executing statement 1. +Query successfully executed. From 495183e508793c1e81e69a822ea14910b1aadeea Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 19 Jul 2018 14:02:06 -0700 Subject: [PATCH 029/249] Added links back to changelog --- CHANGELOG.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 939a6e23d..abaa24db6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,22 +13,22 @@ Updated PECL release packages. Here is the list of updates: - Added support for Ubuntu 18.04 (requires version 17.2 or higher of the [ODBC driver](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) ### Fixed -- Issue #577 - Idle Connection Resiliency doesn't work with Column Encryption enabled connections (fixed in MS ODBC Driver 17.1) -- Issue #678 - Idle Connection Resiliency doesn't work with Connection Pooling (fixed in MS ODBC Driver 17.1) -- Issue #699 - Binding output parameters fails when the query in the stored procedure returns no data. The test case has been added to the test lab. -- Issue #705 - Always Encrypted - Retrieving a negative decimal value (edge case) as output parameter causes truncation -- Issue #706 - Always Encrypted - Cannot insert double with precision and scale (38, 38) -- Issue #707 - Always Encrypted - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers -- Issue #735 - Extended the buffer size for PDO::lastInsertId so that data types other than integers can be supported -- Pull Request #759 - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY -- Pull Request #775 - Fixed the truncation problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC +- Issue [#577](https://github.com/Microsoft/msphpsql/issues/577) - Idle Connection Resiliency doesn't work with Column Encryption enabled connections (fixed in MS ODBC Driver 17.1) +- Issue [#678](https://github.com/Microsoft/msphpsql/issues/678) - Idle Connection Resiliency doesn't work with Connection Pooling (fixed in MS ODBC Driver 17.1) +- Issue [#699](https://github.com/Microsoft/msphpsql/issues/699) - Binding output parameters fails when the query in the stored procedure returns no data. The test case has been added to the test lab. +- Issue [#705](https://github.com/Microsoft/msphpsql/issues/705) - Always Encrypted - Retrieving a negative decimal value (edge case) as output parameter causes truncation +- Issue [#706](https://github.com/Microsoft/msphpsql/issues/706) - Always Encrypted - Cannot insert double with precision and scale (38, 38) +- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - Always Encrypted - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers +- Issue [#735](https://github.com/Microsoft/msphpsql/issues/735) - Extended the buffer size for PDO::lastInsertId so that data types other than integers can be supported +- Pull Request [#759](https://github.com/Microsoft/msphpsql/pull/759) - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY +- Pull Request [#775](https://github.com/Microsoft/msphpsql/pull/775) - Fixed the truncation problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC ### Limitations - No support for inout / output params when using sql_variant type - In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work - Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported - - Issue #716 - With Always Encrypted enabled, named parameters in subqueries are not supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported - [Always Encrypted limitations](https://docs.microsoft.com/en-us/sql/connect/php/using-always-encrypted-php-drivers?view=sql-server-2017#limitations-of-the-php-drivers-when-using-always-encrypted) ### Known Issues @@ -36,7 +36,7 @@ Updated PECL release packages. Here is the list of updates: - When pooling is enabled in Linux or macOS - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) -- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue #674) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) ## 5.2.1-preview - 2018-06-01 Updated PECL release packages. Here is the list of updates: From 96efbdb47b2d901df53b527f6e81468741514305 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 19 Jul 2018 16:18:03 -0700 Subject: [PATCH 030/249] Fixed output for sqlsrv connres tests --- .../sqlsrv/connection_resiliency.phpt | 62 ++++--------------- ...onnection_resiliency_prepare_transact.phpt | 32 +++------- .../connection_resiliency_timeouts.phpt | 30 ++------- 3 files changed, 26 insertions(+), 98 deletions(-) diff --git a/test/functional/sqlsrv/connection_resiliency.phpt b/test/functional/sqlsrv/connection_resiliency.phpt index 1f01f4826..55a3daa24 100644 --- a/test/functional/sqlsrv/connection_resiliency.phpt +++ b/test/functional/sqlsrv/connection_resiliency.phpt @@ -144,7 +144,12 @@ $stmt6 = sqlsrv_query( $conn, "SELECT * FROM $tableName2" ); if( $stmt6 === false ) { echo "Error in statement 6.\n"; - print_r( sqlsrv_errors() ); + $err = sqlsrv_errors(); + if (strpos($err[0][0], '08S01')===false or !($err[0][1]==10054 or $err[0][1]==104) or + (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } else { @@ -188,7 +193,12 @@ $stmt8 = sqlsrv_query( $conn, "SELECT * FROM $tableName2" ); if( $stmt8 === false ) { echo "Error in statement 8.\n"; - print_r( sqlsrv_errors() ); + $err = sqlsrv_errors(); + if (strpos($err[0][0], 'IMSSP')===false or $err[0][1]!=-44 or + strpos($err[0][2], 'The connection cannot process this operation because there is a statement with pending results')===false) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } else { @@ -199,7 +209,7 @@ sqlsrv_close( $conn ); sqlsrv_close( $conn_break ); ?> ---EXPECTREGEX-- +--EXPECT-- Statement 1 successful. 16 rows in result set. Statement 2 successful. @@ -211,51 +221,5 @@ Statement 4 successful. Statement 5 successful. rows in result set. Error in statement 6. -Array -\( - \[0\] => Array - \( - \[0\] => 08S01 - \[SQLSTATE\] => 08S01 - \[1\] => (10054|104) - \[code\] => (10054|104) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68|Error code 0x2746) - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68|Error code 0x2746) - \) - - \[1\] => Array - \( - \[0\] => 08S01 - \[SQLSTATE\] => 08S01 - \[1\] => (10054|104) - \[code\] => (10054|104) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure - \) - -\) Statement 7 successful. Error in statement 8. -Array -\( - \[0\] => Array - \( - \[0\] => IMSSP - \[SQLSTATE\] => IMSSP - \[1\] => -44 - \[code\] => -44 - \[2\] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. - \[message\] => The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option. - \) - - \[1\] => Array - \( - \[0\] => HY000 - \[SQLSTATE\] => HY000 - \[1\] => 0 - \[code\] => 0 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Connection is busy with results for another command - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Connection is busy with results for another command - \) - -\) diff --git a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt index 98239aa89..ae021b3d1 100644 --- a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt +++ b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt @@ -167,40 +167,22 @@ else else { echo "Statement not valid and rollback failed.\n"; - print_r( sqlsrv_errors() ); + $err = sqlsrv_errors(); + if (strpos($err[0][0], '08S02')===false or !($err[0][1]==10054 or $err[0][1]==-1) or + (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } } sqlsrv_close( $conn ); sqlsrv_close( $conn_break ); ?> ---EXPECTREGEX-- +--EXPECT-- Statement 1 prepared. Statement 1 executed. Transaction begun. Transaction was committed. Transaction begun. Statement not valid and rollback failed. -Array -\( - \[0\] => Array - \( - \[0\] => 08S02 - \[SQLSTATE\] => 08S02 - \[1\] => (10054|-1) - \[code\] => (10054|-1) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]\. ) - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\](TCP Provider: An existing connection was forcibly closed by the remote host\.\n|SMux Provider: Physical connection is not usable \[xFFFFFFFF\]\. ) - \) - - \[1\] => Array - \( - \[0\] => 08S02 - \[SQLSTATE\] => 08S02 - \[1\] => (10054|-1) - \[code\] => (10054|-1) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Unable to open a logical session - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Unable to open a logical session - \) - -\) diff --git a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt index be370ad72..8664be85d 100644 --- a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt +++ b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt @@ -33,7 +33,12 @@ $stmt1 = sqlsrv_query( $conn, "SELECT * FROM $tableName1" ); if( $stmt1 === false ) { echo "Error in statement 1.\n"; - print_r( sqlsrv_errors() ); + $err = sqlsrv_errors(); + if (strpos($err[0][0], '08S01')===false or !($err[0][1]==10054 or $err[0][1]==104) or + (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false)) { + echo "Error: Wrong error message.\n"; + print_r($err); + } } else { @@ -78,27 +83,4 @@ DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ) ?> --EXPECTREGEX-- Error in statement 1. -Array -\( - \[0\] => Array - \( - \[0\] => 08S01 - \[SQLSTATE\] => 08S01 - \[1\] => (10054|104) - \[code\] => (10054|104) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68|Error code 0x2746) - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]TCP Provider: (An existing connection was forcibly closed by the remote host\.\n|Error code 0x68|Error code 0x2746) - \) - - \[1\] => Array - \( - \[0\] => 08S01 - \[SQLSTATE\] => 08S01 - \[1\] => (10054|104) - \[code\] => (10054|104) - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure - \[message\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Communication link failure - \) - -\) Statement 2 successful. From 825b429a1174f3c24594230ca4888ac9d289ac54 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 19 Jul 2018 16:43:33 -0700 Subject: [PATCH 031/249] Fixed output --- test/functional/sqlsrv/connection_resiliency.phpt | 4 ++-- .../sqlsrv/connection_resiliency_prepare_transact.phpt | 2 +- test/functional/sqlsrv/connection_resiliency_timeouts.phpt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/functional/sqlsrv/connection_resiliency.phpt b/test/functional/sqlsrv/connection_resiliency.phpt index 55a3daa24..f7c4bf553 100644 --- a/test/functional/sqlsrv/connection_resiliency.phpt +++ b/test/functional/sqlsrv/connection_resiliency.phpt @@ -145,8 +145,8 @@ if( $stmt6 === false ) { echo "Error in statement 6.\n"; $err = sqlsrv_errors(); - if (strpos($err[0][0], '08S01')===false or !($err[0][1]==10054 or $err[0][1]==104) or - (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false)) { + if (strpos($err[0][0], '08S01')===false or + (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false and strpos($err[0][2], 'Session Provider:')===false)) { echo "Error: Wrong error message.\n"; print_r($err); } diff --git a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt index ae021b3d1..274a24af0 100644 --- a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt +++ b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt @@ -169,7 +169,7 @@ else echo "Statement not valid and rollback failed.\n"; $err = sqlsrv_errors(); if (strpos($err[0][0], '08S02')===false or !($err[0][1]==10054 or $err[0][1]==-1) or - (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false)) { + (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false and strpos($err[0][2], 'Session Provider:')===false)) { echo "Error: Wrong error message.\n"; print_r($err); } diff --git a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt index 8664be85d..9685f4ac2 100644 --- a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt +++ b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt @@ -35,7 +35,7 @@ if( $stmt1 === false ) echo "Error in statement 1.\n"; $err = sqlsrv_errors(); if (strpos($err[0][0], '08S01')===false or !($err[0][1]==10054 or $err[0][1]==104) or - (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false)) { + (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false and strpos($err[0][2], 'Session Provider:')===false)) { echo "Error: Wrong error message.\n"; print_r($err); } From 6ee8c44e932353bf78bae23aaf5a9f586225d461 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 19 Jul 2018 16:45:15 -0700 Subject: [PATCH 032/249] Fixed output again --- test/functional/sqlsrv/connection_resiliency.phpt | 2 +- .../sqlsrv/connection_resiliency_prepare_transact.phpt | 2 +- test/functional/sqlsrv/connection_resiliency_timeouts.phpt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/sqlsrv/connection_resiliency.phpt b/test/functional/sqlsrv/connection_resiliency.phpt index f7c4bf553..2d47f6fe5 100644 --- a/test/functional/sqlsrv/connection_resiliency.phpt +++ b/test/functional/sqlsrv/connection_resiliency.phpt @@ -194,7 +194,7 @@ if( $stmt8 === false ) { echo "Error in statement 8.\n"; $err = sqlsrv_errors(); - if (strpos($err[0][0], 'IMSSP')===false or $err[0][1]!=-44 or + if (strpos($err[0][0], 'IMSSP')===false or strpos($err[0][2], 'The connection cannot process this operation because there is a statement with pending results')===false) { echo "Error: Wrong error message.\n"; print_r($err); diff --git a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt index 274a24af0..c42a4e447 100644 --- a/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt +++ b/test/functional/sqlsrv/connection_resiliency_prepare_transact.phpt @@ -168,7 +168,7 @@ else { echo "Statement not valid and rollback failed.\n"; $err = sqlsrv_errors(); - if (strpos($err[0][0], '08S02')===false or !($err[0][1]==10054 or $err[0][1]==-1) or + if (strpos($err[0][0], '08S02')===false or (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false and strpos($err[0][2], 'Session Provider:')===false)) { echo "Error: Wrong error message.\n"; print_r($err); diff --git a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt index 9685f4ac2..1abfdd309 100644 --- a/test/functional/sqlsrv/connection_resiliency_timeouts.phpt +++ b/test/functional/sqlsrv/connection_resiliency_timeouts.phpt @@ -34,7 +34,7 @@ if( $stmt1 === false ) { echo "Error in statement 1.\n"; $err = sqlsrv_errors(); - if (strpos($err[0][0], '08S01')===false or !($err[0][1]==10054 or $err[0][1]==104) or + if (strpos($err[0][0], '08S01')===false or (strpos($err[0][2], 'TCP Provider:')===false and strpos($err[0][2], 'SMux Provider:')===false and strpos($err[0][2], 'Session Provider:')===false)) { echo "Error: Wrong error message.\n"; print_r($err); From f71c52d63dde3bb8e5a607a6588a5d7724d1a604 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 23 Jul 2018 11:21:43 -0700 Subject: [PATCH 033/249] Fixed skipifs for connres --- test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc | 4 ++++ test/functional/sqlsrv/skipif_version_less_than_2k14.inc | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc index 10849b9b2..9e6846df3 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc @@ -24,6 +24,10 @@ if (!$is_win) { if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { die("skip Unsupported ODBC driver version"); } +} else { + if ($msodbcsql_maj < 17) { + die("skip Unsupported ODBC driver version"); + } } // Get SQL Server Version diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc index 74d041fff..3e0a58f7e 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc @@ -26,6 +26,10 @@ if (!$is_win) { if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { die("skip Unsupported ODBC driver version"); } +} else { + if ($msodbcsql_maj < 17) { + die("skip Unsupported ODBC driver version"); + } } // Get SQL Server version From efde09f9afc7788bc53b1ef67b9ffc215d9a7e2f Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 23 Jul 2018 16:38:24 -0700 Subject: [PATCH 034/249] Tweaked per review comments --- .../functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc | 6 +++++- test/functional/sqlsrv/skipif_version_less_than_2k14.inc | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc index 9e6846df3..9f37d93c0 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc @@ -21,8 +21,12 @@ $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; if (!$is_win) { - if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { + if ($msodbcsql_maj < 17) { die("skip Unsupported ODBC driver version"); + } else { + if ($msodbcsql_maj==17 && $msodbcsql_min < 2) { + die("skip Unsupported ODBC driver version"); + } } } else { if ($msodbcsql_maj < 17) { diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc index 3e0a58f7e..d70e4085e 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc @@ -23,8 +23,12 @@ $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; $msodbcsql_min = explode(".", $msodbcsql_ver)[1]; if (!$is_win) { - if ($msodbcsql_maj < 17 or $msodbcsql_min < 2) { + if ($msodbcsql_maj < 17) { die("skip Unsupported ODBC driver version"); + } else { + if ($msodbcsql_maj==17 && $msodbcsql_min < 2) { + die("skip Unsupported ODBC driver version"); + } } } else { if ($msodbcsql_maj < 17) { From b6d815bfc986dc442670a5649c993bb075728300 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 26 Jul 2018 15:21:03 -0700 Subject: [PATCH 035/249] Changes made to source and tests to support PHP 7.3 (#822) * Changes made to support php 7.3 * Correct use of the smart pointer * Fixed the tests for 7.3 * Some clean up for array_init() * Fixed formattings and clean up --- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/shared/core_sqlsrv.h | 13 +- source/shared/core_util.cpp | 6 +- source/sqlsrv/stmt.cpp | 14 +- source/sqlsrv/util.cpp | 53 +++++--- test/functional/sqlsrv/sqlsrv_errors.phpt | 6 +- test/functional/sqlsrv/test_conn_execute.phpt | 48 +++---- .../sqlsrv/test_non_alpha_password.phpt | 122 +++++++++--------- 9 files changed, 143 insertions(+), 123 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 330205565..a7d3258a3 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -546,7 +546,7 @@ int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_ ALLOC_HASHTABLE( pdo_conn_options_ht ); core::sqlsrv_zend_hash_init( *g_pdo_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, - ZVAL_INTERNAL_DTOR, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); // Either of g_pdo_henv_cp or g_pdo_henv_ncp can be used to propogate the error. dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_pdo_henv_cp, dbh->data_source, diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index a05e75471..1a16ab4c6 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -512,7 +512,7 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned msg = static_cast( sqlsrv_malloc( msg_len ) ); core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, error->native_message ); - php_error( E_WARNING, msg ); + php_error(E_WARNING, "%s", msg.get()); } ctx.set_last_error( error ); break; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 5530a5540..d630fb478 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -693,9 +693,9 @@ class hash_auto_ptr : public sqlsrv_auto_ptr { // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. void reset( _In_opt_ HashTable* ptr = NULL ) { - if( _ptr ) { - zend_hash_destroy( _ptr ); - FREE_HASHTABLE( _ptr ); + if (_ptr != NULL) { + zend_hash_destroy(_ptr); + FREE_HASHTABLE(_ptr); } _ptr = ptr; } @@ -2377,10 +2377,13 @@ namespace core { inline void sqlsrv_array_init( _Inout_ sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) { - int zr = ::array_init(new_array); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { +#if PHP_VERSION_ID < 70300 + CHECK_ZEND_ERROR(::array_init(new_array), ctx, SQLSRV_ERROR_ZEND_HASH) { throw CoreException(); } +#else + array_init(new_array); +#endif } inline void sqlsrv_php_stream_from_zval_no_verify( _Inout_ sqlsrv_context& ctx, _Outref_result_maybenull_ php_stream*& stream, _In_opt_ zval* stream_z TSRMLS_DC ) diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index d8b7b2445..38940e085 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -352,11 +352,11 @@ void die( _In_opt_ const char* msg, ... ) va_start( format_args, msg ); DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, last_err_msg, sizeof( last_err_msg ), &format_args ); va_end( format_args ); - if( rc == 0 ) { - php_error( E_ERROR, reinterpret_cast( INTERNAL_FORMAT_ERROR )); + if (rc == 0) { + php_error(E_ERROR, "%s", reinterpret_cast(INTERNAL_FORMAT_ERROR)); } - php_error( E_ERROR, last_err_msg ); + php_error(E_ERROR, "%s", last_err_msg); } namespace { diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index ac334cb9b..712f48d4b 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -889,7 +889,9 @@ PHP_FUNCTION( sqlsrv_fetch_object ) fci.object = Z_OBJ_P( &retval_z ); memset( &fcic, 0, sizeof( fcic )); +#if PHP_VERSION_ID < 70300 fcic.initialized = 1; +#endif fcic.function_handler = class_entry->constructor; fcic.calling_scope = class_entry; @@ -1806,10 +1808,14 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ field_names.transferred(); } - int zr = array_init( &fields ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } + int zr = SUCCESS; +#if PHP_VERSION_ID < 70300 + CHECK_ZEND_ERROR(array_init(&fields), stmt, SQLSRV_ERROR_ZEND_HASH) { + throw ss::SSException(); + } +#else + array_init(&fields); +#endif for( int i = 0; i < num_cols; ++i ) { SQLLEN field_len = -1; diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index bc26658c0..7b35dbd92 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -498,18 +498,21 @@ PHP_FUNCTION( sqlsrv_errors ) LOG_FUNCTION( "sqlsrv_errors" ); - if(( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) || - ( flags != SQLSRV_ERR_ALL && flags != SQLSRV_ERR_ERRORS && flags != SQLSRV_ERR_WARNINGS )) { - LOG( SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ ); - RETURN_FALSE; - } - int result; - zval err_z; - ZVAL_UNDEF( &err_z ); - result = array_init( &err_z ); - if( result == FAILURE ) { - RETURN_FALSE; - } + if(( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) || + ( flags != SQLSRV_ERR_ALL && flags != SQLSRV_ERR_ERRORS && flags != SQLSRV_ERR_WARNINGS )) { + LOG( SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ ); + RETURN_FALSE; + } + zval err_z; + ZVAL_UNDEF(&err_z); +#if PHP_VERSION_ID < 70300 + if (array_init(&err_z) == FAILURE) { + RETURN_FALSE; + } +#else + array_init(&err_z); +#endif + if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_ERRORS ) { if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( errors ) TSRMLS_CC )) { zval_ptr_dtor(&err_z); @@ -746,10 +749,13 @@ sqlsrv_error_const* get_error_message( _In_ unsigned int sqlsrv_error_code ) { void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, _In_ bool warning TSRMLS_DC ) { - - if( array_init( error_z ) == FAILURE ) { +#if PHP_VERSION_ID < 70300 + if (array_init(error_z) == FAILURE) { DIE( "Fatal error during error processing" ); } +#else + array_init(error_z); +#endif // sqlstate zval temp; @@ -828,7 +834,6 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo size_t prev_reported_cnt = 0; bool reported_chain_was_null = false; bool ignored_chain_was_null = false; - int zr = SUCCESS; zval error_z; ZVAL_UNDEF(&error_z); sqlsrv_error_auto_ptr error; @@ -837,10 +842,13 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo if( Z_TYPE_P( reported_chain ) == IS_NULL ) { reported_chain_was_null = true; - zr = array_init( reported_chain ); - if( zr == FAILURE ) { - DIE( "Fatal error in handle_errors_and_warnings" ); +#if PHP_VERSION_ID < 70300 + if (array_init(reported_chain) == FAILURE) { + DIE( "Fatal error during error processing" ); } +#else + array_init(reported_chain); +#endif } else { prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain )); @@ -851,11 +859,14 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo if( Z_TYPE_P( ignored_chain ) == IS_NULL ) { - ignored_chain_was_null = true; - zr = array_init( ignored_chain ); - if( zr == FAILURE ) { + ignored_chain_was_null = true; +#if PHP_VERSION_ID < 70300 + if (array_init(ignored_chain) == FAILURE) { DIE( "Fatal error in handle_errors_and_warnings" ); } +#else + array_init( ignored_chain ); +#endif } } diff --git a/test/functional/sqlsrv/sqlsrv_errors.phpt b/test/functional/sqlsrv/sqlsrv_errors.phpt index 064357da2..fe70816a8 100644 --- a/test/functional/sqlsrv/sqlsrv_errors.phpt +++ b/test/functional/sqlsrv/sqlsrv_errors.phpt @@ -119,7 +119,7 @@ sqlsrv_close returns true even if an error happens. echo "Test successfully done.\n"; ?> --EXPECTF-- -Warning: sqlsrv_close() expects parameter 1 to be resource, boolean given in %Ssqlsrv_errors.php on line %x +Warning: sqlsrv_close() expects parameter 1 to be resource, bool%S given in %Ssqlsrv_errors.php on line %x Array ( [0] => Array @@ -153,7 +153,7 @@ Array ) -Warning: sqlsrv_free_stmt() expects parameter 1 to be resource, integer given in %Ssqlsrv_errors.php on line %x +Warning: sqlsrv_free_stmt() expects parameter 1 to be resource, int%S given in %Ssqlsrv_errors.php on line %x Array ( [0] => Array @@ -172,7 +172,7 @@ Warning: sqlsrv_close(): supplied resource is not a valid ss_sqlsrv_conn resourc Warning: sqlsrv_close() expects parameter 1 to be resource, null given in %Ssqlsrv_errors.php on line %x -Warning: sqlsrv_close() expects parameter 1 to be resource, integer given in %Ssqlsrv_errors.php on line %x +Warning: sqlsrv_close() expects parameter 1 to be resource, int%S given in %Ssqlsrv_errors.php on line %x Array ( [0] => Array diff --git a/test/functional/sqlsrv/test_conn_execute.phpt b/test/functional/sqlsrv/test_conn_execute.phpt index edd979984..55731ef1d 100644 --- a/test/functional/sqlsrv/test_conn_execute.phpt +++ b/test/functional/sqlsrv/test_conn_execute.phpt @@ -1,24 +1,24 @@ ---TEST-- -crash caused by a statement being orphaned when an error occurred during sqlsrv_conn_execute. ---SKIPIF-- - ---FILE-- - ---EXPECTREGEX-- -Warning: sqlsrv_fetch_array\(\) expects parameter 1 to be resource, boolean given in .+(\/|\\)test_conn_execute\.php on line 11 -Test successful +--TEST-- +crash caused by a statement being orphaned when an error occurred during sqlsrv_conn_execute. +--SKIPIF-- + +--FILE-- + +--EXPECTREGEX-- +Warning: sqlsrv_fetch_array\(\) expects parameter 1 to be resource, bool(ean){0,1} given in .+(\/|\\)test_conn_execute\.php on line 11 +Test successful diff --git a/test/functional/sqlsrv/test_non_alpha_password.phpt b/test/functional/sqlsrv/test_non_alpha_password.phpt index ffd138c91..a288a1d4c 100644 --- a/test/functional/sqlsrv/test_non_alpha_password.phpt +++ b/test/functional/sqlsrv/test_non_alpha_password.phpt @@ -1,61 +1,61 @@ ---TEST-- -password with non alphanumeric characters ---DESCRIPTION-- -The first three cases should have no problem connecting. Only the last case fails because the -right curly brace should be escaped with another right brace. -In Azure for this test to pass do not specify any particular database when connecting ---SKIPIF-- - ---FILE-- - "test_password", "pwd" => "! ;4triou" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password2", "pwd" => "!}} ;4triou" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}}" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}" )); -if ($conn) -{ - echo( "Shouldn't have connected" ); -} -$errors = sqlsrv_errors(); -echo $errors[0]["message"]; -sqlsrv_close( $conn ); - -print "Test successful"; -?> ---EXPECTREGEX-- -An unescaped right brace \(}\) was found in either the user name or password. All right braces must be escaped with another right brace \(}}\)\. -Warning: sqlsrv_close\(\) expects parameter 1 to be resource, boolean given in .+(\/|\\)test_non_alpha_password\.php on line 45 -Test successful +--TEST-- +password with non alphanumeric characters +--DESCRIPTION-- +The first three cases should have no problem connecting. Only the last case fails because the +right curly brace should be escaped with another right brace. +In Azure for this test to pass do not specify any particular database when connecting +--SKIPIF-- + +--FILE-- + "test_password", "pwd" => "! ;4triou" )); +if (!$conn) +{ + $errors = sqlsrv_errors(); + echo( $errors[0]["message"]); +} +sqlsrv_close( $conn ); + +$conn = toConnect(array( "UID" => "test_password2", "pwd" => "!}} ;4triou" )); +if (!$conn) +{ + $errors = sqlsrv_errors(); + echo( $errors[0]["message"]); +} +sqlsrv_close( $conn ); + +$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}}" )); +if (!$conn) +{ + $errors = sqlsrv_errors(); + echo( $errors[0]["message"]); +} +sqlsrv_close( $conn ); + +$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}" )); +if ($conn) +{ + echo( "Shouldn't have connected" ); +} +$errors = sqlsrv_errors(); +echo $errors[0]["message"]; +sqlsrv_close( $conn ); + +print "Test successful"; +?> +--EXPECTREGEX-- +An unescaped right brace \(}\) was found in either the user name or password. All right braces must be escaped with another right brace \(}}\)\. +Warning: sqlsrv_close\(\) expects parameter 1 to be resource, bool(ean){0,1} given in .+(\/|\\)test_non_alpha_password\.php on line 45 +Test successful From 7357a1673f79b79c072483179dd70f34d9a0e928 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 26 Jul 2018 16:08:58 -0700 Subject: [PATCH 036/249] One more fix --- test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc | 4 ---- test/functional/sqlsrv/skipif_version_less_than_2k14.inc | 4 ---- 2 files changed, 8 deletions(-) diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc index 9f37d93c0..7db45126b 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc @@ -28,10 +28,6 @@ if (!$is_win) { die("skip Unsupported ODBC driver version"); } } -} else { - if ($msodbcsql_maj < 17) { - die("skip Unsupported ODBC driver version"); - } } // Get SQL Server Version diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc index d70e4085e..0ea10964c 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc @@ -30,10 +30,6 @@ if (!$is_win) { die("skip Unsupported ODBC driver version"); } } -} else { - if ($msodbcsql_maj < 17) { - die("skip Unsupported ODBC driver version"); - } } // Get SQL Server version From adf86f17aed3c32ceaa220420be29900a3a63401 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 31 Jul 2018 13:16:32 -0700 Subject: [PATCH 037/249] Initialising strings with nulls --- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 6 +++--- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/shared/FormattedPrint.cpp | 10 +++++----- source/shared/core_conn.cpp | 12 ++++++------ source/shared/core_results.cpp | 6 +++--- source/shared/core_stmt.cpp | 10 +++++----- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 2 +- source/shared/globalization.h | 2 +- source/sqlsrv/util.cpp | 2 +- 11 files changed, 28 insertions(+), 28 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 330205565..ef2e75831 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -463,7 +463,7 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ driver_dbh->set_func( __FUNCTION__ ); \ int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ - char func[length+1]; \ + char func[length+1] = { '\0' }; \ strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ strcat_s( func, length+1, ": entering" ); \ LOG( SEV_NOTICE, func ); \ diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index d41dde3b7..2e64e22ae 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -347,7 +347,7 @@ void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ driver_stmt->set_func( __FUNCTION__ ); \ int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ - char func[length+1]; \ + char func[length+1] = { '\0' }; \ strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ strcat_s( func, length+1, ": entering" ); \ LOG( SEV_NOTICE, func ); \ @@ -991,7 +991,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno add_assoc_long( return_value, "flags", 0 ); // get the name of the data type - char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ]; + char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ] = { '\0' }; SQLSMALLINT out_buff_len; SQLLEN not_used; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, @@ -1017,7 +1017,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } // add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec - char table_name[ SQL_SERVER_IDENT_SIZE_MAX ]; + char table_name[ SQL_SERVER_IDENT_SIZE_MAX ] = { '\0' }; SQLLEN field_type_num; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX, &out_buff_len, &field_type_num TSRMLS_CC ); diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index a05e75471..2e1c040a6 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -37,7 +37,7 @@ const int WARNING_MIN_LENGTH = static_cast( strlen( WARNING_TEMPLATE // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ]; +char log_msg[ LOG_MSG_SIZE ] = { '\0' }; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index 1b48c0e92..9c107f17c 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -254,7 +254,7 @@ errno_t mplat_wctomb_s( void _CFLTCVT( double * dbl, char * buf, int bufSize, char fmt, int precision, int caps, _locale_t loc = NULL ) { const size_t local_bufsize = 8; - char local_fmt[local_bufsize]; + char local_fmt[local_bufsize] = { '\0' }; if ( 0 != caps ) { @@ -387,7 +387,7 @@ int FormattedPrintA( IFormattedPrintOutput * output, const char *format, v textlen is in multibyte or wide chars if _UNICODE */ union { char sz[BUFFERSIZE]; - } buffer; + } buffer = { '\0' }; WCHAR wchar; /* temp wchar_t */ int buffersize; /* size of text.sz (used only for the call to _cfltcvt) */ int bufferiswide=0; /* non-zero = buffer contains wide chars already */ @@ -905,7 +905,7 @@ int FormattedPrintA( IFormattedPrintOutput * output, const char *format, v WCHAR *p; int retval, count; errno_t e = 0; - char L_buffer[MB_LEN_MAX+1]; + char L_buffer[MB_LEN_MAX+1] = { '\0' }; p = text.wz; count = textlen; @@ -1221,7 +1221,7 @@ static DWORD FormatMessageToBufferA( const char * format, char * buffer, DWORD b DWORD bufsize = std::min(bufferWCharSize, (DWORD)64000); DWORD msg_pos = 0; const DWORD fmtsize = 32; - char fmt[fmtsize]; + char fmt[fmtsize] = { '\0' }; DWORD fmt_pos; char fmt_ch; @@ -1429,7 +1429,7 @@ DWORD FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD d *((char**)lpBuffer) = NULL; const DWORD max_size = 64000; - char local_buf[max_size]; + char local_buf[max_size] = { '\0' }; chars_printed = FormatMessageToBufferA( reinterpret_cast(lpSource), local_buf, max_size, args ); if ( 0 < chars_printed ) { diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index b094e110b..3b65daf57 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -120,7 +120,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont // Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section. #ifndef _WIN32 - char pooling_string[ 128 ] = {0}; + char pooling_string[ 128 ] = { '\0' }; SQLGetPrivateProfileString( "ODBC", "Pooling", "0", pooling_string, sizeof( pooling_string ), "ODBCINST.INI" ); if ( pooling_string[ 0 ] == '1' || toupper( pooling_string[ 0 ] ) == 'Y' || @@ -310,7 +310,7 @@ bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ if( SQL_SUCCEEDED( rc ) ) return false; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = { 0 }; + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = { L'\0' }; SQLSMALLINT len; SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); @@ -327,7 +327,7 @@ bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version ) { #ifndef _WIN32 - char szBuf[DEFAULT_CONN_STR_LEN+1]; // use a large enough buffer size + char szBuf[DEFAULT_CONN_STR_LEN+1] = { '\0' }; // use a large enough buffer size WORD cbBufMax = DEFAULT_CONN_STR_LEN; WORD cbBufOut; char *pszBuf = szBuf; @@ -919,15 +919,15 @@ const char* get_processor_arch( void ) void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) { SQLSMALLINT info_len; - char p[ INFO_BUFFER_LEN ]; + char p[ INFO_BUFFER_LEN ] = { '\0' }; core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); errno = 0; - char version_major_str[ 3 ]; + char version_major_str[ 3 ] = { '\0' }; SERVER_VERSION version_major; memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); - version_major_str[ 2 ] = '\0'; + version_major_str[ 2 ] = { '\0' }; version_major = static_cast( atoi( version_major_str )); CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION ) diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 62a757d86..aa19940f4 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -341,8 +341,8 @@ struct row_dtor_closure { sqlsrv_error* odbc_get_diag_rec( _In_ sqlsrv_stmt* odbc, _In_ SQLSMALLINT record_number ) { - SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ]; - SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ]; + SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ] = { L'\0' }; + SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = { L'\0' }; SQLINTEGER native_code; SQLSMALLINT wnative_message_len = 0; @@ -1525,7 +1525,7 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in sqlsrv_malloc_auto_ptr buffer; buffer = static_cast( sqlsrv_malloc( INITIAL_LOB_FIELD_LEN + extra + sizeof( SQLULEN ))); SQLRETURN r = SQL_SUCCESS; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ]; + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = { '\0' }; SQLLEN last_field_len = 0; bool full_length_returned = false; diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 6d4d4f613..9d72080fd 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1236,7 +1236,7 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout int lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[ 32 ]; + char lock_timeout_sql[ 32 ] = { '\0' }; int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout ); SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), @@ -1304,7 +1304,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } // read the data from the stream, send it via SQLPutData and track how much we've sent. else { - char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; + char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ] = { '\0' }; std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character std::size_t read = php_stream_read( param_stream, buffer, buffer_size ); @@ -1325,7 +1325,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a // expansion of 2x the UTF-8 size. - SQLWCHAR wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ]; + SQLWCHAR wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ] = { L'\0' }; int wbuffer_size = static_cast( sizeof( wbuffer ) / sizeof( SQLWCHAR )); DWORD last_error_code = ERROR_SUCCESS; // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate @@ -1631,7 +1631,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // convert it to a DateTime object and return the created object case SQLSRV_PHPTYPE_DATETIME: { - char field_value_temp[ MAX_DATETIME_STRING_LEN ]; + char field_value_temp[ MAX_DATETIME_STRING_LEN ] = { '\0' }; zval params[1]; zval field_value_temp_z; zval function_z; @@ -2258,7 +2258,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind if( r == SQL_SUCCESS_WITH_INFO ) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { 0 }; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { L'\0' }; SQLSMALLINT len = 0; stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 32780a5b2..3d2f35ea3 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -106,7 +106,7 @@ size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) // if it's not a binary encoded field if( r == SQL_SUCCESS_WITH_INFO ) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { 0 }; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { L'\0' }; SQLSMALLINT len = 0; ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index d8b7b2445..5c0566bf0 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -28,7 +28,7 @@ log_callback g_driver_log; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; // buffer used to hold a formatted log message prior to actually logging it. -char last_err_msg[ 2048 ]; // 2k to hold the error messages +char last_err_msg[ 2048 ] = { '\0' }; // 2k to hold the error messages // routine used by utf16_string_from_mbcs_string unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, diff --git a/source/shared/globalization.h b/source/shared/globalization.h index 88e8d1a40..f21bbd8cb 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -362,7 +362,7 @@ class EncodingConverter { // Use fixed size buffer iteratively to determine final required length const size_t CCH_FIXED_SIZE = 256; - char fixed_buf[ CCH_FIXED_SIZE*sizeof(DestType) ]; + char fixed_buf[ CCH_FIXED_SIZE*sizeof(DestType) ] = { '\0' }; iconv_buffer dest( &fixed_buf[0], CCH_FIXED_SIZE ); diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index bc26658c0..ebd2c182b 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -28,7 +28,7 @@ unsigned int current_log_subsystem = LOG_UTIL; // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ]; +char log_msg[ LOG_MSG_SIZE ] = { '\0' }; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; From a664a5fcf919f67cb70f552bf87ccda7c9bfbee3 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 31 Jul 2018 14:58:21 -0700 Subject: [PATCH 038/249] Removed some spaces --- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 6 +++--- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/shared/FormattedPrint.cpp | 10 +++++----- source/shared/core_conn.cpp | 12 ++++++------ source/shared/core_results.cpp | 6 +++--- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 10 +++++----- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 6 +++--- source/shared/globalization.h | 2 +- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/util.cpp | 2 +- 13 files changed, 32 insertions(+), 32 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index ef2e75831..878f418d9 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -463,7 +463,7 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ driver_dbh->set_func( __FUNCTION__ ); \ int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ - char func[length+1] = { '\0' }; \ + char func[length+1] = {'\0'}; \ strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ strcat_s( func, length+1, ": entering" ); \ LOG( SEV_NOTICE, func ); \ diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 2e64e22ae..48646a29e 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -347,7 +347,7 @@ void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ driver_stmt->set_func( __FUNCTION__ ); \ int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ - char func[length+1] = { '\0' }; \ + char func[length+1] = {'\0'}; \ strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ strcat_s( func, length+1, ": entering" ); \ LOG( SEV_NOTICE, func ); \ @@ -991,7 +991,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno add_assoc_long( return_value, "flags", 0 ); // get the name of the data type - char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ] = { '\0' }; + char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ] = {'\0'}; SQLSMALLINT out_buff_len; SQLLEN not_used; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, @@ -1017,7 +1017,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } // add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec - char table_name[ SQL_SERVER_IDENT_SIZE_MAX ] = { '\0' }; + char table_name[ SQL_SERVER_IDENT_SIZE_MAX ] = {'\0'}; SQLLEN field_type_num; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX, &out_buff_len, &field_type_num TSRMLS_CC ); diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 2e1c040a6..c59ae022f 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -37,7 +37,7 @@ const int WARNING_MIN_LENGTH = static_cast( strlen( WARNING_TEMPLATE // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ] = { '\0' }; +char log_msg[ LOG_MSG_SIZE ] = {'\0'}; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index 9c107f17c..1de33d0ef 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -254,7 +254,7 @@ errno_t mplat_wctomb_s( void _CFLTCVT( double * dbl, char * buf, int bufSize, char fmt, int precision, int caps, _locale_t loc = NULL ) { const size_t local_bufsize = 8; - char local_fmt[local_bufsize] = { '\0' }; + char local_fmt[local_bufsize] = {'\0'}; if ( 0 != caps ) { @@ -387,7 +387,7 @@ int FormattedPrintA( IFormattedPrintOutput * output, const char *format, v textlen is in multibyte or wide chars if _UNICODE */ union { char sz[BUFFERSIZE]; - } buffer = { '\0' }; + } buffer = {'\0'}; WCHAR wchar; /* temp wchar_t */ int buffersize; /* size of text.sz (used only for the call to _cfltcvt) */ int bufferiswide=0; /* non-zero = buffer contains wide chars already */ @@ -905,7 +905,7 @@ int FormattedPrintA( IFormattedPrintOutput * output, const char *format, v WCHAR *p; int retval, count; errno_t e = 0; - char L_buffer[MB_LEN_MAX+1] = { '\0' }; + char L_buffer[MB_LEN_MAX+1] = {'\0'}; p = text.wz; count = textlen; @@ -1221,7 +1221,7 @@ static DWORD FormatMessageToBufferA( const char * format, char * buffer, DWORD b DWORD bufsize = std::min(bufferWCharSize, (DWORD)64000); DWORD msg_pos = 0; const DWORD fmtsize = 32; - char fmt[fmtsize] = { '\0' }; + char fmt[fmtsize] = {'\0'}; DWORD fmt_pos; char fmt_ch; @@ -1429,7 +1429,7 @@ DWORD FormatMessageA(DWORD dwFlags, LPCVOID lpSource, DWORD dwMessageId, DWORD d *((char**)lpBuffer) = NULL; const DWORD max_size = 64000; - char local_buf[max_size] = { '\0' }; + char local_buf[max_size] = {'\0'}; chars_printed = FormatMessageToBufferA( reinterpret_cast(lpSource), local_buf, max_size, args ); if ( 0 < chars_printed ) { diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 3b65daf57..f9ac6e532 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -120,7 +120,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont // Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section. #ifndef _WIN32 - char pooling_string[ 128 ] = { '\0' }; + char pooling_string[ 128 ] = {'\0'}; SQLGetPrivateProfileString( "ODBC", "Pooling", "0", pooling_string, sizeof( pooling_string ), "ODBCINST.INI" ); if ( pooling_string[ 0 ] == '1' || toupper( pooling_string[ 0 ] ) == 'Y' || @@ -310,7 +310,7 @@ bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ if( SQL_SUCCEEDED( rc ) ) return false; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = { L'\0' }; + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = {'\0'}; SQLSMALLINT len; SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); @@ -327,7 +327,7 @@ bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ bool core_search_odbc_driver_unix( _In_ DRIVER_VERSION driver_version ) { #ifndef _WIN32 - char szBuf[DEFAULT_CONN_STR_LEN+1] = { '\0' }; // use a large enough buffer size + char szBuf[DEFAULT_CONN_STR_LEN+1] = {'\0'}; // use a large enough buffer size WORD cbBufMax = DEFAULT_CONN_STR_LEN; WORD cbBufOut; char *pszBuf = szBuf; @@ -919,15 +919,15 @@ const char* get_processor_arch( void ) void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) { SQLSMALLINT info_len; - char p[ INFO_BUFFER_LEN ] = { '\0' }; + char p[ INFO_BUFFER_LEN ] = {'\0'}; core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); errno = 0; - char version_major_str[ 3 ] = { '\0' }; + char version_major_str[ 3 ] = {'\0'}; SERVER_VERSION version_major; memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); - version_major_str[ 2 ] = { '\0' }; + version_major_str[ 2 ] = {'\0'}; version_major = static_cast( atoi( version_major_str )); CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION ) diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index aa19940f4..ec0105859 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -341,8 +341,8 @@ struct row_dtor_closure { sqlsrv_error* odbc_get_diag_rec( _In_ sqlsrv_stmt* odbc, _In_ SQLSMALLINT record_number ) { - SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ] = { L'\0' }; - SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = { L'\0' }; + SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ] = {L'\0'}; + SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = {L'\0'}; SQLINTEGER native_code; SQLSMALLINT wnative_message_len = 0; @@ -1525,7 +1525,7 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in sqlsrv_malloc_auto_ptr buffer; buffer = static_cast( sqlsrv_malloc( INITIAL_LOB_FIELD_LEN + extra + sizeof( SQLULEN ))); SQLRETURN r = SQL_SUCCESS; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = { '\0' }; + SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = {'\0'}; SQLLEN last_field_len = 0; bool full_length_returned = false; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 5530a5540..d75d8c689 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1889,7 +1889,7 @@ namespace core { // and return a more helpful message prepended to the ODBC errors if that error occurs if( !SQL_SUCCEEDED( r )) { - SQLCHAR err_msg[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = { '\0' }; + SQLCHAR err_msg[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = {'\0'}; SQLSMALLINT len = 0; SQLRETURN rtemp = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 9d72080fd..ef07469bb 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1236,7 +1236,7 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout int lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[ 32 ] = { '\0' }; + char lock_timeout_sql[ 32 ] = {'\0'}; int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout ); SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), @@ -1304,7 +1304,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } // read the data from the stream, send it via SQLPutData and track how much we've sent. else { - char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ] = { '\0' }; + char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ] = {'\0'}; std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character std::size_t read = php_stream_read( param_stream, buffer, buffer_size ); @@ -1325,7 +1325,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a // expansion of 2x the UTF-8 size. - SQLWCHAR wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ] = { L'\0' }; + SQLWCHAR wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ] = {L'\0'}; int wbuffer_size = static_cast( sizeof( wbuffer ) / sizeof( SQLWCHAR )); DWORD last_error_code = ERROR_SUCCESS; // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate @@ -1631,7 +1631,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // convert it to a DateTime object and return the created object case SQLSRV_PHPTYPE_DATETIME: { - char field_value_temp[ MAX_DATETIME_STRING_LEN ] = { '\0' }; + char field_value_temp[ MAX_DATETIME_STRING_LEN ] = {'\0'}; zval params[1]; zval field_value_temp_z; zval function_z; @@ -2258,7 +2258,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind if( r == SQL_SUCCESS_WITH_INFO ) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { L'\0' }; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; SQLSMALLINT len = 0; stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 3d2f35ea3..25ec976a7 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -106,7 +106,7 @@ size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) // if it's not a binary encoded field if( r == SQL_SUCCESS_WITH_INFO ) { - SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = { L'\0' }; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; SQLSMALLINT len = 0; ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 5c0566bf0..02259261c 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -28,7 +28,7 @@ log_callback g_driver_log; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; // buffer used to hold a formatted log message prior to actually logging it. -char last_err_msg[ 2048 ] = { '\0' }; // 2k to hold the error messages +char last_err_msg[ 2048 ] = {'\0'}; // 2k to hold the error messages // routine used by utf16_string_from_mbcs_string unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, @@ -219,8 +219,8 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu SQLRETURN r = SQL_SUCCESS; SQLSMALLINT wmessage_len = 0; - SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ] = { L'\0' }; - SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = { L'\0' }; + SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ] = {L'\0'}; + SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = {L'\0'}; SQLSRV_ENCODING enc = ctx.encoding(); switch( h_type ) { diff --git a/source/shared/globalization.h b/source/shared/globalization.h index f21bbd8cb..e2830e175 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -362,7 +362,7 @@ class EncodingConverter { // Use fixed size buffer iteratively to determine final required length const size_t CCH_FIXED_SIZE = 256; - char fixed_buf[ CCH_FIXED_SIZE*sizeof(DestType) ] = { '\0' }; + char fixed_buf[ CCH_FIXED_SIZE*sizeof(DestType) ] = {'\0'}; iconv_buffer dest( &fixed_buf[0], CCH_FIXED_SIZE ); diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index ac334cb9b..4f780bbf1 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1776,7 +1776,7 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ SQLLEN field_name_len = 0; SQLSMALLINT field_name_len_w = 0; - SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2 ] = { L'\0' }; + SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2 ] = {L'\0'}; sqlsrv_malloc_auto_ptr field_name; sqlsrv_malloc_auto_ptr field_names; field_names = static_cast( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field_name ))); diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index ebd2c182b..c431daeff 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -28,7 +28,7 @@ unsigned int current_log_subsystem = LOG_UTIL; // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ] = { '\0' }; +char log_msg[ LOG_MSG_SIZE ] = {'\0'}; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; From 41a7caf1c176153ef7c8d20345ca52917d0e7276 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 31 Jul 2018 17:22:56 -0700 Subject: [PATCH 039/249] Made array index spacing consistent --- source/pdo_sqlsrv/pdo_dbh.cpp | 38 ++++---- source/pdo_sqlsrv/pdo_init.cpp | 6 +- source/pdo_sqlsrv/pdo_parser.cpp | 48 ++++----- source/pdo_sqlsrv/pdo_stmt.cpp | 18 ++-- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/shared/core_conn.cpp | 28 +++--- source/shared/core_results.cpp | 162 +++++++++++++++---------------- source/shared/core_sqlsrv.h | 12 +-- source/shared/core_stmt.cpp | 30 +++--- source/shared/core_util.cpp | 8 +- source/shared/globalization.h | 2 +- source/sqlsrv/conn.cpp | 22 ++--- source/sqlsrv/init.cpp | 6 +- source/sqlsrv/stmt.cpp | 10 +- source/sqlsrv/util.cpp | 2 +- 15 files changed, 197 insertions(+), 197 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 878f418d9..be2c00a61 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -1270,7 +1270,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, try { - char last_insert_id_query[ LAST_INSERT_ID_QUERY_MAX_LEN ] = {'\0'}; + char last_insert_id_query[LAST_INSERT_ID_QUERY_MAX_LEN] = {'\0'}; if( name == NULL ) { strcpy_s( last_insert_id_query, sizeof( last_insert_id_query ), LAST_INSERT_ID_QUERY ); } @@ -1412,13 +1412,13 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const if ( encoding == SQLSRV_ENCODING_BINARY ) { // convert from char* to hex digits using os std::basic_ostringstream os; - for ( size_t index = 0; index < unquoted_len && unquoted[ index ] != '\0'; ++index ) { + for ( size_t index = 0; index < unquoted_len && unquoted[index] != '\0'; ++index ) { // if unquoted is < 0 or > 255, that means this is a non-ascii character. Translation from non-ascii to binary is not supported. // return an empty terminated string for now - if (( int )unquoted[ index ] < 0 || ( int )unquoted[ index ] > 255) { + if (( int )unquoted[index] < 0 || ( int )unquoted[index] > 255) { *quoted_len = 0; *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); - ( *quoted )[ 0 ] = '\0'; + ( *quoted )[0] = '\0'; return 1; } // when an int is < 16 and is appended to os, its hex representation which starts @@ -1427,7 +1427,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const if (( int )unquoted[index] < 16 ) { os << '0'; } - os << std::hex << ( int )unquoted[ index ]; + os << std::hex << ( int )unquoted[index]; } std::basic_string str_hex = os.str(); // each character is represented by 2 digits of hex @@ -1439,13 +1439,13 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const *quoted = reinterpret_cast( sqlsrv_malloc( *quoted_len, sizeof( char ), 1 )); unsigned int out_current = 0; // insert '0x' - ( *quoted )[ out_current++ ] = '0'; - ( *quoted )[ out_current++ ] = 'x'; - for ( size_t index = 0; index < unquoted_str_len && unquoted_str[ index ] != '\0'; ++index ) { - ( *quoted )[ out_current++ ] = unquoted_str[ index ]; + ( *quoted )[out_current++] = '0'; + ( *quoted )[out_current++] = 'x'; + for ( size_t index = 0; index < unquoted_str_len && unquoted_str[index] != '\0'; ++index ) { + ( *quoted )[out_current++] = unquoted_str[index]; } // null terminator - ( *quoted )[ out_current ] = '\0'; + ( *quoted )[out_current] = '\0'; sqlsrv_free( unquoted_str ); return 1; } @@ -1457,7 +1457,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const quotes_needed = 3; } for ( size_t index = 0; index < unquoted_len; ++index ) { - if ( unquoted[ index ] == '\'' ) { + if ( unquoted[index] == '\'' ) { ++quotes_needed; } } @@ -1468,24 +1468,24 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const // insert N if the encoding is UTF8 if ( encoding == SQLSRV_ENCODING_UTF8 ) { - ( *quoted )[ out_current++ ] = 'N'; + ( *quoted )[out_current++] = 'N'; } // insert initial quote - ( *quoted )[ out_current++ ] = '\''; + ( *quoted )[out_current++] = '\''; for ( size_t index = 0; index < unquoted_len; ++index ) { - if ( unquoted[ index ] == '\'' ) { - ( *quoted )[ out_current++ ] = '\''; - ( *quoted )[ out_current++ ] = '\''; + if ( unquoted[index] == '\'' ) { + ( *quoted )[out_current++] = '\''; + ( *quoted )[out_current++] = '\''; } else { - ( *quoted )[ out_current++ ] = unquoted[ index ]; + ( *quoted )[out_current++] = unquoted[index]; } } // trailing quote and null terminator - ( *quoted )[ out_current++ ] = '\''; - ( *quoted )[ out_current ] = '\0'; + ( *quoted )[out_current++] = '\''; + ( *quoted )[out_current] = '\0'; return 1; } diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 65e7a0786..f98799b89 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -132,10 +132,10 @@ PHP_MINIT_FUNCTION(pdo_sqlsrv) g_pdo_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); ::zend_hash_init( g_pdo_errors_ht, 50, NULL, pdo_error_dtor /*pDestructor*/, 1 ); - for( int i = 0; PDO_ERRORS[ i ].error_code != -1; ++i ) { + for( int i = 0; PDO_ERRORS[i].error_code != -1; ++i ) { - void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[ i ].error_code, - &( PDO_ERRORS[ i ].sqlsrv_error ), sizeof( PDO_ERRORS[ i ].sqlsrv_error ) ); + void* zr = ::zend_hash_index_update_mem( g_pdo_errors_ht, PDO_ERRORS[i].error_code, + &( PDO_ERRORS[i].sqlsrv_error ), sizeof( PDO_ERRORS[i].sqlsrv_error ) ); if( zr == NULL ) { LOG( SEV_ERROR, "Failed to insert data into PDO errors hashtable." ); diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index baafeed73..9710e5d2a 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -108,7 +108,7 @@ bool string_parser::discard_white_spaces() return false; } - while( this->is_white_space( this->orig_str[ pos ] )) { + while( this->is_white_space( this->orig_str[pos] )) { if( !next() ) return false; @@ -148,13 +148,13 @@ void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Ino { int new_len = discard_trailing_white_spaces( key, key_len ); - for( int i=0; PDO_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) + for( int i=0; PDO_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) { // discard the null terminator. - if( new_len == ( PDO_CONN_OPTS[ i ].sqlsrv_len - 1 ) && !strncasecmp( key, PDO_CONN_OPTS[ i ].sqlsrv_name, new_len )) { + if( new_len == ( PDO_CONN_OPTS[i].sqlsrv_len - 1 ) && !strncasecmp( key, PDO_CONN_OPTS[i].sqlsrv_name, new_len )) { - this->current_key = PDO_CONN_OPTS[ i ].conn_option_key; - this->current_key_name = PDO_CONN_OPTS[ i ].sqlsrv_name; + this->current_key = PDO_CONN_OPTS[i].conn_option_key; + this->current_key_name = PDO_CONN_OPTS[i].sqlsrv_name; return; } } @@ -164,7 +164,7 @@ void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Ino key_name = static_cast( sqlsrv_malloc( new_len + 1 )); memcpy_s( key_name, new_len + 1 ,key, new_len ); - key_name[ new_len ] = '\0'; + key_name[new_len] = '\0'; THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast( key_name ) ); } @@ -232,7 +232,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) start_pos = this->pos; // read the key name - while( this->orig_str[ pos ] != '=' ) { + while( this->orig_str[pos] != '=' ) { if( !next() ) { @@ -240,7 +240,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } } - this->validate_key( &( this->orig_str[ start_pos ] ), ( pos - start_pos ) TSRMLS_CC ); + this->validate_key( &( this->orig_str[start_pos] ), ( pos - start_pos ) TSRMLS_CC ); state = Value; @@ -249,13 +249,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) case Value: { - SQLSRV_ASSERT(( this->orig_str[ pos ] == '=' ), "conn_string_parser:: parse_conn_string: " + SQLSRV_ASSERT(( this->orig_str[pos] == '=' ), "conn_string_parser:: parse_conn_string: " "Equal was expected" ); next(); // skip "=" // if EOS encountered after 0 or more spaces OR semi-colon encountered. - if( !discard_white_spaces() || this->orig_str[ pos ] == ';' ) { + if( !discard_white_spaces() || this->orig_str[pos] == ';' ) { add_key_value_pair( NULL, 0 TSRMLS_CC ); @@ -265,13 +265,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } else { - // this->orig_str[ pos ] == ';' + // this->orig_str[pos] == ';' state = NextKeyValuePair; } } // if LCB - else if( this->orig_str[ pos ] == '{' ) { + else if( this->orig_str[pos] == '{' ) { start_pos = this->pos; // starting character is LCB state = ValueContent1; @@ -289,7 +289,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) case ValueContent1: { - while ( this->orig_str[ pos ] != '}' ) { + while ( this->orig_str[pos] != '}' ) { if ( ! next() ) { @@ -305,7 +305,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) case ValueContent2: { - while( this->orig_str[ pos ] != ';' ) { + while( this->orig_str[pos] != ';' ) { if( ! next() ) { @@ -313,13 +313,13 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } } - if( !this->is_eos() && this->orig_str[ pos ] == ';' ) { + if( !this->is_eos() && this->orig_str[pos] == ';' ) { // semi-colon encountered, so go to next key-value pair state = NextKeyValuePair; } - add_key_value_pair( &( this->orig_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos TSRMLS_CC ); SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )), "conn_string_parser::parse_conn_string: Invalid state encountered " ); @@ -334,14 +334,14 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) if( !next() ) { // EOS - add_key_value_pair( &( this->orig_str[ start_pos ] ), this->pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos TSRMLS_CC ); break; } SQLSRV_ASSERT( !this->is_eos(), "conn_string_parser::parse_conn_string: Unexpected EOS encountered" ); // if second RCB encountered than go back to ValueContent1 - if( this->orig_str[ pos ] == '}' ) { + if( this->orig_str[pos] == '}' ) { if( !next() ) { @@ -356,20 +356,20 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) int end_pos = this->pos; // discard any trailing white-spaces. - if( this->is_white_space( this->orig_str[ pos ] )) { + if( this->is_white_space( this->orig_str[pos] )) { if( ! this->discard_white_spaces() ) { //EOS - add_key_value_pair( &( this->orig_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos TSRMLS_CC ); break; } } // if semi-colon than go to next key-value pair - if ( this->orig_str[ pos ] == ';' ) { + if ( this->orig_str[pos] == ';' ) { - add_key_value_pair( &( this->orig_str[ start_pos ] ), end_pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos TSRMLS_CC ); state = NextKeyValuePair; break; } @@ -380,7 +380,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } case NextKeyValuePair: { - SQLSRV_ASSERT(( this->orig_str[ pos ] == ';' ), + SQLSRV_ASSERT(( this->orig_str[pos] == ';' ), "conn_string_parser::parse_conn_string: semi-colon was expected." ); // Call next() to skip the semi-colon. @@ -390,7 +390,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) break; } - if( this->orig_str[ pos ] == ';' ) { + if( this->orig_str[pos] == ';' ) { // a second semi-colon is error case. THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, this->pos ); diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 48646a29e..7f16faa70 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -651,13 +651,13 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta if (NULL== (bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, i))) && (NULL == (bind_data = reinterpret_cast(zend_hash_find_ptr(stmt->bound_columns, stmt->columns[i].name))))) { - driver_stmt->bound_column_param_types[ i ] = PDO_PARAM_ZVAL; + driver_stmt->bound_column_param_types[i] = PDO_PARAM_ZVAL; continue; } if( bind_data->param_type != PDO_PARAM_ZVAL ) { - driver_stmt->bound_column_param_types[ i ] = bind_data->param_type; + driver_stmt->bound_column_param_types[i] = bind_data->param_type; bind_data->param_type = PDO_PARAM_ZVAL; } } @@ -744,10 +744,10 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, // if a column is bound to a type different than the column type, figure out a way to convert it to the // type they want - if( stmt->bound_columns && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_ZVAL ) { + if( stmt->bound_columns && driver_stmt->bound_column_param_types[colno] != PDO_PARAM_ZVAL ) { sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type( driver_stmt, - driver_stmt->bound_column_param_types[ colno ] + driver_stmt->bound_column_param_types[colno] TSRMLS_CC ); pdo_bound_param_data* bind_data = NULL; @@ -764,8 +764,8 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, throw pdo::PDOException(); } - CHECK_CUSTOM_ERROR( driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_STR - && driver_stmt->bound_column_param_types[ colno ] != PDO_PARAM_LOB, driver_stmt, + CHECK_CUSTOM_ERROR( driver_stmt->bound_column_param_types[colno] != PDO_PARAM_STR + && driver_stmt->bound_column_param_types[colno] != PDO_PARAM_LOB, driver_stmt, PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, colno + 1 ) { throw pdo::PDOException(); @@ -991,7 +991,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno add_assoc_long( return_value, "flags", 0 ); // get the name of the data type - char field_type_name[ SQL_SERVER_IDENT_SIZE_MAX ] = {'\0'}; + char field_type_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'}; SQLSMALLINT out_buff_len; SQLLEN not_used; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, @@ -1017,13 +1017,13 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } // add the table name of the field. All the tests so far show this to always be "", but we adhere to the PDO spec - char table_name[ SQL_SERVER_IDENT_SIZE_MAX ] = {'\0'}; + char table_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'}; SQLLEN field_type_num; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX, &out_buff_len, &field_type_num TSRMLS_CC ); add_assoc_string( return_value, "table", table_name ); - if( stmt->columns && stmt->columns[ colno ].param_type == PDO_PARAM_ZVAL ) { + if( stmt->columns && stmt->columns[colno].param_type == PDO_PARAM_ZVAL ) { add_assoc_long( return_value, "pdo_type", pdo_type ); } diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index c59ae022f..5b7df6af2 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -37,7 +37,7 @@ const int WARNING_MIN_LENGTH = static_cast( strlen( WARNING_TEMPLATE // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ] = {'\0'}; +char log_msg[LOG_MSG_SIZE] = {'\0'}; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index f9ac6e532..54ec004f2 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -120,11 +120,11 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont // Instead, MSPHPSQL connection pooling is set according to the ODBCINST.INI file in [ODBC] section. #ifndef _WIN32 - char pooling_string[ 128 ] = {'\0'}; + char pooling_string[128] = {'\0'}; SQLGetPrivateProfileString( "ODBC", "Pooling", "0", pooling_string, sizeof( pooling_string ), "ODBCINST.INI" ); - if ( pooling_string[ 0 ] == '1' || toupper( pooling_string[ 0 ] ) == 'Y' || - ( toupper( pooling_string[ 0 ] ) == 'O' && toupper( pooling_string[ 1 ] ) == 'N' )) + if ( pooling_string[0] == '1' || toupper( pooling_string[0] ) == 'Y' || + ( toupper( pooling_string[0] ) == 'O' && toupper( pooling_string[1] ) == 'N' )) { henv = &henv_cp; is_pooled = true; @@ -310,7 +310,7 @@ bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN rc, _In_ if( SQL_SUCCEEDED( rc ) ) return false; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = {'\0'}; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'}; SQLSMALLINT len; SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); @@ -695,7 +695,7 @@ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t v { // if the value is already quoted, then only analyse the part inside the quotes and return it as // unquoted since we quote it when adding it to the connection string. - if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) { + if( value_len > 0 && value[0] == '{' && value[value_len - 1] == '}' ) { ++value; value_len -= 2; } @@ -736,11 +736,11 @@ namespace { connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ SQLULEN key, _In_ const connection_option conn_opts[] TSRMLS_DC ) { - for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) { + for( int opt_idx = 0; conn_opts[opt_idx].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) { - if( key == conn_opts[ opt_idx ].conn_option_key ) { + if( key == conn_opts[opt_idx].conn_option_key ) { - return &conn_opts[ opt_idx ]; + return &conn_opts[opt_idx]; } } @@ -919,15 +919,15 @@ const char* get_processor_arch( void ) void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) { SQLSMALLINT info_len; - char p[ INFO_BUFFER_LEN ] = {'\0'}; + char p[INFO_BUFFER_LEN] = {'\0'}; core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); errno = 0; - char version_major_str[ 3 ] = {'\0'}; + char version_major_str[3] = {'\0'}; SERVER_VERSION version_major; memcpy_s( version_major_str, sizeof( version_major_str ), p, 2 ); - version_major_str[ 2 ] = {'\0'}; + version_major_str[2] = {'\0'}; version_major = static_cast( atoi( version_major_str )); CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION ) @@ -1025,7 +1025,7 @@ void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_l // be escaped, such as a closing }. TSRMLS_C; - if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) { + if( val_len > 0 && val[0] == '{' && val[val_len - 1] == '}' ) { ++val; val_len -= 2; } @@ -1151,8 +1151,8 @@ size_t core_str_zval_is_true( _Inout_ zval* value_z ) // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8) size_t last_char = val_len - 1; - while( isspace(( unsigned char )value_in[ last_char ] )) { - value_in[ last_char ] = '\0'; + while( isspace(( unsigned char )value_in[last_char] )) { + value_in[last_char] = '\0'; val_len = last_char; --last_char; } diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index ec0105859..76f862e7a 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -341,8 +341,8 @@ struct row_dtor_closure { sqlsrv_error* odbc_get_diag_rec( _In_ sqlsrv_stmt* odbc, _In_ SQLSMALLINT record_number ) { - SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ] = {L'\0'}; - SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = {L'\0'}; + SQLWCHAR wsql_state[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; + SQLWCHAR wnative_message[SQL_MAX_ERROR_MESSAGE_LENGTH + 1] = {L'\0'}; SQLINTEGER native_code; SQLSMALLINT wnative_message_len = 0; @@ -459,29 +459,29 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm // set up the conversion matrix if this is the first time we're called if( conv_matrix.size() == 0 ) { - conv_matrix[ SQL_C_CHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::system_to_wide_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[ SQL_C_CHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::string_to_double; - conv_matrix[ SQL_C_CHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::string_to_long; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::wide_to_system_string; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::wstring_to_double; - conv_matrix[ SQL_C_WCHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::wstring_to_long; - conv_matrix[ SQL_C_BINARY ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[ SQL_C_BINARY ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::binary_to_system_string; - conv_matrix[ SQL_C_BINARY ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::binary_to_wide_string; - conv_matrix[ SQL_C_LONG ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::long_to_double; - conv_matrix[ SQL_C_LONG ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[ SQL_C_LONG ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[ SQL_C_LONG ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::long_to_system_string; - conv_matrix[ SQL_C_LONG ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::long_to_wide_string; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::double_to_system_string; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::double_to_long; - conv_matrix[ SQL_C_DOUBLE ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::double_to_wide_string; + conv_matrix[SQL_C_CHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[SQL_C_CHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::system_to_wide_string; + conv_matrix[SQL_C_CHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string; + conv_matrix[SQL_C_CHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::string_to_double; + conv_matrix[SQL_C_CHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::string_to_long; + conv_matrix[SQL_C_WCHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[SQL_C_WCHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string; + conv_matrix[SQL_C_WCHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::wide_to_system_string; + conv_matrix[SQL_C_WCHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::wstring_to_double; + conv_matrix[SQL_C_WCHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::wstring_to_long; + conv_matrix[SQL_C_BINARY][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_same_string; + conv_matrix[SQL_C_BINARY][SQL_C_CHAR] = &sqlsrv_buffered_result_set::binary_to_system_string; + conv_matrix[SQL_C_BINARY][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::binary_to_wide_string; + conv_matrix[SQL_C_LONG][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::long_to_double; + conv_matrix[SQL_C_LONG][SQL_C_LONG] = &sqlsrv_buffered_result_set::to_long; + conv_matrix[SQL_C_LONG][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_long; + conv_matrix[SQL_C_LONG][SQL_C_CHAR] = &sqlsrv_buffered_result_set::long_to_system_string; + conv_matrix[SQL_C_LONG][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::long_to_wide_string; + conv_matrix[SQL_C_DOUBLE][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::to_double; + conv_matrix[SQL_C_DOUBLE][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_double; + conv_matrix[SQL_C_DOUBLE][SQL_C_CHAR] = &sqlsrv_buffered_result_set::double_to_system_string; + conv_matrix[SQL_C_DOUBLE][SQL_C_LONG] = &sqlsrv_buffered_result_set::double_to_long; + conv_matrix[SQL_C_DOUBLE][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::double_to_wide_string; } SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : @@ -687,7 +687,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { out_buffer_length = &out_buffer_temp; - SQLPOINTER* lob_addr = reinterpret_cast( &row[ meta[i].offset ] ); + SQLPOINTER* lob_addr = reinterpret_cast( &row[meta[i].offset] ); *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); // a NULL pointer means NULL field if( *lob_addr == NULL ) { @@ -854,7 +854,7 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ return SQL_ERROR; } - return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length, + return (( this )->*( conv_matrix[meta[field_index].c_type][target_type] ))( field_index, buffer, buffer_length, out_buffer_length ); } @@ -965,9 +965,9 @@ SQLRETURN binary_to_string( _Inout_ SQLCHAR* field_data, _Inout_ SQLLEN& read_so // to get the number of hex digits we can copy SQLLEN to_copy_hex = to_copy / (2 * extra); for( SQLLEN i = 0; i < to_copy_hex; ++i ) { - *h = hex_chars[ (*b & 0xf0) >> 4 ]; + *h = hex_chars[(*b & 0xf0) >> 4]; h++; - *h = hex_chars[ (*b++ & 0x0f) ]; + *h = hex_chars[(*b++ & 0x0f)]; h++; } read_so_far += to_copy_hex; @@ -986,13 +986,13 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( _In_ SQLSMALLINT SQLCHAR* row = get_row(); SQLCHAR* field_data = NULL; - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); } else { - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ); } return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); @@ -1004,13 +1004,13 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( _In_ SQLSMALLINT fi SQLCHAR* row = get_row(); SQLCHAR* field_data = NULL; - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); } else { - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ); } return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error ); @@ -1020,12 +1020,12 @@ SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( _In_ SQLSMALLINT fi SQLRETURN sqlsrv_buffered_result_set::double_to_long( _In_ SQLSMALLINT field_index, _Inout_updates_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to long" ); SQLSRV_ASSERT( buffer_length >= sizeof(SQLLEN), "Buffer length must be able to find a long in " "sqlsrv_buffered_result_set::double_to_long" ); unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); LONG* long_data = reinterpret_cast( buffer ); if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) { @@ -1049,11 +1049,11 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_long( _In_ SQLSMALLINT field_ind SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" ); SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string" ); unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); SQLRETURN r = SQL_SUCCESS; #ifdef _WIN32 r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); @@ -1066,11 +1066,11 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( _In_ SQLSMALLINT SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" ); unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); SQLRETURN r = SQL_SUCCESS; #ifdef _WIN32 r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); @@ -1083,12 +1083,12 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( _In_ SQLSMALLINT fi SQLRETURN sqlsrv_buffered_result_set::long_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to long" ); SQLSRV_ASSERT( buffer_length >= sizeof(double), "Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long" ); unsigned char* row = get_row(); double* double_data = reinterpret_cast( buffer ); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); *double_data = static_cast( *long_data ); *out_buffer_length = sizeof( double ); @@ -1098,11 +1098,11 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_double( _In_ SQLSMALLINT field_ind SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to system string" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to system string" ); SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string" ); unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); SQLRETURN r = SQL_SUCCESS; #ifdef _WIN32 r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); @@ -1115,11 +1115,11 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( _In_ SQLSMALLINT fi SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" ); unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); SQLRETURN r = SQL_SUCCESS; #ifdef _WIN32 r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); @@ -1132,49 +1132,49 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( _In_ SQLSMALLINT fiel SQLRETURN sqlsrv_buffered_result_set::string_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to double" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_CHAR, "Invalid conversion from string to double" ); SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); unsigned char* row = get_row(); - char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); + return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); } SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" ); SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" ); unsigned char* row = get_row(); - SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); + SQLWCHAR* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); + return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); } SQLRETURN sqlsrv_buffered_result_set::string_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); unsigned char* row = get_row(); - char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); + return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); } SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" ); SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" ); unsigned char* row = get_row(); - SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); + SQLWCHAR* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error ); + return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); } SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, @@ -1189,15 +1189,15 @@ SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT fi SQLCHAR* field_data = NULL; SQLULEN field_len = 0; - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; + field_len = **reinterpret_cast( &row[meta[field_index].offset] ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) + read_so_far; } else { - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; + field_len = *reinterpret_cast( &row[meta[field_index].offset] ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ) + read_so_far; } // all fields will be treated as ODBC returns varchar(max) fields: @@ -1264,7 +1264,7 @@ SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT fi return SQL_ERROR; } - ((WCHAR*)buffer)[ to_copy ] = L'\0'; + ((WCHAR*)buffer)[to_copy] = L'\0'; read_so_far += to_copy; break; @@ -1288,7 +1288,7 @@ SQLRETURN sqlsrv_buffered_result_set::to_same_string( _In_ SQLSMALLINT field_ind // Set the amount of space necessary for null characters at the end of the data. SQLSMALLINT extra = 0; - switch( meta[ field_index ].c_type ) { + switch( meta[field_index].c_type ) { case SQL_C_WCHAR: extra = sizeof( SQLWCHAR ); break; @@ -1305,13 +1305,13 @@ SQLRETURN sqlsrv_buffered_result_set::to_same_string( _In_ SQLSMALLINT field_ind SQLCHAR* field_data = NULL; - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); } else { - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ); } // all fields will be treated as ODBC returns varchar(max) fields: @@ -1366,15 +1366,15 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( _In_ SQLSMALLINT fi if( read_so_far == 0 ) { - if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { + if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far; + field_len = **reinterpret_cast( &row[meta[field_index].offset] ); + field_data = *reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) + read_so_far; } else { - field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] ); - field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far; + field_len = *reinterpret_cast( &row[meta[field_index].offset] ); + field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ) + read_so_far; } if ( field_len == 0 ) { // empty string, no need for conversion @@ -1434,7 +1434,7 @@ SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( _In_ SQLSMALLINT fi } SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" ); OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" ); - ((SQLCHAR*) buffer)[ to_copy ] = '\0'; + ((SQLCHAR*) buffer)[to_copy] = '\0'; read_so_far += to_copy; return r; @@ -1450,11 +1450,11 @@ SQLRETURN sqlsrv_buffered_result_set::to_binary_string( _In_ SQLSMALLINT field_i SQLRETURN sqlsrv_buffered_result_set::to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to long" ); SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); memcpy_s( buffer, buffer_length, long_data, sizeof( LONG )); *out_buffer_length = sizeof( LONG ); @@ -1464,11 +1464,11 @@ SQLRETURN sqlsrv_buffered_result_set::to_long( _In_ SQLSMALLINT field_index, _Ou SQLRETURN sqlsrv_buffered_result_set::to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to double" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to double" ); SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ); + double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); memcpy_s( buffer, buffer_length, double_data, sizeof( double )); *out_buffer_length = sizeof( double ); @@ -1489,7 +1489,7 @@ void cache_row_dtor( _In_ zval* data ) if( result_set->col_meta_data(i).length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { - void* out_of_row_data = *reinterpret_cast( &row[ result_set->col_meta_data(i).offset ] ); + void* out_of_row_data = *reinterpret_cast( &row[result_set->col_meta_data(i).offset] ); sqlsrv_free( out_of_row_data ); } } @@ -1525,7 +1525,7 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in sqlsrv_malloc_auto_ptr buffer; buffer = static_cast( sqlsrv_malloc( INITIAL_LOB_FIELD_LEN + extra + sizeof( SQLULEN ))); SQLRETURN r = SQL_SUCCESS; - SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ] = {'\0'}; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'}; SQLLEN last_field_len = 0; bool full_length_returned = false; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index d75d8c689..0e2a47db4 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -70,7 +70,7 @@ typedef struct _OSVERSIONINFOA { DWORD dwMinorVersion; DWORD dwBuildNumber; DWORD dwPlatformId; - CHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage + CHAR szCSDVersion[128]; // Maintenance string for PSS usage } OSVERSIONINFOA, *POSVERSIONINFOA, *LPOSVERSIONINFOA; typedef OSVERSIONINFOA OSVERSIONINFO; #endif // !_WIN32 @@ -546,21 +546,21 @@ class sqlsrv_auto_ptr { // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ int index ) const { - return _ptr[ index ]; + return _ptr[index]; } // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ unsigned int index ) const { - return _ptr[ index ]; + return _ptr[index]; } // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ long index ) const { - return _ptr[ index ]; + return _ptr[index]; } @@ -577,7 +577,7 @@ class sqlsrv_auto_ptr { // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ unsigned short index ) const { - return _ptr[ index ]; + return _ptr[index]; } // access elements of a structure through the auto ptr @@ -1889,7 +1889,7 @@ namespace core { // and return a more helpful message prepended to the ODBC errors if that error occurs if( !SQL_SUCCEEDED( r )) { - SQLCHAR err_msg[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = {'\0'}; + SQLCHAR err_msg[SQL_MAX_ERROR_MESSAGE_LENGTH + 1] = {'\0'}; SQLSMALLINT len = 0; SQLRETURN rtemp = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index ef07469bb..31f2da45e 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -364,7 +364,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ if( stmt->param_ind_ptrs.size() < static_cast( param_num + 1 )){ stmt->param_ind_ptrs.resize( param_num + 1, SQL_NULL_DATA ); } - SQLLEN& ind_ptr = stmt->param_ind_ptrs[ param_num ]; + SQLLEN& ind_ptr = stmt->param_ind_ptrs[param_num]; zval* param_ref = param_z; if( Z_ISREF_P( param_z )){ @@ -964,7 +964,7 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len ); if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) { // prevent the 'string not null terminated' warning - reinterpret_cast( field_value )[ cached->len ] = '\0'; + reinterpret_cast( field_value )[cached->len] = '\0'; } *field_len = cached->len; if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } @@ -1236,7 +1236,7 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout int lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[ 32 ] = {'\0'}; + char lock_timeout_sql[32] = {'\0'}; int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout ); SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), @@ -1304,7 +1304,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } // read the data from the stream, send it via SQLPutData and track how much we've sent. else { - char buffer[ PHP_STREAM_BUFFER_SIZE + 1 ] = {'\0'}; + char buffer[PHP_STREAM_BUFFER_SIZE + 1] = {'\0'}; std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character std::size_t read = php_stream_read( param_stream, buffer, buffer_size ); @@ -1325,7 +1325,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // the size of wbuffer is set for the worst case of UTF-8 to UTF-16 conversion, which is a // expansion of 2x the UTF-8 size. - SQLWCHAR wbuffer[ PHP_STREAM_BUFFER_SIZE + 1 ] = {L'\0'}; + SQLWCHAR wbuffer[PHP_STREAM_BUFFER_SIZE + 1] = {L'\0'}; int wbuffer_size = static_cast( sizeof( wbuffer ) / sizeof( SQLWCHAR )); DWORD last_error_code = ERROR_SUCCESS; // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate @@ -1631,7 +1631,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // convert it to a DateTime object and return the created object case SQLSRV_PHPTYPE_DATETIME: { - char field_value_temp[ MAX_DATETIME_STRING_LEN ] = {'\0'}; + char field_value_temp[MAX_DATETIME_STRING_LEN] = {'\0'}; zval params[1]; zval field_value_temp_z; zval function_z; @@ -1823,7 +1823,7 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve } // null terminate the string, set the size within the zval, and return success - wbuffer[ wchar_size ] = L'\0'; + wbuffer[wchar_size] = L'\0'; core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), wchar_size * sizeof( SQLWCHAR ) ); sqlsrv_free(wbuffer); wbuffer.transferred(); @@ -2075,7 +2075,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter char* str = Z_STRVAL_P( value_z ); - SQLLEN str_len = stmt->param_ind_ptrs[ output_param->param_num ]; + SQLLEN str_len = stmt->param_ind_ptrs[output_param->param_num]; if( str_len == 0 ) { core::sqlsrv_zval_stringl( value_z, "", 0 ); continue; @@ -2127,7 +2127,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // 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'; + str[str_len] = '\0'; core::sqlsrv_zval_stringl(value_z, str, str_len); } else { @@ -2137,7 +2137,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) break; case IS_LONG: // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if( stmt->param_ind_ptrs[ output_param->param_num ] == SQL_NULL_DATA ) { + if( stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA ) { ZVAL_NULL( value_z ); } else if( output_param->is_bool ) { @@ -2442,11 +2442,11 @@ field_value = field_value_temp; stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] TSRMLS_DC ) { - for( int i = 0; stmt_opts[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { + for( int i = 0; stmt_opts[i].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { // if we find the key we're looking for, return it - if( key == stmt_opts[ i ].key ) { - return &stmt_opts[ i ]; + if( key == stmt_opts[i].key ) { + return &stmt_opts[i]; } } @@ -2580,8 +2580,8 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* // The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which // may be less than the size of the buffer since the output may be more than the input. If it is greater, // than the error 22001 is returned by ODBC. - if( stmt->param_ind_ptrs[ paramno ] > buffer_len - (elem_size - buffer_null_extra)) { - stmt->param_ind_ptrs[ paramno ] = buffer_len - (elem_size - buffer_null_extra); + if( stmt->param_ind_ptrs[paramno] > buffer_len - (elem_size - buffer_null_extra)) { + stmt->param_ind_ptrs[paramno] = buffer_len - (elem_size - buffer_null_extra); } } diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 02259261c..c6eca297a 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -28,7 +28,7 @@ log_callback g_driver_log; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; // buffer used to hold a formatted log message prior to actually logging it. -char last_err_msg[ 2048 ] = {'\0'}; // 2k to hold the error messages +char last_err_msg[2048] = {'\0'}; // 2k to hold the error messages // routine used by utf16_string_from_mbcs_string unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, @@ -219,8 +219,8 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu SQLRETURN r = SQL_SUCCESS; SQLSMALLINT wmessage_len = 0; - SQLWCHAR wsqlstate[ SQL_SQLSTATE_BUFSIZE ] = {L'\0'}; - SQLWCHAR wnative_message[ SQL_MAX_ERROR_MESSAGE_LENGTH + 1 ] = {L'\0'}; + SQLWCHAR wsqlstate[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; + SQLWCHAR wnative_message[SQL_MAX_ERROR_MESSAGE_LENGTH + 1] = {L'\0'}; SQLSRV_ENCODING enc = ctx.encoding(); switch( h_type ) { @@ -393,7 +393,7 @@ unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encodin if( required_len == 0 ) { return 0; } - utf16_out_string[ required_len ] = '\0'; + utf16_out_string[required_len] = '\0'; return required_len; } diff --git a/source/shared/globalization.h b/source/shared/globalization.h index e2830e175..0d77fff9d 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -362,7 +362,7 @@ class EncodingConverter { // Use fixed size buffer iteratively to determine final required length const size_t CCH_FIXED_SIZE = 256; - char fixed_buf[ CCH_FIXED_SIZE*sizeof(DestType) ] = {'\0'}; + char fixed_buf[CCH_FIXED_SIZE*sizeof(DestType)] = {'\0'}; iconv_buffer dest( &fixed_buf[0], CCH_FIXED_SIZE ); diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 7bf805089..30bc65139 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -1234,12 +1234,12 @@ void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn TSRMLS_DC ) int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z TSRMLS_DC ) { - for( int i=0; SS_CONN_OPTS[ i ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) + for( int i=0; SS_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) { - if( key_len == SS_CONN_OPTS[ i ].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[ i ].sqlsrv_name )) { + if( key_len == SS_CONN_OPTS[i].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[i].sqlsrv_name )) { - switch( SS_CONN_OPTS[ i ].value_type ) { + switch( SS_CONN_OPTS[i].value_type ) { case CONN_ATTR_BOOL: // bool attributes can be either strings to be appended to the connection string @@ -1250,7 +1250,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In case CONN_ATTR_INT: { CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT, - SS_CONN_OPTS[ i ].sqlsrv_name ) + SS_CONN_OPTS[i].sqlsrv_name ) { throw ss::SSException(); } @@ -1259,7 +1259,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In case CONN_ATTR_STRING: { CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING, - SS_CONN_OPTS[ i ].sqlsrv_name ) { + SS_CONN_OPTS[i].sqlsrv_name ) { throw ss::SSException(); } @@ -1268,7 +1268,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In size_t value_len = Z_STRLEN_P( value_z ); bool escaped = core_is_conn_opt_value_escaped( value, value_len ); - CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[ i ].sqlsrv_name ) { + CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[i].sqlsrv_name ) { throw ss::SSException(); } @@ -1278,7 +1278,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In valid = core_is_authentication_option_valid( value, value_len ); } - CHECK_CUSTOM_ERROR( !valid, ctx, SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, SS_CONN_OPTS[ i ].sqlsrv_name ) { + CHECK_CUSTOM_ERROR( !valid, ctx, SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, SS_CONN_OPTS[i].sqlsrv_name ) { throw ss::SSException(); } @@ -1287,7 +1287,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In } } - return SS_CONN_OPTS[ i ].conn_option_key; + return SS_CONN_OPTS[i].conn_option_key; } } return SQLSRV_CONN_OPTION_INVALID; @@ -1295,10 +1295,10 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len TSRMLS_DC ) { - for( int i = 0; SS_STMT_OPTS[ i ].key != SQLSRV_STMT_OPTION_INVALID; ++i ) + for( int i = 0; SS_STMT_OPTS[i].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { - if( key_len == SS_STMT_OPTS[ i ].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[ i ].name )) { - return SS_STMT_OPTS[ i ].key; + if( key_len == SS_STMT_OPTS[i].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[i].name )) { + return SS_STMT_OPTS[i].key; } } return SQLSRV_STMT_OPTION_INVALID; diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index bb8ab04b4..74463d200 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -537,9 +537,9 @@ PHP_MINIT_FUNCTION(sqlsrv) g_ss_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); ::zend_hash_init( g_ss_errors_ht, 50, NULL, sqlsrv_error_const_dtor /*pDestructor*/, 1 ); - for( int i = 0; SS_ERRORS[ i ].error_code != UINT_MAX; ++i ) { - if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[ i ].error_code, - &( SS_ERRORS[ i ].sqlsrv_error ), sizeof( SS_ERRORS[ i ].sqlsrv_error ))) { + for( int i = 0; SS_ERRORS[i].error_code != UINT_MAX; ++i ) { + if (NULL == ::zend_hash_index_update_mem( g_ss_errors_ht, SS_ERRORS[i].error_code, + &( SS_ERRORS[i].sqlsrv_error ), sizeof( SS_ERRORS[i].sqlsrv_error ))) { LOG( SEV_ERROR, "%1!s!: Failed to insert data into sqlsrv errors hashtable.", _FN_ ); return FAILURE; } diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 4f780bbf1..2c410fb89 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -137,7 +137,7 @@ ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) for( int i=0; i < fetch_fields_count; ++i ) { - sqlsrv_free( fetch_field_names[ i ].name ); + sqlsrv_free( fetch_field_names[i].name ); } sqlsrv_free( fetch_field_names ); } @@ -155,7 +155,7 @@ void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) for( int i=0; i < fetch_fields_count; ++i ) { - sqlsrv_free( fetch_field_names[ i ].name ); + sqlsrv_free( fetch_field_names[i].name ); } sqlsrv_free( fetch_field_names ); } @@ -719,7 +719,7 @@ PHP_FUNCTION( sqlsrv_num_fields ) } } -// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams ]]) +// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams]]) // // Retrieves the next row of data as a PHP object. // @@ -1776,7 +1776,7 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ SQLLEN field_name_len = 0; SQLSMALLINT field_name_len_w = 0; - SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2 ] = {L'\0'}; + SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2] = {L'\0'}; sqlsrv_malloc_auto_ptr field_name; sqlsrv_malloc_auto_ptr field_names; field_names = static_cast( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field_name ))); @@ -1836,7 +1836,7 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ throw ss::SSException(); } - if( stmt->fetch_field_names[ i ].len > 1 || allow_empty_field_names ) { + if( stmt->fetch_field_names[i].len > 1 || allow_empty_field_names ) { zr = add_assoc_zval( &fields, stmt->fetch_field_names[i].name, &field ); CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index c431daeff..f8e076218 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -28,7 +28,7 @@ unsigned int current_log_subsystem = LOG_UTIL; // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; -char log_msg[ LOG_MSG_SIZE ] = {'\0'}; +char log_msg[LOG_MSG_SIZE] = {'\0'}; // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; From 29f4ad710baff8e8a4cc43aabc306c5fbc01848c Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 1 Aug 2018 13:24:20 -0700 Subject: [PATCH 040/249] Fix for compilation problem --- source/pdo_sqlsrv/pdo_dbh.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index be2c00a61..aca3275f3 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -463,7 +463,8 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ driver_dbh->set_func( __FUNCTION__ ); \ int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ - char func[length+1] = {'\0'}; \ + char func[length+1]; \ + memset(func, '\0', length+1); \ strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ strcat_s( func, length+1, ": entering" ); \ LOG( SEV_NOTICE, func ); \ From 0b15997d3c85d0fa0fb5e35bec1b1d5d2c328c70 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 1 Aug 2018 16:14:34 -0700 Subject: [PATCH 041/249] Fix for compilation problem again --- source/pdo_sqlsrv/pdo_stmt.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 7f16faa70..c7196d255 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -347,7 +347,8 @@ void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ driver_stmt->set_func( __FUNCTION__ ); \ int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ - char func[length+1] = {'\0'}; \ + char func[length+1]; \ + memset(func, '\0', length+1); \ strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ strcat_s( func, length+1, ": entering" ); \ LOG( SEV_NOTICE, func ); \ From 909d1fa13081833cd293f26d025316b2da581bb0 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 10 Aug 2018 15:18:08 -0700 Subject: [PATCH 042/249] Before freeing stmt in destructor check if dbh driver data is NULL (#829) * Issue 434 - set dbh driver data to NULL as well in destructor * Reverted the last change but instead check if dbh driver_data is already freed * Modified the comment --- source/pdo_sqlsrv/pdo_stmt.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index c7196d255..154831539 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -496,8 +496,13 @@ int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) LOG( SEV_NOTICE, "pdo_sqlsrv_stmt_dtor: entering" ); // if a PDO statement didn't complete preparation, its driver_data can be NULL - if( driver_stmt == NULL ) { + if (driver_stmt == NULL) { + return 1; + } + // occasionally stmt->dbh->driver_data is already freed and reset but its driver_data is not + if (stmt->dbh != NULL && stmt->dbh->driver_data == NULL) { + stmt->driver_data = NULL; return 1; } From 28a7860828d12e15010a7f73482f505cf5563ab9 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 17 Aug 2018 13:52:23 -0700 Subject: [PATCH 043/249] Added driver to the skipif conditions (#831) --- .../pdo_sqlsrv/skipif_version_less_than_2k14.inc | 12 ++++++------ .../sqlsrv/skipif_version_less_than_2k14.inc | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc index 7db45126b..39d92e39e 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k14.inc @@ -7,13 +7,13 @@ if (!extension_loaded("pdo_sqlsrv")) { die("skip Extension not loaded"); } -$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' ); +$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN'); -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$conn = new PDO( "sqlsrv:server = $server ;", $uid, $pwd ); +$conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd); if ($conn === false) { - die( "skip Could not connect during SKIPIF." ); + die("skip Could not connect during SKIPIF."); } $msodbcsql_ver = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; @@ -33,11 +33,11 @@ if (!$is_win) { // Get SQL Server Version // Exclude this check if running on Azure if (!$daasMode) { - $stmt = $conn->query( "SELECT @@VERSION" ); + $stmt = $conn->query("SELECT @@VERSION"); if ($stmt) { $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; } else { - die( "skip Could not fetch SQL Server version during SKIPIF."); + die("skip Could not fetch SQL Server version during SKIPIF."); } $version = explode(' ', $ver_string); diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc index 0ea10964c..e8c53f2d7 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k14.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k14.inc @@ -7,15 +7,15 @@ if (!extension_loaded("sqlsrv")) { die("skip Extension not loaded"); } -$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' ); +$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN'); -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$connectionInfo = array( "UID"=>$userName, "PWD"=>$userPassword ); +$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver); -$conn = sqlsrv_connect( $server, $connectionInfo ); +$conn = sqlsrv_connect($server, $connectionInfo); if ($conn === false) { - die( "skip Could not connect during SKIPIF." ); + die("skip Could not connect during SKIPIF."); } $msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; @@ -35,9 +35,9 @@ if (!$is_win) { // Get SQL Server version // Exclude this check if running on Azure if (!$daasMode) { - $stmt = sqlsrv_query( $conn, "SELECT @@VERSION" ); + $stmt = sqlsrv_query($conn, "SELECT @@VERSION"); if (sqlsrv_fetch($stmt)) { - $ver_string = sqlsrv_get_field( $stmt, 0 ); + $ver_string = sqlsrv_get_field($stmt, 0); } else { die("skip Could not fetch SQL Server version."); } From 4452a4d61b20656b1a73c8691684d66af79a0014 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 17 Aug 2018 14:18:52 -0700 Subject: [PATCH 044/249] Used git clone instead to download source from a branch of a tag (#832) --- buildscripts/builddrivers.py | 4 ++-- buildscripts/buildtools.py | 37 +++++++++++------------------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/buildscripts/builddrivers.py b/buildscripts/builddrivers.py index 4378ad1f1..e9ac02dcd 100644 --- a/buildscripts/builddrivers.py +++ b/buildscripts/builddrivers.py @@ -239,7 +239,7 @@ def validate_input(question, values): parser.add_argument('--DRIVER', default='all', choices=['all', 'sqlsrv', 'pdo_sqlsrv'], help="driver to build (default: all)") parser.add_argument('--DEBUG', action='store_true', help="enable debug mode (default: False)") parser.add_argument('--REPO', default='Microsoft', help="GitHub repository (default: Microsoft)") - parser.add_argument('--BRANCH', default='dev', help="GitHub repository branch (default: dev)") + parser.add_argument('--BRANCH', default='dev', help="GitHub repository branch or tag (default: dev)") parser.add_argument('--SOURCE', default=None, help="a local path to source file (default: None)") parser.add_argument('--TESTING', action='store_true', help="turns on testing mode (default: False)") parser.add_argument('--DESTPATH', default=None, help="an alternative destination for the drivers (default: None)") @@ -280,7 +280,7 @@ def validate_input(question, values): answer = input("Download source from a GitHub repo? [y/n]: ") if answer == 'yes' or answer == 'y' or answer == '': repo = input("Name of the repo (hit enter for 'Microsoft'): ") - branch = input("Name of the branch (hit enter for 'dev'): ") + branch = input("Name of the branch or tag (hit enter for 'dev'): ") if repo == '': repo = 'Microsoft' if branch == '': diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index 306aafaa7..79b36923a 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -193,7 +193,7 @@ def write_lines_to_copy_source(driver, file): file.write('@CALL ROBOCOPY ' + source + ' ' + dest + ' /s /xx /xo' + os.linesep) @staticmethod - def download_msphpsql_source(repo, branch, dest_folder = 'Source', clean_up = True): + def download_msphpsql_source(repo, branch, dest_folder = 'Source'): """Download to *dest_folder* the msphpsql archive of the specified GitHub *repo* and *branch*. The downloaded files will be removed by default. """ @@ -201,40 +201,25 @@ def download_msphpsql_source(repo, branch, dest_folder = 'Source', clean_up = Tr work_dir = os.path.dirname(os.path.realpath(__file__)) temppath = os.path.join(work_dir, 'temp') - if os.path.exists(temppath): - shutil.rmtree(temppath) - os.makedirs(temppath) + # There is no need to remove tree - + # for Bamboo, it will be cleaned up eventually + # for local development, this can act as a cached copy of the repo + if not os.path.exists(temppath): + os.makedirs(temppath) os.chdir(temppath) - file = branch + '.zip' - url = 'https://github.com/' + repo + '/msphpsql/archive/' + branch + '.zip' - - print('Downloading ' + url + ' ...') - try: - with urllib.request.urlopen(url) as response, open(file, 'wb') as out_file: - shutil.copyfileobj(response, out_file) - except: - print ("Resort to skip ssl verification...") - # need to skip ssl verification on some agents - # see https://www.python.org/dev/peps/pep-0476/ - with urllib.request.urlopen(url, context=ssl._create_unverified_context()) as response, open(file, 'wb') as out_file: - shutil.copyfileobj(response, out_file) - - print('Extracting ' + file + ' ...') - zip = zipfile.ZipFile(file) - zip.extractall() - zip.close() - msphpsqlFolder = os.path.join(temppath, 'msphpsql-' + branch) + + url = 'https://github.com/' + repo + '/msphpsql.git' + command = 'git clone ' + url + ' -b ' + branch + ' --single-branch --depth 1 ' + msphpsqlFolder + os.system(command) + source = os.path.join(msphpsqlFolder, 'source') os.chdir(work_dir) os.system('ROBOCOPY ' + source + '\shared ' + dest_folder + '\shared /xx /xo') os.system('ROBOCOPY ' + source + '\pdo_sqlsrv ' + dest_folder + '\pdo_sqlsrv /xx /xo') os.system('ROBOCOPY ' + source + '\sqlsrv ' + dest_folder + '\sqlsrv /xx /xo') - - if clean_up: - shutil.rmtree(temppath) except: print('Error occurred when downloading source') From 6a688b37276cd0e394699fd0964cc92ef03fc3d9 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 20 Aug 2018 14:51:33 -0700 Subject: [PATCH 045/249] Modified the error handling to make it more flexible (#833) * Made error handling more flexible * Fixed a minor issue with a test --- source/shared/core_sqlsrv.h | 14 ++++++--- source/shared/core_util.cpp | 30 +++++++++++++++++-- .../sqlsrv_ae_type_conversion_select.phpt | 10 +++++-- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 7d7466cb8..b5ae9ef6f 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -175,7 +175,7 @@ const int SQL_SERVER_MAX_TYPE_SIZE = 0; const int SQL_SERVER_MAX_PARAMS = 2100; // increase the maximum message length to accommodate for the long error returned for operand type clash // or for conversion of a long string -const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 8; +const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 2; // max size of a date time string when converting from a DateTime object to a string const int MAX_DATETIME_STRING_LEN = 256; @@ -1889,14 +1889,20 @@ namespace core { // and return a more helpful message prepended to the ODBC errors if that error occurs if( !SQL_SUCCEEDED( r )) { - SQLCHAR err_msg[SQL_MAX_ERROR_MESSAGE_LENGTH + 1] = {'\0'}; + SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'}; SQLSMALLINT len = 0; SQLRETURN rtemp = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, - err_msg, SQL_MAX_ERROR_MESSAGE_LENGTH, &len ); + err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); + if (rtemp == SQL_SUCCESS_WITH_INFO && len > SQL_MAX_MESSAGE_LENGTH) { + // if the error message is this long, then it must not be the mars message + // defined as ODBC_CONNECTION_BUSY_ERROR -- so return here and continue the + // regular error handling + return; + } CHECK_SQL_ERROR_OR_WARNING( rtemp, stmt ) { - + throw CoreException(); } diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 075db3416..04f131ccd 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -265,10 +265,36 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu // We need to calculate number of characters SQLINTEGER wsqlstate_len = sizeof( wsqlstate ) / sizeof( SQLWCHAR ); SQLLEN sqlstate_len = 0; - convert_string_from_utf16(enc, wsqlstate, wsqlstate_len, (char**)&error->sqlstate, sqlstate_len); + convert_string_from_utf16(enc, wsqlstate, wsqlstate_len, (char**)&error->sqlstate, sqlstate_len); + SQLLEN message_len = 0; - convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); + if (r == SQL_SUCCESS_WITH_INFO && wmessage_len > SQL_MAX_ERROR_MESSAGE_LENGTH) { + // note that wmessage_len is the number of characters required for the error message -- + // create a new buffer big enough for this lengthy error message + sqlsrv_malloc_auto_ptr wnative_message_str; + + SQLSMALLINT expected_len = wmessage_len * sizeof(SQLWCHAR); + SQLSMALLINT returned_len = 0; + + wnative_message_str = reinterpret_cast(sqlsrv_malloc(expected_len)); + memset(wnative_message_str, '\0', expected_len); + + SQLRETURN rtemp = ::SQLGetDiagFieldW(h_type, h, record_number, SQL_DIAG_MESSAGE_TEXT, wnative_message_str, wmessage_len, &returned_len); + if (!SQL_SUCCEEDED(rtemp) || returned_len != expected_len) { + // something went wrong + return false; + } + + convert_string_from_utf16(enc, wnative_message_str, wmessage_len, (char**)&error->native_message, message_len); + } else { + convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len); + } + + if (message_len == 0 && error->native_message == NULL) { + // something went wrong + return false; + } break; } diff --git a/test/functional/sqlsrv/sqlsrv_ae_type_conversion_select.phpt b/test/functional/sqlsrv/sqlsrv_ae_type_conversion_select.phpt index ba48e9f67..d4ea1c940 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_type_conversion_select.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_type_conversion_select.phpt @@ -29,8 +29,10 @@ function checkErrors(&$convError) $convError[0][1] != '8114' and $convError[0][1] != '8169') { print_r($convError); - fatalError("Conversion failed with unexpected error message. i=$i, j=$j, v=$v\n"); - } + return false; + } + + return true; } // Build the select queries. We want every combination of types for conversion @@ -202,7 +204,9 @@ for ($v = 0; $v < sizeof($values); ++$v) { if ($stmt == false) { $convError = sqlsrv_errors(); - checkErrors($convError); + if (!checkErrors($convError)) { + fatalError("Conversion failed with unexpected error message. i=$i, j=$j, v=$v\n"); + } if (AE\isDataEncrypted()) { $stmtAE = sqlsrv_query($conn, $selectQueryAE[$i][$j]); From c209b7248fb8de115592b630e583f3af5254d248 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 24 Aug 2018 15:31:13 -0700 Subject: [PATCH 046/249] Enabled Spectre Mitigations (#836) --- source/pdo_sqlsrv/config.w32 | 3 +++ source/sqlsrv/config.w32 | 3 +++ 2 files changed, 6 insertions(+) diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index 7066e6254..a71e329d8 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -36,6 +36,9 @@ if( PHP_PDO_SQLSRV != "no" ) { ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/Zi" ); if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/guard:cf /O2" ); ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" ); + if (VCVERS >= 1913) { + ADD_FLAG("CFLAGS_PDO_SQLSRV", "/Qspectre"); + } ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo'); EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src_class, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); } else { diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 449789c4b..2972a7b19 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -35,6 +35,9 @@ if( PHP_SQLSRV != "no" ) { ADD_FLAG( "CFLAGS_SQLSRV", "/EHsc" ); ADD_FLAG( "CFLAGS_SQLSRV", "/GS" ); ADD_FLAG( "CFLAGS_SQLSRV", "/Zi" ); + if (VCVERS >= 1913) { + ADD_FLAG("CFLAGS_SQLSRV", "/Qspectre"); + } if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_SQLSRV", "/guard:cf /O2" ); EXTENSION("sqlsrv", sqlsrv_src_class , PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); } else { From 084ab7240612dd6582ce5f22303b3b86f5f94080 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 28 Aug 2018 15:18:01 -0700 Subject: [PATCH 047/249] Incorporated changes in PR 634 to pdo_sqlsrv (#834) * Incorporated changes in PR 634 to pdo_sqlsrv * Reverted the changes because the array is for internal use only --- source/pdo_sqlsrv/pdo_dbh.cpp | 30 ++++++++++++++++++------------ source/pdo_sqlsrv/pdo_stmt.cpp | 7 +++++-- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 8935211a2..5d2141cf8 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -1380,11 +1380,15 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const } // only change the encoding if quote is called from the statement level (which should only be called when a statement // is prepared with emulate prepared on) - if ( is_statement ) { - pdo_stmt_t *stmt = Z_PDO_STMT_P( object ); + if (is_statement) { + pdo_stmt_t *stmt = Z_PDO_STMT_P(object); + SQLSRV_ASSERT(stmt != NULL, "pdo_sqlsrv_dbh_quote: stmt object was null"); // set the encoding to be the encoding of the statement otherwise set to be the encoding of the dbh - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); - if ( driver_stmt->encoding() != SQLSRV_ENCODING_INVALID ) { + + pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast(stmt->driver_data); + SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was null"); + + if (driver_stmt->encoding() != SQLSRV_ENCODING_INVALID) { encoding = driver_stmt->encoding(); } else { @@ -1392,18 +1396,20 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const encoding = driver_dbh->encoding(); } // get the placeholder at the current position in driver_stmt->placeholders ht + // Normally it's not a good idea to alter the internal pointer in a hashed array + // (see pull request 634 on GitHub) but in this case this is for internal use only zval* placeholder = NULL; - if (( placeholder = zend_hash_get_current_data( driver_stmt->placeholders )) != NULL && zend_hash_move_forward( driver_stmt->placeholders ) == SUCCESS && stmt->bound_params != NULL ) { + if ((placeholder = zend_hash_get_current_data(driver_stmt->placeholders)) != NULL && zend_hash_move_forward(driver_stmt->placeholders) == SUCCESS && stmt->bound_params != NULL) { pdo_bound_param_data* param = NULL; - if ( Z_TYPE_P( placeholder ) == IS_STRING ) { - param = reinterpret_cast( zend_hash_find_ptr( stmt->bound_params, Z_STR_P( placeholder ))); + if (Z_TYPE_P(placeholder) == IS_STRING) { + param = reinterpret_cast(zend_hash_find_ptr(stmt->bound_params, Z_STR_P(placeholder))); } - else if ( Z_TYPE_P( placeholder ) == IS_LONG) { - param = reinterpret_cast( zend_hash_index_find_ptr( stmt->bound_params, Z_LVAL_P( placeholder ))); + else if (Z_TYPE_P(placeholder) == IS_LONG) { + param = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_params, Z_LVAL_P(placeholder))); } - if ( NULL != param ) { - SQLSRV_ENCODING param_encoding = static_cast( Z_LVAL( param->driver_params )); - if ( param_encoding != SQLSRV_ENCODING_INVALID ) { + if (NULL != param) { + SQLSRV_ENCODING param_encoding = static_cast(Z_LVAL(param->driver_params)); + if (param_encoding != SQLSRV_ENCODING_INVALID) { encoding = param_encoding; } } diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 154831539..de5c35557 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -560,12 +560,15 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) // if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the // subtituted query provided by PDO - if( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { + if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) { // reset the placeholders hashtable internal in case the user reexecutes a statement + // Normally it's not a good idea to alter the internal pointer in a hashed array + // (see pull request 634 on GitHub) but in this case this is for internal use only + zend_hash_internal_pointer_reset(driver_stmt->placeholders); query = stmt->active_query_string; - query_len = static_cast( stmt->active_query_stringlen ); + query_len = static_cast(stmt->active_query_stringlen); } SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); From ae1b413f19044ce7df426d0c923cd6996d76c0cd Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 4 Sep 2018 12:01:15 -0700 Subject: [PATCH 048/249] Modified README re user's suggestion (#841) * Modified README re user's suggestion * Moved the if condition to the end as per review --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 20e3c0e98..2ad3406c8 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ For full details on the system requirements for the drivers, see the [system req On the client machine: - PHP 7.0.x, 7.1.x, or 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows) -- A Web server such as Internet Information Services (IIS) is required. Your Web server must be configured to run PHP - [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-2017) +- If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux. @@ -59,7 +59,7 @@ If you choose to build the drivers, you must be able to build PHP 7 without incl To load the drivers, make sure that the driver is in your PHP extension directory and enable it in your PHP installation's php.ini file by adding `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll` to it. If necessary, specify the extension directory using `extension_dir`, for example: `extension_dir = "C:\PHP\ext"`. Note that the precompiled binaries have different names -- substitute accordingly in php.ini. For more details on loading the drivers, see [Loading the PHP SQL Driver](https://docs.microsoft.com/en-us/sql/connect/php/loading-the-php-sql-driver) on Microsoft Docs. -Finally, restart the Web server. +Finally, if running PHP in a Web server, restart the Web server. ## Install (UNIX) From e51380612db8a546c8869d3914fa54243ddbd0a3 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 6 Sep 2018 11:32:04 -0700 Subject: [PATCH 049/249] Adding supporting for Azure AD access token (#837) * Adding supporting for Azure AD access token * Added more comments for the AD access token skipif files * Save the pointer to access token struct until after connecting * Clear the access token data before freeing the memory * Added a reference as per review --- source/pdo_sqlsrv/pdo_dbh.cpp | 10 ++ source/pdo_sqlsrv/pdo_util.cpp | 9 + source/shared/core_conn.cpp | 115 ++++++++++--- source/shared/core_sqlsrv.h | 14 +- source/shared/msodbcsql.h | 7 + source/sqlsrv/conn.cpp | 10 ++ source/sqlsrv/util.cpp | 8 + test/functional/pdo_sqlsrv/access_token.inc | 3 + .../pdo_sqlsrv/pdo_azure_ad_access_token.phpt | 157 ++++++++++++++++++ .../skipif_azure_ad_acess_token.inc | 41 +++++ .../skipif_version_less_than_2k16.inc | 6 +- test/functional/sqlsrv/access_token.inc | 3 + .../sqlsrv/skipif_azure_ad_acess_token.inc | 45 +++++ .../sqlsrv/skipif_version_less_than_2k16.inc | 6 +- .../sqlsrv/sqlsrv_azure_ad_access_token.phpt | 131 +++++++++++++++ 15 files changed, 536 insertions(+), 29 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/access_token.inc create mode 100644 test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt create mode 100644 test/functional/pdo_sqlsrv/skipif_azure_ad_acess_token.inc create mode 100644 test/functional/sqlsrv/access_token.inc create mode 100644 test/functional/sqlsrv/skipif_azure_ad_acess_token.inc create mode 100644 test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 5d2141cf8..55801de01 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -38,6 +38,7 @@ namespace PDOConnOptionNames { const char Server[] = "Server"; const char APP[] = "APP"; +const char AccessToken[] = "AccessToken"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; @@ -185,6 +186,15 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_STRING, conn_str_append_func::func }, + { + PDOConnOptionNames::AccessToken, + sizeof( PDOConnOptionNames::AccessToken ), + SQLSRV_CONN_OPTION_ACCESS_TOKEN, + ODBCConnOptions::AccessToken, + sizeof( ODBCConnOptions::AccessToken), + CONN_ATTR_STRING, + access_token_set_func::func + }, { PDOConnOptionNames::ApplicationIntent, sizeof( PDOConnOptionNames::ApplicationIntent ), diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 617a2df12..f0497f723 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -429,6 +429,15 @@ pdo_error PDO_ERRORS[] = { SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, { IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -89, false} }, + { + SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, + { IMSSP, (SQLCHAR*) "When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.", -90, false} + }, + { + SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, + { IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false} + }, + { UINT_MAX, {} } }; diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 54ec004f2..d2251fc92 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -243,6 +243,12 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont } // else driver_version not unknown #endif // !_WIN32 + // time to free the access token, if not null + if (conn->azure_ad_access_token != NULL) { + memset(conn->azure_ad_access_token->data, 0, conn->azure_ad_access_token->dataSize); // clear the memory + conn->azure_ad_access_token.reset(); + } + CHECK_SQL_ERROR( r, conn ) { throw core::CoreException(); } @@ -759,35 +765,53 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou { bool mars_mentioned = false; connection_option const* conn_opt; + bool access_token_used = false; try { + // First of all, check if access token is specified. If so, check if UID, PWD, Authentication exist + // No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers + if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) { + bool invalidOptions = false; + + // UID and PWD have to be NULLs... throw an exception as long as the user has specified any of them in the connection string, + // even if they may be empty strings. Likewise if the keyword Authentication exists + if (uid != NULL || pwd != NULL || zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION)) { + invalidOptions = true; + } - // Add the server name - common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); - - // if uid is not present then we use trusted connection. - if(uid == NULL || strnlen_s( uid ) == 0 ) { - - connection_string += "Trusted_Connection={Yes};"; - } - else { - - bool escaped = core_is_conn_opt_value_escaped( uid, strnlen_s( uid )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + CHECK_CUSTOM_ERROR(invalidOptions, conn, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN ) { throw core::CoreException(); } - common_conn_str_append_func( ODBCConnOptions::UID, uid, strnlen_s( uid ), connection_string TSRMLS_CC ); + access_token_used = true; + } - // if no password was given, then don't add a password to the connection string. Perhaps the UID - // given doesn't have a password? - if( pwd != NULL ) { - escaped = core_is_conn_opt_value_escaped( pwd, strnlen_s( pwd )); - CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) { + // Add the server name + common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); + + // if uid is not present then we use trusted connection -- but not when access token is used, because they are incompatible + if (!access_token_used) { + if (uid == NULL || strnlen_s(uid) == 0) { + connection_string += CONNECTION_OPTION_NO_CREDENTIALS; // "Trusted_Connection={Yes};" + } + else { + bool escaped = core_is_conn_opt_value_escaped(uid, strnlen_s(uid)); + CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) { throw core::CoreException(); } - common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strnlen_s( pwd ), connection_string TSRMLS_CC ); + common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string TSRMLS_CC); + + // if no password was given, then don't add a password to the connection string. Perhaps the UID + // given doesn't have a password? + if (pwd != NULL) { + escaped = core_is_conn_opt_value_escaped(pwd, strnlen_s(pwd)); + CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) { + throw core::CoreException(); + } + + common_conn_str_append_func(ODBCConnOptions::PWD, pwd, strnlen_s(pwd), connection_string TSRMLS_CC); + } } } @@ -1172,3 +1196,56 @@ size_t core_str_zval_is_true( _Inout_ zval* value_z ) return 0; // false } + +void access_token_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ) +{ + SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "An access token must be a byte string."); + + size_t value_len = Z_STRLEN_P(value); + + CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN) { + throw core::CoreException(); + } + + const char* value_str = Z_STRVAL_P( value ); + + // The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from + // an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also + // bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the + // SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure + // + // typedef struct AccessToken + // { + // unsigned int dataSize; + // char data[]; + // } ACCESSTOKEN; + // + // NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows. + // + // A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte, + // similar to a UCS-2 string containing only ASCII characters + // + // See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token + + size_t dataSize = 2 * value_len; + + sqlsrv_malloc_auto_ptr accToken; + accToken = reinterpret_cast(sqlsrv_malloc(sizeof(ACCESSTOKEN) + dataSize)); + + ACCESSTOKEN *pAccToken = accToken.get(); + SQLSRV_ASSERT(pAccToken != NULL, "Something went wrong when trying to allocate memory for the access token."); + + pAccToken->dataSize = dataSize; + + // Expand access token with padding bytes + for (size_t i = 0, j = 0; i < dataSize; i += 2, j++) { + pAccToken->data[i] = value_str[j]; + pAccToken->data[i+1] = 0; + } + + core::SQLSetConnectAttr(conn, SQL_COPT_SS_ACCESS_TOKEN, reinterpret_cast(pAccToken), SQL_IS_POINTER); + + // Save the pointer because SQLDriverConnect() will use it to make connection to the server + conn->azure_ad_access_token = pAccToken; + accToken.transferred(); +} diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index b5ae9ef6f..78215f339 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1076,6 +1076,8 @@ struct sqlsrv_conn : public sqlsrv_context { col_encryption_option ce_option; // holds the details of what are required to enable column encryption DRIVER_VERSION driver_version; // version of ODBC driver + sqlsrv_malloc_auto_ptr azure_ad_access_token; + // initialize with default values sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) : sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) @@ -1105,6 +1107,7 @@ enum SQLSRV_STMT_OPTIONS { namespace ODBCConnOptions { const char APP[] = "APP"; +const char AccessToken[] = "AccessToken"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; @@ -1140,6 +1143,7 @@ enum SQLSRV_CONN_OPTIONS { SQLSRV_CONN_OPTION_INVALID, SQLSRV_CONN_OPTION_APP, + SQLSRV_CONN_OPTION_ACCESS_TOKEN, SQLSRV_CONN_OPTION_CHARACTERSET, SQLSRV_CONN_OPTION_CONN_POOLING, SQLSRV_CONN_OPTION_DATABASE, @@ -1222,14 +1226,14 @@ struct driver_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; -struct ce_ksp_provider_set_func { - static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); -}; - struct ce_akv_str_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; +struct access_token_set_func { + static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); +}; + // factory to create a connection (since they are subclassed to instantiate statements) typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC ); @@ -1718,6 +1722,8 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_AKV_SECRET_MISSING, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED, + SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, + SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, // Driver specific error codes starts from here. SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index 3a759252e..30f9ee379 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -92,6 +92,7 @@ #define SQL_COPT_SS_TRUSTEDCMKPATHS (SQL_COPT_SS_BASE_EX+13)// List of trusted CMK paths #define SQL_COPT_SS_CEKCACHETTL (SQL_COPT_SS_BASE_EX+14)// Symmetric Key Cache TTL #define SQL_COPT_SS_AUTHENTICATION (SQL_COPT_SS_BASE_EX+15)// The authentication method used for the connection +#define SQL_COPT_SS_ACCESS_TOKEN (SQL_COPT_SS_BASE_EX+16)// The authentication access token used for the connection // SQLColAttributes driver specific defines. // SQLSetDescField/SQLGetDescField driver specific defines. @@ -370,6 +371,12 @@ #pragma warning(disable:4200) #endif +typedef struct AccessToken +{ + unsigned int dataSize; + char data[]; +} ACCESSTOKEN; + // Keystore Provider interface definition typedef struct CEKeystoreContext { diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 30bc65139..95672d4d7 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -180,6 +180,7 @@ namespace SSConnOptionNames { // most of these strings are the same for both the sqlsrv_connect connection option // and the name put into the connection string. MARS is the only one that's different. const char APP[] = "APP"; +const char AccessToken[] = "AccessToken"; const char ApplicationIntent[] = "ApplicationIntent"; const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; @@ -257,6 +258,15 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_STRING, conn_str_append_func::func }, + { + SSConnOptionNames::AccessToken, + sizeof( SSConnOptionNames::AccessToken ), + SQLSRV_CONN_OPTION_ACCESS_TOKEN, + ODBCConnOptions::AccessToken, + sizeof( ODBCConnOptions::AccessToken), + CONN_ATTR_STRING, + access_token_set_func::func + }, { SSConnOptionNames::ApplicationIntent, sizeof( SSConnOptionNames::ApplicationIntent ), diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index e5063f23e..a1b24c2e7 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -420,6 +420,14 @@ ss_error SS_ERRORS[] = { SQLSRV_ERROR_KEYSTORE_INVALID_VALUE, { IMSSP, (SQLCHAR*) "Invalid value for loading Azure Key Vault.", -114, false} }, + { + SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, + { IMSSP, (SQLCHAR*) "When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.", -115, false} + }, + { + SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, + { IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, false} + }, // terminate the list of errors/warnings { UINT_MAX, {} } diff --git a/test/functional/pdo_sqlsrv/access_token.inc b/test/functional/pdo_sqlsrv/access_token.inc new file mode 100644 index 000000000..dbb2f7786 --- /dev/null +++ b/test/functional/pdo_sqlsrv/access_token.inc @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt new file mode 100644 index 000000000..f468ffd6e --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt @@ -0,0 +1,157 @@ +--TEST-- +Test some basics of Azure AD Access Token support +--DESCRIPTION-- +This test also expects certain exceptions to be thrown under some conditions. +--SKIPIF-- + +--FILE-- +getMessage(), $expectedError) === false) { + echo "AzureAD access token test: expected to fail with $msg\n"; + + print_r($exception->getMessage()); + echo "\n"; + } +} + +function connectWithEmptyAccessToken($server) +{ + $dummyToken = ''; + $expectedError = 'The Azure AD Access Token is empty. Expected a byte string.'; + + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'empty token'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo"); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); +} + +function connectWithInvalidOptions($server) +{ + $dummyToken = 'abcde'; + $expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.'; + $message = 'AzureAD access token test: expected to fail with '; + + $uid = ''; + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'empty UID provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", $uid); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $pwd = ''; + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'empty PWD provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $uid = 'uid'; + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'UID provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", $uid); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $pwd = ''; + $connectionInfo = "AccessToken = $dummyToken;"; + $testCase = 'PWD provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $connectionInfo = "Authentication = SqlPassword; AccessToken = $dummyToken;"; + $testCase = 'Authentication keyword'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo"); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); +} + +function simpleTest($conn) +{ + // Create table + $tableName = 'Simple'; + $col1 = 'Some simple string value'; + + dropTable($conn, $tableName); + + $query = "CREATE TABLE $tableName(ID INT IDENTITY(1,1), COL1 VARCHAR(25))"; + $stmt = $conn->query($query); + + // Insert one row + $query = "INSERT INTO $tableName VALUES ('$col1')"; + $stmt = $conn->query($query); + + // Fetch data + $query = "SELECT * FROM $tableName"; + $stmt = $conn->query($query); + + $result = $stmt->fetch(PDO::FETCH_NUM); + $id = $result[0]; + if ($id != 1) { + echo "AzureAD access token test: fetched id $id unexpected\n"; + } + + $field = $result[1]; + if ($field !== $col1) { + echo "AzureAD access token test: fetched value $field unexpected\n"; + } + + dropTable($conn, $tableName); +} + +// First test some error conditions +require_once('MsSetup.inc'); +connectWithInvalidOptions($server); + +// Then, test with an empty access token +connectWithEmptyAccessToken($server); + +// Next, test with a valid access token and perform some simple tasks +require_once('access_token.inc'); +try { + if ($adServer != 'TARGET_AD_SERVER' && $accToken != 'TARGET_ACCESS_TOKEN') { + $connectionInfo = "Database = $adDatabase; AccessToken = $accToken;"; + $conn = new PDO("sqlsrv:server = $adServer; $connectionInfo"); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + simpleTest($conn); + unset($conn); + } +} catch(PDOException $e) { + print_r( $e->getMessage() ); + echo PHP_EOL; +} + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/skipif_azure_ad_acess_token.inc b/test/functional/pdo_sqlsrv/skipif_azure_ad_acess_token.inc new file mode 100644 index 000000000..d59c02bf6 --- /dev/null +++ b/test/functional/pdo_sqlsrv/skipif_azure_ad_acess_token.inc @@ -0,0 +1,41 @@ +getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; +$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0]; + +$isWin = (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN'); +if (!$isWin && $msodbcsqlMaj < 17) { + die("skip: Unsupported ODBC driver version"); +} + +// Now check SQL Server version - exclude this check if running on Azure +if (!$daasMode) { + $stmt = $conn->query("SELECT @@VERSION"); + if ($stmt) { + $ver_string = $stmt->fetch(PDO::FETCH_NUM)[0]; + } else { + die("skip Could not fetch SQL Server version during SKIPIF."); + } + + $version = explode(' ', $ver_string); + + if ($version[3] < '2016') { + die("skip: Wrong version of SQL Server, 2016 or later required"); + } +} + +?> diff --git a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc index 72553974b..376567733 100644 --- a/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/pdo_sqlsrv/skipif_version_less_than_2k16.inc @@ -7,11 +7,11 @@ if (!extension_loaded("pdo_sqlsrv")) { die("skip Extension not loaded"); } -$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' ); +$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN'); -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$conn = new PDO( "sqlsrv:server = $server ;", $uid, $pwd ); +$conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd); if ($conn === false) { die( "skip Could not connect during SKIPIF." ); } diff --git a/test/functional/sqlsrv/access_token.inc b/test/functional/sqlsrv/access_token.inc new file mode 100644 index 000000000..dbb2f7786 --- /dev/null +++ b/test/functional/sqlsrv/access_token.inc @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_azure_ad_acess_token.inc b/test/functional/sqlsrv/skipif_azure_ad_acess_token.inc new file mode 100644 index 000000000..3c7c447cc --- /dev/null +++ b/test/functional/sqlsrv/skipif_azure_ad_acess_token.inc @@ -0,0 +1,45 @@ +$userName, "PWD"=>$userPassword, "Driver" => $driver); + +$conn = sqlsrv_connect($server, $connectionInfo); +if ($conn === false) { + die("skip: Could not connect during SKIPIF."); +} + +$msodbcsqlVer = sqlsrv_client_info($conn)['DriverVer']; +$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0]; + +$isWin = (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN'); + +if (!$isWin && $msodbcsqlMaj < 17) { + die("skip: Unsupported ODBC driver version"); +} + +// Now check SQL Server version - exclude this check if running on Azure +if (!$daasMode) { + // Get SQL Server version + $stmt = sqlsrv_query($conn, "SELECT @@VERSION"); + if (sqlsrv_fetch($stmt)) { + $verString = sqlsrv_get_field($stmt, 0); + } else { + die("skip Could not fetch SQL Server version."); + } + + $version = explode(' ', $verString); + + if ($version[3] < '2016') { + die("skip: Wrong version of SQL Server, 2016 or later required"); + } +} + +?> diff --git a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc index ce06258a2..303a2030b 100644 --- a/test/functional/sqlsrv/skipif_version_less_than_2k16.inc +++ b/test/functional/sqlsrv/skipif_version_less_than_2k16.inc @@ -7,11 +7,11 @@ if (!extension_loaded("sqlsrv")) { die("skip Extension not loaded"); } -$is_win = ( strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN' ); +$is_win = (strtoupper(substr(php_uname('s'),0,3)) === 'WIN'); -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$connectionInfo = array( "UID"=>$userName, "PWD"=>$userPassword ); +$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver); $conn = sqlsrv_connect( $server, $connectionInfo ); if ($conn === false) { diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt new file mode 100644 index 000000000..96c82d63a --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt @@ -0,0 +1,131 @@ +--TEST-- +Test some basics of Azure AD Access Token support +--DESCRIPTION-- +This test also expects certain exceptions to be thrown under some conditions. +--SKIPIF-- + +--FILE-- + "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty token'); + unset($connectionInfo); +} + +function connectWithInvalidOptions($server) +{ + $dummyToken = 'abcde'; + $expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.'; + + $connectionInfo = array("UID"=>"", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty UID provided'); + unset($connectionInfo); + + $connectionInfo = array("PWD"=>"", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty PWD provided'); + unset($connectionInfo); + + $connectionInfo = array("UID"=>"uid", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'UID provided'); + unset($connectionInfo); + + $connectionInfo = array("PWD"=>"pwd", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'PWD provided'); + unset($connectionInfo); + + $connectionInfo = array("Authentication"=>"SqlPassword", "AccessToken" => "$dummyToken"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'Authentication keyword'); + unset($connectionInfo); +} + +function simpleTest($conn) +{ + // Create table + $tableName = 'Simple'; + $col1 = 'Some simple string value'; + + dropTable($conn, $tableName); + + $query = "CREATE TABLE $tableName(ID INT IDENTITY(1,1), COL1 VARCHAR(25))"; + $stmt = sqlsrv_query($conn, $query); + if (!$stmt) { + fatalError("AzureAD access token test: failed to create a table\n"); + } + + // Insert one row + $query = "INSERT INTO $tableName VALUES ('$col1')"; + $stmt = sqlsrv_query($conn, $query); + if (!$stmt) { + fatalError("AzureAD access token test: failed to insert a row\n"); + } + + // Fetch data + $query = "SELECT * FROM $tableName"; + $stmt = sqlsrv_query($conn, $query); + if (!$stmt) { + fatalError("AzureAD access token test: failed to fetch a table\n"); + } + + while (sqlsrv_fetch($stmt)) { + $id = sqlsrv_get_field($stmt, 0); + if ($id != 1) { + fatalError("AzureAD access token test: fetched id $id unexpected\n"); + } + $field = sqlsrv_get_field($stmt, 1); + if ($field !== $col1) { + fatalError("AzureAD access token test: fetched value $field unexpected\n"); + } + } + + dropTable($conn, $tableName); +} + +// First test some error conditions +connectWithInvalidOptions($server); + +// Then, test with an empty access token +connectWithEmptyAccessToken($server); + +// Next, test with a valid access token and perform some simple tasks +require_once('access_token.inc'); +if ($adServer != 'TARGET_AD_SERVER' && $accToken != 'TARGET_ACCESS_TOKEN') { + $connectionInfo = array("Database"=>$adDatabase, "AccessToken"=>$accToken); + + $conn = sqlsrv_connect($adServer, $connectionInfo); + if ($conn === false) { + fatalError("Could not connect with Azure AD AccessToken.\n"); + } else { + simpleTest($conn); + + sqlsrv_close($conn); + } +} + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file From 7521f095ee0f72c4502237d53886f32d12db1af8 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 17 Sep 2018 16:24:52 -0700 Subject: [PATCH 050/249] Feature request - new PDO_STMT_OPTION_FETCHES_DATETIME_TYPE flag for pdo_sqlsrv to return datetime as objects (#842) * Feature request - issue 648 * Fixed constructor for field_cache and added another test * Added tests for FETCH_BOUND * Added a new test for output param * Modified output param test to set attributes differently * Removed a useless helped function in a test * Combined two new tests into one as per review * Uncommented dropTable --- source/pdo_sqlsrv/pdo_dbh.cpp | 19 +- source/pdo_sqlsrv/pdo_init.cpp | 1 + source/pdo_sqlsrv/pdo_stmt.cpp | 130 ++++++---- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 11 +- source/shared/core_stmt.cpp | 3 +- .../pdo_fetch_datetime_as_output_param.phpt | 87 +++++++ .../pdo_fetch_datetime_time_as_objects.phpt | 238 ++++++++++++++++++ .../pdo_fetch_datetime_time_nulls.phpt | 163 ++++++++++++ 8 files changed, 599 insertions(+), 53 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_datetime_as_output_param.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 55801de01..6e761b04c 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -80,6 +80,7 @@ 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 }; // List of all the statement options supported by this driver. @@ -93,6 +94,7 @@ const stmt_option PDO_STMT_OPTS[] = { { NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr( new stmt_option_buffered_query_limit ) }, { NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr( new stmt_option_emulate_prepares ) }, { NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr( new stmt_option_fetch_numeric ) }, + { NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr( new stmt_option_fetch_datetime ) }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -495,7 +497,8 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo direct_query( false ), query_timeout( QUERY_TIMEOUT_INVALID ), client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), - fetch_numeric( false ) + fetch_numeric( false ), + fetch_datetime( false ) { if( client_buffer_max_size < 0 ) { client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; @@ -1061,6 +1064,10 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout driver_dbh->fetch_numeric = (zend_is_true(val)) ? true : false; break; + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + driver_dbh->fetch_datetime = (zend_is_true(val)) ? true : false; + break; + // Not supported case PDO_ATTR_FETCH_TABLE_NAMES: case PDO_ATTR_FETCH_CATALOG_NAMES: @@ -1212,6 +1219,12 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout break; } + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + { + ZVAL_BOOL( return_value, driver_dbh->fetch_datetime ); + break; + } + default: { THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); @@ -1569,6 +1582,10 @@ void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; break; + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE; + break; + default: CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { throw core::CoreException(); diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index f98799b89..cd6fe4872 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -285,6 +285,7 @@ namespace { { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, { "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 }, // 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 diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index de5c35557..22315bf8d 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -51,6 +51,9 @@ inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori ( _In_ enum pdo_fetch_orienta // for list of supported pdo types. SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _In_ enum pdo_param_type pdo_type TSRMLS_DC ) { + pdo_sqlsrv_stmt *pdo_stmt = static_cast(driver_stmt); + SQLSRV_ASSERT(pdo_stmt != NULL, "pdo_type_to_sqlsrv_php_type: pdo_stmt object was null"); + switch( pdo_type ) { case PDO_PARAM_BOOL: @@ -64,9 +67,12 @@ SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _I return SQLSRV_PHPTYPE_NULL; case PDO_PARAM_LOB: - // TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented. - return SQLSRV_PHPTYPE_STRING; - + if (pdo_stmt->fetch_datetime) { + return SQLSRV_PHPTYPE_DATETIME; + } else { + // TODO: This will eventually be changed to SQLSRV_PHPTYPE_STREAM when output streaming is implemented. + return SQLSRV_PHPTYPE_STRING; + } case PDO_PARAM_STMT: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED ); break; @@ -213,61 +219,63 @@ void meta_data_free( _Inout_ field_meta_data* meta ) zval convert_to_zval( _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val, _In_opt_ SQLLEN field_len ) { zval out_zval; - ZVAL_UNDEF( &out_zval ); + ZVAL_UNDEF(&out_zval); - switch( sqlsrv_php_type ) { - - case SQLSRV_PHPTYPE_INT: - case SQLSRV_PHPTYPE_FLOAT: - { - if( *in_val == NULL ) { - ZVAL_NULL( &out_zval ); - } - else { + switch (sqlsrv_php_type) { - if( sqlsrv_php_type == SQLSRV_PHPTYPE_INT ) { - ZVAL_LONG( &out_zval, **( reinterpret_cast( in_val ))); - } - else { - ZVAL_DOUBLE( &out_zval, **( reinterpret_cast( in_val ))); - } - } + case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_FLOAT: + { + if (*in_val == NULL) { + ZVAL_NULL(&out_zval); + } + else { - if( *in_val ) { - sqlsrv_free( *in_val ); + if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) { + ZVAL_LONG(&out_zval, **(reinterpret_cast(in_val))); + } + else { + ZVAL_DOUBLE(&out_zval, **(reinterpret_cast(in_val))); } - - break; } - case SQLSRV_PHPTYPE_STRING: - case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented - { + if (*in_val) { + sqlsrv_free(*in_val); + } - if( *in_val == NULL ) { + break; + } + case SQLSRV_PHPTYPE_STRING: + case SQLSRV_PHPTYPE_STREAM: // TODO: this will be moved when output streaming is implemented + { + if (*in_val == NULL) { - ZVAL_NULL( &out_zval ); - } - else { + ZVAL_NULL(&out_zval); + } + else { - ZVAL_STRINGL( &out_zval, reinterpret_cast( *in_val ), field_len ); - sqlsrv_free( *in_val ); - } - break; + ZVAL_STRINGL(&out_zval, reinterpret_cast(*in_val), field_len); + sqlsrv_free(*in_val); } - - case SQLSRV_PHPTYPE_DATETIME: - DIE( "Unsupported php type" ); - out_zval = *( reinterpret_cast( *in_val )); - break; + break; + } + case SQLSRV_PHPTYPE_DATETIME: + if (*in_val == NULL) { - case SQLSRV_PHPTYPE_NULL: - ZVAL_NULL( &out_zval ); - break; + ZVAL_NULL(&out_zval); + } + else { - default: - DIE( "Unknown php type" ); - break; + out_zval = *(reinterpret_cast(*in_val)); + sqlsrv_free(*in_val); + } + break; + case SQLSRV_PHPTYPE_NULL: + ZVAL_NULL(&out_zval); + break; + default: + DIE("Unknown php type"); + break; } return out_zval; @@ -339,6 +347,11 @@ void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false; } +void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->fetch_datetime = ( zend_is_true( value_z )) ? true : false; +} // log a function entry point #ifndef _WIN32 @@ -865,6 +878,10 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In driver_stmt->fetch_numeric = ( zend_is_true( val )) ? true : false; break; + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + driver_stmt->fetch_datetime = ( zend_is_true( val )) ? true : false; + break; + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -946,6 +963,12 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; } + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + { + ZVAL_BOOL( return_value, driver_stmt->fetch_datetime ); + break; + } + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -1365,6 +1388,17 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; } break; + case SQL_TYPE_DATE: + case SQL_SS_TIMESTAMPOFFSET: + case SQL_SS_TIME2: + case SQL_TYPE_TIMESTAMP: + if ( this->fetch_datetime ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + } + break; case SQL_BIGINT: case SQL_CHAR: case SQL_DECIMAL: @@ -1373,10 +1407,6 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, case SQL_WCHAR: case SQL_VARCHAR: case SQL_WVARCHAR: - case SQL_TYPE_DATE: - case SQL_SS_TIMESTAMPOFFSET: - case SQL_SS_TIME2: - case SQL_TYPE_TIMESTAMP: case SQL_LONGVARCHAR: case SQL_WLONGVARCHAR: case SQL_SS_XML: diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 38e4cec42..160156b01 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -48,6 +48,7 @@ enum PDO_SQLSRV_ATTR { SQLSRV_ATTR_CURSOR_SCROLL_TYPE, SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, + SQLSRV_ATTR_FETCHES_DATETIME_TYPE }; // valid set of values for TransactionIsolation connection option @@ -203,6 +204,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { long query_timeout; zend_long client_buffer_max_size; bool fetch_numeric; + bool fetch_datetime; pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); }; @@ -241,6 +243,10 @@ struct stmt_option_fetch_numeric : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); }; +struct stmt_option_fetch_datetime : public stmt_option_functor { + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; // a core layer pdo stmt object. This object inherits and overrides the callbacks necessary @@ -253,11 +259,13 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { direct_query_subst_string_len( 0 ), placeholders(NULL), bound_column_param_types( NULL ), - fetch_numeric( false ) + fetch_numeric( false ), + fetch_datetime( false ) { pdo_sqlsrv_dbh* db = static_cast( c ); direct_query = db->direct_query; fetch_numeric = db->fetch_numeric; + fetch_datetime = db->fetch_datetime; } virtual ~pdo_sqlsrv_stmt( void ); @@ -275,6 +283,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { std::vector > current_meta_data; pdo_param_type* bound_column_param_types; bool fetch_numeric; + bool fetch_datetime; }; diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 31f2da45e..4675589d7 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -36,7 +36,8 @@ struct field_cache { : type( t ) { // if the value is NULL, then just record a NULL pointer - if( field_value != NULL ) { + // field_len may be equal to SQL_NULL_DATA even when field_value is not null + if( field_value != NULL && field_len != SQL_NULL_DATA) { value = sqlsrv_malloc( field_len ); memcpy_s( value, field_len, field_value, field_len ); len = field_len; diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_as_output_param.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_as_output_param.phpt new file mode 100644 index 000000000..9cc1bc3b3 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_as_output_param.phpt @@ -0,0 +1,87 @@ +--TEST-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE and datetimes as output params +--DESCRIPTION-- +Do not support returning DateTime objects as output parameters. Setting attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE to true should have no effect. +--SKIPIF-- + +--FILE-- + false); + $conn = connect("", $attr); + + // Generate input values for the test table + $query = 'SELECT SYSDATETIME(), SYSDATETIMEOFFSET(), CONVERT(time, CURRENT_TIMESTAMP)'; + $stmt = $conn->query($query); + $values = $stmt->fetch(PDO::FETCH_NUM); + + // create a test table with the above input date time values + $tableName = "TestDateTimeOutParam"; + $columns = array('c1', 'c2', 'c3'); + $dataTypes = array("datetime2", "datetimeoffset", "time"); + + $colMeta = array(new ColumnMeta($dataTypes[0], $columns[0]), + new ColumnMeta($dataTypes[1], $columns[1]), + new ColumnMeta($dataTypes[2], $columns[2])); + createTable($conn, $tableName, $colMeta); + + $query = "INSERT INTO $tableName VALUES(?, ?, ?)"; + $stmt = $conn->prepare($query); + for ($i = 0; $i < count($columns); $i++) { + $stmt->bindParam($i+1, $values[$i], PDO::PARAM_LOB); + } + $stmt->execute(); + + $lobException = 'An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.'; + + for ($i = 0; $i < count($columns); $i++) { + // create the stored procedure first + $storedProcName = "spDateTimeOutParam" . $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 type PDO::PARAM_STR + $dateStr = ''; + $outSql = getCallProcSqlPlaceholders($storedProcName, 1); + $options = array(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE => true); + $stmt = $conn->prepare($outSql, $options); + $stmt->bindParam(1, $dateStr, PDO::PARAM_STR, 1024); + $stmt->execute(); + + if ($dateStr != $values[$i]) { + echo "Expected $values[$i] for column ' . ($i+1) .' but got: "; + var_dump($dateStr); + } + + // for output param type PDO::PARAM_LOB it should fail with the correct exception + try { + $stmt->bindParam(1, $dateStr, PDO::PARAM_LOB, 1024); + $stmt->execute(); + echo "Expected this to fail\n"; + } catch (PDOException $e) { + $message = $e->getMessage(); + $matched = strpos($message, $lobException); + if (!$matched) { + var_dump($e->errorInfo); + } + } + + dropProc($conn, $storedProcName); + } + + dropTable($conn, $tableName); + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt new file mode 100644 index 000000000..ab8ba5ece --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt @@ -0,0 +1,238 @@ +--TEST-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for date, time and datetime columns +--DESCRIPTION-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for datetime, datetime2, +smalldatetime, datetimeoffset and time columns. The input values are based on current +timestamp and they are retrieved either as strings or date time objects. Note that the +existing attributes ATTR_STRINGIFY_FETCHES and SQLSRV_ATTR_FETCHES_NUMERIC_TYPE +should have no effect on data retrieval. +--SKIPIF-- + +--FILE-- +format('Y-m-d H:i:s.u'); + + // actual datetime value from date time object to string + $dtActual = date_format($dtObj, 'Y-m-d H:i:s.u'); + if ($dtActual != $dtExpected) { + echo "Expected $dtExpected for column $column but the actual value was $dtActual\n"; + } +} + +function runTest($conn, $query, $columns, $values, $useBuffer = false) +{ + // fetch the date time values as strings or date time objects + // prepare with or without buffered cursor + $options = array(); + if ($useBuffer) { + $options = array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, + PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED); + } + + // fetch_numeric off, fetch_datetime off + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $obj = $stmt->fetch(PDO::FETCH_OBJ); + checkStringValues($obj, $columns, $values); + + // fetch_numeric off, fetch_datetime on + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkDTObjectValues($row, $columns, $values, PDO::FETCH_ASSOC); + + // fetch_numeric on, fetch_datetime on + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_BOTH); + checkDTObjectValues($row, $columns, $values, PDO::FETCH_BOTH); + + // ATTR_STRINGIFY_FETCHES should have no effect when fetching date time objects + // Setting it to true only converts numeric values to strings when fetching + // See http://www.php.net/manual/en/pdo.setattribute.php for details + // stringify on, fetch_numeric off, fetch_datetime on + $conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $i = 0; + do { + $stmt->execute(); + $dtObj = $stmt->fetchColumn($i); + checkColumnDTValue($i, $columns[$i], $values, $dtObj); + } while (++$i < count($columns)); + + // reset stringify to off + // fetch_numeric off, fetch_datetime off + $conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_OBJ); + checkStringValues($obj, $columns, $values); + + // conn attribute fetch_datetime on, but statement attribute fetch_datetime off -- + // expected strings to be returned because statement attribute overrides the + // connection attribute + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt->execute(); + $obj = $stmt->fetch(PDO::FETCH_OBJ); + checkStringValues($obj, $columns, $values); + + // conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime on -- + // expected datetime objects to be returned (this time no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkDTObjectValues($row, $columns, $values, PDO::FETCH_ASSOC); + + // likewise, conn attribute fetch_datetime off, but statement attribute + // fetch_datetime on -- expected datetime objects to be returned + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_BOTH); + checkDTObjectValues($row, $columns, $values, PDO::FETCH_BOTH); + + // conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime off -- + // expected strings to be returned (again no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt->execute(); + $obj = $stmt->fetch(PDO::FETCH_LAZY); + checkStringValues($obj, $columns, $values); + + // last test: set statement attribute fetch_datetime on with no change to + // prepared statement -- expected datetime objects to be returned + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $i = 0; + do { + $stmt->execute(); + $dtObj = $stmt->fetchColumn($i); + checkColumnDTValue($i, $columns[$i], $values, $dtObj); + } while (++$i < count($columns)); + + // keep the same settings but test with FETCH_BOUND + for ($i = 0; $i < count($columns); $i++) { + $dateObj = null; + $stmt->execute(); + $stmt->bindColumn($i + 1, $dateObj, PDO::PARAM_LOB); + $row = $stmt->fetch(PDO::FETCH_BOUND); + checkColumnDTValue($i, $columns[$i], $values, $dateObj); + } + + // redo the test but with fetch_datetime off + // expected strings to be returned + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + for ($i = 0; $i < count($columns); $i++) { + $dateStr = null; + $stmt->execute(); + $stmt->bindColumn($i + 1, $dateStr); + $row = $stmt->fetch(PDO::FETCH_BOUND); + if ($dateStr != $values[$i]) { + $col = $columns[$i]; + echo "Expected $values[$i] for column $col but the bound value was: "; + var_dump($dateStr); + } + } +} + +try { + date_default_timezone_set('America/Los_Angeles'); + + $conn = connect(); + + // Generate input values for the test table + $query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(), + CONVERT(smalldatetime, SYSDATETIME()), + CONVERT(datetime, SYSDATETIME()), + SYSDATETIMEOFFSET(), + CONVERT(time, SYSDATETIME())'; + + $stmt = $conn->query($query); + $values = $stmt->fetch(PDO::FETCH_NUM); + + // create a test table with the above input date time values + $tableName = "TestDateTimeOffset"; + $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); + $dataTypes = array('date', 'datetime2', 'smalldatetime', 'datetime', 'datetimeoffset', 'time'); + + $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); + + $query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?)"; + $stmt = $conn->prepare($query); + for ($i = 0; $i < count($columns); $i++) { + $stmt->bindParam($i+1, $values[$i], PDO::PARAM_LOB); + } + $stmt->execute(); + + $query = "SELECT * FROM $tableName"; + + runTest($conn, $query, $columns, $values); + runTest($conn, $query, $columns, $values, true); + + dropTable($conn, $tableName); + + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt new file mode 100644 index 000000000..9bffdf751 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt @@ -0,0 +1,163 @@ +--TEST-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for datetime types with null values +--DESCRIPTION-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE for different datetime types with +null values. Whether retrieved as strings or date time objects should return NULLs. +--SKIPIF-- + +--FILE-- + PDO::CURSOR_SCROLL, + PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED); + } + + // fetch_numeric off, fetch_datetime off + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + checkNullStrings($row, $columns); + + // fetch_numeric off, fetch_datetime on + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, false); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkNullDTObjects($row, $columns, PDO::FETCH_ASSOC); + + // fetch_numeric on, fetch_datetime on + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_BOTH); + checkNullDTObjects($row, $columns, PDO::FETCH_BOTH); + + // conn attribute fetch_datetime on, but statement attribute fetch_datetime off -- + // expected strings to be returned because statement attribute overrides the + // connection attribute + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + checkNullStrings($row, $columns); + + // conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime on -- + // expected datetime objects to be returned (this time no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkNullDTObjects($row, $columns, PDO::FETCH_ASSOC); + + // likewise, conn attribute fetch_datetime off, but statement attribute + // fetch_datetime on -- expected datetime objects to be returned + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_BOTH); + checkNullDTObjects($row, $columns, PDO::FETCH_BOTH); + + // conn attribute fetch_datetime unchanged, but statement attribute fetch_datetime off -- + // expected strings to be returned (again no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, false); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + checkNullStrings($row, $columns); + + // last test: set statement attribute fetch_datetime on with no change to + // prepared statement -- expected datetime objects to be returned + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt->execute(); + $i = 0; + do { + $stmt->execute(); + $dtObj = $stmt->fetchColumn($i); + if (!is_null($dtObj)) { + echo "Expected NULL for column " . ($i + 1) . " but got: "; + var_dump($dtObj); + } + } while (++$i < count($columns)); +} + +try { + $conn = connect(); + + // create a test table + $tableName = "TestNullDateTime"; + $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); + $colMeta = array(new ColumnMeta('date', $columns[0]), + new ColumnMeta('datetime', $columns[1]), + new ColumnMeta('smalldatetime', $columns[2]), + new ColumnMeta('datetime2', $columns[3]), + new ColumnMeta('datetimeoffset', $columns[4]), + new ColumnMeta('time', $columns[5])); + createTable($conn, $tableName, $colMeta); + + $value = null; + $query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?)"; + $stmt = $conn->prepare($query); + for ($i = 0; $i < count($columns); $i++) { + $stmt->bindParam($i+1, $value, PDO::PARAM_NULL); + } + $stmt->execute(); + + $query = "SELECT * FROM $tableName"; + + runTest($conn, $query, $columns); + runTest($conn, $query, $columns, true); + + dropTable($conn, $tableName); + + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done From 902a03263e67023ce47de6383da3a87ec7a0c73f Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 17 Sep 2018 16:25:02 -0700 Subject: [PATCH 051/249] Feature request - add ReturnDatesAsStrings option to statement level for sqlsrv (#844) * Added ReturnDatesAsStrings option to the statement level * Added new tests for ReturnDatesAsStrings at statement level * Added more datetime types as per review --- source/shared/core_sqlsrv.h | 7 + source/shared/core_stmt.cpp | 10 + source/sqlsrv/conn.cpp | 11 +- source/sqlsrv/stmt.cpp | 9 +- .../sqlsrv_statement_datetimes_as_nulls.phpt | 120 +++++++++++ ...sqlsrv_statement_datetimes_as_strings.phpt | 197 ++++++++++++++++++ ...lsrv_statement_datetimes_output_param.phpt | 104 +++++++++ 7 files changed, 453 insertions(+), 5 deletions(-) create mode 100644 test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 78215f339..5f7fcb899 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1098,6 +1098,7 @@ enum SQLSRV_STMT_OPTIONS { SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, SQLSRV_STMT_OPTION_SCROLLABLE, SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, + SQLSRV_STMT_OPTION_DATE_AS_STRING, // Driver specific connection options SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, @@ -1282,6 +1283,11 @@ struct stmt_option_buffered_query_limit : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; +struct stmt_option_date_as_string : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); +}; + // used to hold the table for statment options struct stmt_option { @@ -1393,6 +1399,7 @@ struct sqlsrv_stmt : public sqlsrv_context { // last results unsigned long query_timeout; // maximum allowed statement execution time zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) + bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings // holds output pointers for SQLBindParameter // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 4675589d7..0a87784a1 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -141,6 +141,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error last_field_index( -1 ), past_next_result_end( false ), query_timeout( QUERY_TIMEOUT_INVALID ), + date_as_string(false), buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ), param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte send_streams_at_exec( true ), @@ -1404,6 +1405,15 @@ void stmt_option_buffered_query_limit:: operator()( _Inout_ sqlsrv_stmt* stmt, s core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); } +void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC ) +{ + if (zend_is_true(value_z)) { + stmt->date_as_string = true; + } + else { + stmt->date_as_string = false; + } +} // internal function to release the active stream. Called by each main API function // that will alter the statement and cancel any retrieval of data from a stream. diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 95672d4d7..8178fbc65 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -173,6 +173,7 @@ namespace SSStmtOptionNames { const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec"; const char SCROLLABLE[] = "Scrollable"; const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT; + const char DATE_AS_STRING[] = "ReturnDatesAsStrings"; } namespace SSConnOptionNames { @@ -243,6 +244,12 @@ const stmt_option SS_STMT_OPTS[] = { SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, std::unique_ptr( new stmt_option_buffered_query_limit ) }, + { + SSStmtOptionNames::DATE_AS_STRING, + sizeof( SSStmtOptionNames::DATE_AS_STRING ), + SQLSRV_STMT_OPTION_DATE_AS_STRING, + std::unique_ptr( new stmt_option_date_as_string ) + }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -988,7 +995,7 @@ PHP_FUNCTION( sqlsrv_prepare ) // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( ss_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); @@ -1111,7 +1118,7 @@ PHP_FUNCTION( sqlsrv_query ) // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( ss_stmt_options_ht ); - core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 3 /* # of buckets */, ZVAL_PTR_DTOR, + core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 3f2584990..819798c03 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -129,6 +129,10 @@ ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ fetch_fields_count ( 0 ) { core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC ); + + // initialize date_as_string based on the corresponding connection option + ss_sqlsrv_conn* ss_conn = static_cast(conn); + date_as_string = ss_conn->date_as_string; } ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) @@ -230,7 +234,7 @@ sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _ case SQL_SS_TIMESTAMPOFFSET: case SQL_SS_TIME2: case SQL_TYPE_TIMESTAMP: - if( reinterpret_cast( this->conn )->date_as_string ) { + if (this->date_as_string) { ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; ss_phptype.typeinfo.encoding = this->conn->encoding(); } @@ -1678,8 +1682,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ case SQL_SS_TIME2: case SQL_TYPE_TIMESTAMP: { - ss_sqlsrv_conn* c = static_cast( stmt->conn ); - if( c->date_as_string ) { + if (stmt->date_as_string) { sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); } diff --git a/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt new file mode 100644 index 000000000..e6347fb0c --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt @@ -0,0 +1,120 @@ +--TEST-- +Test retrieving null datetime values with statement option ReturnDatesAsStrings as true +--DESCRIPTION-- +Test retrieving null datetime values with statement option ReturnDatesAsStrings as true, +which is false by default. Whether retrieved as strings or date time objects should return +NULLs. +--SKIPIF-- + +--FILE-- + 'buffered', 'ReturnDatesAsStrings' => true); + } else { + $options = array('ReturnDatesAsStrings' => true); + } + + $size = count($columns); + $stmt = sqlsrv_prepare($conn, $query, array(), $options); + // Fetch by getting one field at a time + sqlsrv_execute($stmt); + if( sqlsrv_fetch( $stmt ) === false) { + fatalError("Failed in retrieving data\n"); + } + for ($i = 0; $i < $size; $i++) { + $field = sqlsrv_get_field($stmt, $i); // expect string + if (!is_null($field)) { + echo "Expected null for column $columns[$i] but got: "; + var_dump($field); + } + } + + // Fetch row as an object + sqlsrv_execute($stmt); + $object = sqlsrv_fetch_object($stmt); + + $objArray = (array)$object; // turn the object into an associated array + for ($i = 0; $i < $size; $i++) { + $col = $columns[$i]; + $val = $objArray[$col]; + + if (!is_null($val)) { + echo "Expected null for column $columns[$i] but got: "; + var_dump($val); + } + } +} + +function createTestTable($conn, $tableName, $columns) +{ + // Create the test table of date and time columns + $dataTypes = array('date', 'smalldatetime', 'datetime', 'datetime2', 'datetimeoffset', 'time'); + + $colMeta = array(new AE\ColumnMeta($dataTypes[0], $columns[0]), + new AE\ColumnMeta($dataTypes[1], $columns[1]), + new AE\ColumnMeta($dataTypes[2], $columns[2]), + new AE\ColumnMeta($dataTypes[3], $columns[3]), + new AE\ColumnMeta($dataTypes[4], $columns[4]), + new AE\ColumnMeta($dataTypes[5], $columns[5])); + AE\createTable($conn, $tableName, $colMeta); + + // Insert null values + $inputData = array($colMeta[0]->colName => null, + $colMeta[1]->colName => null, + $colMeta[2]->colName => null, + $colMeta[3]->colName => null, + $colMeta[4]->colName => null, + $colMeta[5]->colName => null); + $stmt = AE\insertRow($conn, $tableName, $inputData); + if (!$stmt) { + fatalError("Failed to insert data.\n"); + } + sqlsrv_free_stmt($stmt); +} + +function runTest($tableName, $columns, $dateAsString) +{ + // Connect + $conn = connect(array('ReturnDatesAsStrings' => $dateAsString)); + if (!$conn) { + fatalError("Could not connect.\n"); + } + + $query = "SELECT * FROM $tableName"; + testFetch($conn, $query, $columns); + testFetch($conn, $query, $columns, true); + + sqlsrv_close($conn); +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); + +$tableName = "TestNullDateTime"; +$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); + +// Connect +$conn = connect(); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +createTestTable($conn, $tableName, $columns); + +runTest($tableName, $columns, true); +runTest($tableName, $columns, false); + +dropTable($conn, $tableName); + +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt new file mode 100644 index 000000000..23a27a871 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt @@ -0,0 +1,197 @@ +--TEST-- +Test retrieving datetime values with statement option ReturnDatesAsStrings set to true +--DESCRIPTION-- +Test retrieving datetime values with statement option ReturnDatesAsStrings set to true, +which is false by default. The statement option should override the corresponding +connection option ReturnDatesAsStrings. +--SKIPIF-- + +--FILE-- +format('Y-m-d H:i:s.u'); + + // actual datetime value from date time object to string + $dtActual = date_format($actualObj, 'Y-m-d H:i:s.u'); + + return ($dtActual === $dtExpected); +} + +function testNoOption($conn, $tableName, $inputs, $exec) +{ + // Without the statement option, should return datetime values as strings + // because the connection option ReturnDatesAsStrings is set to true + $query = "SELECT * FROM $tableName"; + if ($exec) { + $stmt = sqlsrv_query($conn, $query); + } else { + $stmt = sqlsrv_prepare($conn, $query); + sqlsrv_execute($stmt); + } + + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + + // Compare values only + $diffs = array_diff($inputs, $results); + if (!empty($diffs)) { + echo 'The results are different from the input values: '; + print_r($diffs); + } +} + +function testStmtOption($conn, $tableName, $inputs, $stmtDateAsStr) +{ + // The statement option should always override the connection option + $query = "SELECT * FROM $tableName"; + $options = array('ReturnDatesAsStrings' => $stmtDateAsStr); + $stmt = sqlsrv_query($conn, $query, array(), $options); + + if ($stmtDateAsStr) { + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + + // Compare values only + $diffs = array_diff($inputs, $results); + if (!empty($diffs)) { + echo 'The results are different from the input values: '; + print_r($diffs); + } + } else { + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + + // Expect DateTime Objects in $results + for ($i = 0; $i < count($inputs); $i++) { + if (is_object($results[$i])) { + $matched = compareDateTime($inputs[$i], $results[$i]); + if (!$matched) { + echo "Expected a DateTime object of $inputs[$i] but got: \n"; + var_dump($results[$i]); + } + } else { + echo "Expect a DateTime object but got $results[$i]\n"; + } + } + } +} + +function testFetching($conn, $tableName, $inputs, $columns, $withBuffer) +{ + // The statement option ReturnDatesAsStrings set to true + // Test different fetching + $query = "SELECT * FROM $tableName"; + if ($withBuffer){ + $options = array('Scrollable' => 'buffered', 'ReturnDatesAsStrings' => true); + } else { + $options = array('ReturnDatesAsStrings' => true); + } + + $size = count($inputs); + $stmt = sqlsrv_prepare($conn, $query, array(), $options); + + // Fetch by getting one field at a time + sqlsrv_execute($stmt); + + if( sqlsrv_fetch( $stmt ) === false) { + fatalError("Failed in retrieving data\n"); + } + for ($i = 0; $i < $size; $i++) { + $field = sqlsrv_get_field($stmt, $i); // expect string + if ($field != $inputs[$i]) { + echo "Expected $inputs[$i] for column $columns[$i] but got: "; + var_dump($field); + } + } + + // Fetch row as an object + sqlsrv_execute($stmt); + $object = sqlsrv_fetch_object($stmt); + + $objArray = (array)$object; // turn the object into an associated array + for ($i = 0; $i < $size; $i++) { + $col = $columns[$i]; + $val = $objArray[$col]; + + if ($val != $inputs[$i]) { + echo "Expected $inputs[$i] for column $columns[$i] but got: "; + var_dump($val); + } + } +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); +date_default_timezone_set('America/Los_Angeles'); + +// Connect with ReturnDatesAsStrings option set to true +$conn = connect(array('ReturnDatesAsStrings' => true)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// Generate input values for the test table +$query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(), + CONVERT(smalldatetime, SYSDATETIME()), + CONVERT(datetime, SYSDATETIME()), + SYSDATETIMEOFFSET(), + CONVERT(time, SYSDATETIME())'; +$stmt = sqlsrv_query($conn, $query); +$values = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + +// Create the test table of date and time columns +$tableName = 'StmtDateAsString'; +$columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); +$dataTypes = array('date', 'datetime2', 'smalldatetime', 'datetime', 'datetimeoffset', 'time'); + +$colMeta = array(new AE\ColumnMeta($dataTypes[0], $columns[0]), + new AE\ColumnMeta($dataTypes[1], $columns[1]), + new AE\ColumnMeta($dataTypes[2], $columns[2]), + new AE\ColumnMeta($dataTypes[3], $columns[3]), + new AE\ColumnMeta($dataTypes[4], $columns[4]), + new AE\ColumnMeta($dataTypes[5], $columns[5])); +AE\createTable($conn, $tableName, $colMeta); + +// Insert data values +$inputData = array($colMeta[0]->colName => $values[0], + $colMeta[1]->colName => $values[1], + $colMeta[2]->colName => $values[2], + $colMeta[3]->colName => $values[3], + $colMeta[4]->colName => $values[4], + $colMeta[5]->colName => $values[5]); +$stmt = AE\insertRow($conn, $tableName, $inputData); +if (!$stmt) { + fatalError("Failed to insert data.\n"); +} +sqlsrv_free_stmt($stmt); + +// Do not set ReturnDatesAsStrings at statement level +testNoOption($conn, $tableName, $values, true); +testNoOption($conn, $tableName, $values, false); + +// Set ReturnDatesAsStrings to false at statement level +testStmtOption($conn, $tableName, $values, false); + +sqlsrv_close($conn); + +// Now connect but with ReturnDatesAsStrings option set to false +$conn = connect(array('ReturnDatesAsStrings' => false)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// Set ReturnDatesAsStrings to true at statement level +testStmtOption($conn, $tableName, $values, true); + +// Test fetching by setting ReturnDatesAsStrings to true at statement level +testFetching($conn, $tableName, $values, $columns, true); +testFetching($conn, $tableName, $values, $columns, false); + +dropTable($conn, $tableName); +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt b/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt new file mode 100644 index 000000000..a1aa717b5 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt @@ -0,0 +1,104 @@ +--TEST-- +Test retrieving datetime values as output params with statement option ReturnDatesAsStrings +--DESCRIPTION-- +Test retrieving datetime values as output params with statement option ReturnDatesAsStrings +with sqlsrv_prepare. When ReturnDatesAsStrings option is false, expect an error to return. +--SKIPIF-- + +--FILE-- + $dateAsString)); + if (!$stmt) { + fatalError("Failed when preparing to call $storedProcName"); + } + $result = sqlsrv_execute($stmt); + if ($dateAsString) { + // Expect to succeed when returning a DateTime value as a string + // The output param value should be the same as the input value + if (!$result) { + fatalError("Failed when invoking $storedProcName"); + } + if ($outDateStr != $inputValue) { + echo "Expected $inputValue but got $outDateStr\n"; + } + } else { + // Expect to fail with an error message because setting a DateTime object as the + // output parameter is not allowed + if ($result) { + fatalError("Returning DateTime as output param is expected to fail!"); + } + // Check if the error message is the expected one + $error = sqlsrv_errors()[0]['message']; + $message = 'An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters'; + if (strpos($error, $message) === false) { + print_r(sqlsrv_errors()); + } + } +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); +date_default_timezone_set('America/Los_Angeles'); + +// Connect with ReturnDatesAsStrings option set to true +$conn = connect(array('ReturnDatesAsStrings' => true)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// Generate input values for the test table +$query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(), SYSDATETIMEOFFSET(), CONVERT(time, CURRENT_TIMESTAMP)'; +$stmt = sqlsrv_query($conn, $query); +$values = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + +// Create the test table of date and time columns +$tableName = 'OuputParamDateAsString'; +$columns = array('c1', 'c2', 'c3', 'c4'); +$dataTypes = array('date', 'datetime2', 'datetimeoffset', 'time'); +$sqlTypes = array(SQLSRV_SQLTYPE_DATE, + SQLSRV_SQLTYPE_DATETIME2, + SQLSRV_SQLTYPE_DATETIMEOFFSET, + SQLSRV_SQLTYPE_TIME); +$colMeta = array(new AE\ColumnMeta($dataTypes[0], $columns[0]), + new AE\ColumnMeta($dataTypes[1], $columns[1]), + new AE\ColumnMeta($dataTypes[2], $columns[2]), + new AE\ColumnMeta($dataTypes[3], $columns[3])); +AE\createTable($conn, $tableName, $colMeta); + +// Insert data values +$inputData = array($colMeta[0]->colName => $values[0], + $colMeta[1]->colName => $values[1], + $colMeta[2]->colName => $values[2], + $colMeta[3]->colName => $values[3]); +$stmt = AE\insertRow($conn, $tableName, $inputData); +if (!$stmt) { + fatalError("Failed to insert data.\n"); +} +sqlsrv_free_stmt($stmt); + +for ($i = 0; $i < count($columns); $i++) { + // create the stored procedure first + $storedProcName = "spDateTimeOutParam" . $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 + runTest($conn, $storedProcName, $values[$i], $sqlTypes[$i], true); + runTest($conn, $storedProcName, $values[$i], $sqlTypes[$i], false); +} + +dropTable($conn, $tableName); +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done From 88dfea339a6935a6480eae6db15a2bd70f753380 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 18 Sep 2018 08:20:43 -0700 Subject: [PATCH 052/249] Updated version 5.4.0-preview (#846) * Updated version 5.4.0-preview * Replaced 5.3 with 5.4 --- source/pdo_sqlsrv/config.m4 | 2 +- source/pdo_sqlsrv/config.w32 | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- source/pdo_sqlsrv/pdo_init.cpp | 2 +- source/pdo_sqlsrv/pdo_parser.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 +- source/pdo_sqlsrv/template.rc | 2 +- source/shared/FormattedPrint.cpp | 2 +- source/shared/FormattedPrint.h | 2 +- source/shared/StringFunctions.cpp | 2 +- source/shared/StringFunctions.h | 2 +- source/shared/core_conn.cpp | 2 +- source/shared/core_init.cpp | 2 +- source/shared/core_results.cpp | 2 +- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 2 +- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 2 +- source/shared/globalization.h | 2 +- source/shared/interlockedatomic.h | 2 +- source/shared/interlockedatomic_gcc.h | 2 +- source/shared/interlockedslist.h | 2 +- source/shared/localization.hpp | 2 +- source/shared/localizationimpl.cpp | 2 +- source/shared/msodbcsql.h | 2 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 6 +++--- source/shared/xplat.h | 2 +- source/shared/xplat_intsafe.h | 2 +- source/shared/xplat_winerror.h | 2 +- source/shared/xplat_winnls.h | 2 +- source/sqlsrv/config.m4 | 2 +- source/sqlsrv/config.w32 | 2 +- source/sqlsrv/conn.cpp | 2 +- source/sqlsrv/init.cpp | 2 +- source/sqlsrv/php_sqlsrv.h | 2 +- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 2 +- 42 files changed, 44 insertions(+), 44 deletions(-) diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index b1bfd6e48..3de4fefc5 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.3 for PHP for SQL Server +dnl Microsoft Drivers 5.4 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index a71e329d8..cd6e3a063 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 6e761b04c..b424866e4 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index cd6fe4872..a5fcbdd0c 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 9710e5d2a..e6a70d4d7 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 22315bf8d..3f6bc3283 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index f0497f723..dee2e3e06 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 160156b01..c110a9c6e 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 8b94ddfad..435fa9a3b 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index 1de33d0ef..a5033db49 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -6,7 +6,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index 87f15f463..f7933f97f 100644 --- a/source/shared/FormattedPrint.h +++ b/source/shared/FormattedPrint.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp index d5183fc52..354ebbf6c 100644 --- a/source/shared/StringFunctions.cpp +++ b/source/shared/StringFunctions.cpp @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.h b/source/shared/StringFunctions.h index b12e789b7..475cd84cf 100644 --- a/source/shared/StringFunctions.h +++ b/source/shared/StringFunctions.h @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index d2251fc92..da6284dc7 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index cda38fa55..274c10d26 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 76f862e7a..67a805558 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 5f7fcb899..6c49464cd 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 0a87784a1..46d609078 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 25ec976a7..e4e9e485f 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 04f131ccd..ca097a24b 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/globalization.h b/source/shared/globalization.h index 0d77fff9d..98619d61a 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index cc0163f0f..b8c04643c 100644 --- a/source/shared/interlockedatomic.h +++ b/source/shared/interlockedatomic.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h index e8c8e5bb2..6977ff229 100644 --- a/source/shared/interlockedatomic_gcc.h +++ b/source/shared/interlockedatomic_gcc.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h index bf2bc9ca2..6aa43fb00 100644 --- a/source/shared/interlockedslist.h +++ b/source/shared/interlockedslist.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, singly // linked list. // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index 2ec13e09d..79bd860e2 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 75251eb6d..669462abc 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -5,7 +5,7 @@ // Must be included in one c/cpp file per binary // A build error will occur if this inclusion policy is not followed // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index 30f9ee379..f4912b118 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -20,7 +20,7 @@ // pecuniary loss) arising out of the use of or inability to use // this SDK, even if Microsoft has been advised of the possibility // of such damages. -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index 78478eaa1..8d5b785c3 100644 --- a/source/shared/sal_def.h +++ b/source/shared/sal_def.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index dc3b4ca5b..574dc51a0 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -1,7 +1,7 @@ //--------------------------------------------------------------------------------------------------------------------------------- // File: typedefs_for_linux.h // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/version.h b/source/shared/version.h index 7d6554431..0ad9dcd14 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -4,7 +4,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -26,12 +26,12 @@ // Increase Minor with backward compatible new functionalities and API changes. // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 -#define SQLVERSION_MINOR 3 +#define SQLVERSION_MINOR 4 #define SQLVERSION_PATCH 0 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 -#define PREVIEW 0 +#define PREVIEW 1 #define SEMVER_PRERELEASE // Semantic versioning build metadata, build meta data is not counted in precedence order. diff --git a/source/shared/xplat.h b/source/shared/xplat.h index baa393e22..8e113a5cb 100644 --- a/source/shared/xplat.h +++ b/source/shared/xplat.h @@ -3,7 +3,7 @@ // // Contents: include for definition of Windows types for non-Windows platforms // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h index 1baa473a7..03706dd36 100644 --- a/source/shared/xplat_intsafe.h +++ b/source/shared/xplat_intsafe.h @@ -4,7 +4,7 @@ // Contents: This module defines helper functions to prevent // integer overflow bugs. // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h index 44eb7c844..a5bdf6415 100644 --- a/source/shared/xplat_winerror.h +++ b/source/shared/xplat_winerror.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h index 36aceae16..274263107 100644 --- a/source/shared/xplat_winnls.h +++ b/source/shared/xplat_winnls.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index 6b4190665..c0e4d3af8 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.3 for PHP for SQL Server +dnl Microsoft Drivers 5.4 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 2972a7b19..b6c2b1b1a 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 8178fbc65..dcfc755e2 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 74463d200..45dfb0e58 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 6c5e7b015..1816942ac 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 819798c03..3bbd4af65 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index ffee85372..d70a93a9d 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index a1b24c2e7..aab5e2ebd 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.3 for PHP for SQL Server +// Microsoft Drivers 5.4 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License From 0ba11a2f44dfe1b6ef454de073a3698907df978c Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 21 Sep 2018 13:07:47 -0700 Subject: [PATCH 053/249] Fixed sqlsrv datetime tests to connect with ColumnEncryption variables (#849) --- .../sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt | 4 ++-- .../sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt | 7 ++++++- .../sqlsrv/sqlsrv_statement_datetimes_output_param.phpt | 9 +++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt index e6347fb0c..7c8a16ef2 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_nulls.phpt @@ -81,7 +81,7 @@ function createTestTable($conn, $tableName, $columns) function runTest($tableName, $columns, $dateAsString) { // Connect - $conn = connect(array('ReturnDatesAsStrings' => $dateAsString)); + $conn = AE\connect(array('ReturnDatesAsStrings' => $dateAsString)); if (!$conn) { fatalError("Could not connect.\n"); } @@ -100,7 +100,7 @@ $tableName = "TestNullDateTime"; $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); // Connect -$conn = connect(); +$conn = AE\connect(); if (!$conn) { fatalError("Could not connect.\n"); } diff --git a/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt index 23a27a871..01cc16f2e 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_datetimes_as_strings.phpt @@ -139,6 +139,11 @@ $query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(), CONVERT(time, SYSDATETIME())'; $stmt = sqlsrv_query($conn, $query); $values = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +// Connect again with ColumnEncryption data +$conn = AE\connect(array('ReturnDatesAsStrings' => true)); // Create the test table of date and time columns $tableName = 'StmtDateAsString'; @@ -176,7 +181,7 @@ testStmtOption($conn, $tableName, $values, false); sqlsrv_close($conn); // Now connect but with ReturnDatesAsStrings option set to false -$conn = connect(array('ReturnDatesAsStrings' => false)); +$conn = AE\connect(array('ReturnDatesAsStrings' => false)); if (!$conn) { fatalError("Could not connect.\n"); } diff --git a/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt b/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt index a1aa717b5..4e3add8af 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_datetimes_output_param.phpt @@ -58,6 +58,15 @@ $query = 'SELECT CONVERT(date, SYSDATETIME()), SYSDATETIME(), SYSDATETIMEOFFSET( $stmt = sqlsrv_query($conn, $query); $values = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +// Connect again with ColumnEncryption data +$conn = AE\connect(array('ReturnDatesAsStrings' => true)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + // Create the test table of date and time columns $tableName = 'OuputParamDateAsString'; $columns = array('c1', 'c2', 'c3', 'c4'); From 432901d7a07f7dc6cf81ec4795108a71b82d8d75 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 24 Sep 2018 10:34:19 -0700 Subject: [PATCH 054/249] Change log for 5.4.0-preview (#850) * Updated change log for 5.4.0-preview * Updated 5.4.0 preview to add two new feature requests * Modified change log as per review * Modified the wordings * Updated readme, changelog, and install instructions --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ Linux-mac-install.md | 4 ++-- README.md | 28 ++++++++++++++-------------- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abaa24db6..ecb2ee3f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.4.0-preview - 2018-09-24 +Updated PECL release packages. Here is the list of updates: + +### Added +- Added support for PHP 7.3.0 RC 1 +- Added support for Azure AD Access Token (in Linux / macOS this requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) and [unixODBC](http://www.unixodbc.org/) 2.3.6+) +- Feature Request [#842](https://github.com/Microsoft/msphpsql/pull/842) - new PDO_STMT_OPTION_FETCHES_DATETIME_TYPE flag for pdo_sqlsrv to return datetime as objects +- Feature Request [#844](https://github.com/Microsoft/msphpsql/pull/844) - add ReturnDatesAsStrings option to statement level for sqlsrv +- Compatible with [ODBC Driver 17.3 CTP](https://blogs.msdn.microsoft.com/sqlnativeclient/2018/09/24/odbc-driver-17-3-preview-for-sql-server-released/) + +### Removed +- Dropped support for Ubuntu 17.10 +- Dropped support for PHP 7.0 - [Version 5.3](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver?view=sql-server-2017) is the last to support PHP 7.0. + +### Fixed +- Issue [#434](https://github.com/Microsoft/msphpsql/issues/434) - To avoid the pitfall that could result in a crash, before freeing stmt in the destructor check if its dbh driver data is NULL +- Pull Request [#836](https://github.com/Microsoft/msphpsql/pull/836) - Modified the config files to enable Spectre Mitigations (use /Qspectre switch) for PHP 7.2 +- Pull Request [#833](https://github.com/Microsoft/msphpsql/pull/833) - Streamlined the error handling to remove a potential cause of crash + +### Limitations +- No support for inout / output params when using sql_variant type +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) +- With ColumnEncryption enabled, fetching varbinary(max), varchar(max) or nvarchar(max) may fail with [ODBC Driver 17.3 CTP](https://blogs.msdn.microsoft.com/sqlnativeclient/2018/09/24/odbc-driver-17-3-preview-for-sql-server-released/) + ## 5.3.0 - 2018-07-20 Updated PECL release packages. Here is the list of updates: diff --git a/Linux-mac-install.md b/Linux-mac-install.md index d84eca655..970bc3d00 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -5,13 +5,13 @@ These instructions install PHP 7.2 by default -- see the notes at the beginning ## Contents of this page: -- [Installing the drivers on Ubuntu 16.04, 17.10, and 18.04](#installing-the-drivers-on-ubuntu-1604-1710-and-1804) +- [Installing the drivers on Ubuntu 16.04 and 18.04](#installing-the-drivers-on-ubuntu-1604-and-1804) - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) - [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12) - [Installing the drivers on macOS El Capitan, Sierra and High Sierra](#installing-the-drivers-on-macos-el-capitan-sierra-and-high-sierra) -## Installing the drivers on Ubuntu 16.04, 17.10 and 18.04 +## Installing the drivers on Ubuntu 16.04 and 18.04 > [!NOTE] > To install PHP 7.0 or 7.1, replace 7.2 with 7.0 or 7.1 in the following commands. diff --git a/README.md b/README.md index 2ad3406c8..071d582f0 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ **Welcome to the Microsoft Drivers for PHP for Microsoft SQL Server** -The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2008 R2 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) to handle the low-level communication with SQL Server. +The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2008 R2 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) to handle the low-level communication with SQL Server. -This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.* with improvements on both drivers and some limitations (see Limitations below for details). Upcoming releases will contain additional functionalities, bug fixes, and more. +This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.1+ with improvements on both drivers and some [limitations](https://github.com/Microsoft/msphpsql/releases). Upcoming releases will contain additional functionalities, bug fixes, and more. ## Take our survey @@ -28,11 +28,11 @@ Thank you for taking the time to participate in our last survey. You can continu ## Get Started -* [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/windows) -* [**Ubuntu + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/ubuntu) -* [**RedHat + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/rhel) -* [**SUSE + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/sles) -* [**macOS + SQL Server + PHP 7**](https://www.microsoft.com/en-us/sql-server/developer-get-started/php/mac/) +* [**Windows + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/windows) +* [**Ubuntu + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/ubuntu) +* [**RedHat + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/rhel) +* [**SUSE + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/sles) +* [**macOS + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/mac/) * [**Docker**](https://hub.docker.com/r/lbosqmsft/mssql-php-msphpsql/) @@ -42,11 +42,11 @@ Thank you for taking the time to participate in our last survey. You can continu ## Prerequisites -For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/en-us/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs. +For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs. On the client machine: -- PHP 7.0.x, 7.1.x, or 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows) -- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-2017) +- PHP 7.1.x, or 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows) +- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-2017) - If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux. @@ -57,16 +57,16 @@ The drivers are distributed as pre-compiled extensions for PHP found on the [rel If you choose to build the drivers, you must be able to build PHP 7 without including these extensions. For help building PHP on Windows, see the [official PHP website][phpbuild]. For details on compiling the drivers, see the [documentation](https://github.com/Microsoft/msphpsql/tree/dev/buildscripts#windows) -- an example buildscript is provided, but you can also compile the drivers manually. -To load the drivers, make sure that the driver is in your PHP extension directory and enable it in your PHP installation's php.ini file by adding `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll` to it. If necessary, specify the extension directory using `extension_dir`, for example: `extension_dir = "C:\PHP\ext"`. Note that the precompiled binaries have different names -- substitute accordingly in php.ini. For more details on loading the drivers, see [Loading the PHP SQL Driver](https://docs.microsoft.com/en-us/sql/connect/php/loading-the-php-sql-driver) on Microsoft Docs. +To load the drivers, make sure that the driver is in your PHP extension directory and enable it in your PHP installation's php.ini file by adding `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll` to it. If necessary, specify the extension directory using `extension_dir`, for example: `extension_dir = "C:\PHP\ext"`. Note that the precompiled binaries have different names -- substitute accordingly in php.ini. For more details on loading the drivers, see [Loading the PHP SQL Driver](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver) on Microsoft Docs. Finally, if running PHP in a Web server, restart the Web server. ## Install (UNIX) -For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs](https://docs.microsoft.com/en-us/sql/connect/php/installation-tutorial-linux-mac). +For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/installation-tutorial-linux-mac). ## Sample Code -For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on Microsoft Docs](https://docs.microsoft.com/en-us/sql/connect/php/code-samples-for-php-sql-driver). +For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/code-samples-for-php-sql-driver). ## Limitations and Known Issues Please refer to [Releases](https://github.com/Microsoft/msphpsql/releases) for the latest limitations and known issues. @@ -138,6 +138,6 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf [phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild -[phpdoc]: https://docs.microsoft.com/en-us/sql/connect/php/microsoft-php-driver-for-sql-server?view=sql-server-2017 +[phpdoc]: https://docs.microsoft.com/sql/connect/php/microsoft-php-driver-for-sql-server?view=sql-server-2017 [PHPMan]: http://php.net/manual/install.unix.php From 32732c885eed10a8ca668fd52c47f5c3592e3392 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 26 Sep 2018 14:51:16 -0700 Subject: [PATCH 055/249] Clear AKV data after setting the connection attribute or when exception is thrown (#854) * Dev (#820) * Fixed the potential error reported by Prefast code analysis * Use SQLSRV_ASSERT for checking NULL ptrs * For these AKV tests check env despite not AE connected * Added the driver option to run functional tests * Fixed connection pooling tests for more than one ODBC drivers * added driver option to pdo isPooled.php * Removed win32 ifdefs re connection resiliency (#802) * Set the driver argument for getDSN to null by default (#798) * Added the driver argument to getDSN * Dropped the driver argument but set to null as default * Removed the AE condition in locale support * Modified the AE condition for locale support * Changed int to SQLLEN to avoid infinite loop (#806) * Version 5.3.0 (#803) * Version 5.3.0 * Fixed the wrong replacements * Added comments block to m4 files * Use dnl for comments * Modified AE fetch phptypes test to insert only one row at a time and loop through php types (#801) * Modified AE fetch phptypes test to insert only one row at a time and loop through php types * Fixed formatting * Streamlined two very similar large column name tests (#807) * Streamlined two very similar large column name tests * Changed the EOL * Updates to change log and readme (#811) * Updates to change log and readme * Dropped support for Ubuntu 17 * Modified as per review comments * Fixed connection resiliency tests for Unix, updated AppVeyor for ODBC 17.2 * Fixed expected output * Fixed output and skipifs * Fixed skipifs and output * Fixed driver name * Updated installation instructions and sample script (#813) * Updated instructions and sample test for 5.3.0 RTW * Fixed sample code to adhere to php coding standard * Fixed cases and spaces * Modified NOTE for UB 18.04 based on review comments * Added 'exit' * Modified change log and readme based on review to PR 811 * Applied review comments * build output to debug appveyor failure * removed debug output * Streamlined two very similar large column name tests (#815) * Streamlined two very similar large column name tests * Added random number of test table names to avoid operand clash issues * Replaced to with for based on review * Changelog updated * changelog updated, test skipif changed to run on unix platforms * Fixed skipif typo * Fixed typo in skipif for pdo * Fixed some output for Travis * Moved error checking inside pdo connres tests * Added links back to changelog * Fixed output for sqlsrv connres tests * Fixed output * Fixed output again * Clear AKV data after connection or when exception is thrown * Modified tests too to skip some AKV tests without real credentials * Used assignment operator also free the existing memory --- source/shared/core_conn.cpp | 33 ++++++++++++------- source/shared/core_sqlsrv.h | 18 +++++++--- .../pdo_ae_azure_key_vault_keywords.phpt | 2 +- test/functional/pdo_sqlsrv/skipif_not_akv.inc | 4 +++ test/functional/sqlsrv/skipif_not_akv.inc | 3 ++ .../sqlsrv_ae_azure_key_vault_keywords.phpt | 2 +- 6 files changed, 43 insertions(+), 19 deletions(-) diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index da6284dc7..a1fe06b0c 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -257,7 +257,9 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont throw core::CoreException(); } - load_azure_key_vault( conn ); + // After load_azure_key_vault, reset AKV related variables regardless + load_azure_key_vault(conn); + conn->ce_option.akv_reset(); // determine the version of the server we're connected to. The server version is left in the // connection upon return. @@ -292,6 +294,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont throw; } catch( core::CoreException& ) { + conn->ce_option.akv_reset(); conn_str.clear(); conn->invalidate(); throw; @@ -862,6 +865,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou } catch( core::CoreException& ) { + conn->ce_option.akv_reset(); throw; } } @@ -984,10 +988,10 @@ void load_azure_key_vault(_Inout_ sqlsrv_conn* conn TSRMLS_DC) throw core::CoreException(); } - char *akv_id = Z_STRVAL_P(conn->ce_option.akv_id); - char *akv_secret = Z_STRVAL_P(conn->ce_option.akv_secret); - unsigned int id_len = static_cast(Z_STRLEN_P(conn->ce_option.akv_id)); - unsigned int key_size = static_cast(Z_STRLEN_P(conn->ce_option.akv_secret)); + char *akv_id = conn->ce_option.akv_id.get(); + char *akv_secret = conn->ce_option.akv_secret.get(); + unsigned int id_len = strnlen_s(akv_id); + unsigned int key_size = strnlen_s(akv_secret); configure_azure_key_vault(conn, AKV_CONFIG_FLAGS, conn->ce_option.akv_mode, 0); configure_azure_key_vault(conn, AKV_CONFIG_PRINCIPALID, akv_id, id_len); @@ -1120,6 +1124,7 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval* { SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "Azure Key Vault keywords accept only strings."); + const char *value_str = Z_STRVAL_P(value); size_t value_len = Z_STRLEN_P(value); CHECK_CUSTOM_ERROR(value_len <= 0, conn, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE) { @@ -1130,7 +1135,6 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval* { case SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION: { - char *value_str = Z_STRVAL_P(value); if (!stricmp(value_str, "KeyVaultPassword")) { conn->ce_option.akv_mode = AKVCFG_AUTHMODE_PASSWORD; } else if (!stricmp(value_str, "KeyVaultClientSecret")) { @@ -1145,14 +1149,19 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval* break; } case SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID: - { - conn->ce_option.akv_id = value; - conn->ce_option.akv_required = true; - break; - } case SQLSRV_CONN_OPTION_KEYSTORE_SECRET: { - conn->ce_option.akv_secret = value; + // Create a new string to save a copy of the zvalue + char *pValue = static_cast(sqlsrv_malloc(value_len + 1)); + memcpy_s(pValue, value_len + 1, value_str, value_len); + pValue[value_len] = '\0'; // this makes sure there will be no trailing garbage + + // This will free the existing memory block before assigning the new pointer -- the user might set the value(s) more than once + if (option->conn_option_key == SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID) { + conn->ce_option.akv_id = pValue; + } else { + conn->ce_option.akv_secret = pValue; + } conn->ce_option.akv_required = true; break; } diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 6c49464cd..e6efb3a84 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1055,15 +1055,23 @@ struct stmt_option; // This holds the various details of column encryption. struct col_encryption_option { - bool enabled; // column encryption enabled, false by default - SQLINTEGER akv_mode; - zval_auto_ptr akv_id; - zval_auto_ptr akv_secret; - bool akv_required; + bool enabled; // column encryption enabled, false by default + SQLINTEGER akv_mode; + sqlsrv_malloc_auto_ptr akv_id; + sqlsrv_malloc_auto_ptr akv_secret; + bool akv_required; col_encryption_option() : enabled( false ), akv_mode(-1), akv_required( false ) { } + + void akv_reset() + { + akv_id.reset(); + akv_secret.reset(); + akv_required = false; + akv_mode = -1; + } }; // *** connection resource structure *** diff --git a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt index 514bab244..5f0c40689 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt @@ -1,7 +1,7 @@ --TEST-- Test connection keywords for Azure Key Vault for Always Encrypted. --SKIPIF-- - + --FILE-- + --FILE-- Date: Mon, 1 Oct 2018 23:01:10 +0200 Subject: [PATCH 056/249] Change readme links to https --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 071d582f0..23baf6e89 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ For PHP code samples, please see the [sample](https://github.com/Microsoft/msphp Please refer to [Releases](https://github.com/Microsoft/msphpsql/releases) for the latest limitations and known issues. ## Version number -The version numbers of the PHP drivers follow [semantic versioning](http://semver.org/): +The version numbers of the PHP drivers follow [semantic versioning](https://semver.org/): Given a version number MAJOR.MINOR.PATCH, @@ -128,16 +128,16 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf **Known Issues**: Please visit the [project on Github][project] to view outstanding [issues][issues] and report new ones. -[blog]: http://blogs.msdn.com/b/sqlphp/ +[blog]: https://blogs.msdn.com/b/sqlphp/ [project]: https://github.com/Microsoft/msphpsql [issues]: https://github.com/Microsoft/msphpsql/issues -[phpweb]: http://php.net +[phpweb]: https://php.net [phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild [phpdoc]: https://docs.microsoft.com/sql/connect/php/microsoft-php-driver-for-sql-server?view=sql-server-2017 -[PHPMan]: http://php.net/manual/install.unix.php +[PHPMan]: https://php.net/manual/install.unix.php From 3ce8eb8a239cd47462d81995e2728d1698b9f08d Mon Sep 17 00:00:00 2001 From: Gert de Pagter Date: Mon, 1 Oct 2018 23:01:10 +0200 Subject: [PATCH 057/249] Change readme links to https Merging this commit to dev From b5233069195cb883edff6e3516ea294353c3cc28 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 5 Oct 2018 15:01:18 -0700 Subject: [PATCH 058/249] Save meta data for the fetched result set (#855) * Save meta data on fetched result sets * Fixed a compilation error * Optimized some more -- metadata should be available when fetching * Skip conversion for strings of numeric values, integers, floats, decimals etc * Set encoding char for numeric data * Apply review --- source/pdo_sqlsrv/pdo_stmt.cpp | 9 ++- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 - source/shared/core_sqlsrv.h | 4 + source/shared/core_stmt.cpp | 97 ++++++++++++++--------- source/sqlsrv/stmt.cpp | 123 ++++++++++++++++++----------- 5 files changed, 148 insertions(+), 87 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 3f6bc3283..697ca40e7 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1377,6 +1377,7 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, } else { sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; } break; case SQL_FLOAT: @@ -1386,6 +1387,7 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, } else { sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; } break; case SQL_TYPE_DATE: @@ -1400,10 +1402,13 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, } break; case SQL_BIGINT: - case SQL_CHAR: case SQL_DECIMAL: - case SQL_GUID: case SQL_NUMERIC: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; + break; + case SQL_CHAR: + case SQL_GUID: case SQL_WCHAR: case SQL_VARCHAR: case SQL_WVARCHAR: diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index c110a9c6e..3b13953a4 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -279,8 +279,6 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { size_t direct_query_subst_string_len; // length of query string used for direct queries HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare - // meta data for current result set - std::vector > current_meta_data; pdo_param_type* bound_column_param_types; bool fetch_numeric; bool fetch_datetime; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index e6efb3a84..b623c1b4a 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1365,6 +1365,7 @@ struct sqlsrv_output_param { // forward decls struct sqlsrv_result_set; +struct field_meta_data; // *** parameter metadata struct *** struct param_meta_data @@ -1427,6 +1428,9 @@ struct sqlsrv_stmt : public sqlsrv_context { std::vector param_descriptions; + // meta data for current result set + std::vector> current_meta_data; + sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv TSRMLS_DC ); virtual ~sqlsrv_stmt( void ); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 46d609078..ddb1b981a 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -124,7 +124,6 @@ void send_param_streams( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); void sqlsrv_output_param_dtor( _Inout_ zval* data ); // called when a bound stream parameter is to be destroyed. void sqlsrv_stream_dtor( _Inout_ zval* data ); -bool is_streamable_type( _In_ SQLINTEGER sql_type ); } @@ -997,22 +996,24 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i efree( field_value ); field_value = NULL; *field_len = 0; - } - } - } - - // If the php type was not specified set the php type to be the default type. - if( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID ) { - - // Get the SQL type of the field. - core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); + } + } + } - // Get the length of the field. - core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_LENGTH, NULL, 0, NULL, &sql_field_len TSRMLS_CC ); + // If the php type was not specified set the php type to be the default type. + if (sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) { + SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_sqlsrv_get_field - meta data vector not in sync" ); + sql_field_type = stmt->current_meta_data[field_index]->field_type; + if (stmt->current_meta_data[field_index]->field_precision > 0) { + sql_field_len = stmt->current_meta_data[field_index]->field_precision; + } + else { + sql_field_len = stmt->current_meta_data[field_index]->field_size; + } - // Get the corresponding php type from the sql type. - sqlsrv_php_type = stmt->sql_type_to_php_type( static_cast( sql_field_type ), static_cast( sql_field_len ), prefer_string ); - } + // Get the corresponding php type from the sql type. + sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast(sql_field_type), static_cast(sql_field_len), prefer_string); + } // Verify that we have an acceptable type to convert. CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { @@ -1441,7 +1442,7 @@ void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) namespace { -bool is_streamable_type( _In_ SQLLEN sql_type ) +bool is_streamable_type( _In_ SQLSMALLINT sql_type ) { switch( sql_type ) { case SQL_CHAR: @@ -1460,6 +1461,25 @@ bool is_streamable_type( _In_ SQLLEN sql_type ) return false; } +bool is_a_numeric_type(_In_ SQLSMALLINT sql_type) +{ + switch (sql_type) { + case SQL_BIGINT: + case SQL_BIT: + case SQL_INTEGER: + case SQL_SMALLINT: + case SQL_TINYINT: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_REAL: + case SQL_DECIMAL: + case SQL_NUMERIC: + return true; + } + + return false; +} + void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size TSRMLS_DC ) { try { @@ -1693,12 +1713,10 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i { php_stream* stream = NULL; sqlsrv_stream* ss = NULL; - SQLLEN sql_type; + SQLSMALLINT sql_type; - SQLRETURN r = SQLColAttributeW( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type ); - CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { - throw core::CoreException(); - } + SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_get_field_common - meta data vector not in sync" ); + sql_type = stmt->current_meta_data[field_index]->field_type; CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) { throw core::CoreException(); @@ -2208,9 +2226,30 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind DEBUG_SQLSRV_ASSERT( sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_STRING, "Type should be SQLSRV_PHPTYPE_STRING in get_field_as_string" ); + col_cache* cached = NULL; + if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) { + sql_field_type = cached->sql_type; + sql_display_size = cached->display_size; + } + else { + SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "get_field_as_string - meta data vector not in sync" ); + sql_field_type = stmt->current_meta_data[field_index]->field_type; + + // Calculate the field size. + calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); + + col_cache cache( sql_field_type, sql_display_size ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC ); + } + + // Determine the correct encoding if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); } + // For numbers, no need to convert + if (is_a_numeric_type(sql_field_type)) { + sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_CHAR; + } // Set the C type and account for null characters at the end of the data. switch( sqlsrv_php_type.typeinfo.encoding ) { @@ -2228,22 +2267,6 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind break; } - col_cache* cached = NULL; - if ( NULL != ( cached = static_cast< col_cache* >( zend_hash_index_find_ptr( Z_ARRVAL( stmt->col_cache ), static_cast< zend_ulong >( field_index ))))) { - sql_field_type = cached->sql_type; - sql_display_size = cached->display_size; - } - else { - // Get the SQL type of the field. unixODBC 2.3.1 requires wide calls to support pooling - core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &sql_field_type TSRMLS_CC ); - - // Calculate the field size. - calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); - - col_cache cache( sql_field_type, sql_display_size ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC ); - } - // if this is a large type, then read the first few bytes to get the actual length from SQLGetData if( sql_display_size == 0 || sql_display_size == INT_MAX || sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 3bbd4af65..3da4ebcc7 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -91,7 +91,7 @@ const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not pa /* internal functions */ void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval ); - +SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt* stmt); void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names TSRMLS_DC ); bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size, @@ -110,6 +110,15 @@ bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_p } +// internal helper function to free meta data structures allocated +void meta_data_free( _Inout_ field_meta_data* meta ) +{ + if( meta->field_name ) { + meta->field_name.reset(); + } + sqlsrv_free( meta ); +} + // query options for cursor types namespace SSCursorTypes { @@ -137,6 +146,9 @@ ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) { + std::for_each(current_meta_data.begin(), current_meta_data.end(), meta_data_free); + current_meta_data.clear(); + if( fetch_field_names != NULL ) { for( int i=0; i < fetch_fields_count; ++i ) { @@ -459,27 +471,24 @@ PHP_FUNCTION( sqlsrv_field_metadata ) try { - // get the number of fields in the resultset - num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + // get the number of fields in the resultset and its metadata if not exists + SQLSMALLINT num_cols = get_resultset_meta_data(stmt); zval result_meta_data; ZVAL_UNDEF( &result_meta_data ); core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); for( SQLSMALLINT f = 0; f < num_cols; ++f ) { - - sqlsrv_malloc_auto_ptr core_meta_data; - core_meta_data = core_sqlsrv_field_metadata( stmt, f TSRMLS_CC ); - + field_meta_data* core_meta_data = stmt->current_meta_data[f]; + // initialize the array zval field_array; ZVAL_UNDEF( &field_array ); core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *stmt, &field_array, FieldMetaData::NAME, - reinterpret_cast( core_meta_data->field_name.get() ), 0 TSRMLS_CC ); - - core_meta_data->field_name.transferred(); + // add the field name to the associative array but keep a copy + core::sqlsrv_add_assoc_string(*stmt, &field_array, FieldMetaData::NAME, + reinterpret_cast(core_meta_data->field_name.get()), 1 TSRMLS_CC); core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC ); @@ -519,9 +528,6 @@ PHP_FUNCTION( sqlsrv_field_metadata ) // add this field's meta data to the result set meta data core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC ); - - // always good to call destructor for allocations done through placement new operator. - core_meta_data->~field_meta_data(); } // return our built collection and transfer ownership @@ -567,6 +573,10 @@ PHP_FUNCTION( sqlsrv_next_result ) core_sqlsrv_next_result( stmt TSRMLS_CC, true ); + // clear the current meta data since the new result will generate new meta data + std::for_each(stmt->current_meta_data.begin(), stmt->current_meta_data.end(), meta_data_free); + stmt->current_meta_data.clear(); + if( stmt->past_next_result_end ) { RETURN_NULL(); @@ -1084,7 +1094,7 @@ PHP_FUNCTION( sqlsrv_get_field ) try { // validate that the field index is within range - int num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + SQLSMALLINT num_cols = get_resultset_meta_data(stmt); if( field_index < 0 || field_index >= num_cols ) { THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); @@ -1622,10 +1632,13 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ switch( sql_type ) { case SQL_BIGINT: - case SQL_CHAR: case SQL_DECIMAL: - case SQL_GUID: case SQL_NUMERIC: + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; + break; + case SQL_CHAR: + case SQL_GUID: case SQL_WCHAR: sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); @@ -1647,6 +1660,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ case SQL_SMALLINT: case SQL_TINYINT: sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; break; case SQL_BINARY: case SQL_LONGVARBINARY: @@ -1676,6 +1690,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ case SQL_FLOAT: case SQL_REAL: sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; break; case SQL_TYPE_DATE: case SQL_SS_TIMESTAMPOFFSET: @@ -1759,6 +1774,37 @@ void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) } } +SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) +{ + // get the numer of columns in the result set + SQLSMALLINT num_cols = -1; + + num_cols = stmt->current_meta_data.size(); + bool getMetaData = false; + + if (num_cols == 0) { + getMetaData = true; + num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + } + + try { + if (getMetaData) { + for (int i = 0; i < num_cols; i++) { + sqlsrv_malloc_auto_ptr core_meta_data; + core_meta_data = core_sqlsrv_field_metadata(stmt, i TSRMLS_CC); + stmt->current_meta_data.push_back(core_meta_data.get()); + core_meta_data.transferred(); + } + } + } catch( core::CoreException& ) { + throw; + } + + SQLSRV_ASSERT(num_cols > 0 && stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" ); + + return num_cols; +} + void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names TSRMLS_DC ) { @@ -1772,40 +1818,25 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ throw ss::SSException(); } - // get the numer of columns in the result set - SQLSMALLINT num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + // get the numer of columns in the result set and its metadata if not exists + SQLSMALLINT num_cols = get_resultset_meta_data(stmt); // if this is the first fetch in a new result set, then get the field names and // store them off for successive fetches. - if(( fetch_type & SQLSRV_FETCH_ASSOC ) && stmt->fetch_field_names == NULL ) { + if ((fetch_type & SQLSRV_FETCH_ASSOC) && stmt->fetch_field_names == NULL) { SQLLEN field_name_len = 0; - SQLSMALLINT field_name_len_w = 0; - SQLWCHAR field_name_w[( SS_MAXCOLNAMELEN + 1 ) * 2] = {L'\0'}; - sqlsrv_malloc_auto_ptr field_name; sqlsrv_malloc_auto_ptr field_names; - field_names = static_cast( sqlsrv_malloc( num_cols * sizeof( sqlsrv_fetch_field_name ))); - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); - for( int i = 0; i < num_cols; ++i ) { - - core::SQLColAttributeW ( stmt, i + 1, SQL_DESC_NAME, field_name_w, ( SS_MAXCOLNAMELEN + 1 ) * 2, &field_name_len_w, NULL TSRMLS_CC ); - - //Conversion function expects size in characters - field_name_len_w = field_name_len_w / sizeof ( SQLWCHAR ); - bool converted = convert_string_from_utf16( encoding, field_name_w, - field_name_len_w, ( char** ) &field_name, field_name_len ); - - CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message() ) { - throw core::CoreException(); - } - - field_names[i].name = static_cast( sqlsrv_malloc( field_name_len, sizeof( char ), 1 )); - memcpy_s(( void* )field_names[i].name, ( field_name_len * sizeof( char )) , ( void* ) field_name, field_name_len ); - field_names[i].name[field_name_len] = '\0'; // null terminate the field name since SQLColAttribute doesn't. - field_names[i].len = field_name_len + 1; - field_name.reset(); + field_names = static_cast(sqlsrv_malloc(num_cols * sizeof(sqlsrv_fetch_field_name))); + for (int i = 0; i < num_cols; ++i) { + // The meta data field name is already null-terminated, and the field name len is correct. + field_name_len = stmt->current_meta_data[i]->field_name_len; + field_names[i].name = static_cast(sqlsrv_malloc(field_name_len, sizeof(char), 1)); + memcpy_s((void*)field_names[i].name, (field_name_len * sizeof(char)), (void*)stmt->current_meta_data[i]->field_name, field_name_len); + field_names[i].name[field_name_len] = '\0'; // null terminate the field name after the memcpy + field_names[i].len = field_name_len; // field_name_len should not need to include the null char } - + stmt->fetch_field_names = field_names; stmt->fetch_fields_count = num_cols; field_names.transferred(); @@ -1840,12 +1871,12 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ if( fetch_type & SQLSRV_FETCH_ASSOC ) { - CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 1 && !allow_empty_field_names ), stmt, + CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 0 && !allow_empty_field_names ), stmt, SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) { throw ss::SSException(); } - if( stmt->fetch_field_names[i].len > 1 || allow_empty_field_names ) { + if( stmt->fetch_field_names[i].len > 0 || allow_empty_field_names ) { zr = add_assoc_zval( &fields, stmt->fetch_field_names[i].name, &field ); CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { From a6b1cd5d3ad8b301766ec6e96286c25bbd2f3c0a Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 11 Oct 2018 16:26:20 -0700 Subject: [PATCH 059/249] Added Mojave to macOS instructions (#862) Added Mojave to macOS instructions --- Linux-mac-install.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index 970bc3d00..3988542fd 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -9,7 +9,7 @@ These instructions install PHP 7.2 by default -- see the notes at the beginning - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) - [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12) -- [Installing the drivers on macOS El Capitan, Sierra and High Sierra](#installing-the-drivers-on-macos-el-capitan-sierra-and-high-sierra) +- [Installing the drivers on macOS El Capitan, Sierra, High Sierra and Mojave](#installing-the-drivers-on-macos-el-capitan-sierra-high-sierra-and-mojave) ## Installing the drivers on Ubuntu 16.04 and 18.04 @@ -209,7 +209,7 @@ sudo systemctl restart apache2 ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on macOS El Capitan, Sierra and High Sierra +## Installing the drivers on macOS El Capitan, Sierra, High Sierra and Mojave If you do not already have it, install brew as follows: ``` From 36fd97e69abdf071cfc6d348c8b225a8f5b984e9 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 12 Oct 2018 14:02:40 -0700 Subject: [PATCH 060/249] Fixed the broken links of Appveyor status badge (#863) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 23baf6e89..19a5d88c3 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ Thank you for taking the time to participate in our last survey. You can continu |--------------------------|--------------------------|---------------------------------------|-------------------------------------------| | [![av-image][]][av-site] | [![tv-image][]][tv-site] | [![Coverage Codecov][]][codecov-site] | [![Coverage Coveralls][]][coveralls-site] | -[av-image]: https://ci.appveyor.com/api/projects/status/xhp4nq9ouljnhxqf/branch/dev?svg=true -[av-site]: https://ci.appveyor.com/project/Microsoft-PHPSQL/msphpsql-frhmr/branch/dev +[av-image]: https://ci.appveyor.com/api/projects/status/vo4rfei6lxlamrnc?svg=true +[av-site]: https://ci.appveyor.com/project/msphpsql/msphpsql/branch/dev [tv-image]: https://travis-ci.org/Microsoft/msphpsql.svg?branch=dev [tv-site]: https://travis-ci.org/Microsoft/msphpsql/ [Coverage Coveralls]: https://coveralls.io/repos/github/Microsoft/msphpsql/badge.svg?branch=dev From 18094a6cefe4536458279fb4d69ac901b047bbc2 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 12 Oct 2018 15:22:27 -0700 Subject: [PATCH 061/249] Feature request 415 for sqlsrv (#861) --- source/shared/core_sqlsrv.h | 79 ++-- source/shared/core_stmt.cpp | 156 +++++++- source/sqlsrv/conn.cpp | 7 + source/sqlsrv/util.cpp | 8 + .../sqlsrv_statement_format_decimals.phpt | 370 ++++++++++++++++++ ...lsrv_statement_format_decimals_scales.phpt | 255 ++++++++++++ 6 files changed, 848 insertions(+), 27 deletions(-) create mode 100644 test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index b623c1b4a..35e49cbe3 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1107,6 +1107,7 @@ enum SQLSRV_STMT_OPTIONS { SQLSRV_STMT_OPTION_SCROLLABLE, SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, SQLSRV_STMT_OPTION_DATE_AS_STRING, + SQLSRV_STMT_OPTION_FORMAT_DECIMALS, // Driver specific connection options SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, @@ -1296,6 +1297,11 @@ struct stmt_option_date_as_string : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; +struct stmt_option_format_decimals : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); +}; + // used to hold the table for statment options struct stmt_option { @@ -1334,16 +1340,39 @@ extern php_stream_wrapper g_sqlsrv_stream_wrapper; #define SQLSRV_STREAM_WRAPPER "sqlsrv" #define SQLSRV_STREAM "sqlsrv_stream" +// *** parameter metadata struct *** +struct param_meta_data +{ + SQLSMALLINT sql_type; + SQLSMALLINT decimal_digits; + SQLSMALLINT nullable; + SQLULEN column_size; + + param_meta_data() : sql_type(0), decimal_digits(0), column_size(0), nullable(0) + { + } + + ~param_meta_data() + { + } + + SQLSMALLINT get_sql_type() { return sql_type; } + SQLSMALLINT get_decimal_digits() { return decimal_digits; } + SQLSMALLINT get_nullable() { return nullable; } + SQLULEN get_column_size() { return column_size; } +}; + // holds the output parameter information. Strings also need the encoding and other information for // after processing. Only integer, float, and strings are allowable output parameters. struct sqlsrv_output_param { zval* param_z; SQLSRV_ENCODING encoding; - SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement - SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer - SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary + SQLUSMALLINT param_num; // used to index into the ind_or_len of the statement + SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer + SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary bool is_bool; + param_meta_data meta_data; // parameter meta data // string output param constructor sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len ) : @@ -1361,34 +1390,31 @@ struct sqlsrv_output_param { php_out_type(php_out_type) { } -}; -// forward decls -struct sqlsrv_result_set; -struct field_meta_data; - -// *** parameter metadata struct *** -struct param_meta_data -{ - SQLSMALLINT sql_type; - SQLSMALLINT decimal_digits; - SQLSMALLINT nullable; - SQLULEN column_size; - - param_meta_data() : sql_type(0), decimal_digits(0), column_size(0), nullable(0) - { + void saveMetaData(SQLSMALLINT sql_type, SQLSMALLINT column_size, SQLSMALLINT decimal_digits, SQLSMALLINT nullable = SQL_NULLABLE) + { + meta_data.sql_type = sql_type; + meta_data.column_size = column_size; + meta_data.decimal_digits = decimal_digits; + meta_data.nullable = nullable; } - ~param_meta_data() - { + SQLSMALLINT getDecimalDigits() + { + // Return decimal_digits only for decimal / numeric types. Otherwise, return -1 + if (meta_data.sql_type == SQL_DECIMAL || meta_data.sql_type == SQL_NUMERIC) { + return meta_data.decimal_digits; + } + else { + return -1; + } } - - SQLSMALLINT get_sql_type() { return sql_type; } - SQLSMALLINT get_decimal_digits() { return decimal_digits; } - SQLSMALLINT get_nullable() { return nullable; } - SQLULEN get_column_size() { return column_size; } }; +// forward decls +struct sqlsrv_result_set; +struct field_meta_data; + // *** Statement resource structure *** struct sqlsrv_stmt : public sqlsrv_context { @@ -1409,6 +1435,7 @@ struct sqlsrv_stmt : public sqlsrv_context { unsigned long query_timeout; // maximum allowed statement execution time zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings + short num_decimals; // indicates number of decimals shown in fetched results (-1 by default, which means no formatting required) // holds output pointers for SQLBindParameter // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving @@ -1743,6 +1770,8 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, + SQLSRV_ERROR_INVALID_FORMAT_DECIMALS, + SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, // Driver specific error codes starts from here. SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index ddb1b981a..ae86d4884 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -107,6 +107,7 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); void col_cache_dtor( _Inout_ zval* data_z ); void field_cache_dtor( _Inout_ zval* data_z ); +void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len); void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC ); @@ -141,8 +142,9 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error past_next_result_end( false ), query_timeout( QUERY_TIMEOUT_INVALID ), date_as_string(false), + num_decimals(-1), // -1 means no formatting required buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ), - param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte + param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte send_streams_at_exec( true ), current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), current_stream_read( 0 ) @@ -571,6 +573,8 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ // save the parameter to be adjusted and/or converted after the results are processed sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len ) ); + output_param.saveMetaData(sql_type, column_size, decimal_digits); + save_output_param_for_later( stmt, output_param TSRMLS_CC ); // For output parameters, if we set the column_size to be same as the buffer_len, @@ -1416,6 +1420,21 @@ 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(format_decimals); +} + // internal function to release the active stream. Called by each main API function // that will alter the statement and cancel any retrieval of data from a stream. void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) @@ -2079,6 +2098,130 @@ void field_cache_dtor( _Inout_ zval* data_z ) sqlsrv_free( cache ); } +// To be called for formatting decimal / numeric fetched values from finalize_output_parameters() and/or get_field_as_string() +void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len) +{ + // In SQL Server, the default maximum precision of numeric and decimal data types is 38 + // + // Note: stmt->num_decimals is -1 by default, which means no formatting on decimals / numerics is necessary + // If the required number of decimals is larger than the field scale, will use the column field scale instead. + // This is to ensure the number of decimals adheres to the column field scale. If smaller, the output value may be rounded up. + // + // Note: it's possible that the decimal / numeric value does not contain a decimal dot because the field scale is 0. + // Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of decimals_digits + // + std::string str = field_value; + size_t pos = str.find_first_of('.'); + + if (pos == std::string::npos || decimals_digits < 0) { + return; + } + + SQLSMALLINT num_decimals = decimals_digits; + if (num_decimals > field_scale) { + num_decimals = field_scale; + } + + // We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php + // as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is + // followed by 5 or above. + + bool isNegative = false; + + // If negative, remove the minus sign for now so as not to complicate the rounding process + if (str[0] == '-') { + isNegative = true; + std::ostringstream oss; + oss << str.substr(1); + str = oss.str(); + pos = str.find_first_of('.'); + } + + // Adds the leading zero if not exists + if (pos == 0) { + std::ostringstream oss; + oss << '0' << str; + str = oss.str(); + pos++; + } + + size_t last = 0; + if (num_decimals == 0) { + // Chop all decimal digits, including the decimal dot + size_t pos2 = pos + 1; + short n = str[pos2] - '0'; + if (n >= 5) { + // Start rounding up - starting from the digit left of the dot all the way to the first digit + bool carry_over = true; + for (short p = pos - 1; p >= 0 && carry_over; p--) { + n = str[p] - '0'; + if (n == 9) { + str[p] = '0' ; + carry_over = true; + } + else { + n++; + carry_over = false; + str[p] = '0' + n; + } + } + if (carry_over) { + std::ostringstream oss; + oss << '1' << str.substr(0, pos); + str = oss.str(); + pos++; + } + } + last = pos; + } + else { + size_t pos2 = pos + num_decimals + 1; + // No need to check if rounding is necessary when pos2 has passed the last digit in the input string + if (pos2 < str.length()) { + short n = str[pos2] - '0'; + if (n >= 5) { + // Start rounding up - starting from the digit left of pos2 all the way to the first digit + bool carry_over = true; + for (short p = pos2 - 1; p >= 0 && carry_over; p--) { + if (str[p] == '.') { // Skip the dot + continue; + } + n = str[p] - '0'; + if (n == 9) { + str[p] = '0' ; + carry_over = true; + } + else { + n++; + carry_over = false; + str[p] = '0' + n; + } + } + if (carry_over) { + std::ostringstream oss; + oss << '1' << str.substr(0, pos2); + str = oss.str(); + pos2++; + } + } + } + last = pos2; + } + + // Add the minus sign back if negative + if (isNegative) { + std::ostringstream oss; + oss << '-' << str.substr(0, last); + str = oss.str(); + } else { + str = str.substr(0, last); + } + + size_t len = str.length(); + str.copy(field_value, len); + field_value[len] = '\0'; + *field_len = len; +} // To be called after all results are processed. ODBC and SQL Server do not guarantee that all output // parameters will be present until all results are processed (since output parameters can depend on results @@ -2160,6 +2303,11 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) 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); } } @@ -2214,7 +2362,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind { SQLRETURN r; SQLSMALLINT c_type; - SQLLEN sql_field_type = 0; + SQLSMALLINT sql_field_type = 0; SQLSMALLINT extra = 0; SQLLEN field_len_temp = 0; SQLLEN sql_display_size = 0; @@ -2425,6 +2573,10 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind throw core::CoreException(); } } + + if (stmt->num_decimals >= 0 && (sql_field_type == SQL_DECIMAL || sql_field_type == SQL_NUMERIC)) { + format_decimal_numbers(stmt->num_decimals, stmt->current_meta_data[field_index]->field_scale, field_value_temp, &field_len_temp); + } } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) else { diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index dcfc755e2..d5f324a23 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -174,6 +174,7 @@ namespace SSStmtOptionNames { const char SCROLLABLE[] = "Scrollable"; const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT; const char DATE_AS_STRING[] = "ReturnDatesAsStrings"; + const char FORMAT_DECIMALS[] = "FormatDecimals"; } namespace SSConnOptionNames { @@ -250,6 +251,12 @@ const stmt_option SS_STMT_OPTS[] = { SQLSRV_STMT_OPTION_DATE_AS_STRING, std::unique_ptr( new stmt_option_date_as_string ) }, + { + SSStmtOptionNames::FORMAT_DECIMALS, + sizeof( SSStmtOptionNames::FORMAT_DECIMALS ), + SQLSRV_STMT_OPTION_FORMAT_DECIMALS, + std::unique_ptr( new stmt_option_format_decimals ) + }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index aab5e2ebd..545f699d0 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -428,6 +428,14 @@ ss_error SS_ERRORS[] = { SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, { IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, 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.", -117, 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.", -118, true} + }, // terminate the list of errors/warnings { UINT_MAX, {} } diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt new file mode 100644 index 000000000..7e9e4b243 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt @@ -0,0 +1,370 @@ +--TEST-- +Test how decimal data output values can be formatted (feature request issue 415) +--DESCRIPTION-- +Test how numeric and decimal data output values can be formatted by using the +statement option FormatDecimals, which expects an integer value from the range [0,38], +affecting only the money / decimal types in the fetched result set because they are +always strings to preserve accuracy and precision, unlike other primitive numeric +types that can be retrieved as numbers. + +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. For output params use SQLSRV_SQLTYPE_DECIMAL with the correct precision and scale +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $fieldScale, will show $fieldScale decimal digits + if ($formatDecimal >= 0) { + $numDecimals = ($formatDecimal > $fieldScale) ? $fieldScale : $formatDecimal; + } else { + $numDecimals = $fieldScale; + } + $expected = number_format($input, $numDecimals); + if ($actual === $expected) { + $matched = true; + } else { + echo "For $column: expected $expected but the value is $actual\n"; + } + } + return $matched; +} + +function testErrorCases($conn) +{ + $query = "SELECT 0.0001"; + + $options = array('FormatDecimals' => 1.5); + $stmt = sqlsrv_query($conn, $query, array(), $options); + if ($stmt) { + fatalError("Case 1: expected query to fail!!"); + } else { + $error = sqlsrv_errors()[0]['message']; + $message = 'Expected an integer to specify number of decimals to format the output values of decimal data types.'; + + if (strpos($error, $message) === false) { + print_r(sqlsrv_errors()); + } + } + + $options = array('FormatDecimals' => -1); + $stmt = sqlsrv_query($conn, $query, array(), $options); + if ($stmt) { + fatalError("Case 2: expected query to fail!!"); + } else { + $error = sqlsrv_errors()[0]['message']; + $message = 'For formatting decimal data values, -1 is out of range. Expected an integer from 0 to 38, inclusive.'; + + if (strpos($error, $message) === false) { + print_r(sqlsrv_errors()); + } + } +} + +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 + $values = array('2.9978', '-0.2982', '33.2434', '329.690734', '110.913498'); + $epsilon = 0.001; + + $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 = sqlsrv_query($conn, $query); + $floats = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + if (!$floats) { + echo "testFloatTypes: sqlsrv_fetch_array failed\n"; + } + + // Set FormatDecimals to 2, but the number of decimals in each of the results + // will vary -- FormatDecimals has no effect + $numDigits = 2; + $options = array('FormatDecimals' => $numDigits); + $stmt = sqlsrv_query($conn, $query, array(), $options); + if (sqlsrv_fetch($stmt)) { + for ($i = 0; $i < count($values); $i++) { + $floatStr = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); + $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); + var_dump($floatVal); + } + } + } else { + echo "testFloatTypes: sqlsrv_fetch failed\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('1.9954', '0', '-0.5', '0.2954', '9.6789', '99.991'); + $defaults = array('1.9954', '.0000', '-.5000', '.2954', '9.6789', '99.9910'); + + $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 = sqlsrv_query($conn, $query); + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + for ($i = 0; $i < count($values); $i++) { + if ($defaults[$i] !== $results[$i]) { + echo "testMoneyTypes: Expected default $defaults[$i] but got $results[$i]\n"; + } + } + + // Set FormatDecimals to 0 decimal digits + $numDigits = 0; + $options = array('FormatDecimals' => $numDigits); + $stmt = sqlsrv_query($conn, $query, array(), $options); + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + for ($i = 0; $i < count($values); $i++) { + $value = number_format($values[$i], $numDigits); + if ($value !== $results[$i]) { + echo "testMoneyTypes: Expected $value but got $results[$i]\n"; + } + } + + // Set FormatDecimals to 2 decimal digits + $numDigits = 2; + $options = array('FormatDecimals' => $numDigits); + $stmt = sqlsrv_query($conn, $query, array(), $options); + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + for ($i = 0; $i < count($values); $i++) { + $value = number_format($values[$i], $numDigits); + if ($value !== $results[$i]) { + echo "testMoneyTypes: Expected $value but got $results[$i]\n"; + } + } +} + +function testNoOption($conn, $tableName, $inputs, $columns, $exec) +{ + // Without the statement option, should return decimal values as they are + $query = "SELECT * FROM $tableName"; + if ($exec) { + $stmt = sqlsrv_query($conn, $query); + } else { + $stmt = sqlsrv_prepare($conn, $query); + sqlsrv_execute($stmt); + } + + // Compare values + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + 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 FormatDecimals + $query = "SELECT * FROM $tableName"; + if ($withBuffer){ + $options = array('Scrollable' => 'buffered', 'FormatDecimals' => $formatDecimal); + } else { + $options = array('FormatDecimals' => $formatDecimal); + } + + $size = count($inputs); + $stmt = sqlsrv_prepare($conn, $query, array(), $options); + + // Fetch by getting one field at a time + sqlsrv_execute($stmt); + + if (sqlsrv_fetch($stmt) === false) { + fatalError("Failed in retrieving data\n"); + } + for ($i = 0; $i < $size; $i++) { + $field = sqlsrv_get_field($stmt, $i); // Expect a string + compareNumbers($field, $inputs[$i], $columns[$i], $i, $formatDecimal); + } +} + +function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale) +{ + $outString = ''; + $numDigits = 2; + + // Derive the sqlsrv type SQLSRV_SQLTYPE_DECIMAL($prec, $scale) + $sqlType = call_user_func('SQLSRV_SQLTYPE_DECIMAL', $prec, $scale); + + $outSql = AE\getCallProcSqlPlaceholders($storedProcName, 1); + $stmt = sqlsrv_prepare($conn, $outSql, + array(array(&$outString, SQLSRV_PARAM_OUT, null, $sqlType)), + array('FormatDecimals' => $numDigits)); + if (!$stmt) { + fatalError("getOutputParam: failed when preparing to call $storedProcName"); + } + if (!sqlsrv_execute($stmt)) { + fatalError("getOutputParam: failed to execute procedure $storedProcName"); + } + + // The output param should have been formatted based on $numDigits, if less + // than $scale + $column = 'outputParam'; + compareNumbers($outString, $inputValue, $column, $scale, $numDigits); + 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 = ''; + $stmt = sqlsrv_prepare($conn, $outSql, + array(array(&$outString2, SQLSRV_PARAM_OUT)), + array('FormatDecimals' => $numDigits)); + if (!$stmt) { + fatalError("getOutputParam2: failed when preparing to call $storedProcName"); + } + if (!sqlsrv_execute($stmt)) { + fatalError("getOutputParam2: failed to execute procedure $storedProcName"); + } + + $column = 'outputParam2'; + compareNumbers($outString2, $inputValue, $column, $scale); + sqlsrv_free_stmt($stmt); + } +} + +function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes) +{ + 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); + + dropProc($conn, $storedProcName); + } +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); + +$conn = AE\connect(); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// First to test if leading zero is added +testMoneyTypes($conn); + +// Then test error conditions +testErrorCases($conn); + +// Also test using regular floats +testFloatTypes($conn); + +// Create the test table of decimal / numeric data columns +$tableName = 'sqlsrvFormatDecimals'; + +$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 AE\ColumnMeta($dataTypes[0], $columns[0]), + new AE\ColumnMeta($dataTypes[1], $columns[1]), + new AE\ColumnMeta($dataTypes[2], $columns[2]), + new AE\ColumnMeta($dataTypes[3], $columns[3]), + new AE\ColumnMeta($dataTypes[4], $columns[4]), + new AE\ColumnMeta($dataTypes[5], $columns[5])); +AE\createTable($conn, $tableName, $colMeta); + +// Generate random input values based on precision and scale +$values = array(); +$max2 = 1; +for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) { + // First get a random number + $n = rand(0, 10); + $neg = ($n % 2 == 0) ? -1 : 1; + + // $n1 may or may not be negative + $max1 = 1000; + $n1 = rand(0, $max1) * $neg; + + if ($s > 0) { + $max2 *= 10; + $n2 = rand(0, $max2); + $number = sprintf("%d.%d", $n1, $n2); + } else { + $number = sprintf("%d", $n1); + } + + array_push($values, $number); +} + +// Insert data values as strings +$inputData = array($colMeta[0]->colName => $values[0], + $colMeta[1]->colName => $values[1], + $colMeta[2]->colName => $values[2], + $colMeta[3]->colName => $values[3], + $colMeta[4]->colName => $values[4], + $colMeta[5]->colName => $values[5]); +$stmt = AE\insertRow($conn, $tableName, $inputData); +if (!$stmt) { + var_dump($values); + fatalError("Failed to insert data.\n"); +} +sqlsrv_free_stmt($stmt); + +testNoOption($conn, $tableName, $values, $columns, true); +testNoOption($conn, $tableName, $values, $columns, false); + +// 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); + +dropTable($conn, $tableName); +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt new file mode 100644 index 000000000..4abb0398d --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt @@ -0,0 +1,255 @@ +--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-- + +--FILE-- + $digits)); + + // Restore the $i-th digit with its original digit + $digits[$i] = $d; + } + + $stmt = AE\insertRow($conn, $tableName, $inputData); + if (!$stmt) { + fatalError("Failed to insert data\n"); + } + sqlsrv_free_stmt($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 = sqlsrv_query($conn, $query); + if (!$stmt) { + fatalError("In testVariousScales: failed in default case\n"); + } + if ($obj = sqlsrv_fetch_object($stmt)) { + trace("\n$obj->col1\n"); + if ($obj->col1 !== $input) { + echo "default case: expected $input but returned $obj->col1\n"; + } + } else { + fatalError("In testVariousScales: sqlsrv_fetch_object failed\n"); + } + + // Next, format how many decimal digits to be displayed + $query = "SELECT $column FROM $tableName"; + for ($j = 0; $j <= $scale; $j++) { + $options = array('FormatDecimals' => $j); + $stmt = sqlsrv_query($conn, $query, array(), $options); + + if (sqlsrv_fetch($stmt)) { + $value = sqlsrv_get_field($stmt, 0); + trace("$value\n"); + + if ($j == 0) { + verifyNoDecimals($value, $input, $i); + } else { + verifyWithDecimals($value, $input, $i, $j); + } + } else { + fatalError("Round $i scale $j: sqlsrv_fetch failed\n"); + } + } + } +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); + +$conn = AE\connect(); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +$tableName = createTestTable($conn); +$inputData = insertTestData($conn, $tableName); +testVariousScales($conn, $tableName, $inputData); + +dropTable($conn, $tableName); + +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file From b3072a99eeb33a445f815d99230d3437e1574bbb Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 19 Oct 2018 14:48:21 -0700 Subject: [PATCH 062/249] Modified how to send stream data using SQLPutData and SQLParamData (#865) --- Dockerfile-msphpsql | 6 +- source/shared/core_sqlsrv.h | 3 +- source/shared/core_stmt.cpp | 23 ++-- .../pdo_sqlsrv/MsData_PDO_AllTypes.inc | 2 +- .../pdo_sqlsrv/pdostatement_GetDataType.phpt | Bin 5677 -> 5692 bytes .../pdostatement_bindParam_empty_binary.phpt | 92 +++++++++++++++ .../pdo_sqlsrv/pdostatement_fetchAll.phpt | Bin 17989 -> 18019 bytes .../pdo_sqlsrv/pdostatement_fetchObject.phpt | Bin 3978 -> 3993 bytes .../pdo_sqlsrv/pdostatement_nextRowset.phpt | Bin 10727 -> 10757 bytes .../sqlsrv/sqlsrv_param_empty_binary.phpt | 105 ++++++++++++++++++ test/functional/sqlsrv/test_largeData.phpt | 6 + 11 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdostatement_bindParam_empty_binary.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_param_empty_binary.phpt diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 43435eb3f..ab289479f 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -44,8 +44,10 @@ RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql17 mssql-tools ENV PATH="/opt/mssql-tools/bin:${PATH}" -#install coveralls -RUN python -m pip install --upgrade pip && pip install cpp-coveralls +#install coveralls (upgrade both pip and requests first) +RUN python -m pip install --upgrade pip +RUN python -m pip install --upgrade requests +RUN python -m pip install cpp-coveralls #Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) #One option is to get source from zip file of repository. diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 35e49cbe3..59b5afcd3 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1939,9 +1939,10 @@ namespace core { inline void check_for_mars_error( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r TSRMLS_DC ) { + // Skip this if not SQL_ERROR - // We check for the 'connection busy' error caused by having MultipleActiveResultSets off // and return a more helpful message prepended to the ODBC errors if that error occurs - if( !SQL_SUCCEEDED( r )) { + if (r == SQL_ERROR) { SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'}; SQLSMALLINT len = 0; diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index ae86d4884..58ec45208 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1298,15 +1298,15 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) php_stream* param_stream = NULL; core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); - // if we're at the end, then release our current parameter - if( php_stream_eof( param_stream )) { - // if no data was actually sent prior, then send a NULL - if( stmt->current_stream_read == 0 ) { - // send an empty string, which is what a 0 length does. - char buff[1]; // temp storage to hand to SQLPutData - core::SQLPutData( stmt, buff, 0 TSRMLS_CC ); + // if we're at the end, then reset both current_stream and current_stream_read + if (php_stream_eof(param_stream)) { + // yet return to the very beginning of param_stream since SQLParamData() may ask for the same data again + int ret = php_stream_seek(param_stream, 0, SEEK_SET); + if (ret != 0) { + LOG(SEV_ERROR, "PHP stream: stream seek failed."); + throw core::CoreException(); } - stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); + stmt->current_stream = sqlsrv_stream(NULL, SQLSRV_ENCODING_CHAR); stmt->current_stream_read = 0; } // read the data from the stream, send it via SQLPutData and track how much we've sent. @@ -1322,7 +1322,12 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } stmt->current_stream_read += static_cast( read ); - if( read > 0 ) { + if (read == 0) { + // send an empty string, which is what a 0 length does. + char buff[1]; // temp storage to hand to SQLPutData + core::SQLPutData(stmt, buff, 0 TSRMLS_CC); + } + else if (read > 0) { // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character // then read in the appropriate number more bytes and then retest the string. This way we try at most to convert it // twice. diff --git a/test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc b/test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc index 14f89a1a3..2fe6d98ab 100644 --- a/test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc +++ b/test/functional/pdo_sqlsrv/MsData_PDO_AllTypes.inc @@ -12,7 +12,7 @@ $int_col = array(1, 2); $bin = fopen('php://memory', 'a'); -fwrite($bin, '00'); +fwrite($bin, hex2bin('6162636465')); // 'abcde' rewind($bin); $binary_col = array($bin, $bin); diff --git a/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt b/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt index 99c9b6c5dc9850dcfa24e9e9a65410ba1ee69fb8..bdd87ffe06ab3d07ebd19f8aba3d6bfc6f9d8503 100644 GIT binary patch delta 61 vcmZ3hvqxvcPcBYVO$DXIq~w&;$$z +--FILE-- +prepare($query); + $stmt->bindParam(1, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(2, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(3, $bin, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + + $stmt->execute(); + fclose($bin); + + $bin2 = fopen('php://memory', 'a'); + fwrite($bin2, $inputs[1]); // 'ABC' will be 0x414243 in hex + rewind($bin2); + + $stmt->bindParam(1, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(2, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(3, $bin2, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + + $stmt->execute(); + fclose($bin2); + + // Verify the data by fetching and comparing against the inputs + $query = "SELECT * FROM $tableName"; + $stmt = $conn->query($query); + $rowset = $stmt->fetchAll(); + + for ($i = 0; $i < 2; $i++) { + for ($j = 0; $j < 3; $j++) { + $str = $rowset[$i][$j]; + $len = strlen($str); + $failed = false; + + if ($j == 0) { + // binary fields have fixed size, unlike varbinary ones + if ($len !== $size || trim($str) !== $inputs[$i]) { + $failed = true; + } + } else { + if ($len !== strlen($inputs[$i]) || $str !== $inputs[$i]) { + $failed = true; + } + } + + if ($failed) { + $row = $i + 1; + $col = $j + 1; + echo "Unexpected value returned from row $row and column $col: \n"; + var_dump($str); + } + } + } + + dropTable($conn, $tableName); + unset($stmt); + unset($conn); + + echo "Done\n"; +} catch (PDOException $e) { + var_dump($e); + exit; +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt index dc7ccae64c4d9fda3c8e9e985b30d2227ec19ae5..62bc542d8820bf95e3b41f4271db38eefba6a94f 100644 GIT binary patch delta 139 zcmX@w!}z#|al;Nt*2JXbl+?-ldBizQH5HV=yv=td1(*@s$sc7T5WLO2vZwhF+{t&e okt8So&{o%io1?^~00hxS=CQVRU}kYiQD$Dc23)~rdmT0@00+P+bpQYW delta 128 zcmaFd!+5lZal;NtRs#bD28PM|dBizQH5HV=yv=td1(+2KfLtXm1t5qvG>Eme12chnY1oDLEx|@W#uh4dyCBx1^_>EEg1j+ delta 112 zcmZn-c^hndyDfPsNw@ +--FILE-- + $inputs[0], "VarBinaryCol" => $inputs[1], "VarBinaryMaxCol" => $inputs[2]), $r, AE\INSERT_PREPARE_PARAMS); + + +$inputs = array(new AE\BindParamOption($inputValues[1], + null, + "SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)", + "SQLSRV_SQLTYPE_BINARY($size)"), + new AE\BindParamOption($inputValues[1], + null, + "SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)", + "SQLSRV_SQLTYPE_VARBINARY($size)"), + new AE\BindParamOption($inputValues[1], + null, + "SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_BINARY)", + "SQLSRV_SQLTYPE_VARBINARY('max')")); +$r; +$stmt = AE\insertRow($conn, $tableName, array("BinaryCol" => $inputs[0], "VarBinaryCol" => $inputs[1], "VarBinaryMaxCol" => $inputs[2]), $r, AE\INSERT_PREPARE_PARAMS); + +// Verify the data by fetching and comparing against the inputs +$query = "SELECT * FROM $tableName"; +$stmt = sqlsrv_query($conn, $query); +if (!$stmt) { + fatalError("Failed to retrieve data from $tableName"); +} + +for ($i = 0; $i < 2; $i++) { + $rowNum = $i + 1; + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + if (!$row) { + fatalError("Failed in sqlsrv_fetch_array for row $rowNum"); + } + + for ($j = 0; $j < 3; $j++) { + $str = $row[$j]; + $len = strlen($str); + $failed = false; + + if ($j == 0) { + // binary fields have fixed size, unlike varbinary ones + if ($len !== $size || trim($str) !== $inputValues[$i]) { + $failed = true; + } + } else { + $inputLen = strlen($inputValues[$i]); + if ($len !== $inputLen || $str !== $inputValues[$i]) { + $failed = true; + } + } + + if ($failed) { + $colNum = $j + 1; + echo "Unexpected value returned from row $rowNum and column $colNum: \n"; + var_dump($str); + } + } +} + +dropTable($conn, $tableName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +echo "Done\n"; + +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/test_largeData.phpt b/test/functional/sqlsrv/test_largeData.phpt index 5f1969ae6..e282df2a7 100644 --- a/test/functional/sqlsrv/test_largeData.phpt +++ b/test/functional/sqlsrv/test_largeData.phpt @@ -42,6 +42,12 @@ class my_stream { function stream_seek($offset, $whence) { + // For the purpose of this test only support SEEK_SET to $offset 0 + if ($whence == SEEK_SET && $offset == 0) { + $this->total_read = $offset; + return true; + } + return false; } } From 2a9398f7e0aaf29f30d6b35cf0a673b61f81a98c Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 24 Oct 2018 12:37:14 -0700 Subject: [PATCH 063/249] Updated instructions to include Ubuntu 18.10 (#869) --- Linux-mac-install.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index 3988542fd..4d4eb3ceb 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -5,30 +5,33 @@ These instructions install PHP 7.2 by default -- see the notes at the beginning ## Contents of this page: -- [Installing the drivers on Ubuntu 16.04 and 18.04](#installing-the-drivers-on-ubuntu-1604-and-1804) +- [Installing the drivers on Ubuntu 16.04, 18.04 and 18.10](#installing-the-drivers-on-ubuntu-1604-1804-and-1810) - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) - [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12) - [Installing the drivers on macOS El Capitan, Sierra, High Sierra and Mojave](#installing-the-drivers-on-macos-el-capitan-sierra-high-sierra-and-mojave) -## Installing the drivers on Ubuntu 16.04 and 18.04 +## Installing the drivers on Ubuntu 16.04, 18.04 and 18.10 > [!NOTE] > To install PHP 7.0 or 7.1, replace 7.2 with 7.0 or 7.1 in the following commands. > For Ubuntu 18.04, the step to add the ondrej repository is not required unless -> PHP 7.0 or 7.1 is needed. However, installing PHP 7.0 or 7.1 in Ubuntu 18.04 may -> not work as packages from the ondrej repository come with dependencies that may -> conflict with a base Ubuntu 18.04 install. +> PHP 7.0 or 7.1 is needed. However, installing PHP 7.0 or 7.1 in Ubuntu 18.04 or 18.10 +> may not work as packages from the ondrej repository come with dependencies that may +> conflict with a base Ubuntu 18.04 or 18.10 install. ### Step 1. Install PHP ``` sudo su -add-apt-repository ppa:ondrej/php -y +# The following step is required for Ubuntu 16.04 only +add-apt-repository ppa:ondrej/php -y apt-get update apt-get install php7.2 php7.2-dev php7.2-xml -y --allow-unauthenticated ``` ### Step 2. Install prerequisites -Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). +Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). + +For Ubuntu 18.10, follow the above steps for Ubuntu 18.04 except replace `18.04` by `18.10` and download ODBC 17.3 preview [here](https://www.microsoft.com/en-us/download/details.aspx?id=57341). ### Step 3. Install the PHP drivers for Microsoft SQL Server ``` From f4ad2ae1d4df544246cae7bb6b30ee353b3d4843 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 2 Nov 2018 14:34:27 -0700 Subject: [PATCH 064/249] Feature request 415 for pdo_sqlsrv (#873) --- source/pdo_sqlsrv/pdo_dbh.cpp | 10 +- source/pdo_sqlsrv/pdo_init.cpp | 1 + source/pdo_sqlsrv/pdo_stmt.cpp | 4 + source/pdo_sqlsrv/pdo_util.cpp | 8 + source/pdo_sqlsrv/php_pdo_sqlsrv.h | 5 +- source/shared/core_sqlsrv.h | 3 +- source/shared/core_stmt.cpp | 68 +-- source/shared/core_util.cpp | 19 - .../pdostatement_format_decimals.phpt | 390 ++++++++++++++++++ .../pdostatement_format_decimals_scales.phpt | 248 +++++++++++ .../sqlsrv_statement_format_decimals.phpt | 54 ++- 11 files changed, 744 insertions(+), 66 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt create mode 100644 test/functional/pdo_sqlsrv/pdostatement_format_decimals_scales.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index b424866e4..bf25d5a84 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -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( new stmt_option_emulate_prepares ) }, { NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr( new stmt_option_fetch_numeric ) }, { NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr( new stmt_option_fetch_datetime ) }, + { NULL, 0, PDO_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr( new stmt_option_format_decimals ) }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -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(); diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index a5fcbdd0c..6d47cf5b6 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -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 diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 697ca40e7..de1fc882a 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -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; diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index dee2e3e06..0295b406b 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -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, {} } }; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 3b13953a4..ced89eeef 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -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 diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 59b5afcd3..91be215d8 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -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 ); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 58ec45208..206fe11d5 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -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(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(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); + + if (output_param->encoding != SQLSRV_ENCODING_CHAR) { + char* outString = NULL; + SQLLEN outLen = 0; + bool result = convert_string_from_utf16(output_param->encoding, reinterpret_cast(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); + core::sqlsrv_zval_stringl(value_z, str, str_len); + } } } break; diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index ca097a24b..03675a086 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -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(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"); diff --git a/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt b/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt new file mode 100644 index 000000000..b4410e279 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt @@ -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-- + +--FILE-- +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 diff --git a/test/functional/pdo_sqlsrv/pdostatement_format_decimals_scales.phpt b/test/functional/pdo_sqlsrv/pdostatement_format_decimals_scales.phpt new file mode 100644 index 000000000..a8a40d482 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdostatement_format_decimals_scales.phpt @@ -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-- + +--FILE-- + $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 diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt index 7e9e4b243..23ddbba94 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt @@ -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); From 3679b48df2e307b0a2f1f833450963f5e8ef93ed Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 7 Nov 2018 16:37:11 -0800 Subject: [PATCH 065/249] Skipped some tests when running against Azure (#874) --- test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt | 2 +- test/functional/pdo_sqlsrv/issue_52_pdo.phpt | 2 +- .../pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt | 3 ++- test/functional/sqlsrv/MsCommon.inc | 12 ++++++++++++ test/functional/sqlsrv/TC81_MemoryCheck.phpt | 3 ++- test/functional/sqlsrv/issue_52.phpt | 2 +- .../sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt | 3 ++- .../sqlsrv/sqlsrv_azure_ad_authentication.phpt | 2 +- 8 files changed, 22 insertions(+), 7 deletions(-) diff --git a/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt b/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt index 8cc648386..9fc7cd955 100644 --- a/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt +++ b/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt @@ -6,7 +6,7 @@ emalloc (which only allocate memory in the memory space allocated for the PHP pr --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- + --FILE-- + --FILE-- + --FILE-- $azureUsername, "PWD"=>$azurePassword, - "Authentication"=>'ActiveDirectoryPassword', "TrustServerCertificate"=>true ); + "Authentication"=>'ActiveDirectoryPassword', "TrustServerCertificate"=>false ); $conn = sqlsrv_connect( $azureServer, $connectionInfo ); if( $conn === false ) From 69e82080ea19f633d56c49b983984f8828fc675c Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 13 Nov 2018 15:33:07 -0800 Subject: [PATCH 066/249] Modified config files to add the compiler flag, /Qspectre (#878) --- appveyor.yml | 2 +- source/pdo_sqlsrv/config.w32 | 9 ++++++++- source/sqlsrv/config.w32 | 11 +++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 4a0fcbddd..ee9f2eb37 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,7 +24,7 @@ environment: SQL_INSTANCE: SQL2017 PHP_VC: 15 PHP_MAJOR_VER: 7.2 - PHP_MINOR_VER: latest + PHP_MINOR_VER: 11 PHP_EXE_PATH: x64\Release_TS THREAD: ts platform: x64 diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index cd6e3a063..8b8e4f29a 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -37,8 +37,15 @@ if( PHP_PDO_SQLSRV != "no" ) { if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/guard:cf /O2" ); ADD_FLAG( "CFLAGS_PDO_SQLSRV", "/D ZEND_WIN32_FORCE_INLINE" ); if (VCVERS >= 1913) { + ADD_FLAG("LDFLAGS_PDO_SQLSRV", "/d2:-guardspecload"); ADD_FLAG("CFLAGS_PDO_SQLSRV", "/Qspectre"); - } + } else if (VCVERS == 1900) { + var subver1900 = probe_binary(PHP_CL).substr(6); + if (subver1900 >= 24241) { + ADD_FLAG("LDFLAGS_PDO_SQLSRV", "/d2:-guardspecload"); + ADD_FLAG('CFLAGS_PDO_SQLSRV', "/Qspectre"); + } + } ADD_EXTENSION_DEP('pdo_sqlsrv', 'pdo'); EXTENSION("pdo_sqlsrv", pdo_sqlsrv_src_class, PHP_PDO_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); } else { diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index b6c2b1b1a..e4cd19d01 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -36,11 +36,18 @@ if( PHP_SQLSRV != "no" ) { ADD_FLAG( "CFLAGS_SQLSRV", "/GS" ); ADD_FLAG( "CFLAGS_SQLSRV", "/Zi" ); if (VCVERS >= 1913) { + ADD_FLAG("LDFLAGS_SQLSRV", "/d2:-guardspecload"); ADD_FLAG("CFLAGS_SQLSRV", "/Qspectre"); - } + } else if (VCVERS == 1900) { + var subver1900 = probe_binary(PHP_CL).substr(6); + if (subver1900 >= 24241) { + ADD_FLAG("LDFLAGS_SQLSRV", "/d2:-guardspecload"); + ADD_FLAG('CFLAGS_SQLSRV', "/Qspectre"); + } + } if (PHP_DEBUG != "yes") ADD_FLAG( "CFLAGS_SQLSRV", "/guard:cf /O2" ); EXTENSION("sqlsrv", sqlsrv_src_class , PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); } else { WARNING("sqlsrv not enabled; libraries and headers not found"); - } + } } From d51f6db9c1a5ac57d71bb305892c7482cf8916f0 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 14 Nov 2018 12:19:29 -0800 Subject: [PATCH 067/249] Merge the commit from master re survey image link (#880) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19a5d88c3..5d1422ccb 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.1+ with improv Thank you for taking the time to participate in our last survey. You can continue to help us improve by letting us know how we are doing and how you use PHP by taking our December pulse survey: - + ### Status of Most Recent Builds | AppVeyor (Windows) | Travis CI (Linux) | Coverage (Windows) | Coverage (Linux) | From 78911f4697805fae0265f9ff07981f8bba9bf590 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 16 Nov 2018 15:03:53 -0800 Subject: [PATCH 068/249] Fixed the flaws of decimal tests and added more debugging (#879) --- .../pdostatement_format_decimals.phpt | 17 +++++++++++------ .../sqlsrv_statement_format_decimals.phpt | 13 ++++++++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt b/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt index b4410e279..ff29a8086 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt @@ -188,21 +188,26 @@ function compareNumbers($actual, $input, $column, $fieldScale, $formatDecimal = $matched = false; if ($actual === $input) { $matched = true; - trace("$actual, $input\n"); + trace("Matched: $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; + $expected = number_format($input, $numDecimals); } else { - $numDecimals = $fieldScale; + $expected = number_format($input, $fieldScale); + if (abs($input) < 1) { + // Since no formatting, the leading zero should not be there + trace("Drop leading zero of $input--"); + $expected = str_replace('0.', '.', $expected); + } } - $expected = number_format($input, $numDecimals); - trace("$actual, $expected\n"); + trace("With number_format: $actual, $expected\n"); if ($actual === $expected) { $matched = true; } else { - echo "For $column: expected $expected but the value is $actual\n"; + echo "For $column ($formatDecimal): expected $expected ($input) but the value is $actual\n"; } } return $matched; @@ -265,7 +270,7 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $ino $paramType = PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT; // For inout parameters the input type should match the output one - $outString = '0.0'; + $outString = '0.0'; } else { $paramType = PDO::PARAM_STR; } diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt index 23ddbba94..223e4fb6b 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt @@ -30,19 +30,26 @@ function compareNumbers($actual, $input, $column, $fieldScale, $formatDecimal = $matched = false; if ($actual === $input) { $matched = true; + trace("Matched: $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; + $expected = number_format($input, $numDecimals); } else { - $numDecimals = $fieldScale; + $expected = number_format($input, $fieldScale); + if (abs($input) < 1) { + // Since no formatting, the leading zero should not be there + trace("Drop leading zero of $input--"); + $expected = str_replace('0.', '.', $expected); + } } - $expected = number_format($input, $numDecimals); + trace("With number_format: $actual, $expected\n"); if ($actual === $expected) { $matched = true; } else { - echo "For $column: expected $expected but the value is $actual\n"; + echo "For $column ($formatDecimal): expected $expected ($input) but the value is $actual\n"; } } return $matched; From 8e6c181c593c2f1e92816643d2c41dd295f71d1e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 22 Nov 2018 19:25:30 -0800 Subject: [PATCH 069/249] Changed sample code to adhere to PSR standard (#887) --- sample/pdo_sqlsrv_sample.php | 123 +++++++++++++++++------------------ sample/sqlsrv_sample.php | 120 +++++++++++++++++----------------- 2 files changed, 122 insertions(+), 121 deletions(-) diff --git a/sample/pdo_sqlsrv_sample.php b/sample/pdo_sqlsrv_sample.php index 3c92efa37..4c1f61cd2 100644 --- a/sample/pdo_sqlsrv_sample.php +++ b/sample/pdo_sqlsrv_sample.php @@ -1,74 +1,73 @@ query( $tsql ); - - //Error handling - FormatErrors ($conn->errorInfo()); - - $productCount = 0; - $ctr = 0; - ?> + $database = "yourdatabase"; + $uid = "yourusername"; + $pwd = "yourpassword"; + + //Establishes the connection + $conn = new PDO("sqlsrv:server=$serverName ; Database = $database", $uid, $pwd); + + //Select Query + $tsql = "SELECT [CompanyName] FROM SalesLT.Customer"; + + //Executes the query + $getProducts = $conn->query($tsql); + + //Error handling + FormatErrors($conn->errorInfo()); + + $productCount = 0; + $ctr = 0; + ?>

First 10 results are :

fetch(PDO::FETCH_ASSOC)) - { - if($ctr>9) - break; - $ctr++; - echo($row['CompanyName']); - echo("
"); - $productCount++; - } - $getProducts = NULL; - - $tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.* VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())"; - - //Insert query - $insertReview = $conn->query( $tsql ); - FormatErrors ($conn->errorInfo()); - ?> + while ($row = $getProducts->fetch(PDO::FETCH_ASSOC)) { + if ($ctr>9) { + break; + } + $ctr++; + echo($row['CompanyName']); + echo("
"); + $productCount++; + } + $getProducts = null; + + $tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.* VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())"; + + //Insert query + $insertReview = $conn->query($tsql); + FormatErrors($conn->errorInfo()); + ?>

Product Key inserted is :

fetch(PDO::FETCH_ASSOC)) - { - echo($row['ProductID']."
"); - } - $insertReview = NULL; - - //Delete Query - //We are deleting the same record - $tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?"; - $param = "SQL New 1"; - - $deleteReview = $conn->prepare($tsql); - $deleteReview->bindParam(1, $param); - - $deleteReview->execute(); - FormatErrors ($deleteReview->errorInfo()); - - function FormatErrors( $error ) - { - /* Display error. */ - echo "Error information:
"; - - echo "SQLSTATE: ".$error[0]."
"; - echo "Code: ".$error[1]."
"; - echo "Message: ".$error[2]."
"; - } + while ($row = $insertReview->fetch(PDO::FETCH_ASSOC)) { + echo($row['ProductID']."
"); + } + $insertReview = null; + + //Delete Query + //We are deleting the same record + $tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?"; + $param = "SQL New 1"; + + $deleteReview = $conn->prepare($tsql); + $deleteReview->bindParam(1, $param); + + $deleteReview->execute(); + FormatErrors($deleteReview->errorInfo()); + + function FormatErrors($error) + { + /* Display error. */ + echo "Error information:
"; + + echo "SQLSTATE: ".$error[0]."
"; + echo "Code: ".$error[1]."
"; + echo "Message: ".$error[2]."
"; + } ?> \ No newline at end of file diff --git a/sample/sqlsrv_sample.php b/sample/sqlsrv_sample.php index 886e06da0..db0a0ede3 100644 --- a/sample/sqlsrv_sample.php +++ b/sample/sqlsrv_sample.php @@ -2,68 +2,70 @@ echo "\n"; $serverName = "tcp:yourserver.database.windows.net,1433"; $connectionOptions = array("Database"=>"yourdatabase", "Uid"=>"yourusername", "PWD"=>"yourpassword"); - - //Establishes the connection - $conn = sqlsrv_connect($serverName, $connectionOptions); - //Select Query - $tsql = "SELECT [CompanyName] FROM SalesLT.Customer"; - //Executes the query - $getProducts = sqlsrv_query($conn, $tsql); - //Error handling - if ($getProducts == FALSE) - die(FormatErrors(sqlsrv_errors())); - $productCount = 0; - $ctr = 0; - ?> + + //Establishes the connection + $conn = sqlsrv_connect($serverName, $connectionOptions); + //Select Query + $tsql = "SELECT [CompanyName] FROM SalesLT.Customer"; + //Executes the query + $getProducts = sqlsrv_query($conn, $tsql); + //Error handling + if ($getProducts == false) { + die(FormatErrors(sqlsrv_errors())); + } + $productCount = 0; + $ctr = 0; + ?>

First 10 results are :

9) - break; - $ctr++; - echo($row['CompanyName']); - echo("
"); - $productCount++; - } - sqlsrv_free_stmt($getProducts); - - $tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.ProductID VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())"; - //Insert query - $insertReview = sqlsrv_query($conn, $tsql); - if($insertReview == FALSE) - die(FormatErrors( sqlsrv_errors())); - ?> + while ($row = sqlsrv_fetch_array($getProducts, SQLSRV_FETCH_ASSOC)) { + if ($ctr>9) { + break; + } + $ctr++; + echo($row['CompanyName']); + echo("
"); + $productCount++; + } + sqlsrv_free_stmt($getProducts); + + $tsql = "INSERT INTO SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.ProductID VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())"; + //Insert query + $insertReview = sqlsrv_query($conn, $tsql); + if ($insertReview == false) { + die(FormatErrors(sqlsrv_errors())); + } + ?>

Product Key inserted is :

"; - - foreach ( $errors as $error ) - { - echo "SQLSTATE: ".$error['SQLSTATE']."
"; - echo "Code: ".$error['code']."
"; - echo "Message: ".$error['message']."
"; - } - } + while ($row = sqlsrv_fetch_array($insertReview, SQLSRV_FETCH_ASSOC)) { + echo($row['ProductID']); + } + sqlsrv_free_stmt($insertReview); + //Delete Query + //We are deleting the same record + $tsql = "DELETE FROM [SalesLT].[Product] WHERE Name=?"; + $params = array("SQL New 1"); + + $deleteReview = sqlsrv_prepare($conn, $tsql, $params); + if ($deleteReview == false) { + die(FormatErrors(sqlsrv_errors())); + } + + if (sqlsrv_execute($deleteReview) == false) { + die(FormatErrors(sqlsrv_errors())); + } + + function FormatErrors($errors) + { + /* Display errors. */ + echo "Error information:
"; + + foreach ($errors as $error) { + echo "SQLSTATE: ".$error['SQLSTATE']."
"; + echo "Code: ".$error['code']."
"; + echo "Message: ".$error['message']."
"; + } + } ?> \ No newline at end of file From 76c595fc2bfc0434979f5523feb03572f4bad673 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 27 Nov 2018 17:18:38 -0800 Subject: [PATCH 070/249] Decimal places for money types only (#886) --- source/pdo_sqlsrv/pdo_dbh.cpp | 49 +++- source/pdo_sqlsrv/pdo_init.cpp | 1 + source/pdo_sqlsrv/pdo_stmt.cpp | 18 +- source/pdo_sqlsrv/pdo_util.cpp | 6 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 7 +- source/shared/core_sqlsrv.h | 34 ++- source/shared/core_stmt.cpp | 189 ++++++++----- source/sqlsrv/conn.cpp | 68 +++++ source/sqlsrv/php_sqlsrv.h | 4 + source/sqlsrv/stmt.cpp | 4 +- source/sqlsrv/util.cpp | 6 +- .../pdostatement_format_decimals.phpt | 227 ++++----------- .../pdostatement_format_decimals_scales.phpt | 248 ----------------- .../pdostatement_format_money_scales.phpt | 162 +++++++++++ .../pdostatement_format_money_types.phpt | 244 ++++++++++++++++ .../sqlsrv_statement_format_decimals.phpt | 255 +++++------------ ...lsrv_statement_format_decimals_scales.phpt | 255 ----------------- .../sqlsrv_statement_format_money_scales.phpt | 167 +++++++++++ .../sqlsrv_statement_format_money_types.phpt | 261 ++++++++++++++++++ 19 files changed, 1238 insertions(+), 967 deletions(-) delete mode 100644 test/functional/pdo_sqlsrv/pdostatement_format_decimals_scales.phpt create mode 100644 test/functional/pdo_sqlsrv/pdostatement_format_money_scales.phpt create mode 100644 test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt delete mode 100644 test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index bf25d5a84..77e30f3d3 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -81,7 +81,8 @@ enum PDO_STMT_OPTIONS { PDO_STMT_OPTION_EMULATE_PREPARES, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, - PDO_STMT_OPTION_FORMAT_DECIMALS + PDO_STMT_OPTION_FORMAT_DECIMALS, + PDO_STMT_OPTION_DECIMAL_PLACES }; // List of all the statement options supported by this driver. @@ -97,6 +98,7 @@ const stmt_option PDO_STMT_OPTS[] = { { NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr( new stmt_option_fetch_numeric ) }, { NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr( new stmt_option_fetch_datetime ) }, { NULL, 0, PDO_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr( new stmt_option_format_decimals ) }, + { NULL, 0, PDO_STMT_OPTION_DECIMAL_PLACES, std::unique_ptr( new stmt_option_decimal_places ) }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -500,7 +502,9 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo query_timeout( QUERY_TIMEOUT_INVALID ), client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), fetch_numeric( false ), - fetch_datetime( false ) + fetch_datetime( false ), + format_decimals( false ), + decimal_places( NO_CHANGE_DECIMAL_PLACES ) { if( client_buffer_max_size < 0 ) { client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; @@ -1069,7 +1073,28 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: driver_dbh->fetch_datetime = (zend_is_true(val)) ? true : false; break; - + + case SQLSRV_ATTR_FORMAT_DECIMALS: + driver_dbh->format_decimals = (zend_is_true(val)) ? true : false; + break; + + case SQLSRV_ATTR_DECIMAL_PLACES: + { + // first check if the input is an integer + if (Z_TYPE_P(val) != IS_LONG) { + THROW_PDO_ERROR(driver_dbh, SQLSRV_ERROR_INVALID_DECIMAL_PLACES); + } + + zend_long decimal_places = Z_LVAL_P(val); + if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) { + // ignore decimal_places as this is out of range + decimal_places = NO_CHANGE_DECIMAL_PLACES; + } + + driver_dbh->decimal_places = static_cast(decimal_places); + } + break; + // Not supported case PDO_ATTR_FETCH_TABLE_NAMES: case PDO_ATTR_FETCH_CATALOG_NAMES: @@ -1097,7 +1122,6 @@ 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 ); } @@ -1156,7 +1180,6 @@ 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 ); } @@ -1229,6 +1252,18 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout break; } + case SQLSRV_ATTR_FORMAT_DECIMALS: + { + ZVAL_BOOL( return_value, driver_dbh->format_decimals ); + break; + } + + case SQLSRV_ATTR_DECIMAL_PLACES: + { + ZVAL_LONG( return_value, driver_dbh->decimal_places ); + break; + } + default: { THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); @@ -1594,6 +1629,10 @@ void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ option_key = PDO_STMT_OPTION_FORMAT_DECIMALS; break; + case SQLSRV_ATTR_DECIMAL_PLACES: + option_key = PDO_STMT_OPTION_DECIMAL_PLACES; + break; + default: CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { throw core::CoreException(); diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 6d47cf5b6..000878fae 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -287,6 +287,7 @@ namespace { { "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 }, + { "SQLSRV_ATTR_DECIMAL_PLACES" , SQLSRV_ATTR_DECIMAL_PLACES }, // 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 diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index de1fc882a..d90fd50a7 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -883,7 +883,11 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; case SQLSRV_ATTR_FORMAT_DECIMALS: - core_sqlsrv_set_format_decimals(driver_stmt, val TSRMLS_CC); + driver_stmt->format_decimals = ( zend_is_true( val )) ? true : false; + break; + + case SQLSRV_ATTR_DECIMAL_PLACES: + core_sqlsrv_set_decimal_places(driver_stmt, val TSRMLS_CC); break; default: @@ -973,6 +977,18 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; } + case SQLSRV_ATTR_FORMAT_DECIMALS: + { + ZVAL_BOOL( return_value, driver_stmt->format_decimals ); + break; + } + + case SQLSRV_ATTR_DECIMAL_PLACES: + { + ZVAL_LONG( return_value, driver_stmt->decimal_places ); + break; + } + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 0295b406b..4120876b0 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -438,13 +438,9 @@ pdo_error PDO_ERRORS[] = { { IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false} }, { - SQLSRV_ERROR_INVALID_FORMAT_DECIMALS, + SQLSRV_ERROR_INVALID_DECIMAL_PLACES, { 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, {} } }; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index ced89eeef..d9bb55e59 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -49,7 +49,8 @@ enum PDO_SQLSRV_ATTR { SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, SQLSRV_ATTR_FETCHES_DATETIME_TYPE, - SQLSRV_ATTR_FORMAT_DECIMALS + SQLSRV_ATTR_FORMAT_DECIMALS, + SQLSRV_ATTR_DECIMAL_PLACES }; // valid set of values for TransactionIsolation connection option @@ -206,6 +207,8 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { zend_long client_buffer_max_size; bool fetch_numeric; bool fetch_datetime; + bool format_decimals; + short decimal_places; pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); }; @@ -267,6 +270,8 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { direct_query = db->direct_query; fetch_numeric = db->fetch_numeric; fetch_datetime = db->fetch_datetime; + format_decimals = db->format_decimals; + decimal_places = db->decimal_places; } virtual ~pdo_sqlsrv_stmt( void ); diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 91be215d8..363ccad19 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -173,6 +173,8 @@ const int SQL_SERVER_MAX_FIELD_SIZE = 8000; const int SQL_SERVER_MAX_PRECISION = 38; const int SQL_SERVER_MAX_TYPE_SIZE = 0; const int SQL_SERVER_MAX_PARAMS = 2100; +const int SQL_SERVER_MAX_MONEY_SCALE = 4; + // increase the maximum message length to accommodate for the long error returned for operand type clash // or for conversion of a long string const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 2; @@ -230,6 +232,9 @@ enum SQLSRV_FETCH_TYPE { // buffer size of a sql state (including the null character) const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1; +// default value of decimal places (no formatting required) +const short NO_CHANGE_DECIMAL_PLACES = -1; + // buffer size allocated to retrieve data from a PHP stream. This number // was chosen since PHP doesn't return more than 8k at a time even if // the amount requested was more. @@ -1108,6 +1113,7 @@ enum SQLSRV_STMT_OPTIONS { SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE, SQLSRV_STMT_OPTION_DATE_AS_STRING, SQLSRV_STMT_OPTION_FORMAT_DECIMALS, + SQLSRV_STMT_OPTION_DECIMAL_PLACES, // Driver specific connection options SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, @@ -1302,6 +1308,11 @@ struct stmt_option_format_decimals : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; +struct stmt_option_decimal_places : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); +}; + // used to hold the table for statment options struct stmt_option { @@ -1372,7 +1383,7 @@ struct sqlsrv_output_param { SQLLEN original_buffer_len; // used to make sure the returned length didn't overflow the buffer SQLSRV_PHPTYPE php_out_type; // used to convert output param if necessary bool is_bool; - param_meta_data meta_data; // parameter meta data + param_meta_data meta_data; // parameter meta data // string output param constructor sqlsrv_output_param( _In_ zval* p_z, _In_ SQLSRV_ENCODING enc, _In_ int num, _In_ SQLUINTEGER buffer_len ) : @@ -1399,15 +1410,9 @@ struct sqlsrv_output_param { meta_data.nullable = nullable; } - SQLSMALLINT getDecimalDigits() + param_meta_data& getMetaData() { - // Return decimal_digits only for decimal / numeric types. Otherwise, return -1 - if (meta_data.sql_type == SQL_DECIMAL || meta_data.sql_type == SQL_NUMERIC) { - return meta_data.decimal_digits; - } - else { - return -1; - } + return meta_data; } }; @@ -1435,7 +1440,8 @@ struct sqlsrv_stmt : public sqlsrv_context { unsigned long query_timeout; // maximum allowed statement execution time zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings - short num_decimals; // indicates number of decimals shown in fetched results (-1 by default, which means no formatting required) + bool format_decimals; // false by default but the user can set this to true to add the missing leading zeroes and/or control number of decimal digits to show + short decimal_places; // indicates number of decimals shown in fetched results (-1 by default, which means no change to number of decimal digits) // holds output pointers for SQLBindParameter // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving @@ -1476,9 +1482,10 @@ struct field_meta_data { SQLULEN field_precision; SQLSMALLINT field_scale; SQLSMALLINT field_is_nullable; + bool field_is_money_type; field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), - field_scale (0), field_is_nullable(0) + field_scale (0), field_is_nullable(0), field_is_money_type(false) { } @@ -1527,7 +1534,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); +void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC); //********************************************************************************************************************************* // Result Set @@ -1769,8 +1776,7 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, - SQLSRV_ERROR_INVALID_FORMAT_DECIMALS, - SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, + SQLSRV_ERROR_INVALID_DECIMAL_PLACES, // Driver specific error codes starts from here. SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 206fe11d5..1a4fb7a21 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -107,7 +107,7 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); void col_cache_dtor( _Inout_ zval* data_z ); void field_cache_dtor( _Inout_ zval* data_z ); -void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len); +void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len); void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC ); @@ -142,7 +142,8 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error past_next_result_end( false ), query_timeout( QUERY_TIMEOUT_INVALID ), date_as_string(false), - num_decimals(-1), // -1 means no formatting required + format_decimals(false), // no formatting needed + decimal_places(NO_CHANGE_DECIMAL_PLACES), // the default is no formatting to resultset required buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ), param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte send_streams_at_exec( true ), @@ -925,6 +926,19 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL } } + if (meta_data->field_type == SQL_DECIMAL) { + // Check if it is money type -- get the name of the data type + char field_type_name[SS_MAXCOLNAMELEN] = {'\0'}; + SQLSMALLINT out_buff_len; + SQLLEN not_used; + core::SQLColAttribute(stmt, colno + 1, SQL_DESC_TYPE_NAME, field_type_name, + sizeof( field_type_name ), &out_buff_len, ¬_used TSRMLS_CC); + + if (!strcmp(field_type_name, "money") || !strcmp(field_type_name, "smallmoney")) { + meta_data->field_is_money_type = true; + } + } + // Set the field name lenth meta_data->field_name_len = static_cast( field_name_len ); @@ -1258,20 +1272,21 @@ 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) +void core_sqlsrv_set_decimal_places(_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) { + CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_DECIMAL_PLACES) { 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(); + zend_long decimal_places = Z_LVAL_P(value_z); + if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) { + // ignore decimal_places because it is out of range + decimal_places = NO_CHANGE_DECIMAL_PLACES; } - stmt->num_decimals = static_cast(format_decimals); + stmt->decimal_places = static_cast(decimal_places); } catch( core::CoreException& ) { throw; @@ -1447,7 +1462,17 @@ 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 ) { - core_sqlsrv_set_format_decimals(stmt, value_z TSRMLS_CC); + if (zend_is_true(value_z)) { + stmt->format_decimals = true; + } + else { + stmt->format_decimals = false; + } +} + +void stmt_option_decimal_places:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC ) +{ + core_sqlsrv_set_decimal_places(stmt, value_z TSRMLS_CC); } // internal function to release the active stream. Called by each main API function @@ -2114,25 +2139,29 @@ void field_cache_dtor( _Inout_ zval* data_z ) } // To be called for formatting decimal / numeric fetched values from finalize_output_parameters() and/or get_field_as_string() -void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len) +void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len) { // In SQL Server, the default maximum precision of numeric and decimal data types is 38 // - // Note: stmt->num_decimals is -1 by default, which means no formatting on decimals / numerics is necessary - // If the required number of decimals is larger than the field scale, will use the column field scale instead. - // This is to ensure the number of decimals adheres to the column field scale. If smaller, the output value may be rounded up. + // Note: decimals_places is NO_CHANGE_DECIMAL_PLACES by default, which means no formatting on decimal data is necessary + // This function assumes stmt->format_decimals is true, so it first checks if it is necessary to add the leading zero. + // + // Likewise, if decimals_places is larger than the field scale, decimals_places wil be ignored. This is to ensure the + // number of decimals adheres to the column field scale. If smaller, the output value may be rounded up. // - // Note: it's possible that the decimal / numeric value does not contain a decimal dot because the field scale is 0. - // Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of decimals_digits + // Note: it's possible that the decimal data does not contain a decimal dot because the field scale is 0. + // Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of + // format_decimals and decimals_places // std::string str = field_value; size_t pos = str.find_first_of('.'); - if (pos == std::string::npos || decimals_digits < 0) { + // The decimal dot is not found, simply return + if (pos == std::string::npos) { return; } - SQLSMALLINT num_decimals = decimals_digits; + SQLSMALLINT num_decimals = decimals_places; if (num_decimals > field_scale) { num_decimals = field_scale; } @@ -2160,47 +2189,24 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT f pos++; } - size_t last = 0; - if (num_decimals == 0) { - // Chop all decimal digits, including the decimal dot - size_t pos2 = pos + 1; - short n = str[pos2] - '0'; - if (n >= 5) { - // Start rounding up - starting from the digit left of the dot all the way to the first digit - bool carry_over = true; - for (short p = pos - 1; p >= 0 && carry_over; p--) { - n = str[p] - '0'; - if (n == 9) { - str[p] = '0' ; - carry_over = true; - } - else { - n++; - carry_over = false; - str[p] = '0' + n; - } - } - if (carry_over) { - std::ostringstream oss; - oss << '1' << str.substr(0, pos); - str = oss.str(); - pos++; - } - } - last = pos; - } - else { - size_t pos2 = pos + num_decimals + 1; - // No need to check if rounding is necessary when pos2 has passed the last digit in the input string - if (pos2 < str.length()) { + if (num_decimals == NO_CHANGE_DECIMAL_PLACES) { + // Add the minus sign back if negative + if (isNegative) { + std::ostringstream oss; + oss << '-' << str.substr(0); + str = oss.str(); + } + } else { + // Start formatting + size_t last = 0; + if (num_decimals == 0) { + // Chop all decimal digits, including the decimal dot + size_t pos2 = pos + 1; short n = str[pos2] - '0'; if (n >= 5) { - // Start rounding up - starting from the digit left of pos2 all the way to the first digit + // Start rounding up - starting from the digit left of the dot all the way to the first digit bool carry_over = true; - for (short p = pos2 - 1; p >= 0 && carry_over; p--) { - if (str[p] == '.') { // Skip the dot - continue; - } + for (short p = pos - 1; p >= 0 && carry_over; p--) { n = str[p] - '0'; if (n == 9) { str[p] = '0' ; @@ -2214,22 +2220,54 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_digits, _In_ SQLSMALLINT f } if (carry_over) { std::ostringstream oss; - oss << '1' << str.substr(0, pos2); + oss << '1' << str.substr(0, pos); str = oss.str(); - pos2++; + pos++; } } + last = pos; + } + else { + size_t pos2 = pos + num_decimals + 1; + // No need to check if rounding is necessary when pos2 has passed the last digit in the input string + if (pos2 < str.length()) { + short n = str[pos2] - '0'; + if (n >= 5) { + // Start rounding up - starting from the digit left of pos2 all the way to the first digit + bool carry_over = true; + for (short p = pos2 - 1; p >= 0 && carry_over; p--) { + if (str[p] == '.') { // Skip the dot + continue; + } + n = str[p] - '0'; + if (n == 9) { + str[p] = '0' ; + carry_over = true; + } + else { + n++; + carry_over = false; + str[p] = '0' + n; + } + } + if (carry_over) { + std::ostringstream oss; + oss << '1' << str.substr(0, pos2); + str = oss.str(); + pos2++; + } + } + } + last = pos2; + } + // Add the minus sign back if negative + if (isNegative) { + std::ostringstream oss; + oss << '-' << str.substr(0, last); + str = oss.str(); + } else { + str = str.substr(0, last); } - last = pos2; - } - - // Add the minus sign back if negative - if (isNegative) { - std::ostringstream oss; - oss << '-' << str.substr(0, last); - str = oss.str(); - } else { - str = str.substr(0, last); } size_t len = str.length(); @@ -2313,7 +2351,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) core::sqlsrv_zval_stringl(value_z, str, str_len); } else { - SQLSMALLINT decimal_digits = output_param->getDecimalDigits(); + param_meta_data metaData = output_param->getMetaData(); if (output_param->encoding != SQLSRV_ENCODING_CHAR) { char* outString = NULL; @@ -2323,15 +2361,16 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) throw core::CoreException(); } - if (stmt->num_decimals >= 0 && decimal_digits >= 0) { - format_decimal_numbers(stmt->num_decimals, decimal_digits, outString, &outLen); + if (stmt->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { + format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.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); + if (stmt->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { + format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, str, &str_len); } core::sqlsrv_zval_stringl(value_z, str, str_len); @@ -2601,8 +2640,10 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind } } - if (stmt->num_decimals >= 0 && (sql_field_type == SQL_DECIMAL || sql_field_type == SQL_NUMERIC)) { - format_decimal_numbers(stmt->num_decimals, stmt->current_meta_data[field_index]->field_scale, field_value_temp, &field_len_temp); + if (stmt->format_decimals && (sql_field_type == SQL_DECIMAL || sql_field_type == SQL_NUMERIC)) { + // number of decimal places only affect money / smallmoney fields + SQLSMALLINT decimal_places = (stmt->current_meta_data[field_index]->field_is_money_type) ? stmt->decimal_places : NO_CHANGE_DECIMAL_PLACES; + format_decimal_numbers(decimal_places, stmt->current_meta_data[field_index]->field_scale, field_value_temp, &field_len_temp); } } // else if( sql_display_size >= 1 && sql_display_size <= SQL_SERVER_MAX_FIELD_SIZE ) diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index d5f324a23..60b9f0d16 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -46,6 +46,45 @@ struct date_as_string_func { } }; +struct format_decimals_func +{ + static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC) + { + TSRMLS_C; // show as used to avoid a warning + + ss_sqlsrv_conn* ss_conn = static_cast(conn); + + if (zend_is_true(value)) { + ss_conn->format_decimals = true; + } + else { + ss_conn->format_decimals = false; + } + } +}; + +struct decimal_places_func +{ + + static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC) + { + TSRMLS_C; // show as used to avoid a warning + + // first check if the input is an integer + if (Z_TYPE_P(value) != IS_LONG) { + THROW_SS_ERROR(conn, SQLSRV_ERROR_INVALID_DECIMAL_PLACES); + } + + zend_long decimal_places = Z_LVAL_P(value); + if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) { + decimal_places = NO_CHANGE_DECIMAL_PLACES; + } + + ss_sqlsrv_conn* ss_conn = static_cast(conn); + ss_conn->decimal_places = static_cast(decimal_places); + } +}; + struct conn_char_set_func { static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) @@ -175,6 +214,7 @@ namespace SSStmtOptionNames { const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT; const char DATE_AS_STRING[] = "ReturnDatesAsStrings"; const char FORMAT_DECIMALS[] = "FormatDecimals"; + const char DECIMAL_PLACES[] = "DecimalPlaces"; } namespace SSConnOptionNames { @@ -192,6 +232,8 @@ const char ConnectionPooling[] = "ConnectionPooling"; const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; const char Database[] = "Database"; +const char DecimalPlaces[] = "DecimalPlaces"; +const char FormatDecimals[] = "FormatDecimals"; const char DateAsString[] = "ReturnDatesAsStrings"; const char Driver[] = "Driver"; const char Encrypt[] = "Encrypt"; @@ -217,6 +259,8 @@ const char WSID[] = "WSID"; enum SS_CONN_OPTIONS { SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC, + SS_CONN_OPTION_FORMAT_DECIMALS, + SS_CONN_OPTION_DECIMAL_PLACES, }; //List of all statement options supported by this driver @@ -257,6 +301,12 @@ const stmt_option SS_STMT_OPTS[] = { SQLSRV_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr( new stmt_option_format_decimals ) }, + { + SSStmtOptionNames::DECIMAL_PLACES, + sizeof( SSStmtOptionNames::DECIMAL_PLACES), + SQLSRV_STMT_OPTION_DECIMAL_PLACES, + std::unique_ptr( new stmt_option_decimal_places ) + }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -515,6 +565,24 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_BOOL, date_as_string_func::func }, + { + SSConnOptionNames::FormatDecimals, + sizeof( SSConnOptionNames::FormatDecimals), + SS_CONN_OPTION_FORMAT_DECIMALS, + SSConnOptionNames::FormatDecimals, + sizeof( SSConnOptionNames::FormatDecimals), + CONN_ATTR_BOOL, + format_decimals_func::func + }, + { + SSConnOptionNames::DecimalPlaces, + sizeof( SSConnOptionNames::DecimalPlaces), + SS_CONN_OPTION_DECIMAL_PLACES, + SSConnOptionNames::DecimalPlaces, + sizeof( SSConnOptionNames::DecimalPlaces), + CONN_ATTR_INT, + decimal_places_func::func + }, { NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table }; diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 1816942ac..3f77ecbcf 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -131,6 +131,8 @@ struct ss_sqlsrv_conn : sqlsrv_conn { HashTable* stmts; bool date_as_string; + bool format_decimals; // flag set to turn on formatting for values of decimal / numeric types + short decimal_places; // number of decimal digits to show in a result set unless format_numbers is false bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls // static variables used in process_params @@ -142,6 +144,8 @@ struct ss_sqlsrv_conn : sqlsrv_conn sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), stmts( NULL ), date_as_string( false ), + format_decimals( false ), + decimal_places( NO_CHANGE_DECIMAL_PLACES ), in_transaction( false ) { } diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 3da4ebcc7..e9ce41e8a 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -139,9 +139,11 @@ ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ { core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC ); - // initialize date_as_string based on the corresponding connection option + // inherit other values based on the corresponding connection options ss_sqlsrv_conn* ss_conn = static_cast(conn); date_as_string = ss_conn->date_as_string; + format_decimals = ss_conn->format_decimals; + decimal_places = ss_conn->decimal_places; } ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 545f699d0..ff9e7c863 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -429,13 +429,9 @@ ss_error SS_ERRORS[] = { { IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -116, false} }, { - SQLSRV_ERROR_INVALID_FORMAT_DECIMALS, + SQLSRV_ERROR_INVALID_DECIMAL_PLACES, { IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -117, 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.", -118, true} - }, // terminate the list of errors/warnings { UINT_MAX, {} } diff --git a/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt b/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt index ff29a8086..07303c4b7 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt @@ -1,24 +1,17 @@ --TEST-- -Test statement attribute PDO::SQLSRV_ATTR_FORMAT_DECIMALS for decimal types +Test connection and statement attributes for formatting decimal and numeric data (feature request issue 415) --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. +Test the attributes PDO::SQLSRV_ATTR_FORMAT_DECIMALS and PDO::SQLSRV_ATTR_DECIMAL_PLACES, the latter affects money types only, not decimal or numeric types (feature request issue 415). +Money, decimal or numeric types 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. +Setting PDO::SQLSRV_ATTR_FORMAT_DECIMALS to false will turn off all formatting, regardless of PDO::SQLSRV_ATTR_DECIMAL_PLACES value. Also, any negative PDO::SQLSRV_ATTR_DECIMAL_PLACES value will be ignored. Likewise, since money or smallmoney fields have scale 4, if PDO::SQLSRV_ATTR_DECIMAL_PLACES value is larger than 4, it will be ignored as well. 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 +2. Set PDO::SQLSRV_ATTR_FORMAT_DECIMALS to true to add the leading zeroes to money and decimal types, if missing. +3. No support for output params + +The attributes PDO::SQLSRV_ATTR_FORMAT_DECIMALS and PDO::SQLSRV_ATTR_DECIMAL_PLACES will only format the +fetched results and have no effect on other operations like insertion or update. --ENV-- PHPT_EXEC=true --SKIPIF-- @@ -35,171 +28,52 @@ function checkException($exception, $expected) } } -function testPdoAttribute($conn, $setAttr) +function testErrorCases($conn) { - // Expects exception because PDO::SQLSRV_ATTR_FORMAT_DECIMALS - // is a statement level attribute + $expected = 'Expected an integer to specify number of decimals to format the output values of decimal data types'; + $query = "SELECT 0.0001"; + 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"; + $conn->setAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS, 0); + $format = $conn->getAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS); + if ($format !== false) { + echo 'The value of PDO::SQLSRV_ATTR_FORMAT_DECIMALS should be false\n'; + var_dump($format); } + + $conn->setAttribute(PDO::SQLSRV_ATTR_DECIMAL_PLACES, 1.5); } 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); + $options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => 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); + $options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => true); $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) +function compareNumbers($actual, $input, $column, $fieldScale, $format = true) { $matched = false; if ($actual === $input) { $matched = true; trace("Matched: $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; - $expected = number_format($input, $numDecimals); - } else { - $expected = number_format($input, $fieldScale); + // if no formatting, there will be no leading zero + $expected = number_format($input, $fieldScale); + if (!$format) { if (abs($input) < 1) { // Since no formatting, the leading zero should not be there - trace("Drop leading zero of $input--"); + trace("Drop leading zero of $input: "); $expected = str_replace('0.', '.', $expected); } } @@ -207,7 +81,7 @@ function compareNumbers($actual, $input, $column, $fieldScale, $formatDecimal = if ($actual === $expected) { $matched = true; } else { - echo "For $column ($formatDecimal): expected $expected ($input) but the value is $actual\n"; + echo "For $column ($fieldScale): expected $expected ($input) but the value is $actual\n"; } } return $matched; @@ -223,35 +97,35 @@ function testNoOption($conn, $tableName, $inputs, $columns) $results = $stmt->fetch(PDO::FETCH_NUM); trace("\ntestNoOption:\n"); for ($i = 0; $i < count($inputs); $i++) { - compareNumbers($results[$i], $inputs[$i], $columns[$i], $i); + compareNumbers($results[$i], $inputs[$i], $columns[$i], $i, false); } } -function testStmtOption($conn, $tableName, $inputs, $columns, $formatDecimal, $withBuffer) +function testStmtOption($conn, $tableName, $inputs, $columns, $decimalPlaces, $withBuffer) { - // Decimal values should return decimal digits based on the valid statement - // option PDO::SQLSRV_ATTR_FORMAT_DECIMALS + // Decimal values should NOT be affected by the statement + // attribute 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); + PDO::SQLSRV_ATTR_DECIMAL_PLACES => $decimalPlaces); } else { - $options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => $formatDecimal); + $options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => $decimalPlaces); } $size = count($inputs); $stmt = $conn->prepare($query, $options); // Fetch by getting one field at a time - trace("\ntestStmtOption: $formatDecimal and buffered $withBuffer\n"); + trace("\ntestStmtOption: $decimalPlaces 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); + compareNumbers($field, $inputs[$i], $columns[$i], $i); } } @@ -262,7 +136,7 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $ino $outSql = getCallProcSqlPlaceholders($storedProcName, 1); - $options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => $numDigits); + $options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => $numDigits); $stmt = $conn->prepare($outSql, $options); $len = 1024; @@ -279,17 +153,17 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $ino $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 + // unaffected by the statement attr PDO::SQLSRV_ATTR_DECIMAL_PLACES. + // If ColumnEncryption is enabled, in which case the driver is able + // to derive the decimal type, leading zero will be added if missing if (isAEConnected()) { trace("\ngetOutputParam ($inout) with AE:\n"); $column = 'outputParamAE'; - compareNumbers($outString, $inputValue, $column, $scale, $numDigits); + compareNumbers($outString, $inputValue, $column, $scale, true); } else { trace("\ngetOutputParam ($inout) without AE:\n"); $column = 'outputParam'; - compareNumbers($outString, $inputValue, $column, $scale); + compareNumbers($outString, $inputValue, $column, $scale, false); } } @@ -314,16 +188,8 @@ try { $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'; @@ -347,8 +213,8 @@ try { $n = rand(1, 6); $neg = ($n % 2 == 0) ? -1 : 1; - // $n1 may or may not be negative - $n1 = rand(0, 1000) * $neg; + // $n1, a tiny number, which may or may not be negative, + $n1 = rand(0, 5) * $neg; if ($s > 0) { $max *= 10; @@ -371,6 +237,11 @@ try { testNoOption($conn, $tableName, $values, $columns, true); + // Turn on formatting, which only add leading zeroes, if missing + // decimal and numeric types should be unaffected by + // PDO::SQLSRV_ATTR_DECIMAL_PLACES whatsoever + $conn->setAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS, 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); diff --git a/test/functional/pdo_sqlsrv/pdostatement_format_decimals_scales.phpt b/test/functional/pdo_sqlsrv/pdostatement_format_decimals_scales.phpt deleted file mode 100644 index a8a40d482..000000000 --- a/test/functional/pdo_sqlsrv/pdostatement_format_decimals_scales.phpt +++ /dev/null @@ -1,248 +0,0 @@ ---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-- - ---FILE-- - $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 diff --git a/test/functional/pdo_sqlsrv/pdostatement_format_money_scales.phpt b/test/functional/pdo_sqlsrv/pdostatement_format_money_scales.phpt new file mode 100644 index 000000000..f7618fe0d --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdostatement_format_money_scales.phpt @@ -0,0 +1,162 @@ +--TEST-- +Test various decimal places of money values (feature request issue 415) +--DESCRIPTION-- +In SQL Server, the maximum precision of money type is 19 with scale 4. Generate a long numeric string and get rid of the last digit to make it a 15-digit-string. Then replace one digit at a time with a dot '.' to make it a decimal input string for testing. + +For example, +string(15) ".23456789098765" +string(15) "1.3456789098765" +string(15) "12.456789098765" +string(15) "123.56789098765" +string(15) "1234.6789098765" +... +string(15) "1234567890987.5" +string(15) "12345678909876." + +The inserted money data will be +0.2346 +1.3457 +12.4568 +123.5679 +1234.6789 +... +1234567890987.5000 +12345678909876.0000 + +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $digits)); + + // Restore the $i-th digit with its original digit + $digits[$i] = $d; + } + + $stmt = insertRow($conn, $tableName, $inputData); + unset($stmt); +} + +function numberFormat($value, $numDecimals) +{ + return number_format($value, $numDecimals, '.', ''); +} + +/**** +The function testVariousScales() will fetch one column at a time, using scale from 0 up to 4 allowed for that column type. + +For example, if the input string is +1234567890.2345 + +When fetching data, using scale from 0 to 4, the following values are expected to return: +1234567890 +1234567890.2 +1234567890.23 +1234567890.235 +1234567890.2345 +****/ +function testVariousScales($conn, $tableName) +{ + global $prec, $scale; + $max = $prec - $scale; + + for ($i = 0; $i < $max; $i++) { + $column = "col_$i"; + + $query = "SELECT $column as col1 FROM $tableName"; + + // Default case: no formatting + $stmt = $conn->query($query); + if ($obj = $stmt->fetchObject()) { + trace("\n$obj->col1\n"); + $input = $obj->col1; + } else { + echo "In testVariousScales: fetchObject failed\n"; + } + + // Next, format how many decimals to be displayed + $query = "SELECT $column FROM $tableName"; + for ($j = 0; $j <= $scale; $j++) { + $options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => true, PDO::SQLSRV_ATTR_DECIMAL_PLACES => $j); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + + $stmt->bindColumn($column, $value); + if ($stmt->fetch(PDO::FETCH_BOUND)) { + trace("$value\n"); + + $expected = numberFormat($input, $j); + if ($value !== $expected) { + echo "testVariousScales ($j): Expected $expected but got $value\n"; + } + } else { + echo "Round $i scale $j: fetch failed\n"; + } + } + } +} + +try { + // This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION + // Default is no formatting, but set it to false anyway + $conn = connect(); + $conn->setAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS, false); + + $tableName = createTestTable($conn); + insertTestData($conn, $tableName); + testVariousScales($conn, $tableName); + + dropTable($conn, $tableName); + + echo "Done\n"; + + unset($conn); +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt b/test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt new file mode 100644 index 000000000..c02d634ab --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt @@ -0,0 +1,244 @@ +--TEST-- +Test connection attributes for formatting money data (feature request issue 415) +--DESCRIPTION-- +Test how money data in the fetched values can be formatted by using the connection attributes PDO::SQLSRV_ATTR_FORMAT_DECIMALS and PDO::SQLSRV_ATTR_DECIMAL_PLACES, the latter works only with integer values. No effect on other operations like insertion or update. + +The PDO::SQLSRV_ATTR_DECIMAL_PLACES attribute only affects money/smallmoney fields. If its value is out of range, for example, it's negative or larger than the original scale, then its value will be ignored. + +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). For this reason, it is not recommended to use formatted money values as inputs to any calculation. + +The corresponding statement attributes always override the inherited values from the connection object. Setting PDO::SQLSRV_ATTR_FORMAT_DECIMALS to false will automatically turn off any formatting of decimal data in the result set, ignoring PDO::SQLSRV_ATTR_DECIMAL_PLACES value. + +By only setting PDO::SQLSRV_ATTR_FORMAT_DECIMALS to true will add the leading zeroes, if missing. + +Do not support output params. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +query($query); + $floats = $stmt->fetch(PDO::FETCH_NUM); + unset($stmt); + + // By default the floating point numbers are fetched as strings + $stmt = $conn->prepare($query); + 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 "$diff: Expected $floatVal but $floatVal1 returned. \n"; + } + } + } +} + +function verifyMoneyFormatting($conn, $query, $values, $format) +{ + if ($format) { + // Set SQLSRV_ATTR_FORMAT_DECIMALS to true but + // set SQLSRV_ATTR_DECIMAL_PLACES to a negative number + // to override the inherited attribute + $options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => -1, PDO::SQLSRV_ATTR_FORMAT_DECIMALS => true); + } else { + // Set SQLSRV_ATTR_FORMAT_DECIMALS to false will + // turn off any formatting -- overriding the inherited + // attributes + $options = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => false); + } + + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $results = $stmt->fetch(PDO::FETCH_NUM); + + trace("\verifyMoneyFormatting:\n"); + for ($i = 0; $i < count($values); $i++) { + // money types have a scale of 4 + $default = numberFormat($values[$i], 4); + if (!$format) { + // No formatting - should drop the leading zero, if exists + if (abs($values[$i]) < 1) { + $default = str_replace('0.', '.', $default); + } + } + if ($default !== $results[$i]) { + echo "verifyMoneyFormatting ($format): Expected $default but got $results[$i]\n"; + } + } +} + +function verifyMoneyValues($conn, $numDigits, $query, $values, $override) +{ + if ($override) { + $options = array(PDO::SQLSRV_ATTR_DECIMAL_PLACES => $numDigits); + $stmt = $conn->prepare($query, $options); + } else { + // Use the connection defaults + $stmt = $conn->prepare($query); + } + $stmt->execute(); + $results = $stmt->fetch(PDO::FETCH_NUM); + + trace("\nverifyMoneyValues:\n"); + for ($i = 0; $i < count($values); $i++) { + $value = numberFormat($values[$i], $numDigits); + trace("$results[$i], $value\n"); + + if ($value !== $results[$i]) { + echo "testMoneyTypes ($override, $numDigits): Expected $value but got $results[$i]\n"; + } + } +} + +function testMoneyTypes($conn, $numDigits) +{ + // With money and smallmoney types, which are essentially decimal types + // As of today, ODBC driver does not support Always Encrypted feature with money / smallmoney + $values = array(); + $nColumns = 6; + for ($i = 0; $i < $nColumns; $i++) { + // First get a random number + $n = rand(0, 10); + $neg = ($n % 2 == 0) ? -1 : 1; + + // $n1 may or may not be negative + $max = 10; + $n1 = rand(0, $max) * $neg; + $n2 = rand(1, $max * 1000); + + $number = sprintf("%d.%d", $n1, $n2); + array_push($values, $number); + } + + $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])"; + + // Do not override the connection attributes + verifyMoneyValues($conn, $numDigits, $query, $values, false); + // Next, override statement attribute to set number of + // decimal places + verifyMoneyValues($conn, 0, $query, $values, true); + + // Set Formatting attribute to true then false + verifyMoneyFormatting($conn, $query, $values, true); + verifyMoneyFormatting($conn, $query, $values, false); +} + +function connGetAttributes($conn, $numDigits) +{ + $format = $conn->getAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS); + if ($format !== true) { + echo "The returned value of SQLSRV_ATTR_FORMAT_DECIMALS, $format, is wrong\n"; + + return false; + } + + $digits = $conn->getAttribute(PDO::SQLSRV_ATTR_DECIMAL_PLACES); + if ($digits != $numDigits) { + echo "The returned value of SQLSRV_ATTR_DECIMAL_PLACES, $digits, is wrong\n"; + + return false; + } + + return true; +} + +function connectWithAttrs($numDigits) +{ + $attr = array(PDO::SQLSRV_ATTR_FORMAT_DECIMALS => true, + PDO::SQLSRV_ATTR_DECIMAL_PLACES => $numDigits); + + // This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION + $conn = connect('', $attr); + + if (connGetAttributes($conn, $numDigits)) { + // First test with money types + testMoneyTypes($conn, $numDigits); + + // Also test using regular floats + testFloatTypes($conn, $numDigits); + } + unset($conn); +} + +function connectSetAttrs($numDigits) +{ + // This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION + $conn = connect(); + $conn->setAttribute(PDO::SQLSRV_ATTR_FORMAT_DECIMALS, true); + $conn->setAttribute(PDO::SQLSRV_ATTR_DECIMAL_PLACES, $numDigits); + + if (connGetAttributes($conn, $numDigits)) { + // First test with money types + testMoneyTypes($conn, $numDigits); + + // Also test using regular floats + testFloatTypes($conn, $numDigits); + } + + unset($conn); +} + +try { + connectWithAttrs(2); + connectSetAttrs(3); + + echo "Done\n"; + + unset($conn); +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt index 223e4fb6b..a2faf7cf4 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_format_decimals.phpt @@ -1,22 +1,18 @@ --TEST-- -Test how decimal data output values can be formatted (feature request issue 415) +Test connection and statement attributes for formatting decimal and numeric data (feature request issue 415) --DESCRIPTION-- -Test how numeric and decimal data output values can be formatted by using the -statement option FormatDecimals, which expects an integer value from the range [0,38], -affecting only the money / decimal types in the fetched result set because they are -always strings to preserve accuracy and precision, unlike other primitive numeric -types that can be retrieved as numbers. - -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. For output params use SQLSRV_SQLTYPE_DECIMAL with the correct precision and scale +Test the connection and statement options, FormatDecimals and +DecimalPlaces, the latter affects money types only, not +decimal or numeric types (feature request issue 415). +Money, decimal or numeric types 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. + +Setting FormatDecimals to false will turn off all formatting, regardless of DecimalPlaces value. Also, any negative DecimalPlaces value will be ignored. Likewise, since money or smallmoney fields have scale 4, if DecimalPlaces value is larger than 4, it will be ignored as well. + +1. By default, data will be returned with the original precision and scale +2. Set FormatDecimals to true to add the leading zeroes to money and decimal types, if missing. +3. For output params, leading zeroes will be added for any decimal fields if FormatDecimals is true, but only if either SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC is set correctly to match the original column type and its precision / scale. + +FormatDecimals and DecimalPlaces will only format the fetched results and have no effect on other operations like insertion or update. --ENV-- PHPT_EXEC=true --SKIPIF-- @@ -25,23 +21,19 @@ PHPT_EXEC=true $fieldScale, will show $fieldScale decimal digits - if ($formatDecimal >= 0) { - $numDecimals = ($formatDecimal > $fieldScale) ? $fieldScale : $formatDecimal; - $expected = number_format($input, $numDecimals); - } else { - $expected = number_format($input, $fieldScale); + // If no formatting, there will be no leading zero + $expected = number_format($input, $fieldScale); + if (!$format) { if (abs($input) < 1) { // Since no formatting, the leading zero should not be there - trace("Drop leading zero of $input--"); + trace("Drop leading zero of $input: "); $expected = str_replace('0.', '.', $expected); } } @@ -49,7 +41,7 @@ function compareNumbers($actual, $input, $column, $fieldScale, $formatDecimal = if ($actual === $expected) { $matched = true; } else { - echo "For $column ($formatDecimal): expected $expected ($input) but the value is $actual\n"; + echo "For $column ($fieldScale): expected $expected ($input) but the value is $actual\n"; } } return $matched; @@ -58,134 +50,34 @@ function compareNumbers($actual, $input, $column, $fieldScale, $formatDecimal = function testErrorCases($conn) { $query = "SELECT 0.0001"; - - $options = array('FormatDecimals' => 1.5); + $message = 'Expected an integer to specify number of decimals to format the output values of decimal data types.'; + + $options = array('DecimalPlaces' => 1.5); $stmt = sqlsrv_query($conn, $query, array(), $options); if ($stmt) { fatalError("Case 1: expected query to fail!!"); } else { $error = sqlsrv_errors()[0]['message']; - $message = 'Expected an integer to specify number of decimals to format the output values of decimal data types.'; - if (strpos($error, $message) === false) { print_r(sqlsrv_errors()); } } - $options = array('FormatDecimals' => -1); + $options = array('DecimalPlaces' => true); $stmt = sqlsrv_query($conn, $query, array(), $options); if ($stmt) { fatalError("Case 2: expected query to fail!!"); } else { $error = sqlsrv_errors()[0]['message']; - $message = 'For formatting decimal data values, -1 is out of range. Expected an integer from 0 to 38, inclusive.'; - if (strpos($error, $message) === false) { print_r(sqlsrv_errors()); } } } -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 - $values = array('2.9978', '-0.2982', '33.2434', '329.690734', '110.913498'); - $epsilon = 0.001; - - $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 = sqlsrv_query($conn, $query); - $floats = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); - if (!$floats) { - echo "testFloatTypes: sqlsrv_fetch_array failed\n"; - } - - // Set FormatDecimals to 2, but the number of decimals in each of the results - // will vary -- FormatDecimals has no effect - $numDigits = 2; - $options = array('FormatDecimals' => $numDigits); - $stmt = sqlsrv_query($conn, $query, array(), $options); - 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 && $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 { - echo "testFloatTypes: sqlsrv_fetch failed\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('1.9954', '0', '-0.5', '0.2954', '9.6789', '99.991'); - $defaults = array('1.9954', '.0000', '-.5000', '.2954', '9.6789', '99.9910'); - - $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 = sqlsrv_query($conn, $query); - $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); - for ($i = 0; $i < count($values); $i++) { - if ($defaults[$i] !== $results[$i]) { - echo "testMoneyTypes: Expected default $defaults[$i] but got $results[$i]\n"; - } - } - - // Set FormatDecimals to 0 decimal digits - $numDigits = 0; - $options = array('FormatDecimals' => $numDigits); - $stmt = sqlsrv_query($conn, $query, array(), $options); - $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); - for ($i = 0; $i < count($values); $i++) { - $value = number_format($values[$i], $numDigits); - if ($value !== $results[$i]) { - echo "testMoneyTypes: Expected $value but got $results[$i]\n"; - } - } - - // Set FormatDecimals to 2 decimal digits - $numDigits = 2; - $options = array('FormatDecimals' => $numDigits); - $stmt = sqlsrv_query($conn, $query, array(), $options); - $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); - for ($i = 0; $i < count($values); $i++) { - $value = number_format($values[$i], $numDigits); - if ($value !== $results[$i]) { - echo "testMoneyTypes: Expected $value but got $results[$i]\n"; - } - } -} - function testNoOption($conn, $tableName, $inputs, $columns, $exec) { - // Without the statement option, should return decimal values as they are + // This should return decimal values as they are $query = "SELECT * FROM $tableName"; if ($exec) { $stmt = sqlsrv_query($conn, $query); @@ -194,27 +86,27 @@ function testNoOption($conn, $tableName, $inputs, $columns, $exec) sqlsrv_execute($stmt); } - // Compare values + // Compare values $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); for ($i = 0; $i < count($inputs); $i++) { - compareNumbers($results[$i], $inputs[$i], $columns[$i], $i); + compareNumbers($results[$i], $inputs[$i], $columns[$i], $i, false); } } -function testStmtOption($conn, $tableName, $inputs, $columns, $formatDecimal, $withBuffer) +function testStmtOption($conn, $tableName, $inputs, $columns, $decimalPlaces, $withBuffer) { - // Decimal values should return decimal digits based on the valid statement - // option FormatDecimals + // Decimal values should NOT be affected by the statement + // option DecimalPlaces $query = "SELECT * FROM $tableName"; if ($withBuffer){ - $options = array('Scrollable' => 'buffered', 'FormatDecimals' => $formatDecimal); + $options = array('Scrollable' => 'buffered', 'DecimalPlaces' => $decimalPlaces); } else { - $options = array('FormatDecimals' => $formatDecimal); + $options = array('DecimalPlaces' => $decimalPlaces); } $size = count($inputs); $stmt = sqlsrv_prepare($conn, $query, array(), $options); - + // Fetch by getting one field at a time sqlsrv_execute($stmt); @@ -223,26 +115,28 @@ function testStmtOption($conn, $tableName, $inputs, $columns, $formatDecimal, $w } for ($i = 0; $i < $size; $i++) { $field = sqlsrv_get_field($stmt, $i); // Expect a string - compareNumbers($field, $inputs[$i], $columns[$i], $i, $formatDecimal); + compareNumbers($field, $inputs[$i], $columns[$i], $i, true); } } -function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $inout) +function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $numeric, $inout) { $outString = ''; $numDigits = 2; $dir = SQLSRV_PARAM_OUT; - - // 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 + + // The output param value should be the same as the input, + // unaffected by the statement attr DecimalPlaces. If + // the correct sql type is specified or ColumnEncryption + // is enabled, in which case the driver is able to derive + // the correct field type, leading zero will be added + // if missing $sqlType = null; if (!AE\isColEncrypted()) { - $sqlType = call_user_func('SQLSRV_SQLTYPE_DECIMAL', $prec, $scale); + $type = ($numeric) ? 'SQLSRV_SQLTYPE_NUMERIC' : 'SQLSRV_SQLTYPE_DECIMAL'; + $sqlType = call_user_func($type, $prec, $scale); } - + // For inout parameters the input type should match the output one if ($inout) { $dir = SQLSRV_PARAM_INOUT; @@ -250,38 +144,37 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $ino } $outSql = AE\getCallProcSqlPlaceholders($storedProcName, 1); - $stmt = sqlsrv_prepare($conn, $outSql, - array(array(&$outString, $dir, null, $sqlType)), - array('FormatDecimals' => $numDigits)); + $stmt = sqlsrv_prepare($conn, $outSql, + array(array(&$outString, $dir, null, $sqlType)), + array('DecimalPlaces' => $numDigits)); if (!$stmt) { fatalError("getOutputParam: failed when preparing to call $storedProcName"); } if (!sqlsrv_execute($stmt)) { fatalError("getOutputParam: failed to execute procedure $storedProcName"); } - - // The output param should have been formatted based on $numDigits, if less - // than $scale + + // Verify value of output param $column = 'outputParam'; - compareNumbers($outString, $inputValue, $column, $scale, $numDigits); + compareNumbers($outString, $inputValue, $column, $scale, true); sqlsrv_free_stmt($stmt); - + if (!AE\isColEncrypted()) { // With ColumnEncryption enabled, the driver is able to derive the decimal type, // so skip this part of the test $outString2 = $inout ? '0.0' : ''; - $stmt = sqlsrv_prepare($conn, $outSql, - array(array(&$outString2, $dir)), - array('FormatDecimals' => $numDigits)); + $stmt = sqlsrv_prepare($conn, $outSql, + array(array(&$outString2, $dir)), + array('DecimalPlaces' => $numDigits)); if (!$stmt) { fatalError("getOutputParam2: failed when preparing to call $storedProcName"); } if (!sqlsrv_execute($stmt)) { fatalError("getOutputParam2: failed to execute procedure $storedProcName"); } - + $column = 'outputParam2'; - compareNumbers($outString2, $inputValue, $column, $scale); + compareNumbers($outString2, $inputValue, $column, $scale, true); sqlsrv_free_stmt($stmt); } } @@ -296,8 +189,8 @@ function testOutputParam($conn, $tableName, $inputs, $columns, $dataTypes, $inou createProc($conn, $storedProcName, $procArgs, $procCode); // Call stored procedure to retrieve output param - getOutputParam($conn, $storedProcName, $inputs[$i], $p, $i, $inout); - + getOutputParam($conn, $storedProcName, $inputs[$i], $p, $i, $i > 2, $inout); + dropProc($conn, $storedProcName); } } @@ -310,16 +203,10 @@ if (!$conn) { fatalError("Could not connect.\n"); } -// First to test if leading zero is added -testMoneyTypes($conn); - -// Then test error conditions +// Test error conditions testErrorCases($conn); -// Also test using regular floats -testFloatTypes($conn); - -// Create the test table of decimal / numeric data columns +// Create the test table of decimal / numeric data columns $tableName = 'sqlsrvFormatDecimals'; $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); @@ -338,13 +225,13 @@ $values = array(); $max2 = 1; for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) { // First get a random number - $n = rand(0, 10); + $n = rand(1, 6); $neg = ($n % 2 == 0) ? -1 : 1; - - // $n1 may or may not be negative - $max1 = 1000; + + // $n1 is a tiny number, which may or may not be negative + $max1 = 5; $n1 = rand(0, $max1) * $neg; - + if ($s > 0) { $max2 *= 10; $n2 = rand(0, $max2); @@ -352,7 +239,7 @@ for ($s = 0, $p = 3; $s < count($columns); $s++, $p++) { } else { $number = sprintf("%d", $n1); } - + array_push($values, $number); } @@ -373,6 +260,14 @@ sqlsrv_free_stmt($stmt); testNoOption($conn, $tableName, $values, $columns, true); testNoOption($conn, $tableName, $values, $columns, false); +sqlsrv_close($conn); + +// Reconnect with FormatDecimals option set to true +$conn = AE\connect(array('FormatDecimals' => true)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + // Now try with setting number decimals to 3 then 2 testStmtOption($conn, $tableName, $values, $columns, 3, false); testStmtOption($conn, $tableName, $values, $columns, 3, true); @@ -384,7 +279,7 @@ testStmtOption($conn, $tableName, $values, $columns, 2, true); testOutputParam($conn, $tableName, $values, $columns, $dataTypes); testOutputParam($conn, $tableName, $values, $columns, $dataTypes, true); -dropTable($conn, $tableName); +dropTable($conn, $tableName); sqlsrv_close($conn); echo "Done\n"; diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt deleted file mode 100644 index 4abb0398d..000000000 --- a/test/functional/sqlsrv/sqlsrv_statement_format_decimals_scales.phpt +++ /dev/null @@ -1,255 +0,0 @@ ---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-- - ---FILE-- - $digits)); - - // Restore the $i-th digit with its original digit - $digits[$i] = $d; - } - - $stmt = AE\insertRow($conn, $tableName, $inputData); - if (!$stmt) { - fatalError("Failed to insert data\n"); - } - sqlsrv_free_stmt($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 = sqlsrv_query($conn, $query); - if (!$stmt) { - fatalError("In testVariousScales: failed in default case\n"); - } - if ($obj = sqlsrv_fetch_object($stmt)) { - trace("\n$obj->col1\n"); - if ($obj->col1 !== $input) { - echo "default case: expected $input but returned $obj->col1\n"; - } - } else { - fatalError("In testVariousScales: sqlsrv_fetch_object failed\n"); - } - - // Next, format how many decimal digits to be displayed - $query = "SELECT $column FROM $tableName"; - for ($j = 0; $j <= $scale; $j++) { - $options = array('FormatDecimals' => $j); - $stmt = sqlsrv_query($conn, $query, array(), $options); - - if (sqlsrv_fetch($stmt)) { - $value = sqlsrv_get_field($stmt, 0); - trace("$value\n"); - - if ($j == 0) { - verifyNoDecimals($value, $input, $i); - } else { - verifyWithDecimals($value, $input, $i, $j); - } - } else { - fatalError("Round $i scale $j: sqlsrv_fetch failed\n"); - } - } - } -} - -set_time_limit(0); -sqlsrv_configure('WarningsReturnAsErrors', 1); - -$conn = AE\connect(); -if (!$conn) { - fatalError("Could not connect.\n"); -} - -$tableName = createTestTable($conn); -$inputData = insertTestData($conn, $tableName); -testVariousScales($conn, $tableName, $inputData); - -dropTable($conn, $tableName); - -sqlsrv_close($conn); - -echo "Done\n"; -?> ---EXPECT-- -Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt new file mode 100644 index 000000000..b27a3c480 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt @@ -0,0 +1,167 @@ +--TEST-- +Test various decimal places of money values (feature request issue 415) +--DESCRIPTION-- +In SQL Server, the maximum precision of money type is 19 with scale 4. Generate a long numeric string and get rid of the last digit to make it a 15-digit-string. Then replace one digit at a time with a dot '.' to make it a decimal input string for testing. + +For example, +string(15) ".23456789098765" +string(15) "1.3456789098765" +string(15) "12.456789098765" +string(15) "123.56789098765" +string(15) "1234.6789098765" +... +string(15) "1234567890987.5" +string(15) "12345678909876." + +The inserted money data will be +0.2346 +1.3457 +12.4568 +123.5679 +1234.6789 +... +1234567890987.5000 +12345678909876.0000 + +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $digits)); + trace($digits); + + // Restore the $i-th digit with its original digit + $digits[$i] = $d; + } + + $stmt = AE\insertRow($conn, $tableName, $inputData); + if (!$stmt) { + fatalError("Failed to insert data\n"); + } + sqlsrv_free_stmt($stmt); +} + +function numberFormat($value, $numDecimals) +{ + return number_format($value, $numDecimals, '.', ''); +} + +/**** +The function testVariousScales() will fetch one column at a time, using scale from 0 up to 4 allowed for that column type. + +For example, if the input string is +1234567890.2345 + +When fetching data, using scale from 0 to 4, the following values are expected to return: +1234567890 +1234567890.2 +1234567890.23 +1234567890.235 +1234567890.2345 +****/ +function testVariousScales($conn, $tableName) +{ + global $prec, $scale; + $max = $prec - $scale; + + for ($i = 0; $i < $max; $i++) { + $column = "col_$i"; + + $query = "SELECT $column as col1 FROM $tableName"; + // Default case: no formatting + $stmt = sqlsrv_query($conn, $query); + if (!$stmt) { + fatalError("In testVariousScales: failed in default case\n"); + } + if ($obj = sqlsrv_fetch_object($stmt)) { + trace("\n$obj->col1\n"); + $input = $obj->col1; + } else { + fatalError("In testVariousScales: sqlsrv_fetch_object failed\n"); + } + + // Next, format how many decimals to be displayed + $query = "SELECT $column FROM $tableName"; + for ($j = 0; $j <= $scale; $j++) { + $options = array('FormatDecimals' => true,'DecimalPlaces' => $j); + $stmt = sqlsrv_query($conn, $query, array(), $options); + + if (sqlsrv_fetch($stmt)) { + $value = sqlsrv_get_field($stmt, 0); + trace("$value\n"); + + $expected = numberFormat($input, $j); + if ($value !== $expected) { + echo "testVariousScales ($j): Expected $expected but got $value\n"; + } + } else { + fatalError("Round $i scale $j: sqlsrv_fetch failed\n"); + } + } + } +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); + +// Default is no formatting, but set it to false anyway +$conn = AE\connect(array('FormatDecimals' => false)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +$tableName = createTestTable($conn); +insertTestData($conn, $tableName); +testVariousScales($conn, $tableName); + +dropTable($conn, $tableName); + +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt new file mode 100644 index 000000000..ac2c1cd01 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt @@ -0,0 +1,261 @@ +--TEST-- +Test the options for formatting money data (feature request issue 415) +--DESCRIPTION-- +Test how money data in the fetched values can be formatted by using the connection +option FormatDecimals and DecimalPlaces, the latter works only with integer +values. No effect on other operations like insertion or update. + +The option DecimalPlaces only affects money/smallmoney fields. If its value is out of range, for example, it's negative or larger than the original scale, then its value will be ignored. + +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). For this reason, it is not recommended to use formatted money values as inputs to any calculation. + +The corresponding statement options always override the inherited values from the connection object. Setting FormatDecimals to false will automatically turn off any formatting of decimal data in the result set, ignoring DecimalPlaces value. + +By only setting FormatDecimals to true will add the leading zeroes, if missing. For output params, missing zeroes will be added if either SQLSRV_SQLTYPE_MONEY or SQLSRV_SQLTYPE_SMALLMONEY is set as the SQLSRV SQL Type. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $epsilon) { + echo "$diff: Expected $floats[$i] but returned "; + var_dump($floatVal); + } + } + } + } else { + echo "testFloatTypes: sqlsrv_fetch failed\n"; + } +} + +function verifyMoneyValues($conn, $numDigits, $query, $values, $override) +{ + if ($override) { + $options = array('DecimalPlaces' => $numDigits); + $stmt = sqlsrv_query($conn, $query, array(), $options); + } else { + $stmt = sqlsrv_query($conn, $query); + } + + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + trace("\nverifyMoneyValues:\n"); + for ($i = 0; $i < count($values); $i++) { + $value = numberFormat($values[$i], $numDigits); + trace("$results[$i], $value\n"); + + if ($value !== $results[$i]) { + echo "verifyMoneyValues ($override, $numDigits): Expected $value but got $results[$i]\n"; + } + } +} + +function verifyMoneyFormatting($conn, $query, $values, $format) +{ + if ($format) { + // Set FormatDecimals to true to turn on formatting, but setting + // DecimalPlaces to a negative number, which will be ignored. + $nDigits = -1; + $options = array('FormatDecimals' => true, 'DecimalPlaces' => $nDigits); + $stmt = sqlsrv_query($conn, $query, array(), $options); + } else { + // Set FormatDecimals to false to turn off formatting. + // This should override the inherited connection + // options, and by default, money and smallmoney types + // have scale of 4 digits + $options = array('FormatDecimals' => false); + $stmt = sqlsrv_query($conn, $query, array(), $options); + } + $results = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); + + for ($i = 0; $i < count($values); $i++) { + $default = numberFormat($values[$i], 4); + if (!$format) { + // No formatting - should drop the leading zero, if exists + if (abs($values[$i]) < 1) { + $default = str_replace('0.', '.', $default); + } + } + if ($default !== $results[$i]) { + echo "verifyMoneyFormatting ($format): Expected default $default but got $results[$i]\n"; + } + } +} + +function getOutputParam($conn, $spProcName, $input, $money, $inout) +{ + $outString = '0.0'; + $dir = ($inout) ? SQLSRV_PARAM_INOUT : SQLSRV_PARAM_OUT; + $sqlType = ($money) ? SQLSRV_SQLTYPE_MONEY : SQLSRV_SQLTYPE_SMALLMONEY; + + $outSql = AE\getCallProcSqlPlaceholders($spProcName, 1); + $stmt = sqlsrv_prepare($conn, $outSql, + array(array(&$outString, $dir, null, $sqlType))); + if (!$stmt) { + fatalError("getOutputParam: failed when preparing to call $spProcName"); + } + if (!sqlsrv_execute($stmt)) { + fatalError("getOutputParam: failed to execute procedure $spProcName"); + } + + // FormatDecimals only add leading zeroes, but do + // not support controlling decimal places, so + // use scale 4 for money/smallmoney types + $expected = numberFormat($input, 4); + trace("getOutputParam result is $outString and expected $expected\n"); + + if ($outString !== $expected) { + echo "getOutputParam ($inout): Expected $expected but got $outString\n"; + var_dump($expected); + var_dump($outString); + } +} + +function testOutputParam($conn) +{ + // Create a table for testing output param + $tableName = 'sqlsrvMoneyFormats'; + $values = array(0.12345, 0.34567); + $query = "SELECT CONVERT(smallmoney, $values[0]) AS m1, + CONVERT(money, $values[1]) AS m2 + INTO $tableName"; + + $stmt = sqlsrv_query($conn, $query); + for ($i = 0; $i < 2; $i++) { + // Create the stored procedure first + $storedProcName = "spMoneyFormats" . $i; + $dataType = ($i == 0) ? 'smallmoney' : 'money'; + $procArgs = "@col $dataType OUTPUT"; + $column = 'm' . ($i + 1); + $procCode = "SELECT @col = $column FROM $tableName"; + createProc($conn, $storedProcName, $procArgs, $procCode); + + getOutputParam($conn, $storedProcName, $values[$i], $i, false); + getOutputParam($conn, $storedProcName, $values[$i], $i, true); + + dropProc($conn, $storedProcName); + } + + dropTable($conn, $tableName); +} + +function testMoneyTypes($conn) +{ + global $numDigits; // inherited from connection option + + // With money and smallmoney types, which are essentially decimal types + // As of today, ODBC driver does not support Always Encrypted feature with money / smallmoney + $values = array(); + $nColumns = 6; + for ($i = 0; $i < $nColumns; $i++) { + // First get a random number + $n = rand(0, 10); + $neg = ($n % 2 == 0) ? -1 : 1; + + // $n1 may or may not be negative + $max = 10; + $n1 = rand(0, $max) * $neg; + $n2 = rand(1, $max * 1000); + + $number = sprintf("%d.%d", $n1, $n2); + array_push($values, $number); + } + + $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])"; + + // Do not override the connection attributes + verifyMoneyValues($conn, $numDigits, $query, $values, false); + // Next, override statement attribute to set number of + // decimal places to 0 + verifyMoneyValues($conn, 0, $query, $values, true); + + // Set Formatting attribute to true then false + verifyMoneyFormatting($conn, $query, $values, true); + verifyMoneyFormatting($conn, $query, $values, false); +} + +set_time_limit(0); +sqlsrv_configure('WarningsReturnAsErrors', 1); + +$numDigits = 2; + +$conn = AE\connect(array('FormatDecimals' => true, 'DecimalPlaces' => $numDigits)); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +// First to test if leading zero is added +testMoneyTypes($conn); + +// Also test using regular floats +testFloatTypes($conn); + +// Test output params +testOutputParam($conn); + +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done From ae29f73aeeb519f6275d5347dd4728ee67b432cc Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 29 Nov 2018 08:54:45 -0800 Subject: [PATCH 071/249] Version update for 5.5.0-preview (#889) --- source/pdo_sqlsrv/config.m4 | 2 +- source/pdo_sqlsrv/config.w32 | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- source/pdo_sqlsrv/pdo_init.cpp | 2 +- source/pdo_sqlsrv/pdo_parser.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 +- source/pdo_sqlsrv/template.rc | 2 +- source/shared/FormattedPrint.cpp | 2 +- source/shared/FormattedPrint.h | 2 +- source/shared/StringFunctions.cpp | 2 +- source/shared/StringFunctions.h | 2 +- source/shared/core_conn.cpp | 2 +- source/shared/core_init.cpp | 2 +- source/shared/core_results.cpp | 2 +- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 2 +- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 2 +- source/shared/globalization.h | 2 +- source/shared/interlockedatomic.h | 2 +- source/shared/interlockedatomic_gcc.h | 2 +- source/shared/interlockedslist.h | 2 +- source/shared/localization.hpp | 2 +- source/shared/localizationimpl.cpp | 2 +- source/shared/msodbcsql.h | 2 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 4 ++-- source/shared/xplat.h | 2 +- source/shared/xplat_intsafe.h | 2 +- source/shared/xplat_winerror.h | 2 +- source/shared/xplat_winnls.h | 2 +- source/sqlsrv/config.m4 | 2 +- source/sqlsrv/config.w32 | 2 +- source/sqlsrv/conn.cpp | 2 +- source/sqlsrv/init.cpp | 2 +- source/sqlsrv/php_sqlsrv.h | 2 +- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 2 +- 42 files changed, 43 insertions(+), 43 deletions(-) diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index 3de4fefc5..82aa69e20 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.4 for PHP for SQL Server +dnl Microsoft Drivers 5.5 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index 8b8e4f29a..143627483 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 77e30f3d3..d1f76e495 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 000878fae..6b7b54e39 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index e6a70d4d7..984f983c5 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index d90fd50a7..3f7e72b8b 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 4120876b0..91efcb902 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index d9bb55e59..f31da7c70 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 435fa9a3b..7b74950aa 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index a5033db49..0e3beadd1 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -6,7 +6,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index f7933f97f..0c29e81f0 100644 --- a/source/shared/FormattedPrint.h +++ b/source/shared/FormattedPrint.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp index 354ebbf6c..6f494dc4c 100644 --- a/source/shared/StringFunctions.cpp +++ b/source/shared/StringFunctions.cpp @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.h b/source/shared/StringFunctions.h index 475cd84cf..bcd250b62 100644 --- a/source/shared/StringFunctions.h +++ b/source/shared/StringFunctions.h @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index a1fe06b0c..692ad9e8e 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index 274c10d26..ea604e791 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 67a805558..f4f00b371 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 363ccad19..400e9ea2f 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 1a4fb7a21..6e3e8326f 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index e4e9e485f..826fcf2ed 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 03675a086..bfb7f7779 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/globalization.h b/source/shared/globalization.h index 98619d61a..00beba14f 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index b8c04643c..953c25804 100644 --- a/source/shared/interlockedatomic.h +++ b/source/shared/interlockedatomic.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h index 6977ff229..ba5087b52 100644 --- a/source/shared/interlockedatomic_gcc.h +++ b/source/shared/interlockedatomic_gcc.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h index 6aa43fb00..76e2eda8e 100644 --- a/source/shared/interlockedslist.h +++ b/source/shared/interlockedslist.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, singly // linked list. // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index 79bd860e2..44a3e82d3 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 669462abc..74eccdde1 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -5,7 +5,7 @@ // Must be included in one c/cpp file per binary // A build error will occur if this inclusion policy is not followed // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index f4912b118..b440a95aa 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -20,7 +20,7 @@ // pecuniary loss) arising out of the use of or inability to use // this SDK, even if Microsoft has been advised of the possibility // of such damages. -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index 8d5b785c3..b000b640d 100644 --- a/source/shared/sal_def.h +++ b/source/shared/sal_def.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index 574dc51a0..d035e3666 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -1,7 +1,7 @@ //--------------------------------------------------------------------------------------------------------------------------------- // File: typedefs_for_linux.h // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/version.h b/source/shared/version.h index 0ad9dcd14..256fb51d9 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -4,7 +4,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -26,7 +26,7 @@ // Increase Minor with backward compatible new functionalities and API changes. // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 -#define SQLVERSION_MINOR 4 +#define SQLVERSION_MINOR 5 #define SQLVERSION_PATCH 0 #define SQLVERSION_BUILD 0 diff --git a/source/shared/xplat.h b/source/shared/xplat.h index 8e113a5cb..bb4888ea2 100644 --- a/source/shared/xplat.h +++ b/source/shared/xplat.h @@ -3,7 +3,7 @@ // // Contents: include for definition of Windows types for non-Windows platforms // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h index 03706dd36..571c62b2a 100644 --- a/source/shared/xplat_intsafe.h +++ b/source/shared/xplat_intsafe.h @@ -4,7 +4,7 @@ // Contents: This module defines helper functions to prevent // integer overflow bugs. // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h index a5bdf6415..88a97baa0 100644 --- a/source/shared/xplat_winerror.h +++ b/source/shared/xplat_winerror.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h index 274263107..7bdac15e1 100644 --- a/source/shared/xplat_winnls.h +++ b/source/shared/xplat_winnls.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index c0e4d3af8..5af9e7cd5 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.4 for PHP for SQL Server +dnl Microsoft Drivers 5.5 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index e4cd19d01..dc9f0b5fb 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 60b9f0d16..1de0c6b6f 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 45dfb0e58..9079ac752 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 3f77ecbcf..f60c6c849 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index e9ce41e8a..1f0357acc 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index d70a93a9d..88eefb55c 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index ff9e7c863..d4be03f5b 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.4 for PHP for SQL Server +// Microsoft Drivers 5.5 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License From cbdc01c0073c15a531b7e5d73ffe6444fe010623 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 29 Nov 2018 08:55:09 -0800 Subject: [PATCH 072/249] Fixed the error in the pdo decimal test (#890) --- .../pdo_sqlsrv/pdostatement_format_decimals.phpt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt b/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt index 07303c4b7..ebbb3e532 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_format_decimals.phpt @@ -152,18 +152,20 @@ function getOutputParam($conn, $storedProcName, $inputValue, $prec, $scale, $ino $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_DECIMAL_PLACES. + // The output param value should be unaffected by the attr + // PDO::SQLSRV_ATTR_DECIMAL_PLACES. Without ColumnEncryption, the + // output param is treated as a regular string (not a decimal), so + // no missing leading zeroes. // If ColumnEncryption is enabled, in which case the driver is able - // to derive the decimal type, leading zero will be added if missing + // to derive the decimal type, leading zero will be added if missing. if (isAEConnected()) { trace("\ngetOutputParam ($inout) with AE:\n"); $column = 'outputParamAE'; - compareNumbers($outString, $inputValue, $column, $scale, true); + compareNumbers($outString, $inputValue, $column, $scale); } else { trace("\ngetOutputParam ($inout) without AE:\n"); $column = 'outputParam'; - compareNumbers($outString, $inputValue, $column, $scale, false); + compareNumbers($outString, $inputValue, $column, $scale); } } From 9195f84f60833eee26dd8a2b54a9b3c53ae20c8e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 3 Dec 2018 12:08:21 -0800 Subject: [PATCH 073/249] Removed warning messages while compiling extensions (#892) --- source/shared/core_conn.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 692ad9e8e..8ec79e0c1 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -244,7 +244,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont #endif // !_WIN32 // time to free the access token, if not null - if (conn->azure_ad_access_token != NULL) { + if (conn->azure_ad_access_token) { memset(conn->azure_ad_access_token->data, 0, conn->azure_ad_access_token->dataSize); // clear the memory conn->azure_ad_access_token.reset(); } @@ -980,11 +980,11 @@ void load_azure_key_vault(_Inout_ sqlsrv_conn* conn TSRMLS_DC) throw core::CoreException(); } - CHECK_CUSTOM_ERROR(conn->ce_option.akv_id == NULL, conn, SQLSRV_ERROR_AKV_NAME_MISSING) { + CHECK_CUSTOM_ERROR(!conn->ce_option.akv_id, conn, SQLSRV_ERROR_AKV_NAME_MISSING) { throw core::CoreException(); } - CHECK_CUSTOM_ERROR(conn->ce_option.akv_secret == NULL, conn, SQLSRV_ERROR_AKV_SECRET_MISSING) { + CHECK_CUSTOM_ERROR(!conn->ce_option.akv_secret, conn, SQLSRV_ERROR_AKV_SECRET_MISSING) { throw core::CoreException(); } From 2f92a262dcf6e9fe1fa8094f4a10c0ca739b0704 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 4 Dec 2018 13:00:34 -0800 Subject: [PATCH 074/249] Improve performance of Unicode conversions (#891) --- source/shared/core_stmt.cpp | 280 +++++++------- source/shared/core_util.cpp | 18 +- source/shared/globalization.h | 2 + source/shared/localization.hpp | 10 +- source/shared/localizationimpl.cpp | 580 +++++++++++++++++++++++++++++ 5 files changed, 745 insertions(+), 145 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 6e3e8326f..3b263fe22 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -97,7 +97,7 @@ size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) cons bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* convert_param_z ); void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype - sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC); + sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC); // returns the ODBC C type constant that matches the PHP type and encoding given SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ); void default_sql_size_and_scale( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned int paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding, @@ -110,7 +110,7 @@ void field_cache_dtor( _Inout_ zval* data_z ); void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len); void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, - _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC ); + _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC ); stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] TSRMLS_DC ); bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ); // assure there is enough space for the output parameter string @@ -150,7 +150,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error current_stream( NULL, SQLSRV_ENCODING_DEFAULT ), current_stream_read( 0 ) { - ZVAL_UNDEF( &active_stream ); + ZVAL_UNDEF( &active_stream ); // initialize the input string parameters array (which holds zvals) core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); @@ -262,7 +262,7 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht, _In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver TSRMLS_DC ) { - sqlsrv_malloc_auto_ptr stmt; + sqlsrv_malloc_auto_ptr stmt; SQLHANDLE stmt_h = SQL_NULL_HANDLE; sqlsrv_stmt* return_stmt = NULL; @@ -280,26 +280,26 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm // process the options array given to core_sqlsrv_prepare. if( options_ht && zend_hash_num_elements( options_ht ) > 0 && valid_stmt_opts ) { - zend_ulong index = -1; - zend_string *key = NULL; - zval* value_z = NULL; + zend_ulong index = -1; + zend_string *key = NULL; + zval* value_z = NULL; - ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { + ZEND_HASH_FOREACH_KEY_VAL( options_ht, index, key, value_z ) { - int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; + int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; - // The driver layer should ensure a valid key. - DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); + // The driver layer should ensure a valid key. + DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); + const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); - // if the key didn't match, then return the error to the script. - // The driver layer should ensure that the key is valid. - DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); + // if the key didn't match, then return the error to the script. + // The driver layer should ensure that the key is valid. + DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); - // perform the actions the statement option needs done. - (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); - } ZEND_HASH_FOREACH_END(); + // perform the actions the statement option needs done. + (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); + } ZEND_HASH_FOREACH_END(); } return_stmt = stmt; @@ -495,7 +495,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ){ // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool, php_out_type); + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool, php_out_type); save_output_param_for_later( stmt, output_param TSRMLS_CC ); } } @@ -503,11 +503,11 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ case IS_DOUBLE: { buffer = ¶m_z->value; - buffer_len = sizeof( Z_DVAL_P( param_z )); + buffer_len = sizeof( Z_DVAL_P( param_z )); ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ){ // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned - sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool, php_out_type); + sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool, php_out_type); save_output_param_for_later( stmt, output_param TSRMLS_CC ); } } @@ -621,10 +621,10 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ zval buffer_z; zval format_z; zval params[1]; - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( &buffer_z ); - ZVAL_UNDEF( &format_z ); - ZVAL_UNDEF( params ); + ZVAL_UNDEF( &function_z ); + ZVAL_UNDEF( &buffer_z ); + ZVAL_UNDEF( &format_z ); + ZVAL_UNDEF( params ); bool valid_class_name_found = false; @@ -653,23 +653,23 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ // meaning there is too much information in the character string. If the user specifies the 'datetimeoffset' // sql type, it lacks the timezone. if( sql_type == SQL_SS_TIMESTAMPOFFSET ){ - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIMEOFFSET_FORMAT ), DateTime::DATETIMEOFFSET_FORMAT_LEN ); } else if( sql_type == SQL_TYPE_DATE ){ - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATE_FORMAT ), DateTime::DATE_FORMAT_LEN ); } else{ - core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); + core::sqlsrv_zval_stringl( &format_z, const_cast( DateTime::DATETIME_FORMAT ), DateTime::DATETIME_FORMAT_LEN ); } // call the DateTime::format member function to convert the object to a string that SQL Server understands - core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); + core::sqlsrv_zval_stringl( &function_z, "format", sizeof( "format" ) - 1 ); params[0] = format_z; // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the // DateTime object and $format_z is the format string. int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); - zend_string_release( Z_STR( format_z )); - zend_string_release( Z_STR( function_z )); + zend_string_release( Z_STR( format_z )); + zend_string_release( Z_STR( function_z )); CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ){ throw core::CoreException(); } @@ -696,7 +696,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ } core::SQLBindParameter( stmt, param_num + 1, direction, - c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); if ( stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP ) { if( decimal_digits == 3 ) @@ -885,14 +885,14 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL meta_data = new ( sqlsrv_malloc( sizeof( field_meta_data ))) field_meta_data(); field_name_temp = static_cast( sqlsrv_malloc( ( SS_MAXCOLNAMELEN + 1 ) * sizeof( SQLWCHAR ) )); SQLSRV_ENCODING encoding = ( (stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); - try{ + try{ core::SQLDescribeColW( stmt, colno + 1, field_name_temp, SS_MAXCOLNAMELEN + 1, &field_len_temp, &( meta_data->field_type ), & ( meta_data->field_size ), & ( meta_data->field_scale ), &( meta_data->field_is_nullable ) TSRMLS_CC ); - } - catch ( core::CoreException& e ) { - throw e; - } + } + catch ( core::CoreException& e ) { + throw e; + } bool converted = convert_string_from_utf16( encoding, field_name_temp, field_len_temp, ( char** ) &( meta_data->field_name ), field_name_len ); @@ -960,50 +960,50 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL // Nothing, excpetion thrown if an error occurs void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_phptype sqlsrv_php_type_in, _In_ bool prefer_string, - _Outref_result_bytebuffer_maybenull_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len, _In_ bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) + _Outref_result_bytebuffer_maybenull_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len, _In_ bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) { - try { - - // close the stream to release the resource - close_active_stream(stmt TSRMLS_CC); - - // if the field has been retrieved before, return the previous result - field_cache* cached = NULL; - if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { - // the field value is NULL - if( cached->value == NULL ) { - field_value = NULL; - *field_len = 0; - if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } - } - else { - - field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); - memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len ); - if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) { - // prevent the 'string not null terminated' warning - reinterpret_cast( field_value )[cached->len] = '\0'; - } - *field_len = cached->len; - if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } - } - return; - } - - sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; - - SQLLEN sql_field_type = 0; - SQLLEN sql_field_len = 0; - - // Make sure that the statement was executed and not just prepared. - CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { - throw core::CoreException(); - } - - // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they - // may also be retrieved. - if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { + try { + + // close the stream to release the resource + close_active_stream(stmt TSRMLS_CC); + + // if the field has been retrieved before, return the previous result + field_cache* cached = NULL; + if (NULL != ( cached = static_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), static_cast( field_index ))))) { + // the field value is NULL + if( cached->value == NULL ) { + field_value = NULL; + *field_len = 0; + if( sqlsrv_php_type_out ) { *sqlsrv_php_type_out = SQLSRV_PHPTYPE_NULL; } + } + else { + + field_value = sqlsrv_malloc( cached->len, sizeof( char ), 1 ); + memcpy_s( field_value, ( cached->len * sizeof( char )), cached->value, cached->len ); + if( cached->type.typeinfo.type == SQLSRV_PHPTYPE_STRING) { + // prevent the 'string not null terminated' warning + reinterpret_cast( field_value )[cached->len] = '\0'; + } + *field_len = cached->len; + if( sqlsrv_php_type_out) { *sqlsrv_php_type_out = static_cast(cached->type.typeinfo.type); } + } + return; + } + + sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; + + SQLLEN sql_field_type = 0; + SQLLEN sql_field_len = 0; + + // Make sure that the statement was executed and not just prepared. + CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { + throw core::CoreException(); + } + + // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they + // may also be retrieved. + if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { sqlsrv_phptype invalid; invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { @@ -1033,27 +1033,27 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast(sql_field_type), static_cast(sql_field_len), prefer_string); } - // Verify that we have an acceptable type to convert. - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { - throw core::CoreException(); - } + // Verify that we have an acceptable type to convert. + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { + throw core::CoreException(); + } - if( sqlsrv_php_type_out != NULL ) - *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); + if( sqlsrv_php_type_out != NULL ) + *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); - // Retrieve the data - core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + // Retrieve the data + core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); - // if the user wants us to cache the field, we'll do it - if( cache_field ) { - field_cache cache( field_value, *field_len, sqlsrv_php_type ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); - } - } + // if the user wants us to cache the field, we'll do it + if( cache_field ) { + field_cache cache( field_value, *field_len, sqlsrv_php_type ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); + } + } - catch( core::CoreException& e ) { - throw e; - } + catch( core::CoreException& e ) { + throw e; + } } // core_sqlsrv_has_any_result @@ -1347,14 +1347,14 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // read the data from the stream, send it via SQLPutData and track how much we've sent. else { char buffer[PHP_STREAM_BUFFER_SIZE + 1] = {'\0'}; - std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character + std::size_t buffer_size = sizeof( buffer ) - 3; // -3 to preserve enough space for a cut off UTF-8 character std::size_t read = php_stream_read( param_stream, buffer, buffer_size ); - if (read > UINT_MAX) - { - LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); - throw core::CoreException(); - } + if (read > UINT_MAX) + { + LOG(SEV_ERROR, "PHP stream: buffer length exceeded."); + throw core::CoreException(); + } stmt->current_stream_read += static_cast( read ); if (read == 0) { @@ -1374,8 +1374,8 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // expansion of 2x the UTF-8 size. SQLWCHAR wbuffer[PHP_STREAM_BUFFER_SIZE + 1] = {L'\0'}; int wbuffer_size = static_cast( sizeof( wbuffer ) / sizeof( SQLWCHAR )); - DWORD last_error_code = ERROR_SUCCESS; - // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate + DWORD last_error_code = ERROR_SUCCESS; + // buffer_size is the # of wchars. Since it set to stmt->param_buffer_size / 2, this is accurate #ifndef _WIN32 int wsize = SystemLocale::ToUtf16Strict( stmt->current_stream.encoding, buffer, static_cast(read), wbuffer, wbuffer_size, &last_error_code ); #else @@ -1383,7 +1383,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) last_error_code = GetLastError(); #endif // !_WIN32 - if( wsize == 0 && last_error_code == ERROR_NO_UNICODE_TRANSLATION ) { + if( wsize == 0 && last_error_code == ERROR_NO_UNICODE_TRANSLATION ) { // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more // in, then reattempt the conversion. If it fails the second time, then an error is returned. @@ -1873,23 +1873,26 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve std::size_t buffer_len = Z_STRLEN_P( input_param_z ); int wchar_size; - if (buffer_len > INT_MAX) - { - LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); - throw core::CoreException(); - } + if (buffer_len > INT_MAX) + { + LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded."); + throw core::CoreException(); + } // if the string is empty, then just return that the conversion succeeded as // MultiByteToWideChar will "fail" on an empty string. if( buffer_len == 0 ) { - core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); + core::sqlsrv_zval_stringl( converted_param_z, "", 0 ); return true; } - // if the parameter is an input parameter, calc the size of the necessary buffer from the length of the string #ifndef _WIN32 - wchar_size = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); + // Declare wchar_size to be the largest possible number of UTF-16 characters after + // conversion, to avoid the performance penalty of calling ToUtf16 + wchar_size = buffer_len; #else + // Calculate the size of the necessary buffer from the length of the string - + // no performance penalty because MultiByteToWidechar is highly optimised wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); #endif // !_WIN32 @@ -1901,17 +1904,18 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve wbuffer = reinterpret_cast( sqlsrv_malloc( (wchar_size + 1) * sizeof( SQLWCHAR ) )); // convert the utf-8 string to a wchar string in the new buffer #ifndef _WIN32 - int r = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast( buffer ), static_cast( buffer_len ), wbuffer, wchar_size ); + int rc = SystemLocale::ToUtf16Strict( CP_UTF8, reinterpret_cast( buffer ), static_cast( buffer_len ), wbuffer, wchar_size ); #else - int r = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), static_cast( buffer_len ), wbuffer, wchar_size ); + int rc = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), static_cast( buffer_len ), wbuffer, wchar_size ); #endif // !_WIN32 // if there was a problem converting the string, then free the memory and return false - if( r == 0 ) { + if( rc == 0 ) { return false; } + wchar_size = rc; // null terminate the string, set the size within the zval, and return success - wbuffer[wchar_size] = L'\0'; + wbuffer[ wchar_size ] = L'\0'; core::sqlsrv_zval_stringl( converted_param_z, reinterpret_cast( wbuffer.get() ), wchar_size * sizeof( SQLWCHAR ) ); sqlsrv_free(wbuffer); wbuffer.transferred(); @@ -1995,7 +1999,7 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) { sql_type = SQL_UNKNOWN_TYPE; - int php_type = Z_TYPE_P(param_z); + int php_type = Z_TYPE_P(param_z); switch( php_type ) { case IS_NULL: @@ -2135,7 +2139,7 @@ void field_cache_dtor( _Inout_ zval* data_z ) { sqlsrv_free( cache->value ); } - sqlsrv_free( cache ); + sqlsrv_free( cache ); } // To be called for formatting decimal / numeric fetched values from finalize_output_parameters() and/or get_field_as_string() @@ -2288,13 +2292,13 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) return; HashTable* params_ht = Z_ARRVAL( stmt->output_params ); - zend_ulong index = -1; - zend_string* key = NULL; - void* output_param_temp = NULL; + zend_ulong index = -1; + zend_string* key = NULL; + void* output_param_temp = NULL; - ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { - sqlsrv_output_param* output_param = static_cast( output_param_temp ); - zval* value_z = Z_REFVAL_P( output_param->param_z ); + ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { + sqlsrv_output_param* output_param = static_cast( output_param_temp ); + zval* value_z = Z_REFVAL_P( output_param->param_z ); switch( Z_TYPE_P( value_z )) { case IS_STRING: { @@ -2415,7 +2419,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); break; } - value_z = NULL; + value_z = NULL; } ZEND_HASH_FOREACH_END(); // empty the hash table since it's been processed @@ -2812,24 +2816,24 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* // allocate enough space to ALWAYS include the NULL regardless of the type being retrieved since // we set the last byte(s) to be NULL to avoid the debug build warning from the Zend engine about // not having a NULL terminator on a string. - zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); + zend_string* param_z_string = zend_string_realloc( Z_STR_P(param_z), expected_len, 0 ); // A zval string len doesn't include the null. This calculates the length it should be // regardless of whether the ODBC type contains the NULL or not. // null terminate the string to avoid a warning in debug PHP builds - ZSTR_VAL(param_z_string)[without_null_len] = '\0'; - ZVAL_NEW_STR(param_z, param_z_string); + ZSTR_VAL(param_z_string)[without_null_len] = '\0'; + ZVAL_NEW_STR(param_z, param_z_string); - // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the - // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY - buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; + // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the + // buffer when retrieving anything but SQLSRV_ENC_BINARY/SQL_C_BINARY + buffer_len = Z_STRLEN_P(param_z) - buffer_null_extra; - // Zend string length doesn't include the null terminator - ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; + // Zend string length doesn't include the null terminator + ZSTR_LEN(Z_STR_P(param_z)) -= elem_size; } - buffer = Z_STRVAL_P(param_z); + buffer = Z_STRVAL_P(param_z); // The StrLen_Ind_Ptr parameter of SQLBindParameter should contain the length of the data to send, which // may be less than the size of the buffer since the output may be more than the input. If it is greater, @@ -3013,7 +3017,7 @@ void sqlsrv_output_param_dtor( _Inout_ zval* data ) { sqlsrv_output_param *output_param = static_cast( Z_PTR_P( data )); zval_ptr_dtor( output_param->param_z ); // undo the reference to the string we will no longer hold - sqlsrv_free( output_param ); + sqlsrv_free( output_param ); } // called by Zend for each stream in the sqlsrv_stmt::param_streams hash table when it is cleaned/destroyed @@ -3021,7 +3025,7 @@ void sqlsrv_stream_dtor( _Inout_ zval* data ) { sqlsrv_stream* stream_encoding = static_cast( Z_PTR_P( data )); zval_ptr_dtor( stream_encoding->stream_z ); // undo the reference to the stream we will no longer hold - sqlsrv_free( stream_encoding ); + sqlsrv_free( stream_encoding ); } } diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index bfb7f7779..4aa2b37fd 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -127,10 +127,13 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_( flags = WC_ERR_INVALID_CHARS; } - // calculate the number of characters needed #ifndef _WIN32 - cchOutLen = SystemLocale::FromUtf16Strict( encoding, inString, cchInLen, NULL, 0 ); + // Allocate enough space to hold the largest possible number of bytes for UTF-8 conversion + // instead of calling FromUtf16, for performance reasons + cchOutLen = 4*cchInLen; #else + // Calculate the number of output bytes required - no performance hit here because + // WideCharToMultiByte is highly optimised cchOutLen = WideCharToMultiByte( encoding, flags, inString, cchInLen, NULL, 0, NULL, NULL ); @@ -142,9 +145,10 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_( // Create a buffer to fit the encoded string char* newString = reinterpret_cast( sqlsrv_malloc( cchOutLen + 1 /* NULL char*/ )); + memset(newString, '\0', cchOutLen+1); #ifndef _WIN32 - int rc = SystemLocale::FromUtf16( encoding, inString, cchInLen, newString, static_cast(cchOutLen)); + int rc = SystemLocale::FromUtf16Strict( encoding, inString, cchInLen, newString, static_cast(cchOutLen)); #else int rc = WideCharToMultiByte( encoding, flags, inString, cchInLen, newString, static_cast(cchOutLen), NULL, NULL ); #endif // !_WIN32 @@ -153,9 +157,13 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_( sqlsrv_free( newString ); return false; } + char* newString2 = reinterpret_cast( sqlsrv_malloc( rc + 1 /* NULL char*/ )); + memset(newString2, '\0', rc+1); + memcpy_s(newString2, rc, newString, rc); + sqlsrv_free( newString ); - *outString = newString; - newString[cchOutLen] = '\0'; // null terminate the encoded string + *outString = newString2; + cchOutLen = rc; return true; } diff --git a/source/shared/globalization.h b/source/shared/globalization.h index 00beba14f..f3545de2e 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -261,6 +261,8 @@ class EncodingConverter return 0; } } + //if a shift sequence is encountered, we need to advance output buffer + iconv_ret = iconv( m_pCvtCache->GetIConv(), NULL, NULL, &dest.m_pBytes, &dest.m_nBytesLeft ); } return cchDest - (dest.m_nBytesLeft / sizeof(DestType)); diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index 44a3e82d3..6328f1fbd 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -169,8 +169,14 @@ class SystemLocale static size_t FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, __out_ecount_opt(cchDest) char * dest, size_t cchDest, bool * pHasDataLoss = NULL, DWORD * pErrorCode = NULL); - - + // CP1252 to UTF16 conversion which does not involve iconv + static size_t CP1252ToUtf16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode ); + + // UTF8/16 conversion which does not involve iconv + static size_t Utf8To16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode ); + static size_t Utf8From16( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode ); + static size_t Utf8To16Strict( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode ); + static size_t Utf8From16Strict( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode ); // ----------------------------------------------------------------------- // Public Member Functions diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 74eccdde1..38ee64b7e 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -336,9 +336,300 @@ const SystemLocale & SystemLocale::Singleton() return s_Default; } + +// Convert CP1252 to UTF-16 without requiring iconv or taking a lock. +// This is trivial because, except for the 80-9F range, CP1252 bytes +// directly map to the corresponding UTF-16 codepoint. +size_t SystemLocale::CP1252ToUtf16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode ) +{ + const static WCHAR s_1252Map[] = + { + 0x20AC, 0x003F, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x003F, 0x017D, 0x003F, + 0x003F, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x003F, 0x017E, 0x0178 + }; + const unsigned char *usrc = reinterpret_cast(src); + const unsigned char *srcEnd = usrc + cchSrc; + const WCHAR *destEnd = dest + cchDest; + + while(usrc < srcEnd && dest < destEnd) + { + DWORD ucode = *usrc++; + *dest++ = (ucode <= 127 || ucode >= 160) ? ucode : s_1252Map[ucode - 128]; + } + pErrorCode && (*pErrorCode = (dest == destEnd && usrc != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS); + return cchDest - (destEnd - dest); +} + +// Convert UTF-8 to UTF-16 without requiring iconv or taking a lock. +// 0abcdefg -> 0abcdefg 00000000 +// 110abcde 10fghijk -> defghijk 00000abc +// 1110abcd 10efghij 10klmnop -> ijklmnop abcdefgh +// 11110abc 10defghi 10jklmno 10pqrstu -> cdfghijk 110110ab nopqrstu 11011lm +size_t SystemLocale::Utf8To16( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode ) +{ + const unsigned char *usrc = reinterpret_cast(src); + const unsigned char *srcEnd = usrc + cchSrc; + const WCHAR *destEnd = dest + cchDest; + DWORD dummyError; + if (!pErrorCode) + { + pErrorCode = &dummyError; + } + *pErrorCode = 0; + + while(usrc < srcEnd && dest < destEnd) + { + DWORD ucode = *usrc++; + if(ucode <= 127) // Most common case for ASCII + { + *dest++ = ucode; + } + else if(ucode < 0xC0) // unexpected trailing byte 10xxxxxx + { + goto Invalid; + } + else if(ucode < 0xE0) // 110abcde 10fghijk + { + if (usrc >= srcEnd || *usrc < 0x80 || *usrc > 0xBF || + (*dest = (ucode & 0x1F)<<6 | (*usrc++ & 0x3F)) < 0x80) + { + *dest = 0xFFFD; + } + dest++; + } + else if(ucode < 0xF0) // 1110abcd 10efghij 10klmnop + { + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c1 = *usrc; + if (c1 < 0x80 || c1 > 0xBF) + { + goto Invalid; + } + usrc++; + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c2 = *usrc; + if (c2 < 0x80 || c2 > 0xBF) + { + goto Invalid; + } + usrc++; + ucode = (ucode&15)<<12 | (c1&0x3F)<<6 | (c2&0x3F); + if (ucode < 0x800 || ucode >= 0xD800 && ucode <= 0xDFFF) + { + goto Invalid; + } + *dest++ = ucode; + } + else if(ucode < 0xF8) // 11110abc 10defghi 10jklmno 10pqrstu + { + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c1 = *usrc; + if (c1 < 0x80 || c1 > 0xBF) + { + goto Invalid; + } + usrc++; + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c2 = *usrc; + if (c2 < 0x80 || c2 > 0xBF) + { + goto Invalid; + } + usrc++; + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c3 = *usrc; + if (c3 < 0x80 || c3 > 0xBF) + { + goto Invalid; + } + usrc++; + ucode = (ucode&7)<<18 | (c1&0x3F)<<12 | (c2&0x3F)<<6 | (c3&0x3F); + + if (ucode < 0x10000 // overlong encoding + || ucode > 0x10FFFF // exceeds Unicode range + || ucode >= 0xD800 && ucode <= 0xDFFF) // surrogate pairs + { + goto Invalid; + } + if (dest >= destEnd - 1) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return cchDest - (destEnd - dest); + } + ucode -= 0x10000; + // Lead surrogate + *dest++ = 0xD800 + (ucode >> 10); + // Trail surrogate + *dest++ = 0xDC00 + (ucode & 0x3FF); + } + else // invalid + { + Invalid: + *dest++ = 0xFFFD; + } + } + if (!*pErrorCode) + { + *pErrorCode = (dest == destEnd && usrc != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS; + } + return cchDest - (destEnd - dest); +} + +size_t SystemLocale::Utf8To16Strict( const char *src, SSIZE_T cchSrc, WCHAR *dest, size_t cchDest, DWORD *pErrorCode ) +{ + const unsigned char *usrc = reinterpret_cast(src); + const unsigned char *srcEnd = usrc + cchSrc; + const WCHAR *destEnd = dest + cchDest; + DWORD dummyError; + if (!pErrorCode) + { + pErrorCode = &dummyError; + } + *pErrorCode = 0; + + while(usrc < srcEnd && dest < destEnd) + { + DWORD ucode = *usrc++; + if(ucode <= 127) // Most common case for ASCII + { + *dest++ = ucode; + } + else if(ucode < 0xC0) // unexpected trailing byte 10xxxxxx + { + goto Invalid; + } + else if(ucode < 0xE0) // 110abcde 10fghijk + { + if (usrc >= srcEnd || *usrc < 0x80 || *usrc > 0xBF || + (*dest = (ucode & 0x1F)<<6 | (*usrc++ & 0x3F)) < 0x80) + { + goto Invalid; + } + dest++; + } + else if(ucode < 0xF0) // 1110abcd 10efghij 10klmnop + { + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c1 = *usrc; + if (c1 < 0x80 || c1 > 0xBF) + { + goto Invalid; + } + usrc++; + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c2 = *usrc; + if (c2 < 0x80 || c2 > 0xBF) + { + goto Invalid; + } + usrc++; + ucode = (ucode&15)<<12 | (c1&0x3F)<<6 | (c2&0x3F); + if (ucode < 0x800 || ucode >= 0xD800 && ucode <= 0xDFFF) + { + goto Invalid; + } + *dest++ = ucode; + } + else if(ucode < 0xF8) // 11110abc 10defghi 10jklmno 10pqrstu + { + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c1 = *usrc; + if (c1 < 0x80 || c1 > 0xBF) + { + goto Invalid; + } + usrc++; + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c2 = *usrc; + if (c2 < 0x80 || c2 > 0xBF) + { + goto Invalid; + } + usrc++; + if (usrc >= srcEnd) + { + goto Invalid; + } + DWORD c3 = *usrc; + if (c3 < 0x80 || c3 > 0xBF) + { + goto Invalid; + } + usrc++; + ucode = (ucode&7)<<18 | (c1&0x3F)<<12 | (c2&0x3F)<<6 | (c3&0x3F); + + if (ucode < 0x10000 // overlong encoding + || ucode > 0x10FFFF // exceeds Unicode range + || ucode >= 0xD800 && ucode <= 0xDFFF) // surrogate pairs + { + goto Invalid; + } + if (dest >= destEnd - 1) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return cchDest - (destEnd - dest); + } + ucode -= 0x10000; + // Lead surrogate + *dest++ = 0xD800 + (ucode >> 10); + // Trail surrogate + *dest++ = 0xDC00 + (ucode & 0x3FF); + } + else // invalid + { + Invalid: + *pErrorCode = ERROR_NO_UNICODE_TRANSLATION; + return 0 ; + } + } + if (!*pErrorCode) + { + *pErrorCode = (dest == destEnd && usrc != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS; + } + return cchDest - (destEnd - dest); +} + size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) { srcCodePage = ExpandSpecialCP( srcCodePage ); + if ( dest ) + { + if ( srcCodePage == CP_UTF8 ) + { + return SystemLocale::Utf8To16( src, cchSrc < 0 ? (1+strlen(src)) : cchSrc, dest, cchDest, pErrorCode ); + } + else if ( srcCodePage == 1252 ) + { + return SystemLocale::CP1252ToUtf16( src, cchSrc < 0 ? (1+strlen(src)) : cchSrc, dest, cchDest, pErrorCode ); + } + } EncodingConverter cvt( CP_UTF16, srcCodePage ); if ( !cvt.Initialize() ) { @@ -354,6 +645,17 @@ size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T cchSrc, WCHAR * dest, size_t cchDest, DWORD * pErrorCode ) { srcCodePage = ExpandSpecialCP( srcCodePage ); + if ( dest ) + { + if ( srcCodePage == CP_UTF8 ) + { + return SystemLocale::Utf8To16Strict( src, cchSrc < 0 ? (1+strlen(src)) : cchSrc, dest, cchDest, pErrorCode ); + } + else if ( srcCodePage == 1252 ) + { + return SystemLocale::CP1252ToUtf16( src, cchSrc < 0 ? (1+strlen(src)) : cchSrc, dest, cchDest, pErrorCode ); + } + } EncodingConverter cvt( CP_UTF16, srcCodePage ); if ( !cvt.Initialize() ) { @@ -366,9 +668,282 @@ size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T return cvt.Convert( dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode ); } +size_t SystemLocale::Utf8From16( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode ) +{ + const WCHAR *srcEnd = src + cchSrc; + char *destEnd = dest + cchDest; + DWORD dummyError; + if (!pErrorCode) + { + pErrorCode = &dummyError; + } + *pErrorCode = 0; + + // null dest is a special mode to calculate the output size required. + if (!dest) + { + size_t cbOut = 0; + while (src < srcEnd) + { + DWORD wch = *src++; + if (wch < 128) // most common case. + { + cbOut++; + } + else if (wch < 0x800) // 127 to 2047: 2 bytes + { + cbOut += 2; + } + else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes + { + cbOut += 3; + } + else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes + { + if (src >= srcEnd) + { + cbOut += 3; // lone surrogate at end + } + else if (*src < 0xDC00 || *src > 0xDFFF) + { + cbOut += 3; // low surrogate not followed by high + } + else + { + cbOut += 4; + } + } + else // unexpected trail surrogate + { + cbOut += 3; + } + } + return cbOut; + } + while ( src < srcEnd && dest < destEnd ) + { + DWORD wch = *src++; + if (wch < 128) // most common case. + { + *dest++ = wch; + } + else if (wch < 0x800) // 127 to 2047: 2 bytes + { + if (destEnd - dest < 2) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + *dest++ = 0xC0 | (wch >> 6); + *dest++ = 0x80 | (wch & 0x3F); + } + else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes + { + if (destEnd - dest < 3) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + *dest++ = 0xE0 | (wch >> 12); + *dest++ = 0x80 | (wch >> 6)&0x3F; + *dest++ = 0x80 | (wch &0x3F); + } + else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes + { + if (src >= srcEnd) + { + *pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end + if (destEnd - dest < 3) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + *dest++ = 0xEF; + *dest++ = 0xBF; + *dest++ = 0xBD; + continue; + } + if (*src < 0xDC00 || *src > 0xDFFF) + { + // low surrogate not followed by high + if (destEnd - dest < 3) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + *dest++ = 0xEF; + *dest++ = 0xBF; + *dest++ = 0xBD; + continue; + } + wch = 0x10000 + ((wch - 0xD800)<<10) + *src++ - 0xDC00; + if (destEnd - dest < 4) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + *dest++ = 0xF0 | (wch >> 18); + *dest++ = 0x80 | (wch >>12)&0x3F; + *dest++ = 0x80 | (wch >> 6)&0x3F; + *dest++ = 0x80 | wch&0x3F; + } + else // unexpected trail surrogate + { + *pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end + if (destEnd - dest < 3) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + *dest++ = 0xEF; + *dest++ = 0xBF; + *dest++ = 0xBD; + } + } + if (!*pErrorCode) + { + *pErrorCode = (dest == destEnd && src != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS; + } + return *pErrorCode == ERROR_INSUFFICIENT_BUFFER ? 0 : cchDest - (destEnd - dest); +} + +size_t SystemLocale::Utf8From16Strict( const WCHAR *src, SSIZE_T cchSrc, char *dest, size_t cchDest, DWORD *pErrorCode ) +{ + const WCHAR *srcEnd = src + cchSrc; + char *destEnd = dest + cchDest; + DWORD dummyError; + if (!pErrorCode) + { + pErrorCode = &dummyError; + } + *pErrorCode = 0; + + // null dest is a special mode to calculate the output size required. + if (!dest) + { + size_t cbOut = 0; + while (src < srcEnd) + { + DWORD wch = *src++; + if (wch < 128) // most common case. + { + cbOut++; + } + else if (wch < 0x800) // 127 to 2047: 2 bytes + { + cbOut += 2; + } + else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes + { + cbOut += 3; + } + else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes + { + if (src >= srcEnd) + { + cbOut += 3; // lone surrogate at end + } + else if (*src < 0xDC00 || *src > 0xDFFF) + { + cbOut += 3; // low surrogate not followed by high + } + else + { + cbOut += 4; + } + } + else // unexpected trail surrogate + { + cbOut += 3; + } + } + return cbOut; + } + while ( src < srcEnd && dest < destEnd ) + { + DWORD wch = *src++; + if (wch < 128) // most common case. + { + *dest++ = wch; + } + else if (wch < 0x800) // 127 to 2047: 2 bytes + { + if (destEnd - dest < 2) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + *dest++ = 0xC0 | (wch >> 6); + *dest++ = 0x80 | (wch & 0x3F); + } + else if (wch < 0xD800 || wch > 0xDFFF) // 2048 to 55295 and 57344 to 65535: 3 bytes + { + if (destEnd - dest < 3) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + *dest++ = 0xE0 | (wch >> 12); + *dest++ = 0x80 | (wch >> 6)&0x3F; + *dest++ = 0x80 | (wch &0x3F); + } + else if (wch < 0xDC00) // 65536 to end of Unicode: 4 bytes + { + if (src >= srcEnd) + { + *pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end + if (destEnd - dest < 3) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + } + + return 0; + } + if (*src < 0xDC00 || *src > 0xDFFF) + { + *pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // low surrogate not followed by high + if (destEnd - dest < 3) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + } + return 0; + } + wch = 0x10000 + ((wch - 0xD800)<<10) + *src++ - 0xDC00; + if (destEnd - dest < 4) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + *dest++ = 0xF0 | (wch >> 18); + *dest++ = 0x80 | (wch >>12)&0x3F; + *dest++ = 0x80 | (wch >> 6)&0x3F; + *dest++ = 0x80 | wch&0x3F; + } + else // unexpected trail surrogate + { + *pErrorCode = ERROR_NO_UNICODE_TRANSLATION; // lone surrogate at end + if (destEnd - dest < 3) + { + *pErrorCode = ERROR_INSUFFICIENT_BUFFER; + } + return 0; + } + } + if (!*pErrorCode) + { + *pErrorCode = (dest == destEnd && src != srcEnd) ? ERROR_INSUFFICIENT_BUFFER : ERROR_SUCCESS; + } + return *pErrorCode == ERROR_INSUFFICIENT_BUFFER ? 0 : cchDest - (destEnd - dest); +} + size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode ) { destCodePage = ExpandSpecialCP( destCodePage ); + if ( destCodePage == CP_UTF8 ) + { + pHasDataLoss && (*pHasDataLoss = 0); + return SystemLocale::Utf8From16( src, cchSrc < 0 ? 1+mplat_wcslen(src) : cchSrc, dest, cchDest, pErrorCode ); + } EncodingConverter cvt( destCodePage, CP_UTF16 ); if ( !cvt.Initialize() ) { @@ -384,6 +959,11 @@ size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cc size_t SystemLocale::FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE_T cchSrc, char * dest, size_t cchDest, bool * pHasDataLoss, DWORD * pErrorCode) { destCodePage = ExpandSpecialCP(destCodePage); + if ( destCodePage == CP_UTF8 ) + { + pHasDataLoss && (*pHasDataLoss = 0); + return SystemLocale::Utf8From16Strict( src, cchSrc < 0 ? 1+mplat_wcslen(src) : cchSrc, dest, cchDest, pErrorCode ); + } EncodingConverter cvt(destCodePage, CP_UTF16); if (!cvt.Initialize()) { From ac8ea11126cfff98c8c24a712ede889fcc2a952e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 4 Dec 2018 15:43:57 -0800 Subject: [PATCH 075/249] Update sqlsrv_statement_format_money_scales.phpt Do not encrypt money / smallmoney fields in the test table --- .../sqlsrv/sqlsrv_statement_format_money_scales.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt index b27a3c480..f6b3e753f 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_format_money_scales.phpt @@ -48,7 +48,7 @@ function createTestTable($conn) $column = "col_$i"; $dataType = 'money'; - array_push($colMeta, new AE\ColumnMeta($dataType, $column)); + array_push($colMeta, new AE\ColumnMeta($dataType, $column, null, true, true)); } AE\createTable($conn, $tableName, $colMeta); @@ -164,4 +164,4 @@ sqlsrv_close($conn); echo "Done\n"; ?> --EXPECT-- -Done \ No newline at end of file +Done From 94c5a67403849a2d953b3fb020d879b6735276dc Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 6 Dec 2018 08:35:19 -0800 Subject: [PATCH 076/249] Change log 5.5.0-preview (#895) --- CHANGELOG.md | 155 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 94 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb2ee3f2..83a46a7fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,40 @@ # Change Log All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/) +The format is based on [Keep a Changelog](http://keepachangelog.com/) + +## 5.5.0-preview - 2018-12-07 +Updated PECL release packages. Here is the list of updates: + +### Added +- Added support for PHP 7.3.0 +- Added support for Linux Ubuntu 18.10 and mac OS Mojave +- Feature Request [#415](https://github.com/Microsoft/msphpsql/pull/886) - new options at connection and statement levels for both drivers for formatting decimal values in the fetched results + +### Fixed +- Pull Request [#854](https://github.com/Microsoft/msphpsql/pull/854) - Clear Azure Key Vault data after connection attributes are successfully set or when exception is thrown +- Pull Request [#855](https://github.com/Microsoft/msphpsql/pull/855) - Improved performance by saving meta data before fetching and skipping unnecessary conversions for numeric data +- Pull Request [#865](https://github.com/Microsoft/msphpsql/pull/865) - Corrected the way SQLPutData and SQLParamData are used when sending stream data to the server +- Pull Request [#878](https://github.com/Microsoft/msphpsql/pull/878) - Modified the config files to enable Spectre Mitigations for PHP 7.1 (see related Request [#836](https://github.com/Microsoft/msphpsql/pull/836)) +- Pull Request [#891](https://github.com/Microsoft/msphpsql/pull/891) - Improved performance of Unicode conversions +- Pull Request [#892](https://github.com/Microsoft/msphpsql/pull/892) - Removed warning messages while compiling extensions + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) +- With ColumnEncryption enabled, fetching varbinary(max), varchar(max) or nvarchar(max) may fail with [ODBC Driver 17.3 CTP](https://blogs.msdn.microsoft.com/sqlnativeclient/2018/09/24/odbc-driver-17-3-preview-for-sql-server-released/) ## 5.4.0-preview - 2018-09-24 Updated PECL release packages. Here is the list of updates: @@ -53,7 +86,7 @@ Updated PECL release packages. Here is the list of updates: - Issue [#699](https://github.com/Microsoft/msphpsql/issues/699) - Binding output parameters fails when the query in the stored procedure returns no data. The test case has been added to the test lab. - Issue [#705](https://github.com/Microsoft/msphpsql/issues/705) - Always Encrypted - Retrieving a negative decimal value (edge case) as output parameter causes truncation - Issue [#706](https://github.com/Microsoft/msphpsql/issues/706) - Always Encrypted - Cannot insert double with precision and scale (38, 38) -- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - Always Encrypted - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers +- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - Always Encrypted - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers - Issue [#735](https://github.com/Microsoft/msphpsql/issues/735) - Extended the buffer size for PDO::lastInsertId so that data types other than integers can be supported - Pull Request [#759](https://github.com/Microsoft/msphpsql/pull/759) - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY - Pull Request [#775](https://github.com/Microsoft/msphpsql/pull/775) - Fixed the truncation problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC @@ -77,7 +110,7 @@ Updated PECL release packages. Here is the list of updates: Updated PECL release packages. Here is the list of updates: ### Added -- Added support for Azure Key Vault for Always Encrypted for basic CRUD functionalities such that Always Encrypted feature is also available to Linux or macOS users +- Added support for Azure Key Vault for Always Encrypted for basic CRUD functionalities such that Always Encrypted feature is also available to Linux or macOS users - Added support for macOS High Sierra (requires [MS ODBC Driver 17+](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017)) ### Fixed @@ -86,7 +119,7 @@ Updated PECL release packages. Here is the list of updates: - Issue [#699](https://github.com/Microsoft/msphpsql/issues/699) - Binding output parameter failed when the query in the stored procedure returned no data. The test case has been added to the test lab. - Issue [#705](https://github.com/Microsoft/msphpsql/issues/705) - AE - Retrieving a negative decimal value (edge case) as output parameter causes truncation - Issue [#706](https://github.com/Microsoft/msphpsql/issues/706) - AE - Cannot insert double with precision and scale (38, 38) -- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - AE - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers +- Issue [#707](https://github.com/Microsoft/msphpsql/issues/707) - AE - Fetching decimals / numerics as output parameters bound to PDO::PARAM_BOOL or PDO::PARAM_INT returns floats, not integers - Issue [#735](https://github.com/Microsoft/msphpsql/issues/735) - Extended the buffer size for PDO lastInsertId such that data types other than integers can be supported - Pull Request [#759](https://github.com/Microsoft/msphpsql/pull/759) - Removed the limitation of binding a binary as inout param as PDO::PARAM_STR with SQLSRV_ENCODING_BINARY - Pull Request [#775](https://github.com/Microsoft/msphpsql/pull/775) - Fixed the problem for output params with SQL types specified as SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC @@ -227,7 +260,7 @@ Updated PECL release packages. Here is the list of updates: - PDO::quote with string containing ASCII NUL character (Issue [#538]( https://github.com/Microsoft/msphpsql/issues/538)) - Appropriate error message is returned when calling nextRowset() or sqlsrv_next_result() on an empty result set (issue [#507 ](https://github.com/Microsoft/msphpsql/issues/507)) - Decimal types with no decimals are correctly handled when AE is enabled (PR [#544](https://github.com/Microsoft/msphpsql/pull/544)) -- Search for installed ODBC drivers in Linux/macOS first before attempting to connect using the default ODBC driver +- Search for installed ODBC drivers in Linux/macOS first before attempting to connect using the default ODBC driver - BIGINT as an output param no longer results in value out of range exception when the returned value is larger than a maximum integer ([PR #567](https://github.com/Microsoft/msphpsql/pull/567)) ### Limitations @@ -282,7 +315,7 @@ Updated PECL release packages. Here is the list of updates: ### Changed - Implementation of PDO::lastInsertId($name) to return the last inserted sequence number if the sequence name is supplied to the function ([lastInsertId](https://github.com/Microsoft/msphpsql/wiki/Features#lastinsertid)) - + ### Removed - No longer support Ubuntu 15 - Supplying tablename into PDO::lastInsertId($name) no longer return the last inserted row ([lastInsertId](https://github.com/Microsoft/msphpsql/wiki/Features#lastinsertid)) @@ -299,45 +332,45 @@ Updated PECL release packages. Here is the list of updates: Production Ready release for SQLSRV and PDO_SQLSRV drivers on Sierra, El Capitan, Debian 8, Ubuntu 15, Ubuntu 16, CentOS 7, and Windows. Here is the changlog since the last Production Ready release. ### Added -- Added Unicode Column name support ([issue #138](https://github.com/Microsoft/msphpsql/issues/138)). -- Support for Always On Availability groups via Transparent Network IP Resolution ([TNIR](https://github.com/Microsoft/msphpsql/wiki/Features#TNIR)) +- Added Unicode Column name support ([issue #138](https://github.com/Microsoft/msphpsql/issues/138)). +- Support for Always On Availability groups via Transparent Network IP Resolution ([TNIR](https://github.com/Microsoft/msphpsql/wiki/Features#TNIR)) - Added support for sql_variant data type with limitation ([issue #51](https://github.com/Microsoft/msphpsql/issues/51) and [issue #127](https://github.com/Microsoft/msphpsql/issues/127)) -- Support drivers on Debian Jessie (tested on Debian 8.7) -- Connection Resiliency support in Windows -- Connection pooling support for Linux and macOS -- Support for Mac (El Capitan and above) +- Support drivers on Debian Jessie (tested on Debian 8.7) +- Connection Resiliency support in Windows +- Connection pooling support for Linux and macOS +- Support for Mac (El Capitan and above) - Azure Active Directory Authentication with ActiveDirectoryPassword and SqlPassword ### Fixed -- Fixed PECL installation errors when PHP was installed from source ([issue #213](https://github.com/Microsoft/msphpsql/issues/213)). -- Fixed the assertion error (Linux) when fetching data from a binary column using the binary encoding ([issue #226](https://github.com/Microsoft/msphpsql/issues/226)). -- Fixed issue output parameters bound to empty string ([issue #182](https://github.com/Microsoft/msphpsql/issues/182)). -- Fixed a memory leak in closing connection resources. -- Fixed load ordering issue in MacOS ([issue #417](https://github.com/Microsoft/msphpsql/issues/417)) -- Added a workaround for a bug in unixODBC 2.3.4 when connection pooling is enabled. -- Fixed the issue with driver loading order in macOS +- Fixed PECL installation errors when PHP was installed from source ([issue #213](https://github.com/Microsoft/msphpsql/issues/213)). +- Fixed the assertion error (Linux) when fetching data from a binary column using the binary encoding ([issue #226](https://github.com/Microsoft/msphpsql/issues/226)). +- Fixed issue output parameters bound to empty string ([issue #182](https://github.com/Microsoft/msphpsql/issues/182)). +- Fixed a memory leak in closing connection resources. +- Fixed load ordering issue in MacOS ([issue #417](https://github.com/Microsoft/msphpsql/issues/417)) +- Added a workaround for a bug in unixODBC 2.3.4 when connection pooling is enabled. +- Fixed the issue with driver loading order in macOS - Fixed null returned when an empty string is set to an output parameter ([issue #308](https://github.com/Microsoft/msphpsql/issues/308)). -- #### Fixed in SQLSRV - - Fixed sqlsrv client buffer size to only allow positive integers ([issue #228](https://github.com/Microsoft/msphpsql/issues/228)). - - Fixed sqlsrv_num_rows() when the client buffered result is null ([issue #330](https://github.com/Microsoft/msphpsql/issues/330)). - - Fixed issues with sqlsrv_has_rows() to prevent it from moving statement cursor ([issue #37](https://github.com/Microsoft/msphpsql/issues/37)). - - Fixed conversion warnings because of some const chars ([issue #332](https://github.com/Microsoft/msphpsql/issues/332)). - - Fixed debug abort error when building the driver in debug mode with PHP 7.1. - - Fixed string truncation when binding varchar(max), nvarchar(max), varbinary(max), and xml types ([issue #231](https://github.com/Microsoft/msphpsql/issues/231)). - - Fixed fatal error when fetching empty nvarchar ([issue #69](https://github.com/Microsoft/msphpsql/issues/69)). +- #### Fixed in SQLSRV + - Fixed sqlsrv client buffer size to only allow positive integers ([issue #228](https://github.com/Microsoft/msphpsql/issues/228)). + - Fixed sqlsrv_num_rows() when the client buffered result is null ([issue #330](https://github.com/Microsoft/msphpsql/issues/330)). + - Fixed issues with sqlsrv_has_rows() to prevent it from moving statement cursor ([issue #37](https://github.com/Microsoft/msphpsql/issues/37)). + - Fixed conversion warnings because of some const chars ([issue #332](https://github.com/Microsoft/msphpsql/issues/332)). + - Fixed debug abort error when building the driver in debug mode with PHP 7.1. + - Fixed string truncation when binding varchar(max), nvarchar(max), varbinary(max), and xml types ([issue #231](https://github.com/Microsoft/msphpsql/issues/231)). + - Fixed fatal error when fetching empty nvarchar ([issue #69](https://github.com/Microsoft/msphpsql/issues/69)). - Fixed fatal error when calling sqlsrv_fetch() with an out of bound offset for SQLSRV_SCROLL_ABSOLUTE ([issue #223](https://github.com/Microsoft/msphpsql/issues/223)). - #### Fixed in PDO_SQLSRV - - Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). - - Improved performance by implementing a cache to store column SQL types and display sizes ([issue #189](https://github.com/Microsoft/msphpsql/issues/189)). - - Fixed segmentation fault with PDOStatement::getColumnMeta() when the supplied column index is out of range ([issue #224](https://github.com/Microsoft/msphpsql/issues/224)). - - Fixed issue with the unsupported attribute PDO::ATTR_PERSISTENT in connection ([issue #65](https://github.com/Microsoft/msphpsql/issues/65)). - - Fixed the issue with executing DELETE operation on a non-existent value ([issue #336](https://github.com/Microsoft/msphpsql/issues/336)). - - Fixed incorrectly binding of unicode parameter when emulate prepare is on and the encoding is set at the statement level ([issue #92](https://github.com/Microsoft/msphpsql/issues/92)). - - Fixed binary column binding when emulate prepare is on ([issue #140](https://github.com/Microsoft/msphpsql/issues/140)). - - Fixed wrong value returned when fetching varbinary value on Linux ([issue #270](https://github.com/Microsoft/msphpsql/issues/270)). - - Fixed binary data not returned when the column is bound by name ([issue #35](https://github.com/Microsoft/msphpsql/issues/35)). + - Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). + - Improved performance by implementing a cache to store column SQL types and display sizes ([issue #189](https://github.com/Microsoft/msphpsql/issues/189)). + - Fixed segmentation fault with PDOStatement::getColumnMeta() when the supplied column index is out of range ([issue #224](https://github.com/Microsoft/msphpsql/issues/224)). + - Fixed issue with the unsupported attribute PDO::ATTR_PERSISTENT in connection ([issue #65](https://github.com/Microsoft/msphpsql/issues/65)). + - Fixed the issue with executing DELETE operation on a non-existent value ([issue #336](https://github.com/Microsoft/msphpsql/issues/336)). + - Fixed incorrectly binding of unicode parameter when emulate prepare is on and the encoding is set at the statement level ([issue #92](https://github.com/Microsoft/msphpsql/issues/92)). + - Fixed binary column binding when emulate prepare is on ([issue #140](https://github.com/Microsoft/msphpsql/issues/140)). + - Fixed wrong value returned when fetching varbinary value on Linux ([issue #270](https://github.com/Microsoft/msphpsql/issues/270)). + - Fixed binary data not returned when the column is bound by name ([issue #35](https://github.com/Microsoft/msphpsql/issues/35)). - Fixed exception thrown on closeCursor() when the statement has not been executed ([issue #267](https://github.com/Microsoft/msphpsql/issues/267)). - + ### Limitation - No support for inout / output params when using sql_variant type @@ -363,7 +396,7 @@ Here is the list of updates: - When pooling is enabled in Linux or MAC - unixODBC <= 2.3.4 (Linux and MAC) might not return proper diagnostics information, such as error messages, warnings and informative messages - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Connection-Pooling-on-Linux-and-Mac) - + ## Windows/Linux/MAC 4.2.0-preview - 2017-05-19 Here is the list of updates: @@ -386,7 +419,7 @@ Here is the list of updates: - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Connection-Pooling-on-Linux-and-Mac) ## Windows/Linux/MAC 4.1.9-preview - 2017-05-08 -- Updated documentation for Readme regarding instructions for Linux and MAC +- Updated documentation for Readme regarding instructions for Linux and MAC - Updated PECL release packages. Here is the list of updates: ### Added - Azure Active Directory Authentication with ActiveDirectoryPassword and SqlPassword @@ -405,11 +438,11 @@ Here is the list of updates: - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Connection-Pooling-on-Linux-and-Mac) ## Windows/Linux/MAC 4.1.8-preview - 2017-04-10 -Updated documentation for Readme regarding instructions for Linux and MAC +Updated documentation for Readme regarding instructions for Linux and MAC Updated PECL release packages. Here is the list of updates: ### Added -- [Connection Resiliency](https://github.com/Microsoft/msphpsql/wiki/Connection-Resiliency) now supported in Windows -- [Connection pooling](https://github.com/Microsoft/msphpsql/wiki/Connection-Pooling-on-Linux-and-Mac) now works in MAC +- [Connection Resiliency](https://github.com/Microsoft/msphpsql/wiki/Connection-Resiliency) now supported in Windows +- [Connection pooling](https://github.com/Microsoft/msphpsql/wiki/Connection-Pooling-on-Linux-and-Mac) now works in MAC ### Fixed #### SQLSRV and PDO_SQLSRV @@ -485,27 +518,27 @@ Updated Windows drivers (4.1.5) compiled with PHP 7.0.14 and 7.1 are available. ###Fixed - Fixed issue output parameters bound to empty string ([issue #182](https://github.com/Microsoft/msphpsql/issues/182)). -- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). +- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). ### Changed - Code structure is updated to facilitate the development; shared codes between both drivers are moved to "shared" folder to avoid code duplication issues in development. To build the driver from source: - - if you are building the driver from source using PHP source, copy the "shared" folder as a subfolder to both the sqlsrv and pdo_sqlsrv folders. + - if you are building the driver from source using PHP source, copy the "shared" folder as a subfolder to both the sqlsrv and pdo_sqlsrv folders. ## Linux 4.0.8 - 2016-12-19 Production release of Linux drivers is available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. Here is the list of updates: ### Added - Added `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` attribute support in PDO_SQLSRV driver.`SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag handles numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, - `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` - If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. + `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` + If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. Note for exceptions: - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. - Added Unicode Column name support([issue #138](https://github.com/Microsoft/msphpsql/issues/138)). ###Fixed -- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). +- Fixed issue with SQLSRV_ATTR_FETCHES_NUMERIC_TYPE when column return type is set on statement ([issue #173](https://github.com/Microsoft/msphpsql/issues/173)). - Fixed precision issues when double data type returned as strings using buffered queries in PDO_SQLSRV driver. - Fixed issue with buffered cursor in PDO_SQLSRV driver when CharacterSet is UTF-8 ([issue #192](https://github.com/Microsoft/msphpsql/issues/192)). - Fixed segmentation fault in error cases when error message is returned with emulate prepare attribute is set to true in PDO_SQLSRV driver. @@ -514,7 +547,7 @@ Production release of Linux drivers is available for Ubuntu 15.04, Ubuntu 16.04, ## Linux 4.0.7 - 2016-11-23 -Linux drivers compiled with PHP 7.0.13 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. +Linux drivers compiled with PHP 7.0.13 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. ### Added - Ported buffered cursor to Linux. @@ -522,7 +555,7 @@ Linux drivers compiled with PHP 7.0.13 are available for Ubuntu 15.04, Ubuntu 16 ### Changed - Code structure is updated to facilitate the development; shared codes between both drivers are moved to "shared" folder to avoid code duplication issues in development. To build the driver from source, use "packagize" script as follows: - if you are using the phpize, clone or download the “sourceâ€, run the script within the “source†directory and then run phpize. - - if you are building the driver from source using PHP source, give the path to the PHP source to the script. + - if you are building the driver from source using PHP source, give the path to the PHP source to the script. ### Fixed - Fixed string truncation error when inserting long strings. @@ -532,7 +565,7 @@ Linux drivers compiled with PHP 7.0.13 are available for Ubuntu 15.04, Ubuntu 16 - Fixed issues with binding input text, ntext, and image parameters. ## Linux 4.0.6 - 2016-10-25 -Linux drivers compiled with PHP 7.0.12 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. +Linux drivers compiled with PHP 7.0.12 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. ### Changed - Drivers versioning has been redesigned as Major#.Minor#.Release#.Build#. Build number is specific to binaries and it doesn't match with the number on the source. @@ -540,7 +573,7 @@ Linux drivers compiled with PHP 7.0.12 are available for Ubuntu 15.04, Ubuntu 16 ### Fixed - Fixed the issue with duplicate warning messages in PDO_SQLSRV drivers when error mode is set to PDO::ERRMODE_WARNING. - - Fixed the issue with invalid UTF-8 strings, those are detected before executing any queries and proper error message is returned. + - Fixed the issue with invalid UTF-8 strings, those are detected before executing any queries and proper error message is returned. - Fixed segmentation fault in sqlsrv_fetch_object and sqlsrv_fetch_array function. ## Windows 4.1.4 - 2016-10-25 @@ -551,9 +584,9 @@ Windows drivers compiled with PHP 7.0.12 and 7.1 are available. Here is the lis ### Fixed - Fixed the issue with duplicate warning messages in PDO_SQLSRV drivers when error mode is set to PDO::ERRMODE_WARNING. - + ## Linux 4.0.5 - 2016-10-04 -Linux drivers compiled with PHP 7.0.11 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. +Linux drivers compiled with PHP 7.0.11 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. ### Fixed - Fixed segmentation fault when calling PDOStatement::getColumnMeta on RedHat 7.2. @@ -567,7 +600,7 @@ Updated Windows drivers (4.1.3) compiled with PHP 7.0.11 and 7.1.0RC3 are avail - Fixed [issue #139](https://github.com/Microsoft/msphpsql/issues/139) : sqlsrv_fetch_object calls custom class constructor in static context and outputs an error. ##Linux 4.0.4 - 2016-09-09 -Linux drivers compiled with PHP 7.0.10 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. +Linux drivers compiled with PHP 7.0.10 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. ### Added - Added Support for EMULATE_PREPARE feature. @@ -613,10 +646,10 @@ Updated Windows drivers (4.1.2) compiled with PHP 7.0.10 are available. Here is ### Fixed - Fixed [issue #119](https://github.com/Microsoft/msphpsql/issues/119) (modifying class name in sqlsrv_fetch_object). - - + + ## Linux 4.0.3 - 2016-08-23 -Linux drivers compiled with PHP 7.0.9 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. +Linux drivers compiled with PHP 7.0.9 are available for Ubuntu 15.04, Ubuntu 16.04, and RedHat 7.2. ### Fixed - Fixed data corruption in binding integer parameters. @@ -646,17 +679,17 @@ Updated Windows drivers(4.1.1) compiled with PHP 7.0.9 are available and include ### Fixed - `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` connection attribute flag is added to PDO_SQLSRV driver to handle numeric fetches from columns with numeric Sql types (only bit, integer, smallint, tinyint, float and real). This flag can be turned on by setting its value in `PDO::setAttribute` to `true`, For example, - `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` - If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. + `$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,true);` + If `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is set to `true` the results from an integer column will be represented as an `int`, likewise, Sql types float and real will be represented as `float`. Note for exceptions: - When connection option flag `ATTR_STRINGIFY_FETCHES` is on, even when `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is on, the return value will still be string. - When the returned PDO type in bind column is `PDO_PARAM_INT`, the return value from a integer column will be int even if `SQLSRV_ATTR_FETCHES_NUMERIC_TYPE` is off. - - Fixed float truncation when using buffered query. + - Fixed float truncation when using buffered query. - Fixed handling of Unicode strings and binary when emulate prepare is on in `PDOStatement::bindParam`. To bind a unicode string, `PDO::SQLSRV_ENCODING_UTF8` should be set using `$driverOption`, and to bind a string to column of Sql type binary, `PDO::SQLSRV_ENCODING_BINARY` should be set. - Fixed string truncation in bind output parameters when the size is not set and the length of initialized variable is less than the output. - Fixed bind string parameters as bidirectional parameters (`PDO::PARAM_INPUT_OUTPUT `) in PDO_SQLSRV driver. Note for output or bidirectional parameters, `PDOStatement::closeCursor` should be called to get the output value. - + ## Linux 4.0.1 - 2016-07-09 ### Added From fb55bb7e3748383aad141b16cad300e9ac3643c5 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 6 Dec 2018 15:23:19 -0800 Subject: [PATCH 077/249] updated docs for php 7.3 --- Linux-mac-install.md | 74 ++++++++++++++++++++++++-------------------- README.md | 4 +-- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index 4d4eb3ceb..f12b1de9d 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,39 +1,35 @@ # Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server -The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft drivers for PHP for Microsoft SQL Server on Ubuntu 16.04, 17.10 and 18.04, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12 and 10.13. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for Microsoft SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for Microsoft SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver)). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup). +The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12, 10.13, and 10.14. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver.md). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver.md##loading-the-driver-at-php-startup). These instructions install PHP 7.2 by default -- see the notes at the beginning of each section to install PHP 7.0 or 7.1. ## Contents of this page: -- [Installing the drivers on Ubuntu 16.04, 18.04 and 18.10](#installing-the-drivers-on-ubuntu-1604-1804-and-1810) +- [Installing the drivers on Ubuntu 16.04, 18.04, and 18.10](#installing-the-drivers-on-ubuntu-1604-1804-and-1810) - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) - [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12) -- [Installing the drivers on macOS El Capitan, Sierra, High Sierra and Mojave](#installing-the-drivers-on-macos-el-capitan-sierra-high-sierra-and-mojave) +- [Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-el-capitan-sierra-high-sierra-and-mojave) -## Installing the drivers on Ubuntu 16.04, 18.04 and 18.10 +## Installing the drivers on Ubuntu 16.04, 18.04, and 18.10 > [!NOTE] -> To install PHP 7.0 or 7.1, replace 7.2 with 7.0 or 7.1 in the following commands. -> For Ubuntu 18.04, the step to add the ondrej repository is not required unless -> PHP 7.0 or 7.1 is needed. However, installing PHP 7.0 or 7.1 in Ubuntu 18.04 or 18.10 -> may not work as packages from the ondrej repository come with dependencies that may -> conflict with a base Ubuntu 18.04 or 18.10 install. +> To install PHP 7.0, 7.1, or 7.3, replace `7.2` with `7.0`, `7.1`, or `7.3` in the following commands. ### Step 1. Install PHP ``` sudo su -# The following step is required for Ubuntu 16.04 only -add-apt-repository ppa:ondrej/php -y +add-apt-repository ppa:ondrej/php -y apt-get update apt-get install php7.2 php7.2-dev php7.2-xml -y --allow-unauthenticated ``` ### Step 2. Install prerequisites -Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). - -For Ubuntu 18.10, follow the above steps for Ubuntu 18.04 except replace `18.04` by `18.10` and download ODBC 17.3 preview [here](https://www.microsoft.com/en-us/download/details.aspx?id=57341). +Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). ### Step 3. Install the PHP drivers for Microsoft SQL Server + +> [!NOTE] +> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -62,22 +58,23 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Red Hat 7 > [!NOTE] -> To install PHP 7.0 or 7.1, replace remi-php72 with remi-php70 or remi-php71 respectively in the following commands. +> To install PHP 7.0, 7.1, or 7.3, replace `remi-php72` with `remi-php70`, `remi-php71`, or `remi-php73` respectively in the following commands. ### Step 1. Install PHP ``` sudo su wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm +wget https://rpms.remirepo.net/enterprise/remi-release-7.rpm rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm subscription-manager repos --enable=rhel-7-server-optional-rpms +yum install yum-utils yum-config-manager --enable remi-php72 yum update yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc ``` ### Step 2. Install prerequisites -Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). +Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). Compiling the PHP drivers with PECL with PHP 7.2 requires a more recent GCC than the default: ``` @@ -86,6 +83,9 @@ sudo yum install devtoolset-7 scl enable devtoolset-7 bash ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server + +> [!NOTE] +> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -106,7 +106,7 @@ sudo make install ``` You can alternatively download the prebuilt binaries from the [Github project page](https://github.com/Microsoft/msphpsql/releases), or install from the Remi repo: ``` -sudo yum install php-sqlsrv php-pdo_sqlsrv +sudo yum install php-sqlsrv ``` ### Step 4. Install Apache ``` @@ -125,7 +125,7 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Debian 8 and 9 > [!NOTE] -> To install PHP 7.0 or 7.1, replace 7.2 in the following commands with 7.0 or 7.1. +> To install PHP 7.0, 7.1, or 7.3, replace `7.2` with `7.0`, `7.1`, or `7.3` in the following commands. ### Step 1. Install PHP ``` @@ -137,7 +137,7 @@ apt-get update apt-get install -y php7.2 php7.2-dev php7.2-xml ``` ### Step 2. Install prerequisites -Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). +Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). You may also need to generate the correct locale to get PHP output to display correctly in a browser. For example, for the en_US UTF-8 locale, run the following commands: ``` @@ -147,6 +147,9 @@ locale-gen ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server + +> [!NOTE] +> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -174,21 +177,24 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Suse 12 > [!NOTE] -> To install PHP 7.0, skip the command below adding the repository - 7.0 is the default PHP on suse 12. -> To install PHP 7.1, replace the repository URL below with the following URL: - `http://download.opensuse.org/repositories/devel:/languages:/php:/php71/SLE_12/devel:languages:php:php71.repo` +> To install PHP 7.0 or 7.1, replace the repository URL below with one of the following URLs: +`https://download.opensuse.org/repositories/devel:languages:php:php70/SLE_12_SP3/devel:languages:php:php70.repo` +`https://download.opensuse.org/repositories/devel:languages:php:php71/SLE_12_SP3/devel:languages:php:php71.repo` ### Step 1. Install PHP ``` sudo su -zypper -n ar -f http://download.opensuse.org/repositories/devel:languages:php/SLE_12/devel:languages:php.repo +zypper -n ar -f https://download.opensuse.org/repositories/devel:languages:php/SLE_12_SP3/devel:languages:php.repo zypper --gpg-auto-import-keys refresh zypper -n install php7 php7-pear php7-devel ``` ### Step 2. Install prerequisites -Install the ODBC driver for Suse 12 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). +Install the ODBC driver for Suse 12 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). ### Step 3. Install the PHP drivers for Microsoft SQL Server + +> [!NOTE] +> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -212,7 +218,7 @@ sudo systemctl restart apache2 ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on macOS El Capitan, Sierra, High Sierra and Mojave +## Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave If you do not already have it, install brew as follows: ``` @@ -220,7 +226,7 @@ If you do not already have it, install brew as follows: ``` > [!NOTE] -> To install PHP 7.0 or 7.1, replace php@7.2 with php@7.0 or php@7.1 respectively in the following commands. +> To install PHP 7.0, 7.1, or 7.3, replace `php@7.2` with `php@7.0`, `php@7.1`, or `php@7.3` respectively in the following commands. ### Step 1. Install PHP @@ -229,15 +235,13 @@ brew tap brew tap homebrew/core brew install php@7.2 ``` - PHP should now be in your path -- run `php -v` to verify that you are running the correct version of PHP. If PHP is not in your path or it is not the correct version, run the following: - ``` brew link --force --overwrite php@7.2 ``` ### Step 2. Install prerequisites -Install the ODBC driver for macOS by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). +Install the ODBC driver for macOS by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). In addition, you may need to install the GNU make tools: ``` @@ -245,6 +249,9 @@ brew install autoconf automake libtool ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server + +> [!NOTE] +> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -253,10 +260,10 @@ sudo pecl install pdo_sqlsrv ``` brew install apache2 ``` -To find the Apache configuration file for your Apache installation, run +To find the Apache configuration file for your Apache installation, run ``` apachectl -V | grep SERVER_CONFIG_FILE -``` +``` and substitute the path for `httpd.conf` in the following commands: ``` echo "LoadModule php7_module /usr/local/opt/php@7.2/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf @@ -320,4 +327,5 @@ function formatErrors($errors) } ?> ``` -Point your browser to http://localhost/testsql.php (http://localhost:8080/testsql.php on macOS). You should now be able to connect to your SQL Server/Azure SQL database. +Point your browser to https://localhost/testsql.php (https://localhost:8080/testsql.php on macOS). You should now be able to connect to your SQL Server/Azure SQL database. + diff --git a/README.md b/README.md index 5d1422ccb..bd2b2349b 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ Thank you for taking the time to participate in our last survey. You can continu For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs. On the client machine: -- PHP 7.1.x, or 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows) -- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-2017) +- PHP 7.1.x, 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows), or 7.3.x +- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server) - If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux. From 21241765bdd2c5dd33b5d9335570112b51825917 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 6 Dec 2018 16:06:16 -0800 Subject: [PATCH 078/249] Fixed broken links --- Linux-mac-install.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index f12b1de9d..cee028b53 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,5 +1,5 @@ # Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server -The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12, 10.13, and 10.14. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver.md). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver.md##loading-the-driver-at-php-startup). +The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12, 10.13, and 10.14. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver.md##loading-the-driver-at-php-startup). These instructions install PHP 7.2 by default -- see the notes at the beginning of each section to install PHP 7.0 or 7.1. @@ -24,7 +24,7 @@ apt-get update apt-get install php7.2 php7.2-dev php7.2-xml -y --allow-unauthenticated ``` ### Step 2. Install prerequisites -Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). +Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). ### Step 3. Install the PHP drivers for Microsoft SQL Server @@ -74,7 +74,7 @@ yum update yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc ``` ### Step 2. Install prerequisites -Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). +Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). Compiling the PHP drivers with PECL with PHP 7.2 requires a more recent GCC than the default: ``` @@ -137,7 +137,7 @@ apt-get update apt-get install -y php7.2 php7.2-dev php7.2-xml ``` ### Step 2. Install prerequisites -Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). +Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). You may also need to generate the correct locale to get PHP output to display correctly in a browser. For example, for the en_US UTF-8 locale, run the following commands: ``` @@ -189,7 +189,7 @@ zypper --gpg-auto-import-keys refresh zypper -n install php7 php7-pear php7-devel ``` ### Step 2. Install prerequisites -Install the ODBC driver for Suse 12 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). +Install the ODBC driver for Suse 12 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). ### Step 3. Install the PHP drivers for Microsoft SQL Server @@ -241,7 +241,7 @@ brew link --force --overwrite php@7.2 ``` ### Step 2. Install prerequisites -Install the ODBC driver for macOS by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server.md). +Install the ODBC driver for macOS by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). In addition, you may need to install the GNU make tools: ``` From a8b561511ff929f365b9ae1159c3dbe58add3c3c Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 6 Dec 2018 16:28:17 -0800 Subject: [PATCH 079/249] Added back Ubuntu 18.10 ODBC instruction --- Linux-mac-install.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index cee028b53..c83bec3fb 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -26,6 +26,8 @@ apt-get install php7.2 php7.2-dev php7.2-xml -y --allow-unauthenticated ### Step 2. Install prerequisites Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). +For Ubuntu 18.10, follow the above steps for Ubuntu 18.04 except replace `18.04` by `18.10` and download ODBC 17.3 preview [here](https://www.microsoft.com/download/details.aspx?id=57341). + ### Step 3. Install the PHP drivers for Microsoft SQL Server > [!NOTE] From e30ebfabe8ae99b39be4ddde32fc20f1cd112b05 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 10 Dec 2018 16:11:39 -0800 Subject: [PATCH 080/249] Drop tests related to fake passwords (#905) --- test/functional/pdo_sqlsrv/pdo_passwords.phpt | 57 ----------------- test/functional/setup/create_logins_azure.sql | 18 ------ test/functional/setup/create_users_azure.sql | 21 ------- test/functional/setup/setup_dbs.py | 15 ----- test/functional/setup/test_password.sql | 31 ---------- .../sqlsrv/test_non_alpha_password.phpt | 61 ------------------- 6 files changed, 203 deletions(-) delete mode 100644 test/functional/pdo_sqlsrv/pdo_passwords.phpt delete mode 100644 test/functional/setup/create_logins_azure.sql delete mode 100644 test/functional/setup/create_users_azure.sql delete mode 100644 test/functional/setup/test_password.sql delete mode 100644 test/functional/sqlsrv/test_non_alpha_password.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_passwords.phpt b/test/functional/pdo_sqlsrv/pdo_passwords.phpt deleted file mode 100644 index c672fc95e..000000000 --- a/test/functional/pdo_sqlsrv/pdo_passwords.phpt +++ /dev/null @@ -1,57 +0,0 @@ ---TEST-- -Test password with non alphanumeric characters ---DESCRIPTION-- -The first three cases should have no problem connecting. Only the last case fails because the -right curly brace should be escaped with another right brace. -In Azure for this test to pass do not specify any particular database when connecting ---SKIPIF-- - ---FILE-- -getMessage() . "\n"); -} -try { - // Test 2 - $conn = new PDO($dsn, "test_password2", "!}} ;4triou"); - if (!$conn) { - echo "Test 2: Should have connected."; - } - unset($conn); -} catch (PDOException $e) { - print_r($e->getMessage() . "\n"); -} -try { - // Test 3 - $conn = new PDO($dsn, "test_password3", "! ;4triou}}"); - if (!$conn) { - echo "Test 3: Should have connected."; - } - unset($conn); -} catch (PDOException $e) { - print_r($e->getMessage() . "\n"); -} -// Test invalid password. -try { - // Test 4 - $conn = new PDO($dsn, "test_password3", "! ;4triou}"); -} catch (PDOException $e) { - print_r($e->getMessage()); - exit; -} - -?> - ---EXPECTREGEX-- -SQLSTATE\[IMSSP\]: An unescaped right brace \(}\) was found in either the user name or password\. All right braces must be escaped with another right brace \(}}\)\. diff --git a/test/functional/setup/create_logins_azure.sql b/test/functional/setup/create_logins_azure.sql deleted file mode 100644 index ecc5530a3..000000000 --- a/test/functional/setup/create_logins_azure.sql +++ /dev/null @@ -1,18 +0,0 @@ ---for this script to work in Azure, use sqlcmd to connect to master database -IF NOT EXISTS (SELECT name FROM sys.sql_logins WHERE name = 'test_password') -BEGIN - CREATE LOGIN test_password WITH PASSWORD='! ;4triou'; -END -GO - -IF NOT EXISTS (SELECT name FROM sys.sql_logins WHERE name = 'test_password2') -BEGIN - CREATE LOGIN test_password2 WITH PASSWORD='!} ;4triou'; -END -GO - -IF NOT EXISTS (SELECT name FROM sys.sql_logins WHERE name = 'test_password3') -BEGIN - CREATE LOGIN test_password3 WITH PASSWORD='! ;4triou}'; -END -GO diff --git a/test/functional/setup/create_users_azure.sql b/test/functional/setup/create_users_azure.sql deleted file mode 100644 index af5770639..000000000 --- a/test/functional/setup/create_users_azure.sql +++ /dev/null @@ -1,21 +0,0 @@ ---for this script to work in Azure, create_logins_azure.sql must have been invoked beforehand ---assuming these logins exist, use sqlcmd to connect to a test database ---these users will be granted access to that database -IF NOT EXISTS (SELECT name FROM sysusers WHERE name = 'test_password') -BEGIN - CREATE USER test_password FROM LOGIN test_password; -END -GO - -IF NOT EXISTS (SELECT name FROM sysusers WHERE name = 'test_password2') -BEGIN - CREATE USER test_password2 FROM LOGIN test_password2; -END -GO - -IF NOT EXISTS (SELECT name FROM sysusers WHERE name = 'test_password3') -BEGIN - CREATE USER test_password3 FROM LOGIN test_password3; -END -GO - diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 32b821ef1..8b0d6be5b 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -10,19 +10,6 @@ from subprocess import Popen, PIPE from exec_sql_scripts import * -def createLoginUsers(conn_options, dbname, azure): - if (azure.lower() == 'yes'): - # can only create logins in the master database - createLoginUsersAzure('create_logins_azure.sql', conn_options, 'master') - # create users to use those logins to access the test database (dbname) - createLoginUsersAzure('create_users_azure.sql', conn_options, dbname) - else: - executeSQLscript('test_password.sql', conn_options, dbname) - -def createLoginUsersAzure(sqlfile, conn_options, dbname): - inst_command = 'sqlcmd ' + conn_options + ' -i ' + sqlfile + ' -d ' + dbname - executeCommmand(inst_command) - def setupTestDatabase(conn_options, dbname, azure): sqlFiles = ['test_types.sql', '168256.sql', 'cd_info.sql', 'tracks.sql'] @@ -78,8 +65,6 @@ def setupAE(conn_options, dbname): if (args.AZURE.lower() == 'no'): executeSQLscript('create_db.sql', conn_options, args.DBNAME) - # create login users - createLoginUsers(conn_options, args.DBNAME, args.AZURE) # create tables in the new database setupTestDatabase(conn_options, args.DBNAME, args.AZURE) # populate these tables diff --git a/test/functional/setup/test_password.sql b/test/functional/setup/test_password.sql deleted file mode 100644 index 683556c86..000000000 --- a/test/functional/setup/test_password.sql +++ /dev/null @@ -1,31 +0,0 @@ ---first, create new logins (user id / password pair) if not yet created -USE master; -GO - -IF NOT EXISTS (SELECT name FROM master..syslogins WHERE name = 'test_password') -BEGIN - CREATE LOGIN test_password WITH PASSWORD='! ;4triou'; -END -GO - -IF NOT EXISTS (SELECT name FROM master..syslogins WHERE name = 'test_password2') -BEGIN - CREATE LOGIN test_password2 WITH PASSWORD='!} ;4triou'; -END -GO - -IF NOT EXISTS (SELECT name FROM master..syslogins WHERE name = 'test_password3') -BEGIN - CREATE LOGIN test_password3 WITH PASSWORD='! ;4triou}'; -END -GO - ---the following users will be granted access to the test database -USE $(dbname); -GO - -CREATE USER test_password FROM LOGIN test_password; -CREATE USER test_password2 FROM LOGIN test_password2; -CREATE USER test_password3 FROM LOGIN test_password3; -GO - diff --git a/test/functional/sqlsrv/test_non_alpha_password.phpt b/test/functional/sqlsrv/test_non_alpha_password.phpt deleted file mode 100644 index a288a1d4c..000000000 --- a/test/functional/sqlsrv/test_non_alpha_password.phpt +++ /dev/null @@ -1,61 +0,0 @@ ---TEST-- -password with non alphanumeric characters ---DESCRIPTION-- -The first three cases should have no problem connecting. Only the last case fails because the -right curly brace should be escaped with another right brace. -In Azure for this test to pass do not specify any particular database when connecting ---SKIPIF-- - ---FILE-- - "test_password", "pwd" => "! ;4triou" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password2", "pwd" => "!}} ;4triou" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}}" )); -if (!$conn) -{ - $errors = sqlsrv_errors(); - echo( $errors[0]["message"]); -} -sqlsrv_close( $conn ); - -$conn = toConnect(array( "UID" => "test_password3", "pwd" => "! ;4triou}" )); -if ($conn) -{ - echo( "Shouldn't have connected" ); -} -$errors = sqlsrv_errors(); -echo $errors[0]["message"]; -sqlsrv_close( $conn ); - -print "Test successful"; -?> ---EXPECTREGEX-- -An unescaped right brace \(}\) was found in either the user name or password. All right braces must be escaped with another right brace \(}}\)\. -Warning: sqlsrv_close\(\) expects parameter 1 to be resource, bool(ean){0,1} given in .+(\/|\\)test_non_alpha_password\.php on line 45 -Test successful From d4f840f630453b0aaf230d6f40a3db46a374efd4 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 17 Dec 2018 12:25:37 -0800 Subject: [PATCH 081/249] Initialize output param buffer when allocating extra space (#907) --- source/shared/core_stmt.cpp | 11 ++- .../pdo_900_output_param_memory_data.phpt | 80 +++++++++++++++++++ .../sqlsrv_900_output_param_memory_data.phpt | 77 ++++++++++++++++++ 3 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_900_output_param_memory_data.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_900_output_param_memory_data.phpt diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 3b263fe22..ffeb3def6 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2770,10 +2770,10 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* { SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); buffer_len = Z_STRLEN_P( param_z ); + SQLLEN original_len = buffer_len; SQLLEN expected_len; SQLLEN buffer_null_extra; SQLLEN elem_size; - SQLLEN without_null_len; // calculate the size of each 'element' represented by column_size. WCHAR is of course 2, // as is a n(var)char/ntext field being returned as a binary field. @@ -2801,9 +2801,6 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* // binary fields aren't null terminated, so we need to account for that in our buffer length calcuations buffer_null_extra = (c_type == SQL_C_BINARY) ? elem_size : 0; - // this is the size of the string for Zend and for the StrLen parameter to SQLBindParameter - without_null_len = field_size * elem_size; - // increment to include the null terminator since the Zend length doesn't include the null terminator buffer_len += elem_size; @@ -2821,8 +2818,10 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* // A zval string len doesn't include the null. This calculates the length it should be // regardless of whether the ODBC type contains the NULL or not. - // null terminate the string to avoid a warning in debug PHP builds - ZSTR_VAL(param_z_string)[without_null_len] = '\0'; + // initialize the newly allocated space + char *p = ZSTR_VAL(param_z_string); + p = p + original_len; + memset(p, '\0', expected_len - original_len); ZVAL_NEW_STR(param_z, param_z_string); // buffer_len is the length passed to SQLBindParameter. It must contain the space for NULL in the diff --git a/test/functional/pdo_sqlsrv/pdo_900_output_param_memory_data.phpt b/test/functional/pdo_sqlsrv/pdo_900_output_param_memory_data.phpt new file mode 100644 index 000000000..b1683ec91 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_900_output_param_memory_data.phpt @@ -0,0 +1,80 @@ +--TEST-- +GitHub issue 900 - output parameter displays data from memory when not finalized +--DESCRIPTION-- +This test verifies that when there is an active resultset and output parameter not finalized, it should not show any data from client memory. This test does not work with AlwaysEncrypted because the output param is not assigned in the stored procedure. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +prepare("$storedProcName @OUTPUT = :output"); + if ($inout) { + $paramType = PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT; + } else { + $paramType = PDO::PARAM_STR; + } + $stmt->bindParam('output', $output, $paramType, $size); + + $stmt->execute(); + + // The output param should be doubled in size for wide characters. + // However, it should not contain any data so after trimming it + // should be merely an empty string because it was originally set to null + $len = strlen($output); + $result = trim($output); + + if ($len != ($size * 2) || $result !== "" ) { + echo "Unexpected output param for $dataType: "; + var_dump($output); + } + + $stmt->closeCursor(); + if (!is_null($output)) { + echo "Output param should be null when finalized!"; + } + unset($stmt); + } catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; + } +} + +try { + // This helper method sets PDO::ATTR_ERRMODE to PDO::ERRMODE_EXCEPTION + // $conn = connect(); + $conn = new PDO( "sqlsrv:server=$server; Database = $databaseName", $uid, $pwd); + $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + + $dataTypes = array("VARCHAR(256)", "VARCHAR(512)", "VARCHAR(max)", "NVARCHAR(256)", "NVARCHAR(512)", "NVARCHAR(max)"); + for ($i = 0, $p = 3; $i < count($dataTypes); $i++, $p++) { + // Create the stored procedure first + $storedProcName = "spNullOutputParam" . $i; + $procArgs = "@OUTPUT $dataTypes[$i] OUTPUT"; + $procCode = "SELECT 1, 2, 3"; + + createProc($conn, $storedProcName, $procArgs, $procCode); + getOutputParam($conn, $storedProcName, $dataTypes[$i], false); + getOutputParam($conn, $storedProcName, $dataTypes[$i], true); + + // Drop the stored procedure + dropProc($conn, $storedProcName); + } + + echo "Done\n"; + + unset($conn); +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_900_output_param_memory_data.phpt b/test/functional/sqlsrv/sqlsrv_900_output_param_memory_data.phpt new file mode 100644 index 000000000..ee195951f --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_900_output_param_memory_data.phpt @@ -0,0 +1,77 @@ +--TEST-- +GitHub issue 900 - output parameter displays data from memory when not finalized +--DESCRIPTION-- +This test verifies that when there is an active resultset and output parameter not finalized, it should not show any data from client memory. This test does not work with AlwaysEncrypted because the output param is not assigned in the stored procedure. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "UTF-8")); +if (!$conn) { + fatalError("Could not connect.\n"); +} + +$dataTypes = array("VARCHAR(256)", "VARCHAR(512)", "VARCHAR(max)", "NVARCHAR(256)", "NVARCHAR(512)", "NVARCHAR(max)"); +for ($i = 0, $p = 3; $i < count($dataTypes); $i++, $p++) { + // Create the stored procedure first + $storedProcName = "spNullOutputParam" . $i; + $procArgs = "@OUTPUT $dataTypes[$i] OUTPUT"; + $procCode = "SELECT 1, 2, 3"; + + createProc($conn, $storedProcName, $procArgs, $procCode); + getOutputParam($conn, $storedProcName, false, ($i < 3)); + getOutputParam($conn, $storedProcName, true, ($i < 3)); + + // Drop the stored procedure + dropProc($conn, $storedProcName); +} + +echo "Done\n"; + +sqlsrv_close($conn); + +?> +--EXPECT-- +Done From 4efb54e45a80e1d8839b5cd655c091e6f13b717c Mon Sep 17 00:00:00 2001 From: Jannes Jeising Date: Thu, 20 Dec 2018 20:49:06 +0100 Subject: [PATCH 082/249] Enable compiling extensions statically into PHP (#904) --- source/pdo_sqlsrv/config.m4 | 1 - source/pdo_sqlsrv/pdo_dbh.cpp | 6 +- source/pdo_sqlsrv/pdo_init.cpp | 28 +- source/pdo_sqlsrv/pdo_parser.cpp | 6 +- source/pdo_sqlsrv/pdo_stmt.cpp | 6 +- source/pdo_sqlsrv/pdo_util.cpp | 6 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 376 +---------------- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 409 +++++++++++++++++++ source/shared/core_init.cpp | 17 - source/sqlsrv/config.m4 | 5 +- source/sqlsrv/config.w32 | 4 +- source/sqlsrv/conn.cpp | 6 +- source/sqlsrv/init.cpp | 28 +- source/sqlsrv/php_sqlsrv.h | 538 ++----------------------- source/sqlsrv/php_sqlsrv_int.h | 468 +++++++++++++++++++++ source/sqlsrv/stmt.cpp | 7 +- source/sqlsrv/util.cpp | 6 +- 17 files changed, 999 insertions(+), 918 deletions(-) create mode 100644 source/pdo_sqlsrv/php_pdo_sqlsrv_int.h create mode 100644 source/sqlsrv/php_sqlsrv_int.h diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index 82aa69e20..a1f757ece 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -95,4 +95,3 @@ if test "$PHP_PDO_SQLSRV" != "no"; then PHP_ADD_EXTENSION_DEP(pdo_sqlsrv, pdo) PHP_ADD_BUILD_DIR([$ext_builddir/shared], 1) fi - diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index d1f76e495..c364e12dc 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -17,7 +17,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" #include #include diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 6b7b54e39..2f38b0082 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -17,12 +17,18 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" +#ifdef COMPILE_DL_PDO_SQLSRV #ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE(); #endif ZEND_GET_MODULE(g_pdo_sqlsrv) +#endif extern "C" { @@ -308,3 +314,23 @@ namespace { { NULL , 0 } // terminate the table }; } + +// DllMain for the extension. +#ifdef _WIN32 +// Only needed if extension is built shared +#ifdef COMPILE_DL_PDO_SQLSRV +BOOL WINAPI DllMain( _In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, LPVOID ) +{ + switch( fdwReason ) { + case DLL_PROCESS_ATTACH: + // store the module handle for use by client_info and server_info + g_sqlsrv_hmodule = hinstDLL; + break; + default: + break; + } + + return TRUE; +} +#endif +#endif diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 984f983c5..1513c6615 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -19,7 +19,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" // Constructor conn_string_parser:: conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 3f7e72b8b..c2b0ebd99 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -17,7 +17,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" // *** internal variables and constants *** diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 91efcb902..1e27ea4c4 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -17,7 +17,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_pdo_sqlsrv.h" +extern "C" { + #include "php_pdo_sqlsrv.h" +} + +#include "php_pdo_sqlsrv_int.h" #include "zend_exceptions.h" diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index f31da7c70..c001c4802 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -20,55 +20,12 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "core_sqlsrv.h" -#include "version.h" - -extern "C" { - -#include "pdo/php_pdo.h" -#include "pdo/php_pdo_driver.h" - -} - -#include -#include - - -//********************************************************************************************************************************* -// Constants and Types -//********************************************************************************************************************************* - -// sqlsrv driver specific PDO attributes -enum PDO_SQLSRV_ATTR { - - // 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_FORMAT_DECIMALS, - SQLSRV_ATTR_DECIMAL_PLACES -}; - -// valid set of values for TransactionIsolation connection option -namespace PDOTxnIsolationValues { - - const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED"; - const char READ_COMMITTED[] = "READ_COMMITTED"; - const char REPEATABLE_READ[] = "REPEATABLE_READ"; - const char SERIALIZABLE[] = "SERIALIZABLE"; - const char SNAPSHOT[] = "SNAPSHOT"; -} +#include "php.h" //********************************************************************************************************************************* // Global variables //********************************************************************************************************************************* -extern "C" { - // request level variables ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) @@ -79,32 +36,6 @@ ZEND_END_MODULE_GLOBALS(pdo_sqlsrv) ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv); -} - -// macros used to access the global variables. Use these to make global variable access agnostic to threads -#ifdef ZTS -#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v) -#else -#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v -#endif - -// INI settings and constants -// (these are defined as macros to allow concatenation as we do below) -#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" -#define INI_PDO_SQLSRV_LOG "log_severity" -#define INI_PREFIX "pdo_sqlsrv." - -PHP_INI_BEGIN() - STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, - zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, - client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) -PHP_INI_END() - -// henv context for creating connections -extern sqlsrv_context* g_pdo_henv_cp; -extern sqlsrv_context* g_pdo_henv_ncp; - //********************************************************************************************************************************* // Initialization @@ -128,309 +59,4 @@ PHP_MINFO_FUNCTION(pdo_sqlsrv); extern zend_module_entry g_pdo_sqlsrv_module_entry; // describes the extension to PHP -// Basic string parser -class string_parser -{ - protected: - const char* orig_str; - sqlsrv_context* ctx; - int len; - int pos; - unsigned int current_key; - HashTable* element_ht; - inline bool next(void); - inline bool is_eos(void); - inline bool is_white_space( _In_ char c ); - bool discard_white_spaces(void); - void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC ); -}; - -//********************************************************************************************************************************* -// PDO DSN Parser -//********************************************************************************************************************************* - -// Parser class used to parse DSN connection string. -class conn_string_parser : private string_parser -{ - enum States - { - FirstKeyValuePair, - Key, - Value, - ValueContent1, - ValueContent2, - RCBEncountered, - NextKeyValuePair, - }; - - private: - const char* current_key_name; - int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len ); - void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC); - - protected: - void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC); - - public: - conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ); - void parse_conn_string( TSRMLS_D ); -}; - -//********************************************************************************************************************************* -// PDO Query Parser -//********************************************************************************************************************************* - -// Parser class used to parse DSN named placeholders. -class sql_string_parser : private string_parser -{ - private: - bool is_placeholder_char(char); - public: - void add_key_int_value_pair( _In_ unsigned int value TSRMLS_DC ); - sql_string_parser(_In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholder_ht); - void parse_sql_string(TSRMLS_D); -}; - -//********************************************************************************************************************************* -// Connection -//********************************************************************************************************************************* -extern const connection_option PDO_CONN_OPTS[]; - -int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options TSRMLS_DC); - -// a core layer pdo dbh object. This object inherits and overrides the statement factory -struct pdo_sqlsrv_dbh : public sqlsrv_conn { - - zval* stmts; - bool direct_query; - long query_timeout; - zend_long client_buffer_max_size; - bool fetch_numeric; - bool fetch_datetime; - bool format_decimals; - short decimal_places; - - pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); -}; - - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -struct stmt_option_encoding : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_pdo_scrollable : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_direct_query : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_cursor_scroll_type : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_emulate_prepares : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_fetch_numeric : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -struct stmt_option_fetch_datetime : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; - -// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary -struct pdo_sqlsrv_stmt : public sqlsrv_stmt { - - pdo_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv TSRMLS_DC ) : - sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), - direct_query( false ), - direct_query_subst_string( NULL ), - direct_query_subst_string_len( 0 ), - placeholders(NULL), - bound_column_param_types( NULL ), - fetch_numeric( false ), - fetch_datetime( false ) - { - pdo_sqlsrv_dbh* db = static_cast( c ); - direct_query = db->direct_query; - fetch_numeric = db->fetch_numeric; - fetch_datetime = db->fetch_datetime; - format_decimals = db->format_decimals; - decimal_places = db->decimal_places; - } - - virtual ~pdo_sqlsrv_stmt( void ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types - virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); - - bool direct_query; // flag set if the query should be executed directly or prepared - const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters - size_t direct_query_subst_string_len; // length of query string used for direct queries - HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare - - pdo_param_type* bound_column_param_types; - bool fetch_numeric; - bool fetch_datetime; -}; - - -//********************************************************************************************************************************* -// Error Handling Functions -//********************************************************************************************************************************* - -// represents the mapping between an error_code and the corresponding error message. -struct pdo_error { - - unsigned int error_code; - sqlsrv_error_const sqlsrv_error; -}; - -// called when an error occurs in the core layer. These routines are set as the error_callback in a -// context. The context is passed to this function since it contains the function - -bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, - _In_opt_ va_list* print_args ); -bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, - _In_opt_ va_list* print_args ); -bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, - _In_opt_ va_list* print_args ); - -// common routine to transfer a sqlsrv_context's error to a PDO zval -void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Out_ zval* pdo_zval ); - -// reset the errors from the last operation -inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh TSRMLS_DC ) -{ - strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error - - // release the last statement from the dbh so that error handling won't have a statement passed to it - if( dbh->query_stmt ) { - dbh->query_stmt = NULL; - zval_ptr_dtor( &dbh->query_stmt_zval ); - } - - // if the driver isn't valid, just return (PDO calls close sometimes more than once?) - if( dbh->driver_data == NULL ) { - return; - } - - // reset the last error on the sqlsrv_context - sqlsrv_context* ctx = static_cast( dbh->driver_data ); - - if( ctx->last_error() ) { - ctx->last_error().reset(); - } -} - -#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); - -inline void pdo_reset_stmt_error( _Inout_ pdo_stmt_t* stmt ) -{ - strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error - - // if the driver isn't valid, just return (PDO calls close sometimes more than once?) - if( stmt->driver_data == NULL ) { - return; - } - - // reset the last error on the sqlsrv_context - sqlsrv_context* ctx = static_cast( stmt->driver_data ); - - if( ctx->last_error() ) { - ctx->last_error().reset(); - } -} - -#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt ); - -// validate the driver objects -#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); } -#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); } - - -//********************************************************************************************************************************* -// Utility Functions -//********************************************************************************************************************************* - -// List of PDO specific error messages. -enum PDO_ERROR_CODES { - - PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC, - PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, - PDO_SQLSRV_ERROR_INVALID_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, - PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, - PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, - PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, - PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, - PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, - PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, - PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, - PDO_SQLSRV_ERROR_PARAM_PARSE, - PDO_SQLSRV_ERROR_LAST_INSERT_ID, - PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, - PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, - PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, - PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, - PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, - PDO_SQLSRV_ERROR_INVALID_DSN_STRING, - PDO_SQLSRV_ERROR_INVALID_DSN_KEY, - PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, - PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, - PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, - PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, - SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, - PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, - PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, - PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, - PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, - PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED, - PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, - PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED, - PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED -}; - -extern pdo_error PDO_ERRORS[]; - -#define THROW_PDO_ERROR( ctx, custom, ... ) \ - call_error_handler( ctx, custom TSRMLS_CC, false, ## __VA_ARGS__ ); \ - throw pdo::PDOException(); - -namespace pdo { - - // an error which occurred in our PDO driver, NOT an exception thrown by PDO - struct PDOException : public core::CoreException { - - PDOException() : CoreException() - { - } - }; - -} // namespace pdo - -// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro -void pdo_sqlsrv_log( _In_opt_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); - #endif /* PHP_PDO_SQLSRV_H */ - diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h new file mode 100644 index 000000000..3db1d99e4 --- /dev/null +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -0,0 +1,409 @@ +#ifndef PHP_PDO_SQLSRV_INT_H +#define PHP_PDO_SQLSRV_INT_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: php_pdo_sqlsrv_int.h +// +// Contents: Internal declarations for the extension +// +// Microsoft Drivers 5.5 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" +#include "version.h" + +extern "C" { + #include "pdo/php_pdo.h" + #include "pdo/php_pdo_driver.h" +} + +#include +#include + +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* + +// henv context for creating connections +extern sqlsrv_context* g_pdo_henv_cp; +extern sqlsrv_context* g_pdo_henv_ncp; + +// used for getting the version information +extern HMODULE g_sqlsrv_hmodule; + +// macros used to access the global variables. Use these to make global variable access agnostic to threads +#ifdef ZTS +#define PDO_SQLSRV_G(v) TSRMG(pdo_sqlsrv_globals_id, zend_pdo_sqlsrv_globals *, v) +#else +#define PDO_SQLSRV_G(v) pdo_sqlsrv_globals.v +#endif + +// INI settings and constants +// (these are defined as macros to allow concatenation as we do below) +#define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" +#define INI_PDO_SQLSRV_LOG "log_severity" +#define INI_PREFIX "pdo_sqlsrv." + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, + zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, + client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) +PHP_INI_END() + + +//********************************************************************************************************************************* +// Constants and Types +//********************************************************************************************************************************* + +// sqlsrv driver specific PDO attributes +enum PDO_SQLSRV_ATTR { + + // 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_FORMAT_DECIMALS, + SQLSRV_ATTR_DECIMAL_PLACES +}; + +// valid set of values for TransactionIsolation connection option +namespace PDOTxnIsolationValues { + + const char READ_UNCOMMITTED[] = "READ_UNCOMMITTED"; + const char READ_COMMITTED[] = "READ_COMMITTED"; + const char REPEATABLE_READ[] = "REPEATABLE_READ"; + const char SERIALIZABLE[] = "SERIALIZABLE"; + const char SNAPSHOT[] = "SNAPSHOT"; +} + + +//********************************************************************************************************************************* +// Initialization +//********************************************************************************************************************************* + +// Basic string parser +class string_parser +{ + protected: + const char* orig_str; + sqlsrv_context* ctx; + int len; + int pos; + unsigned int current_key; + HashTable* element_ht; + inline bool next(void); + inline bool is_eos(void); + inline bool is_white_space( _In_ char c ); + bool discard_white_spaces(void); + void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC ); +}; + + +//********************************************************************************************************************************* +// PDO DSN Parser +//********************************************************************************************************************************* + +// Parser class used to parse DSN connection string. +class conn_string_parser : private string_parser +{ + enum States + { + FirstKeyValuePair, + Key, + Value, + ValueContent1, + ValueContent2, + RCBEncountered, + NextKeyValuePair, + }; + + private: + const char* current_key_name; + int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len ); + void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC); + + protected: + void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC); + + public: + conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ); + void parse_conn_string( TSRMLS_D ); +}; + + +//********************************************************************************************************************************* +// PDO Query Parser +//********************************************************************************************************************************* + +// Parser class used to parse DSN named placeholders. +class sql_string_parser : private string_parser +{ + private: + bool is_placeholder_char(char); + public: + void add_key_int_value_pair( _In_ unsigned int value TSRMLS_DC ); + sql_string_parser(_In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholder_ht); + void parse_sql_string(TSRMLS_D); +}; + + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* + +extern const connection_option PDO_CONN_OPTS[]; + +int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options TSRMLS_DC); + +// a core layer pdo dbh object. This object inherits and overrides the statement factory +struct pdo_sqlsrv_dbh : public sqlsrv_conn { + + zval* stmts; + bool direct_query; + long query_timeout; + zend_long client_buffer_max_size; + bool fetch_numeric; + bool fetch_datetime; + bool format_decimals; + short decimal_places; + + pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); +}; + + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +struct stmt_option_encoding : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_pdo_scrollable : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_direct_query : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_cursor_scroll_type : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_emulate_prepares : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_fetch_numeric : public stmt_option_functor { + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +struct stmt_option_fetch_datetime : public stmt_option_functor { + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; + +// a core layer pdo stmt object. This object inherits and overrides the callbacks necessary +struct pdo_sqlsrv_stmt : public sqlsrv_stmt { + + pdo_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv TSRMLS_DC ) : + sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), + direct_query( false ), + direct_query_subst_string( NULL ), + direct_query_subst_string_len( 0 ), + placeholders(NULL), + bound_column_param_types( NULL ), + fetch_numeric( false ), + fetch_datetime( false ) + { + pdo_sqlsrv_dbh* db = static_cast( c ); + direct_query = db->direct_query; + fetch_numeric = db->fetch_numeric; + fetch_datetime = db->fetch_datetime; + format_decimals = db->format_decimals; + decimal_places = db->decimal_places; + } + + virtual ~pdo_sqlsrv_stmt( void ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types + virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); + + bool direct_query; // flag set if the query should be executed directly or prepared + const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters + size_t direct_query_subst_string_len; // length of query string used for direct queries + HashTable* placeholders; // hashtable of named placeholders to keep track of params ordering in emulate prepare + + pdo_param_type* bound_column_param_types; + bool fetch_numeric; + bool fetch_datetime; +}; + + +//********************************************************************************************************************************* +// Error Handling Functions +//********************************************************************************************************************************* + +// represents the mapping between an error_code and the corresponding error message. +struct pdo_error { + + unsigned int error_code; + sqlsrv_error_const sqlsrv_error; +}; + +// called when an error occurs in the core layer. These routines are set as the error_callback in a +// context. The context is passed to this function since it contains the function + +bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, + _In_opt_ va_list* print_args ); +bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, + _In_opt_ va_list* print_args ); +bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, + _In_opt_ va_list* print_args ); + +// common routine to transfer a sqlsrv_context's error to a PDO zval +void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Out_ zval* pdo_zval ); + +// reset the errors from the last operation +inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh TSRMLS_DC ) +{ + strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error + + // release the last statement from the dbh so that error handling won't have a statement passed to it + if( dbh->query_stmt ) { + dbh->query_stmt = NULL; + zval_ptr_dtor( &dbh->query_stmt_zval ); + } + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( dbh->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( dbh->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); + +inline void pdo_reset_stmt_error( _Inout_ pdo_stmt_t* stmt ) +{ + strcpy_s( stmt->error_code, sizeof( stmt->error_code ), "00000" ); // 00000 means no error + + // if the driver isn't valid, just return (PDO calls close sometimes more than once?) + if( stmt->driver_data == NULL ) { + return; + } + + // reset the last error on the sqlsrv_context + sqlsrv_context* ctx = static_cast( stmt->driver_data ); + + if( ctx->last_error() ) { + ctx->last_error().reset(); + } +} + +#define PDO_RESET_STMT_ERROR pdo_reset_stmt_error( stmt ); + +// validate the driver objects +#define PDO_VALIDATE_CONN if( dbh->driver_data == NULL ) { DIE( "Invalid driver data in PDO object." ); } +#define PDO_VALIDATE_STMT if( stmt->driver_data == NULL ) { DIE( "Invalid driver data in PDOStatement object." ); } + + +//********************************************************************************************************************************* +// Utility Functions +//********************************************************************************************************************************* + +// List of PDO specific error messages. +enum PDO_ERROR_CODES { + + PDO_SQLSRV_ERROR_INVALID_DBH_ATTR = SQLSRV_ERROR_DRIVER_SPECIFIC, + PDO_SQLSRV_ERROR_INVALID_STMT_ATTR, + PDO_SQLSRV_ERROR_INVALID_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM, + PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED, + PDO_SQLSRV_ERROR_UNSUPPORTED_DBH_ATTR, + PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR, + PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR, + PDO_SQLSRV_ERROR_INVALID_STMT_OPTION, + PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE, + PDO_SQLSRV_ERROR_FUNCTION_NOT_IMPLEMENTED, + PDO_SQLSRV_ERROR_PARAM_PARSE, + PDO_SQLSRV_ERROR_LAST_INSERT_ID, + PDO_SQLSRV_ERROR_INVALID_COLUMN_DRIVER_DATA, + PDO_SQLSRV_ERROR_COLUMN_TYPE_DOES_NOT_SUPPORT_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_COLUMN_ENCODING, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING, + PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, + PDO_SQLSRV_ERROR_CURSOR_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_DSN_STRING, + PDO_SQLSRV_ERROR_INVALID_DSN_KEY, + PDO_SQLSRV_ERROR_INVALID_DSN_VALUE, + PDO_SQLSRV_ERROR_SERVER_NOT_SPECIFIED, + PDO_SQLSRV_ERROR_DSN_STRING_ENDED_UNEXPECTEDLY, + PDO_SQLSRV_ERROR_EXTRA_SEMI_COLON_IN_DSN_STRING, + SQLSRV_ERROR_UNESCAPED_RIGHT_BRACE_IN_DSN, + PDO_SQLSRV_ERROR_RCB_MISSING_IN_DSN_VALUE, + PDO_SQLSRV_ERROR_DQ_ATTR_AT_PREPARE_ONLY, + PDO_SQLSRV_ERROR_INVALID_COLUMN_INDEX, + PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + PDO_SQLSRV_ERROR_INVALID_CURSOR_WITH_SCROLL_TYPE, + PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED, + PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, + PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED, + PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED +}; + +extern pdo_error PDO_ERRORS[]; + +#define THROW_PDO_ERROR( ctx, custom, ... ) \ + call_error_handler( ctx, custom TSRMLS_CC, false, ## __VA_ARGS__ ); \ + throw pdo::PDOException(); + +namespace pdo { + + // an error which occurred in our PDO driver, NOT an exception thrown by PDO + struct PDOException : public core::CoreException { + + PDOException() : CoreException() + { + } + }; + +} // namespace pdo + +// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro +void pdo_sqlsrv_log( _In_opt_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); + + +#endif /* PHP_PDO_SQLSRV_INT_H */ diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index ea604e791..03b56d4ff 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -159,20 +159,3 @@ void core_sqlsrv_mshutdown( _Inout_ sqlsrv_context& henv_cp, _Inout_ sqlsrv_cont return; } - -// DllMain for the extension. -#ifdef _WIN32 -BOOL WINAPI DllMain( _In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, LPVOID ) -{ - switch( fdwReason ) { - case DLL_PROCESS_ATTACH: - // store the module handle for use by client_info and server_info - g_sqlsrv_hmodule = hinstDLL; - break; - default: - break; - } - - return TRUE; -} -#endif diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index 5af9e7cd5..d78c1a09c 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -40,7 +40,10 @@ if test "$PHP_SQLSRV" != "no"; then shared/StringFunctions.cpp \ " AC_MSG_CHECKING([for SQLSRV headers]) - if test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then + if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h && test "$PHP_PDO_SQLSRV" != "no"; then + pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/ + shared_src_class="" + elif test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then sqlsrv_inc_path=$srcdir/ext/sqlsrv/shared/ elif test -f $srcdir/shared/core_sqlsrv.h; then sqlsrv_inc_path=$srcdir/shared/ diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index dc9f0b5fb..d34f98756 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -27,7 +27,9 @@ if( PHP_SQLSRV != "no" ) { if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") && CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")&& CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_SQLSRV", configure_module_dirname + "\\shared")) { - ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "sqlsrv" ); + if (PHP_PDO_SQLSRV == "no" || PHP_SQLSRV_SHARED) { + ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "sqlsrv" ); + } CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_SQLSRV_ODBC"); CHECK_HEADER_ADD_INCLUDE("sqlext.h", "CFLAGS_SQLSRV_ODBC"); ADD_FLAG( "LDFLAGS_SQLSRV", "/NXCOMPAT /DYNAMICBASE /debug /guard:cf" ); diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 1de0c6b6f..72114e3c0 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -17,7 +17,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_sqlsrv.h" +extern "C" { + #include "php_sqlsrv.h" +} + +#include "php_sqlsrv_int.h" #include #include diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 9079ac752..fd1ab2055 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -16,12 +16,18 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_sqlsrv.h" +extern "C" { + #include "php_sqlsrv.h" +} + +#include "php_sqlsrv_int.h" +#ifdef COMPILE_DL_SQLSRV #ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE(); #endif ZEND_GET_MODULE(g_sqlsrv) +#endif extern "C" { @@ -685,3 +691,23 @@ PHP_MINFO_FUNCTION(sqlsrv) php_info_print_table_end(); DISPLAY_INI_ENTRIES(); } + +// DllMain for the extension. +#ifdef _WIN32 +// Only needed if extension is built shared +#ifdef COMPILE_DL_SQLSRV +BOOL WINAPI DllMain( _In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, LPVOID ) +{ + switch( fdwReason ) { + case DLL_PROCESS_ATTACH: + // store the module handle for use by client_info and server_info + g_sqlsrv_hmodule = hinstDLL; + break; + default: + break; + } + + return TRUE; +} +#endif +#endif diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index f60c6c849..891cf1256 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -22,84 +22,44 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "core_sqlsrv.h" -#include "version.h" +#include "php.h" -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef PHP_WIN32 -#define PHP_SQLSRV_API __declspec(dllexport) -#else -#define PHP_SQLSRV_API -#endif - -// OACR is an internal Microsoft static code analysis tool -#if defined(OACR) -#include -OACR_WARNING_PUSH -OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." ) -OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." ) -OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." ) -OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." ) -#endif +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* -extern "C" { +// request level variables +ZEND_BEGIN_MODULE_GLOBALS(sqlsrv) -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 ) -#endif +// global objects for errors and warnings. These are returned by sqlsrv_errors. +zval errors; +zval warnings; -#ifdef ZTS -#include "TSRM.h" -#endif +// flags for error handling and logging (set via sqlsrv_configure or php.ini) +zend_long log_severity; +zend_long log_subsystems; +zend_long current_subsystem; +zend_bool warnings_return_as_errors; +zend_long buffered_query_limit; -#if _MSC_VER >= 1400 -// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. -// php.h defines this constant as unsigned int which causes a compile error -// in ws2tcpip.h. Fortunately php.h allows an override by defining -// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define -// socklen_t here and override the php.h version. -typedef int socklen_t; -#define HAVE_SOCKLEN_T -#endif +ZEND_END_MODULE_GLOBALS(sqlsrv) -#if defined(_MSC_VER) -#pragma warning(pop) -#endif +ZEND_EXTERN_MODULE_GLOBALS(sqlsrv); -#if PHP_MAJOR_VERSION < 7 -#error Trying to compile "Microsoft Drivers for PHP for SQL Server (SQLSRV Driver)" with an unsupported version of PHP -#endif +// macro used to access the global variables. Use it to make global variable access agnostic to threads +#define SQLSRV_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(sqlsrv, v) -#if ZEND_DEBUG -// debug build causes warning C4505 to pop up from the Zend header files -#pragma warning( disable: 4505 ) +#if defined(ZTS) +ZEND_TSRMLS_CACHE_EXTERN(); #endif -} // extern "C" //********************************************************************************************************************************* // Initialization Functions //********************************************************************************************************************************* -// module global variables (initialized in minit and freed in mshutdown) -extern HashTable* g_ss_errors_ht; -extern HashTable* g_ss_encodings_ht; -extern HashTable* g_ss_warnings_to_ignore_ht; - // variables set during initialization extern zend_module_entry g_sqlsrv_module_entry; // describes the extension to PHP -extern HMODULE g_sqlsrv_hmodule; // used for getting the version information - -// henv context for creating connections -extern sqlsrv_context* g_ss_henv_cp; -extern sqlsrv_context* g_ss_henv_ncp; - -extern bool isVistaOrGreater; // used to determine if OS is Vista or Greater #define phpext_sqlsrv_ptr &g_sqlsrv_module_entry @@ -114,9 +74,11 @@ PHP_RSHUTDOWN_FUNCTION(sqlsrv); // module info function (info returned by phpinfo()) PHP_MINFO_FUNCTION(sqlsrv); + //********************************************************************************************************************************* -// Connection +// Functions //********************************************************************************************************************************* + PHP_FUNCTION(sqlsrv_connect); PHP_FUNCTION(sqlsrv_begin_transaction); PHP_FUNCTION(sqlsrv_client_info); @@ -127,90 +89,6 @@ PHP_FUNCTION(sqlsrv_prepare); PHP_FUNCTION(sqlsrv_rollback); PHP_FUNCTION(sqlsrv_server_info); -struct ss_sqlsrv_conn : sqlsrv_conn -{ - HashTable* stmts; - bool date_as_string; - bool format_decimals; // flag set to turn on formatting for values of decimal / numeric types - short decimal_places; // number of decimal digits to show in a result set unless format_numbers is false - bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls - - // static variables used in process_params - static const char* resource_name; - static int descriptor; - - // initialize with default values - ss_sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC ) : - sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), - stmts( NULL ), - date_as_string( false ), - format_decimals( false ), - decimal_places( NO_CHANGE_DECIMAL_PLACES ), - in_transaction( false ) - { - } -}; - -// resource destructor -void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ); - -//********************************************************************************************************************************* -// Statement -//********************************************************************************************************************************* - -// holds the field names for reuse by sqlsrv_fetch_array/object as keys -struct sqlsrv_fetch_field_name { - char* name; - SQLLEN len; -}; - -struct stmt_option_ss_scrollable : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); -}; - -// This object inherits and overrides the callbacks necessary -struct ss_sqlsrv_stmt : public sqlsrv_stmt { - - ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv TSRMLS_DC ); - - virtual ~ss_sqlsrv_stmt( void ); - - void new_result_set( TSRMLS_D ); - - // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants - sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); - - bool prepared; // whether the statement has been prepared yet (used for error messages) - zend_ulong conn_index; // index into the connection hash that contains this statement structure - zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute - sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys - int fetch_fields_count; - - // static variables used in process_params - static const char* resource_name; - static int descriptor; - -}; - -// holds the field names for reuse by sqlsrv_fetch_array/object as keys -struct sqlsrv_fetch_field { - char* name; - unsigned int len; -}; - -// holds the stream param and the encoding that it was assigned -struct sqlsrv_stream_encoding { - zval* stream_z; - unsigned int encoding; - - sqlsrv_stream_encoding( _In_ zval* str_z, _In_ unsigned int enc ) : - stream_z( str_z ), encoding( enc ) - { - } -}; - -// *** statement functions *** PHP_FUNCTION(sqlsrv_cancel); PHP_FUNCTION(sqlsrv_execute); PHP_FUNCTION(sqlsrv_fetch); @@ -226,20 +104,6 @@ PHP_FUNCTION(sqlsrv_num_rows); PHP_FUNCTION(sqlsrv_rows_affected); PHP_FUNCTION(sqlsrv_send_stream_data); -// resource destructor -void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ); - -// "internal" statement functions shared by functions in conn.cpp and stmt.cpp -void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ); -bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function - TSRMLS_DC ); -void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC ); -void free_stmt_resource( _Inout_ zval* stmt_z TSRMLS_DC ); - -//********************************************************************************************************************************* -// Type Functions -//********************************************************************************************************************************* - // type functions for SQL types. // to expose SQL Server paramterized types, we use functions that return encoded integers that contain the size/precision etc. // for example, SQLSRV_SQLTYPE_VARCHAR(4000) matches the usage of SQLSRV_SQLTYPE_INT with the size added. @@ -258,368 +122,14 @@ PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR); PHP_FUNCTION(SQLSRV_PHPTYPE_STREAM); PHP_FUNCTION(SQLSRV_PHPTYPE_STRING); -//********************************************************************************************************************************* -// Global variables -//********************************************************************************************************************************* - -extern "C" { - -// request level variables -ZEND_BEGIN_MODULE_GLOBALS(sqlsrv) - -// global objects for errors and warnings. These are returned by sqlsrv_errors. -zval errors; -zval warnings; - -// flags for error handling and logging (set via sqlsrv_configure or php.ini) -zend_long log_severity; -zend_long log_subsystems; -zend_long current_subsystem; -zend_bool warnings_return_as_errors; -zend_long buffered_query_limit; - -ZEND_END_MODULE_GLOBALS(sqlsrv) - -ZEND_EXTERN_MODULE_GLOBALS(sqlsrv); - -} - -// macro used to access the global variables. Use it to make global variable access agnostic to threads -#define SQLSRV_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(sqlsrv, v) - -#if defined(ZTS) -ZEND_TSRMLS_CACHE_EXTERN(); -#endif - -// INI settings and constants -// (these are defined as macros to allow concatenation as we do below) -#define INI_WARNINGS_RETURN_AS_ERRORS "WarningsReturnAsErrors" -#define INI_LOG_SEVERITY "LogSeverity" -#define INI_LOG_SUBSYSTEMS "LogSubsystems" -#define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize" -#define INI_PREFIX "sqlsrv." - -PHP_INI_BEGIN() - STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors, - zend_sqlsrv_globals, sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SEVERITY, "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_sqlsrv_globals, - sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SUBSYSTEMS, "0", PHP_INI_ALL, OnUpdateLong, log_subsystems, zend_sqlsrv_globals, - sqlsrv_globals ) - STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit, - zend_sqlsrv_globals, sqlsrv_globals ) -PHP_INI_END() - -//********************************************************************************************************************************* -// Configuration -//********************************************************************************************************************************* // These functions set and retrieve configuration settings. Configuration settings defined are: // WarningsReturnAsErrors - treat all ODBC warnings as errors and return false from sqlsrv APIs. // LogSeverity - combination of severity of messages to log (see Logging) // LogSubsystems - subsystems within sqlsrv to log messages (see Logging) - PHP_FUNCTION(sqlsrv_configure); PHP_FUNCTION(sqlsrv_get_config); -//********************************************************************************************************************************* -// Errors -//********************************************************************************************************************************* - -// represents the mapping between an error_code and the corresponding error message. -struct ss_error { - - unsigned int error_code; - sqlsrv_error_const sqlsrv_error; -}; - -// List of all driver specific error codes. -enum SS_ERROR_CODES { - - SS_SQLSRV_ERROR_ALREADY_IN_TXN = SQLSRV_ERROR_DRIVER_SPECIFIC, - SS_SQLSRV_ERROR_NOT_IN_TXN, - SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, - SS_SQLSRV_ERROR_REGISTER_RESOURCE, - SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, - SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, - SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, - SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, - SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, - SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, - SS_SQLSRV_ERROR_ZEND_BAD_CLASS, - SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, - SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, - SS_SQLSRV_ERROR_INVALID_OPTION, - SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, - SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, - SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, - SS_SQLSRV_ERROR_VAR_REQUIRED, - SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, - SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, - SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, - SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, - SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, - SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED -}; - -extern ss_error SS_ERRORS[]; - -bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool warning TSRMLS_DC, _In_opt_ va_list* print_args ); - // *** extension error functions *** PHP_FUNCTION(sqlsrv_errors); -// convert from the default encoding specified by the "CharacterSet" -// connection option to UTF-16. mbcs_len and utf16_len are sizes in -// bytes. The return is the number of UTF-16 characters in the string -// returned in utf16_out_string. -unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, - _In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer(mbcs_in_string) wchar_t* utf16_out_string, - _In_ unsigned int utf16_len ); -// create a wide char string from the passed in mbcs string. NULL is returned if the string -// could not be created. No error is posted by this function. utf16_len is the number of -// wchar_t characters, not the number of bytes. -SQLWCHAR* utf16_string_from_mbcs_string( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, - _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); - -// *** internal error macros and functions *** -bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); - -// release current error lists and set to NULL -inline void reset_errors( TSRMLS_D ) -{ - if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) { - DIE( "sqlsrv_errors contains an invalid type" ); - } - if( Z_TYPE( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE( SQLSRV_G( warnings )) != IS_NULL ) { - DIE( "sqlsrv_warnings contains an invalid type" ); - } - - if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL( SQLSRV_G( errors ))); - FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( errors ))); - } - if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY ) { - zend_hash_destroy( Z_ARRVAL( SQLSRV_G( warnings ))); - FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( warnings ))); - } - - ZVAL_NULL( &SQLSRV_G( errors )); - ZVAL_NULL( &SQLSRV_G( warnings )); -} - -#define THROW_SS_ERROR( ctx, error_code, ... ) \ - (void)call_error_handler( ctx, error_code TSRMLS_CC, false /*warning*/, ## __VA_ARGS__ ); \ - throw ss::SSException(); - - -class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr > { - -public: - - sqlsrv_context_auto_ptr( void ) : - sqlsrv_auto_ptr( NULL ) - { - } - - sqlsrv_context_auto_ptr( _Inout_opt_ const sqlsrv_context_auto_ptr& src ) : - sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >( src ) - { - } - - // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. - void reset( _In_opt_ sqlsrv_context* ptr = NULL ) - { - if( _ptr ) { - _ptr->~sqlsrv_context(); - sqlsrv_free( (void*) _ptr ); - } - _ptr = ptr; - } - - sqlsrv_context* operator=( _In_opt_ sqlsrv_context* ptr ) - { - return sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >::operator=( ptr ); - } - - void operator=( _Inout_opt_ sqlsrv_context_auto_ptr& src ) - { - sqlsrv_context* p = src.get(); - src.transferred(); - this->_ptr = p; - } -}; - - - -//********************************************************************************************************************************* -// Logging -//********************************************************************************************************************************* -#define LOG_FUNCTION( function_name ) \ - const char* _FN_ = function_name; \ - SQLSRV_G( current_subsystem ) = current_log_subsystem; \ - LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); - -#define SET_FUNCTION_NAME( context ) \ -{ \ - (context).set_func( _FN_ ); \ -} - -// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro -void ss_sqlsrv_log( _In_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); - -// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise. -enum logging_subsystems { - LOG_INIT = 0x01, - LOG_CONN = 0x02, - LOG_STMT = 0x04, - LOG_UTIL = 0x08, - LOG_ALL = -1, -}; - -//********************************************************************************************************************************* -// Common function wrappers -// have to place this namespace before the utility functions -// otherwise can't compile in Linux because 'ss' not defined -//********************************************************************************************************************************* -namespace ss { - - // an error which occurred in our SQLSRV driver - struct SSException : public core::CoreException { - - SSException() - { - } - }; - - inline void zend_register_resource( _Inout_ zval& rsrc_result, _Inout_ void* rsrc_pointer, _In_ int rsrc_type, _In_opt_ const char* rsrc_name TSRMLS_DC) - { - int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); - CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE, - rsrc_name ) { - throw ss::SSException(); - } - Z_TYPE_INFO(rsrc_result) = IS_RESOURCE_EX; - } -} // namespace ss - -//********************************************************************************************************************************* -// Utility Functions -//********************************************************************************************************************************* - -// generic function used to validate parameters to a PHP function. -// Register an invalid parameter error and returns NULL when parameters don't match the spec given. -template -inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, _In_ char const* param_spec, _In_ const char* calling_func, _In_ size_t param_count, ... ) -{ - SQLSRV_UNUSED( return_value ); - - zval* rsrc; - H* h; - - // reset the errors from the previous API call - reset_errors( TSRMLS_C ); - - if( ZEND_NUM_ARGS() > param_count + 1 ) { - DIE( "Param count and argument count don't match." ); - return NULL; // for static analysis tools - } - - try { - - if( param_count > 6 ) { - DIE( "Param count cannot exceed 6" ); - return NULL; // for static analysis tools - } - - void* arr[6]; - va_list vaList; - va_start(vaList, param_count); //set the pointer to first argument - - for(size_t i = 0; i < param_count; ++i) { - - arr[i] = va_arg(vaList, void*); - } - - va_end(vaList); - - int result = SUCCESS; - - // dummy context to pass to the error handler - sqlsrv_context error_ctx( 0, ss_error_handler, NULL ); - error_ctx.set_func( calling_func ); - - switch( param_count ) { - - case 0: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc ); - break; - - case 1: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0] ); - break; - - case 2: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1] ); - break; - - case 3: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2] ); - break; - - case 4: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3] ); - break; - - case 5: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3], arr[4] ); - break; - - case 6: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], - arr[1], arr[2], arr[3], arr[4], arr[5] ); - break; - - default: - { - THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ); - break; - } - } - - CHECK_CUSTOM_ERROR(( result == FAILURE ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { - - throw ss::SSException(); - } - - // get the resource registered - h = static_cast( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, H::resource_name, H::descriptor )); - - CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { - - throw ss::SSException(); - } - - h->set_func( calling_func ); - } - - catch( core::CoreException& ) { - - return NULL; - } - catch ( ... ) { - - DIE( "%1!s!: Unknown exception caught in process_params.", calling_func ); - } - - return h; -} - #endif /* PHP_SQLSRV_H */ diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h new file mode 100644 index 000000000..d54e0d54e --- /dev/null +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -0,0 +1,468 @@ +#ifndef PHP_SQLSRV_INT_H +#define PHP_SQLSRV_INT_H + +//--------------------------------------------------------------------------------------------------------------------------------- +// File: php_sqlsrv_int.h +// +// Contents: Internal declarations for the extension +// +// Comments: Also contains "internal" declarations shared across source files. +// +// Microsoft Drivers 5.5 for PHP for SQL Server +// Copyright(c) Microsoft Corporation +// All rights reserved. +// MIT License +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//--------------------------------------------------------------------------------------------------------------------------------- + +#include "core_sqlsrv.h" +#include "version.h" + +//********************************************************************************************************************************* +// Global variables +//********************************************************************************************************************************* + +// INI settings and constants +// (these are defined as macros to allow concatenation as we do below) +#define INI_WARNINGS_RETURN_AS_ERRORS "WarningsReturnAsErrors" +#define INI_LOG_SEVERITY "LogSeverity" +#define INI_LOG_SUBSYSTEMS "LogSubsystems" +#define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize" +#define INI_PREFIX "sqlsrv." + +PHP_INI_BEGIN() + STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors, + zend_sqlsrv_globals, sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SEVERITY, "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_sqlsrv_globals, + sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_LOG_SUBSYSTEMS, "0", PHP_INI_ALL, OnUpdateLong, log_subsystems, zend_sqlsrv_globals, + sqlsrv_globals ) + STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit, + zend_sqlsrv_globals, sqlsrv_globals ) +PHP_INI_END() + + +//********************************************************************************************************************************* +// Initialization Functions +//********************************************************************************************************************************* + +// module global variables (initialized in minit and freed in mshutdown) +extern HashTable* g_ss_errors_ht; +extern HashTable* g_ss_encodings_ht; +extern HashTable* g_ss_warnings_to_ignore_ht; + +extern HMODULE g_sqlsrv_hmodule; // used for getting the version information + +// henv context for creating connections +extern sqlsrv_context* g_ss_henv_cp; +extern sqlsrv_context* g_ss_henv_ncp; + + +//********************************************************************************************************************************* +// Connection +//********************************************************************************************************************************* + +struct ss_sqlsrv_conn : sqlsrv_conn +{ + HashTable* stmts; + bool date_as_string; + bool format_decimals; // flag set to turn on formatting for values of decimal / numeric types + short decimal_places; // number of decimal digits to show in a result set unless format_numbers is false + bool in_transaction; // flag set when inside a transaction and used for checking validity of tran API calls + + // static variables used in process_params + static const char* resource_name; + static int descriptor; + + // initialize with default values + ss_sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC ) : + sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), + stmts( NULL ), + date_as_string( false ), + format_decimals( false ), + decimal_places( NO_CHANGE_DECIMAL_PLACES ), + in_transaction( false ) + { + } +}; + +// resource destructor +void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ); + + +//********************************************************************************************************************************* +// Statement +//********************************************************************************************************************************* + +// holds the field names for reuse by sqlsrv_fetch_array/object as keys +struct sqlsrv_fetch_field_name { + char* name; + SQLLEN len; +}; + +struct stmt_option_ss_scrollable : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); +}; + +// This object inherits and overrides the callbacks necessary +struct ss_sqlsrv_stmt : public sqlsrv_stmt { + + ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv TSRMLS_DC ); + + virtual ~ss_sqlsrv_stmt( void ); + + void new_result_set( TSRMLS_D ); + + // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants + sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); + + bool prepared; // whether the statement has been prepared yet (used for error messages) + zend_ulong conn_index; // index into the connection hash that contains this statement structure + zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute + sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys + int fetch_fields_count; + + // static variables used in process_params + static const char* resource_name; + static int descriptor; + +}; + +// holds the field names for reuse by sqlsrv_fetch_array/object as keys +struct sqlsrv_fetch_field { + char* name; + unsigned int len; +}; + +// holds the stream param and the encoding that it was assigned +struct sqlsrv_stream_encoding { + zval* stream_z; + unsigned int encoding; + + sqlsrv_stream_encoding( _In_ zval* str_z, _In_ unsigned int enc ) : + stream_z( str_z ), encoding( enc ) + { + } +}; + +// resource destructor +void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ); + +// "internal" statement functions shared by functions in conn.cpp and stmt.cpp +void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ); +bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function + TSRMLS_DC ); +void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC ); +void free_stmt_resource( _Inout_ zval* stmt_z TSRMLS_DC ); + + +//********************************************************************************************************************************* +// Errors +//********************************************************************************************************************************* + +// represents the mapping between an error_code and the corresponding error message. +struct ss_error { + + unsigned int error_code; + sqlsrv_error_const sqlsrv_error; +}; + +// List of all driver specific error codes. +enum SS_ERROR_CODES { + + SS_SQLSRV_ERROR_ALREADY_IN_TXN = SQLSRV_ERROR_DRIVER_SPECIFIC, + SS_SQLSRV_ERROR_NOT_IN_TXN, + SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, + SS_SQLSRV_ERROR_REGISTER_RESOURCE, + SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY, + SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED, + SS_SQLSRV_ERROR_INVALID_FETCH_STYLE, + SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, + SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, + SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, + SS_SQLSRV_ERROR_ZEND_BAD_CLASS, + SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE, + SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE, + SS_SQLSRV_ERROR_INVALID_OPTION, + SS_SQLSRV_ERROR_PARAM_INVALID_INDEX, + SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, + SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, + SS_SQLSRV_ERROR_VAR_REQUIRED, + SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, + SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, + SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE, + SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, + SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, + SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED +}; + +extern ss_error SS_ERRORS[]; + +bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool warning TSRMLS_DC, _In_opt_ va_list* print_args ); + +// convert from the default encoding specified by the "CharacterSet" +// connection option to UTF-16. mbcs_len and utf16_len are sizes in +// bytes. The return is the number of UTF-16 characters in the string +// returned in utf16_out_string. +unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, + _In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer(mbcs_in_string) wchar_t* utf16_out_string, + _In_ unsigned int utf16_len ); +// create a wide char string from the passed in mbcs string. NULL is returned if the string +// could not be created. No error is posted by this function. utf16_len is the number of +// wchar_t characters, not the number of bytes. +SQLWCHAR* utf16_string_from_mbcs_string( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, + _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); + +// *** internal error macros and functions *** +bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, + sqlsrv_error const* ssphp TSRMLS_DC, ... ); +void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, + sqlsrv_error const* ssphp TSRMLS_DC, ... ); +void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); + +// release current error lists and set to NULL +inline void reset_errors( TSRMLS_D ) +{ + if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) { + DIE( "sqlsrv_errors contains an invalid type" ); + } + if( Z_TYPE( SQLSRV_G( warnings )) != IS_ARRAY && Z_TYPE( SQLSRV_G( warnings )) != IS_NULL ) { + DIE( "sqlsrv_warnings contains an invalid type" ); + } + + if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( SQLSRV_G( errors ))); + FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( errors ))); + } + if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY ) { + zend_hash_destroy( Z_ARRVAL( SQLSRV_G( warnings ))); + FREE_HASHTABLE( Z_ARRVAL( SQLSRV_G( warnings ))); + } + + ZVAL_NULL( &SQLSRV_G( errors )); + ZVAL_NULL( &SQLSRV_G( warnings )); +} + +#define THROW_SS_ERROR( ctx, error_code, ... ) \ + (void)call_error_handler( ctx, error_code TSRMLS_CC, false /*warning*/, ## __VA_ARGS__ ); \ + throw ss::SSException(); + + +class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr > { + +public: + + sqlsrv_context_auto_ptr( void ) : + sqlsrv_auto_ptr( NULL ) + { + } + + sqlsrv_context_auto_ptr( _Inout_opt_ const sqlsrv_context_auto_ptr& src ) : + sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >( src ) + { + } + + // free the original pointer and assign a new pointer. Use NULL to simply free the pointer. + void reset( _In_opt_ sqlsrv_context* ptr = NULL ) + { + if( _ptr ) { + _ptr->~sqlsrv_context(); + sqlsrv_free( (void*) _ptr ); + } + _ptr = ptr; + } + + sqlsrv_context* operator=( _In_opt_ sqlsrv_context* ptr ) + { + return sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_context_auto_ptr >::operator=( ptr ); + } + + void operator=( _Inout_opt_ sqlsrv_context_auto_ptr& src ) + { + sqlsrv_context* p = src.get(); + src.transferred(); + this->_ptr = p; + } +}; + + +//********************************************************************************************************************************* +// Logging +//********************************************************************************************************************************* + +#define LOG_FUNCTION( function_name ) \ + const char* _FN_ = function_name; \ + SQLSRV_G( current_subsystem ) = current_log_subsystem; \ + LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); + +#define SET_FUNCTION_NAME( context ) \ +{ \ + (context).set_func( _FN_ ); \ +} + +// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro +void ss_sqlsrv_log( _In_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); + +// subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise. +enum logging_subsystems { + LOG_INIT = 0x01, + LOG_CONN = 0x02, + LOG_STMT = 0x04, + LOG_UTIL = 0x08, + LOG_ALL = -1, +}; + + +//********************************************************************************************************************************* +// Common function wrappers +// have to place this namespace before the utility functions +// otherwise can't compile in Linux because 'ss' not defined +//********************************************************************************************************************************* + +namespace ss { + + // an error which occurred in our SQLSRV driver + struct SSException : public core::CoreException { + + SSException() + { + } + }; + + inline void zend_register_resource( _Inout_ zval& rsrc_result, _Inout_ void* rsrc_pointer, _In_ int rsrc_type, _In_opt_ const char* rsrc_name TSRMLS_DC) + { + int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); + CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE, + rsrc_name ) { + throw ss::SSException(); + } + Z_TYPE_INFO(rsrc_result) = IS_RESOURCE_EX; + } +} // namespace ss + + +//********************************************************************************************************************************* +// Utility Functions +//********************************************************************************************************************************* + +// generic function used to validate parameters to a PHP function. +// Register an invalid parameter error and returns NULL when parameters don't match the spec given. +template +inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, _In_ char const* param_spec, _In_ const char* calling_func, _In_ size_t param_count, ... ) +{ + SQLSRV_UNUSED( return_value ); + + zval* rsrc; + H* h; + + // reset the errors from the previous API call + reset_errors( TSRMLS_C ); + + if( ZEND_NUM_ARGS() > param_count + 1 ) { + DIE( "Param count and argument count don't match." ); + return NULL; // for static analysis tools + } + + try { + + if( param_count > 6 ) { + DIE( "Param count cannot exceed 6" ); + return NULL; // for static analysis tools + } + + void* arr[6]; + va_list vaList; + va_start(vaList, param_count); //set the pointer to first argument + + for(size_t i = 0; i < param_count; ++i) { + + arr[i] = va_arg(vaList, void*); + } + + va_end(vaList); + + int result = SUCCESS; + + // dummy context to pass to the error handler + sqlsrv_context error_ctx( 0, ss_error_handler, NULL ); + error_ctx.set_func( calling_func ); + + switch( param_count ) { + + case 0: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc ); + break; + + case 1: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0] ); + break; + + case 2: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1] ); + break; + + case 3: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2] ); + break; + + case 4: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3] ); + break; + + case 5: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3], arr[4] ); + break; + + case 6: + result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + arr[1], arr[2], arr[3], arr[4], arr[5] ); + break; + + default: + { + THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ); + break; + } + } + + CHECK_CUSTOM_ERROR(( result == FAILURE ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { + + throw ss::SSException(); + } + + // get the resource registered + h = static_cast( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, H::resource_name, H::descriptor )); + + CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { + + throw ss::SSException(); + } + + h->set_func( calling_func ); + } + + catch( core::CoreException& ) { + + return NULL; + } + catch ( ... ) { + + DIE( "%1!s!: Unknown exception caught in process_params.", calling_func ); + } + + return h; +} + +#endif /* PHP_SQLSRV_INT_H */ diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 1f0357acc..3807027e6 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -18,7 +18,12 @@ //--------------------------------------------------------------------------------------------------------------------------------- // *** header files *** -#include "php_sqlsrv.h" +extern "C" { + #include "php_sqlsrv.h" +} + +#include "php_sqlsrv_int.h" + #ifdef _WIN32 #include #endif // _WIN32 diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index d4be03f5b..21be09780 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -19,7 +19,11 @@ // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- -#include "php_sqlsrv.h" +extern "C" { + #include "php_sqlsrv.h" +} + +#include "php_sqlsrv_int.h" namespace { From 5801edd5c6f4f20d1088fd14a97a869b01fb67f6 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 4 Jan 2019 12:53:35 -0800 Subject: [PATCH 083/249] Dropped dbname variable and set QUOTED_IDENTIFIER to ON (#911) --- test/functional/setup/168256.sql | 3 -- test/functional/setup/cd_info.sql | 5 +-- test/functional/setup/cleanup_dbs.py | 7 ++-- test/functional/setup/create_db.sql | 18 +++------- test/functional/setup/drop_db.sql | 9 +---- test/functional/setup/exec_sql_scripts.py | 39 +++++++------------- test/functional/setup/setup_dbs.py | 43 ++++++++++------------- test/functional/setup/test_types.sql | 31 ++++++++-------- test/functional/setup/tracks.sql | 3 -- 9 files changed, 54 insertions(+), 104 deletions(-) diff --git a/test/functional/setup/168256.sql b/test/functional/setup/168256.sql index 82dbe487e..f4484c34f 100644 --- a/test/functional/setup/168256.sql +++ b/test/functional/setup/168256.sql @@ -1,6 +1,3 @@ -USE $(dbname) -GO - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[168256]') AND type in (N'U')) diff --git a/test/functional/setup/cd_info.sql b/test/functional/setup/cd_info.sql index 40d3bb048..96240acf9 100644 --- a/test/functional/setup/cd_info.sql +++ b/test/functional/setup/cd_info.sql @@ -1,11 +1,8 @@ -USE $(dbname) -GO - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[tracks]') AND type in (N'U')) BEGIN -ALTER TABLE $(dbname)..[tracks] DROP CONSTRAINT [FK__tracks__asin__7F60ED59] +ALTER TABLE [tracks] DROP CONSTRAINT [FK__tracks__asin__7F60ED59] END GO diff --git a/test/functional/setup/cleanup_dbs.py b/test/functional/setup/cleanup_dbs.py index 86406303b..db78ea032 100644 --- a/test/functional/setup/cleanup_dbs.py +++ b/test/functional/setup/cleanup_dbs.py @@ -3,10 +3,8 @@ import os import sys -import subprocess import platform import argparse -from subprocess import Popen, PIPE from exec_sql_scripts import * if __name__ == '__main__': @@ -25,8 +23,9 @@ sys.exit(1) conn_options = ' -S ' + server + ' -U ' + uid + ' -P ' + pwd + ' ' - - executeSQLscript( os.path.join( os.path.dirname(os.path.realpath(__file__)), 'drop_db.sql'), conn_options, args.DBNAME) + + sql_script = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'drop_db.sql'); + manageTestDB(sql_script, conn_options, args.DBNAME) # if Windows, remove self signed certificate using ps command if platform.system() == 'Windows': diff --git a/test/functional/setup/create_db.sql b/test/functional/setup/create_db.sql index d8dee7acf..2c13bc32d 100644 --- a/test/functional/setup/create_db.sql +++ b/test/functional/setup/create_db.sql @@ -1,13 +1,5 @@ -USE [master] -GO - -IF EXISTS (SELECT name FROM sys.databases WHERE name = '$(dbname)' ) - -BEGIN -DROP DATABASE $(dbname) -END - -CREATE DATABASE $(dbname) - -GO - +IF EXISTS (SELECT name FROM sys.databases WHERE name = 'TEST_DB' ) DROP DATABASE TEST_DB + +CREATE DATABASE TEST_DB +GO + diff --git a/test/functional/setup/drop_db.sql b/test/functional/setup/drop_db.sql index d45743f55..b31a055f2 100644 --- a/test/functional/setup/drop_db.sql +++ b/test/functional/setup/drop_db.sql @@ -1,8 +1 @@ -USE [master] -GO - -IF EXISTS (SELECT name FROM sys.databases WHERE name = '$(dbname)' ) - -BEGIN -DROP DATABASE $(dbname) -END +IF EXISTS (SELECT name FROM sys.databases WHERE name = 'TEST_DB' ) DROP DATABASE TEST_DB diff --git a/test/functional/setup/exec_sql_scripts.py b/test/functional/setup/exec_sql_scripts.py index 808955889..0ab264343 100644 --- a/test/functional/setup/exec_sql_scripts.py +++ b/test/functional/setup/exec_sql_scripts.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 -# contains helper methods +# contains helper methods import os -import sys import subprocess -import platform -import argparse from subprocess import Popen, PIPE def executeCommmand(inst_command): @@ -15,29 +12,17 @@ def executeCommmand(inst_command): print (oo) def executeSQLscript(sqlfile, conn_options, dbname): - if platform.system() == 'Windows': - executeSQLscriptWindows(sqlfile, conn_options, dbname) - elif platform.system() == 'Linux' or platform.system() == 'Darwin': - executeSQLscriptUnix(sqlfile, conn_options, dbname) - -def executeSQLscriptWindows(sqlfile, conn_options, dbname): - inst_command = 'sqlcmd ' + conn_options + ' -i ' + sqlfile + ' -v dbname =' + dbname + inst_command = 'sqlcmd -I ' + conn_options + ' -i ' + sqlfile + ' -d ' + dbname executeCommmand(inst_command) -def executeSQLscriptUnix(sqlfile, conn_options, dbname): - # This is a workaround because sqlcmd in Unix does not support -v option for variables. - # It inserts setvar dbname into the beginning of a temp .sql file - tmpFileName = sqlfile[0:-4] + '_tmp.sql' - redirect_string = '(echo :setvar dbname {0}) > {2}; cat {1} >> {2}; ' - sqlcmd = 'sqlcmd ' + conn_options + ' -i ' + tmpFileName +def manageTestDB(sqlfile, conn_options, dbname): + tmp_sql_file = 'test_db_tmp.sql' + if os.path.exists(tmp_sql_file): + os.remove(tmp_sql_file) + with open(sqlfile, 'r') as infile: + script = infile.read().replace('TEST_DB', dbname) + with open(tmp_sql_file, 'w') as outfile: + outfile.write(script) - # Execute a simple query via sqlcmd: without this step, the next step fails in travis CI - simple_cmd = 'sqlcmd ' + conn_options + ' -Q \"select @@Version\" ' - executeCommmand(simple_cmd) - - # inst_command = redirect_string.format(dbname, sqlfile, tmpFileName) + sqlcmd - inst_command = redirect_string.format(dbname, sqlfile, tmpFileName) - executeCommmand(inst_command) - executeCommmand(sqlcmd) - - os.remove(tmpFileName) + executeSQLscript(tmp_sql_file, conn_options, 'master') + os.remove(tmp_sql_file) diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 8b0d6be5b..58900526d 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -1,21 +1,15 @@ #!/usr/bin/env python3 # py setup_dbs.py -dbname -azure -# OR -# py setup_dbs.py -dbname +# OR +# py setup_dbs.py -dbname import os import sys -import subprocess import platform import argparse -from subprocess import Popen, PIPE from exec_sql_scripts import * def setupTestDatabase(conn_options, dbname, azure): sqlFiles = ['test_types.sql', '168256.sql', 'cd_info.sql', 'tracks.sql'] - - # for Azure, must specify the database for the sql scripts to work - if (azure.lower() == 'yes'): - conn_options += ' -d ' + dbname for sqlFile in sqlFiles: executeSQLscript(sqlFile, conn_options, dbname) @@ -28,29 +22,29 @@ def populateTables(conn_options, dbname): executeBulkCopy(conn_options, dbname, '168256', '168256') def executeBulkCopy(conn_options, dbname, tblname, datafile): - redirect_string = 'bcp {0}..[{1}] in {2}.dat -f {2}.fmt ' - inst_command = redirect_string.format(dbname, tblname, datafile) + conn_options + redirect_string = 'bcp {0}..{1} in {2}.dat -f {2}.fmt -q' + inst_command = redirect_string.format(dbname, tblname, datafile) + conn_options executeCommmand(inst_command) - + def setupAE(conn_options, dbname): if (platform.system() == 'Windows'): # import self signed certificate inst_command = "certutil -user -p '' -importPFX My PHPcert.pfx NoRoot" executeCommmand(inst_command) # create Column Master Key and Column Encryption Key - script_command = 'sqlcmd ' + conn_options + ' -i ae_keys.sql -d ' + dbname + script_command = 'sqlcmd -I ' + conn_options + ' -i ae_keys.sql -d ' + dbname executeCommmand(script_command) - + if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-dbname', '--DBNAME', required=True) parser.add_argument('-azure', '--AZURE', required=False, default='no') args = parser.parse_args() - - try: - server = os.environ['TEST_PHP_SQL_SERVER'] - uid = os.environ['TEST_PHP_SQL_UID'] - pwd = os.environ['TEST_PHP_SQL_PWD'] + + try: + server = os.environ['TEST_PHP_SQL_SERVER'] + uid = os.environ['TEST_PHP_SQL_UID'] + pwd = os.environ['TEST_PHP_SQL_PWD'] except : print("TEST_PHP_SQL_SERVER environment variable must be set to the name of the server to use") print("TEST_PHP_SQL_UID environment variable must be set to the name of the user to authenticate with") @@ -59,18 +53,17 @@ def setupAE(conn_options, dbname): current_working_dir=os.getcwd() os.chdir(os.path.dirname(os.path.realpath(__file__))) - conn_options = ' -S ' + server + ' -U ' + uid + ' -P ' + pwd + ' ' - + conn_options = ' -S ' + server + ' -U ' + uid + ' -P ' + pwd + ' ' + # In Azure, assume an empty test database has been created using Azure portal if (args.AZURE.lower() == 'no'): - executeSQLscript('create_db.sql', conn_options, args.DBNAME) + manageTestDB('create_db.sql', conn_options, args.DBNAME) # create tables in the new database - setupTestDatabase(conn_options, args.DBNAME, args.AZURE) + setupTestDatabase(conn_options, args.DBNAME, args.AZURE) # populate these tables populateTables(conn_options, args.DBNAME) # setup AE (certificate, column master key and column encryption key) setupAE(conn_options, args.DBNAME) - - os.chdir(current_working_dir) - + + os.chdir(current_working_dir) \ No newline at end of file diff --git a/test/functional/setup/test_types.sql b/test/functional/setup/test_types.sql index eef114de5..6c5afb9b5 100644 --- a/test/functional/setup/test_types.sql +++ b/test/functional/setup/test_types.sql @@ -1,28 +1,25 @@ -USE $(dbname) -GO - CREATE TABLE [test_types] ([bigint_type] BIGINT null, - [int_type] INT null, - [smallint_type] SMALLINT null, - [tinyint_type] TINYINT null, - [bit_type] BIT null, - [decimal_type] DECIMAL(38,0) null, - [money_type] MONEY null, - [smallmoney_type] SMALLMONEY null, - [float_type] FLOAT(53) null, - [real_type] REAL null, - [datetime_type] DATETIME null, - [smalldatetime_type] SMALLDATETIME null ); + [int_type] INT null, + [smallint_type] SMALLINT null, + [tinyint_type] TINYINT null, + [bit_type] BIT null, + [decimal_type] DECIMAL(38,0) null, + [money_type] MONEY null, + [smallmoney_type] SMALLMONEY null, + [float_type] FLOAT(53) null, + [real_type] REAL null, + [datetime_type] DATETIME null, + [smalldatetime_type] SMALLDATETIME null ); GO -- maximum test -INSERT INTO $(dbname)..[test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) +INSERT INTO [test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) VALUES (9223372036854775807, 2147483647, 32767, 255, 1, 9999999999999999999999999999999999999, '12/12/1968 16:20', 922337203685477.5807, 214748.3647, 1.79E+308, 1.18E-38 ) -- minimum test -INSERT INTO $(dbname)..[test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) +INSERT INTO [test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) VALUES (-9223372036854775808, -2147483648, -32768, 0, 0, -10000000000000000000000000000000000001,'12/12/1968 16:20', -922337203685477.5808, -214748.3648, -1.79E+308, -1.18E-38 ) -- zero test -INSERT INTO $(dbname)..[test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) +INSERT INTO [test_types] (bigint_type, int_type, smallint_type, tinyint_type, bit_type, decimal_type, datetime_type, money_type, smallmoney_type, float_type, real_type) VALUES (0, 0, 0, 0, 0, 0, '12/12/1968 16:20', 0, 0, 0, 0) GO diff --git a/test/functional/setup/tracks.sql b/test/functional/setup/tracks.sql index 2ba3b793a..57bd914f9 100644 --- a/test/functional/setup/tracks.sql +++ b/test/functional/setup/tracks.sql @@ -1,6 +1,3 @@ -USE $(dbname) -GO - IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[tracks]') AND type in (N'U')) From 25d6812087d2bc08091b030298edc9a451d90965 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 7 Jan 2019 15:36:59 -0800 Subject: [PATCH 084/249] Skipped the non-applicables tests against Azure Data Warehouse (#913) --- .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 27 +++++++ .../pdo_sqlsrv/PDO81_MemoryCheck.phpt | 2 +- ...ment_bindParam_output_emulate_prepare.phpt | 1 + .../pdo_sqlsrv/pdo_574_next_rowset.phpt | 1 + .../pdo_azure_ad_authentication.phpt | 67 +++++++---------- .../pdo_sqlsrv/pdo_prepare_attribute.phpt | 1 + .../functional/pdo_sqlsrv/skipif_azure_dw.inc | 10 +++ test/functional/sqlsrv/0013.phpt | 2 +- test/functional/sqlsrv/0022.phpt | 2 +- test/functional/sqlsrv/53_0021.phpt | 2 +- test/functional/sqlsrv/MsCommon.inc | 22 ++++++ test/functional/sqlsrv/TC81_MemoryCheck.phpt | 4 +- .../sqlsrv/bugfix_dataCorruption.phpt | 2 +- test/functional/sqlsrv/fix_test_168256-2.phpt | 2 +- test/functional/sqlsrv/fix_test_168256.phpt | 2 +- test/functional/sqlsrv/fix_test_182741.phpt | 1 + test/functional/sqlsrv/skipif_azure_dw.inc | 12 +++ .../sqlsrv/sqlsrv_574_next_result.phpt | 1 + .../sqlsrv_azure_ad_authentication.phpt | 69 +++++++----------- .../functional/sqlsrv/sqlsrv_data_to_str.phpt | 2 +- test/functional/sqlsrv/sqlsrv_get_field.phpt | 2 +- test/functional/sqlsrv/sqlsrv_metadata.phpt | 2 +- test/functional/sqlsrv/sqlsrv_readStream.phpt | 2 +- .../sqlsrv/srv_223_sqlsrv_fetch_absolute.phpt | 3 +- .../sqlsrv/test_closeConnection.phpt | 2 +- test/functional/sqlsrv/test_fetch.phpt | 2 +- test/functional/sqlsrv/test_fetch2.phpt | 2 +- test/functional/sqlsrv/test_insert_null.phpt | 2 +- .../sqlsrv/test_insert_nullStr.phpt | Bin 2663 -> 2672 bytes test/functional/sqlsrv/test_largeData.phpt | 2 +- test/functional/sqlsrv/test_newError_msg.phpt | 2 +- .../sqlsrv/test_sqlsrv_phptype_stream.phpt | Bin 10374 -> 10383 bytes test/functional/sqlsrv/test_stream.phpt | 2 +- .../sqlsrv/test_warning_errors2.phpt | 2 +- 34 files changed, 149 insertions(+), 108 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/skipif_azure_dw.inc create mode 100644 test/functional/sqlsrv/skipif_azure_dw.inc diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index cf2ff605c..d327d8599 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -624,6 +624,33 @@ function IsDaasMode() return ($daasMode ? true : false); } +function isAzureDW() +{ + // Check if running Azure Data Warehouse + // For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql + try { + $conn = connect(); + + // Check if running Azure Data Warehouse + // For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql + $tsql = "SELECT SERVERPROPERTY ('edition'), SERVERPROPERTY ('EngineEdition')"; + $stmt = $conn->query($tsql); + + $result = $stmt->fetch(PDO::FETCH_NUM); + $edition = $result[0]; + $engEd = intval($result[1]); + + if ($edition == "SQL Azure" && $engEd == 6) { + return true; + } else { + return false; + } + } catch (Exception $e) { + echo $e->getMessage(); + die("skip Failed to connect or could not fetch edition info."); + } +} + function FatalError($errorMsg) { if (!IsPdoMode()) { diff --git a/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt b/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt index 9fc7cd955..5513290b6 100644 --- a/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt +++ b/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt @@ -6,7 +6,7 @@ emalloc (which only allocate memory in the memory space allocated for the PHP pr --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- getMessage() ); + print_r($e->getMessage()); echo "\n"; } -$stmt = $conn->query( "SELECT count(*) FROM cd_info" ); -if ( $stmt === false ) -{ +// For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql +$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); +$stmt = $conn->query("SELECT SERVERPROPERTY('EngineEdition')"); +if ($stmt === false) { echo "Query failed.\n"; -} -else -{ - $result = $stmt->fetch(); - var_dump( $result ); +} else { + $result = $stmt->fetch(PDO::FETCH_NUM); + $edition = $result[0]; + var_dump($edition); } -$conn = null; +unset($conn); /////////////////////////////////////////////////////////////////////////////////////////// // Test Azure AD with integrated authentication. This should fail because @@ -43,16 +40,13 @@ $conn = null; // $connectionInfo = "Authentication = ActiveDirectoryIntegrated; TrustServerCertificate = true;"; -try -{ - $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo" ); +try { + $conn = new PDO("sqlsrv:server = $server ; $connectionInfo"); echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n"; - $conn = null; -} -catch( PDOException $e ) -{ + unset($conn); +} catch (PDOException $e) { echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n"; - print_r( $e->getMessage() ); + print_r($e->getMessage()); echo "\n"; } @@ -65,35 +59,24 @@ $azureDatabase = $adDatabase; $azureUsername = $adUser; $azurePassword = $adPassword; -if ($azureServer != 'TARGET_AD_SERVER') -{ +if ($azureServer != 'TARGET_AD_SERVER') { $connectionInfo = "Authentication = ActiveDirectoryPassword; TrustServerCertificate = false"; - try - { - $conn = new PDO( "sqlsrv:server = $azureServer ; $connectionInfo", $azureUsername, $azurePassword ); + try { + $conn = new PDO("sqlsrv:server = $azureServer ; $connectionInfo", $azureUsername, $azurePassword); echo "Connected successfully with Authentication=ActiveDirectoryPassword.\n"; - } - catch( PDOException $e ) - { + } catch (PDOException $e) { echo "Could not connect with ActiveDirectoryPassword.\n"; - print_r( $e->getMessage() ); + print_r($e->getMessage()); echo "\n"; } -} -else -{ +} else { echo "Not testing with Authentication=ActiveDirectoryPassword.\n"; } ?> --EXPECTF-- Connected successfully with Authentication=SqlPassword. -array(2) { - [""]=> - string(1) "7" - [0]=> - string(1) "7" -} +string(1) "%d" Could not connect with Authentication=ActiveDirectoryIntegrated. SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. %s with Authentication=ActiveDirectoryPassword. diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_attribute.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_attribute.phpt index d0da03e1d..01774f081 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_attribute.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_attribute.phpt @@ -4,6 +4,7 @@ Test PDO::prepare() with PDO::ATTR_EMULATE_PREPARES. PHPT_EXEC=true --SKIPIF-- + --FILE-- \ No newline at end of file diff --git a/test/functional/sqlsrv/0013.phpt b/test/functional/sqlsrv/0013.phpt index cb2236dfd..6ccb46426 100644 --- a/test/functional/sqlsrv/0013.phpt +++ b/test/functional/sqlsrv/0013.phpt @@ -1,7 +1,7 @@ --TEST-- A test for a simple query --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- + + --FILE-- + --FILE-- + --FILE-- + --FILE-- + --FILE-- \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_574_next_result.phpt b/test/functional/sqlsrv/sqlsrv_574_next_result.phpt index c21f71787..82cb4b4e9 100644 --- a/test/functional/sqlsrv/sqlsrv_574_next_result.phpt +++ b/test/functional/sqlsrv/sqlsrv_574_next_result.phpt @@ -6,6 +6,7 @@ Verifies the functionality of sqlsrv_next_result PHPT_EXEC=true --SKIPIF-- + --FILE-- $databaseName, "UID"=>$uid, "PWD"=>$pwd, "Authentication"=>'SqlPassword', "TrustServerCertificate"=>true); -$conn = sqlsrv_connect( $server, $connectionInfo ); +$conn = sqlsrv_connect($server, $connectionInfo); -if( $conn === false ) -{ +if ($conn === false) { echo "Could not connect with Authentication=SqlPassword.\n"; - var_dump( sqlsrv_errors() ); -} -else -{ + var_dump(sqlsrv_errors()); +} else { echo "Connected successfully with Authentication=SqlPassword.\n"; } -$stmt = sqlsrv_query( $conn, "SELECT count(*) FROM cd_info" ); -if ( $stmt === false ) -{ +// For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql +$stmt = sqlsrv_query($conn, "SELECT SERVERPROPERTY('EngineEdition')"); +if (sqlsrv_fetch($stmt)) { + $edition = sqlsrv_get_field($stmt, 0); + var_dump($edition); +} else { echo "Query failed.\n"; } -else -{ - $result = sqlsrv_fetch_array( $stmt ); - var_dump( $result ); -} -sqlsrv_free_stmt( $stmt ); -sqlsrv_close( $conn ); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); /////////////////////////////////////////////////////////////////////////////////////////// // Test Azure AD with integrated authentication. This should fail because @@ -45,17 +40,14 @@ sqlsrv_close( $conn ); // $connectionInfo = array( "Authentication"=>"ActiveDirectoryIntegrated", "TrustServerCertificate"=>true ); -$conn = sqlsrv_connect( $server, $connectionInfo ); -if( $conn === false ) -{ +$conn = sqlsrv_connect($server, $connectionInfo); +if ($conn === false) { echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n"; $errors = sqlsrv_errors(); print_r($errors[0]); -} -else -{ +} else { echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n"; - sqlsrv_close( $conn ); + sqlsrv_close($conn); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -67,36 +59,25 @@ $azureDatabase = $adDatabase; $azureUsername = $adUser; $azurePassword = $adPassword; -if ($azureServer != 'TARGET_AD_SERVER') -{ - $connectionInfo = array( "UID"=>$azureUsername, "PWD"=>$azurePassword, +if ($azureServer != 'TARGET_AD_SERVER') { + $connectionInfo = array( "UID"=>$azureUsername, "PWD"=>$azurePassword, "Authentication"=>'ActiveDirectoryPassword', "TrustServerCertificate"=>false ); - $conn = sqlsrv_connect( $azureServer, $connectionInfo ); - if( $conn === false ) - { + $conn = sqlsrv_connect($azureServer, $connectionInfo); + if ($conn === false) { echo "Could not connect with ActiveDirectoryPassword.\n"; - print_r( sqlsrv_errors() ); - } - else - { + print_r(sqlsrv_errors()); + } else { echo "Connected successfully with Authentication=ActiveDirectoryPassword.\n"; - sqlsrv_close( $conn ); + sqlsrv_close($conn); } -} -else -{ +} else { echo "Not testing with Authentication=ActiveDirectoryPassword.\n"; } ?> --EXPECTF-- Connected successfully with Authentication=SqlPassword. -array(2) { - [0]=> - int(7) - [""]=> - int(7) -} +string(1) "%d" Could not connect with Authentication=ActiveDirectoryIntegrated. Array ( diff --git a/test/functional/sqlsrv/sqlsrv_data_to_str.phpt b/test/functional/sqlsrv/sqlsrv_data_to_str.phpt index 5f456108f..08f42b26b 100644 --- a/test/functional/sqlsrv/sqlsrv_data_to_str.phpt +++ b/test/functional/sqlsrv/sqlsrv_data_to_str.phpt @@ -1,7 +1,7 @@ --TEST-- large types to strings of 1MB size. --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- + --FILE-- SQLSRV_CURSOR_CLIENT_BUFFERED)); if ($stmt === false) { printErrors(); diff --git a/test/functional/sqlsrv/test_closeConnection.phpt b/test/functional/sqlsrv/test_closeConnection.phpt index 24c96e0d1..f796bf801 100644 --- a/test/functional/sqlsrv/test_closeConnection.phpt +++ b/test/functional/sqlsrv/test_closeConnection.phpt @@ -1,7 +1,7 @@ --TEST-- using an already closed connection. --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- + --FILE-- >c%`KE&v->1Rwwa diff --git a/test/functional/sqlsrv/test_largeData.phpt b/test/functional/sqlsrv/test_largeData.phpt index e282df2a7..4fa2d830a 100644 --- a/test/functional/sqlsrv/test_largeData.phpt +++ b/test/functional/sqlsrv/test_largeData.phpt @@ -1,7 +1,7 @@ --TEST-- send a large amount (10MB) using encryption. --SKIPIF-- - + --FILE-- + --FILE-- <^sa%^9CqRa%r9pHjZj+gbwvQ^5zj delta 10 RcmeAVYzv&=y)ndE0{|Em1N#5~ diff --git a/test/functional/sqlsrv/test_stream.phpt b/test/functional/sqlsrv/test_stream.phpt index 3836341de..dee4e72e9 100644 --- a/test/functional/sqlsrv/test_stream.phpt +++ b/test/functional/sqlsrv/test_stream.phpt @@ -14,7 +14,7 @@ Test for stream zombifying. fatalError("Failed to connect."); } - $stmt = sqlsrv_query($conn, "SELECT * FROM [test_streamable_types]"); + $stmt = sqlsrv_query($conn, "SELECT * FROM sys.objects"); $metadata = sqlsrv_field_metadata($stmt); $count = count($metadata); sqlsrv_fetch($stmt); diff --git a/test/functional/sqlsrv/test_warning_errors2.phpt b/test/functional/sqlsrv/test_warning_errors2.phpt index 4145565ac..15353ac6c 100644 --- a/test/functional/sqlsrv/test_warning_errors2.phpt +++ b/test/functional/sqlsrv/test_warning_errors2.phpt @@ -15,7 +15,7 @@ if( $conn === false ) { die( print_r( sqlsrv_errors(), true )); } -$stmt = sqlsrv_prepare( $conn, "SELECT * FROM [cd_info]"); +$stmt = sqlsrv_prepare( $conn, "SELECT * FROM sys.objects"); $result = sqlsrv_field_metadata( $stmt ); if( $result === false ) { From 9a372582f90d6764b677b7a072cb10c937e6c009 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 11 Jan 2019 17:17:45 -0800 Subject: [PATCH 085/249] Support for Managed Identity for Azure resources (#875) --- source/pdo_sqlsrv/pdo_util.cpp | 6 +- source/shared/core_conn.cpp | 47 +++++++- source/shared/core_sqlsrv.h | 2 + source/sqlsrv/util.cpp | 6 +- .../pdo_azure_ad_authentication.phpt | 2 +- .../pdo_azure_ad_managed_identity.phpt | 114 ++++++++++++++++++ .../sqlsrv_azure_ad_authentication.phpt | 4 +- .../sqlsrv_azure_ad_managed_identity.phpt | 88 ++++++++++++++ 8 files changed, 258 insertions(+), 11 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 1e27ea4c4..cf9aa0299 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -383,7 +383,7 @@ pdo_error PDO_ERRORS[] = { }, { PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, - { IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -73, false } + { IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported.", -73, false } }, { SQLSRV_ERROR_CE_DRIVER_REQUIRED, @@ -445,6 +445,10 @@ pdo_error PDO_ERRORS[] = { SQLSRV_ERROR_INVALID_DECIMAL_PLACES, { IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -92, false} }, + { + SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL, + { IMSSP, (SQLCHAR*) "When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted.", -93, false} + }, { UINT_MAX, {} } }; diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 8ec79e0c1..1b0a8b16e 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -730,7 +730,7 @@ bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t if (value_len <= 0) return false; - if( ! stricmp( value, AzureADOptions::AZURE_AUTH_SQL_PASSWORD ) || ! stricmp( value, AzureADOptions::AZURE_AUTH_AD_PASSWORD ) ) { + if (!stricmp(value, AzureADOptions::AZURE_AUTH_SQL_PASSWORD) || !stricmp(value, AzureADOptions::AZURE_AUTH_AD_PASSWORD) || !stricmp(value, AzureADOptions::AZURE_AUTH_AD_MSI)) { return true; } @@ -769,16 +769,18 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou bool mars_mentioned = false; connection_option const* conn_opt; bool access_token_used = false; + bool authentication_option_used = zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION); try { - // First of all, check if access token is specified. If so, check if UID, PWD, Authentication exist + // Since connection options access token and authentication cannot coexist, check if both of them are used. + // If access token is specified, check UID and PWD as well. // No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) { bool invalidOptions = false; // UID and PWD have to be NULLs... throw an exception as long as the user has specified any of them in the connection string, // even if they may be empty strings. Likewise if the keyword Authentication exists - if (uid != NULL || pwd != NULL || zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION)) { + if (uid != NULL || pwd != NULL || authentication_option_used) { invalidOptions = true; } @@ -789,11 +791,44 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou access_token_used = true; } + // Check if Authentication is ActiveDirectoryMSI + // https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview + bool activeDirectoryMSI = false; + if (authentication_option_used) { + zval* auth_option = NULL; + auth_option = zend_hash_index_find(options, SQLSRV_CONN_OPTION_AUTHENTICATION); + + char* option = Z_STRVAL_P(auth_option); + + if (!stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) { + activeDirectoryMSI = true; + + // There are two types of managed identities: + // (1) A system-assigned managed identity: UID must be NULL + // (2) A user-assigned managed identity: UID defined but must not be an empty string + // In both cases, PWD must be NULL + + bool invalid = false; + if (pwd != NULL) { + invalid = true; + } else { + if (uid != NULL && strnlen_s(uid) == 0) { + invalid = true; + } + } + + CHECK_CUSTOM_ERROR(invalid, conn, SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL ) { + throw core::CoreException(); + } + } + } + // Add the server name common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); - - // if uid is not present then we use trusted connection -- but not when access token is used, because they are incompatible - if (!access_token_used) { + + // If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used, + // because they are incompatible + if (!access_token_used && !activeDirectoryMSI) { if (uid == NULL || strnlen_s(uid) == 0) { connection_string += CONNECTION_OPTION_NO_CREDENTIALS; // "Trusted_Connection={Yes};" } diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 400e9ea2f..6149429c2 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -191,6 +191,7 @@ const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; namespace AzureADOptions { const char AZURE_AUTH_SQL_PASSWORD[] = "SqlPassword"; const char AZURE_AUTH_AD_PASSWORD[] = "ActiveDirectoryPassword"; + const char AZURE_AUTH_AD_MSI[] = "ActiveDirectoryMsi"; } // the message returned by ODBC Driver for SQL Server @@ -1777,6 +1778,7 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN, SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, SQLSRV_ERROR_INVALID_DECIMAL_PLACES, + SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL, // Driver specific error codes starts from here. SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 21be09780..591829b2b 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -372,7 +372,7 @@ ss_error SS_ERRORS[] = { }, { SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, - { IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -62, false } + { IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported.", -62, false } }, { SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED, @@ -436,6 +436,10 @@ ss_error SS_ERRORS[] = { SQLSRV_ERROR_INVALID_DECIMAL_PLACES, { IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -117, false} }, + { + SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL, + { IMSSP, (SQLCHAR*) "When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted.", -118, false} + }, // terminate the list of errors/warnings { UINT_MAX, {} } diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt index e2d85dd45..105f63953 100644 --- a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt @@ -78,5 +78,5 @@ if ($azureServer != 'TARGET_AD_SERVER') { Connected successfully with Authentication=SqlPassword. string(1) "%d" Could not connect with Authentication=ActiveDirectoryIntegrated. -SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. +SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported. %s with Authentication=ActiveDirectoryPassword. diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt new file mode 100644 index 000000000..00a0c9507 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt @@ -0,0 +1,114 @@ +--TEST-- +Test some error conditions of Azure AD Managed Identity support +--DESCRIPTION-- +This test expects certain exceptions to be thrown under some conditions. +--SKIPIF-- + +--FILE-- +getMessage(), $expectedError) === false) { + echo "AzureAD Managed Identity test: expected to fail with $msg\n"; + + print_r($exception->getMessage()); + echo "\n"; + } +} + +function connectWithInvalidOptions() +{ + global $server; + + $message = 'AzureAD Managed Identity test: expected to fail with '; + $expectedError = 'When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted'; + + $uid = ''; + $connectionInfo = "Authentication = ActiveDirectoryMsi;"; + $testCase = 'empty UID provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", $uid); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $pwd = ''; + $connectionInfo = "Authentication = ActiveDirectoryMsi;"; + $testCase = 'empty PWD provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $pwd = 'dummy'; + $connectionInfo = "Authentication = ActiveDirectoryMsi;"; + $testCase = 'PWD provided'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo", null, $pwd); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); + + $expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.'; + $connectionInfo = "Authentication = ActiveDirectoryMsi; AccessToken = '123';"; + $testCase = 'AccessToken option'; + try { + $conn = new PDO("sqlsrv:server = $server; $connectionInfo"); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + verifyErrorMessage($e, $expectedError, $testCase); + } + unset($connectionInfo); +} + +function connectInvalidServer() +{ + global $server, $driver, $uid, $pwd; + + try { + $conn = new PDO("sqlsrv:server = $server; driver=$driver;", $uid, $pwd); + + $msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; + $version = explode(".", $msodbcsqlVer); + + if ($version[0] < 17 || $version[1] < 3) { + //skip the rest of this test, which requires ODBC driver 17.3 or above + return; + } + unset($conn); + + // Try connecting to an invalid server, should get an exception from ODBC + $connectionInfo = "Authentication = ActiveDirectoryMsi;"; + $testCase = 'invalidServer'; + try { + $conn = new PDO("sqlsrv:server = invalidServer; $connectionInfo", null, null); + echo $message . $testCase . PHP_EOL; + } catch(PDOException $e) { + // TODO: check the exception message here + } + } catch(PDOException $e) { + print_r($e->getMessage()); + } +} + +require_once('MsSetup.inc'); + +// Test some error conditions +connectWithInvalidOptions(); + +// Make a connection to an invalid server +connectInvalidServer(); + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt index af582dcb7..51f61b276 100644 --- a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt @@ -85,7 +85,7 @@ Array [SQLSTATE] => IMSSP [1] => -62 [code] => -62 - [2] => Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. - [message] => Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported. + [2] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported. + [message] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, or ActiveDirectoryMsi is supported. ) %s with Authentication=ActiveDirectoryPassword. diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt new file mode 100644 index 000000000..644731eb7 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt @@ -0,0 +1,88 @@ +--TEST-- +Test some error conditions of Azure AD Managed Identity support +--DESCRIPTION-- +This test expects certain exceptions to be thrown under some conditions. +--SKIPIF-- + +--FILE-- +"", "Authentication" => "ActiveDirectoryMsi"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty UID provided'); + unset($connectionInfo); + + $connectionInfo = array("PWD"=>"", "Authentication" => "ActiveDirectoryMsi"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'empty PWD provided'); + unset($connectionInfo); + + $connectionInfo = array("PWD"=>"pwd", "Authentication" => "ActiveDirectoryMsi"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'PWD provided'); + unset($connectionInfo); + + $expectedError = 'When using Azure AD Access Token, the connection string must not contain UID, PWD, or Authentication keywords.'; + $connectionInfo = array("Authentication"=>"ActiveDirectoryMsi", "AccessToken" => "123"); + $conn = sqlsrv_connect($server, $connectionInfo); + verifyErrorMessage($conn, $expectedError, 'AccessToken option'); + unset($connectionInfo); +} + +function connectInvalidServer() +{ + global $server, $driver, $userName, $userPassword; + + $connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver); + $conn = sqlsrv_connect($server, $connectionInfo); + if ($conn === false) { + fatalError("Failed to connect in connectInvalidServer."); + } + + $msodbcsqlVer = sqlsrv_client_info($conn)['DriverVer']; + $version = explode(".", $msodbcsqlVer); + + if ($version[0] < 17 || $version[1] < 3) { + //skip the rest of this test, which requires ODBC driver 17.3 or above + return; + } + sqlsrv_close($conn); + + // Try connecting to an invalid server, should get an exception from ODBC + $connectionInfo = array("Authentication"=>"ActiveDirectoryMsi"); + $conn = sqlsrv_connect('invalidServer', $connectionInfo); + if ($conn) { + fatalError("AzureAD Managed Identity test: expected to fail with invalidServer\n"); + } else { + // TODO: check the exception message here, using verifyErrorMessage() + } +} + +// Test some error conditions +connectWithInvalidOptions($server); + +// Make a connection to an invalid server +connectInvalidServer(); + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file From d6c8cc2e15610a1f0fbcaa9a64a5015ac45980b2 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 16 Jan 2019 10:19:01 -0800 Subject: [PATCH 086/249] Changed version 5.6.0 (#918) --- LICENSE | 2 +- source/pdo_sqlsrv/config.m4 | 2 +- source/pdo_sqlsrv/config.w32 | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- source/pdo_sqlsrv/pdo_init.cpp | 2 +- source/pdo_sqlsrv/pdo_parser.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 2 +- source/pdo_sqlsrv/template.rc | 2 +- source/shared/FormattedPrint.cpp | 2 +- source/shared/FormattedPrint.h | 2 +- source/shared/StringFunctions.cpp | 2 +- source/shared/StringFunctions.h | 2 +- source/shared/core_conn.cpp | 2 +- source/shared/core_init.cpp | 2 +- source/shared/core_results.cpp | 2 +- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 2 +- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 2 +- source/shared/globalization.h | 2 +- source/shared/interlockedatomic.h | 2 +- source/shared/interlockedatomic_gcc.h | 2 +- source/shared/interlockedslist.h | 2 +- source/shared/localization.hpp | 2 +- source/shared/localizationimpl.cpp | 2 +- source/shared/msodbcsql.h | 2 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 6 +++--- source/shared/xplat.h | 2 +- source/shared/xplat_intsafe.h | 2 +- source/shared/xplat_winerror.h | 2 +- source/shared/xplat_winnls.h | 2 +- source/sqlsrv/config.m4 | 2 +- source/sqlsrv/config.w32 | 2 +- source/sqlsrv/conn.cpp | 2 +- source/sqlsrv/init.cpp | 2 +- source/sqlsrv/php_sqlsrv.h | 2 +- source/sqlsrv/php_sqlsrv_int.h | 2 +- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 2 +- 45 files changed, 47 insertions(+), 47 deletions(-) diff --git a/LICENSE b/LICENSE index 13fab6115..6fa366d5d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright(c) 2018 Microsoft Corporation +Copyright(c) 2019 Microsoft Corporation All rights reserved. MIT License diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index a1f757ece..68dbea0e3 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.5 for PHP for SQL Server +dnl Microsoft Drivers 5.6 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index 143627483..95bff567b 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index c364e12dc..669bc62b5 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 2f38b0082..bee4cfc52 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 1513c6615..edd333e46 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index c2b0ebd99..cd8c697f1 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index cf9aa0299..e399fda11 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index c001c4802..3e013f95c 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 3db1d99e4..4b50bbc75 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -6,7 +6,7 @@ // // Contents: Internal declarations for the extension // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 7b74950aa..fdbeaa576 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index 0e3beadd1..b664159c5 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -6,7 +6,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index 0c29e81f0..907b16479 100644 --- a/source/shared/FormattedPrint.h +++ b/source/shared/FormattedPrint.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp index 6f494dc4c..6aac5a051 100644 --- a/source/shared/StringFunctions.cpp +++ b/source/shared/StringFunctions.cpp @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.h b/source/shared/StringFunctions.h index bcd250b62..a2bfceaa9 100644 --- a/source/shared/StringFunctions.h +++ b/source/shared/StringFunctions.h @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 1b0a8b16e..3d195fe9c 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index 03b56d4ff..504a145b6 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index f4f00b371..93427bd76 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 6149429c2..886d5e829 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index ffeb3def6..9cac96a53 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 826fcf2ed..d822d4a85 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 4aa2b37fd..515eb38b1 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/globalization.h b/source/shared/globalization.h index f3545de2e..4ddccc52b 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index 953c25804..f46e2b1df 100644 --- a/source/shared/interlockedatomic.h +++ b/source/shared/interlockedatomic.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h index ba5087b52..8a1f3732c 100644 --- a/source/shared/interlockedatomic_gcc.h +++ b/source/shared/interlockedatomic_gcc.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h index 76e2eda8e..cd71452a6 100644 --- a/source/shared/interlockedslist.h +++ b/source/shared/interlockedslist.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, singly // linked list. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index 6328f1fbd..a572ab021 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 38ee64b7e..94f70bb81 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -5,7 +5,7 @@ // Must be included in one c/cpp file per binary // A build error will occur if this inclusion policy is not followed // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index b440a95aa..2392c5e39 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -20,7 +20,7 @@ // pecuniary loss) arising out of the use of or inability to use // this SDK, even if Microsoft has been advised of the possibility // of such damages. -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index b000b640d..053fb199c 100644 --- a/source/shared/sal_def.h +++ b/source/shared/sal_def.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index d035e3666..390d03cc7 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -1,7 +1,7 @@ //--------------------------------------------------------------------------------------------------------------------------------- // File: typedefs_for_linux.h // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/version.h b/source/shared/version.h index 256fb51d9..c7c554813 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -4,7 +4,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -26,12 +26,12 @@ // Increase Minor with backward compatible new functionalities and API changes. // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 -#define SQLVERSION_MINOR 5 +#define SQLVERSION_MINOR 6 #define SQLVERSION_PATCH 0 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 -#define PREVIEW 1 +#define PREVIEW 0 #define SEMVER_PRERELEASE // Semantic versioning build metadata, build meta data is not counted in precedence order. diff --git a/source/shared/xplat.h b/source/shared/xplat.h index bb4888ea2..1b1956391 100644 --- a/source/shared/xplat.h +++ b/source/shared/xplat.h @@ -3,7 +3,7 @@ // // Contents: include for definition of Windows types for non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h index 571c62b2a..91db26523 100644 --- a/source/shared/xplat_intsafe.h +++ b/source/shared/xplat_intsafe.h @@ -4,7 +4,7 @@ // Contents: This module defines helper functions to prevent // integer overflow bugs. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h index 88a97baa0..34e7ffb50 100644 --- a/source/shared/xplat_winerror.h +++ b/source/shared/xplat_winerror.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h index 7bdac15e1..bc81cf58c 100644 --- a/source/shared/xplat_winnls.h +++ b/source/shared/xplat_winnls.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index d78c1a09c..fa0546de0 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.5 for PHP for SQL Server +dnl Microsoft Drivers 5.6 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index d34f98756..5a2477e99 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 72114e3c0..532934f72 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index fd1ab2055..80014524d 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 891cf1256..4de7f5c9a 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index d54e0d54e..c294f465b 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 3807027e6..c366e0305 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index 88eefb55c..bbe471322 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 591829b2b..cec66c3f3 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.5 for PHP for SQL Server +// Microsoft Drivers 5.6 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License From 04f531d81372b4cd804b6504aa1188a4ad43c9c4 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 21 Jan 2019 08:16:25 -0800 Subject: [PATCH 087/249] Initialize hasLoss before passing into Convert function (#919) --- source/shared/localizationimpl.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 94f70bb81..e67bbf226 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -638,7 +638,7 @@ size_t SystemLocale::ToUtf16( UINT srcCodePage, const char * src, SSIZE_T cchSrc return 0; } size_t cchSrcActual = (cchSrc < 0 ? (1+strnlen_s(src)) : cchSrc); - bool hasLoss; + bool hasLoss = false; return cvt.Convert( dest, cchDest, src, cchSrcActual, false, &hasLoss, pErrorCode ); } @@ -664,7 +664,7 @@ size_t SystemLocale::ToUtf16Strict( UINT srcCodePage, const char * src, SSIZE_T return 0; } size_t cchSrcActual = (cchSrc < 0 ? (1+strnlen_s(src)) : cchSrc); - bool hasLoss; + bool hasLoss = false; return cvt.Convert( dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode ); } @@ -952,7 +952,7 @@ size_t SystemLocale::FromUtf16( UINT destCodePage, const WCHAR * src, SSIZE_T cc return 0; } size_t cchSrcActual = (cchSrc < 0 ? (1+mplat_wcslen(src)) : cchSrc); - bool hasLoss; + bool hasLoss = false; return cvt.Convert( dest, cchDest, src, cchSrcActual, false, &hasLoss, pErrorCode ); } @@ -972,7 +972,7 @@ size_t SystemLocale::FromUtf16Strict(UINT destCodePage, const WCHAR * src, SSIZE return 0; } size_t cchSrcActual = (cchSrc < 0 ? (1 + mplat_wcslen(src)) : cchSrc); - bool hasLoss; + bool hasLoss = false; return cvt.Convert(dest, cchDest, src, cchSrcActual, true, &hasLoss, pErrorCode); } From d9b6e054557449319308f620fdb46cc0eba6e1a4 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 25 Jan 2019 15:53:58 -0800 Subject: [PATCH 088/249] Added new tests for setting client buffer size related to issue 228 (#920) --- ...28_setAttribute_clientbuffermaxkbsize.phpt | 32 +++-- ...etConnAttribute_clientbuffermaxkbsize.phpt | 94 +++++++++++++++ .../srv_228_sqlsrv_clientbuffermaxkbsize.phpt | 45 ++++++-- ...8_sqlsrv_clientbuffermaxkbsize_option.phpt | 109 ++++++++++++++++++ 4 files changed, 260 insertions(+), 20 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_228_setConnAttribute_clientbuffermaxkbsize.phpt create mode 100644 test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt b/test/functional/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt index 0e94b1050..fd2517d82 100644 --- a/test/functional/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt +++ b/test/functional/pdo_sqlsrv/pdo_228_setAttribute_clientbuffermaxkbsize.phpt @@ -1,5 +1,7 @@ --TEST-- -sqlsrv_has_rows() using a forward and scrollable cursor +GitHub issue #228 - how max client buffer size affects the fetching of data +--DESCRIPTION-- +A pdo_sqlsrv variation of the example in GitHub issue 228, using PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE the statement attribute. --SKIPIF-- --FILE-- @@ -21,28 +23,38 @@ try { insertRow($conn, $tableName2, array("c1_int" => 990021574, "c2_varchar" => ">vh~Ö.bÃ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÃÃœh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÃö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÃBî@~AZöÃOßC@äoÃuCÃœ,ÃÄa:îäÄÖý:h*ouªuÃ¥vUz_ArßAªãaãvÃÃ¥AUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Ãœa~/v@Ã¥Az©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zÃ¥,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÃÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÃ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢CßovÃ¥+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÃböÄåbîðîÃa~©ßîÄßã<î>Ã¥Bã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ãœ,ßAÃ>,ðßß+ßÜ©|Ãr©bCðâüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ãa@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ãœ,@U*ÃvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÃÃœOÃoðßðÃUýZÃB:+ÄÃã£")); $size = 2; - $stmt = $conn->prepare("SELECT * FROM $tableName1", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); - $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + $stmt = $conn->prepare("SELECT * FROM $tableName1", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); + $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); echo("Client Buffer Size in KB: $attr\n"); - $stmt->execute(); + $stmt->execute(); $numRows = 0; while ($result = $stmt->fetch()) { $numRows++; } echo ("Number of rows: $numRows\n"); - $size = 3; - $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); - $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + $size = 3; + $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); + $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); echo("Client Buffer Size in KB: $attr\n"); - $stmt->execute(); + $stmt->execute(); $numRows = 0; while ($result = $stmt->fetch()) { $numRows++; } - echo ("Number of rows: $numRows\n"); + $size = 1; + $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED,PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE=> $size)); + $attr = $stmt->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + echo("Client Buffer Size in KB: $attr\n"); + try { + $stmt->execute(); + echo "Expect this to fail!!\n"; + } catch (PDOException $e) { + var_dump($e->getMessage()); + } + dropTable($conn, $tableName1); dropTable($conn, $tableName2); unset($stmt); @@ -58,4 +70,6 @@ Client Buffer Size in KB: 2 Number of rows: 1 Client Buffer Size in KB: 3 Number of rows: 1 +Client Buffer Size in KB: 1 +string(65) "SQLSTATE[IMSSP]: Memory limit of 1 KB exceeded for buffered query" Done diff --git a/test/functional/pdo_sqlsrv/pdo_228_setConnAttribute_clientbuffermaxkbsize.phpt b/test/functional/pdo_sqlsrv/pdo_228_setConnAttribute_clientbuffermaxkbsize.phpt new file mode 100644 index 000000000..b501bd7b9 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_228_setConnAttribute_clientbuffermaxkbsize.phpt @@ -0,0 +1,94 @@ +--TEST-- +GitHub issue #228 - how max client buffer size affects the fetching of data +--DESCRIPTION-- +A pdo_sqlsrv variation of the example in GitHub issue 228, using PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE the connection attribute. +--SKIPIF-- + +--FILE-- +setAttribute(PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, $size); + } catch (PDOException $e) { + if (strpos($e->getMessage(), $error) === false) { + echo $e->getMessage() . "\n"; + } + } +} + +try { + // Connect + $conn = connect(); + + $error = 'The PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE attribute is not a number or the number is not positive. Only positive numbers are valid for this attribute.'; + testErrors($conn, 0, $error); + testErrors($conn, 2.99, $error); + + // Create 2 tables + $tableName1 = 'pdo_228_1'; + $tableName2 = 'pdo_228_2'; + + createTable($conn,$tableName1, array("c1_int" => "int", "c2_varchar" => "varchar(1000)")); + insertRow($conn, $tableName1, array("c1_int" => 990021574, "c2_varchar" => ">vh~Ö.bÃ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÃÃœh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÃö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÃBî@~AZöÃOßC@äoÃuCÃœ,ÃÄa:îäÄÖý:h*ouªuÃ¥vUz_ArßAªãaãvÃÃ¥AUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Ãœa~/v@Ã¥Az©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zÃ¥,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÃÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÃ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢CßovÃ¥+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÃböÄåbîðîÃa~©ßîÄßã<î>Ã¥Bã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ãœ,ßAÃ>,ðßß+ßÜ©|Ãr©bCðâüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ãa@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ãœ,@U*ÃvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÃÃœOÃoðßðÃUýZÃB:+ÄÃã£")); + + createTable($conn,$tableName2, array("c1_int" => "int", "c2_varchar" => "varchar(max)")); + insertRow($conn, $tableName2, array("c1_int" => 990021574, "c2_varchar" => ">vh~Ö.bÃ*äß/ÄAabýZâOüzr£ðAß+|~|OU¢a|U<ßrv.uCB.ÃÃœh_î+ãå@üðöã,U+ßvuU:/ý_Öãî/ð|bB|_Zbua©r++BA¢z£.üî¢öåäözÜ¢ßb:aöCrÄ~ýZ¢uªÃö.hhßð*zÜÜß*ãüåýãÄ+åýüüaߢÃÃBî@~AZöÃOßC@äoÃuCÃœ,ÃÄa:îäÄÖý:h*ouªuÃ¥vUz_ArßAªãaãvÃÃ¥AUüAB:¢Äz|öub<üZvößüå:ãÄ@r/ZAÄðÄÄvzîv~C/£|ýýbüÖ~£|Öå<Ãœa~/v@Ã¥Az©¢£U_ßhbaÃß,zz<ã¢|<ä©>öAuövÖ>abu,zÃ¥,+ß/ü/ª_bbB:ÃC~£ü/O©O©ªAª_,|a¢~ýý/b>ßC@/böîöh>~£ð+Bßr©ÄÃÖßã:bA@:>B:UAbããîÜ~uÜ£îCöÖ£©_ÜßzÃ+ÖýZb,A:<z.ãîÄzC@©*ä|ã._ßZOäb¢CßovÃ¥+uv.£B~~b£ª|ÖÄîßö>©Ãbb|©©ðA£åO~âãüîuvÄÜýUzîOÖ/oOßO*>ªßzêÖÃböÄåbîðîÃa~©ßîÄßã<î>Ã¥Bã_ý*ah¢rOĪ,ßo¢¢a|BÖäzU£.B£@Ãœ,ßAÃ>,ðßß+ßÜ©|Ãr©bCðâüãz>AßðåÃ>bÄåÄ|Z~äÃ/Cb*£bð_/Ãa@~AÜãO+ý*CîîÃzÄöÃa©+@vuz>î>©.Cv>hÃý>©Bä,ö~@~@r,AðCu@Ãœ,@U*ÃvöÃêuã.Öa*uZªoZ/ðÖ©ßv_<ÖvåÜÃÃœOÃoðßðÃUýZÃB:+ÄÃã£")); + + $size = 2; + $conn->setAttribute(PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, $size); + $stmt = $conn->prepare("SELECT * FROM $tableName1", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $attr = $conn->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + echo("Client Buffer Size in KB: $attr\n"); + $stmt->execute(); + $numRows = 0; + while ($result = $stmt->fetch()) { + $numRows++; + } + echo ("Number of rows: $numRows\n"); + + $size = 3; + $conn->setAttribute(PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, $size); + $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $attr = $conn->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + echo("Client Buffer Size in KB: $attr\n"); + $stmt->execute(); + $numRows = 0; + while ($result = $stmt->fetch()) { + $numRows++; + } + + echo ("Number of rows: $numRows\n"); + + $size = 1; + $conn->setAttribute(PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, $size); + $stmt = $conn->prepare("SELECT * FROM $tableName2", array(constant('PDO::ATTR_CURSOR') => PDO::CURSOR_SCROLL,PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $attr = $conn->getAttribute(constant('PDO::SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE')); + echo("Client Buffer Size in KB: $attr\n"); + try { + $stmt->execute(); + echo "Expect this to fail!!\n"; + } catch (PDOException $e) { + var_dump($e->getMessage()); + } + + dropTable($conn, $tableName1); + dropTable($conn, $tableName2); + unset($stmt); + unset($conn); + print "Done"; +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> + +--EXPECT-- +Client Buffer Size in KB: 2 +Number of rows: 1 +Client Buffer Size in KB: 3 +Number of rows: 1 +Client Buffer Size in KB: 1 +string(65) "SQLSTATE[IMSSP]: Memory limit of 1 KB exceeded for buffered query" +Done diff --git a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt index 6b4d1100c..2c0e1b42e 100644 --- a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt +++ b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize.phpt @@ -1,5 +1,7 @@ --TEST-- -sqlsrv_has_rows() using a forward and scrollable cursor +GitHub issue #228 - how ClientBufferMaxKBSize affects sqlsrv_has_rows and sqlsrv_fetch_array +--DESCRIPTION-- +Based on the example in GitHub issue 228, configuring ClientBufferMaxKBSize with sqlsrv_configure. --SKIPIF-- --FILE-- @@ -7,6 +9,20 @@ sqlsrv_has_rows() using a forward and scrollable cursor require_once('MsCommon.inc'); +function testErrors($conn) +{ + // set client buffer size to 0KB returns false + $ret = sqlsrv_configure('ClientBufferMaxKBSize', 0); + if (!$ret) { + echo sqlsrv_errors()[0]['message'] . "\n"; + } + + $ret = sqlsrv_configure('ClientBufferMaxKBSize', -1.9); + if (!$ret) { + echo sqlsrv_errors()[0]['message'] . "\n"; + } +} + function fetchData($conn, $table, $size) { $ret = sqlsrv_configure('ClientBufferMaxKBSize', $size); @@ -16,10 +32,13 @@ function fetchData($conn, $table, $size) echo("ClientBufferMaxKBSize is $attr\n"); sqlsrv_execute($stmt); + if ($size < 2) { + echo sqlsrv_errors()[0]['message'] . "\n"; + } + $rows = sqlsrv_has_rows($stmt); var_dump($rows); - sqlsrv_execute($stmt); $numRowsFetched = 0; while ($row = sqlsrv_fetch_array($stmt)) { $numRowsFetched++; @@ -40,18 +59,17 @@ $stmt = AE\createTable($conn, $tableName1, $columns); unset($columns); $columns = array(new AE\ColumnMeta('int', 'c1_int'), - new AE\ColumnMeta('varchar(1036)', 'c2_varchar_1036')); + new AE\ColumnMeta('varchar(1400)', 'c2_varchar_1400')); $stmt = AE\createTable($conn, $tableName2, $columns); -// insert > 1KB into c2_varchar_max & c2_varchar_1036 (1036 characters). -$longString = 'This is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a testThis is a test'; +// insert > 1KB into c2_varchar_max & c2_varchar_1400 (1400 characters). +$longString = str_repeat('This is a test', 100); $stmt = AE\insertRow($conn, $tableName1, array('c1_int' => 1, 'c2_varchar_max' => $longString)); -$stmt = AE\insertRow($conn, $tableName2, array('c1_int' => 1, 'c2_varchar_1036' => $longString)); +$stmt = AE\insertRow($conn, $tableName2, array('c1_int' => 1, 'c2_varchar_1400' => $longString)); +sqlsrv_free_stmt($stmt); -// set client buffer size to 0KB returns false -$ret = sqlsrv_configure('ClientBufferMaxKBSize', 0); -var_dump($ret); +testErrors($conn); // set client buffer size to 1KB $size = 1; @@ -62,19 +80,24 @@ $size = 2; fetchData($conn, $tableName1, $size); // this should return 1 row. fetchData($conn, $tableName2, $size); // this should return 1 row. -sqlsrv_free_stmt($stmt); +dropTable($conn, $tableName1); +dropTable($conn, $tableName2); + sqlsrv_close($conn); print "Done" ?> --EXPECT-- -bool(false) +Setting for ClientBufferMaxKBSize was non-int or non-positive. +Setting for ClientBufferMaxKBSize was non-int or non-positive. bool(true) ClientBufferMaxKBSize is 1 +Memory limit of 1 KB exceeded for buffered query bool(false) Number of rows fetched: 0 bool(true) ClientBufferMaxKBSize is 1 +Memory limit of 1 KB exceeded for buffered query bool(false) Number of rows fetched: 0 bool(true) diff --git a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt new file mode 100644 index 000000000..459169d93 --- /dev/null +++ b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt @@ -0,0 +1,109 @@ +--TEST-- +GitHub issue #228 - how ClientBufferMaxKBSize affects sqlsrv_has_rows and sqlsrv_fetch_array +--DESCRIPTION-- +A variation of the example in GitHub issue 228, using ClientBufferMaxKBSize the statement option. +--SKIPIF-- + +--FILE-- +"buffered", "ClientBufferMaxKBSize" => 0)); + if ($stmt !== false) { + echo("Setting client buffer size to 0KB should have failed\n"); + } else { + if (strpos(sqlsrv_errors()[0]['message'], $error) === false) { + print_r(sqlsrv_errors()); + } + } + + // set client buffer size to 0.99KB + $stmt = sqlsrv_prepare($conn, $query, array(), array("Scrollable"=>"buffered", "ClientBufferMaxKBSize" => 0.99)); + if ($stmt !== false) { + echo("Setting client buffer size to 0.99KB should have failed\n"); + } else { + if (strpos(sqlsrv_errors()[0]['message'], $error) === false) { + print_r(sqlsrv_errors()); + } + } +} + +function fetchData($conn, $table, $size) +{ + $stmt = sqlsrv_prepare($conn, "SELECT * FROM $table", array(), array("Scrollable"=>"buffered", "ClientBufferMaxKBSize" => $size)); + + $numRowsExpected = ($size > 1) ? 1 : 0; + $res = sqlsrv_execute($stmt); + if ($res && $size < 2) { + echo "Expect this to fail\n"; + } else { + $error = 'Memory limit of 1 KB exceeded for buffered query'; + if (strpos(sqlsrv_errors()[0]['message'], $error) === false) { + print_r(sqlsrv_errors()); + } + } + + $rows = sqlsrv_has_rows($stmt); + if ($numRowsExpected && !$rows) { + fatalError("sqlsrv_has_rows failed\n"); + } + + $numRowsFetched = 0; + while ($row = sqlsrv_fetch_array($stmt)) { + $numRowsFetched++; + } + if ($numRowsExpected != $numRowsFetched) { + echo("Expected $numRowsExpected but number of rows fetched is $numRowsFetched\n"); + } +} + +// connect +$conn = AE\connect(); + +$tableName1 = 'php_test_table_1'; +$tableName2 = 'php_test_table_2'; + +// Create tables +$columns = array(new AE\ColumnMeta('int', 'c1_int'), + new AE\ColumnMeta('varchar(max)', 'c2_varchar_max')); +$stmt = AE\createTable($conn, $tableName1, $columns); + +unset($columns); +$columns = array(new AE\ColumnMeta('int', 'c1_int'), + new AE\ColumnMeta('varchar(1050)', 'c2_varchar_1050')); +$stmt = AE\createTable($conn, $tableName2, $columns); + +// insert > 1KB into c2_varchar_max & c2_varchar_1050 (1050 characters). +$longString = str_repeat('This is a test', 75); + +$stmt = AE\insertRow($conn, $tableName1, array('c1_int' => 1, 'c2_varchar_max' => $longString)); +$stmt = AE\insertRow($conn, $tableName2, array('c1_int' => 1, 'c2_varchar_1050' => $longString)); +sqlsrv_free_stmt($stmt); + +$error = 'Setting for ClientBufferMaxKBSize was non-int or non-positive'; +testErrors($conn, $tableName1, $error); + +// set client buffer size to 1KB +$size = 1; +fetchData($conn, $tableName1, $size); // this should return 0 rows. +fetchData($conn, $tableName2, $size); // this should return 0 rows. +// set client buffer size to 2KB +$size = 2; +fetchData($conn, $tableName1, $size); // this should return 1 row. +fetchData($conn, $tableName2, $size); // this should return 1 row. + +dropTable($conn, $tableName1); +dropTable($conn, $tableName2); + +sqlsrv_close($conn); +print "Done" +?> + +--EXPECT-- +Done From 9e49a176d87485830a3eb54f2586ab363716e710 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 5 Feb 2019 15:42:53 -0800 Subject: [PATCH 089/249] Fixed load order issue in sqlsrv --- source/sqlsrv/config.m4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index fa0546de0..20d4d8589 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -40,7 +40,7 @@ if test "$PHP_SQLSRV" != "no"; then shared/StringFunctions.cpp \ " AC_MSG_CHECKING([for SQLSRV headers]) - if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h && test "$PHP_PDO_SQLSRV" != "no"; then + if test -f $srcdir/ext/pdo_sqlsrv/shared/core_sqlsrv.h && test "$PHP_PDO_SQLSRV" != "no" && test "$PHP_PDO_SQLSRV_SHARED" == "no"; then pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/ shared_src_class="" elif test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then From 3b9739a6c260642ba6eee1d1d4bc8096d6000582 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 6 Feb 2019 15:57:52 -0800 Subject: [PATCH 090/249] Added source indexing for symbols (#922) --- buildscripts/builddrivers.py | 53 ++++++++++++- buildscripts/buildtools.py | 3 +- buildscripts/indexsymbols.py | 145 +++++++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 buildscripts/indexsymbols.py diff --git a/buildscripts/builddrivers.py b/buildscripts/builddrivers.py index e9ac02dcd..ff301cc53 100644 --- a/buildscripts/builddrivers.py +++ b/buildscripts/builddrivers.py @@ -24,7 +24,9 @@ import shutil import os.path import argparse +import subprocess from buildtools import BuildUtil +from indexsymbols import * class BuildDriver(object): """Build sqlsrv and/or pdo_sqlsrv drivers with PHP source with the following properties: @@ -38,6 +40,8 @@ class BuildDriver(object): make_clean # a boolean flag - whether make clean is necessary source_path # path to a local source folder testing # whether the user has turned on testing mode + srctool_path # path to source indexing tools (empty string by default) + tag_version # tag version for source indexing (empty string by default) """ def __init__(self, phpver, driver, arch, thread, debug, repo, branch, source, path, testing, no_rename): @@ -49,6 +53,8 @@ def __init__(self, phpver, driver, arch, thread, debug, repo, branch, source, pa self.testing = testing self.rebuild = False self.make_clean = False + self.srctool_path = '' + self.tag_version = '' def show_config(self): print() @@ -112,6 +118,34 @@ def get_local_source(self, source_path): print("The path provided is invalid. Please re-enter.") return source + def index_all_symbols(self, ext_dir, srctool_path, tag_version): + """This takes care of indexing all the symbols + + :param ext_dir: the directory where we can find the built extension(s) + :param srctool_path: the path to the tools for source indexing + :param tag_version: tag version for source indexing + :outcome: all symbols will be source indexed + """ + work_dir = os.path.dirname(os.path.realpath(__file__)) + os.chdir(srctool_path) + + if self.util.driver == 'all': + driver = 'sqlsrv' + pdbfile = os.path.join(ext_dir, self.util.driver_name(driver, '.pdb')) + print('Indexing this symbol: ', pdbfile) + run_indexing_tools(pdbfile, driver, tag_version) + driver = 'pdo_sqlsrv' + pdbfile = os.path.join(ext_dir, self.util.driver_name(driver, '.pdb')) + print('Indexing this symbol: ', pdbfile) + run_indexing_tools(pdbfile, driver, tag_version) + else: + driver = self.util.driver + pdbfile = os.path.join(ext_dir, self.util.driver_name(driver, '.pdb')) + print('Indexing this symbol: ', pdbfile) + run_indexing_tools(pdbfile, driver, tag_version) + + os.chdir(work_dir) + def build_extensions(self, root_dir, logfile): """This takes care of getting the drivers' source files, building the drivers. If dest_path is defined, the binaries will be copied to the designated destinations. @@ -151,6 +185,12 @@ def build_extensions(self, root_dir, logfile): # ext_dir is the directory where we can find the built extension(s) ext_dir = self.util.build_drivers(self.make_clean, dest, logfile) + # Do source indexing only if the tag and tools path are both specified + if self.tag_version is not '' and self.srctool_path is not '': + print('Source indexing begins...') + self.index_all_symbols(ext_dir, self.srctool_path, self.tag_version) + print('Source indexing done') + # Copy the binaries if a destination path is defined if self.dest_path is not None: dest_drivers = os.path.join(self.dest_path, self.util.major_version(), self.util.arch) @@ -172,9 +212,12 @@ def build_extensions(self, root_dir, logfile): return ext_dir - def build(self): + def build(self, srctool_path, tag_version): """This is the main entry point of building drivers for PHP. For development, this will loop till the user decides to quit. + + :param srctool_path: the path to the tools for source indexing + :param tag_version: tag version for source indexing """ self.show_config() @@ -191,6 +234,10 @@ def build(self): logfile = self.util.get_logfile_name() + # Save source indexing details + self.srctool_path = srctool_path + self.tag_version = tag_version + try: ext_dir = self.build_extensions(root_dir, logfile) print('Build Completed') @@ -244,6 +291,8 @@ def validate_input(question, values): parser.add_argument('--TESTING', action='store_true', help="turns on testing mode (default: False)") parser.add_argument('--DESTPATH', default=None, help="an alternative destination for the drivers (default: None)") parser.add_argument('--NO_RENAME', action='store_true', help="drivers will not be renamed(default: False)") + parser.add_argument('--SRCIDX_PATH', default='', help="the path to the tools for source indexing (default: '')") + parser.add_argument('--TAG_VERSION', default='', help="the tag version for source indexing (default: '')") args = parser.parse_args() @@ -305,4 +354,4 @@ def validate_input(question, values): path, testing, no_rename) - builder.build() + builder.build(args.SRCIDX_PATH, args.TAG_VERSION) diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index 79b36923a..59f8b8f55 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -336,7 +336,6 @@ def build_drivers(self, make_clean = False, dest = None, log_file = None): is complete. """ work_dir = os.path.dirname(os.path.realpath(__file__)) - # First, update the driver source file contents source_dir = os.path.join(work_dir, 'Source') if self.driver == 'all': @@ -402,6 +401,7 @@ def build_drivers(self, make_clean = False, dest = None, log_file = None): # Final step, copy the binaries to the right place ext_dir = self.copy_binaries(sdk_dir, copy_to_ext) + return ext_dir def rename_binary(self, path, driver): @@ -454,7 +454,6 @@ def copy_binaries(self, sdk_dir, copy_to_ext): shutil.copy(os.path.join(phpsrc, 'php.ini-production'), php_ini_file) # Copy run-tests.php as well - phpsrc = self.phpsrc_root(sdk_dir) shutil.copy(os.path.join(phpsrc, 'run-tests.php'), build_dir) print('Copying the binaries from', build_dir) diff --git a/buildscripts/indexsymbols.py b/buildscripts/indexsymbols.py new file mode 100644 index 000000000..bcf6ffbd3 --- /dev/null +++ b/buildscripts/indexsymbols.py @@ -0,0 +1,145 @@ +#!/usr/bin/python3 +######################################################################################### +# +# Description: This contains helper methods for source indexing +# +# Requirement: +# python 3.x +# srctool.exe and pdbstr.exe +# +############################################################################################# + +import os.path +import argparse +import subprocess +from subprocess import Popen, PIPE + +def write_index(index_filename, tag_version): + """This writes to a temporary index file for the pdbstr tool + + For example + + SRCSRV: ini ------------------------------------------------ + VERSION=1 + SRCSRV: variables ------------------------------------------ + PATH=%var2% + SRCSRVTRG=%TARG%\%PDBVERSION%\%fnbksl%(%var2%) + SRCURL=https://raw.githubusercontent.com/Microsoft/msphpsql/%SRCVERSION%/source/%PATH% + SRCSRVCMD=powershell -Command "$r=New-Object -ComObject Msxml2.XMLHTTP; $r.open('GET', '%SRCURL%', $false); $r.send(); [io.file]::WriteAllBytes('%SRCSRVTRG%', $r.responseBody)" + SRCVERSION=v5.6.0 + PDBVERSION=v5.6.0 + For example + """ + with open(index_filename, 'w') as f: + f.write('SRCSRV: ini ------------------------------------------------' + os.linesep) + f.write('VERSION=1' + os.linesep) + f.write('SRCSRV: variables ------------------------------------------' + os.linesep) + f.write('PATH=%var2%' + os.linesep) + f.write('SRCSRVTRG=%TARG%\%PDBVERSION%\%fnbksl%(%var2%)' + os.linesep) + f.write('SRCURL=https://raw.githubusercontent.com/Microsoft/msphpsql/%SRCVERSION%/source/%PATH%' + os.linesep) + f.write('SRCSRVCMD=powershell -Command ') + f.write('\"$r=New-Object -ComObject Msxml2.XMLHTTP; ') + f.write('$r.open(\'GET\', \'%SRCURL%\', $false); ') + f.write('$r.send(); [io.file]::WriteAllBytes(\'%SRCSRVTRG%\', $r.responseBody)\"' + os.linesep) + f.write('SRCVERSION=' + tag_version + os.linesep) + f.write('PDBVERSION=' + tag_version + os.linesep) + +def append_source_filess(index_filename, source_files, driver): + """This appends the paths to different source files to the temporary index file + + For example + + SRCSRV: source files --------------------------------------- + c:\php-sdk\phpdev\vc15\x86\php-7.2.14-src\ext\pdo_sqlsrv\pdo_dbh.cpp*pdo_sqlsrv/pdo_dbh.cpp + c:\php-sdk\phpdev\vc15\x86\php-7.2.14-src\ext\pdo_sqlsrv\pdo_init.cpp*pdo_sqlsrv/pdo_init.cpp + ... ... + c:\php-sdk\phpdev\vc15\x86\php-7.2.14-src\ext\pdo_sqlsrv\shared\core_stream.cpp*shared/core_stream.cpp + c:\php-sdk\phpdev\vc15\x86\php-7.2.14-src\ext\pdo_sqlsrv\shared\core_util.cpp*shared/core_util.cpp + SRCSRV: end ------------------------------------------------ + """ + failed = False + with open(index_filename, 'a') as idx_file: + idx_file.write('SRCSRV: source files ---------------------------------------' + os.linesep) + with open(source_files, 'r') as src_file: + for line in src_file: + pos = line.find('shared') + if (pos > 0): # it's a nested folder, so it must be positive + relative_path = line[pos:] + src_line = line[:-1] + '*' + relative_path.replace('\\', '/') + else: # not a file in the shared folder + pos = line.find(driver) + if (pos <= 0): + print('ERROR: Expected to find', driver, 'in', line) + failed = True + break + else: + relative_path = line[pos:] + src_line = line[:-1] + '*' + relative_path.replace('\\', '/') + idx_file.write(src_line) + idx_file.write('SRCSRV: end ------------------------------------------------' + os.linesep) + return failed + +def run_indexing_tools(pdbfile, driver, tag_version): + """This invokes the source indexing tools, srctool.exe and pdbstr.exe + + :param pdbfile: the absolute path to the symbol file + :param driver: either sqlsrv or pdo_sqlsrv + :param tag_version: tag version for source indexing + :outcome: the driver pdb file will be source indexed + """ + # run srctool.exe to get all driver's source files from the PDB file + # srctool.exe -r | find "\" | sort > files.txt + batch_filename = 'runsrctool.bat' + index_filename = 'idx.txt' + source_files = 'files.txt' + + with open(batch_filename, 'w') as batch_file: + batch_file.write('@ECHO OFF' + os.linesep) + batch_file.write('@CALL srctool -r %1 | find "%2\\" | sort > ' + source_files + ' 2>&1' + os.linesep) + + get_source_filess = batch_filename + ' {0} {1} ' + get_source_filess_cmd = get_source_filess.format(pdbfile, driver) + subprocess.call(get_source_filess_cmd) + + # create an index file using the above inputs for pdbstr.exe + write_index(index_filename, tag_version) + failed = append_source_filess(index_filename, source_files, driver) + + if failed: + print("ERROR: Failed to prepare the temporary index file for the pdbstr tool") + exit(1) + + # run pdbstr.exe to insert the information into the PDB file + # pdbstr.exe -w -p: -i:idx.txt -s:srcsrv + pdbstr_str = 'pdbstr.exe -w -p:{0} -i:{1} -s:srcsrv' + pdbstr_cmd = pdbstr_str.format(pdbfile, index_filename) + subprocess.call(pdbstr_cmd) + + os.remove(batch_filename) + os.remove(index_filename) + os.remove(source_files) + +################################### Main Function ################################### +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('PDBFILE', help="the path to the pdb file for source indexing") + parser.add_argument('DRIVER', choices=['sqlsrv', 'pdo_sqlsrv'], help="driver name of this pdb file") + parser.add_argument('TAG_VERSION', help="the tag version for source indexing (e.g. v5.6.0)") + parser.add_argument('TOOLS_PATH',help="the path to the source indexing tools") + + args = parser.parse_args() + + srctool_exe = os.path.join(args.TOOLS_PATH, 'srctool.exe') + pdbstr_exe = os.path.join(args.TOOLS_PATH, 'pdbstr.exe') + if not os.path.exists(srctool_exe) or not os.path.exists(pdbstr_exe): + print('ERROR: Missing the required source indexing tools') + exit(1) + + work_dir = os.path.dirname(os.path.realpath(__file__)) + os.chdir(args.TOOLS_PATH) + + print('Source indexing begins...') + run_indexing_tools(args.PDBFILE, args.DRIVER.lower(), args.TAG_VERSION) + print('Source indexing done') + + os.chdir(work_dir) From a39be12f969979b533326488b24378f1053b83bd Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 8 Feb 2019 11:07:04 -0800 Subject: [PATCH 091/249] Modified linux and mac instructions for 5.6.0 RTW (#926) --- Linux-mac-install.md | 84 +++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 47 deletions(-) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index c83bec3fb..2a7357f47 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,37 +1,32 @@ # Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server -The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12, and macOS 10.11, 10.12, 10.13, and 10.14. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver.md##loading-the-driver-at-php-startup). +The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12 and 15, and macOS 10.11, 10.12, 10.13, and 10.14. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver##loading-the-driver-at-php-startup). -These instructions install PHP 7.2 by default -- see the notes at the beginning of each section to install PHP 7.0 or 7.1. +These instructions install PHP 7.3 by default. Note that some supported Linux distros default to PHP 7.0 or earlier, which is not supported for the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.1 or 7.2 instead. ## Contents of this page: - [Installing the drivers on Ubuntu 16.04, 18.04, and 18.10](#installing-the-drivers-on-ubuntu-1604-1804-and-1810) - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) -- [Installing the drivers on Suse 12](#installing-the-drivers-on-suse-12) +- [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15) - [Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-el-capitan-sierra-high-sierra-and-mojave) ## Installing the drivers on Ubuntu 16.04, 18.04, and 18.10 > [!NOTE] -> To install PHP 7.0, 7.1, or 7.3, replace `7.2` with `7.0`, `7.1`, or `7.3` in the following commands. +> To install PHP 7.1 or 7.2, replace 7.3 with 7.1 or 7.2 in the following commands. ### Step 1. Install PHP ``` sudo su add-apt-repository ppa:ondrej/php -y apt-get update -apt-get install php7.2 php7.2-dev php7.2-xml -y --allow-unauthenticated +apt-get install php7.3 php7.3-dev php7.3-xml -y --allow-unauthenticated ``` ### Step 2. Install prerequisites Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). -For Ubuntu 18.10, follow the above steps for Ubuntu 18.04 except replace `18.04` by `18.10` and download ODBC 17.3 preview [here](https://www.microsoft.com/download/details.aspx?id=57341). - ### Step 3. Install the PHP drivers for Microsoft SQL Server - -> [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -46,9 +41,9 @@ sudo su apt-get install libapache2-mod-php7.2 apache2 a2dismod mpm_event a2enmod mpm_prefork -a2enmod php7.2 -echo "extension=pdo_sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/30-pdo_sqlsrv.ini -echo "extension=sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/20-sqlsrv.ini +a2enmod php7.3 +echo "extension=pdo_sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/30-pdo_sqlsrv.ini +echo "extension=sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/20-sqlsrv.ini exit ``` ### Step 5. Restart Apache and test the sample script @@ -60,7 +55,7 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Red Hat 7 > [!NOTE] -> To install PHP 7.0, 7.1, or 7.3, replace `remi-php72` with `remi-php70`, `remi-php71`, or `remi-php73` respectively in the following commands. +> To install PHP 7.1 or 7.2, replace remi-php73 with remi-php71 or remi-php72 respectively in the following commands. ### Step 1. Install PHP @@ -71,23 +66,20 @@ wget https://rpms.remirepo.net/enterprise/remi-release-7.rpm rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm subscription-manager repos --enable=rhel-7-server-optional-rpms yum install yum-utils -yum-config-manager --enable remi-php72 +yum-config-manager --enable remi-php73 yum update yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc ``` ### Step 2. Install prerequisites Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). -Compiling the PHP drivers with PECL with PHP 7.2 requires a more recent GCC than the default: +Compiling the PHP drivers with PECL with PHP 7.2 or 7.3 requires a more recent GCC than the default: ``` sudo yum-config-manager --enable rhel-server-rhscl-7-rpms sudo yum install devtoolset-7 scl enable devtoolset-7 bash ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server - -> [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -99,8 +91,8 @@ exit An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually (similar steps for pdo_sqlsrv): ``` pecl download sqlsrv -tar xvzf sqlsrv-5.3.0.tgz -cd sqlsrv-5.3.0/ +tar xvzf sqlsrv-5.6.0.tgz +cd sqlsrv-5.6.0/ phpize ./configure --with-php-config=/usr/bin/php-config make @@ -127,7 +119,7 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Debian 8 and 9 > [!NOTE] -> To install PHP 7.0, 7.1, or 7.3, replace `7.2` with `7.0`, `7.1`, or `7.3` in the following commands. +> To install PHP 7.1 or 7.2, replace 7.3 in the following commands with 7.1 or 7.2. ### Step 1. Install PHP ``` @@ -136,7 +128,7 @@ apt-get install curl apt-transport-https wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list apt-get update -apt-get install -y php7.2 php7.2-dev php7.2-xml +apt-get install -y php7.3 php7.3-dev php7.3-xml ``` ### Step 2. Install prerequisites Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). @@ -149,9 +141,6 @@ locale-gen ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server - -> [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -163,12 +152,12 @@ exit ### Step 4. Install Apache and configure driver loading ``` sudo su -apt-get install libapache2-mod-php7.2 apache2 +apt-get install libapache2-mod-php7.3 apache2 a2dismod mpm_event a2enmod mpm_prefork -a2enmod php7.2 -echo "extension=pdo_sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/30-pdo_sqlsrv.ini -echo "extension=sqlsrv.so" >> /etc/php/7.2/apache2/conf.d/20-sqlsrv.ini +a2enmod php7.3 +echo "extension=pdo_sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/30-pdo_sqlsrv.ini +echo "extension=sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/20-sqlsrv.ini ``` ### Step 5. Restart Apache and test the sample script ``` @@ -176,27 +165,32 @@ sudo service apache2 restart ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on Suse 12 +## Installing the drivers on Suse 12 and 15 > [!NOTE] -> To install PHP 7.0 or 7.1, replace the repository URL below with one of the following URLs: -`https://download.opensuse.org/repositories/devel:languages:php:php70/SLE_12_SP3/devel:languages:php:php70.repo` -`https://download.opensuse.org/repositories/devel:languages:php:php71/SLE_12_SP3/devel:languages:php:php71.repo` +> In the following instructions, replace with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15 or SLE_15_SP1, and similarly for other versions. Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or to `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse. + +> [!NOTE] +> Packages for PHP 7.3 are not available for Suse 12. +> To install PHP 7.1, replace the repository URL below with the following URL: + `https://download.opensuse.org/repositories/devel:/languages:/php:/php71//devel:languages:php:php71.repo`. +> To install PHP 7.2, replace the repository URL below with the following URL: + `https://download.opensuse.org/repositories/devel:/languages:/php:/php72//devel:languages:php:php72.repo`. ### Step 1. Install PHP ``` sudo su -zypper -n ar -f https://download.opensuse.org/repositories/devel:languages:php/SLE_12_SP3/devel:languages:php.repo +zypper -n ar -f https://download.opensuse.org/repositories/devel:languages:php//devel:languages:php.repo zypper --gpg-auto-import-keys refresh -zypper -n install php7 php7-pear php7-devel +zypper -n install php7 php7-pear php7-devel php7-openssl ``` ### Step 2. Install prerequisites -Install the ODBC driver for Suse 12 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). +Install the ODBC driver for Suse by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). ### Step 3. Install the PHP drivers for Microsoft SQL Server - > [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. +> If you get an error message saying `Connection to 'pecl.php.net:443' failed: Unable to find the socket transport "ssl"`, edit the pecl script at /usr/bin/pecl and remove the `-n` switch in the last line. This switch prevents PECL from loading ini files when PHP is called, which prevents the OpenSSL extension from loading. + ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -228,18 +222,18 @@ If you do not already have it, install brew as follows: ``` > [!NOTE] -> To install PHP 7.0, 7.1, or 7.3, replace `php@7.2` with `php@7.0`, `php@7.1`, or `php@7.3` respectively in the following commands. +> To install PHP 7.1 or 7.2, replace php@7.3 with php@7.1 or php@7.2 respectively in the following commands. ### Step 1. Install PHP ``` brew tap brew tap homebrew/core -brew install php@7.2 +brew install php@7.3 ``` PHP should now be in your path -- run `php -v` to verify that you are running the correct version of PHP. If PHP is not in your path or it is not the correct version, run the following: ``` -brew link --force --overwrite php@7.2 +brew link --force --overwrite php@7.3 ``` ### Step 2. Install prerequisites @@ -251,9 +245,6 @@ brew install autoconf automake libtool ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server - -> [!NOTE] -> If using PHP 7.3, replace `sqlsrv` and `pdo_sqlsrv` in the following commands with `sqlsrv-5.4.0preview` and `pdo_sqlsrv-5.4.0preview` or later, as earlier versions are not compatible with PHP 7.3. ``` sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv @@ -329,5 +320,4 @@ function formatErrors($errors) } ?> ``` -Point your browser to https://localhost/testsql.php (https://localhost:8080/testsql.php on macOS). You should now be able to connect to your SQL Server/Azure SQL database. - +Point your browser to https://localhost/testsql.php (https://localhost:8080/testsql.php on macOS). You should now be able to connect to your SQL Server/Azure SQL database. \ No newline at end of file From c5989d8a114b221325b443ff16398d93f7472a1a Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 11 Feb 2019 14:42:15 -0800 Subject: [PATCH 092/249] Change log 5.6.0 (#921) --- CHANGELOG.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ README.md | 7 ++++--- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83a46a7fa..946ce626a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,52 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.6.0 - 2019-02-15 +Updated PECL release packages. Here is the list of updates: + +### Added +- Added support for PHP 7.3 +- Added support for Linux SUSE 15, Ubuntu 18.10 and mac OS Mojave +- Feature Request [#415](https://github.com/Microsoft/msphpsql/pull/886) - new options at connection and statement levels for both drivers for formatting decimal values in the fetched results +- Added support for Azure AD Access Token (in Linux / macOS this requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) and [unixODBC](http://www.unixodbc.org/) 2.3.6+) +- Added support for Authentication with Azure Active Directory using Managed Identity for Azure Resources (requires [MS ODBC Driver 17.3+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server)) +- Feature Request [#842](https://github.com/Microsoft/msphpsql/pull/842) - new PDO_STMT_OPTION_FETCHES_DATETIME_TYPE flag for pdo_sqlsrv to return datetime as objects +- Feature Request [#844](https://github.com/Microsoft/msphpsql/pull/844) - add ReturnDatesAsStrings option to statement level for sqlsrv + +### Removed +- Dropped support for Ubuntu 17.10 +- Dropped support for PHP 7.0 - [Version 5.3](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver?view=sql-server-2017) is the last to support PHP 7.0. + +### Fixed +- Issue [#434](https://github.com/Microsoft/msphpsql/issues/434) - To avoid possible crashes, before freeing stmt in the destructor check if its dbh driver data is NULL +- Pull Request [#833](https://github.com/Microsoft/msphpsql/pull/833) - Streamlined the error handling to remove a potential cause of crash +- Pull Request [#836](https://github.com/Microsoft/msphpsql/pull/836) - Modified the config files to enable Spectre Mitigations (use /Qspectre switch) for PHP 7.2 (see related Request [#878](https://github.com/Microsoft/msphpsql/pull/878)) +- Pull Request [#854](https://github.com/Microsoft/msphpsql/pull/854) - Clear Azure Key Vault data after connection attributes are successfully set or when exception is thrown +- Pull Request [#855](https://github.com/Microsoft/msphpsql/pull/855) - Improved performance by saving meta data before fetching and skipping unnecessary conversions for numeric data +- Pull Request [#865](https://github.com/Microsoft/msphpsql/pull/865) - Corrected the way SQLPutData and SQLParamData are used when sending stream data to the server +- Pull Request [#878](https://github.com/Microsoft/msphpsql/pull/878) - Modified the config files to enable Spectre Mitigations for PHP 7.1 (see related Request [#836](https://github.com/Microsoft/msphpsql/pull/836)) +- Pull Request [#891](https://github.com/Microsoft/msphpsql/pull/891) - Improved performance of Unicode conversions +- Pull Request [#892](https://github.com/Microsoft/msphpsql/pull/892) - Removed warning messages while compiling extensions +- Pull Request [#904](https://github.com/Microsoft/msphpsql/pull/904) - Enabled compiling extensions statically into PHP +- Pull Request [#907](https://github.com/Microsoft/msphpsql/pull/907) - Initialized output param buffer when allocating extra space +- Pull Request [#919](https://github.com/Microsoft/msphpsql/pull/919) - Initialized a boolean variable before passing it by reference into a function that will modify its value + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) + ## 5.5.0-preview - 2018-12-07 Updated PECL release packages. Here is the list of updates: diff --git a/README.md b/README.md index bd2b2349b..84952fe35 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ On the server side, Microsoft SQL Server 2008 R2 and above on Windows are suppor The drivers are distributed as pre-compiled extensions for PHP found on the [releases page](https://github.com/Microsoft/msphpsql/releases). They are available in thread-safe and non thread-safe versions, and in 32-bit and 64-bit versions. The source code for the drivers is also available, and you can compile them as thread safe or non-thread safe versions. The thread safety configuration of your web server will determine which version you need. -If you choose to build the drivers, you must be able to build PHP 7 without including these extensions. For help building PHP on Windows, see the [official PHP website][phpbuild]. For details on compiling the drivers, see the [documentation](https://github.com/Microsoft/msphpsql/tree/dev/buildscripts#windows) -- an example buildscript is provided, but you can also compile the drivers manually. +If you choose to build the drivers, you must be able to build PHP 7.* without including these extensions. For help building PHP on Windows, see the [official PHP website][phpbuild]. For details on compiling the drivers, see the [documentation](https://github.com/Microsoft/msphpsql/tree/dev/buildscripts#windows) -- an example buildscript is provided, but you can also compile the drivers manually. To load the drivers, make sure that the driver is in your PHP extension directory and enable it in your PHP installation's php.ini file by adding `extension=php_sqlsrv.dll` and/or `extension=php_pdo_sqlsrv.dll` to it. If necessary, specify the extension directory using `extension_dir`, for example: `extension_dir = "C:\PHP\ext"`. Note that the precompiled binaries have different names -- substitute accordingly in php.ini. For more details on loading the drivers, see [Loading the PHP SQL Driver](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver) on Microsoft Docs. @@ -88,6 +88,7 @@ The version number may have trailing pre-release version identifiers to indicate ## Future Plans - Expand SQL Server 2016 feature support (example: Azure Active Directory) - Add more verification/fundamental tests +- Improve performance - Bug fixes ## Guidelines for Reporting Issues @@ -99,14 +100,14 @@ We appreciate you taking the time to test the driver, provide feedback and repor Thank you! -## FAQs +## Questions **Q:** Can we get dates for any of the Future Plans listed above? **A:** At this time, Microsoft is not able to announce dates. We are working hard to release future versions of the driver and will share future plans as appropriate. **Q:** What's next? -**A:** On July 20, 2018 we released the production release version 5.3.0 of our PHP Driver. We will continue working on our future plans and releasing previews of upcoming releases. +**A:** We will continue working on our future plans and releasing previews of upcoming [releases](https://github.com/Microsoft/msphpsql/releases) **Q:** Is Microsoft taking pull requests for this project? From 5b2b7504da8336767f0484c3ebfab00ee831e76f Mon Sep 17 00:00:00 2001 From: Guillaume Degoulet Date: Wed, 13 Feb 2019 13:56:12 +0100 Subject: [PATCH 093/249] add Language option on connect --- source/pdo_sqlsrv/pdo_dbh.cpp | 10 +++++ source/shared/core_sqlsrv.h | 2 + source/sqlsrv/conn.cpp | 10 +++++ ...t_error_encoding_with_language_option.phpt | 41 +++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 test/functional/sqlsrv/test_error_encoding_with_language_option.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 669bc62b5..0f87c7c29 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -48,6 +48,7 @@ const char AttachDBFileName[] = "AttachDbFileName"; const char Authentication[] = "Authentication"; const char ColumnEncryption[] = "ColumnEncryption"; const char ConnectionPooling[] = "ConnectionPooling"; +const char Language[] = "Language"; const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; const char Database[] = "Database"; @@ -241,6 +242,15 @@ const connection_option PDO_CONN_OPTS[] = { CONN_ATTR_BOOL, conn_null_func::func }, + { + PDOConnOptionNames::Language, + sizeof( PDOConnOptionNames::Language ), + SQLSRV_CONN_OPTION_LANGUAGE, + ODBCConnOptions::Language, + sizeof( ODBCConnOptions::Language ), + CONN_ATTR_STRING, + conn_str_append_func::func + }, { PDOConnOptionNames::Driver, sizeof(PDOConnOptionNames::Driver), diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 886d5e829..d4ffdda57 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1131,6 +1131,7 @@ const char Authentication[] = "Authentication"; const char Driver[] = "Driver"; const char CharacterSet[] = "CharacterSet"; const char ConnectionPooling[] = "ConnectionPooling"; +const char Language[] = "Language"; const char ColumnEncryption[] = "ColumnEncryption"; const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; @@ -1163,6 +1164,7 @@ enum SQLSRV_CONN_OPTIONS { SQLSRV_CONN_OPTION_ACCESS_TOKEN, SQLSRV_CONN_OPTION_CHARACTERSET, SQLSRV_CONN_OPTION_CONN_POOLING, + SQLSRV_CONN_OPTION_LANGUAGE, SQLSRV_CONN_OPTION_DATABASE, SQLSRV_CONN_OPTION_ENCRYPT, SQLSRV_CONN_OPTION_FAILOVER_PARTNER, diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 532934f72..f4827863e 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -233,6 +233,7 @@ const char Authentication[] = "Authentication"; const char CharacterSet[] = "CharacterSet"; const char ColumnEncryption[] = "ColumnEncryption"; const char ConnectionPooling[] = "ConnectionPooling"; +const char Language[] = "Language"; const char ConnectRetryCount[] = "ConnectRetryCount"; const char ConnectRetryInterval[] = "ConnectRetryInterval"; const char Database[] = "Database"; @@ -380,6 +381,15 @@ const connection_option SS_CONN_OPTS[] = { CONN_ATTR_BOOL, conn_null_func::func }, + { + SSConnOptionNames::Language, + sizeof(SSConnOptionNames::Language), + SQLSRV_CONN_OPTION_LANGUAGE, + ODBCConnOptions::Language, + sizeof(ODBCConnOptions::Language), + CONN_ATTR_STRING, + conn_str_append_func::func + }, { SSConnOptionNames::Driver, sizeof(SSConnOptionNames::Driver), diff --git a/test/functional/sqlsrv/test_error_encoding_with_language_option.phpt b/test/functional/sqlsrv/test_error_encoding_with_language_option.phpt new file mode 100644 index 000000000..0d5d4891f --- /dev/null +++ b/test/functional/sqlsrv/test_error_encoding_with_language_option.phpt @@ -0,0 +1,41 @@ +--TEST-- +Encoding of sqlsrv errors +--SKIPIF-- + +--FILE-- +'UTF-8','Language'=>'German' )); +if (!$conn) { + die(print_r(sqlsrv_errors(), true)); +} + +$stmt = sqlsrv_query($conn, "select *, BadColumn from sys.syslanguages"); +if ($stmt) { + echo 'OK!'; + sqlsrv_free_stmt($stmt); +} else { + $errs = sqlsrv_errors(); + print_r($errs); +} + +sqlsrv_close($conn); + +?> +--EXPECTF-- +Array +( + [0] => Array + ( + [0] => 42S22 + [SQLSTATE] => 42S22 + [1] => 207 + [code] => 207 + [2] => %SUngültiger Spaltenname %cBadColumn%c. + [message] => %SUngültiger Spaltenname %cBadColumn%c. + ) + +) From b1b7a40f70a8cb9fc5a82d415d79eb3a2305e9ac Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 27 Feb 2019 11:10:26 -0800 Subject: [PATCH 094/249] Updated AppVeyor to download ODBC driver 17.3 (#941) --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ee9f2eb37..b8be2c437 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -81,10 +81,10 @@ install: } Else { $env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER; } - - echo Downloading MSODBCSQL 17.2 + - echo Downloading MSODBCSQL 17.3 # AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it - - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.2.0.1_x64.msi', 'c:\projects\msodbcsql_17.2.0.1_x64.msi') - - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.2.0.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.3.1.1_x64.msi', 'c:\projects\msodbcsql_17.3.1.1_x64.msi') + - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.3.1.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL - echo Checking the version of MSODBCSQL - reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server" - dir %WINDIR%\System32\msodbcsql*.dll From 12d01c918966f5ff908adb25d5586944949b88be Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 27 Feb 2019 16:54:26 -0800 Subject: [PATCH 095/249] Issue 937 - fixed ASSERT and added new tests (#940) --- source/pdo_sqlsrv/pdo_stmt.cpp | 18 ++- source/sqlsrv/stmt.cpp | 2 +- .../pdo_sqlsrv/pdo_937_metadata.phpt | 122 +++++++++++++++++ .../pdostatement_getColumnMeta.phpt | 3 +- ...tement_getColumnMeta_unicode_col_name.phpt | 3 +- test/functional/sqlsrv/srv_937_metadata.phpt | 125 ++++++++++++++++++ 6 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_937_metadata.phpt create mode 100644 test/functional/sqlsrv/srv_937_metadata.phpt diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index cd8c697f1..507fc94ca 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1029,20 +1029,28 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno try { SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: pdo_stmt object was null" ); - SQLSRV_ASSERT( stmt->columns != NULL, "pdo_sqlsrv_stmt_get_col_meta: columns are not available." ); SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." ); - sqlsrv_malloc_auto_ptr core_meta_data; - sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: stmt->driver_data was null"); - SQLSRV_ASSERT( colno >= 0 && colno < stmt->column_count, "pdo_sqlsrv_stmt_get_col_meta: invalid column number." ); + // Based on PDOStatement::getColumnMeta API, this should return FALSE + // if the requested column does not exist in the result set, or if + // no result set exists. Thus, do not use SQLSRV_ASSERT, which causes + // the script to fail right away. Instead, log this warning if logging + // is enabled + if (colno < 0 || colno >= stmt->column_count || stmt->columns == NULL) { + LOG( SEV_WARNING, "Invalid column number %1!d!", colno ); + return FAILURE; + } - core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); // initialize the array to nothing, as PDO requires us to create it core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); + sqlsrv_malloc_auto_ptr core_meta_data; + + core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); + // add the following fields: flags, native_type, driver:decl_type, table add_assoc_long( return_value, "flags", 0 ); diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index c366e0305..9d60de25f 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1807,7 +1807,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) throw; } - SQLSRV_ASSERT(num_cols > 0 && stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" ); + SQLSRV_ASSERT(stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" ); return num_cols; } diff --git a/test/functional/pdo_sqlsrv/pdo_937_metadata.phpt b/test/functional/pdo_sqlsrv/pdo_937_metadata.phpt new file mode 100644 index 000000000..d0cad7e75 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_937_metadata.phpt @@ -0,0 +1,122 @@ +--TEST-- +GitHub issue 937 - getting metadata will not fail after an UPDATE / DELETE statement +--DESCRIPTION-- +Verifies that getColumnMeta will not fail after processing an UPDATE / DELETE query that returns no fields. Instead, it should simply return FALSE because no result set exists. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +getColumnMeta(0); + if ($metadata !== FALSE) { + echo "Expects FALSE because no result set exists!\n"; + } +} + +try { + $conn = connect(); + + dropTable($conn, $tableName); + dropProc($conn, $procName); + + $tsql = "CREATE TABLE $tableName([id] [int] NOT NULL, [name] [varchar](10) NOT NULL)"; + $conn->query($tsql); + + $id = 3; + $tsql = "INSERT INTO $tableName VALUES ($id, 'abcde')"; + $conn->query($tsql); + + $tsql = "UPDATE $tableName SET name = 'updated' WHERE id = $id"; + $stmt = $conn->prepare($tsql); + $stmt->execute(); + $numCol = $metadata = $stmt->columnCount(); + echo "Number of columns after UPDATE: $numCol\n"; + checkMetaData($stmt); + + $tsql = "SELECT * FROM $tableName"; + $stmt = $conn->query($tsql); + $numCol = $metadata = $stmt->columnCount(); + for ($i = 0; $i < $numCol; $i++) { + $metadata = $stmt->getColumnMeta($i); + var_dump($metadata); + } + + createProc($conn, $procName, "@id int, @val varchar(10) OUTPUT", "SELECT @val = name FROM $tableName WHERE id = @id"); + + $value = ''; + $tsql = "{CALL [$procName] (?, ?)}"; + $stmt = $conn->prepare($tsql); + $stmt->bindParam(1, $id, PDO::PARAM_INT); + $stmt->bindParam(2, $value, PDO::PARAM_STR, 10); + $stmt->execute(); + $numCol = $metadata = $stmt->columnCount(); + echo "Number of columns after PROCEDURE: $numCol\n"; + echo "Value returned: $value\n"; + checkMetaData($stmt); + + $query = "DELETE FROM $tableName WHERE name = 'updated'"; + $stmt = $conn->query($query); + $numCol = $metadata = $stmt->columnCount(); + echo "Number of columns after DELETE: $numCol\n"; + checkMetaData($stmt); +} catch (PDOException $e) { + echo $e->getMessage() . PHP_EOL; +} + +dropTable($conn, $tableName); +dropProc($conn, $procName); + +unset($stmt); +unset($conn); + +?> +--EXPECT-- +Number of columns after UPDATE: 0 +array(8) { + ["flags"]=> + int(0) + ["sqlsrv:decl_type"]=> + string(3) "int" + ["native_type"]=> + string(6) "string" + ["table"]=> + string(0) "" + ["pdo_type"]=> + int(2) + ["name"]=> + string(2) "id" + ["len"]=> + int(10) + ["precision"]=> + int(0) +} +array(8) { + ["flags"]=> + int(0) + ["sqlsrv:decl_type"]=> + string(7) "varchar" + ["native_type"]=> + string(6) "string" + ["table"]=> + string(0) "" + ["pdo_type"]=> + int(2) + ["name"]=> + string(4) "name" + ["len"]=> + int(10) + ["precision"]=> + int(0) +} +Number of columns after PROCEDURE: 0 +Value returned: updated +Number of columns after DELETE: 0 diff --git a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt index f42d1d3d3..40805dc23 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt @@ -220,5 +220,4 @@ array(7) { Warning: PDOStatement::getColumnMeta(): SQLSTATE[42P10]: Invalid column reference: column number must be non-negative in %s on line %x bool(false) - -Fatal error: pdo_sqlsrv_stmt_get_col_meta: invalid column number. in %s on line %x +bool(false) diff --git a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt index bb01be10a..e7075ee46 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt @@ -259,5 +259,4 @@ array(7) { Warning: PDOStatement::getColumnMeta(): SQLSTATE[42P10]: Invalid column reference: column number must be non-negative in %s on line %x bool(false) - -Fatal error: pdo_sqlsrv_stmt_get_col_meta: invalid column number. in %s on line %x +bool(false) \ No newline at end of file diff --git a/test/functional/sqlsrv/srv_937_metadata.phpt b/test/functional/sqlsrv/srv_937_metadata.phpt new file mode 100644 index 000000000..47c9452fe --- /dev/null +++ b/test/functional/sqlsrv/srv_937_metadata.phpt @@ -0,0 +1,125 @@ +--TEST-- +GitHub issue #937 - getting metadata will not fail after an UPDATE / DELETE statement +--DESCRIPTION-- +Verifies that sqlsrv_field_metadata will return an empty array after processing an +UPDATE / DELETE query that returns no fields. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "buffered"); +$tsql = "DELETE FROM $tableName WHERE dummyColumn = 'updated'"; +$stmt = sqlsrv_query($conn, $tsql, array(), $options); +$fieldmeta = sqlsrv_field_metadata($stmt); +var_dump($fieldmeta); + +dropTable($conn, $tableName); +dropProc($conn, $procName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +array(2) { + [0]=> + array(6) { + ["Name"]=> + string(2) "id" + ["Type"]=> + int(4) + ["Size"]=> + NULL + ["Precision"]=> + int(10) + ["Scale"]=> + NULL + ["Nullable"]=> + int(0) + } + [1]=> + array(6) { + ["Name"]=> + string(11) "dummyColumn" + ["Type"]=> + int(12) + ["Size"]=> + int(10) + ["Precision"]=> + NULL + ["Scale"]=> + NULL + ["Nullable"]=> + int(0) + } +} +array(0) { +} +The value returned: updated +array(0) { +} +array(0) { +} \ No newline at end of file From 27d5f64ff9796e38f51c0b166c3c8c2e430b7729 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 28 Feb 2019 15:06:02 -0800 Subject: [PATCH 096/249] Changed travis to pull mcr.microsoft.com/mssql/server:2017-latest instead (#943) --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6d5141ed2..b94c0a186 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,10 +20,10 @@ env: - TEST_PHP_SQL_PWD=Password123 before_install: - - docker pull microsoft/mssql-server-linux:2017-latest + - docker pull mcr.microsoft.com/mssql/server:2017-latest install: - - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d microsoft/mssql-server-linux:2017-latest + - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2017-latest - docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql . before_script: From d60748e0cd22476eff36dbfd384dd273343b5bed Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 4 Mar 2019 18:11:37 -0800 Subject: [PATCH 097/249] Modified money tests to test the accuracies of floats (#944) --- .../pdostatement_format_money_types.phpt | 14 +++----------- .../sqlsrv_statement_format_money_types.phpt | 16 +++------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt b/test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt index c02d634ab..1f1d341a1 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_format_money_types.phpt @@ -64,17 +64,9 @@ function testFloatTypes($conn, $numDigits) 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 "$diff: Expected $floatVal but $floatVal1 returned. \n"; - } + $diff = abs($floatVal1 - $floatVal) / $floatVal; + if ($diff > $epsilon) { + echo "$diff: Expected $floatVal but $floatVal1 returned. \n"; } } } diff --git a/test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt b/test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt index ac2c1cd01..abd4bb7d0 100644 --- a/test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt +++ b/test/functional/sqlsrv/sqlsrv_statement_format_money_types.phpt @@ -63,20 +63,10 @@ function testFloatTypes($conn) 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 && $floatVal != $floats[$i]) { - echo "Expected $floats[$i] but returned "; + $diff = abs($floatVal - $floats[$i]) / $floats[$i]; + if ($diff > $epsilon) { + echo "$diff: Expected $floats[$i] but returned "; var_dump($floatVal); - } else { - $diff = abs($floatVal - $floats[$i]) / $floats[$i]; - if ($diff > $epsilon) { - echo "$diff: Expected $floats[$i] but returned "; - var_dump($floatVal); - } } } } else { From 7309fb90b18cd93940f28c6928f152404accb675 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 8 Mar 2019 11:43:36 -0800 Subject: [PATCH 098/249] Fixed the returned values for PDOStatement::getColumnMeta (#946) --- source/pdo_sqlsrv/pdo_stmt.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 507fc94ca..773ad953a 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1020,7 +1020,7 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In // colno - The index of the field for which to return the metadata. // return_value - zval* consisting of the metadata. // Return: -// 0 for failure, 1 for success. +// FAILURE for failure, SUCCESS for success. int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value TSRMLS_DC) { PDO_RESET_STMT_ERROR; @@ -1096,14 +1096,14 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } catch( core::CoreException& ) { - return 0; + return FAILURE; } catch(...) { DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); } - return 1; + return SUCCESS; } From 1332e3b5f89adf4b9bbd2f6d8fa7ae338e94f1f3 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 13 Mar 2019 15:19:05 -0700 Subject: [PATCH 099/249] Onboarding to Azure Pipelines (#949) --- azure-pipelines.yml | 309 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..42a31524f --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,309 @@ +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +variables: + phpVersion: 7.3 + server: 'localhost,1433' + host: 'sql1' + sqlsrv_db: 'sqlsrv_testdb' + pdo_sqlsrv_db: 'pdo_sqlsrv_testdb' + uid: 'sa' + pwd: 'Password456!' + +trigger: +- dev + +jobs: +- job: macOS + pool: + vmImage: 'macOS-10.13' + steps: + - checkout: self + clean: true + fetchDepth: 1 + + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.6' + architecture: 'x64' + + - script: | + brew tap + brew tap homebrew/core + brew install autoconf automake libtool + brew install php@$(phpVersion) + php -v + displayName: 'Install PHP' + + - script: | + echo ready to build extensions + cd $(Build.SourcesDirectory)/source + chmod a+x packagize.sh + ./packagize.sh + + cd $(Build.SourcesDirectory)/source/sqlsrv + ls -al + phpize && ./configure && make && sudo make install + cp run-tests.php $(Build.SourcesDirectory)/test/functional/sqlsrv + + cd $(Build.SourcesDirectory)/source/pdo_sqlsrv + ls -al + phpize && ./configure && make && sudo make install + cp run-tests.php $(Build.SourcesDirectory)/test/functional/pdo_sqlsrv + + echo extension=pdo_sqlsrv.so >> `php --ini | grep "Loaded Configuration File" | sed -e "s|.*:\s*||"` + echo extension=sqlsrv.so >> `php --ini | grep "Loaded Configuration File" | sed -e "s|.*:\s*||"` + + php --ri sqlsrv + php --ri pdo_sqlsrv + displayName: 'Build and install drivers' + +- job: Linux + pool: + vmImage: 'ubuntu-16.04' + steps: + - checkout: self + clean: true + fetchDepth: 1 + + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.6' + architecture: 'x64' + + - script: | + sudo update-alternatives --set php /usr/bin/php$(phpVersion) + sudo update-alternatives --set phar /usr/bin/phar$(phpVersion) + sudo update-alternatives --set phpdbg /usr/bin/phpdbg$(phpVersion) + sudo update-alternatives --set php-cgi /usr/bin/php-cgi$(phpVersion) + sudo update-alternatives --set phar.phar /usr/bin/phar.phar$(phpVersion) + php -version + displayName: 'Use PHP version $(phpVersion)' + + - script: | + echo install ODBC and dependencies + sudo apt-get purge unixodbc + sudo apt autoremove + sudo curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - + curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > mssql-release.list + sudo mv mssql-release.list /etc/apt/sources.list.d/ + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install msodbcsql17 mssql-tools + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc + source ~/.bashrc + sudo apt-get install unixodbc-dev + odbcinst --j + odbcinst -q -d -n "ODBC Driver 17 for SQL Server" + displayName: 'Install prerequisites' + + - script: | + docker pull mcr.microsoft.com/mssql/server:2017-latest + docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=$(pwd)' -p 1433:1433 -h $(host) --name=$(host) -d mcr.microsoft.com/mssql/server:2017-latest + docker ps -a + sqlcmd -S $(server) -U $(uid) -P $(pwd) -Q 'select @@Version' + displayName: 'Run SQL Server for Linux' + + - script: | + sudo sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen + sudo locale-gen en_US + sudo locale-gen en_US.UTF-8 + export LANG='en_US.UTF-8' + export LANGUAGE='en_US:en' + export LC_ALL='en_US.UTF-8' + displayName: 'Generate locales for testing' + + - script: | + echo setting env variables + export TEST_PHP_SQL_SERVER='$(server)' + export TEST_PHP_SQL_UID='$(uid)' + export TEST_PHP_SQL_PWD='$(pwd)' + + cd $(Build.SourcesDirectory)/test/functional/setup + python ./setup_dbs.py -dbname $(sqlsrv_db) + python ./setup_dbs.py -dbname $(pdo_sqlsrv_db) + displayName: 'Set up test databases' + + - script: | + echo ready to build extensions + cd $(Build.SourcesDirectory)/source + chmod a+x packagize.sh + ./packagize.sh + + dest=`php --ini | grep "Scan for additional .ini files" | sudo sed -e "s|.*:\s*||"`/ + + cd $(Build.SourcesDirectory)/source/sqlsrv + ls -al + phpize && ./configure && make && sudo make install + cp run-tests.php $(Build.SourcesDirectory)/test/functional/sqlsrv + echo extension=sqlsrv.so >> 20-sqlsrv.ini + + echo copying sqlsrv to $dest + sudo cp 20-sqlsrv.ini $dest + + cd $(Build.SourcesDirectory)/source/pdo_sqlsrv + ls -al + phpize && ./configure && make && sudo make install + cp run-tests.php $(Build.SourcesDirectory)/test/functional/pdo_sqlsrv + echo extension=pdo_sqlsrv.so >> 30-pdo_sqlsrv.ini + + echo copying pdo_sqlsrv to $dest + sudo cp 30-pdo_sqlsrv.ini $dest + + php --ri sqlsrv + php --ri pdo_sqlsrv + displayName: 'Build and install drivers' + + - script: | + cd $(Build.SourcesDirectory)/test/functional/sqlsrv + sed -i -e 's/TARGET_SERVER/'"$(server)"'/g' MsSetup.inc + sed -i -e 's/TARGET_DATABASE/'"$(sqlsrv_db)"'/g' MsSetup.inc + sed -i -e 's/TARGET_USERNAME/'"$(uid)"'/g' MsSetup.inc + sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc + + php run-tests.php -P ./*.phpt 2>&1 | tee ../sqlsrv.log + displayName: 'Run sqlsrv functional tests' + + - script: | + cd $(Build.SourcesDirectory)/test/functional/pdo_sqlsrv + sed -i -e 's/TARGET_SERVER/'"$(server)"'/g' MsSetup.inc + sed -i -e 's/TARGET_DATABASE/'"$(pdo_sqlsrv_db)"'/g' MsSetup.inc + sed -i -e 's/TARGET_USERNAME/'"$(uid)"'/g' MsSetup.inc + sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc + + php run-tests.php -P ./*.phpt 2>&1 | tee ../pdo_sqlsrv.log + displayName: 'Run pdo_sqlsrv functional tests' + + - script: | + cd $(Build.SourcesDirectory)/test/functional/ + for f in sqlsrv/*.diff; do ls $f 2>/dev/null; cat $f 2>/dev/null; echo ''; done || true + for f in pdo_sqlsrv/*.diff; do ls $f 2>/dev/null; cat $f 2>/dev/null; echo ''; done || true + python output.py + ls -l *.xml + displayName: 'Processing test results' + + - task: PublishTestResults@2 + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '*.xml' + failTaskOnFailedTests: true + searchFolder: '$(Build.SourcesDirectory)/test/functional/' + + - script: | + docker stop $(host) + docker rm $(host) + displayName: 'Stop SQL Server for Linux' + condition: always() + +- job: Windows + pool: + vmImage: 'vs2017-win2016' + steps: + - checkout: self + clean: true + fetchDepth: 1 + + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.6' + architecture: 'x64' + + - script: | + dir C:\tools\php\php* + dir C:\tools\php\ext\ + echo extension_dir=C:\tools\php\ext >> C:\tools\php\php.ini + php --ini + php -v + displayName: 'Check PHP' + + - powershell: | + cd $(Build.SourcesDirectory)\test\functional\sqlsrv + (Get-Content .\MsSetup.inc) | ForEach-Object { $_ -replace "TARGET_SERVER", "$(host)" -replace "TARGET_DATABASE", "$(sqlsrv_db)" -replace "TARGET_USERNAME", "$(uid)" -replace "TARGET_PASSWORD", "$(pwd)" } | Set-Content .\MsSetup.inc + Select-String $(host) .\MsSetup.inc + Select-String $(sqlsrv_db) .\MsSetup.inc + cd $(Build.SourcesDirectory)\test\functional\pdo_sqlsrv + (Get-Content .\MsSetup.inc) | ForEach-Object { $_ -replace "TARGET_SERVER", "$(host)" -replace "TARGET_DATABASE", "$(pdo_sqlsrv_db)" -replace "TARGET_USERNAME", "$(uid)" -replace "TARGET_PASSWORD", "$(pwd)" } | Set-Content .\MsSetup.inc + Select-String $(host) .\MsSetup.inc + Select-String $(pdo_sqlsrv_db) .\MsSetup.inc + displayName: 'Update connection credentials' + condition: false + + - powershell: | + $client = New-Object Net.WebClient + $client.DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.3.1.1_x64.msi', 'msodbcsql_17.3.1.1_x64.msi') + $client.DownloadFile('https://download.microsoft.com/download/D/5/E/D5EEF288-A277-45C8-855B-8E2CB7E25B96/x64/msodbcsql.msi', 'msodbcsql_13.1.msi') + $client.DownloadFile('https://download.microsoft.com/download/4/C/C/4CC1A229-3C56-4A7F-A3BA-F903C73E5895/EN/x64/MsSqlCmdLnUtils.msi', 'MsSqlCmdLnUtils.msi') + dir *.msi + displayName: 'Download ODBC msi and sql tools msi' + condition: false + + - script: | + msiexec /i "msodbcsql_17.3.1.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL + reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server" + dir %WINDIR%\System32\msodbcsql*.dll + displayName: 'Install ODBC driver' + condition: false + + # TOFIX: Install ODBC 13.1 because of SQLCMD 15 installation bug -- this step should be removed later + - script: msiexec /i "msodbcsql_13.1.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL + condition: false + + # FOR SOME REASON the set up did not set the PATH + - script: | + msiexec /i "MsSqlCmdLnUtils.msi" /qn IACCEPTMSSQLCMDLNUTILSLICENSETERMS=YES + displayName: 'Install SQL command line utilities version 15' + condition: false + + - powershell: | + $client = New-Object Net.WebClient + $client.Headers.Add("user-agent", "azure pipeline build") + $client.DownloadFile("https://windows.php.net/downloads/releases/sha256sum.txt", "sha256sum.txt") + $env:VERSION=type sha256sum.txt | where { $_ -match "php-($(phpVersion)\.\d+)-src" } | foreach { $matches[1] } + Write-Host "Latest PHP $(phpVersion) is ${env:VERSION}" + cd $(Build.SourcesDirectory)/buildscripts/ + python builddrivers.py --PHPVER=${env:VERSION} --ARCH=x64 --THREAD=nts --SOURCE=$(Build.SourcesDirectory)/source --TESTING --NO_RENAME + cp php-sdk\phpdev\vc15\x64\php-${env:VERSION}-src\run-tests.php $(Build.SourcesDirectory)\test\functional\sqlsrv + cp php-sdk\phpdev\vc15\x64\php-${env:VERSION}-src\run-tests.php $(Build.SourcesDirectory)\test\functional\pdo_sqlsrv + dir *sqlsrv*.dll + cp *sqlsrv*.dll C:\tools\php\ext\ + displayName: 'Build drivers for the latest version of PHP $(phpVersion)' + + - script: | + echo extension=php_sqlsrv.dll >> C:\tools\php\php.ini + echo extension=php_pdo_sqlsrv.dll >> C:\tools\php\php.ini + php --ri sqlsrv + php --ri pdo_sqlsrv + displayName: 'Load drivers' + + - script: | + docker pull microsoft/mssql-server-windows-developer + docker run -d --name sqlcontainer -h $(host) -p 1433:1433 -e sa_password=$(pwd) -e ACCEPT_EULA=Y microsoft/mssql-server-windows-developer + docker ps -a + displayName: 'Run SQL Server for Windows Server' + condition: false + + - script: | + set path=C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;%path% + sqlcmd -S $(host) -U $(uid) -P $(pwd) -Q "SELECT @@Version" + set TEST_PHP_SQL_SERVER=$(host) + set TEST_PHP_SQL_UID=$(uid) + set TEST_PHP_SQL_PWD=$(pwd) + cd $(Build.SourcesDirectory)\test\functional\setup + python setup_dbs.py -dbname $(sqlsrv_db) + python setup_dbs.py -dbname $(pdo_sqlsrv_db) + displayName: 'Set up test databases' + condition: false + + - script: | + cd $(Build.SourcesDirectory)\test\functional\sqlsrv + php run-tests.php -P sqlsrv_client_info.phpt + cd $(Build.SourcesDirectory)\test\functional\pdo_sqlsrv + php run-tests.php -P pdo_getAttribute_clientInfo.phpt + displayName: 'Smoke testing' + condition: false + + - script: | + docker stop sqlcontainer + docker rm sqlcontainer + displayName: 'Stop SQL Server for Windows Server' + condition: false \ No newline at end of file From 840ebc2378b0c555b29e124562f4224af34b4678 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 14 Mar 2019 11:17:14 -0700 Subject: [PATCH 100/249] Fixed the error in Issue 570 (#952) --- source/shared/core_stream.cpp | 3 +- .../sqlsrv/srv_570_fetch_varbinary.phpt | 143 ++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 test/functional/sqlsrv/srv_570_fetch_varbinary.phpt diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index d822d4a85..7d0e1beb6 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -89,7 +89,8 @@ size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) break; } - SQLRETURN r = SQLGetData( ss->stmt->handle(), ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read ); + // Warnings will be handled below + SQLRETURN r = ss->stmt->current_results->get_data(ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read, false /*handle_warning*/ TSRMLS_CC); CHECK_SQL_ERROR( r, ss->stmt ) { stream->eof = 1; diff --git a/test/functional/sqlsrv/srv_570_fetch_varbinary.phpt b/test/functional/sqlsrv/srv_570_fetch_varbinary.phpt new file mode 100644 index 000000000..34f112403 --- /dev/null +++ b/test/functional/sqlsrv/srv_570_fetch_varbinary.phpt @@ -0,0 +1,143 @@ +--TEST-- +GitHub issue #570 - fetching a varbinary field as a stream using client buffer +--DESCRIPTION-- +Verifies that a varbinary field (with size or max) can be successfully fetched even when a client buffer is used. There is no more "Invalid cursor state" error. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + SQLSRV_CURSOR_CLIENT_BUFFERED, "ClientBufferMaxKBSize" => 51200)); + } else { + $stmt = sqlsrv_prepare($conn, $tsql); + } + if ($stmt === false) { + fatalError("Error in preparing the query ($buffered)."); + } + + $result = sqlsrv_execute($stmt); + if ($result === false) { + fatalError("Error in executing the query ($buffered)."); + } + } else { + if ($buffered) { + // Use the default buffer size in this case + $stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED)); + } else { + $stmt = sqlsrv_query($conn, $tsql); + } + + if ($stmt === false) { + fatalError("Error in sqlsrv_query ($buffered)."); + } + } + + fetchData($stmt, $data, $buffered); +} + +function runTest($conn, $columnType, $path) +{ + $tableName = 'srvTestTable_570' . rand(0, 10); + dropTable($conn, $tableName); + + // Create the test table with only one column + $tsql = "CREATE TABLE $tableName([picture] $columnType NOT NULL)"; + $stmt = sqlsrv_query($conn, $tsql); + if (!$stmt) { + fatalError("Failed to create table $tableName\n"); + } + + // Insert php.gif as stream data + $tsql = "INSERT INTO $tableName (picture) VALUES (?)"; + + $data = fopen($path, 'rb'); + if (!$data) { + fatalError('Could not open image for reading.'); + } + + $params = array($data, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY)); + $stmt = sqlsrv_query($conn, $tsql, array($params)); + if ($stmt === false) { + fatalError("Failed to insert image into $tableName"); + } + do { + $read = sqlsrv_send_stream_data($stmt); + } while ($read); + sqlsrv_free_stmt($stmt); + + // Start testing, with or without client buffer, using prepared statement or direct query + $tsql = "SELECT picture FROM $tableName"; + runQuery($conn, $data, $tsql, false, true); + runQuery($conn, $data, $tsql, true, true); + runQuery($conn, $data, $tsql, false, false); + runQuery($conn, $data, $tsql, true, false); + + // Clean up + fclose($data); + + dropTable($conn, $tableName); +} + +require_once('MsCommon.inc'); + +$conn = connect(); +if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); +} + +if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $pic = '\\php.gif'; +} else { // other than Windows + $pic = '/php.gif'; +} +$path = dirname($_SERVER['PHP_SELF']) . $pic; + +runTest($conn, 'VARBINARY(MAX)', $path); +runTest($conn, 'VARBINARY(4096)', $path); + +echo "Done\n"; + +sqlsrv_close($conn); +?> +--EXPECT-- +Done \ No newline at end of file From bedc4305bd5c77f016f593541e93c38338e49372 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 14 Mar 2019 11:17:40 -0700 Subject: [PATCH 101/249] Added a new status badge on readme (#953) --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 84952fe35..1af72d831 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,16 @@ Thank you for taking the time to participate in our last survey. You can continu ### Status of Most Recent Builds -| AppVeyor (Windows) | Travis CI (Linux) | Coverage (Windows) | Coverage (Linux) | -|--------------------------|--------------------------|---------------------------------------|-------------------------------------------| -| [![av-image][]][av-site] | [![tv-image][]][tv-site] | [![Coverage Codecov][]][codecov-site] | [![Coverage Coveralls][]][coveralls-site] | +Azure Pipelines | AppVeyor (Windows) | Travis CI (Linux) | Coverage (Windows) | Coverage (Linux) | +|---------------------|--------------------------|--------------------------|---------------------------------------|-------------------------------------------| +| [![az-image][]][az-site] | [![av-image][]][av-site] | [![tv-image][]][tv-site] | [![Coverage Codecov][]][codecov-site] | [![Coverage Coveralls][]][coveralls-site] | [av-image]: https://ci.appveyor.com/api/projects/status/vo4rfei6lxlamrnc?svg=true [av-site]: https://ci.appveyor.com/project/msphpsql/msphpsql/branch/dev [tv-image]: https://travis-ci.org/Microsoft/msphpsql.svg?branch=dev [tv-site]: https://travis-ci.org/Microsoft/msphpsql/ +[az-site]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_build/latest?definitionId=6&branchName=dev +[az-image]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_apis/build/status/Microsoft.msphpsql?branchName=dev [Coverage Coveralls]: https://coveralls.io/repos/github/Microsoft/msphpsql/badge.svg?branch=dev [coveralls-site]: https://coveralls.io/github/Microsoft/msphpsql?branch=dev [Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/dev/graph/badge.svg From df8d7da32864d493311bef9f2894660896fc99d1 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 14 Mar 2019 15:15:33 -0700 Subject: [PATCH 102/249] Added new tests for issue 569 (#951) --- .../pdo_sqlsrv/pdo_569_query_varcharmax.phpt | 94 +++++++++++++++ .../sqlsrv/srv_569_query_varcharmax.phpt | 109 ++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt create mode 100644 test/functional/sqlsrv/srv_569_query_varcharmax.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt new file mode 100644 index 000000000..e56399882 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt @@ -0,0 +1,94 @@ +--TEST-- +GitHub issue #569 - direct query on varchar max fields results in function sequence error +--DESCRIPTION-- +Verifies that the problem is no longer reproducible. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $tableName = 'pdoTestTable_569'; + dropTable($conn, $tableName); + + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $tsql = "CREATE TABLE $tableName ([c1] varchar(max) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (ENCRYPTION_TYPE = deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AEColumnKey))"; + } else { + $tsql = "CREATE TABLE $tableName ([c1] varchar(max))"; + } + $conn->exec($tsql); + + $input = 'some very large string'; + $tsql = "INSERT INTO $tableName (c1) VALUES (?)"; + $stmt = $conn->prepare($tsql); + $param = array($input); + $stmt->execute($param); + + $tsql = "SELECT * FROM $tableName"; + try { + $stmt = $conn->prepare($tsql); + $stmt->execute(); + } catch (PDOException $e) { + echo ("Failed to read from $tableName\n"); + echo $e->getMessage(); + } + + $row = $stmt->fetch(PDO::FETCH_NUM); + if ($row[0] !== $input) { + echo "Expected $input but got: "; + var_dump($row[0]); + } + + $tsql2 = "DELETE FROM $tableName"; + $rows = $conn->exec($tsql2); + if ($rows !== 1) { + echo 'Expected 1 row affected but got: '; + var_dump($rows); + } + + // Fetch from the empty table + try { + $stmt = $conn->prepare($tsql); + $stmt->execute(); + } catch (PDOException $e) { + echo ("Failed to read $tableName, now empty\n"); + echo $e->getMessage(); + } + + $result = $stmt->fetch(PDO::FETCH_NUM); + if ($result !== false) { + echo 'Expected bool(false) when fetching an empty table but got: '; + var_dump($result); + } + + // Fetch the same table but using client cursor + $stmt = $conn->prepare($tsql, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL)); + $stmt->execute(); + + $result = $stmt->fetch(); + if ($result !== false) { + echo 'Expected bool(false) when fetching an empty table but got: '; + var_dump($result); + } + + dropTable($conn, $tableName); + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + echo $e->getMessage(); +} + +echo "Done\n"; + +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/srv_569_query_varcharmax.phpt b/test/functional/sqlsrv/srv_569_query_varcharmax.phpt new file mode 100644 index 000000000..1a41a629d --- /dev/null +++ b/test/functional/sqlsrv/srv_569_query_varcharmax.phpt @@ -0,0 +1,109 @@ +--TEST-- +GitHub issue #569 - sqlsrv_query on varchar max fields results in function sequence error +--DESCRIPTION-- +Verifies that the problem is no longer reproducible. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $database, "UID" => $userName, "PWD" => $userPassword, "ColumnEncryption" => "Enabled"); +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn === false) { + fatalError("Failed to connect to $server."); +} + +$tableName = 'srvTestTable_569'; + +dropTable($conn, $tableName); + +if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $tsql = "CREATE TABLE $tableName ([c1] varchar(max) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (ENCRYPTION_TYPE = deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AEColumnKey))"; +} else { + $tsql = "CREATE TABLE $tableName ([c1] varchar(max))"; +} + +$stmt = sqlsrv_query($conn, $tsql); +if (!$stmt) { + fatalError("Failed to create $tableName"); +} + +$input = 'some very large string'; +$stmt = sqlsrv_prepare($conn, "INSERT INTO $tableName (c1) VALUES (?)", array($input)); +sqlsrv_execute($stmt); + +$tsql = "SELECT * FROM $tableName"; +$stmt = sqlsrv_query($conn, $tsql); +if (!$stmt) { + fatalError("Failed to read from $tableName"); +} + +sqlsrv_fetch($stmt); +$fieldVal = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); + +if ($fieldVal !== $input) { + echo "Expected $input but got: "; + var_dump($fieldVal); +} + +$tsql2 = "DELETE FROM $tableName"; +$stmt = sqlsrv_query($conn, $tsql2); +if (!$stmt) { + fatalError("Failed to delete rows from $tableName"); +} + +$stmt = sqlsrv_query($conn, $tsql); +if (!$stmt) { + fatalError("Failed to read $tableName, now empty"); +} + +$result = sqlsrv_fetch($stmt); +if (!is_null($result)) { + echo 'Expected null when fetching an empty table but got: '; + var_dump($result); +} + +$fieldVal = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); +verifyFetchError(); +if ($fieldVal !== false) { + echo 'Expected bool(false) but got: '; + var_dump($fieldVal); +} + +$stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>"buffered")); +$result = sqlsrv_fetch($stmt); +if (!is_null($result)) { + echo 'Expected null when fetching an empty table but got: '; + var_dump($result); +} + +$fieldVal = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); +verifyFetchError(); +if ($fieldVal !== false) { + echo 'Expected bool(false) but got: '; + var_dump($fieldVal); +} + + +dropTable($conn, $tableName); + +echo "Done\n"; + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +Done \ No newline at end of file From 15f61bd0b4e095978cddb2de9671f3d324fc17c3 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 18 Mar 2019 08:23:31 -0700 Subject: [PATCH 103/249] Fix issue 955 - errors building sqlsrv alone (#956) --- azure-pipelines.yml | 6 ++++-- source/sqlsrv/config.w32 | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 42a31524f..ab688eae8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -261,12 +261,14 @@ jobs: $env:VERSION=type sha256sum.txt | where { $_ -match "php-($(phpVersion)\.\d+)-src" } | foreach { $matches[1] } Write-Host "Latest PHP $(phpVersion) is ${env:VERSION}" cd $(Build.SourcesDirectory)/buildscripts/ - python builddrivers.py --PHPVER=${env:VERSION} --ARCH=x64 --THREAD=nts --SOURCE=$(Build.SourcesDirectory)/source --TESTING --NO_RENAME + python builddrivers.py --PHPVER=${env:VERSION} --DRIVER=sqlsrv --ARCH=x64 --THREAD=nts --SOURCE=$(Build.SourcesDirectory)/source --TESTING --NO_RENAME + dir *sqlsrv*.dll + python builddrivers.py --PHPVER=${env:VERSION} --DRIVER=pdo_sqlsrv --ARCH=x64 --THREAD=nts --SOURCE=$(Build.SourcesDirectory)/source --TESTING --NO_RENAME cp php-sdk\phpdev\vc15\x64\php-${env:VERSION}-src\run-tests.php $(Build.SourcesDirectory)\test\functional\sqlsrv cp php-sdk\phpdev\vc15\x64\php-${env:VERSION}-src\run-tests.php $(Build.SourcesDirectory)\test\functional\pdo_sqlsrv dir *sqlsrv*.dll cp *sqlsrv*.dll C:\tools\php\ext\ - displayName: 'Build drivers for the latest version of PHP $(phpVersion)' + displayName: 'Build drivers (separately) for the latest version of PHP $(phpVersion)' - script: | echo extension=php_sqlsrv.dll >> C:\tools\php\php.ini diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 5a2477e99..887d1e775 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -27,7 +27,7 @@ if( PHP_SQLSRV != "no" ) { if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") && CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")&& CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_SQLSRV", configure_module_dirname + "\\shared")) { - if (PHP_PDO_SQLSRV == "no" || PHP_SQLSRV_SHARED) { + if (PHP_SQLSRV_SHARED || PHP_PDO_SQLSRV == "no") { ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "sqlsrv" ); } CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_SQLSRV_ODBC"); From 7f56eab86e7d6d6ad5cfcc750265ab6040b9bbd2 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 18 Mar 2019 08:46:20 -0700 Subject: [PATCH 104/249] Modified test_largeData for Linux CI (#954) --- test/functional/sqlsrv/test_largeData.phpt | 143 +++++++++++++-------- 1 file changed, 91 insertions(+), 52 deletions(-) diff --git a/test/functional/sqlsrv/test_largeData.phpt b/test/functional/sqlsrv/test_largeData.phpt index 4fa2d830a..72feabf8a 100644 --- a/test/functional/sqlsrv/test_largeData.phpt +++ b/test/functional/sqlsrv/test_largeData.phpt @@ -1,119 +1,158 @@ --TEST-- -send a large amount (10MB) using encryption. +Send a large amount (10MB) using encryption. In a Linux CI environment use a smaller size. --SKIPIF-- --FILE-- total_read = 0; return true; } - function stream_read( $count ) + public function stream_read($count) { - if( $this->total_read > 20000000 ) { + global $limit; + if ($this->total_read > $limit) { return 0; } global $packets; ++$packets; - $str = str_repeat( "A", $count ); + + // 8192 is passed to stream_read as $count + $str = str_repeat("A", $count); $this->total_read += $count; return $str; } - function stream_write($data) + public function stream_write($data) { } - function stream_tell() + public function stream_tell() { return $this->total_read; } - function stream_eof() + public function stream_eof() { - return $this->total_read > 20000000; + global $limit; + return $this->total_read > $limit; } - function stream_seek($offset, $whence) + public function stream_seek($offset, $whence) { // For the purpose of this test only support SEEK_SET to $offset 0 if ($whence == SEEK_SET && $offset == 0) { $this->total_read = $offset; return true; - } + } + return false; + } +} + +function isServerInLinux($conn) +{ + // This checks if SQL Server is running in Linux (Docker) in a CI environment + // If so, the major version must be 14 or above (SQL Server 2017 or above) + $serverVer = sqlsrv_server_info($conn)['SQLServerVersion']; + if (explode('.', $serverVer)[0] < 14) { return false; } + + // The view sys.dm_os_host_info, available starting in SQL Server 2017, is somewhat similar to sys.dm_os_windows_info. + // It returns one row that displays operating system version information and has columns to differentiate + // Windows and Linux. + $stmt = sqlsrv_query($conn, 'SELECT host_platform FROM sys.dm_os_host_info'); + if ($stmt && sqlsrv_fetch($stmt)) { + $host = sqlsrv_get_field($stmt, 0); + return ($host === 'Linux'); + } + + return false; } set_time_limit(0); -sqlsrv_configure( 'WarningsReturnAsErrors', 0 ); -sqlsrv_configure( 'LogSubsystems', SQLSRV_LOG_SYSTEM_ALL ); +sqlsrv_configure('WarningsReturnAsErrors', 0); +sqlsrv_configure('LogSubsystems', SQLSRV_LOG_SYSTEM_ALL); $packets = 0; +$limit = 20000000; -$result = stream_wrapper_register( "mystr", "my_stream" ); -if( !$result ) { - die( "Couldn't register stream class." ); +$result = stream_wrapper_register("mystr", "my_stream"); +if (!$result) { + die("Couldn't register stream class."); } -require( 'MsCommon.inc' ); +require_once('MsCommon.inc'); $conn = Connect(array( 'Encrypt' => true, 'TrustServerCertificate' => true )); -if( $conn === false ) { - die( print_r( sqlsrv_errors(), true )); +if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); } -$stmt = sqlsrv_query( $conn, "IF OBJECT_ID('test_lob', 'U') IS NOT NULL DROP TABLE test_lob" ); -if( $stmt !== false ) sqlsrv_free_stmt( $stmt ); +// In a Linux CI environment use a smaller size +if (isServerInLinux($conn)) { + $limit /= 100; +} + +$stmt = sqlsrv_query($conn, "IF OBJECT_ID('test_lob', 'U') IS NOT NULL DROP TABLE test_lob"); +if ($stmt !== false) { + sqlsrv_free_stmt($stmt); +} -$stmt = sqlsrv_query( $conn, "CREATE TABLE test_lob (id tinyint, stuff varbinary(max))" ); -if( $stmt === false ) { - die( print_r( sqlsrv_errors(), true )); +$stmt = sqlsrv_query($conn, "CREATE TABLE test_lob (id tinyint, stuff varbinary(max))"); +if ($stmt === false) { + die(print_r(sqlsrv_errors(), true)); } -sqlsrv_free_stmt( $stmt ); +sqlsrv_free_stmt($stmt); -$lob = fopen( "mystr://test_data", "rb" ); -if( !$lob ) { - die( "failed opening test stream.\n" ); +$lob = fopen("mystr://test_data", "rb"); +if (!$lob) { + die("failed opening test stream.\n"); } -$stmt = sqlsrv_query( $conn, "INSERT INTO test_lob (id, stuff) VALUES (?,?)", array( 1, array( $lob, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), SQLSRV_SQLTYPE_VARBINARY('max')))); -if( $stmt === false ) { - die( print_r( sqlsrv_errors(), true )); +$stmt = sqlsrv_query($conn, "INSERT INTO test_lob (id, stuff) VALUES (?,?)", array( 1, array( $lob, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), SQLSRV_SQLTYPE_VARBINARY('max')))); +if ($stmt === false) { + die(print_r(sqlsrv_errors(), true)); } -while( $result = sqlsrv_send_stream_data( $stmt )) { +while ($result = sqlsrv_send_stream_data($stmt)) { ++$packets; } -if( $result === false ) { - die( print_r( sqlsrv_errors(), true )); +if ($result === false) { + die(print_r(sqlsrv_errors(), true)); } -echo "$packets sent.\n"; -$stmt = sqlsrv_query( $conn, "SELECT LEN(stuff) FROM test_lob" ); -if( $stmt === false ) { - die( print_r( sqlsrv_errors(), true )); +// Number of packets sent should be $limit / 8192 (rounded up) +// Length of the varbinary = $packetsSent * 8192 + 1 (HEX 30 appended at the end) +$packetsSent = ceil($limit / 8192); +$length = $packetsSent * 8192 + 1; +if ($packets != $packetsSent) { + echo "$packets sent.\n"; } -while( $result = sqlsrv_fetch_array( $stmt )) { - print_r( $result ); + +$stmt = sqlsrv_query($conn, "SELECT LEN(stuff) FROM test_lob"); +if ($stmt === false) { + die(print_r(sqlsrv_errors(), true)); +} +while ($result = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC)) { + if ($result[0] != $length) { + print_r($result); + } } -sqlsrv_query( $conn, "DROP TABLE test_lob" ); +sqlsrv_query($conn, "DROP TABLE test_lob"); -sqlsrv_free_stmt( $stmt ); -sqlsrv_close( $conn ); +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); sleep(10); // since this is a long test, we give the database some time to finish + +echo "Done\n"; ?> --EXPECT-- -2442 sent. -Array -( - [0] => 20004865 - [] => 20004865 -) +Done From a6cee775f7ba469da3cbc7aeb96d5eb2d5125b22 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 27 Feb 2019 16:54:26 -0800 Subject: [PATCH 105/249] Issue 937 - fixed ASSERT and added new tests (#940) (cherry picked from commit 12d01c918966f5ff908adb25d5586944949b88be) --- source/pdo_sqlsrv/pdo_stmt.cpp | 18 ++- source/sqlsrv/stmt.cpp | 2 +- .../pdo_sqlsrv/pdo_937_metadata.phpt | 122 +++++++++++++++++ .../pdostatement_getColumnMeta.phpt | 3 +- ...tement_getColumnMeta_unicode_col_name.phpt | 3 +- test/functional/sqlsrv/srv_937_metadata.phpt | 125 ++++++++++++++++++ 6 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_937_metadata.phpt create mode 100644 test/functional/sqlsrv/srv_937_metadata.phpt diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index cd8c697f1..507fc94ca 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1029,20 +1029,28 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno try { SQLSRV_ASSERT( stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: pdo_stmt object was null" ); - SQLSRV_ASSERT( stmt->columns != NULL, "pdo_sqlsrv_stmt_get_col_meta: columns are not available." ); SQLSRV_ASSERT( Z_TYPE_P( return_value ) == IS_NULL, "Metadata already has value. Must be NULL." ); - sqlsrv_malloc_auto_ptr core_meta_data; - sqlsrv_stmt* driver_stmt = static_cast( stmt->driver_data ); SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_get_col_meta: stmt->driver_data was null"); - SQLSRV_ASSERT( colno >= 0 && colno < stmt->column_count, "pdo_sqlsrv_stmt_get_col_meta: invalid column number." ); + // Based on PDOStatement::getColumnMeta API, this should return FALSE + // if the requested column does not exist in the result set, or if + // no result set exists. Thus, do not use SQLSRV_ASSERT, which causes + // the script to fail right away. Instead, log this warning if logging + // is enabled + if (colno < 0 || colno >= stmt->column_count || stmt->columns == NULL) { + LOG( SEV_WARNING, "Invalid column number %1!d!", colno ); + return FAILURE; + } - core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); // initialize the array to nothing, as PDO requires us to create it core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); + sqlsrv_malloc_auto_ptr core_meta_data; + + core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); + // add the following fields: flags, native_type, driver:decl_type, table add_assoc_long( return_value, "flags", 0 ); diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index c366e0305..9d60de25f 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1807,7 +1807,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) throw; } - SQLSRV_ASSERT(num_cols > 0 && stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" ); + SQLSRV_ASSERT(stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" ); return num_cols; } diff --git a/test/functional/pdo_sqlsrv/pdo_937_metadata.phpt b/test/functional/pdo_sqlsrv/pdo_937_metadata.phpt new file mode 100644 index 000000000..d0cad7e75 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_937_metadata.phpt @@ -0,0 +1,122 @@ +--TEST-- +GitHub issue 937 - getting metadata will not fail after an UPDATE / DELETE statement +--DESCRIPTION-- +Verifies that getColumnMeta will not fail after processing an UPDATE / DELETE query that returns no fields. Instead, it should simply return FALSE because no result set exists. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +getColumnMeta(0); + if ($metadata !== FALSE) { + echo "Expects FALSE because no result set exists!\n"; + } +} + +try { + $conn = connect(); + + dropTable($conn, $tableName); + dropProc($conn, $procName); + + $tsql = "CREATE TABLE $tableName([id] [int] NOT NULL, [name] [varchar](10) NOT NULL)"; + $conn->query($tsql); + + $id = 3; + $tsql = "INSERT INTO $tableName VALUES ($id, 'abcde')"; + $conn->query($tsql); + + $tsql = "UPDATE $tableName SET name = 'updated' WHERE id = $id"; + $stmt = $conn->prepare($tsql); + $stmt->execute(); + $numCol = $metadata = $stmt->columnCount(); + echo "Number of columns after UPDATE: $numCol\n"; + checkMetaData($stmt); + + $tsql = "SELECT * FROM $tableName"; + $stmt = $conn->query($tsql); + $numCol = $metadata = $stmt->columnCount(); + for ($i = 0; $i < $numCol; $i++) { + $metadata = $stmt->getColumnMeta($i); + var_dump($metadata); + } + + createProc($conn, $procName, "@id int, @val varchar(10) OUTPUT", "SELECT @val = name FROM $tableName WHERE id = @id"); + + $value = ''; + $tsql = "{CALL [$procName] (?, ?)}"; + $stmt = $conn->prepare($tsql); + $stmt->bindParam(1, $id, PDO::PARAM_INT); + $stmt->bindParam(2, $value, PDO::PARAM_STR, 10); + $stmt->execute(); + $numCol = $metadata = $stmt->columnCount(); + echo "Number of columns after PROCEDURE: $numCol\n"; + echo "Value returned: $value\n"; + checkMetaData($stmt); + + $query = "DELETE FROM $tableName WHERE name = 'updated'"; + $stmt = $conn->query($query); + $numCol = $metadata = $stmt->columnCount(); + echo "Number of columns after DELETE: $numCol\n"; + checkMetaData($stmt); +} catch (PDOException $e) { + echo $e->getMessage() . PHP_EOL; +} + +dropTable($conn, $tableName); +dropProc($conn, $procName); + +unset($stmt); +unset($conn); + +?> +--EXPECT-- +Number of columns after UPDATE: 0 +array(8) { + ["flags"]=> + int(0) + ["sqlsrv:decl_type"]=> + string(3) "int" + ["native_type"]=> + string(6) "string" + ["table"]=> + string(0) "" + ["pdo_type"]=> + int(2) + ["name"]=> + string(2) "id" + ["len"]=> + int(10) + ["precision"]=> + int(0) +} +array(8) { + ["flags"]=> + int(0) + ["sqlsrv:decl_type"]=> + string(7) "varchar" + ["native_type"]=> + string(6) "string" + ["table"]=> + string(0) "" + ["pdo_type"]=> + int(2) + ["name"]=> + string(4) "name" + ["len"]=> + int(10) + ["precision"]=> + int(0) +} +Number of columns after PROCEDURE: 0 +Value returned: updated +Number of columns after DELETE: 0 diff --git a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt index f42d1d3d3..40805dc23 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt @@ -220,5 +220,4 @@ array(7) { Warning: PDOStatement::getColumnMeta(): SQLSTATE[42P10]: Invalid column reference: column number must be non-negative in %s on line %x bool(false) - -Fatal error: pdo_sqlsrv_stmt_get_col_meta: invalid column number. in %s on line %x +bool(false) diff --git a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt index bb01be10a..e7075ee46 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt @@ -259,5 +259,4 @@ array(7) { Warning: PDOStatement::getColumnMeta(): SQLSTATE[42P10]: Invalid column reference: column number must be non-negative in %s on line %x bool(false) - -Fatal error: pdo_sqlsrv_stmt_get_col_meta: invalid column number. in %s on line %x +bool(false) \ No newline at end of file diff --git a/test/functional/sqlsrv/srv_937_metadata.phpt b/test/functional/sqlsrv/srv_937_metadata.phpt new file mode 100644 index 000000000..47c9452fe --- /dev/null +++ b/test/functional/sqlsrv/srv_937_metadata.phpt @@ -0,0 +1,125 @@ +--TEST-- +GitHub issue #937 - getting metadata will not fail after an UPDATE / DELETE statement +--DESCRIPTION-- +Verifies that sqlsrv_field_metadata will return an empty array after processing an +UPDATE / DELETE query that returns no fields. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "buffered"); +$tsql = "DELETE FROM $tableName WHERE dummyColumn = 'updated'"; +$stmt = sqlsrv_query($conn, $tsql, array(), $options); +$fieldmeta = sqlsrv_field_metadata($stmt); +var_dump($fieldmeta); + +dropTable($conn, $tableName); +dropProc($conn, $procName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +array(2) { + [0]=> + array(6) { + ["Name"]=> + string(2) "id" + ["Type"]=> + int(4) + ["Size"]=> + NULL + ["Precision"]=> + int(10) + ["Scale"]=> + NULL + ["Nullable"]=> + int(0) + } + [1]=> + array(6) { + ["Name"]=> + string(11) "dummyColumn" + ["Type"]=> + int(12) + ["Size"]=> + int(10) + ["Precision"]=> + NULL + ["Scale"]=> + NULL + ["Nullable"]=> + int(0) + } +} +array(0) { +} +The value returned: updated +array(0) { +} +array(0) { +} \ No newline at end of file From 763913d7cabcf03e7c5c3d9ccdc57bc180f5a97c Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 8 Mar 2019 11:43:36 -0800 Subject: [PATCH 106/249] Fixed the returned values for PDOStatement::getColumnMeta (#946) (cherry picked from commit 7309fb90b18cd93940f28c6928f152404accb675) --- source/pdo_sqlsrv/pdo_stmt.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 507fc94ca..773ad953a 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1020,7 +1020,7 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In // colno - The index of the field for which to return the metadata. // return_value - zval* consisting of the metadata. // Return: -// 0 for failure, 1 for success. +// FAILURE for failure, SUCCESS for success. int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value TSRMLS_DC) { PDO_RESET_STMT_ERROR; @@ -1096,14 +1096,14 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } catch( core::CoreException& ) { - return 0; + return FAILURE; } catch(...) { DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); } - return 1; + return SUCCESS; } From 2c8f8caf8df5c32eb82e91f038dd9e66b6a31bde Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 18 Mar 2019 08:23:31 -0700 Subject: [PATCH 107/249] Fix issue 955 - errors building sqlsrv alone (#956) (cherry picked from commit 15f61bd0b4e095978cddb2de9671f3d324fc17c3) --- source/sqlsrv/config.w32 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 5a2477e99..887d1e775 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -27,7 +27,7 @@ if( PHP_SQLSRV != "no" ) { if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") && CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")&& CHECK_HEADER_ADD_INCLUDE( "core_sqlsrv.h", "CFLAGS_SQLSRV", configure_module_dirname + "\\shared")) { - if (PHP_PDO_SQLSRV == "no" || PHP_SQLSRV_SHARED) { + if (PHP_SQLSRV_SHARED || PHP_PDO_SQLSRV == "no") { ADD_SOURCES( configure_module_dirname + "\\shared", shared_src_class, "sqlsrv" ); } CHECK_HEADER_ADD_INCLUDE("sql.h", "CFLAGS_SQLSRV_ODBC"); From 3c712135755ece80a73cdd6ecffe34ff6993e337 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 18 Mar 2019 16:50:19 -0700 Subject: [PATCH 108/249] 5.6.1 hotfix --- CHANGELOG.md | 25 +++++++++++++++++++++++++ source/shared/version.h | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 946ce626a..d93cbd2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.6.1 - 2019-03-19 +Updated PECL release packages. Here is the list of updates: + +### Fixed +- Issue [#937](https://github.com/Microsoft/msphpsql/issues/937) - removed problematic ASSERTs when getting metadata and used logging for invalid column numbers in PDOStatement::getColumnMeta() +- Issue [#955](https://github.com/Microsoft/msphpsql/issues/955) - modified sqlsrv config file such that it can be compiled independently +- Pull Request [#946](https://github.com/Microsoft/msphpsql/pull/946) - fixed the implementation of PDOStatement::getColumnMeta() such that getColumnMeta() will return false when something goes wrong + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) +- In SUSE 15, Azure Active Directory connections may fail if PHP is installed from packages (Issue [#934](https://github.com/Microsoft/msphpsql/issues/934)) + ## 5.6.0 - 2019-02-15 Updated PECL release packages. Here is the list of updates: diff --git a/source/shared/version.h b/source/shared/version.h index c7c554813..7424d9001 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -27,7 +27,7 @@ // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 #define SQLVERSION_MINOR 6 -#define SQLVERSION_PATCH 0 +#define SQLVERSION_PATCH 1 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 From db488ca22da31d29a28e8cc40196dc1a979618ae Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 19 Mar 2019 14:30:23 -0700 Subject: [PATCH 109/249] Updated change log --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d93cbd2cb..b7916f19b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) Updated PECL release packages. Here is the list of updates: ### Fixed -- Issue [#937](https://github.com/Microsoft/msphpsql/issues/937) - removed problematic ASSERTs when getting metadata and used logging for invalid column numbers in PDOStatement::getColumnMeta() -- Issue [#955](https://github.com/Microsoft/msphpsql/issues/955) - modified sqlsrv config file such that it can be compiled independently -- Pull Request [#946](https://github.com/Microsoft/msphpsql/pull/946) - fixed the implementation of PDOStatement::getColumnMeta() such that getColumnMeta() will return false when something goes wrong +- Issue [#937](https://github.com/Microsoft/msphpsql/issues/937) - fixed assumptions made when calculating field or column metadata which may have resulted in application termination +- Issue [#955](https://github.com/Microsoft/msphpsql/issues/955) - modified sqlsrv config file such that it can be compiled independently of pdo_sqlsrv +- Pull Request [#946](https://github.com/Microsoft/msphpsql/pull/946) - fixed PDOStatement::getColumnMeta() to return false when something goes wrong ### Limitations - No support for inout / output params when using sql_variant type From a99e7c30bae87a83cf6057280ca62002959b85fa Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 29 Mar 2019 08:28:46 -0700 Subject: [PATCH 110/249] Tests modified for language option for SQL Azure (#963) --- .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 23 ++++++++ .../pdo_sqlsrv/pdo_929_language_option.phpt | 54 ++++++++++++++++++ test/functional/sqlsrv/MsCommon.inc | 20 +++++++ .../sqlsrv/test_error_encoding.phpt | 52 ++++++++++------- ...t_error_encoding_with_language_option.phpt | 56 ++++++++++++------- 5 files changed, 164 insertions(+), 41 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_929_language_option.phpt diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index d327d8599..969d14469 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -624,6 +624,29 @@ function IsDaasMode() return ($daasMode ? true : false); } +function isSQLAzure() +{ + // 'SQL Azure' indicates SQL Database or SQL Data Warehouse + // For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql + try { + $conn = connect(); + $tsql = "SELECT SERVERPROPERTY ('edition')"; + $stmt = $conn->query($tsql); + + $result = $stmt->fetch(PDO::FETCH_NUM); + $edition = $result[0]; + + if ($edition === "SQL Azure") { + return true; + } else { + return false; + } + } catch (Exception $e) { + echo $e->getMessage(); + die("Could not fetch server property."); + } +} + function isAzureDW() { // Check if running Azure Data Warehouse diff --git a/test/functional/pdo_sqlsrv/pdo_929_language_option.phpt b/test/functional/pdo_sqlsrv/pdo_929_language_option.phpt new file mode 100644 index 000000000..870a84578 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_929_language_option.phpt @@ -0,0 +1,54 @@ +--TEST-- +GitHub issue 929 - able to change the language when connecting +--DESCRIPTION-- +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +getCode(); + if ($code !== '42S22') { + echo "Expected SQLSTATE 42S22\n"; + var_dump($code); + } + + // The error message is different when testing against Azure DB / Data Warehouse + // Use wildcard patterns for matching + if (isSQLAzure()) { + $expected = "*Invalid column name [\"']BadColumn[\"']\."; + } else { + $expected = "*Ungültiger Spaltenname [\"']BadColumn[\"']\."; + } + + $message = $e->getMessage(); + if (!fnmatch($expected, $message)) { + echo "Expected to find $expected in the error message\n"; + var_dump($message); + } + +} + +require_once("MsSetup.inc"); + +try { + $conn = new PDO("sqlsrv:server=$server;Language = German", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $tsql = "SELECT *, BadColumn FROM sys.syslanguages"; + $conn->query($tsql); + echo 'This should have failed!\n'; +} catch (PDOException $e) { + verifyErrorContents($e); +} + +unset($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index eb4933b72..dec27c554 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -84,6 +84,26 @@ function isDaasMode() return ($daasMode ? true : false); } +function isSQLAzure() +{ + // 'SQL Azure' indicates SQL Database or SQL Data Warehouse + // For details, https://docs.microsoft.com/sql/t-sql/functions/serverproperty-transact-sql + $conn = connect(); + $tsql = "SELECT SERVERPROPERTY ('edition')"; + $stmt = sqlsrv_query($conn, $tsql); + + if (sqlsrv_fetch($stmt)) { + $edition = sqlsrv_get_field($stmt, 0); + if ($edition === "SQL Azure") { + return true; + } else { + return false; + } + } else { + die("Could not fetch server property."); + } +} + function isAzureDW() { // Check if running Azure Data Warehouse diff --git a/test/functional/sqlsrv/test_error_encoding.phpt b/test/functional/sqlsrv/test_error_encoding.phpt index 76302e20b..c14b41ced 100644 --- a/test/functional/sqlsrv/test_error_encoding.phpt +++ b/test/functional/sqlsrv/test_error_encoding.phpt @@ -4,11 +4,35 @@ Encoding of sqlsrv errors --FILE-- 'UTF-8' )); +$connectionOptions = array('UID' => $userName, 'PWD' => $userPassword, 'CharacterSet' => 'UTF-8'); +$conn = sqlsrv_connect($server, $connectionOptions); if (!$conn) { die(print_r(sqlsrv_errors(), true)); } @@ -22,27 +46,15 @@ sqlsrv_free_stmt($stmt); $stmt = sqlsrv_query($conn, "select *, BadColumn from sys.syslanguages"); if ($stmt) { - echo 'OK!'; + echo 'This should have failed!\n'; sqlsrv_free_stmt($stmt); } else { - $errs = sqlsrv_errors(); - print_r($errs); + verifyErrorContents(); } sqlsrv_close($conn); +echo "Done\n"; ?> ---EXPECTF-- -Array -( - [0] => Array - ( - [0] => 42S22 - [SQLSTATE] => 42S22 - [1] => 207 - [code] => 207 - [2] => %SUngültiger Spaltenname %cBadColumn%c. - [message] => %SUngültiger Spaltenname %cBadColumn%c. - ) - -) +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/test_error_encoding_with_language_option.phpt b/test/functional/sqlsrv/test_error_encoding_with_language_option.phpt index 0d5d4891f..6309af7e9 100644 --- a/test/functional/sqlsrv/test_error_encoding_with_language_option.phpt +++ b/test/functional/sqlsrv/test_error_encoding_with_language_option.phpt @@ -1,41 +1,55 @@ --TEST-- -Encoding of sqlsrv errors +GitHub issue 929 - able to change the language when connecting +--DESCRIPTION-- +A test similar to test_error_encoding, created for GitHub issue 929 --SKIPIF-- --FILE-- 'UTF-8','Language'=>'German' )); +$connectionOptions = array('UID' => $userName, 'PWD' => $userPassword, 'CharacterSet' => 'UTF-8', 'Language' => 'German'); +$conn = sqlsrv_connect($server, $connectionOptions); if (!$conn) { die(print_r(sqlsrv_errors(), true)); } $stmt = sqlsrv_query($conn, "select *, BadColumn from sys.syslanguages"); if ($stmt) { - echo 'OK!'; + echo 'This should have failed!\n'; sqlsrv_free_stmt($stmt); } else { - $errs = sqlsrv_errors(); - print_r($errs); + verifyErrorContents(); } sqlsrv_close($conn); +echo "Done\n"; ?> ---EXPECTF-- -Array -( - [0] => Array - ( - [0] => 42S22 - [SQLSTATE] => 42S22 - [1] => 207 - [code] => 207 - [2] => %SUngültiger Spaltenname %cBadColumn%c. - [message] => %SUngültiger Spaltenname %cBadColumn%c. - ) - -) +--EXPECT-- +Done \ No newline at end of file From 7d389e0cffa6840a95ea720673380f9ae0b2b477 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 29 Mar 2019 10:40:07 -0700 Subject: [PATCH 111/249] Update azure-pipelines.yml for Azure Pipelines [skip ci] (#964) --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ab688eae8..6af2ba098 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,7 +3,7 @@ variables: phpVersion: 7.3 - server: 'localhost,1433' + server: 'localhost' host: 'sql1' sqlsrv_db: 'sqlsrv_testdb' pdo_sqlsrv_db: 'pdo_sqlsrv_testdb' From 1ba1f21eb84960455893b78c4e3fe5c31770bc2e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 1 Apr 2019 13:20:05 -0700 Subject: [PATCH 112/249] Added more checks for error conditions (#965) --- test/functional/sqlsrv/0075.phpt | 34 +++++++- .../sqlsrv/sqlsrv_378_out_param_error.phpt | 77 ++++++++++++++++--- .../functional/sqlsrv/sqlsrv_data_to_str.phpt | 14 +++- test/functional/sqlsrv/test_scrollable.phpt | 22 +++++- 4 files changed, 133 insertions(+), 14 deletions(-) diff --git a/test/functional/sqlsrv/0075.phpt b/test/functional/sqlsrv/0075.phpt index 327c2260e..1657af0f4 100644 --- a/test/functional/sqlsrv/0075.phpt +++ b/test/functional/sqlsrv/0075.phpt @@ -1,5 +1,7 @@ --TEST-- Fix for output string parameter truncation error +--DESCRIPTION-- +This test includes calling sqlsrv_query with an array of parameters with a named key, which should result in an error. --SKIPIF-- --FILE-- @@ -23,10 +25,25 @@ if ($s === false) { $inValue1 = "Some data"; $outValue1 = ""; +$tsql = '{CALL [test_output] (?, ?)}'; + $s = sqlsrv_query( $conn, - "{CALL [test_output] (?, ?)}", - array(array($inValue1, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_VARCHAR(512)), + $tsql, + array("k1" => array($inValue1, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_VARCHAR(512)), + array(&$outValue1, SQLSRV_PARAM_OUT, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARCHAR(512))) +); + +if ($s !== false) { + echo "Expect this to fail!\n"; +} else { + print_r(sqlsrv_errors()); +} + +$s = sqlsrv_query( + $conn, + $tsql, + array(array($inValue1, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_VARCHAR(512)), array(&$outValue1, SQLSRV_PARAM_OUT, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARCHAR(512))) ); @@ -45,5 +62,18 @@ sqlsrv_close($conn); ?> --EXPECT-- +Array +( + [0] => Array + ( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -57 + [code] => -57 + [2] => String keys are not allowed in parameters arrays. + [message] => String keys are not allowed in parameters arrays. + ) + +) 512 Some data diff --git a/test/functional/sqlsrv/sqlsrv_378_out_param_error.phpt b/test/functional/sqlsrv/sqlsrv_378_out_param_error.phpt index ebb7334f9..a0169225d 100644 --- a/test/functional/sqlsrv/sqlsrv_378_out_param_error.phpt +++ b/test/functional/sqlsrv/sqlsrv_378_out_param_error.phpt @@ -2,11 +2,14 @@ This test verifies that GitHub issue #378 is fixed. --DESCRIPTION-- GitHub issue #378 - output parameters appends garbage info when variable is initialized with different data type -steps to reproduce the issue: +Steps to reproduce the issue: 1- create a store procedure with print and output parameter 2- initialize output parameters to a different data type other than the type declared in sp. 3- set the WarningsReturnAsErrors to true 4- call sp. +Also check error conditions by passing output parameters NOT by reference. +--ENV-- +PHPT_EXEC=true --SKIPIF-- --FILE-- @@ -19,11 +22,8 @@ $conn = AE\connect(); $procName = 'test_378'; createSP($conn, $procName); -sqlsrv_configure('WarningsReturnAsErrors', true); -executeSP($conn, $procName); - -sqlsrv_configure('WarningsReturnAsErrors', false); -executeSP($conn, $procName); +runTests($conn, $procName, true); +runTests($conn, $procName, false); dropProc($conn, $procName); echo "Done\n"; @@ -46,7 +46,34 @@ function createSP($conn, $procName) } } -function executeSP($conn, $procName) +//-------------------functions------------------- +function runTests($conn, $procName, $warningAsErrors) +{ + sqlsrv_configure('WarningsReturnAsErrors', $warningAsErrors); + + trace("\nWarningsReturnAsErrors: $warningAsErrors\n"); + + executeSP($conn, $procName, true, false); + executeSP($conn, $procName, true, true); + executeSP($conn, $procName, false, false); + executeSP($conn, $procName, false, true); +} + +function compareErrors() +{ + $message = 'Variable parameter 3 not passed by reference (prefaced with an &). Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value.'; + + $error = sqlsrv_errors()[0]['message']; + + if ($error !== $message) { + print_r(sqlsrv_errors(), true); + return; + } + + trace("Comparing errors: matched!\n"); +} + +function executeSP($conn, $procName, $noRef, $prepare) { $expected = 3; $v1 = 1; @@ -54,14 +81,44 @@ function executeSP($conn, $procName) $v3 = 'str'; $res = true; - if (AE\isColEncrypted()) { - $stmt = sqlsrv_prepare($conn, "{call $procName( ?, ?, ?)}", array($v1, $v2, array(&$v3, SQLSRV_PARAM_OUT))); + $tsql = "{call $procName( ?, ?, ?)}"; + + if ($noRef) { + $params = array($v1, $v2, array($v3, SQLSRV_PARAM_OUT)); + } else { + $params = array($v1, $v2, array(&$v3, SQLSRV_PARAM_OUT)); + } + + trace("No reference: $noRef\n"); + trace("Use prepared stmt: $prepare\n"); + + if (AE\isColEncrypted() || $prepare) { + $stmt = sqlsrv_prepare($conn, $tsql, $params); if ($stmt) { $res = sqlsrv_execute($stmt); + } else { + fatalError("executeSP: failed in preparing statement with reference($noRef)"); } + if ($noRef) { + if ($res !== false) { + echo "Expect this to fail!\n"; + } + compareErrors(); + return; + } } else { - $stmt = sqlsrv_query($conn, "{call $procName( ?, ?, ?)}", array($v1, $v2, array(&$v3, SQLSRV_PARAM_OUT))); + $stmt = sqlsrv_query($conn, $tsql, $params); + if ($noRef) { + if ($stmt !== false) { + echo "Expect this to fail!\n"; + } + compareErrors(); + return; + } } + + trace("No errors: $v3 and $expected\n"); + // No errors expected if ($stmt === false || !$res) { print_r(sqlsrv_errors(), true); } diff --git a/test/functional/sqlsrv/sqlsrv_data_to_str.phpt b/test/functional/sqlsrv/sqlsrv_data_to_str.phpt index 08f42b26b..8087d1d0e 100644 --- a/test/functional/sqlsrv/sqlsrv_data_to_str.phpt +++ b/test/functional/sqlsrv/sqlsrv_data_to_str.phpt @@ -1,5 +1,7 @@ --TEST-- large types to strings of 1MB size. +--DESCRIPTION-- +This includes a test by providing an invalid php type. --SKIPIF-- --FILE-- @@ -8,7 +10,7 @@ large types to strings of 1MB size. sqlsrv_configure( 'WarningsReturnAsErrors', 0 ); sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL ); - require( 'MsCommon.inc' ); + require_once( 'MsCommon.inc' ); $conn = Connect(); if( !$conn ) { @@ -59,6 +61,16 @@ large types to strings of 1MB size. die( "sqlsrv_get_field(6) failed." ); } + $str = sqlsrv_get_field( $stmt, 0, SQLSRV_PHPTYPE_STRING("UTF") ); + if ($str === false) { + $error = sqlsrv_errors()[0]['message']; + if ($error !== 'Invalid type') { + fatalError('Unexpected error returned'); + } + } else { + echo "Expect sqlsrv_get_field(7) to fail!\n"; + } + sqlsrv_free_stmt( $stmt ); sqlsrv_close( $conn ); diff --git a/test/functional/sqlsrv/test_scrollable.phpt b/test/functional/sqlsrv/test_scrollable.phpt index 7e67a597a..524da1517 100644 --- a/test/functional/sqlsrv/test_scrollable.phpt +++ b/test/functional/sqlsrv/test_scrollable.phpt @@ -1,5 +1,5 @@ --TEST-- -scrollable result sets. +Scrollable result sets with a simple test for an expected error. --SKIPIF-- --FILE-- @@ -69,6 +69,13 @@ for ($i = 1; $i <= $numRows; $i++) { } $query = "SELECT * FROM $tableName"; +$options = array('Scrollable' => 'dummy'); +$stmt = sqlsrv_query($conn, $query, array(), $options); +if ($stmt !== false) { + fatalError("Expect dummy scrollable to fail!\n"); +} +print_r(sqlsrv_errors()); + $options = array('Scrollable' => SQLSRV_CURSOR_FORWARD); $stmt = sqlsrv_query($conn, $query, array(), $options); @@ -205,4 +212,17 @@ echo "Test succeeded.\n"; ?> --EXPECT-- +Array +( + [0] => Array + ( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -54 + [code] => -54 + [2] => The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', 'keyset', 'forward', or 'buffered'. + [message] => The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', 'keyset', 'forward', or 'buffered'. + ) + +) Test succeeded. From a8a0146671f9e0b1609911c58fa4473f6d4a8425 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 2 Apr 2019 16:08:53 -0700 Subject: [PATCH 113/249] Removed forward cursor condition --- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 773ad953a..33932d7e3 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -694,7 +694,7 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta // support for the PDO rowCount method. Since rowCount doesn't call a method, PDO relies on us to fill the // pdo_stmt_t::row_count member - if( driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { + if( driver_stmt->past_fetch_end) {// || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); From 4b6b650db11f4589304d1ed4fb28eb8ab4ff7122 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 5 Apr 2019 12:34:33 -0700 Subject: [PATCH 114/249] Added row and column count checks --- source/pdo_sqlsrv/pdo_stmt.cpp | 19 ++++++++++++++++++ source/shared/core_sqlsrv.h | 6 ++++-- source/shared/core_stmt.cpp | 35 +++++++++++++++++++++++++++++----- source/sqlsrv/stmt.cpp | 4 ++++ 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 33932d7e3..906f73fd2 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -593,12 +593,27 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) if ( execReturn == SQL_NO_DATA ) { stmt->column_count = 0; stmt->row_count = 0; + driver_stmt->column_count = 0; + driver_stmt->row_count = 0; + driver_stmt->columns_rows_obtained = true; } else { + if (driver_stmt->columns_rows_obtained == false) + { stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); // return the row count regardless if there are any rows or not stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + driver_stmt->column_count = stmt->column_count; + driver_stmt->row_count = stmt->row_count; + driver_stmt->columns_rows_obtained = true; + } + else + { + stmt->column_count = driver_stmt->column_count; + stmt->row_count = driver_stmt->row_count; + } } // workaround for a bug in the PDO driver manager. It is fairly simple to crash the PDO driver manager with @@ -1146,6 +1161,10 @@ int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) // return the row count regardless if there are any rows or not stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + + driver_stmt->column_count = stmt->column_count; + driver_stmt->row_count = stmt->row_count; + driver_stmt->columns_rows_obtained = true; } catch( core::CoreException& ) { diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index d4ffdda57..2102636f6 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1438,8 +1438,10 @@ struct sqlsrv_stmt : public sqlsrv_context { bool has_rows; // Has_rows is set if there are actual rows in the row set bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called int last_field_index; // last field retrieved by core_sqlsrv_get_field - bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the - // last results + bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the last results + bool columns_rows_obtained; // Whether or not SQLNumResultCols and SQLRowCount have been called for the active result set + int column_count; // Number of columns in the current result set obtained from SQLNumResultCols + int row_count; // Number of rows in the current result set obtained from SQLRowCount unsigned long query_timeout; // maximum allowed statement execution time zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 9cac96a53..352d2ff03 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -140,6 +140,9 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error fetch_called( false ), last_field_index( -1 ), past_next_result_end( false ), + columns_rows_obtained( false ), + column_count( 0 ), + row_count( 0 ), query_timeout( QUERY_TIMEOUT_INVALID ), date_as_string(false), format_decimals(false), // no formatting needed @@ -225,6 +228,9 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) this->past_next_result_end = false; this->past_fetch_end = false; this->last_field_index = -1; + this->columns_rows_obtained = false; + this->column_count = 0; + this->row_count = 0; // delete any current results if( current_results ) { @@ -821,7 +827,13 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient } // First time only if ( !stmt->fetch_called ) { - SQLSMALLINT has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + SQLSMALLINT has_fields; + if (stmt->columns_rows_obtained) { + has_fields = stmt->column_count; + } else { + has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + } + CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { throw core::CoreException(); } @@ -1066,10 +1078,23 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { - // Use SQLNumResultCols to determine if we have rows or not. - SQLSMALLINT num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); - // use SQLRowCount to determine if there is a rows status waiting - SQLLEN rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); + SQLSMALLINT num_cols, rows_affected; + if (stmt->columns_rows_obtained) + { + num_cols = stmt->column_count; + rows_affected = stmt->row_count; + } + else + { + // Use SQLNumResultCols to determine if we have rows or not + num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + stmt->column_count = num_cols; + // Use SQLRowCount to determine if there is a rows status waiting + rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); + stmt->row_count = rows_affected; + stmt->columns_rows_obtained = true; + } + return (num_cols != 0) || (rows_affected > 0); } diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 9d60de25f..bdd1d2f83 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1791,7 +1791,11 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) if (num_cols == 0) { getMetaData = true; + if (stmt->columns_rows_obtained == false) { num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + } else { + num_cols = stmt->column_count; + } } try { From 486ab9fb088629c6a89a5c5fb111a6b4ff59253e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 9 Apr 2019 07:28:26 -0700 Subject: [PATCH 115/249] Revert "Update azure-pipelines.yml for Azure Pipelines [skip ci] (#964)" (#969) This reverts commit 7d389e0cffa6840a95ea720673380f9ae0b2b477. --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6af2ba098..ab688eae8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,7 +3,7 @@ variables: phpVersion: 7.3 - server: 'localhost' + server: 'localhost,1433' host: 'sql1' sqlsrv_db: 'sqlsrv_testdb' pdo_sqlsrv_db: 'pdo_sqlsrv_testdb' From 8ba932b1ca4752acf82bba0714f576dd36acdd11 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 9 Apr 2019 09:34:31 -0700 Subject: [PATCH 116/249] Add new pdo_sqlsrv tests for utf8 encoding errors (#966) --- .../pdo_sqlsrv/pdo_construct_attr_errors.phpt | 137 ++++++++++ .../pdo_sqlsrv/pdo_empty_result_error.phpt | 3 + .../pdo_insert_fetch_invalid_utf16.phpt | 86 +++++++ .../pdo_insert_fetch_utf8stream.phpt | 106 ++++++++ .../pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt | 233 ++++++++++++++++++ .../pdo_sqlsrv/pdo_warning_errors.phpt | 107 ++++++++ 6 files changed, 672 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_insert_fetch_invalid_utf16.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8stream.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_warning_errors.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt b/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt new file mode 100644 index 000000000..a27bdc39f --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt @@ -0,0 +1,137 @@ +--TEST-- +Test various connection errors with invalid attributes +--DESCRIPTION-- +This is similar to sqlsrv sqlsrv_connStr.phpt such that invalid connection attributes or values used when connecting. +--SKIPIF-- + +--FILE-- + PDO::ERRMODE_EXCEPTION); + $conn = connect("", $options); + $attr = ($binary) ? PDO::SQLSRV_ENCODING_BINARY : 'gibberish'; + + $conn->setAttribute(PDO::SQLSRV_ATTR_ENCODING, $attr); + echo "Should have failed about an invalid encoding.\n"; + } catch (PDOException $e) { + $error = '*An invalid encoding was specified for SQLSRV_ATTR_ENCODING.'; + if (!fnmatch($error, $e->getMessage())) { + echo "invalidEncoding($binary)\n"; + var_dump($e->getMessage()); + } + } +} + +function invalidServer() +{ + global $uid, $pwd; + + // Test an invalid server name in UTF-8 + try { + $options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + $invalid = pack("H*", "ffc0"); + $conn = new PDO("sqlsrv:server = $invalid;", $uid, $pwd, $options); + echo "Should have failed to connect to invalid server.\n"; + } catch (PDOException $e) { + $error1 = '*Login timeout expired'; + $error2 = '*An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page*'; + if (fnmatch($error1, $e->getMessage()) || fnmatch($error2, $e->getMessage())) { + ; // matched at least one of the expected error messages + } else { + echo "invalidServer\n"; + var_dump($e->getMessage()); + } + } +} + +function utf8APP() +{ + global $server, $uid, $pwd; + try { + // Use a UTF-8 name + $app = pack('H*', 'c59ec6a1d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7'); + $dsn = "APP = $app;"; + $conn = connect($dsn); + } catch (PDOException $e) { + echo "With APP in UTF8 it should not have failed!\n"; + var_dump($e->getMessage()); + } +} + +function invalidCredentials() +{ + global $server, $database; + + // Use valid UTF-8 + $user = pack('H*', 'c59ec6a1d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7'); + $passwd = pack('H*', 'c59ec6a1d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7'); + + $options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + $error1 = "*Login failed for user \'*\'."; + $error2 = "*Login timeout expired*"; + + try { + $conn = new PDO("sqlsrv:server = $server; database = $database;", $user, $passwd, $options); + echo "Should have failed to connect\n"; + } catch (PDOException $e) { + if (fnmatch($error1, $e->getMessage()) || fnmatch($error2, $e->getMessage())) { + ; // matched at least one of the expected error messages + } else { + echo "invalidCredentials()\n"; + var_dump($e->getMessage()); + } + } +} + +function invalidPassword() +{ + global $server, $database; + + // Use valid UTF-8 + $user = pack('H*', 'c59ec6a1d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7'); + // Use invalid UTF-8 + $passwd = pack('H*', 'c59ec6c0d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7'); + + $options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + + // Possible errors + $error = "*An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page.*"; + $error1 = "*Login failed for user \'*\'."; + $error2 = "*Login timeout expired*"; + + try { + $conn = new PDO("sqlsrv:server = $server; database = $database;", $user, $passwd, $options); + echo "Should have failed to connect\n"; + } catch (PDOException $e) { + if (!fnmatch($error, $e->getMessage())) { + // Sometimes it might fail with two other possible error messages + if (fnmatch($error1, $e->getMessage()) || fnmatch($error2, $e->getMessage())) { + ; // matched at least one of the expected error messages + } else { + echo "invalidPassword()\n"; + var_dump($e->getMessage()); + } + } + } +} + +try { + invalidEncoding(false); + invalidEncoding(true); + invalidServer(); + utf8APP(); + invalidCredentials(); + invalidPassword(); + + echo "Done\n"; +} catch (PDOException $e) { + var_dump($e); +} +?> +--EXPECT-- +Done + diff --git a/test/functional/pdo_sqlsrv/pdo_empty_result_error.phpt b/test/functional/pdo_sqlsrv/pdo_empty_result_error.phpt index 0d2b730aa..32bacc697 100644 --- a/test/functional/pdo_sqlsrv/pdo_empty_result_error.phpt +++ b/test/functional/pdo_sqlsrv/pdo_empty_result_error.phpt @@ -12,6 +12,7 @@ require_once("MsCommon.inc"); // These are the error messages we expect at various points below $errorNoMoreResults = "There are no more results returned by the query."; $errorNoFields = "The active result for the query contains no fields."; +$errorNoMoreRows = "There are no more rows in the active result set. Since this result set is not scrollable, no more data may be retrieved."; // This function compares the expected error message and the error returned by errorInfo(). function CheckError($stmt, $expectedError=NULL) @@ -94,6 +95,7 @@ echo "Empty result set, call fetch first: ##################################\n"; $stmt = $conn->query("TestEmptySetProc @a='a', @b='w'"); Fetch($stmt); +Fetch($stmt, $errorNoMoreRows); NextResult($stmt); Fetch($stmt); NextResult($stmt, $errorNoMoreResults); @@ -158,6 +160,7 @@ Next result... Next result... Empty result set, call fetch first: ################################## Fetch... +Fetch... Next result... Fetch... Next result... diff --git a/test/functional/pdo_sqlsrv/pdo_insert_fetch_invalid_utf16.phpt b/test/functional/pdo_sqlsrv/pdo_insert_fetch_invalid_utf16.phpt new file mode 100644 index 000000000..c90e31353 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_insert_fetch_invalid_utf16.phpt @@ -0,0 +1,86 @@ +--TEST-- +Test fetching invalid UTF-16 from the server +--DESCRIPTION-- +This is similar to sqlsrv 0079.phpt with checking for error conditions concerning encoding issues. +--SKIPIF-- + +--FILE-- +setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_SYSTEM); + + // Create test table + $tableName = 'pdoUTF16invalid'; + $columns = array(new ColumnMeta('int', 'id', 'identity'), + new ColumnMeta('nvarchar(100)', 'c1')); + $stmt = createTable($conn, $tableName, $columns); + + // 0xdc00,0xdbff is an invalid surrogate pair + $invalidUTF16 = pack("H*", '410042004300440000DCFFDB45004600'); + + $insertSql = "INSERT INTO $tableName (c1) VALUES (?)"; + $stmt = $conn->prepare($insertSql); + $stmt->bindParam(1, $invalidUTF16, PDO::PARAM_STR, null, PDO::SQLSRV_ENCODING_BINARY); + $stmt->execute(); + + try { + // Now fetch data with UTF-8 encoding + $tsql = "SELECT * FROM $tableName"; + $stmt = $conn->prepare($tsql); + $stmt->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_UTF8); + $stmt->execute(); + $utf8 = $stmt->fetchColumn(1); // Ignore the id column + echo "fetchColumn should have failed with an error.\n"; + } catch (PDOException $e) { + $error = '*An error occurred translating string for a field to UTF-8:*'; + if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } + + dropProc($conn, 'Utf16InvalidOut'); + $createProc = <<query($createProc); + + try { + $invalidUTF16Out = ''; + $tsql = '{call Utf16InvalidOut(?)}'; + $stmt = $conn->prepare($tsql); + $stmt->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_UTF8); + $stmt->bindParam(1, $invalidUTF16Out, PDO::PARAM_STR, 25); + $stmt->execute(); + } catch (PDOException $e) { + $error = '*An error occurred translating string for an output param to UTF-8:*'; + if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } + + echo "Done\n"; + + // Done testing with the stored procedure and test table + dropProc($conn, 'Utf16InvalidOut'); + dropTable($conn, $tableName); + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8stream.phpt b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8stream.phpt new file mode 100644 index 000000000..b5d2c9b26 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8stream.phpt @@ -0,0 +1,106 @@ +--TEST-- +Test inserting UTF-8 stream via PHP including some checking of error conditions +--DESCRIPTION-- +This is similar to sqlsrv 0067.phpt with checking for error conditions concerning encoding issues. +--SKIPIF-- + +--FILE-- +prepare($insertSql); + $stmt->bindParam(1, $f1); + $stmt->bindParam(2, $f2); + $stmt->bindParam(3, $f3); + $stmt->bindParam(4, $f4, PDO::PARAM_LOB); + + $stmt->execute(); + + // Next test UTF-8 cutoff in the middle of a valid 3 byte UTF-8 char + $utf8 = str_repeat("41", 8188); + $utf8 = $utf8 . "e38395"; + $utf8 = pack("H*", $utf8); + $f4 = fopen("data://text/plain," . $utf8, "r"); + $stmt->bindParam(4, $f4, PDO::PARAM_LOB); + $stmt->execute(); + + // Now test a 2 byte incomplete character + $utf8 = str_repeat("41", 8188); + $utf8 = $utf8 . "dfa0"; + $utf8 = pack("H*", $utf8); + $f4 = fopen("data://text/plain," . $utf8, "r"); + $stmt->bindParam(4, $f4, PDO::PARAM_LOB); + $stmt->execute(); + + // Then test a 4 byte incomplete character + $utf8 = str_repeat("41", 8186); + $utf8 = $utf8 . "f1a680bf"; + $utf8 = pack("H*", $utf8); + $f4 = fopen("data://text/plain," . $utf8, "r"); + $stmt->bindParam(4, $f4, PDO::PARAM_LOB); + $stmt->execute(); + + // Finally, verify error conditions with invalid inputs + $error = '*An error occurred translating a PHP stream from UTF-8 to UTF-16:*'; + + // First test UTF-8 cutoff (really cutoff) + $utf8 = str_repeat("41", 8188); + $utf8 = $utf8 . "e383"; + $utf8 = pack("H*", $utf8); + $f4 = fopen("data://text/plain," . $utf8, "r"); + try { + $stmt->bindParam(4, $f4, PDO::PARAM_LOB); + $stmt->execute(); + echo "Should have failed with a cutoff UTF-8 string\n"; + } catch (PDOException $e) { + if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } + + // Then test UTF-8 invalid/corrupt stream + $utf8 = str_repeat("41", 8188); + $utf8 = $utf8 . "e38395e38395"; + $utf8 = substr_replace($utf8, "fe", 1000, 2); + $utf8 = pack("H*", $utf8); + $f4 = fopen("data://text/plain," . $utf8, "r"); + try { + $stmt->bindParam(4, $f4, PDO::PARAM_LOB); + $stmt->execute(); + echo "Should have failed with an invalid UTF-8 string\n"; + } catch (PDOException $e) { + if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } + + echo "Done\n"; + + // Done testing with stored procedures and table + dropTable($conn, $tableName); + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt new file mode 100644 index 000000000..acbeaf233 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt @@ -0,0 +1,233 @@ +--TEST-- +Test inserting and retrieving UTF-8 text +--DESCRIPTION-- +This is similar to sqlsrv 0065.phpt with checking for error conditions concerning encoding issues. +--SKIPIF-- + +--FILE-- + 0) { + if ($results[$i] !== $utf8) { + echo $columns[$i]->colName . ' does not match the inserted UTF-8 text'; + var_dump($results[$i]); + } + } else { + // The first column, a varchar(100) column, should have question marks, + // like this one: + $expected = "So?e sä???? ?SCII-te×t"; + // With AE, the fetched result may be different in Windows and other + // platforms -- the point is to check if there are some '?' + if (!isAEConnected() && $results[$i] !== $expected) { + echo $columns[$i]->colName . " does not match $expected"; + var_dump($results[$i]); + } else { + $arr = explode('?', $results[$i]); + if (count($arr) == 1) { + // this means there is no question mark in $t + echo $columns[$i]->colName . " value is unexpected"; + var_dump($results[$i]); + } + } + } + } +} + +function dropProcedures($conn) +{ + // Drop all procedures + dropProc($conn, "pdoIntDoubleProc"); + dropProc($conn, "pdoUTF8OutProc"); + dropProc($conn, "pdoUTF8OutWithResultsetProc"); + dropProc($conn, "pdoUTF8InOutProc"); +} + +function createProcedures($conn, $tableName) +{ + // Drop all procedures first + dropProcedures($conn); + + $createProc = <<query($createProc); + + $createProc = "CREATE PROCEDURE pdoUTF8OutWithResultsetProc @param NVARCHAR(25) OUTPUT AS BEGIN SELECT c1, c2, c3 FROM $tableName SET @param = CONVERT(NVARCHAR(25), 0x5E01A1013C04170120005B01E400DD1040044001C11E200086035A010801280130012D0065012E21D7006701); END"; + $stmt = $conn->query($createProc); + + $createProc = "CREATE PROCEDURE pdoUTF8InOutProc @param NVARCHAR(25) OUTPUT AS BEGIN SET @param = CONVERT(NVARCHAR(25), 0x6001E11EDD10130120006101E200DD1040043A01BB1E2000C5005A01C700CF0007042D006501BF1E45046301); END"; + $stmt = $conn->query($createProc); + + $createProc = "CREATE PROCEDURE pdoIntDoubleProc @param INT OUTPUT AS BEGIN SET @param = @param + @param; END;"; + $stmt = $conn->query($createProc); +} + +function runBaselineProc($conn) +{ + $sql = "{call pdoIntDoubleProc(?)}"; + $val = 1; + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $val, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 100); + $stmt->execute(); + + if ($val !== 2) { + echo "Incorrect value $val for pdoIntDoubleProc\n"; + } +} + +function runImmediateConversion($conn, $utf8) +{ + $sql = "{call pdoUTF8OutProc(?)}"; + $val = ''; + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $val, PDO::PARAM_STR, 50); + $stmt->execute(); + + if ($val !== $utf8) { + echo "Incorrect value $val for pdoUTF8OutProc\n"; + } +} + +function runProcWithResultset($conn, $utf8) +{ + $sql = "{call pdoUTF8OutWithResultsetProc(?)}"; + $val = ''; + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $val, PDO::PARAM_STR, 50); + $stmt->execute(); + + // Moves the cursor to the next result set + $stmt->nextRowset(); + + if ($val !== $utf8) { + echo "Incorrect value $val for pdoUTF8OutWithResultsetProc\n"; + } +} + +function runInOutProcWithErrors($conn, $utf8_2) +{ + // The input string is smaller than the output size for testing + $val = 'This is a test.'; + + // The following should work + $sql = "{call pdoUTF8InOutProc(?)}"; + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $val, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 25); + $stmt->execute(); + + if ($val !== $utf8_2) { + echo "Incorrect value $val for pdoUTF8InOutProc Part 1\n"; + } + + // Use a much longer input string + $val = 'This is a longer test that exceeds the returned values buffer size so that we can test an input buffer size larger than the output buffer size.'; + try { + $stmt->bindParam(1, $val, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 25); + $stmt->execute(); + echo "Should have failed since the string is too long!\n"; + } catch (PDOException $e) { + $error = '*String data, right truncation'; + if ($e->getCode() !== "22001" || !fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } +} + +function runIntDoubleProcWithErrors($conn) +{ + $sql = "{call pdoIntDoubleProc(?)}"; + $val = pack('H*', 'ffffffff'); + + try { + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $val, PDO::PARAM_STR); + $stmt->execute(); + echo "Should have failed because of an invalid utf-8 string!\n"; + } catch (PDOException $e) { + $error = '*An error occurred translating string for input param 1 to UCS-2:*'; + if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } +} + +try { + $conn = connect(); + + // Create test table + $tableName = 'pdoUTF8test'; + $columns = array(new ColumnMeta('varchar(100)', 'c1'), + new ColumnMeta('nvarchar(100)', 'c2'), + new ColumnMeta('nvarchar(max)', 'c3')); + $stmt = createTable($conn, $tableName, $columns); + + $utf8 = "Şơмė śäáƒÑ€Å€á» ΆŚĈĨİ-ť℮×ŧ"; + + $insertSql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?, ?, ?)"; + $stmt1 = $conn->prepare($insertSql); + $stmt1->bindParam(1, $utf8); + $stmt1->bindParam(2, $utf8); + $stmt1->bindParam(3, $utf8); + + $stmt1->execute(); + + $stmt2 = $conn->prepare("SELECT c1, c2, c3 FROM $tableName"); + $stmt2->execute(); + $results = $stmt2->fetch(PDO::FETCH_NUM); + verifyColumnData($columns, $results, $utf8); + + // Start creating stored procedures for testing + createProcedures($conn, $tableName); + + runBaselineProc($conn); + runImmediateConversion($conn, $utf8); + runProcWithResultset($conn, $utf8); + + // Use another set of UTF-8 text to test + $utf8_2 = "ŠỡáƒÄ“ šâáƒÑ€Äºáº» ÅŚÇÃЇ-ťếхţ"; + runInOutProcWithErrors($conn, $utf8_2); + + // Now insert second row + $utf8_3 = pack('H*', '7a61cc86c7bdceb2f18fb3bf'); + $stmt1->bindParam(1, $utf8_3); + $stmt1->bindParam(2, $utf8_3); + $stmt1->bindParam(3, $utf8_3); + $stmt1->execute(); + + // Fetch data, ignoring first row + $stmt2->execute(); + $stmt2->fetch(PDO::FETCH_NUM); + + // Move to the second row and check second field + $results2 = $stmt2->fetch(PDO::FETCH_NUM); + if ($results2[1] !== $utf8_3) { + echo "Unexpected $results2[1] from field 2 in second row.\n"; + } + + // Last test with an invalid input + runIntDoubleProcWithErrors($conn); + + echo "Done\n"; + + // Done testing with stored procedures and table + dropProcedures($conn); + dropTable($conn, $tableName); + + unset($stmt1); + unset($stmt2); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_warning_errors.phpt b/test/functional/pdo_sqlsrv/pdo_warning_errors.phpt new file mode 100644 index 000000000..d92da15d7 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_warning_errors.phpt @@ -0,0 +1,107 @@ +--TEST-- +Test various scenarios which all return the same error about statement not executed +--DESCRIPTION-- +This is similar to sqlsrv test_warning_errors2.phpt with checking for error conditions concerning fetching and metadata. +--SKIPIF-- + +--FILE-- +nextRowset(); + if (!is_null($error)) { + echo "getNextResult: expect this to fail with an error from the driver\n"; + } elseif ($result !== false) { + echo "getNextResult: expect this to simply return false\n"; + } + } catch (PDOException $e) { + if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } +} + +try { + $conn = connect(); + + $tsql = 'SELECT name FROM sys.objects'; + $stmt = $conn->prepare($tsql); + + $colCount = $stmt->columnCount(); + if ($colCount != 0) { + echo "Before execute(), result set should only have 0 columns\n"; + } + $metadata = $stmt->getColumnMeta(0); + if ($metadata !== false) { + echo "Before execute(), result set is empty so getColumnMeta should have failed\n"; + } + + // When fetching, PDO checks if statement is executed before passing the + // control to the driver, so it simply fails without error message + $result = $stmt->fetch(PDO::FETCH_ASSOC); + if ($result !== false) { + echo "Before execute(), fetch should have failed\n"; + } + + $result = $stmt->fetchAll(PDO::FETCH_ASSOC); + var_dump($result); + + $result = $stmt->fetchAll(PDO::FETCH_COLUMN, 0); + var_dump($result); + + getNextResult($stmt, $execError); + + // Now, call execute() + $stmt->execute(); + + $colCount = $stmt->columnCount(); + if ($colCount != 1) { + echo "Expected only one column\n"; + } + + $metadata = $stmt->getColumnMeta(0); + if ($metadata['native_type'] !== 'string') { + echo "The metadata returned is unexpected: \n"; + var_dump($metadata); + } + + $result = $stmt->fetch(PDO::FETCH_ASSOC); + if (!is_array($result) && count($result) == 0) { + echo "After execute(), fetch should have returned an array with results\n"; + } + + $result = $stmt->fetchAll(PDO::FETCH_COLUMN, 0); + if (!is_array($result) && count($result) == 0) { + echo "After execute(), fetchAll should have returned an array with results\n"; + } + + getNextResult($stmt); + + getNextResult($stmt, $noMoreResult); + + $result = $stmt->fetch(PDO::FETCH_ASSOC); + if ($result !== false) { + // When nextRowset() fails, it resets the execute flag to false + echo "After nextRowset failed, fetch should have failed\n"; + } + + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +array(0) { +} +array(0) { +} +Done \ No newline at end of file From 1e4f0147272af65589019ad290a52aa2010cb438 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 9 Apr 2019 15:30:24 -0700 Subject: [PATCH 117/249] Modified to check if qualified for AE connections (#967) --- .../pdo_sqlsrv/pdo_569_query_varcharmax.phpt | 11 +++++++++++ .../sqlsrv/srv_569_query_varcharmax.phpt | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt index e56399882..671cbc269 100644 --- a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt +++ b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt @@ -12,6 +12,17 @@ require_once("MsSetup.inc"); require_once("MsCommon_mid-refactor.inc"); try { + // This test requires to connect with the Always Encrypted feature + // First check if the system is qualified to run this test + $dsn = getDSN($server, null); + $conn = new PDO($dsn, $uid, $pwd); + if (!isAEQualified($conn)) { + echo "Done\n"; + return; + } + unset($conn); + + // Now connect with ColumnEncryption enabled $connectionInfo = "ColumnEncryption = Enabled;"; $conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd); $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); diff --git a/test/functional/sqlsrv/srv_569_query_varcharmax.phpt b/test/functional/sqlsrv/srv_569_query_varcharmax.phpt index 1a41a629d..314ec54cc 100644 --- a/test/functional/sqlsrv/srv_569_query_varcharmax.phpt +++ b/test/functional/sqlsrv/srv_569_query_varcharmax.phpt @@ -19,7 +19,22 @@ function verifyFetchError() require_once('MsCommon.inc'); -$connectionOptions = array("Database" => $database, "UID" => $userName, "PWD" => $userPassword, "ColumnEncryption" => "Enabled"); +// This test requires to connect with the Always Encrypted feature +// First check if the system is qualified to run this test +$options = array("Database" => $database, "UID" => $userName, "PWD" => $userPassword); +$conn = sqlsrv_connect($server, $options); +if ($conn === false) { + fatalError("Failed to connect to $server."); +} + +if (!AE\isQualified($conn)) { + echo "Done\n"; + return; +} +sqlsrv_close($conn); + +// Now connect with ColumnEncryption enabled +$connectionOptions = array_merge($options, array('ColumnEncryption' => 'Enabled')); $conn = sqlsrv_connect($server, $connectionOptions); if ($conn === false) { fatalError("Failed to connect to $server."); From e2a6ece5272fec2cf52693fc083addd8e84e6097 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 11 Apr 2019 12:11:02 -0700 Subject: [PATCH 118/249] Fixed test and error message --- source/sqlsrv/util.cpp | 6 +++--- test/functional/sqlsrv/sqlsrv_empty_result_error.phpt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index cec66c3f3..dcddcdcd3 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -196,7 +196,7 @@ ss_error SS_ERRORS[] = { { SQLSRV_ERROR_NO_FIELDS, - { IMSSP, (SQLCHAR*)"The active result for the query contains no fields.", -28, false } + { IMSSP, (SQLCHAR*)"The active result for the query contains no fields, so no result set was created.", -28, false } }, { @@ -205,8 +205,8 @@ ss_error SS_ERRORS[] = { }, { - SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, - { IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true } + SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, + { IMSSP, (SQLCHAR*)"Failed to create an instance of class %1!s!.", -30, true } }, { diff --git a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt index 7f751324c..1aaec0275 100644 --- a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt +++ b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt @@ -12,7 +12,7 @@ require_once("MsCommon.inc"); // These are the error messages we expect at various points below $errorNoMoreResults = "There are no more results returned by the query."; $errorNoMoreRows = "There are no more rows in the active result set. Since this result set is not scrollable, no more data may be retrieved."; -$errorNoFields = "The active result for the query contains no fields."; +$errorNoFields = "The active result for the query contains no fields, so no result set was created."; // Variable function gets an error message that depends on the OS function getFuncSeqError() From 847493bdff59a1a18ae7a86eedc222bf29b26f36 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 11 Apr 2019 12:33:39 -0700 Subject: [PATCH 119/249] Minor fixes --- source/pdo_sqlsrv/pdo_stmt.cpp | 8 ++++---- source/shared/core_sqlsrv.h | 6 +++--- source/sqlsrv/stmt.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 906f73fd2..575fc8c69 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -600,10 +600,10 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) else { if (driver_stmt->columns_rows_obtained == false) { - stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); - // return the row count regardless if there are any rows or not - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + // return the row count regardless if there are any rows or not + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); driver_stmt->column_count = stmt->column_count; driver_stmt->row_count = stmt->row_count; @@ -709,7 +709,7 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta // support for the PDO rowCount method. Since rowCount doesn't call a method, PDO relies on us to fill the // pdo_stmt_t::row_count member - if( driver_stmt->past_fetch_end) {// || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { + if( driver_stmt->past_fetch_end) { stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 2102636f6..1c6cacad6 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1439,9 +1439,9 @@ struct sqlsrv_stmt : public sqlsrv_context { bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called int last_field_index; // last field retrieved by core_sqlsrv_get_field bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the last results - bool columns_rows_obtained; // Whether or not SQLNumResultCols and SQLRowCount have been called for the active result set - int column_count; // Number of columns in the current result set obtained from SQLNumResultCols - int row_count; // Number of rows in the current result set obtained from SQLRowCount + bool columns_rows_obtained; // Whether or not SQLNumResultCols and SQLRowCount have been called for the active result + short column_count; // Number of columns in the current result set obtained from SQLNumResultCols + long row_count; // Number of rows in the current result set obtained from SQLRowCount unsigned long query_timeout; // maximum allowed statement execution time zend_long buffered_query_limit; // maximum allowed memory for a buffered query (measured in KB) bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index bdd1d2f83..aa97231e8 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1792,7 +1792,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) if (num_cols == 0) { getMetaData = true; if (stmt->columns_rows_obtained == false) { - num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); } else { num_cols = stmt->column_count; } From cf03cbb6f7283be90f20ab609fba68ad97d27ce9 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 11 Apr 2019 12:47:16 -0700 Subject: [PATCH 120/249] Test fixes --- test/functional/sqlsrv/sqlsrv_empty_result_error.phpt | 2 +- test/functional/sqlsrv/test_warning_errors3.phpt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt index 1aaec0275..bfea07d92 100644 --- a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt +++ b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt @@ -153,7 +153,7 @@ echo "Null result set, call next result first: #############################\n"; $stmt = sqlsrv_query($conn, "TestEmptySetProc @a='a', @b='c'"); NextResult($stmt, []); -Fetch($stmt, [$errorFuncSeq()]); +Fetch($stmt, [$errorNoFields]); // Call next_result twice in succession on a null result set echo "Null result set, call next result twice: #############################\n"; diff --git a/test/functional/sqlsrv/test_warning_errors3.phpt b/test/functional/sqlsrv/test_warning_errors3.phpt index 1b0bdc66a..f0e7848c9 100644 --- a/test/functional/sqlsrv/test_warning_errors3.phpt +++ b/test/functional/sqlsrv/test_warning_errors3.phpt @@ -128,8 +128,8 @@ Array [SQLSTATE] => IMSSP [1] => -28 [code] => -28 - [2] => The active result for the query contains no fields. - [message] => The active result for the query contains no fields. + [2] => The active result for the query contains no fields, so no result set was created. + [message] => The active result for the query contains no fields, so no result set was created. ) ) From 62738bacf3c0cf51d4c6c7adee8857b4b355ef17 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 12 Apr 2019 20:49:03 -0700 Subject: [PATCH 121/249] Addressed review comments --- source/pdo_sqlsrv/pdo_stmt.cpp | 36 +++++++++++-------- source/shared/core_sqlsrv.h | 6 +++- source/shared/core_stmt.cpp | 30 ++++++++-------- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/util.cpp | 2 +- .../sqlsrv/sqlsrv_empty_result_error.phpt | 2 +- .../sqlsrv/test_warning_errors3.phpt | 4 +-- 7 files changed, 48 insertions(+), 34 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 575fc8c69..f8dd160cc 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -595,23 +595,22 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) stmt->row_count = 0; driver_stmt->column_count = 0; driver_stmt->row_count = 0; - driver_stmt->columns_rows_obtained = true; } else { - if (driver_stmt->columns_rows_obtained == false) - { + if (driver_stmt->column_count == ACTIVE_NUM_COLS_INVALID) { stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + driver_stmt->column_count = stmt->column_count; + } + else { + stmt->column_count = driver_stmt->column_count; + } + if (driver_stmt->row_count == ACTIVE_NUM_ROWS_INVALID) { // return the row count regardless if there are any rows or not stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); - - driver_stmt->column_count = stmt->column_count; driver_stmt->row_count = stmt->row_count; - driver_stmt->columns_rows_obtained = true; } - else - { - stmt->column_count = driver_stmt->column_count; + else { stmt->row_count = driver_stmt->row_count; } } @@ -707,11 +706,21 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori ); bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset TSRMLS_CC ); - // support for the PDO rowCount method. Since rowCount doesn't call a method, PDO relies on us to fill the - // pdo_stmt_t::row_count member - if( driver_stmt->past_fetch_end) { + // support for the PDO rowCount method. Since rowCount doesn't call a + // method, PDO relies on us to fill the pdo_stmt_t::row_count member + // The if condition was changed from + // `driver_stmt->past_fetch_end || driver_stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY` + // because it caused SQLRowCount to be called at each fetch if using a non-forward cursor + // which is unnecessary and a performance hit + if( driver_stmt->past_fetch_end || driver_stmt->cursor_type == SQL_CURSOR_DYNAMIC) { - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + if (driver_stmt->row_count == ACTIVE_NUM_ROWS_INVALID) { + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + driver_stmt->row_count = stmt->row_count; + } + else { + stmt->row_count = driver_stmt->row_count; + } // a row_count of -1 means no rows, but we change it to 0 if( stmt->row_count == -1 ) { @@ -1164,7 +1173,6 @@ int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) driver_stmt->column_count = stmt->column_count; driver_stmt->row_count = stmt->row_count; - driver_stmt->columns_rows_obtained = true; } catch( core::CoreException& ) { diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 1c6cacad6..347a89d78 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -182,6 +182,11 @@ const int SQL_MAX_ERROR_MESSAGE_LENGTH = SQL_MAX_MESSAGE_LENGTH * 2; // max size of a date time string when converting from a DateTime object to a string const int MAX_DATETIME_STRING_LEN = 256; +// identifier for whether or not we have obtained the number of rows and columns +// of a result +const short ACTIVE_NUM_COLS_INVALID = -99; +const long ACTIVE_NUM_ROWS_INVALID = -99; + // precision and scale for the date time types between servers const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; @@ -1439,7 +1444,6 @@ struct sqlsrv_stmt : public sqlsrv_context { bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called int last_field_index; // last field retrieved by core_sqlsrv_get_field bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the last results - bool columns_rows_obtained; // Whether or not SQLNumResultCols and SQLRowCount have been called for the active result short column_count; // Number of columns in the current result set obtained from SQLNumResultCols long row_count; // Number of rows in the current result set obtained from SQLRowCount unsigned long query_timeout; // maximum allowed statement execution time diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 352d2ff03..9d7f68019 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -140,9 +140,8 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error fetch_called( false ), last_field_index( -1 ), past_next_result_end( false ), - columns_rows_obtained( false ), - column_count( 0 ), - row_count( 0 ), + column_count( ACTIVE_NUM_COLS_INVALID ), + row_count( ACTIVE_NUM_ROWS_INVALID ), query_timeout( QUERY_TIMEOUT_INVALID ), date_as_string(false), format_decimals(false), // no formatting needed @@ -228,9 +227,8 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) this->past_next_result_end = false; this->past_fetch_end = false; this->last_field_index = -1; - this->columns_rows_obtained = false; - this->column_count = 0; - this->row_count = 0; + this->column_count = ACTIVE_NUM_COLS_INVALID; + this->row_count = ACTIVE_NUM_ROWS_INVALID; // delete any current results if( current_results ) { @@ -828,7 +826,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient // First time only if ( !stmt->fetch_called ) { SQLSMALLINT has_fields; - if (stmt->columns_rows_obtained) { + if (stmt->column_count != ACTIVE_NUM_COLS_INVALID) { has_fields = stmt->column_count; } else { has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); @@ -1078,21 +1076,25 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { - SQLSMALLINT num_cols, rows_affected; - if (stmt->columns_rows_obtained) - { + SQLSMALLINT num_cols; + SQLLEN rows_affected; + + if (stmt->column_count != ACTIVE_NUM_COLS_INVALID) { num_cols = stmt->column_count; - rows_affected = stmt->row_count; } - else - { + else { // Use SQLNumResultCols to determine if we have rows or not num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); stmt->column_count = num_cols; + } + + if (stmt->row_count != ACTIVE_NUM_ROWS_INVALID) { + rows_affected = stmt->row_count; + } + else { // Use SQLRowCount to determine if there is a rows status waiting rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); stmt->row_count = rows_affected; - stmt->columns_rows_obtained = true; } return (num_cols != 0) || (rows_affected > 0); diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index aa97231e8..0765b7d47 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1791,7 +1791,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) if (num_cols == 0) { getMetaData = true; - if (stmt->columns_rows_obtained == false) { + if (stmt->column_count == ACTIVE_NUM_COLS_INVALID) { num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); } else { num_cols = stmt->column_count; diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index dcddcdcd3..e4ea34728 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -196,7 +196,7 @@ ss_error SS_ERRORS[] = { { SQLSRV_ERROR_NO_FIELDS, - { IMSSP, (SQLCHAR*)"The active result for the query contains no fields, so no result set was created.", -28, false } + { IMSSP, (SQLCHAR*)"The active result for the query contains no fields.", -28, false } }, { diff --git a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt index bfea07d92..92b7f0ae2 100644 --- a/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt +++ b/test/functional/sqlsrv/sqlsrv_empty_result_error.phpt @@ -12,7 +12,7 @@ require_once("MsCommon.inc"); // These are the error messages we expect at various points below $errorNoMoreResults = "There are no more results returned by the query."; $errorNoMoreRows = "There are no more rows in the active result set. Since this result set is not scrollable, no more data may be retrieved."; -$errorNoFields = "The active result for the query contains no fields, so no result set was created."; +$errorNoFields = "The active result for the query contains no fields."; // Variable function gets an error message that depends on the OS function getFuncSeqError() diff --git a/test/functional/sqlsrv/test_warning_errors3.phpt b/test/functional/sqlsrv/test_warning_errors3.phpt index f0e7848c9..1b0bdc66a 100644 --- a/test/functional/sqlsrv/test_warning_errors3.phpt +++ b/test/functional/sqlsrv/test_warning_errors3.phpt @@ -128,8 +128,8 @@ Array [SQLSTATE] => IMSSP [1] => -28 [code] => -28 - [2] => The active result for the query contains no fields, so no result set was created. - [message] => The active result for the query contains no fields, so no result set was created. + [2] => The active result for the query contains no fields. + [message] => The active result for the query contains no fields. ) ) From b0251101943a08cc43b1e499b664200c56325afc Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 12 Apr 2019 22:41:06 -0700 Subject: [PATCH 122/249] Fixed test failure --- source/pdo_sqlsrv/pdo_stmt.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index f8dd160cc..e72b5dc25 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -714,13 +714,8 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta // which is unnecessary and a performance hit if( driver_stmt->past_fetch_end || driver_stmt->cursor_type == SQL_CURSOR_DYNAMIC) { - if (driver_stmt->row_count == ACTIVE_NUM_ROWS_INVALID) { - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); - driver_stmt->row_count = stmt->row_count; - } - else { - stmt->row_count = driver_stmt->row_count; - } + stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + driver_stmt->row_count = stmt->row_count; // a row_count of -1 means no rows, but we change it to 0 if( stmt->row_count == -1 ) { From 368d088000e50cba04d27fd61de4bdec10de24ea Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 15 Apr 2019 12:22:26 -0700 Subject: [PATCH 123/249] Made Azure AD tests more robust (#973) --- .../pdo_sqlsrv/pdo_azure_ad_access_token.phpt | 42 ++++++++++++++--- .../pdo_azure_ad_authentication.phpt | 36 +++++++++++---- .../sqlsrv/sqlsrv_azure_ad_access_token.phpt | 42 ++++++++++++----- .../sqlsrv_azure_ad_authentication.phpt | 45 ++++++++++++++----- 4 files changed, 129 insertions(+), 36 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt index f468ffd6e..ce36df037 100644 --- a/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt @@ -129,6 +129,25 @@ function simpleTest($conn) dropTable($conn, $tableName); } +function connectAzureDB($accToken, $showException) +{ + global $adServer, $adDatabase, $maxAttempts; + + $conn = false; + try { + $connectionInfo = "Database = $adDatabase; AccessToken = $accToken;"; + $conn = new PDO("sqlsrv:server = $adServer; $connectionInfo"); + } catch (PDOException $e) { + if ($showException) { + echo "Could not connect with Azure AD AccessToken after $maxAttempts retries.\n"; + print_r($e->getMessage()); + echo PHP_EOL; + } + } + + return $conn; +} + // First test some error conditions require_once('MsSetup.inc'); connectWithInvalidOptions($server); @@ -138,13 +157,26 @@ connectWithEmptyAccessToken($server); // Next, test with a valid access token and perform some simple tasks require_once('access_token.inc'); +$maxAttempts = 3; + try { if ($adServer != 'TARGET_AD_SERVER' && $accToken != 'TARGET_ACCESS_TOKEN') { - $connectionInfo = "Database = $adDatabase; AccessToken = $accToken;"; - $conn = new PDO("sqlsrv:server = $adServer; $connectionInfo"); - $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); - simpleTest($conn); - unset($conn); + $conn = false; + $numAttempts = 0; + do { + $conn = connectAzureDB($accToken, ($numAttempts == ($maxAttempts - 1))); + if ($conn === false) { + $numAttempts++; + sleep(10); + } + } while ($conn === false && $numAttempts < $maxAttempts); + + // Proceed when successfully connected + if ($conn) { + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + simpleTest($conn); + unset($conn); + } } } catch(PDOException $e) { print_r( $e->getMessage() ); diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt index 105f63953..d5cf54b08 100644 --- a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt @@ -55,21 +55,39 @@ try { // your credentials to test, or this part is skipped. // $azureServer = $adServer; -$azureDatabase = $adDatabase; -$azureUsername = $adUser; -$azurePassword = $adPassword; +$maxAttempts = 3; -if ($azureServer != 'TARGET_AD_SERVER') { +function connectAzureDB($showException) +{ + global $adServer, $adUser, $adPassword, $maxAttempts; + $connectionInfo = "Authentication = ActiveDirectoryPassword; TrustServerCertificate = false"; - + + $conn = false; try { - $conn = new PDO("sqlsrv:server = $azureServer ; $connectionInfo", $azureUsername, $azurePassword); + $conn = new PDO("sqlsrv:server = $adServer; $connectionInfo", $adUser, $adPassword); echo "Connected successfully with Authentication=ActiveDirectoryPassword.\n"; } catch (PDOException $e) { - echo "Could not connect with ActiveDirectoryPassword.\n"; - print_r($e->getMessage()); - echo "\n"; + if ($showException) { + echo "Could not connect with ActiveDirectoryPassword after $maxAttempts retries.\n"; + print_r($e->getMessage()); + echo "\n"; + } } + + return $conn; +} + +if ($azureServer != 'TARGET_AD_SERVER') { + $conn = false; + $numAttempts = 0; + do { + $conn = connectAzureDB($numAttempts == ($maxAttempts - 1)); + if ($conn === false) { + $numAttempts++; + sleep(10); + } + } while ($conn === false && $numAttempts < $maxAttempts); } else { echo "Not testing with Authentication=ActiveDirectoryPassword.\n"; } diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt index 96c82d63a..fcbc40801 100644 --- a/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt @@ -104,25 +104,47 @@ function simpleTest($conn) dropTable($conn, $tableName); } -// First test some error conditions -connectWithInvalidOptions($server); - -// Then, test with an empty access token -connectWithEmptyAccessToken($server); - -// Next, test with a valid access token and perform some simple tasks -require_once('access_token.inc'); -if ($adServer != 'TARGET_AD_SERVER' && $accToken != 'TARGET_ACCESS_TOKEN') { +function connectAzureDB($accToken, $showException) +{ + global $adServer, $adDatabase, $maxAttempts; + + $conn = false; $connectionInfo = array("Database"=>$adDatabase, "AccessToken"=>$accToken); $conn = sqlsrv_connect($adServer, $connectionInfo); if ($conn === false) { - fatalError("Could not connect with Azure AD AccessToken.\n"); + if ($showException) { + fatalError("Could not connect with Azure AD AccessToken after $maxAttempts retries.\n"); + } } else { simpleTest($conn); sqlsrv_close($conn); } + + return $conn; +} + +// First test some error conditions +connectWithInvalidOptions($server); + +// Then, test with an empty access token +connectWithEmptyAccessToken($server); + +// Next, test with a valid access token and perform some simple tasks +require_once('access_token.inc'); +$maxAttempts = 3; + +if ($adServer != 'TARGET_AD_SERVER' && $accToken != 'TARGET_ACCESS_TOKEN') { + $conn = false; + $numAttempts = 0; + do { + $conn = connectAzureDB($accToken, ($numAttempts == ($maxAttempts - 1))); + if ($conn === false) { + $numAttempts++; + sleep(10); + } + } while ($conn === false && $numAttempts < $maxAttempts); } echo "Done\n"; diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt index 51f61b276..f02a6f62b 100644 --- a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt @@ -54,23 +54,44 @@ if ($conn === false) { // Test Azure AD on an Azure database instance. Replace $azureServer, etc with // your credentials to test, or this part is skipped. // -$azureServer = $adServer; -$azureDatabase = $adDatabase; -$azureUsername = $adUser; -$azurePassword = $adPassword; - -if ($azureServer != 'TARGET_AD_SERVER') { - $connectionInfo = array( "UID"=>$azureUsername, "PWD"=>$azurePassword, - "Authentication"=>'ActiveDirectoryPassword', "TrustServerCertificate"=>false ); - - $conn = sqlsrv_connect($azureServer, $connectionInfo); +function connectAzureDB($showException) +{ + global $adServer, $adUser, $adPassword, $maxAttempts; + + $connectionInfo = array("UID"=>$adUser, + "PWD"=>$adPassword, + "Authentication"=>'ActiveDirectoryPassword', + "TrustServerCertificate"=>false ); + + $conn = false; + $conn = sqlsrv_connect($adServer, $connectionInfo); if ($conn === false) { - echo "Could not connect with ActiveDirectoryPassword.\n"; - print_r(sqlsrv_errors()); + if ($showException) { + echo "Could not connect with ActiveDirectoryPassword after $maxAttempts retries.\n"; + print_r(sqlsrv_errors()); + } } else { echo "Connected successfully with Authentication=ActiveDirectoryPassword.\n"; sqlsrv_close($conn); } + + return $conn; +} + +$azureServer = $adServer; +$maxAttempts = 3; + +if ($azureServer != 'TARGET_AD_SERVER') { + $conn = false; + $numAttempts = 0; + do { + $conn = connectAzureDB($numAttempts == ($maxAttempts - 1)); + if ($conn === false) { + $numAttempts++; + sleep(10); + } + } while ($conn === false && $numAttempts < $maxAttempts); + } else { echo "Not testing with Authentication=ActiveDirectoryPassword.\n"; } From ad1d990cda38af30d6cd76e3adc0be42193a1438 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 15 Apr 2019 13:48:48 -0700 Subject: [PATCH 124/249] Addressed review comments --- source/shared/core_stmt.cpp | 2 ++ source/sqlsrv/stmt.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 9d7f68019..19c448e54 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -823,6 +823,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { throw core::CoreException(); } + // First time only if ( !stmt->fetch_called ) { SQLSMALLINT has_fields; @@ -830,6 +831,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient has_fields = stmt->column_count; } else { has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + stmt->column_count = has_fields; } CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 0765b7d47..9d16d24e2 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1793,6 +1793,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) getMetaData = true; if (stmt->column_count == ACTIVE_NUM_COLS_INVALID) { num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + stmt->column_count = num_cols; } else { num_cols = stmt->column_count; } From fd24a9774afda2b6d756032638a6c01465ce05c7 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 15 Apr 2019 15:15:02 -0700 Subject: [PATCH 125/249] Issue 970: use quotes for variables (#971) --- source/packagize.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/packagize.sh b/source/packagize.sh index dc9051b3c..637aefc15 100644 --- a/source/packagize.sh +++ b/source/packagize.sh @@ -1,11 +1,11 @@ #!/bin/bash -if [ "$1" == "" ]; then - cp -rf $PWD/shared $PWD/sqlsrv - cp -rf $PWD/shared $PWD/pdo_sqlsrv +if [[ -z $1 ]]; then + cp -rf "$PWD"/shared "$PWD"/sqlsrv + cp -rf "$PWD"/shared "$PWD"/pdo_sqlsrv else [[ -d $1 ]] || { echo "No such path!"; exit 1; } - cp -rf $PWD/sqlsrv $1 - cp -rf $PWD/pdo_sqlsrv $1 - cp -rf $PWD/shared $1/sqlsrv - cp -rf $PWD/shared $1/pdo_sqlsrv + cp -rf "$PWD"/sqlsrv "$1" + cp -rf "$PWD"/pdo_sqlsrv "$1" + cp -rf "$PWD"/shared "$1"/sqlsrv + cp -rf "$PWD"/shared "$1"/pdo_sqlsrv fi From c1b54aabebd97f7fc6a508d8f5908c01233e60e1 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 19 Apr 2019 13:26:34 -0700 Subject: [PATCH 126/249] Added batch query test --- .../functional/sqlsrv/sqlsrv_batch_query.phpt | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 test/functional/sqlsrv/sqlsrv_batch_query.phpt diff --git a/test/functional/sqlsrv/sqlsrv_batch_query.phpt b/test/functional/sqlsrv/sqlsrv_batch_query.phpt new file mode 100644 index 000000000..b26e7b14d --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_batch_query.phpt @@ -0,0 +1,179 @@ +--TEST-- +Test a batch query with different cursor types +--DESCRIPTION-- +Verifies that batch queries don't work with dynamic, static, and keyset +server-side cursors, and checks that correct column and row counts are +returned otherwise +--SKIPIF-- + +--FILE-- +86, 'c2_tinyint'=>0, 'c3_smallint'=>4534, 'c4_bigint'=>-1, 'c5_bit'=>0), + array('c1_int'=>-217483648, 'c2_tinyint'=>31, 'c3_smallint'=>-212, 'c4_bigint'=>546098342985694, 'c5_bit'=>1), + array('c1_int'=>0, 'c2_tinyint'=>127, 'c3_smallint'=>32767, 'c4_bigint'=>9223372036854775807, 'c5_bit'=>0), + array('c1_int'=>-432987563, 'c2_tinyint'=>255, 'c3_smallint'=>0, 'c4_bigint'=>5115115115115115115, 'c5_bit'=>0), + array('c1_int'=>7, 'c2_tinyint'=>1, 'c3_smallint'=>7, 'c4_bigint'=>7, 'c5_bit'=>1), + ); + +for ($i=0; $i < sizeof($inputs); ++$i) { + $stmt = AE\insertRow($conn, $tableName, $inputs[$i]); + sqlsrv_free_stmt($stmt); +} + +$query = "SELECT c1_int from batch_query_test; + SELECT c2_tinyint from batch_query_test; + SELECT c3_smallint from batch_query_test; + SELECT c4_bigint from batch_query_test; + SELECT c5_bit from batch_query_test;"; + +// Test the batch query with different cursor types +for ($i = 0; $i < sizeof($cursors); ++$i) +{ + $cursor = $cursors[$i]; + echo "Testing with ".$cursor." cursor...\n"; + + $stmt = sqlsrv_prepare($conn, $query, array(), array("Scrollable"=>$cursor)); + if (!$stmt) { + fatalError("Error preparing statement with ".$cursor." cursor\n"); + } + + if (!sqlsrv_execute($stmt)) { + if ($cursor == 'forward' or $cursor == 'buffered') { + fatalError("Statement execution failed unexpectedly with a ".$cursor." cursor\n"); + } else { + checkErrors($noCursor); + continue; + } + } + + // Check the column and row count before and after running through + // all results + checkColumnsAndRows($stmt, $cursor, $wrongCursor); + + while ($res = sqlsrv_fetch_array($stmt)) + { } + + checkColumnsAndRows($stmt, $cursor, $wrongCursor); + + $numResultSets = 1; + while ($next = sqlsrv_next_result($stmt)) { + checkColumnsAndRows($stmt, $cursor, $wrongCursor); + + while ($res = sqlsrv_fetch_array($stmt)) + { } + + checkColumnsAndRows($stmt, $cursor, $wrongCursor); + ++$numResultSets; + } + + if ($numResultSets != $expectedResultSets) { + fatalError("Unexpected number of result sets, expected ".$expectedResultedSets.", got ".$numResultSets."\n"); + } + + // We expect an error if sqlsrv_next_result returns false, + // but not if it returns null (i.e. if we are genuinely at + // the end of all the result sets with a buffered cursor) + if ($next === false) { + if ($cursor == 'forward') { + checkErrors($noNextResult); + } else { + fatalError("sqlsrv_next_result failed with a ".$cursor." cursor\n"); + } + } + + sqlsrv_free_stmt($stmt); +} + +dropTable($conn, $tableName); +sqlsrv_close($conn); + +echo "Done.\n"; +?> +--EXPECT-- +Testing with forward cursor... +Testing with dynamic cursor... +Testing with static cursor... +Testing with keyset cursor... +Testing with buffered cursor... +Done. From 13fe59d510583a31611adc39754aeabdd7bb691c Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 19 Apr 2019 16:02:19 -0700 Subject: [PATCH 127/249] Fixed 32 bit test failure --- test/functional/sqlsrv/sqlsrv_batch_query.phpt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_batch_query.phpt b/test/functional/sqlsrv/sqlsrv_batch_query.phpt index b26e7b14d..d3f6205df 100644 --- a/test/functional/sqlsrv/sqlsrv_batch_query.phpt +++ b/test/functional/sqlsrv/sqlsrv_batch_query.phpt @@ -91,7 +91,7 @@ sqlsrv_free_stmt($stmt); $inputs = array(array('c1_int'=>86, 'c2_tinyint'=>0, 'c3_smallint'=>4534, 'c4_bigint'=>-1, 'c5_bit'=>0), array('c1_int'=>-217483648, 'c2_tinyint'=>31, 'c3_smallint'=>-212, 'c4_bigint'=>546098342985694, 'c5_bit'=>1), - array('c1_int'=>0, 'c2_tinyint'=>127, 'c3_smallint'=>32767, 'c4_bigint'=>9223372036854775807, 'c5_bit'=>0), + array('c1_int'=>0, 'c2_tinyint'=>127, 'c3_smallint'=>32767, 'c4_bigint'=>9223372000000000000, 'c5_bit'=>0), array('c1_int'=>-432987563, 'c2_tinyint'=>255, 'c3_smallint'=>0, 'c4_bigint'=>5115115115115115115, 'c5_bit'=>0), array('c1_int'=>7, 'c2_tinyint'=>1, 'c3_smallint'=>7, 'c4_bigint'=>7, 'c5_bit'=>1), ); @@ -131,7 +131,7 @@ for ($i = 0; $i < sizeof($cursors); ++$i) // all results checkColumnsAndRows($stmt, $cursor, $wrongCursor); - while ($res = sqlsrv_fetch_array($stmt)) + while ($res = sqlsrv_fetch($stmt)) { } checkColumnsAndRows($stmt, $cursor, $wrongCursor); @@ -140,7 +140,7 @@ for ($i = 0; $i < sizeof($cursors); ++$i) while ($next = sqlsrv_next_result($stmt)) { checkColumnsAndRows($stmt, $cursor, $wrongCursor); - while ($res = sqlsrv_fetch_array($stmt)) + while ($res = sqlsrv_fetch($stmt)) { } checkColumnsAndRows($stmt, $cursor, $wrongCursor); From ea17b72c83fd373562fc1a7361af65eda17f0654 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 22 Apr 2019 16:54:43 -0700 Subject: [PATCH 128/249] Addressed review comments --- .../functional/sqlsrv/sqlsrv_batch_query.phpt | 125 +++++++++++------- 1 file changed, 75 insertions(+), 50 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_batch_query.phpt b/test/functional/sqlsrv/sqlsrv_batch_query.phpt index d3f6205df..cb388b7bb 100644 --- a/test/functional/sqlsrv/sqlsrv_batch_query.phpt +++ b/test/functional/sqlsrv/sqlsrv_batch_query.phpt @@ -3,25 +3,44 @@ Test a batch query with different cursor types --DESCRIPTION-- Verifies that batch queries don't work with dynamic, static, and keyset server-side cursors, and checks that correct column and row counts are -returned otherwise +returned otherwise. For information on the expected behaviour of cursors +with batch queries, see +https://docs.microsoft.com/en-us/previous-versions/visualstudio/aa266531(v=vs.60) --SKIPIF-- - + --FILE-- 86, 'c2_tinyint'=>0, 'c3_smallint'=>4534, 'c4_bigint'=>-1, 'c5_bit'=>0), - array('c1_int'=>-217483648, 'c2_tinyint'=>31, 'c3_smallint'=>-212, 'c4_bigint'=>546098342985694, 'c5_bit'=>1), - array('c1_int'=>0, 'c2_tinyint'=>127, 'c3_smallint'=>32767, 'c4_bigint'=>9223372000000000000, 'c5_bit'=>0), - array('c1_int'=>-432987563, 'c2_tinyint'=>255, 'c3_smallint'=>0, 'c4_bigint'=>5115115115115115115, 'c5_bit'=>0), - array('c1_int'=>7, 'c2_tinyint'=>1, 'c3_smallint'=>7, 'c4_bigint'=>7, 'c5_bit'=>1), - ); - +$inputs = array(); + +// Generate the inputs for insertRow() +for ($i = 0; $i < $expectedRows; ++$i) +{ + $inputs[] = array(); + for ($j = 0; $j < $expectedResultSets; ++$j) + { + $inputs[$i][$colName[$j]] = $data[$j][$i]; + } +} + for ($i=0; $i < sizeof($inputs); ++$i) { $stmt = AE\insertRow($conn, $tableName, $inputs[$i]); sqlsrv_free_stmt($stmt); } -$query = "SELECT c1_int from batch_query_test; - SELECT c2_tinyint from batch_query_test; - SELECT c3_smallint from batch_query_test; - SELECT c4_bigint from batch_query_test; - SELECT c5_bit from batch_query_test;"; +$query = "SELECT c1_int from $tableName; + SELECT c2_tinyint from $tableName; + SELECT c3_smallint from $tableName; + SELECT c4_bigint from $tableName; + SELECT c5_bit from $tableName;"; // Test the batch query with different cursor types for ($i = 0; $i < sizeof($cursors); ++$i) { $cursor = $cursors[$i]; - echo "Testing with ".$cursor." cursor...\n"; + echo "Testing with $cursor cursor...\n"; $stmt = sqlsrv_prepare($conn, $query, array(), array("Scrollable"=>$cursor)); if (!$stmt) { - fatalError("Error preparing statement with ".$cursor." cursor\n"); + fatalError("Error preparing statement with $cursor cursor\n"); } if (!sqlsrv_execute($stmt)) { if ($cursor == 'forward' or $cursor == 'buffered') { - fatalError("Statement execution failed unexpectedly with a ".$cursor." cursor\n"); + fatalError("Statement execution failed unexpectedly with a $cursor cursor\n"); } else { checkErrors($noCursor); continue; } } - // Check the column and row count before and after running through - // all results - checkColumnsAndRows($stmt, $cursor, $wrongCursor); - - while ($res = sqlsrv_fetch($stmt)) - { } - - checkColumnsAndRows($stmt, $cursor, $wrongCursor); + $numResultSets = 0; - $numResultSets = 1; - while ($next = sqlsrv_next_result($stmt)) { + // Check the column and row count before and after running through + // each result set, because some cursor types may return the number + // of rows only after fetching all rows in the result set + do { checkColumnsAndRows($stmt, $cursor, $wrongCursor); - while ($res = sqlsrv_fetch($stmt)) - { } - + $row = 0; + while ($res = sqlsrv_fetch_array($stmt)) { + if ($res[0] != $data[$numResultSets][$row]) { + fatalError("Wrong result, expected ".$data[$numResultSets][$row].", got $res[0]\n"); + } + ++$row; + } + checkColumnsAndRows($stmt, $cursor, $wrongCursor); ++$numResultSets; - } + + } while ($next = sqlsrv_next_result($stmt)); if ($numResultSets != $expectedResultSets) { - fatalError("Unexpected number of result sets, expected ".$expectedResultedSets.", got ".$numResultSets."\n"); + fatalError("Unexpected number of result sets, expected $expectedResultedSets, got $numResultSets\n"); } // We expect an error if sqlsrv_next_result returns false, @@ -158,7 +183,7 @@ for ($i = 0; $i < sizeof($cursors); ++$i) if ($cursor == 'forward') { checkErrors($noNextResult); } else { - fatalError("sqlsrv_next_result failed with a ".$cursor." cursor\n"); + fatalError("sqlsrv_next_result failed with a $cursor cursor\n"); } } From 63c6bd385f7f0e80487d682359d751e213a23c84 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 23 Apr 2019 11:28:44 -0700 Subject: [PATCH 129/249] Formatting changes --- .../functional/sqlsrv/sqlsrv_batch_query.phpt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_batch_query.phpt b/test/functional/sqlsrv/sqlsrv_batch_query.phpt index cb388b7bb..36057a579 100644 --- a/test/functional/sqlsrv/sqlsrv_batch_query.phpt +++ b/test/functional/sqlsrv/sqlsrv_batch_query.phpt @@ -39,7 +39,7 @@ $expectedCols = 1; $expectedRows = sizeof($data[0]); // Expected result sets = number of columns, since the batch fetches each column sequentially -$expectedResultSets = 5; +$expectedResultSets = sizeof($colName); function checkErrors($expectedError) { @@ -111,11 +111,9 @@ sqlsrv_free_stmt($stmt); $inputs = array(); // Generate the inputs for insertRow() -for ($i = 0; $i < $expectedRows; ++$i) -{ +for ($i = 0; $i < $expectedRows; ++$i) { $inputs[] = array(); - for ($j = 0; $j < $expectedResultSets; ++$j) - { + for ($j = 0; $j < $expectedResultSets; ++$j) { $inputs[$i][$colName[$j]] = $data[$j][$i]; } } @@ -125,15 +123,14 @@ for ($i=0; $i < sizeof($inputs); ++$i) { sqlsrv_free_stmt($stmt); } -$query = "SELECT c1_int from $tableName; - SELECT c2_tinyint from $tableName; - SELECT c3_smallint from $tableName; - SELECT c4_bigint from $tableName; - SELECT c5_bit from $tableName;"; +$query = "SELECT c1_int FROM $tableName; + SELECT c2_tinyint FROM $tableName; + SELECT c3_smallint FROM $tableName; + SELECT c4_bigint FROM $tableName; + SELECT c5_bit FROM $tableName;"; // Test the batch query with different cursor types -for ($i = 0; $i < sizeof($cursors); ++$i) -{ +for ($i = 0; $i < sizeof($cursors); ++$i) { $cursor = $cursors[$i]; echo "Testing with $cursor cursor...\n"; From ee3c85afa863eaff81512ad6399de572ffb943d9 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 24 Apr 2019 11:37:31 -0700 Subject: [PATCH 130/249] Used different skipif conditions for these two tests that require AE connections (#977) --- .../pdo_sqlsrv/pdo_569_query_varcharmax.phpt | 31 ++++++++++------ .../sqlsrv/srv_569_query_varcharmax.phpt | 36 ++++++++++--------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt index 671cbc269..0d3617336 100644 --- a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt +++ b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt @@ -5,23 +5,32 @@ Verifies that the problem is no longer reproducible. --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- + $database, "UID" => $userName, "PWD" => $userPassword); +$conn = sqlsrv_connect($server, $options); +if ($conn === false) { + die("Error: could not connect during SKIPIF!"); +} + +if (!AE\isQualified($conn)) { + die("skip - AE feature not supported in the current environment."); +} +?> --FILE-- $database, "UID" => $userName, "PWD" => $userPassword); -$conn = sqlsrv_connect($server, $options); -if ($conn === false) { - fatalError("Failed to connect to $server."); -} - -if (!AE\isQualified($conn)) { - echo "Done\n"; - return; -} -sqlsrv_close($conn); - // Now connect with ColumnEncryption enabled -$connectionOptions = array_merge($options, array('ColumnEncryption' => 'Enabled')); +$connectionOptions = array("Database" => $database, "UID" => $userName, "PWD" => $userPassword, 'ColumnEncryption' => 'Enabled'); $conn = sqlsrv_connect($server, $connectionOptions); if ($conn === false) { fatalError("Failed to connect to $server."); From 0f93bbe6fed23e7114f4e75717a19474a71ee721 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 24 Apr 2019 12:49:33 -0700 Subject: [PATCH 131/249] Simplified insert logic --- test/functional/sqlsrv/sqlsrv_batch_query.phpt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_batch_query.phpt b/test/functional/sqlsrv/sqlsrv_batch_query.phpt index 36057a579..4cfa1445a 100644 --- a/test/functional/sqlsrv/sqlsrv_batch_query.phpt +++ b/test/functional/sqlsrv/sqlsrv_batch_query.phpt @@ -110,16 +110,14 @@ sqlsrv_free_stmt($stmt); $inputs = array(); -// Generate the inputs for insertRow() +// Insert each row. Need an associative array to use insertRow() for ($i = 0; $i < $expectedRows; ++$i) { - $inputs[] = array(); + $inputs = array(); for ($j = 0; $j < $expectedResultSets; ++$j) { - $inputs[$i][$colName[$j]] = $data[$j][$i]; + $inputs[$colName[$j]] = $data[$j][$i]; } -} -for ($i=0; $i < sizeof($inputs); ++$i) { - $stmt = AE\insertRow($conn, $tableName, $inputs[$i]); + $stmt = AE\insertRow($conn, $tableName, $inputs); sqlsrv_free_stmt($stmt); } From a8e1138fd6c5bcbf2639929272d76f55a9b1a5c8 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 24 Apr 2019 16:06:33 -0700 Subject: [PATCH 132/249] Modified get column meta method to reference saved metadata (#978) --- .travis.yml | 4 ++-- source/pdo_sqlsrv/pdo_stmt.cpp | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index b94c0a186..8e9f1d30a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,10 +20,10 @@ env: - TEST_PHP_SQL_PWD=Password123 before_install: - - docker pull mcr.microsoft.com/mssql/server:2017-latest + - docker pull mcr.microsoft.com/mssql/server:2019-CTP2.4-ubuntu install: - - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2017-latest + - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-CTP2.4-ubuntu - docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql . before_script: diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index e72b5dc25..6aa36e5e2 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -478,7 +478,6 @@ int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno TSRML // Set the name column_data->name = zend_string_init( (const char*)core_meta_data->field_name.get(), core_meta_data->field_name_len, 0 ); - core_meta_data->field_name.reset(); // Set the maxlen column_data->maxlen = ( core_meta_data->field_precision > 0 ) ? core_meta_data->field_precision : core_meta_data->field_size; @@ -1066,10 +1065,12 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno // initialize the array to nothing, as PDO requires us to create it core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); - sqlsrv_malloc_auto_ptr core_meta_data; - - core_meta_data = core_sqlsrv_field_metadata( driver_stmt, (SQLSMALLINT) colno TSRMLS_CC ); + field_meta_data* core_meta_data; + // metadata should have been saved earlier + SQLSRV_ASSERT(colno < driver_stmt->current_meta_data.size(), "pdo_sqlsrv_stmt_get_col_meta: Metadata vector out of sync with column numbers"); + core_meta_data = driver_stmt->current_meta_data[colno]; + // add the following fields: flags, native_type, driver:decl_type, table add_assoc_long( return_value, "flags", 0 ); @@ -1109,9 +1110,6 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno if( stmt->columns && stmt->columns[colno].param_type == PDO_PARAM_ZVAL ) { add_assoc_long( return_value, "pdo_type", pdo_type ); } - - // this will ensure that the field_name field, which is an auto pointer gets freed. - (*core_meta_data).~field_meta_data(); } catch( core::CoreException& ) { From e8d13896ba29efc32e0c8c4c3fc6141506c29791 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 25 Apr 2019 15:58:24 -0700 Subject: [PATCH 133/249] Revert "Used different skipif conditions for these two tests that require AE connections (#977)" (#980) This reverts commit ee3c85afa863eaff81512ad6399de572ffb943d9. --- .../pdo_sqlsrv/pdo_569_query_varcharmax.phpt | 31 ++++++---------- .../sqlsrv/srv_569_query_varcharmax.phpt | 36 +++++++++---------- 2 files changed, 27 insertions(+), 40 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt index 0d3617336..671cbc269 100644 --- a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt +++ b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt @@ -5,32 +5,23 @@ Verifies that the problem is no longer reproducible. --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- $database, "UID" => $userName, "PWD" => $userPassword); -$conn = sqlsrv_connect($server, $options); -if ($conn === false) { - die("Error: could not connect during SKIPIF!"); -} - -if (!AE\isQualified($conn)) { - die("skip - AE feature not supported in the current environment."); -} -?> + --FILE-- $database, "UID" => $userName, "PWD" => $userPassword); +$conn = sqlsrv_connect($server, $options); +if ($conn === false) { + fatalError("Failed to connect to $server."); +} + +if (!AE\isQualified($conn)) { + echo "Done\n"; + return; +} +sqlsrv_close($conn); + // Now connect with ColumnEncryption enabled -$connectionOptions = array("Database" => $database, "UID" => $userName, "PWD" => $userPassword, 'ColumnEncryption' => 'Enabled'); +$connectionOptions = array_merge($options, array('ColumnEncryption' => 'Enabled')); $conn = sqlsrv_connect($server, $connectionOptions); if ($conn === false) { fatalError("Failed to connect to $server."); From c5b6540498e8828621ac5aa2c37c6f1392bd9950 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 26 Apr 2019 16:30:15 -0700 Subject: [PATCH 134/249] Fixed failing tests (#981) --- .../pdo_sqlsrv/pdo_569_query_varcharmax.phpt | 19 +++++++-------- .../pdo_sqlsrv/pdo_construct_attr_errors.phpt | 5 +++- .../sqlsrv/srv_569_query_varcharmax.phpt | 23 +++++++++---------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt index 671cbc269..16947da08 100644 --- a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt +++ b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax.phpt @@ -5,7 +5,7 @@ Verifies that the problem is no longer reproducible. --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $tableName = 'pdoTestTable_569'; dropTable($conn, $tableName); - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + if ($qualified && strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $tsql = "CREATE TABLE $tableName ([c1] varchar(max) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (ENCRYPTION_TYPE = deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AEColumnKey))"; } else { $tsql = "CREATE TABLE $tableName ([c1] varchar(max))"; diff --git a/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt b/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt index a27bdc39f..a31974d94 100644 --- a/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt +++ b/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt @@ -73,12 +73,15 @@ function invalidCredentials() $options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); $error1 = "*Login failed for user \'*\'."; $error2 = "*Login timeout expired*"; + $error3 = "*Could not open a connection to SQL Server*"; try { $conn = new PDO("sqlsrv:server = $server; database = $database;", $user, $passwd, $options); echo "Should have failed to connect\n"; } catch (PDOException $e) { - if (fnmatch($error1, $e->getMessage()) || fnmatch($error2, $e->getMessage())) { + if (fnmatch($error1, $e->getMessage()) || + fnmatch($error2, $e->getMessage()) || + fnmatch($error3, $e->getMessage())) { ; // matched at least one of the expected error messages } else { echo "invalidCredentials()\n"; diff --git a/test/functional/sqlsrv/srv_569_query_varcharmax.phpt b/test/functional/sqlsrv/srv_569_query_varcharmax.phpt index 314ec54cc..6d4bab355 100644 --- a/test/functional/sqlsrv/srv_569_query_varcharmax.phpt +++ b/test/functional/sqlsrv/srv_569_query_varcharmax.phpt @@ -5,7 +5,7 @@ Verifies that the problem is no longer reproducible. --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- 'Enabled')); -$conn = sqlsrv_connect($server, $connectionOptions); -if ($conn === false) { - fatalError("Failed to connect to $server."); + // Now connect with ColumnEncryption enabled + $connectionOptions = array_merge($options, array('ColumnEncryption' => 'Enabled')); + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) { + fatalError("Failed to connect to $server."); + } } $tableName = 'srvTestTable_569'; dropTable($conn, $tableName); -if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { +if ($qualified && strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $tsql = "CREATE TABLE $tableName ([c1] varchar(max) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (ENCRYPTION_TYPE = deterministic, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AEColumnKey))"; } else { $tsql = "CREATE TABLE $tableName ([c1] varchar(max))"; From 9e90a42d1b655e861b0ad3c51461bd85060b231b Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 1 May 2019 08:03:33 -0700 Subject: [PATCH 135/249] Data Classification sensitivity metadata retrieval (#979) --- source/pdo_sqlsrv/pdo_dbh.cpp | 133 +++++---- source/pdo_sqlsrv/pdo_init.cpp | 1 + source/pdo_sqlsrv/pdo_stmt.cpp | 26 +- source/pdo_sqlsrv/pdo_util.cpp | 12 + source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 3 +- source/shared/core_sqlsrv.h | 94 ++++++ source/shared/core_stmt.cpp | 122 ++++++++ source/shared/core_util.cpp | 189 ++++++++++++ source/shared/msodbcsql.h | 6 +- source/sqlsrv/conn.cpp | 7 + source/sqlsrv/stmt.cpp | 8 + source/sqlsrv/util.cpp | 12 + .../pdo_sqlsrv/pdo_data_classification.phpt | 270 ++++++++++++++++++ .../sqlsrv/sqlsrv_data_classification.phpt | 261 +++++++++++++++++ 14 files changed, 1079 insertions(+), 65 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_data_classification.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_data_classification.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 0f87c7c29..f0238fb6d 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -87,7 +87,8 @@ enum PDO_STMT_OPTIONS { PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, PDO_STMT_OPTION_FORMAT_DECIMALS, - PDO_STMT_OPTION_DECIMAL_PLACES + PDO_STMT_OPTION_DECIMAL_PLACES, + PDO_STMT_OPTION_DATA_CLASSIFICATION }; // List of all the statement options supported by this driver. @@ -104,6 +105,7 @@ const stmt_option PDO_STMT_OPTS[] = { { NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr( new stmt_option_fetch_datetime ) }, { NULL, 0, PDO_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr( new stmt_option_format_decimals ) }, { NULL, 0, PDO_STMT_OPTION_DECIMAL_PLACES, std::unique_ptr( new stmt_option_decimal_places ) }, + { NULL, 0, PDO_STMT_OPTION_DATA_CLASSIFICATION, std::unique_ptr( new stmt_option_data_classification ) }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; @@ -1136,6 +1138,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_DATA_CLASSIFICATION: { THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); } @@ -1193,7 +1196,8 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout // Statement level only case PDO_ATTR_EMULATE_PREPARES: case PDO_ATTR_CURSOR: - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + case SQLSRV_ATTR_DATA_CLASSIFICATION: { THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR ); } @@ -1594,70 +1598,75 @@ namespace { // Maps the PDO driver specific statement option/attribute constants to the core layer // statement option/attribute constants. -void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ HashTable* options_ht, - _Inout_ zval* data TSRMLS_DC ) +void add_stmt_option_key(_Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ HashTable* options_ht, + _Inout_ zval* data TSRMLS_DC) { - zend_ulong option_key = -1; - switch( key ) { - - case PDO_ATTR_CURSOR: - option_key = SQLSRV_STMT_OPTION_SCROLLABLE; - break; - - case SQLSRV_ATTR_ENCODING: - option_key = PDO_STMT_OPTION_ENCODING; - break; - - case SQLSRV_ATTR_QUERY_TIMEOUT: - option_key = SQLSRV_STMT_OPTION_QUERY_TIMEOUT; - break; - - case PDO_ATTR_STATEMENT_CLASS: - break; - - case SQLSRV_ATTR_DIRECT_QUERY: - option_key = PDO_STMT_OPTION_DIRECT_QUERY; - break; - - case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: - option_key = PDO_STMT_OPTION_CURSOR_SCROLL_TYPE; - break; - - case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - option_key = PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE; - break; - - case PDO_ATTR_EMULATE_PREPARES: - option_key = PDO_STMT_OPTION_EMULATE_PREPARES; - break; - - case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: - option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; - break; - - case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: - option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE; - break; - - case SQLSRV_ATTR_FORMAT_DECIMALS: - option_key = PDO_STMT_OPTION_FORMAT_DECIMALS; - break; - - case SQLSRV_ATTR_DECIMAL_PLACES: - option_key = PDO_STMT_OPTION_DECIMAL_PLACES; - break; - - default: - CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { - throw core::CoreException(); - } - break; + zend_ulong option_key = -1; + switch (key) { + + case PDO_ATTR_CURSOR: + option_key = SQLSRV_STMT_OPTION_SCROLLABLE; + break; + + case SQLSRV_ATTR_ENCODING: + option_key = PDO_STMT_OPTION_ENCODING; + break; + + case SQLSRV_ATTR_QUERY_TIMEOUT: + option_key = SQLSRV_STMT_OPTION_QUERY_TIMEOUT; + break; + + case PDO_ATTR_STATEMENT_CLASS: + break; + + case SQLSRV_ATTR_DIRECT_QUERY: + option_key = PDO_STMT_OPTION_DIRECT_QUERY; + break; + + case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: + option_key = PDO_STMT_OPTION_CURSOR_SCROLL_TYPE; + break; + + case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: + option_key = PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE; + break; + + case PDO_ATTR_EMULATE_PREPARES: + option_key = PDO_STMT_OPTION_EMULATE_PREPARES; + break; + + case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: + option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; + break; + + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: + option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE; + break; + + case SQLSRV_ATTR_FORMAT_DECIMALS: + option_key = PDO_STMT_OPTION_FORMAT_DECIMALS; + break; + + case SQLSRV_ATTR_DECIMAL_PLACES: + option_key = PDO_STMT_OPTION_DECIMAL_PLACES; + break; + + case SQLSRV_ATTR_DATA_CLASSIFICATION: + option_key = PDO_STMT_OPTION_DATA_CLASSIFICATION; + break; + + default: + CHECK_CUSTOM_ERROR(true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION) + { + throw core::CoreException(); + } + break; } // if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it - if( option_key != -1 ) { - zval_add_ref( data ); - core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data TSRMLS_CC ); + if (option_key != -1) { + zval_add_ref(data); + core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data TSRMLS_CC); } } diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index bee4cfc52..e913ca8f1 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -294,6 +294,7 @@ namespace { { "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE }, { "SQLSRV_ATTR_FORMAT_DECIMALS" , SQLSRV_ATTR_FORMAT_DECIMALS }, { "SQLSRV_ATTR_DECIMAL_PLACES" , SQLSRV_ATTR_DECIMAL_PLACES }, + { "SQLSRV_ATTR_DATA_CLASSIFICATION" , SQLSRV_ATTR_DATA_CLASSIFICATION }, // 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 diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 6aa36e5e2..edc5284d3 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -912,6 +912,10 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In core_sqlsrv_set_decimal_places(driver_stmt, val TSRMLS_CC); break; + case SQLSRV_ATTR_DATA_CLASSIFICATION: + driver_stmt->data_classification = (zend_is_true(val)) ? true : false; + break; + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -1011,6 +1015,12 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; } + case SQLSRV_ATTR_DATA_CLASSIFICATION: + { + ZVAL_BOOL(return_value, driver_stmt->data_classification); + break; + } + default: THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR ); break; @@ -1072,7 +1082,21 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno core_meta_data = driver_stmt->current_meta_data[colno]; // add the following fields: flags, native_type, driver:decl_type, table - add_assoc_long( return_value, "flags", 0 ); + if (driver_stmt->data_classification) { + core_sqlsrv_sensitivity_metadata(driver_stmt); + + // initialize the column data classification array + zval data_classification; + ZVAL_UNDEF(&data_classification); + core::sqlsrv_array_init(*driver_stmt, &data_classification TSRMLS_CC ); + + data_classification::fill_column_sensitivity_array(driver_stmt, (SQLSMALLINT)colno, &data_classification); + + add_assoc_zval(return_value, "flags", &data_classification); + } + else { + add_assoc_long(return_value, "flags", 0); + } // get the name of the data type char field_type_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'}; diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index e399fda11..b64477404 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -449,6 +449,18 @@ pdo_error PDO_ERRORS[] = { SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL, { IMSSP, (SQLCHAR*) "When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted.", -93, false} }, + { + SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION, + { IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -94, false} + }, + { + SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE, + { IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata. If the driver and the server both support the Data Classification feature, check whether the query returns columns with classification information.", -95, false} + }, + { + SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, + { IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -96, true} + }, { UINT_MAX, {} } }; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 4b50bbc75..841b9e43b 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -79,7 +79,8 @@ enum PDO_SQLSRV_ATTR { SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, SQLSRV_ATTR_FETCHES_DATETIME_TYPE, SQLSRV_ATTR_FORMAT_DECIMALS, - SQLSRV_ATTR_DECIMAL_PLACES + SQLSRV_ATTR_DECIMAL_PLACES, + SQLSRV_ATTR_DATA_CLASSIFICATION }; // valid set of values for TransactionIsolation connection option diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 347a89d78..e573f953d 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1120,6 +1120,7 @@ enum SQLSRV_STMT_OPTIONS { SQLSRV_STMT_OPTION_DATE_AS_STRING, SQLSRV_STMT_OPTION_FORMAT_DECIMALS, SQLSRV_STMT_OPTION_DECIMAL_PLACES, + SQLSRV_STMT_OPTION_DATA_CLASSIFICATION, // Driver specific connection options SQLSRV_STMT_OPTION_DRIVER_SPECIFIC = 1000, @@ -1321,6 +1322,11 @@ struct stmt_option_decimal_places : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); }; +struct stmt_option_data_classification : public stmt_option_functor { + + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); +}; + // used to hold the table for statment options struct stmt_option { @@ -1424,6 +1430,75 @@ struct sqlsrv_output_param { } }; +namespace data_classification { + // *** data classficiation metadata structures and helper methods -- to store and/or process the sensitivity classification data *** + struct name_id_pair; + struct sensitivity_metadata; + + void name_id_pair_free(name_id_pair * pair); + void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector>& pairs, _Inout_ unsigned char **pptr TSRMLS_CC); + void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr); + USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *column_data TSRMLS_CC); + + struct name_id_pair { + UCHAR name_len; + sqlsrv_malloc_auto_ptr name; + UCHAR id_len; + sqlsrv_malloc_auto_ptr id; + + name_id_pair() : name_len(0), id_len(0) + { + } + + ~name_id_pair() + { + } + }; + + struct label_infotype_pair { + USHORT label_idx; + USHORT infotype_idx; + + label_infotype_pair() : label_idx(0), infotype_idx(0) + { + } + }; + + struct column_sensitivity { + USHORT num_pairs; + std::vector label_info_pairs; + + column_sensitivity() : num_pairs(0) + { + } + + ~column_sensitivity() + { + label_info_pairs.clear(); + } + }; + + struct sensitivity_metadata { + USHORT num_labels; + std::vector> labels; + USHORT num_infotypes; + std::vector> infotypes; + USHORT num_columns; + std::vector columns_sensitivity; + + sensitivity_metadata() : num_labels(0), num_infotypes(0), num_columns(0) + { + } + + ~sensitivity_metadata() + { + reset(); + } + + void reset(); + }; +} // namespace data_classification + // forward decls struct sqlsrv_result_set; struct field_meta_data; @@ -1434,6 +1509,9 @@ struct sqlsrv_stmt : public sqlsrv_context { void free_param_data( TSRMLS_D ); virtual void new_result_set( TSRMLS_D ); + // free sensitivity classification metadata + void clean_up_sensitivity_metadata(); + sqlsrv_conn* conn; // Connection that created this statement bool executed; // Whether the statement has been executed yet (used for error messages) @@ -1451,6 +1529,7 @@ struct sqlsrv_stmt : public sqlsrv_context { bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings bool format_decimals; // false by default but the user can set this to true to add the missing leading zeroes and/or control number of decimal digits to show short decimal_places; // indicates number of decimals shown in fetched results (-1 by default, which means no change to number of decimal digits) + bool data_classification; // false by default but the user can set this to true to retrieve data classification sensitivity metadata // holds output pointers for SQLBindParameter // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving @@ -1473,6 +1552,9 @@ struct sqlsrv_stmt : public sqlsrv_context { // meta data for current result set std::vector> current_meta_data; + // meta data for data classification + sqlsrv_malloc_auto_ptr current_sensitivity_metadata; + sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv TSRMLS_DC ); virtual ~sqlsrv_stmt( void ); @@ -1544,6 +1626,7 @@ 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_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC); +void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); //********************************************************************************************************************************* // Result Set @@ -1787,6 +1870,9 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_EMPTY_ACCESS_TOKEN, SQLSRV_ERROR_INVALID_DECIMAL_PLACES, SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL, + SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION, + SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE, + SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, // Driver specific error codes starts from here. SQLSRV_ERROR_DRIVER_SPECIFIC = 1000, @@ -2451,6 +2537,14 @@ namespace core { } } + inline void sqlsrv_add_assoc_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _In_ zval* val TSRMLS_DC ) + { + int zr = ::add_assoc_zval(array_z, key, val); + CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { + throw CoreException(); + } + } + inline void sqlsrv_array_init( _Inout_ sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) { #if PHP_VERSION_ID < 70300 diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 19c448e54..f3d3e6fb5 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -146,6 +146,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error date_as_string(false), format_decimals(false), // no formatting needed decimal_places(NO_CHANGE_DECIMAL_PLACES), // the default is no formatting to resultset required + data_classification(false), buffered_query_limit( sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ), param_ind_ptrs( 10 ), // initially hold 10 elements, which should cover 90% of the cases and only take < 100 byte send_streams_at_exec( true ), @@ -191,6 +192,9 @@ sqlsrv_stmt::~sqlsrv_stmt( void ) current_results = NULL; } + // delete sensivity data + clean_up_sensitivity_metadata(); + invalidate(); zval_ptr_dtor( ¶m_input_strings ); zval_ptr_dtor( &output_params ); @@ -237,6 +241,9 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) current_results = NULL; } + // delete sensivity data + clean_up_sensitivity_metadata(); + // create a new result set if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { sqlsrv_malloc_auto_ptr result; @@ -250,6 +257,16 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) } } +// free sensitivity classification metadata +void sqlsrv_stmt::clean_up_sensitivity_metadata() +{ + if (current_sensitivity_metadata) { + current_sensitivity_metadata->~sensitivity_metadata(); + sqlsrv_free(current_sensitivity_metadata); + current_sensitivity_metadata = NULL; + } +} + // core_sqlsrv_create_stmt // Common code to allocate a statement from either driver. Returns a valid driver statement object or // throws an exception if an error occurs. @@ -959,6 +976,101 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL return result_field_meta_data; } +void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +{ + sqlsrv_malloc_auto_ptr dcbuf; + SQLINTEGER dclen = 0; + SQLINTEGER dclenout = 0; + SQLHANDLE ird; + SQLRETURN r; + + try { + if (!stmt->data_classification) { + return; + } + + if (stmt->current_sensitivity_metadata != NULL) { + // Already cached, so return + return; + } + + CHECK_CUSTOM_ERROR(!stmt->executed, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION) { + throw core::CoreException(); + } + + // Reference: https://docs.microsoft.com/sql/connect/odbc/data-classification + // To retrieve sensitivity classfication data, the first step is to retrieve the IRD(Implementation Row Descriptor) handle by + // calling SQLGetStmtAttr with SQL_ATTR_IMP_ROW_DESC statement attribute + r = ::SQLGetStmtAttr(stmt->handle(), SQL_ATTR_IMP_ROW_DESC, (SQLPOINTER)&ird, SQL_IS_POINTER, 0); + CHECK_SQL_ERROR_OR_WARNING(r, stmt) { + LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in getting Implementation Row Descriptor handle." ); + throw core::CoreException(); + } + + // First call to get dclen + r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, dcbuf, 0, &dclen); + if (r != SQL_SUCCESS || dclen == 0) { + // log the error first + LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW first time." ); + + // If this fails, check if it is the "Invalid Descriptor Field error" + SQLRETURN rc; + SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'}; + SQLSMALLINT len; + rc = ::SQLGetDiagField(SQL_HANDLE_DESC, ird, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC); + + CHECK_SQL_ERROR_OR_WARNING(rc, stmt) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR(!strcmp("HY091", reinterpret_cast(state)), stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "Unexpected SQL Error state") { + throw core::CoreException(); + } + } + + // Call again to read SQL_CA_SS_DATA_CLASSIFICATION data + dcbuf = static_cast(sqlsrv_malloc(dclen * sizeof(char))); + + r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, dcbuf, dclen, &dclenout); + if (r != SQL_SUCCESS) { + LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW again." ); + + CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "SQLGetDescFieldW failed unexpectedly") { + throw core::CoreException(); + } + } + + // Start parsing the data (blob) + using namespace data_classification; + unsigned char *dcptr = dcbuf; + + sqlsrv_malloc_auto_ptr sensitivity_meta; + sensitivity_meta = new (sqlsrv_malloc(sizeof(sensitivity_metadata))) sensitivity_metadata(); + + // Parse the name id pairs for labels first then info types + parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_labels, sensitivity_meta->labels, &dcptr); + parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_infotypes, sensitivity_meta->infotypes, &dcptr); + + // Next parse the sensitivity properties + parse_column_sensitivity_props(sensitivity_meta, &dcptr); + + unsigned char *dcend = dcbuf; + dcend += dclen; + + CHECK_CUSTOM_ERROR(dcptr != dcend, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "Metadata parsing ends unexpectedly") { + throw core::CoreException(); + } + + stmt->current_sensitivity_metadata = sensitivity_meta; + sensitivity_meta.transferred(); + } catch (core::CoreException& e) { + throw e; + } +} // core_sqlsrv_get_field // Return the value of a column from ODBC @@ -1504,6 +1616,16 @@ void stmt_option_decimal_places:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op core_sqlsrv_set_decimal_places(stmt, value_z TSRMLS_CC); } +void stmt_option_data_classification:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC ) +{ + if (zend_is_true(value_z)) { + stmt->data_classification = true; + } + else { + stmt->data_classification = false; + } +} + // internal function to release the active stream. Called by each main API function // that will alter the statement and cancel any retrieval of data from a stream. void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 515eb38b1..5db20c3fc 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -414,3 +414,192 @@ unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encodin } } + + +namespace data_classification { + const char* DATA_CLASS = "Data Classification"; + const char* LABEL = "Label"; + const char* INFOTYPE = "Information Type"; + const char* NAME = "name"; + const char* ID = "id"; + + void convert_sensivity_field(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_ENCODING encoding, _In_ unsigned char *ptr, _In_ int len, _Inout_updates_bytes_(cchOutLen) char** field_name) + { + sqlsrv_malloc_auto_ptr temp_field_name; + int temp_field_len = len * 2; + SQLLEN field_name_len = 0; + + temp_field_name = static_cast(sqlsrv_malloc((len + 1) * sizeof(SQLWCHAR))); + memcpy_s(temp_field_name, temp_field_len, ptr, temp_field_len); + temp_field_name[temp_field_len] = '\0'; + + bool converted = convert_string_from_utf16(encoding, temp_field_name, len, field_name, field_name_len); + + CHECK_CUSTOM_ERROR(!converted, stmt, SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE, get_last_error_message()) { + throw core::CoreException(); + } + } + + void name_id_pair_free(_Inout_ name_id_pair* pair) + { + if (pair->name) { + pair->name.reset(); + } + if (pair->id) { + pair->id.reset(); + } + sqlsrv_free(pair); + } + + void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector>& pairs, _Inout_ unsigned char **pptr) + { + unsigned char *ptr = *pptr; + unsigned short npairs; + numpairs = npairs = *(unsigned short*)ptr; + SQLSRV_ENCODING encoding = ((stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); + + ptr += sizeof(unsigned short); + while (npairs--) { + int namelen, idlen; + unsigned char *nameptr, *idptr; + + sqlsrv_malloc_auto_ptr pair; + pair = new(sqlsrv_malloc(sizeof(name_id_pair))) name_id_pair(); + + sqlsrv_malloc_auto_ptr name; + sqlsrv_malloc_auto_ptr id; + + namelen = *ptr++; + nameptr = ptr; + + pair->name_len = namelen; + convert_sensivity_field(stmt, encoding, nameptr, namelen, (char**)&name); + pair->name = name; + + ptr += namelen * 2; + idlen = *ptr++; + idptr = ptr; + ptr += idlen * 2; + + pair->id_len = idlen; + convert_sensivity_field(stmt, encoding, idptr, idlen, (char**)&id); + pair->id = id; + + pairs.push_back(pair.get()); + pair.transferred(); + } + *pptr = ptr; + } + + void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr) + { + unsigned char *ptr = *pptr; + unsigned short ncols; + + // Get number of columns + meta->num_columns = ncols = *(reinterpret_cast(ptr)); + + // Move forward + ptr += sizeof(unsigned short); + + while (ncols--) { + unsigned short npairs = *(reinterpret_cast(ptr)); + + ptr += sizeof(unsigned short); + + column_sensitivity column; + column.num_pairs = npairs; + + while (npairs--) { + label_infotype_pair pair; + + unsigned short labelidx, typeidx; + labelidx = *(reinterpret_cast(ptr)); + ptr += sizeof(unsigned short); + typeidx = *(reinterpret_cast(ptr)); + ptr += sizeof(unsigned short); + + pair.label_idx = labelidx; + pair.infotype_idx = typeidx; + + column.label_info_pairs.push_back(pair); + } + + meta->columns_sensitivity.push_back(column); + } + + *pptr = ptr; + } + + USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *return_array TSRMLS_CC) + { + sensitivity_metadata* meta = stmt->current_sensitivity_metadata; + if (meta == NULL) { + return 0; + } + + SQLSRV_ASSERT(colno >= 0 && colno < meta->num_columns, "fill_column_sensitivity_array: column number out of bounds"); + + zval data_classification; + ZVAL_UNDEF(&data_classification); + core::sqlsrv_array_init(*stmt, &data_classification TSRMLS_CC ); + + USHORT num_pairs = meta->columns_sensitivity[colno].num_pairs; + + if (num_pairs == 0) { + core::sqlsrv_add_assoc_zval(*stmt, return_array, DATA_CLASS, &data_classification TSRMLS_CC); + + return 0; + } + + zval sensitivity_properties; + ZVAL_UNDEF(&sensitivity_properties); + core::sqlsrv_array_init(*stmt, &sensitivity_properties TSRMLS_CC); + + for (USHORT j = 0; j < num_pairs; j++) { + zval label_array, infotype_array; + ZVAL_UNDEF(&label_array); + ZVAL_UNDEF(&infotype_array); + + core::sqlsrv_array_init(*stmt, &label_array TSRMLS_CC); + core::sqlsrv_array_init(*stmt, &infotype_array TSRMLS_CC); + + USHORT labelidx = meta->columns_sensitivity[colno].label_info_pairs[j].label_idx; + USHORT typeidx = meta->columns_sensitivity[colno].label_info_pairs[j].infotype_idx; + + char *label = meta->labels[labelidx]->name; + char *label_id = meta->labels[labelidx]->id; + char *infotype = meta->infotypes[typeidx]->name; + char *infotype_id = meta->infotypes[typeidx]->id; + + core::sqlsrv_add_assoc_string(*stmt, &label_array, NAME, label, 1 TSRMLS_CC); + core::sqlsrv_add_assoc_string(*stmt, &label_array, ID, label_id, 1 TSRMLS_CC); + + core::sqlsrv_add_assoc_zval(*stmt, &sensitivity_properties, LABEL, &label_array TSRMLS_CC); + + core::sqlsrv_add_assoc_string(*stmt, &infotype_array, NAME, infotype, 1 TSRMLS_CC); + core::sqlsrv_add_assoc_string(*stmt, &infotype_array, ID, infotype_id, 1 TSRMLS_CC); + + core::sqlsrv_add_assoc_zval(*stmt, &sensitivity_properties, INFOTYPE, &infotype_array TSRMLS_CC); + + // add the pair of sensitivity properties to data_classification + core::sqlsrv_add_next_index_zval(*stmt, &data_classification, &sensitivity_properties TSRMLS_CC ); + } + + // add data classfication as associative array + core::sqlsrv_add_assoc_zval(*stmt, return_array, DATA_CLASS, &data_classification TSRMLS_CC); + + return num_pairs; + } + + void sensitivity_metadata::reset() + { + std::for_each(labels.begin(), labels.end(), name_id_pair_free); + labels.clear(); + + std::for_each(infotypes.begin(), infotypes.end(), name_id_pair_free); + infotypes.clear(); + + columns_sensitivity.clear(); + } +} // namespace data_classification \ No newline at end of file diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index 2392c5e39..3a1555e81 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -144,7 +144,11 @@ // force column encryption #define SQL_CA_SS_FORCE_ENCRYPT (SQL_CA_SS_BASE+36) // indicate mandatory encryption for this parameter -#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+37) +// Data Classification +#define SQL_CA_SS_DATA_CLASSIFICATION (SQL_CA_SS_BASE+37) // retrieve data classification information + +#define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+38) + // Defines for use with SQL_COPT_SS_INTEGRATED_SECURITY - Pre-Connect Option only #define SQL_IS_OFF 0L // Integrated security isn't used #define SQL_IS_ON 1L // Integrated security is used diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index f4827863e..1275e7234 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -219,6 +219,7 @@ namespace SSStmtOptionNames { const char DATE_AS_STRING[] = "ReturnDatesAsStrings"; const char FORMAT_DECIMALS[] = "FormatDecimals"; const char DECIMAL_PLACES[] = "DecimalPlaces"; + const char DATA_CLASSIFICATION[] = "DataClassification"; } namespace SSConnOptionNames { @@ -312,6 +313,12 @@ const stmt_option SS_STMT_OPTS[] = { SQLSRV_STMT_OPTION_DECIMAL_PLACES, std::unique_ptr( new stmt_option_decimal_places ) }, + { + SSStmtOptionNames::DATA_CLASSIFICATION, + sizeof( SSStmtOptionNames::DATA_CLASSIFICATION ), + SQLSRV_STMT_OPTION_DATA_CLASSIFICATION, + std::unique_ptr( new stmt_option_data_classification ) + }, { NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr{} }, }; diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 9d16d24e2..b6f56b6e3 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -481,6 +481,10 @@ PHP_FUNCTION( sqlsrv_field_metadata ) // get the number of fields in the resultset and its metadata if not exists SQLSMALLINT num_cols = get_resultset_meta_data(stmt); + if (stmt->data_classification) { + core_sqlsrv_sensitivity_metadata(stmt); + } + zval result_meta_data; ZVAL_UNDEF( &result_meta_data ); core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); @@ -533,6 +537,10 @@ PHP_FUNCTION( sqlsrv_field_metadata ) core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable TSRMLS_CC ); + if (stmt->data_classification) { + data_classification::fill_column_sensitivity_array(stmt, f, &field_array); + } + // add this field's meta data to the result set meta data core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC ); } diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index e4ea34728..5f69c5a32 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -440,6 +440,18 @@ ss_error SS_ERRORS[] = { SQLSRV_ERROR_AAD_MSI_UID_PWD_NOT_NULL, { IMSSP, (SQLCHAR*) "When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted.", -118, false} }, + { + SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION, + { IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -119, false} + }, + { + SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE, + { IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata. If the driver and the server both support the Data Classification feature, check whether the query returns columns with classification information.", -120, false} + }, + { + SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, + { IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -121, true} + }, // terminate the list of errors/warnings { UINT_MAX, {} } diff --git a/test/functional/pdo_sqlsrv/pdo_data_classification.phpt b/test/functional/pdo_sqlsrv/pdo_data_classification.phpt new file mode 100644 index 000000000..1b29c6049 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_data_classification.phpt @@ -0,0 +1,270 @@ +--TEST-- +Test data classification feature - retrieving sensitivity metadata if supported +--DESCRIPTION-- +If both ODBC and server support this feature, this test verifies that sensitivity metadata can be added and correctly retrieved. If not, it will at least test the new statement attribute and some error cases. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + PDO::ERRMODE_EXCEPTION, PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => true); + $conn = new PDO($dsn, $uid, $pwd, $attr); + } catch (PDOException $e) { + if (!fnmatch($stmtErr, $e->getMessage())) { + echo "Connection attribute test (1) unexpected\n"; + var_dump($e->getMessage()); + } + } + + try { + $dsn = getDSN($server, $databaseName, $driver); + $attr = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + $conn = new PDO($dsn, $uid, $pwd, $attr); + $conn->setAttribute(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION, true); + } catch (PDOException $e) { + if (!fnmatch($stmtErr, $e->getMessage())) { + echo "Connection attribute test (2) unexpected\n"; + var_dump($e->getMessage()); + } + } + + try { + $dsn = getDSN($server, $databaseName, $driver); + $attr = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + $conn = new PDO($dsn, $uid, $pwd, $attr); + $conn->getAttribute(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION); + } catch (PDOException $e) { + if (!fnmatch($noSupportErr, $e->getMessage())) { + echo "Connection attribute test (3) unexpected\n"; + var_dump($e->getMessage()); + } + } +} + +function testNotAvailable($conn, $tableName, $isSupported) +{ + // If supported, the query should return a column with no classification + $options = array(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => true); + $tsql = ($isSupported)? "SELECT PatientId FROM $tableName" : "SELECT * FROM $tableName"; + $stmt = $conn->prepare($tsql, $options); + $stmt->execute(); + + $notAvailableErr = '*Failed to retrieve Data Classification Sensitivity Metadata. If the driver and the server both support the Data Classification feature, check whether the query returns columns with classification information.'; + try { + $metadata = $stmt->getColumnMeta(0); + echo "testNotAvailable: expected getColumnMeta to fail\n"; + } catch (PDOException $e) { + if (!fnmatch($notAvailableErr, $e->getMessage())) { + echo "testNotAvailable: exception unexpected\n"; + var_dump($e->getMessage()); + } + } +} + +function isDataClassSupported($conn) +{ + // Check both SQL Server version and ODBC driver version + $msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; + $version = explode(".", $msodbcsqlVer); + + // ODBC Driver must be 17.2 or above + if ($version[0] < 17 || $version[1] < 2) { + return false; + } + + // SQL Server must be SQL Server 2019 or above + $serverVer = $conn->getAttribute(PDO::ATTR_SERVER_VERSION); + if (explode('.', $serverVer)[0] < 15) + return false; + + return true; +} + +function getRegularMetadata($conn, $tsql) +{ + // Run the query without data classification metadata + $stmt1 = $conn->query($tsql); + + // Run the query with the attribute set to false + $options = array(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => false); + $stmt2 = $conn->prepare($tsql, $options); + $stmt2->execute(); + + // The metadata for each column should be identical + $numCol = $stmt1->columnCount(); + for ($i = 0; $i < $numCol; $i++) { + $metadata1 = $stmt1->getColumnMeta($i); + $metadata2 = $stmt2->getColumnMeta($i); + + $diff = array_diff($metadata1, $metadata2); + if (!empty($diff)) { + print_r($diff); + } + } + + return $stmt1; +} + +function verifyClassInfo($input, $actual) +{ + // For simplicity of this test, only one set of sensitivity data (Label, Information Type) + if (count($actual) != 1) { + echo "Expected an array with only one element\n"; + return false; + } + + if (count($actual[0]) != 2) { + echo "Expected a Label pair and Information Type pair\n"; + return false; + } + + // Label should be name and id pair (id should be empty) + if (count($actual[0]['Label']) != 2) { + echo "Expected only two elements for the label\n"; + return false; + } + $label = $input[0]; + if ($actual[0]['Label']['name'] !== $label || !empty($actual[0]['Label']['id'])){ + return false; + } + + // Like Label, Information Type should also be name and id pair (id should be empty) + if (count($actual[0]['Information Type']) != 2) { + echo "Expected only two elements for the information type\n"; + return false; + } + $info = $input[1]; + if ($actual[0]['Information Type']['name'] !== $info || !empty($actual[0]['Information Type']['id'])){ + return false; + } + + return true; +} + +function compareDataClassification($stmt1, $stmt2, $classData) +{ + $numCol = $stmt1->columnCount(); + $noClassInfo = array('Data Classification' => array()); + + for ($i = 0; $i < $numCol; $i++) { + $metadata1 = $stmt1->getColumnMeta($i); + $metadata2 = $stmt2->getColumnMeta($i); + + // If classification sensitivity data exists, only the + // 'flags' field should be different + foreach ($metadata2 as $key => $value) { + if ($key == 'flags') { + // Is classification input data empty? + if (empty($classData[$i])) { + // Then it should be equivalent to $noClassInfo + if ($value !== $noClassInfo) { + var_dump($value); + } + } else { + // Verify the classification metadata + if (!verifyClassInfo($classData[$i], $value['Data Classification'])) { + var_dump($value); + } + } + } else { + // The other fields should be identical + if ($metadata1[$key] !== $value) { + var_dump($value); + } + } + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////// +try { + testConnAttrCases(); + + $conn = connect(); + $isSupported = isDataClassSupported($conn); + + // Create a test table + $tableName = 'pdoPatients'; + $colMeta = array(new ColumnMeta('INT', 'PatientId', 'IDENTITY NOT NULL'), + new ColumnMeta('CHAR(11)', 'SSN'), + new ColumnMeta('NVARCHAR(50)', 'FirstName'), + new ColumnMeta('NVARCHAR(50)', 'LastName'), + new ColumnMeta('DATE', 'BirthDate')); + createTable($conn, $tableName, $colMeta); + + // If data classification is supported, then add sensitivity classification metadata + // to columns SSN and Birthdate + $classData = [ + array(), + array('Highly Confidential - GDPR', 'Credentials'), + array(), + array(), + array('Confidential Personal Data', 'Birthdays') + ]; + + if ($isSupported) { + // column SSN + $label = $classData[1][0]; + $infoType = $classData[1][1]; + $sql = "ADD SENSITIVITY CLASSIFICATION TO [$tableName].SSN WITH (LABEL = '$label', INFORMATION_TYPE = '$infoType')"; + $conn->query($sql); + + // column BirthDate + $label = $classData[4][0]; + $infoType = $classData[4][1]; + $sql = "ADD SENSITIVITY CLASSIFICATION TO [$tableName].BirthDate WITH (LABEL = '$label', INFORMATION_TYPE = '$infoType')"; + $conn->query($sql); + } + + // Test another error condition + testNotAvailable($conn, $tableName, $isSupported); + + // Run the query without data classification metadata + $tsql = "SELECT * FROM $tableName"; + $stmt = getRegularMetadata($conn, $tsql); + + // Proceeed to retrieve sensitivity metadata, if supported + if ($isSupported) { + $options = array(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => true); + $stmt1 = $conn->prepare($tsql, $options); + $stmt1->execute(); + + compareDataClassification($stmt, $stmt1, $classData); + + // $stmt2 should produce the same result as the previous $stmt1 + $stmt2 = $conn->prepare($tsql); + $stmt2->execute(); + $stmt2->setAttribute(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION, true); + + compareDataClassification($stmt, $stmt2, $classData); + + unset($stmt1); + unset($stmt2); + } + + dropTable($conn, $tableName); + + unset($stmt); + unset($conn); + + echo "Done\n"; +} catch (PDOException $e) { + var_dump($e->getMessage()); +} +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_data_classification.phpt b/test/functional/sqlsrv/sqlsrv_data_classification.phpt new file mode 100644 index 000000000..4ea83cbb3 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_data_classification.phpt @@ -0,0 +1,261 @@ +--TEST-- +Test data classification feature - retrieving sensitivity metadata if supported +--DESCRIPTION-- +If both ODBC and server support this feature, this test verifies that sensitivity metadata can be added and correctly retrieved. If not, it will at least test the new statement attribute and some error cases. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + true); + $tsql = ($isSupported)? "SELECT PatientId FROM $tableName" : "SELECT * FROM $tableName"; + $stmt = sqlsrv_query($conn, $tsql, array(), $options); + if (!$stmt) { + fatalError("testErrorCases (1): failed with sqlsrv_query '$tsql'.\n"); + } + + $notAvailableErr = '*Failed to retrieve Data Classification Sensitivity Metadata. If the driver and the server both support the Data Classification feature, check whether the query returns columns with classification information.'; + + $metadata = sqlsrv_field_metadata($stmt); + if ($metadata) { + echo "testErrorCases (1): expected sqlsrv_field_metadata to fail\n"; + } + + if (!fnmatch($notAvailableErr, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + + // (2) call sqlsrv_prepare() with DataClassification but do not execute the stmt + $stmt = sqlsrv_prepare($conn, $tsql, array(), $options); + if (!$stmt) { + fatalError("testErrorCases (2): failed with sqlsrv_prepare '$tsql'.\n"); + } + + $executeFirstErr = '*The statement must be executed to retrieve Data Classification Sensitivity Metadata.'; + $metadata = sqlsrv_field_metadata($stmt); + if ($metadata) { + echo "testErrorCases (2): expected sqlsrv_field_metadata to fail\n"; + } + + if (!fnmatch($executeFirstErr, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } +} + +function isDataClassSupported($conn) +{ + // Check both SQL Server version and ODBC driver version + $msodbcsqlVer = sqlsrv_client_info($conn)['DriverVer']; + $version = explode(".", $msodbcsqlVer); + + // ODBC Driver must be 17.2 or above + if ($version[0] < 17 || $version[1] < 2) { + return false; + } + + // SQL Server must be SQL Server 2019 or above + $serverVer = sqlsrv_server_info($conn)['SQLServerVersion']; + if (explode('.', $serverVer)[0] < 15) { + return false; + } + + return true; +} + +function getRegularMetadata($conn, $tsql) +{ + // Run the query without data classification metadata + $stmt1 = sqlsrv_query($conn, $tsql); + if (!$stmt1) { + fatalError("getRegularMetadata (1): failed in sqlsrv_query.\n"); + } + + // Run the query with the attribute set to false + $options = array('DataClassification' => false); + $stmt2 = sqlsrv_query($conn, $tsql, array(), $options); + if (!$stmt2) { + fatalError("getRegularMetadata (2): failed in sqlsrv_query.\n"); + } + + // The metadata for each statement, column by column, should be identical + $numCol = sqlsrv_num_fields($stmt1); + $metadata1 = sqlsrv_field_metadata($stmt1); + $metadata2 = sqlsrv_field_metadata($stmt2); + + for ($i = 0; $i < $numCol; $i++) { + $diff = array_diff($metadata1[$i], $metadata2[$i]); + if (!empty($diff)) { + print_r($diff); + } + } + + return $stmt1; +} + +function verifyClassInfo($input, $actual) +{ + // For simplicity of this test, only one set of sensitivity data. Namely, + // an array with one set of Label (name, id) and Information Type (name, id) + if (count($actual) != 1) { + echo "Expected an array with only one element\n"; + return false; + } + + if (count($actual[0]) != 2) { + echo "Expected a Label pair and Information Type pair\n"; + return false; + } + + // Label should be name and id pair (id should be empty) + if (count($actual[0]['Label']) != 2) { + echo "Expected only two elements for the label\n"; + return false; + } + $label = $input[0]; + if ($actual[0]['Label']['name'] !== $label || !empty($actual[0]['Label']['id'])){ + return false; + } + + // Like Label, Information Type should also be name and id pair (id should be empty) + if (count($actual[0]['Information Type']) != 2) { + echo "Expected only two elements for the information type\n"; + return false; + } + $info = $input[1]; + if ($actual[0]['Information Type']['name'] !== $info || !empty($actual[0]['Information Type']['id'])){ + return false; + } + + return true; +} + +function compareDataClassification($stmt1, $stmt2, $classData) +{ + $numCol = sqlsrv_num_fields($stmt1); + + $metadata1 = sqlsrv_field_metadata($stmt1); + $metadata2 = sqlsrv_field_metadata($stmt2); + + // The built-in array_diff_assoc() function compares the keys and values + // of two (or more) arrays, and returns an array that contains the entries + // from array1 that are not present in array2 or array3, etc. + // + // For this test, $metadata2 should have one extra key 'Data Classification', + // which should not be present in $metadata1 + // + // If the column does not have sensitivity metadata, the value should be an + // empty array. Otherwise, it should contain an array with one set of + // Label (name, id) and Information Type (name, id) + + $noClassInfo = array('Data Classification' => array()); + for ($i = 0; $i < $numCol; $i++) { + $diff = array_diff_assoc($metadata2[$i], $metadata1[$i]); + + // Is classification input data empty? + if (empty($classData[$i])) { + // Then it should be equivalent to $noClassInfo + if ($diff !== $noClassInfo) { + var_dump($diff); + } + } else { + // Verify the classification metadata + if (!verifyClassInfo($classData[$i], $diff['Data Classification'])) { + var_dump($diff); + } + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////// +require_once('MsCommon.inc'); + +$conn = AE\connect(); +if (!$conn) { + fatalError("Failed to connect.\n"); +} + +$isSupported = isDataClassSupported($conn); + +// Create a test table +$tableName = 'srvPatients'; +$colMeta = array(new AE\ColumnMeta('INT', 'PatientId', 'IDENTITY NOT NULL'), + new AE\ColumnMeta('CHAR(11)', 'SSN'), + new AE\ColumnMeta('NVARCHAR(50)', 'FirstName'), + new AE\ColumnMeta('NVARCHAR(50)', 'LastName'), + new AE\ColumnMeta('DATE', 'BirthDate')); +AE\createTable($conn, $tableName, $colMeta); + +// If data classification is supported, then add sensitivity classification metadata +// to columns SSN and Birthdate +$classData = [ + array(), + array('Highly Confidential - GDPR', 'Credentials'), + array(), + array(), + array('Confidential Personal Data', 'Birthdays') + ]; + +if ($isSupported) { + // column SSN + $label = $classData[1][0]; + $infoType = $classData[1][1]; + $sql = "ADD SENSITIVITY CLASSIFICATION TO [$tableName].SSN WITH (LABEL = '$label', INFORMATION_TYPE = '$infoType')"; + $stmt = sqlsrv_query($conn, $sql); + if (!$stmt) { + fatalError("SSN: Add sensitivity $label and $infoType failed.\n"); + } + + // column BirthDate + $label = $classData[4][0]; + $infoType = $classData[4][1]; + $sql = "ADD SENSITIVITY CLASSIFICATION TO [$tableName].BirthDate WITH (LABEL = '$label', INFORMATION_TYPE = '$infoType')"; + $stmt = sqlsrv_query($conn, $sql); + if (!$stmt) { + fatalError("BirthDate: Add sensitivity $label and $infoType failed.\n"); + } +} + +testErrorCases($conn, $tableName, $isSupported); + +// Run the query without data classification metadata +$tsql = "SELECT * FROM $tableName"; +$stmt = getRegularMetadata($conn, $tsql); + +// Proceeed to retrieve sensitivity metadata, if supported +if ($isSupported) { + $options = array('DataClassification' => true); + $stmt1 = sqlsrv_prepare($conn, $tsql, array(), $options); + if (!$stmt1) { + fatalError("Error when calling sqlsrv_prepare '$tsql'.\n"); + } + if (!sqlsrv_execute($stmt1)) { + fatalError("Error in executing statement.\n"); + } + + compareDataClassification($stmt, $stmt1, $classData); + sqlsrv_free_stmt($stmt1); + + // $stmt2 should produce the same result as the previous $stmt1 + $stmt2 = sqlsrv_query($conn, $tsql, array(), $options); + if (!$stmt2) { + fatalError("Error when calling sqlsrv_query '$tsql'.\n"); + } + + compareDataClassification($stmt, $stmt2, $classData); + sqlsrv_free_stmt($stmt2); +} + +sqlsrv_free_stmt($stmt); + +dropTable($conn, $tableName); +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done From e02db623df77ef028ce8b133713efa0022265e71 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 1 May 2019 13:09:15 -0700 Subject: [PATCH 136/249] Added more pdo tests to verify different error conditions (#984) --- ...y_encoding_error_bound_by_name_errors.phpt | 158 ++++++++++++++++++ .../pdo_sqlsrv/pdo_fetch_column_twice.phpt | 151 +++++++++++++++++ .../pdo_sqlsrv/pdo_output_decimal.phpt | 4 +- .../pdo_sqlsrv/pdo_output_decimal_errors.phpt | 128 ++++++++++++++ .../pdo_sqlsrv/pdostatement_get_set_attr.phpt | 108 ++++++------ 5 files changed, 499 insertions(+), 50 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_035_binary_encoding_error_bound_by_name_errors.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_column_twice.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_output_decimal_errors.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_035_binary_encoding_error_bound_by_name_errors.phpt b/test/functional/pdo_sqlsrv/pdo_035_binary_encoding_error_bound_by_name_errors.phpt new file mode 100644 index 000000000..14ccac361 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_035_binary_encoding_error_bound_by_name_errors.phpt @@ -0,0 +1,158 @@ +--TEST-- +GitHub Issue #35 binary encoding error when binding by name +--DESCRIPTION-- +Based on pdo_035_binary_encoding_error_bound_by_name.phpt but this includes error checking for various encoding errors +--SKIPIF-- + +--FILE-- +prepare($sql); + $stmt->bindParam(1, $value, PDO::PARAM_INT, 0, PDO::SQLSRV_ENCODING_DEFAULT); + $stmt->setAttribute(constant('PDO::SQLSRV_ATTR_ENCODING'), PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(2, $input, PDO::PARAM_LOB); + $stmt->execute(); + } catch (PDOException $e) { + $error = '*An encoding was specified for parameter 1. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an encoding option.'; + if (!fnmatch($error, $e->getMessage())) { + echo "Error message unexpected in bindTypeNoEncoding\n"; + var_dump($e->getMessage()); + } + } +} + +function bindDefaultEncoding($conn, $sql, $input) +{ + try { + $value = 1; + + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $value, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_DEFAULT); + $stmt->setAttribute(constant('PDO::SQLSRV_ATTR_ENCODING'), PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(2, $input, PDO::PARAM_LOB); + $stmt->execute(); + } catch (PDOException $e) { + $error = '*Invalid encoding specified for parameter 1.'; + if (!fnmatch($error, $e->getMessage())) { + echo "Error message unexpected in bindDefaultEncoding\n"; + var_dump($e->getMessage()); + } + } +} + +function insertData($conn, $sql, $input) +{ + try { + $value = 1; + + $stmt = $conn->prepare($sql); + $stmt->bindParam(1, $value); + $stmt->setAttribute(constant('PDO::SQLSRV_ATTR_ENCODING'), PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(2, $input, PDO::PARAM_LOB); + $stmt->execute(); + } catch (PDOException $e) { + echo "Error unexpected in insertData\n"; + var_dump($e->getMessage()); + } +} + +function invalidEncoding1($conn, $sql) +{ + try { + $stmt = $conn->prepare($sql); + $stmt->bindColumn(1, $id, PDO::PARAM_INT, 0, PDO::SQLSRV_ENCODING_UTF8); + $stmt->execute(); + $stmt->fetch(PDO::FETCH_BOUND); + } catch (PDOException $e) { + $error = '*An encoding was specified for column 1. Only PDO::PARAM_LOB and PDO::PARAM_STR column types can take an encoding option.'; + if (!fnmatch($error, $e->getMessage())) { + echo "Error message unexpected in invalidEncoding1\n"; + var_dump($e->getMessage()); + } + } +} + +function invalidEncoding2($conn, $sql) +{ + try { + $stmt = $conn->prepare($sql); + $stmt->bindColumn('Value', $val1, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_DEFAULT); + $stmt->execute(); + $stmt->fetch(PDO::FETCH_BOUND); + } catch (PDOException $e) { + $error = '*Invalid encoding specified for column 1.'; + if (!fnmatch($error, $e->getMessage())) { + echo "Error message unexpected in invalidEncoding2\n"; + var_dump($e->getMessage()); + } + } +} + +function invalidEncoding3($conn, $sql) +{ + try { + $stmt = $conn->prepare($sql); + $stmt->bindColumn(1, $id, PDO::PARAM_STR, 0, "dummy"); + $stmt->execute(); + $stmt->fetch(PDO::FETCH_BOUND); + } catch (PDOException $e) { + $error = '*An invalid type or value was given as bound column driver data for column 1. Only encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.'; + if (!fnmatch($error, $e->getMessage())) { + echo "Error message unexpected in invalidEncoding3\n"; + var_dump($e->getMessage()); + } + } +} + +try { + require_once( "MsCommon_mid-refactor.inc" ); + + // Connect + $conn = connect(); + + // Create a table + $tableName = "testTableIssue35"; + createTable($conn, $tableName, array("ID" => "int", "Value" => "varbinary(max)")); + + // Insert data using bind parameters + $sql = "INSERT INTO $tableName VALUES (?, ?)"; + $message = "This is to test github issue 35."; + $value = base64_encode($message); + + // Errors expected + bindTypeNoEncoding($conn, $sql, $value); + bindDefaultEncoding($conn, $sql, $value); + + // No error expected + insertData($conn, $sql, $value); + + // Fetch data, but test several invalid encoding issues (errors expected) + $sql = "SELECT * FROM $tableName"; + invalidEncoding1($conn, $sql); + invalidEncoding2($conn, $sql); + invalidEncoding3($conn, $sql); + + // Now fetch it back + $stmt = $conn->prepare("SELECT Value FROM $tableName"); + $stmt->bindColumn('Value', $val1, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->execute(); + $stmt->fetch(PDO::FETCH_BOUND); + var_dump($val1 === $value); + + // Close connection + dropTable($conn, $tableName); + unset($stmt); + unset($conn); + print "Done\n"; +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +bool(true) +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_column_twice.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_column_twice.phpt new file mode 100644 index 000000000..7e2ec1946 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_column_twice.phpt @@ -0,0 +1,151 @@ +--TEST-- +Test fetchColumn twice in a row. Intentionally trigger various error messages. +--DESCRIPTION-- +This is similar to sqlsrv_fetch_field_twice_data_types.phpt. +--SKIPIF-- + +--FILE-- +prepare($tsql); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row !== false) { + echo "fetchBeforeExecute: fetch should have failed before execute!\n"; + } + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + + for ($i = 0; $i < count($inputs); $i++) { + if ($row[$i] !== $inputs[$i]) { + echo "fetchBeforeExecute: expected $inputs[$i] but got $row[$i]\n"; + } + } + + unset($stmt); + } catch (PDOException $e) { + var_dump($e->getMessage()); + } +} + +function fetchColumnTwice($conn, $tableName, $col, $input) +{ + try { + $tsql = "SELECT * FROM $tableName"; + $stmt = $conn->query($tsql); + $result = $stmt->fetchColumn($col); + if ($result !== $input) { + echo "fetchColumnTwice (1): expected $input but got $result\n"; + } + $result = $stmt->fetchColumn($col); + if ($result !== false) { + echo "fetchColumnTwice (2): expected the second fetchColumn to fail\n"; + } + + // Re-run the query with fetch style + $stmt = $conn->query($tsql, PDO::FETCH_COLUMN, $col); + $result = $stmt->fetch(); + if ($result !== $input) { + echo "fetchColumnTwice (3): expected $input but got $result\n"; + } + $result = $stmt->fetch(); + if ($result !== false) { + echo "fetchColumnTwice (4): expected the second fetch to fail\n"; + } + $result = $stmt->fetchColumn($col); + echo "fetchColumnTwice (5): expected fetchColumn to throw an exception\n"; + unset($stmt); + } catch (PDOException $e) { + $error = '*There are no more rows in the active result set. Since this result set is not scrollable, no more data may be retrieved.'; + + if (!fnmatch($error, $e->getMessage())) { + echo "Error message unexpected in fetchColumnTwice\n"; + var_dump($e->getMessage()); + } + } +} + +function fetchColumnOutOfBound1($conn, $tableName, $col) +{ + try { + $tsql = "SELECT * FROM $tableName"; + $stmt = $conn->query($tsql); + $result = $stmt->fetchColumn($col); + echo "fetchColumnOutOfBound1: expected fetchColumn to throw an exception\n"; + unset($stmt); + } catch (PDOException $e) { + $error1 = '*General error: Invalid column index'; + $error2 = '*An invalid column number was specified.'; + + // Different errors may be returned depending on running with run-tests.php or not + if (fnmatch($error1, $e->getMessage()) || fnmatch($error2, $e->getMessage())) { + ; + } else { + echo "Error message unexpected in fetchColumnOutOfBound1\n"; + var_dump($e->getMessage()); + } + } +} + +function fetchColumnOutOfBound2($conn, $tableName, $col) +{ + try { + $tsql = "SELECT * FROM $tableName"; + $stmt = $conn->query($tsql, PDO::FETCH_COLUMN, $col); + $result = $stmt->fetch(); + unset($stmt); + } catch (PDOException $e) { + var_dump($e->getMessage()); + } +} + +try { + $conn = connect(); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $tableName = 'pdoFetchColumnTwice'; + $colMeta = array(new ColumnMeta('int', 'c1_int'), + new ColumnMeta('varchar(20)', 'c2_varchar'), + new ColumnMeta('decimal(5, 3)', 'c3_decimal'), + new ColumnMeta('datetime', 'c4_datetime')); + createTable($conn, $tableName, $colMeta); + + $inputs = array('968580013', 'dummy value', '3.438', ('1756-04-16 23:27:09.130')); + $numCols = count($inputs); + + $tsql = "INSERT INTO $tableName(c1_int, c2_varchar, c3_decimal, c4_datetime) VALUES (?,?,?,?)"; + $stmt = $conn->prepare($tsql); + + for ($i = 0; $i < $numCols; $i++) { + $stmt->bindParam($i + 1, $inputs[$i]); + } + $stmt->execute(); + unset($stmt); + + fetchBeforeExecute($conn, $tableName, $inputs); + for ($i = 0; $i < $numCols; $i++) { + fetchColumnTwice($conn, $tableName, $i, $inputs[$i]); + } + + fetchColumnOutOfBound1($conn, $tableName, -1); + + // Change to warning mode + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + fetchColumnOutOfBound2($conn, $tableName, $numCols + 1); + + dropTable($conn, $tableName); + unset($conn); + echo "Done\n"; +} catch (PDOException $e) { + var_dump($e); +} +?> +--EXPECTREGEX-- +Warning: PDOStatement::fetch\(\): SQLSTATE\[HY000\]: General error: Invalid column index in .+(\/|\\)pdo_fetch_column_twice.php on line [0-9]+ + +Warning: PDOStatement::fetch\(\): SQLSTATE\[HY000\]: General error in .+(\/|\\)pdo_fetch_column_twice.php on line [0-9]+ +Done diff --git a/test/functional/pdo_sqlsrv/pdo_output_decimal.phpt b/test/functional/pdo_sqlsrv/pdo_output_decimal.phpt index b4512c31f..3cdee20c9 100644 --- a/test/functional/pdo_sqlsrv/pdo_output_decimal.phpt +++ b/test/functional/pdo_sqlsrv/pdo_output_decimal.phpt @@ -42,7 +42,7 @@ try { $expected1 = "7.4"; $expected2 = "7"; if ($outValue1 == $expected1 && $outValue2 == $expected2) { - echo "Test Successfully\n"; + echo "Test Successful\n"; } dropProc($conn, $proc_scale); @@ -56,4 +56,4 @@ try { ?> --EXPECT-- -Test Successfully +Test Successful diff --git a/test/functional/pdo_sqlsrv/pdo_output_decimal_errors.phpt b/test/functional/pdo_sqlsrv/pdo_output_decimal_errors.phpt new file mode 100644 index 000000000..5ec6c6fb8 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_output_decimal_errors.phpt @@ -0,0 +1,128 @@ +--TEST-- +Call stored procedures with inputs of different datatypes to get outputs of various types +--DESCRIPTION-- +Similar to pdo_output_decimal.phpt but this time intentionally test some error cases +--SKIPIF-- + +--FILE-- +prepare("{CALL $proc (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindValue(2, $inValue2); + $stmt->bindParam(3, $outValue1, PDO::PARAM_STR, -1); + $stmt->execute(); + } catch (PDOException $e) { + $error = '*Invalid size for output string parameter 3. Input/output string parameters must have an explicit length.'; + + if (!fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } +} + +function testInvalidDirection($conn, $proc) +{ + global $inValue1, $inValue2, $outValue1; + + // Request input output parameter but do not provide a size + try { + $stmt = $conn->prepare("{CALL $proc (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindValue(2, $inValue2); + $stmt->bindParam(3, $outValue1, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT); + $stmt->execute(); + } catch (PDOException $e) { + $error = '*Invalid direction specified for parameter 3. Input/output parameters must have a length.'; + + if (!fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } +} + +function testInvalidType($conn, $proc) +{ + global $inValue1, $inValue2; + + $outValue = 0.3; + + // Pass an invalid type that is incompatible for the output parameter + try { + $stmt = $conn->prepare("{CALL $proc (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindValue(2, $inValue2); + $stmt->bindParam(3, $outValue, PDO::PARAM_BOOL | PDO::PARAM_INPUT_OUTPUT, 1024); + $stmt->execute(); + } catch (PDOException $e) { + $error = '*Types for parameter value and PDO::PARAM_* constant must be compatible for input/output parameter 3.'; + + if (!fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } +} + +try { + $conn = connect(); + + $proc_scale = getProcName('scale_proc'); + $proc_no_scale = getProcName('noScale_proc'); + + $stmt = $conn->exec("CREATE PROC $proc_scale (@p1 DECIMAL(18, 1), @p2 DECIMAL(18, 1), @p3 CHAR(128) OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(CHAR(128), @p1 + @p2) END"); + + $inValue1 = '2.1'; + $inValue2 = '5.3'; + $outValue1 = '0'; + $outValue2 = '0'; + + // First error case: pass an invalid size for the output parameter + testInvalidSize($conn, $proc_scale); + testInvalidDirection($conn, $proc_scale); + testInvalidType($conn, $proc_scale); + + $stmt = $conn->prepare("{CALL $proc_scale (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindValue(2, $inValue2); + $stmt->bindParam(3, $outValue1, PDO::PARAM_STR, 300); + $stmt->execute(); + + $outValue1 = trim($outValue1); + + $stmt = $conn->exec("CREATE PROC $proc_no_scale (@p1 DECIMAL, @p2 DECIMAL, @p3 CHAR(128) OUTPUT) + AS BEGIN SELECT @p3 = CONVERT(CHAR(128), @p1 + @p2) END"); + + $stmt = $conn->prepare("{CALL $proc_no_scale (?, ?, ?)}"); + $stmt->bindValue(1, $inValue1); + $stmt->bindValue(2, $inValue2); + $stmt->bindParam(3, $outValue2, PDO::PARAM_STR, 300); + $stmt->execute(); + + $outValue2 = trim($outValue2); + + $expected1 = "7.4"; + $expected2 = "7"; + if ($outValue1 == $expected1 && $outValue2 == $expected2) { + echo "Test Successfully done\n"; + } + + dropProc($conn, $proc_scale); + dropProc($conn, $proc_no_scale); + + unset($stmt); + unset($conn); +} catch (Exception $e) { + echo $e->getMessage(); +} +?> + +--EXPECT-- +Test Successfully done diff --git a/test/functional/pdo_sqlsrv/pdostatement_get_set_attr.phpt b/test/functional/pdo_sqlsrv/pdostatement_get_set_attr.phpt index af341baa8..087ce2967 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_get_set_attr.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_get_set_attr.phpt @@ -1,5 +1,5 @@ --TEST-- -Test setting and getting various statement attributes. +Test setting and getting various statement attributes with error verifications. --SKIPIF-- --FILE-- @@ -8,40 +8,30 @@ Test setting and getting various statement attributes. function set_stmt_option($conn, $arr) { try { - - $stmt = $conn->prepare( "Select * from temptb", $arr ); + $stmt = $conn->prepare("Select * from temptb", $arr); return $stmt; - } - - catch( PDOException $e) - { + } catch (PDOException $e) { echo $e->getMessage() . "\n\n"; - return NULL; - } + return null; + } } function set_stmt_attr($conn, $attr, $val) { - $stmt = NULL; - try - { + $stmt = null; + try { echo "Set Attribute: " . $attr . "\n"; - $stmt = $conn->prepare( "Select * from temptb"); - } - catch( PDOException $e) - { + $stmt = $conn->prepare("Select * from temptb"); + } catch (PDOException $e) { echo $e->getMessage() . "\n\n"; - return NULL; + return null; } try { $res = $stmt->setAttribute(constant($attr), $val); var_dump($res); echo "\n\n"; - } - - catch( PDOException $e) - { + } catch (PDOException $e) { echo $e->getMessage() . "\n\n"; } return $stmt; @@ -49,27 +39,23 @@ function set_stmt_attr($conn, $attr, $val) function get_stmt_attr($stmt, $attr) { - try - { + try { echo "Get Attribute: " . $attr. "\n"; $res = $stmt->getAttribute(constant($attr)); var_dump($res); echo "\n"; - } - - catch( PDOException $e) - { + } catch (PDOException $e) { echo $e->getMessage() . "\n\n"; - } + } } // valid function Test1($conn) -{ - echo "Test1 - Set stmt option: SQLSRV_ATTR_ENCODING, ATTR_CURSOR, SQLSRV_ATTR_QUERY_TIMEOUT \n"; - set_stmt_option($conn, array(PDO::SQLSRV_ATTR_ENCODING => 3, PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY, PDO::SQLSRV_ATTR_QUERY_TIMEOUT => 44)); - echo "Test Successful\n\n"; -} + { + echo "Test1 - Set stmt option: SQLSRV_ATTR_ENCODING, ATTR_CURSOR, SQLSRV_ATTR_QUERY_TIMEOUT \n"; + set_stmt_option($conn, array(PDO::SQLSRV_ATTR_ENCODING => 3, PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY, PDO::SQLSRV_ATTR_QUERY_TIMEOUT => 44)); + echo "Test Successful\n\n"; + } // invalid function Test2($conn) @@ -84,10 +70,11 @@ function Test3($conn) echo "Test3 \n"; $attr = "PDO::ATTR_CURSOR"; $stmt = set_stmt_attr($conn, $attr, 1); - if($stmt) + if ($stmt) { get_stmt_attr($stmt, $attr); - else + } else { echo "Test3: stmt was null"; + } } // not supported attribute @@ -117,16 +104,33 @@ function Test6($conn) $attr = "PDO::SQLSRV_ATTR_QUERY_TIMEOUT"; $stmt = set_stmt_attr($conn, $attr, 45); get_stmt_attr($stmt, $attr); - } - -try -{ +// PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED or invalid option +// Expect errors +function Test7($conn) +{ + echo "Test7 - Set stmt option: SQLSRV_ATTR_CURSOR_SCROLL_TYPE \n"; + set_stmt_option($conn, array(PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + + // pass an invalid option + set_stmt_option($conn, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => 10)); +} + +// PDO::SQLSRV_ATTR_DIRECT_QUERY as statement attribute +// Expect error +function Test8($conn) +{ + echo "Test8 - Set stmt attr: SQLSRV_ATTR_DIRECT_QUERY \n"; + $attr = "PDO::SQLSRV_ATTR_DIRECT_QUERY"; + $stmt = set_stmt_attr($conn, $attr, true); +} + +try { include("MsSetup.inc"); - $conn = new PDO( "sqlsrv:Server=$server; Database = $databaseName ", $uid, $pwd); - $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + $conn = new PDO("sqlsrv:Server=$server; Database = $databaseName ", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $conn->exec("IF OBJECT_ID('temptb', 'U') IS NOT NULL DROP TABLE temptb"); $conn->exec("CREATE TABLE temptb(id INT NOT NULL PRIMARY KEY, val VARCHAR(10)) "); @@ -136,12 +140,12 @@ try test4($conn); test5($conn); test6($conn); - -} - -catch( PDOException $e ) { - - var_dump( $e ); + test7($conn); + test8($conn); + + $conn->exec("DROP TABLE temptb"); +} catch (PDOException $e) { + var_dump($e); exit; } @@ -181,4 +185,12 @@ bool\(true\) Get Attribute: PDO::SQLSRV_ATTR_QUERY_TIMEOUT -int\(45\) \ No newline at end of file +int\(45\) + +Test7 - Set stmt option: SQLSRV_ATTR_CURSOR_SCROLL_TYPE +SQLSTATE\[IMSSP\]: The PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE attribute may only be set when PDO::ATTR_CURSOR is set to PDO::CURSOR_SCROLL in the $driver_options array of PDO::prepare. + +SQLSTATE\[IMSSP\]: The value passed for the 'Scrollable' statement option is invalid. + +Test8 - Set stmt attr: SQLSRV_ATTR_DIRECT_QUERY +SQLSTATE\[IMSSP\]: The PDO::SQLSRV_ATTR_DIRECT_QUERY attribute may only be set in the $driver_options array of PDO::prepare. \ No newline at end of file From ffd98497507c739c353c10e214020b32a286599a Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 8 May 2019 15:18:15 -0700 Subject: [PATCH 137/249] Fixed memory issues with data classification (#985) --- buildscripts/buildtools.py | 2 +- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 9 ++-- source/shared/core_util.cpp | 22 +++++--- source/sqlsrv/stmt.cpp | 2 +- .../pdo_sqlsrv/pdo_data_classification.phpt | 49 +++++++++++++++++- .../sqlsrv/sqlsrv_data_classification.phpt | 50 +++++++++++++++++-- 7 files changed, 116 insertions(+), 20 deletions(-) diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index 59f8b8f55..c1f9df4cd 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -282,7 +282,7 @@ def generate_build_options(self): else: # pdo_sqlsrv cmd_line = ' --enable-pdo --with-pdo-sqlsrv=shared ' + cmd_line - cmd_line = 'cscript configure.js --disable-all --enable-cli --enable-cgi --enable-embed' + cmd_line + cmd_line = 'cscript configure.js --disable-all --enable-cli --enable-cgi --enable-json --enable-embed' + cmd_line if self.thread == 'nts': cmd_line = cmd_line + ' --disable-zts' return cmd_line diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index e573f953d..b7a8c3921 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1436,7 +1436,7 @@ namespace data_classification { struct sensitivity_metadata; void name_id_pair_free(name_id_pair * pair); - void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector>& pairs, _Inout_ unsigned char **pptr TSRMLS_CC); + void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector>* pairs, _Inout_ unsigned char **pptr TSRMLS_CC); void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr); USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *column_data TSRMLS_CC); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index f3d3e6fb5..a2fd1501c 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -262,8 +262,7 @@ void sqlsrv_stmt::clean_up_sensitivity_metadata() { if (current_sensitivity_metadata) { current_sensitivity_metadata->~sensitivity_metadata(); - sqlsrv_free(current_sensitivity_metadata); - current_sensitivity_metadata = NULL; + current_sensitivity_metadata.reset(); } } @@ -968,7 +967,7 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL } } - // Set the field name lenth + // Set the field name length meta_data->field_name_len = static_cast( field_name_len ); field_meta_data* result_field_meta_data = meta_data; @@ -1052,8 +1051,8 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) sensitivity_meta = new (sqlsrv_malloc(sizeof(sensitivity_metadata))) sensitivity_metadata(); // Parse the name id pairs for labels first then info types - parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_labels, sensitivity_meta->labels, &dcptr); - parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_infotypes, sensitivity_meta->infotypes, &dcptr); + parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_labels, &sensitivity_meta->labels, &dcptr); + parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_infotypes, &sensitivity_meta->infotypes, &dcptr); // Next parse the sensitivity properties parse_column_sensitivity_props(sensitivity_meta, &dcptr); diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 5db20c3fc..8fea03358 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -426,12 +426,18 @@ namespace data_classification { void convert_sensivity_field(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_ENCODING encoding, _In_ unsigned char *ptr, _In_ int len, _Inout_updates_bytes_(cchOutLen) char** field_name) { sqlsrv_malloc_auto_ptr temp_field_name; - int temp_field_len = len * 2; + int temp_field_len = len * sizeof(SQLWCHAR); SQLLEN field_name_len = 0; + if (len == 0) { + *field_name = reinterpret_cast(sqlsrv_malloc(1)); + *field_name[0] = '\0'; + return; + } + temp_field_name = static_cast(sqlsrv_malloc((len + 1) * sizeof(SQLWCHAR))); + memset(temp_field_name, L'\0', len + 1); memcpy_s(temp_field_name, temp_field_len, ptr, temp_field_len); - temp_field_name[temp_field_len] = '\0'; bool converted = convert_string_from_utf16(encoding, temp_field_name, len, field_name, field_name_len); @@ -450,14 +456,16 @@ namespace data_classification { } sqlsrv_free(pair); } - - void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector>& pairs, _Inout_ unsigned char **pptr) + + void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector>* pairs, _Inout_ unsigned char **pptr) { unsigned char *ptr = *pptr; unsigned short npairs; - numpairs = npairs = *(unsigned short*)ptr; + numpairs = npairs = *(reinterpret_cast(ptr)); SQLSRV_ENCODING encoding = ((stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); + pairs->reserve(numpairs); + ptr += sizeof(unsigned short); while (npairs--) { int namelen, idlen; @@ -485,7 +493,7 @@ namespace data_classification { convert_sensivity_field(stmt, encoding, idptr, idlen, (char**)&id); pair->id = id; - pairs.push_back(pair.get()); + pairs->push_back(pair.get()); pair.transferred(); } *pptr = ptr; @@ -537,7 +545,7 @@ namespace data_classification { if (meta == NULL) { return 0; } - + SQLSRV_ASSERT(colno >= 0 && colno < meta->num_columns, "fill_column_sensitivity_array: column number out of bounds"); zval data_classification; diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index b6f56b6e3..da2d3c945 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -538,7 +538,7 @@ PHP_FUNCTION( sqlsrv_field_metadata ) TSRMLS_CC ); if (stmt->data_classification) { - data_classification::fill_column_sensitivity_array(stmt, f, &field_array); + data_classification::fill_column_sensitivity_array(stmt, f, &field_array TSRMLS_CC); } // add this field's meta data to the result set meta data diff --git a/test/functional/pdo_sqlsrv/pdo_data_classification.phpt b/test/functional/pdo_sqlsrv/pdo_data_classification.phpt index 1b29c6049..fd37e07f5 100644 --- a/test/functional/pdo_sqlsrv/pdo_data_classification.phpt +++ b/test/functional/pdo_sqlsrv/pdo_data_classification.phpt @@ -11,6 +11,8 @@ PHPT_EXEC=true require_once('MsSetup.inc'); require_once('MsCommon_mid-refactor.inc'); +$dataClassKey = 'Data Classification'; + function testConnAttrCases() { // Attribute PDO::SQLSRV_ATTR_DATA_CLASSIFICATION is limited to statement level only @@ -157,8 +159,10 @@ function verifyClassInfo($input, $actual) function compareDataClassification($stmt1, $stmt2, $classData) { + global $dataClassKey; + $numCol = $stmt1->columnCount(); - $noClassInfo = array('Data Classification' => array()); + $noClassInfo = array($dataClassKey => array()); for ($i = 0; $i < $numCol; $i++) { $metadata1 = $stmt1->getColumnMeta($i); @@ -176,7 +180,7 @@ function compareDataClassification($stmt1, $stmt2, $classData) } } else { // Verify the classification metadata - if (!verifyClassInfo($classData[$i], $value['Data Classification'])) { + if (!verifyClassInfo($classData[$i], $value[$dataClassKey])) { var_dump($value); } } @@ -190,6 +194,45 @@ function compareDataClassification($stmt1, $stmt2, $classData) } } +function runBatchQuery($conn, $tableName) +{ + global $dataClassKey; + + $options = array(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => true); + $tsql = "SELECT SSN, BirthDate FROM $tableName"; + + // Run a batch query + $batchQuery = $tsql . ';' . $tsql; + $stmt = $conn->prepare($batchQuery, $options); + $stmt->execute(); + + $numCol = $stmt->columnCount(); + + // The metadata returned should be the same + $c = rand(0, $numCol - 1); + $metadata1 = $stmt->getColumnMeta($c); + $stmt->nextRowset(); + $metadata2 = $stmt->getColumnMeta($c); + + // Check the returned flags + $data1 = $metadata1['flags']; + $data2 = $metadata2['flags']; + + if (!array_key_exists($dataClassKey, $data1) || !array_key_exists($dataClassKey, $data2)) { + echo "Metadata returned with no classification data\n"; + var_dump($data1); + var_dump($data2); + } else { + $jstr1 = json_encode($data1[$dataClassKey]); + $jstr2 = json_encode($data2[$dataClassKey]); + if ($jstr1 !== $jstr2) { + echo "The JSON encoded strings should be identical\n"; + var_dump($jstr1); + var_dump($jstr2); + } + } +} + /////////////////////////////////////////////////////////////////////////////////////// try { testConnAttrCases(); @@ -254,6 +297,8 @@ try { unset($stmt1); unset($stmt2); + + runBatchQuery($conn, $tableName); } dropTable($conn, $tableName); diff --git a/test/functional/sqlsrv/sqlsrv_data_classification.phpt b/test/functional/sqlsrv/sqlsrv_data_classification.phpt index 4ea83cbb3..2d6cce420 100644 --- a/test/functional/sqlsrv/sqlsrv_data_classification.phpt +++ b/test/functional/sqlsrv/sqlsrv_data_classification.phpt @@ -7,7 +7,9 @@ PHPT_EXEC=true --SKIPIF-- --FILE-- - array()); + $noClassInfo = array($dataClassKey => array()); for ($i = 0; $i < $numCol; $i++) { $diff = array_diff_assoc($metadata2[$i], $metadata1[$i]); @@ -164,13 +168,51 @@ function compareDataClassification($stmt1, $stmt2, $classData) } } else { // Verify the classification metadata - if (!verifyClassInfo($classData[$i], $diff['Data Classification'])) { + if (!verifyClassInfo($classData[$i], $diff[$dataClassKey])) { var_dump($diff); } } } } +function runBatchQuery($conn, $tableName) +{ + global $dataClassKey; + + $options = array('DataClassification' => true); + $tsql = "SELECT SSN, BirthDate FROM $tableName"; + $batchQuery = $tsql . ';' . $tsql; + + $stmt = sqlsrv_query($conn, $batchQuery, array(), $options); + if (!$stmt) { + fatalError("Error when calling sqlsrv_query '$tsql'.\n"); + } + + $numCol = sqlsrv_num_fields($stmt); + $c = rand(0, $numCol - 1); + + $metadata1 = sqlsrv_field_metadata($stmt); + if (!$metadata1 || !array_key_exists($dataClassKey, $metadata1[$c])) { + fatalError("runBatchQuery(1): failed to get metadata"); + } + $result = sqlsrv_next_result($stmt); + if (is_null($result) || !$result) { + fatalError("runBatchQuery: failed to get next result"); + } + $metadata2 = sqlsrv_field_metadata($stmt); + if (!$metadata2 || !array_key_exists($dataClassKey, $metadata2[$c])) { + fatalError("runBatchQuery(2): failed to get metadata"); + } + + $jstr1 = json_encode($metadata1[$c][$dataClassKey]); + $jstr2 = json_encode($metadata2[$c][$dataClassKey]); + if ($jstr1 !== $jstr2) { + echo "The JSON encoded strings should be identical\n"; + var_dump($jstr1); + var_dump($jstr2); + } +} + /////////////////////////////////////////////////////////////////////////////////////// require_once('MsCommon.inc'); @@ -248,6 +290,8 @@ if ($isSupported) { compareDataClassification($stmt, $stmt2, $classData); sqlsrv_free_stmt($stmt2); + + runBatchQuery($conn, $tableName); } sqlsrv_free_stmt($stmt); From 6ad5c1e60dfc36764e6d4eabcbcfe4d30ca9e626 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 9 May 2019 16:56:18 -0700 Subject: [PATCH 138/249] Added connection string flag --- source/shared/core_conn.cpp | 2 +- source/shared/core_sqlsrv.h | 2 +- source/shared/core_util.cpp | 20 +++++++++++++------- source/sqlsrv/php_sqlsrv_int.h | 4 ++-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 3d195fe9c..527143557 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -386,7 +386,7 @@ SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& con // We only support UTF-8 encoding for connection string. // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW - wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast( conn_str.length() ), &wconn_len ); + wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast( conn_str.length() ), &wconn_len, true ); CHECK_CUSTOM_ERROR( wconn_string == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message()) { diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index b7a8c3921..08bc290f3 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1808,7 +1808,7 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _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 ); +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, bool is_connection_string = false ); //********************************************************************************************************************************* // Error handling routines and Predefined Errors diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 8fea03358..acb53d015 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -34,7 +34,7 @@ char last_err_msg[2048] = {'\0'}; // 2k to hold the error messages unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, _In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string, - _In_ unsigned int utf16_len ); + _In_ unsigned int utf16_len, bool is_connection_string = false ); } // SQLSTATE for all internal errors @@ -172,11 +172,11 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_( // allocation of the destination string. An empty string passed in returns // failure since it's a failure case for convert_string_from_default_encoding. 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 ) + _Out_ unsigned int* utf16_len, bool is_connection_string ) { *utf16_len = (mbcs_len + 1); SQLWCHAR* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len * sizeof( SQLWCHAR ))); - *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, utf16_string, *utf16_len ); + *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, utf16_string, *utf16_len, is_connection_string ); if( *utf16_len == 0 ) { // we preserve the error and reset it because sqlsrv_free resets the last error @@ -384,7 +384,7 @@ namespace { // to convert. unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, _In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string, - _In_ unsigned int utf16_len ) + _In_ unsigned int utf16_len, bool is_connection_string ) { unsigned int win_encoding = CP_ACP; switch( php_encoding ) { @@ -399,8 +399,14 @@ unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encodin win_encoding = php_encoding; break; } -#ifndef _WIN32 - unsigned int required_len = SystemLocale::ToUtf16( win_encoding, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); +#ifndef _WIN32 + unsigned int required_len; + if (is_connection_string) { + required_len = SystemLocale::ToUtf16Strict( win_encoding, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); + } + else { + required_len = SystemLocale::ToUtf16( win_encoding, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); + } #else unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); #endif // !_Win32 @@ -610,4 +616,4 @@ namespace data_classification { columns_sensitivity.clear(); } -} // namespace data_classification \ No newline at end of file +} // namespace data_classification diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index c294f465b..a12ff674d 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -214,12 +214,12 @@ bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_err // returned in utf16_out_string. unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, _In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer(mbcs_in_string) wchar_t* utf16_out_string, - _In_ unsigned int utf16_len ); + _In_ unsigned int utf16_len, bool is_connection_string = false ); // create a wide char string from the passed in mbcs string. NULL is returned if the string // could not be created. No error is posted by this function. utf16_len is the number of // wchar_t characters, not the number of bytes. SQLWCHAR* utf16_string_from_mbcs_string( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, - _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len ); + _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len, bool is_connection_string = false ); // *** internal error macros and functions *** bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, From aeeba5ca1da6798003a97223253f4f4feaf4ded2 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 9 May 2019 16:58:35 -0700 Subject: [PATCH 139/249] Removed unix skipif --- test/functional/sqlsrv/sqlsrv_connStr.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/sqlsrv/sqlsrv_connStr.phpt b/test/functional/sqlsrv/sqlsrv_connStr.phpt index 144ceb94a..460ae391d 100644 --- a/test/functional/sqlsrv/sqlsrv_connStr.phpt +++ b/test/functional/sqlsrv/sqlsrv_connStr.phpt @@ -1,7 +1,7 @@ --TEST-- UTF-8 connection strings --SKIPIF-- - + --FILE-- Date: Thu, 9 May 2019 20:17:54 -0700 Subject: [PATCH 140/249] Fixed test output --- test/functional/sqlsrv/sqlsrv_connStr.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_connStr.phpt b/test/functional/sqlsrv/sqlsrv_connStr.phpt index 460ae391d..9d6d9b6fb 100644 --- a/test/functional/sqlsrv/sqlsrv_connStr.phpt +++ b/test/functional/sqlsrv/sqlsrv_connStr.phpt @@ -117,9 +117,9 @@ Array [SQLSTATE] => IMSSP [1] => -47 [code] => -47 - [2] => An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page. + [2] => An error occurred translating the connection string to UTF-16: %s - [message] => An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page. + [message] => An error occurred translating the connection string to UTF-16: %s ) From e031c1a3fa72f5d2749c2a6dda38d98fa507e86e Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 10 May 2019 12:56:51 -0700 Subject: [PATCH 141/249] Fixed pdo test --- test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt b/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt index a31974d94..7b0f101a6 100644 --- a/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt +++ b/test/functional/pdo_sqlsrv/pdo_construct_attr_errors.phpt @@ -38,7 +38,7 @@ function invalidServer() echo "Should have failed to connect to invalid server.\n"; } catch (PDOException $e) { $error1 = '*Login timeout expired'; - $error2 = '*An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page*'; + $error2 = '*An error occurred translating the connection string to UTF-16: *'; if (fnmatch($error1, $e->getMessage()) || fnmatch($error2, $e->getMessage())) { ; // matched at least one of the expected error messages } else { @@ -102,7 +102,7 @@ function invalidPassword() $options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); // Possible errors - $error = "*An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page.*"; + $error = "*An error occurred translating the connection string to UTF-16: *"; $error1 = "*Login failed for user \'*\'."; $error2 = "*Login timeout expired*"; From 9d9acc3627b96031c04a8afc6a8a0825d1e6e862 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 10 May 2019 13:03:22 -0700 Subject: [PATCH 142/249] Changed flag name --- source/shared/core_sqlsrv.h | 2 +- source/shared/core_util.cpp | 10 +++++----- source/sqlsrv/php_sqlsrv_int.h | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 08bc290f3..c6a0eb97a 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1808,7 +1808,7 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _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, bool is_connection_string = false ); +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, bool use_strict_conversion = false ); //********************************************************************************************************************************* // Error handling routines and Predefined Errors diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index acb53d015..e5427f97a 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -34,7 +34,7 @@ char last_err_msg[2048] = {'\0'}; // 2k to hold the error messages unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, _In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string, - _In_ unsigned int utf16_len, bool is_connection_string = false ); + _In_ unsigned int utf16_len, bool use_strict_conversion = false ); } // SQLSTATE for all internal errors @@ -172,11 +172,11 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_( // allocation of the destination string. An empty string passed in returns // failure since it's a failure case for convert_string_from_default_encoding. 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, bool is_connection_string ) + _Out_ unsigned int* utf16_len, bool use_strict_conversion ) { *utf16_len = (mbcs_len + 1); SQLWCHAR* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len * sizeof( SQLWCHAR ))); - *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, utf16_string, *utf16_len, is_connection_string ); + *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, utf16_string, *utf16_len, use_strict_conversion ); if( *utf16_len == 0 ) { // we preserve the error and reset it because sqlsrv_free resets the last error @@ -384,7 +384,7 @@ namespace { // to convert. unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, _In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string, - _In_ unsigned int utf16_len, bool is_connection_string ) + _In_ unsigned int utf16_len, bool use_strict_conversion ) { unsigned int win_encoding = CP_ACP; switch( php_encoding ) { @@ -401,7 +401,7 @@ unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encodin } #ifndef _WIN32 unsigned int required_len; - if (is_connection_string) { + if (use_strict_conversion) { required_len = SystemLocale::ToUtf16Strict( win_encoding, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); } else { diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index a12ff674d..bd555fe10 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -214,12 +214,12 @@ bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_err // returned in utf16_out_string. unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) char const* mbcs_in_string, _In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer(mbcs_in_string) wchar_t* utf16_out_string, - _In_ unsigned int utf16_len, bool is_connection_string = false ); + _In_ unsigned int utf16_len, bool use_strict_conversion = false ); // create a wide char string from the passed in mbcs string. NULL is returned if the string // could not be created. No error is posted by this function. utf16_len is the number of // wchar_t characters, not the number of bytes. SQLWCHAR* utf16_string_from_mbcs_string( _In_ unsigned int php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, - _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len, bool is_connection_string = false ); + _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len, bool use_strict_conversion = false ); // *** internal error macros and functions *** bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, From ca6d6cbb8ad4db52f1c8309831a1031ef4edfadf Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 10 May 2019 15:02:45 -0700 Subject: [PATCH 143/249] Fixed test output --- test/functional/sqlsrv/sqlsrv_connStr.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_connStr.phpt b/test/functional/sqlsrv/sqlsrv_connStr.phpt index 9d6d9b6fb..34ace5781 100644 --- a/test/functional/sqlsrv/sqlsrv_connStr.phpt +++ b/test/functional/sqlsrv/sqlsrv_connStr.phpt @@ -155,9 +155,9 @@ Array [SQLSTATE] => IMSSP [1] => -47 [code] => -47 - [2] => An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page. + [2] => An error occurred translating the connection string to UTF-16: %s - [message] => An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page. + [message] => An error occurred translating the connection string to UTF-16: %s ) From 3b6b076c1fb17e2cb6e06e736cef561bf5387ab4 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 10 May 2019 15:33:24 -0700 Subject: [PATCH 144/249] Updated links and versions (#987) (#988) --- Linux-mac-install.md | 12 ++++++------ README.md | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index 2a7357f47..e34f07b06 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -9,7 +9,7 @@ These instructions install PHP 7.3 by default. Note that some supported Linux di - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) - [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) - [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15) -- [Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-el-capitan-sierra-high-sierra-and-mojave) +- [Installing the drivers on macOS Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-sierra-high-sierra-and-mojave) ## Installing the drivers on Ubuntu 16.04, 18.04, and 18.10 @@ -38,7 +38,7 @@ exit ### Step 4. Install Apache and configure driver loading ``` sudo su -apt-get install libapache2-mod-php7.2 apache2 +apt-get install libapache2-mod-php7.3 apache2 a2dismod mpm_event a2enmod mpm_prefork a2enmod php7.3 @@ -91,8 +91,8 @@ exit An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually (similar steps for pdo_sqlsrv): ``` pecl download sqlsrv -tar xvzf sqlsrv-5.6.0.tgz -cd sqlsrv-5.6.0/ +tar xvzf sqlsrv-5.6.1.tgz +cd sqlsrv-5.6.1/ phpize ./configure --with-php-config=/usr/bin/php-config make @@ -214,7 +214,7 @@ sudo systemctl restart apache2 ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on macOS El Capitan, Sierra, High Sierra, and Mojave +## Installing the drivers on macOS Sierra, High Sierra, and Mojave If you do not already have it, install brew as follows: ``` @@ -259,7 +259,7 @@ apachectl -V | grep SERVER_CONFIG_FILE ``` and substitute the path for `httpd.conf` in the following commands: ``` -echo "LoadModule php7_module /usr/local/opt/php@7.2/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf +echo "LoadModule php7_module /usr/local/opt/php@7.3/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf (echo ""; echo "SetHandler application/x-httpd-php"; echo "";) >> /usr/local/etc/httpd/httpd.conf ``` ### Step 5. Restart Apache and test the sample script diff --git a/README.md b/README.md index 1af72d831..842a99b17 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,12 @@ Azure Pipelines | AppVeyor (Windows) | Travis CI (Linux) | Co [av-image]: https://ci.appveyor.com/api/projects/status/vo4rfei6lxlamrnc?svg=true [av-site]: https://ci.appveyor.com/project/msphpsql/msphpsql/branch/dev -[tv-image]: https://travis-ci.org/Microsoft/msphpsql.svg?branch=dev -[tv-site]: https://travis-ci.org/Microsoft/msphpsql/ +[tv-image]: https://travis-ci.org/microsoft/msphpsql.svg?branch=dev +[tv-site]: https://travis-ci.org/microsoft/msphpsql/ [az-site]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_build/latest?definitionId=6&branchName=dev [az-image]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_apis/build/status/Microsoft.msphpsql?branchName=dev -[Coverage Coveralls]: https://coveralls.io/repos/github/Microsoft/msphpsql/badge.svg?branch=dev -[coveralls-site]: https://coveralls.io/github/Microsoft/msphpsql?branch=dev +[Coverage Coveralls]: https://coveralls.io/repos/github/microsoft/msphpsql/badge.svg?branch=dev +[coveralls-site]: https://coveralls.io/github/microsoft/msphpsql?branch=dev [Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/dev/graph/badge.svg [codecov-site]: https://codecov.io/gh/microsoft/msphpsql From 209c4fdad08bd4255b8255a06ab4c8d6428c5c01 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 10 May 2019 16:05:05 -0700 Subject: [PATCH 145/249] Fixed test output (again) --- test/functional/sqlsrv/sqlsrv_connStr.phpt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_connStr.phpt b/test/functional/sqlsrv/sqlsrv_connStr.phpt index 34ace5781..9f6924ebe 100644 --- a/test/functional/sqlsrv/sqlsrv_connStr.phpt +++ b/test/functional/sqlsrv/sqlsrv_connStr.phpt @@ -118,9 +118,7 @@ Array [1] => -47 [code] => -47 [2] => An error occurred translating the connection string to UTF-16: %s - [message] => An error occurred translating the connection string to UTF-16: %s - ) ) @@ -156,9 +154,7 @@ Array [1] => -47 [code] => -47 [2] => An error occurred translating the connection string to UTF-16: %s - [message] => An error occurred translating the connection string to UTF-16: %s - ) ) From 50ba3244da237742faad6d5077e844d1ff25f8dd Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 10 May 2019 16:35:22 -0700 Subject: [PATCH 146/249] Fixed test output (again) --- test/functional/sqlsrv/sqlsrv_connStr.phpt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_connStr.phpt b/test/functional/sqlsrv/sqlsrv_connStr.phpt index 9f6924ebe..779613d16 100644 --- a/test/functional/sqlsrv/sqlsrv_connStr.phpt +++ b/test/functional/sqlsrv/sqlsrv_connStr.phpt @@ -134,16 +134,6 @@ Array [message] => %SLogin failed for user '%s'. ) - [1] => Array - ( - [0] => 28000 - [SQLSTATE] => 28000 - [1] => 18456 - [code] => 18456 - [2] => %SLogin failed for user '%s'. - [message] => %SLogin failed for user '%s'. - ) - ) Array ( From 39c57af2b663f8c1734a47aa0e97a276b1096e32 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 10 May 2019 16:56:45 -0700 Subject: [PATCH 147/249] Fixed test output (again) --- test/functional/sqlsrv/sqlsrv_connStr.phpt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/sqlsrv/sqlsrv_connStr.phpt b/test/functional/sqlsrv/sqlsrv_connStr.phpt index 779613d16..3c5d7934a 100644 --- a/test/functional/sqlsrv/sqlsrv_connStr.phpt +++ b/test/functional/sqlsrv/sqlsrv_connStr.phpt @@ -67,7 +67,8 @@ $c = connect(array( if ($c !== false) { fatalError("sqlsrv_connect(3) should have failed"); } -print_r(sqlsrv_errors()); +// On Windows, two errors are returned, with the same content. So just check the first one. +print_r(sqlsrv_errors()[0]); // invalid UTF-8 in the pwd $c = connect(array( From 69759676419b460a61d5bb9b24ec10107bb8a966 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Fri, 10 May 2019 22:16:46 -0700 Subject: [PATCH 148/249] Replaced expected test output altogether --- test/functional/sqlsrv/sqlsrv_connStr.phpt | 94 ++++++---------------- 1 file changed, 23 insertions(+), 71 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_connStr.phpt b/test/functional/sqlsrv/sqlsrv_connStr.phpt index 3c5d7934a..7629e3c7c 100644 --- a/test/functional/sqlsrv/sqlsrv_connStr.phpt +++ b/test/functional/sqlsrv/sqlsrv_connStr.phpt @@ -6,6 +6,13 @@ UTF-8 connection strings 'gibberish' )); if ($c !== false) { fatalError("Should have errored on an invalid encoding."); } -print_r(sqlsrv_errors()); +checkErrors($gibberishEncoding); $c = connect(array( 'CharacterSet' => SQLSRV_ENC_BINARY )); if ($c !== false) { fatalError("Should have errored on an invalid encoding."); } -print_r(sqlsrv_errors()); +checkErrors($binaryEncoding); $c = connect(array( 'CharacterSet' => SQLSRV_ENC_CHAR )); if ($c === false) { @@ -50,7 +68,7 @@ $c = sqlsrv_connect($server_invalid, array( 'Database' => 'test', 'CharacterSet' if ($c !== false) { fatalError("sqlsrv_connect(1) should have failed"); } -print_r(sqlsrv_errors()); +checkErrors($utf16Error); // APP has a UTF-8 name $c = connect(array( @@ -67,8 +85,7 @@ $c = connect(array( if ($c !== false) { fatalError("sqlsrv_connect(3) should have failed"); } -// On Windows, two errors are returned, with the same content. So just check the first one. -print_r(sqlsrv_errors()[0]); +checkErrors($userLoginFailed); // invalid UTF-8 in the pwd $c = connect(array( @@ -78,75 +95,10 @@ $c = connect(array( if ($c !== false) { fatalError("sqlsrv_connect(4) should have failed"); } -print_r(sqlsrv_errors()); +checkErrors($utf16Error); echo "Test succeeded.\n"; ?> --EXPECTF-- -Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -48 - [code] => -48 - [2] => The encoding 'gibberish' is not a supported encoding for the CharacterSet connection option. - [message] => The encoding 'gibberish' is not a supported encoding for the CharacterSet connection option. - ) - -) -Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -48 - [code] => -48 - [2] => The encoding 'binary' is not a supported encoding for the CharacterSet connection option. - [message] => The encoding 'binary' is not a supported encoding for the CharacterSet connection option. - ) - -) -Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -47 - [code] => -47 - [2] => An error occurred translating the connection string to UTF-16: %s - [message] => An error occurred translating the connection string to UTF-16: %s - ) - -) -Array -( - [0] => Array - ( - [0] => 28000 - [SQLSTATE] => 28000 - [1] => 18456 - [code] => 18456 - [2] => %SLogin failed for user '%s'. - [message] => %SLogin failed for user '%s'. - ) - -) -Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -47 - [code] => -47 - [2] => An error occurred translating the connection string to UTF-16: %s - [message] => An error occurred translating the connection string to UTF-16: %s - ) - -) Test succeeded. From 06ff53daa882b05d125fdd48e3136ac0c2a0edb7 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Sat, 11 May 2019 13:35:12 -0700 Subject: [PATCH 149/249] Fixed locale issue --- source/shared/localizationimpl.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index e67bbf226..ea38206a8 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -280,9 +280,25 @@ bool EncodingConverter::Initialize() using namespace std; SystemLocale::SystemLocale( const char * localeName ) - : m_pLocale( new std::locale(localeName) ) - , m_uAnsiCP(CP_UTF8) + : m_uAnsiCP(CP_UTF8) + , m_pLocale(NULL) { + const char* DEFAULT_LOCALE = "en_US.UTF-8"; + + try + { + m_pLocale = new std::locale(localeName); + } + catch(const std::exception& e) + { + localeName = DEFAULT_LOCALE; + } + + if(!m_pLocale) + m_pLocale = new std::locale(localeName); + + + // Mapping from locale charset to codepage struct LocaleCP { const char* localeName; @@ -331,8 +347,7 @@ const SystemLocale & SystemLocale::Singleton() #if !defined(__GNUC__) || defined(NO_THREADSAFE_STATICS) #error "Relying on GCC's threadsafe initialization of local statics." #endif - // get locale from environment and set as default - static const SystemLocale s_Default(setlocale(LC_ALL, NULL)); + static const SystemLocale s_Default(setlocale(LC_CTYPE, NULL)); return s_Default; } From 1aa8dea2ae185cf3a2c63963691b2715ee0adaa6 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 13 May 2019 11:14:53 -0700 Subject: [PATCH 150/249] Corrected formatting --- source/shared/localizationimpl.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index ea38206a8..8f7dd402b 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -285,18 +285,16 @@ SystemLocale::SystemLocale( const char * localeName ) { const char* DEFAULT_LOCALE = "en_US.UTF-8"; - try - { + try { m_pLocale = new std::locale(localeName); } - catch(const std::exception& e) - { + catch(const std::exception& e) { localeName = DEFAULT_LOCALE; } - if(!m_pLocale) + if(!m_pLocale) { m_pLocale = new std::locale(localeName); - + } // Mapping from locale charset to codepage struct LocaleCP From 60f20bd1ea1bb710470170a13fe536f078691edd Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 13 May 2019 11:15:49 -0700 Subject: [PATCH 151/249] Replaced EXPECTF with EXPECT --- test/functional/sqlsrv/sqlsrv_connStr.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/sqlsrv/sqlsrv_connStr.phpt b/test/functional/sqlsrv/sqlsrv_connStr.phpt index 7629e3c7c..7c92bd997 100644 --- a/test/functional/sqlsrv/sqlsrv_connStr.phpt +++ b/test/functional/sqlsrv/sqlsrv_connStr.phpt @@ -100,5 +100,5 @@ checkErrors($utf16Error); echo "Test succeeded.\n"; ?> ---EXPECTF-- +--EXPECT-- Test succeeded. From 7e0bf91eecb9a81f4dc489e07015311d9b6c8d63 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 13 May 2019 15:46:25 -0700 Subject: [PATCH 152/249] Fixed two failing tests (#991) --- ...ry_encoding_error_bound_by_name_errors.phpt | 10 ++++++++-- .../sqlsrv/srv_007_login_timeout.phpt | 18 +++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_035_binary_encoding_error_bound_by_name_errors.phpt b/test/functional/pdo_sqlsrv/pdo_035_binary_encoding_error_bound_by_name_errors.phpt index 14ccac361..39d307297 100644 --- a/test/functional/pdo_sqlsrv/pdo_035_binary_encoding_error_bound_by_name_errors.phpt +++ b/test/functional/pdo_sqlsrv/pdo_035_binary_encoding_error_bound_by_name_errors.phpt @@ -17,6 +17,7 @@ function bindTypeNoEncoding($conn, $sql, $input) $stmt->setAttribute(constant('PDO::SQLSRV_ATTR_ENCODING'), PDO::SQLSRV_ENCODING_BINARY); $stmt->bindParam(2, $input, PDO::PARAM_LOB); $stmt->execute(); + echo "bindTypeNoEncoding: expected to fail!\n"; } catch (PDOException $e) { $error = '*An encoding was specified for parameter 1. Only PDO::PARAM_LOB and PDO::PARAM_STR can take an encoding option.'; if (!fnmatch($error, $e->getMessage())) { @@ -36,6 +37,7 @@ function bindDefaultEncoding($conn, $sql, $input) $stmt->setAttribute(constant('PDO::SQLSRV_ATTR_ENCODING'), PDO::SQLSRV_ENCODING_BINARY); $stmt->bindParam(2, $input, PDO::PARAM_LOB); $stmt->execute(); + echo "bindDefaultEncoding: expected to fail!\n"; } catch (PDOException $e) { $error = '*Invalid encoding specified for parameter 1.'; if (!fnmatch($error, $e->getMessage())) { @@ -52,8 +54,9 @@ function insertData($conn, $sql, $input) $stmt = $conn->prepare($sql); $stmt->bindParam(1, $value); - $stmt->setAttribute(constant('PDO::SQLSRV_ATTR_ENCODING'), PDO::SQLSRV_ENCODING_BINARY); - $stmt->bindParam(2, $input, PDO::PARAM_LOB); + // Specify binary encoding for the second parameter only such that the first + // parameter is unaffected + $stmt->bindParam(2, $input, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); $stmt->execute(); } catch (PDOException $e) { echo "Error unexpected in insertData\n"; @@ -68,6 +71,7 @@ function invalidEncoding1($conn, $sql) $stmt->bindColumn(1, $id, PDO::PARAM_INT, 0, PDO::SQLSRV_ENCODING_UTF8); $stmt->execute(); $stmt->fetch(PDO::FETCH_BOUND); + echo "invalidEncoding1: expected to fail!\n"; } catch (PDOException $e) { $error = '*An encoding was specified for column 1. Only PDO::PARAM_LOB and PDO::PARAM_STR column types can take an encoding option.'; if (!fnmatch($error, $e->getMessage())) { @@ -84,6 +88,7 @@ function invalidEncoding2($conn, $sql) $stmt->bindColumn('Value', $val1, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_DEFAULT); $stmt->execute(); $stmt->fetch(PDO::FETCH_BOUND); + echo "invalidEncoding2: expected to fail!\n"; } catch (PDOException $e) { $error = '*Invalid encoding specified for column 1.'; if (!fnmatch($error, $e->getMessage())) { @@ -100,6 +105,7 @@ function invalidEncoding3($conn, $sql) $stmt->bindColumn(1, $id, PDO::PARAM_STR, 0, "dummy"); $stmt->execute(); $stmt->fetch(PDO::FETCH_BOUND); + echo "invalidEncoding3: expected to fail!\n"; } catch (PDOException $e) { $error = '*An invalid type or value was given as bound column driver data for column 1. Only encoding constants such as PDO::SQLSRV_ENCODING_UTF8 may be used as bound column driver data.'; if (!fnmatch($error, $e->getMessage())) { diff --git a/test/functional/sqlsrv/srv_007_login_timeout.phpt b/test/functional/sqlsrv/srv_007_login_timeout.phpt index c8fa5cf19..681b149a1 100644 --- a/test/functional/sqlsrv/srv_007_login_timeout.phpt +++ b/test/functional/sqlsrv/srv_007_login_timeout.phpt @@ -11,15 +11,23 @@ $serverName = "WRONG_SERVER_NAME"; $t0 = microtime(true); -$conn = sqlsrv_connect($serverName , array("LoginTimeout" => 8)); +// Based on the following reference, a login timeout of less than approximately 10 seconds +// is not reliable. The defaut is 15 seconds so we fix it at 20 seconds. +// https://docs.microsoft.com/sql/connect/odbc/windows/features-of-the-microsoft-odbc-driver-for-sql-server-on-windows + +$timeout = 20; +$conn = sqlsrv_connect($serverName , array("LoginTimeout" => $timeout)); $t1 = microtime(true); -echo "Connection attempt time: " . ($t1 - $t0) . " [sec]\n"; +$elapsed = $t1 - $t0; +$diff = abs($elapsed - $timeout); + +if ($elapsed < $timeout || $diff > 1.0) { + echo "Connection failed at $elapsed secs. Leeway is 1.0 sec but the difference is $diff\n"; +} print "Done"; ?> - ---EXPECTREGEX-- -Connection attempt time: [7-9]\.[0-9]+ \[sec\] +--EXPECT-- Done From ac59cfd56a17e26adee663e7c066bf5434acd452 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 17 May 2019 11:36:24 -0700 Subject: [PATCH 153/249] Redesigned some tests based on recent test results (#992) --- source/shared/core_stmt.cpp | 2 +- .../pdo_sqlsrv/pdo_data_classification.phpt | 17 +++++-- .../sqlsrv/sqlsrv_data_classification.phpt | 17 +++++-- .../sqlsrv/srv_007_login_timeout.phpt | 49 +++++++++++++------ 4 files changed, 58 insertions(+), 27 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index a2fd1501c..87c58a361 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1026,7 +1026,7 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) throw core::CoreException(); } - CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "Unexpected SQL Error state") { + CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "Check if ODBC driver or the server supports the Data Classification feature.") { throw core::CoreException(); } } diff --git a/test/functional/pdo_sqlsrv/pdo_data_classification.phpt b/test/functional/pdo_sqlsrv/pdo_data_classification.phpt index fd37e07f5..13add5bb7 100644 --- a/test/functional/pdo_sqlsrv/pdo_data_classification.phpt +++ b/test/functional/pdo_sqlsrv/pdo_data_classification.phpt @@ -57,7 +57,7 @@ function testConnAttrCases() } } -function testNotAvailable($conn, $tableName, $isSupported) +function testNotAvailable($conn, $tableName, $isSupported, $driverCapable) { // If supported, the query should return a column with no classification $options = array(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => true); @@ -66,25 +66,31 @@ function testNotAvailable($conn, $tableName, $isSupported) $stmt->execute(); $notAvailableErr = '*Failed to retrieve Data Classification Sensitivity Metadata. If the driver and the server both support the Data Classification feature, check whether the query returns columns with classification information.'; + + $unexpectedErrorState = '*Failed to retrieve Data Classification Sensitivity Metadata: Check if ODBC driver or the server supports the Data Classification feature.'; + + $error = ($driverCapable) ? $notAvailableErr : $unexpectedErrorState; try { $metadata = $stmt->getColumnMeta(0); echo "testNotAvailable: expected getColumnMeta to fail\n"; } catch (PDOException $e) { - if (!fnmatch($notAvailableErr, $e->getMessage())) { + if (!fnmatch($error, $e->getMessage())) { echo "testNotAvailable: exception unexpected\n"; var_dump($e->getMessage()); } } } -function isDataClassSupported($conn) +function isDataClassSupported($conn, &$driverCapable) { // Check both SQL Server version and ODBC driver version $msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; $version = explode(".", $msodbcsqlVer); // ODBC Driver must be 17.2 or above + $driverCapable = true; if ($version[0] < 17 || $version[1] < 2) { + $driverCapable = false; return false; } @@ -238,7 +244,8 @@ try { testConnAttrCases(); $conn = connect(); - $isSupported = isDataClassSupported($conn); + $driverCapable = true; + $isSupported = isDataClassSupported($conn, $driverCapable); // Create a test table $tableName = 'pdoPatients'; @@ -274,7 +281,7 @@ try { } // Test another error condition - testNotAvailable($conn, $tableName, $isSupported); + testNotAvailable($conn, $tableName, $isSupported, $driverCapable); // Run the query without data classification metadata $tsql = "SELECT * FROM $tableName"; diff --git a/test/functional/sqlsrv/sqlsrv_data_classification.phpt b/test/functional/sqlsrv/sqlsrv_data_classification.phpt index 2d6cce420..ab06cce63 100644 --- a/test/functional/sqlsrv/sqlsrv_data_classification.phpt +++ b/test/functional/sqlsrv/sqlsrv_data_classification.phpt @@ -10,7 +10,7 @@ PHPT_EXEC=true $timeout)); - -$t1 = microtime(true); - -$elapsed = $t1 - $t0; -$diff = abs($elapsed - $timeout); - -if ($elapsed < $timeout || $diff > 1.0) { - echo "Connection failed at $elapsed secs. Leeway is 1.0 sec but the difference is $diff\n"; -} - -print "Done"; +$timeout = 20; +$maxAttempts = 3; +$numAttempts = 0; +$leeway = 1.0; +$missed = false; + +do { + $t0 = microtime(true); + + $conn = sqlsrv_connect($serverName , array("LoginTimeout" => $timeout)); + $numAttempts++; + + $t1 = microtime(true); + + // Sometimes time elapsed might be less than expected timeout, such as 19.99* + // something, but 1.0 second leeway should be reasonable + $elapsed = $t1 - $t0; + $diff = abs($elapsed - $timeout); + + $missed = ($diff > $leeway); + if ($missed) { + if ($numAttempts == $maxAttempts) { + echo "Connection failed at $elapsed secs. Leeway is $leeway sec but the difference is $diff\n"; + } else { + // The test will fail but this helps us decide if this test should be redesigned + echo "$numAttempts\t"; + sleep(5); + } + } +} while ($missed && $numAttempts < $maxAttempts); + +print "Done\n"; ?> --EXPECT-- Done From 9122c1574a30d14d35d141ae16a28e78ee8ee113 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 17 May 2019 11:36:51 -0700 Subject: [PATCH 154/249] Modified pipelines to connect using sqlcmd inside of the container instead (#995) --- azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ab688eae8..3f048788b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -101,7 +101,8 @@ jobs: docker pull mcr.microsoft.com/mssql/server:2017-latest docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=$(pwd)' -p 1433:1433 -h $(host) --name=$(host) -d mcr.microsoft.com/mssql/server:2017-latest docker ps -a - sqlcmd -S $(server) -U $(uid) -P $(pwd) -Q 'select @@Version' + sleep 5 + docker exec -t $(host) /opt/mssql-tools/bin/sqlcmd -S $(server) -U $(uid) -P $(pwd) -Q 'select @@Version' displayName: 'Run SQL Server for Linux' - script: | From 68d7903e695e952cee1c0a192759b5a265137763 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 22 May 2019 11:07:42 -0700 Subject: [PATCH 155/249] Added batch query --- .../pdo_sqlsrv/pdo_batch_query.phpt | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_batch_query.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_batch_query.phpt b/test/functional/pdo_sqlsrv/pdo_batch_query.phpt new file mode 100644 index 000000000..3fa44cc91 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_batch_query.phpt @@ -0,0 +1,204 @@ +--TEST-- +Test a batch query with different cursor types +--DESCRIPTION-- +Verifies row and column counts from batch queries. This is the +equivalent of sqlsrv_batch_query.phpt on the sqlsrv side. +TODO: Fix this test once error reporting in PDO is fixed, because batch +queries are not supposed to work with server side cursors. For now, no errors +or warnings are returned. For information on the expected behaviour of cursors +with batch queries, see +https://docs.microsoft.com/en-us/previous-versions/visualstudio/aa266531(v=vs.60) +--SKIPIF-- + +--FILE-- + PDO::CURSOR_FWDONLY), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_DYNAMIC), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_STATIC), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_KEYSET), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED), + ); + +// Data for testing, all integer types +$data = array(array(86, -217483648, 0, -432987563, 7, 217483647), + array(0, 31, 127, 255, 1, 10), + array(4534, -212, 32767, 0, 7, -32768), + array(-1, 546098342985600, 9223372000000000000, 5115115115115, 7, -7), + array(0, 1, 0, 0, 1, 1), + ); + +// Column names +$colName = array('c1_int', 'c2_tinyint', 'c3_smallint', 'c4_bigint', 'c5_bit'); + +// Fetch one column at a time +$expectedCols = 1; + +// Number of table rows +$expectedRows = sizeof($data[0]); + +// Expected result sets = number of columns, since the batch fetches each column sequentially +$expectedResultSets = sizeof($colName); + +function checkErrors($expectedError) +{ + // TODO: Fill this in once PDO error reporting is fixed +} + +function checkColumnsAndRows($stmt, $cursor, $before) +{ + global $expectedCols, $expectedRows; + + $cols = $stmt->columnCount(); + + if ($cols != $expectedCols) { + fatalError("Incorrect number of columns returned with $cursor cursor. Expected $expectedCols columns, got $cols columns\n"); + } + + $rows = $stmt->rowCount(); + + // Buffered cursors always return the correct number of rows. Other cursors + // return -1 rows before fetching. Static and keyset cursors return -1 even + // after fetching, while forward and dynamic cursors return the correct + // number of rows after fetching. + if ($cursor == 'buffered') { + if ($rows != $expectedRows) { + fatalError("Incorrect number of columns returned with buffered cursor. Expected $expectedRows rows, got $rows rows\n"); + } + } else { + if ($before) { + if ($rows !== -1) { + fatalError("Incorrect number of rows returned before fetching with a $cursor cursor. Expected -1 rows, got $rows rows\n"); + } + } else { + if ($cursor == 'static' or $cursor == 'keyset') { + if ($rows !== -1) { + fatalError("Incorrect number of rows returned before fetching with a $cursor cursor. Expected -1 rows, got $rows rows\n"); + } + } else { + if ($rows != $expectedRows) { + fatalError("Incorrect number of columns returned with buffered cursor. Expected $expectedRows rows, got $rows rows\n"); + } + } + } + } +} + +function printCursor($element) +{ + $cursor = 'forward'; + switch($element) + { + case 0: + echo "Testing with forward cursor...\n"; + break; + case 1: + echo "Testing with dynamic cursor...\n"; + $cursor = 'dynamic'; + break; + case 2: + echo "Testing with static cursor...\n"; + $cursor = 'static'; + break; + case 3: + echo "Testing with keyset cursor...\n"; + $cursor = 'keyset'; + break; + case 4: + echo "Testing with buffered cursor...\n"; + $cursor = 'buffered'; + break; + default: + fatalError("Unknown cursor type! Exiting\n"); + } + return $cursor; +} + +$conn = connect(); +$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +// Create and populate a table of integer types +$tableName = 'batch_query_test'; +$columns = array(new ColumnMeta('int', $colName[0]), + new ColumnMeta('tinyint', $colName[1]), + new ColumnMeta('smallint',$colName[2]), + new ColumnMeta('bigint', $colName[3]), + new ColumnMeta('bit', $colName[4])); + +createTable($conn, $tableName, $columns); + +// Insert each row. Need an associative array to use insertRow() +for ($i = 0; $i < $expectedRows; ++$i) { + $inputs = array(); + for ($j = 0; $j < $expectedResultSets; ++$j) { + $inputs[$colName[$j]] = $data[$j][$i]; + } + + $stmt = insertRow($conn, $tableName, $inputs); + unset($inputs); + unset($stmt); +} + +$query = "SELECT c1_int FROM $tableName; + SELECT c2_tinyint FROM $tableName; + SELECT c3_smallint FROM $tableName; + SELECT c4_bigint FROM $tableName; + SELECT c5_bit FROM $tableName;"; + +// Test the batch query with different cursor types +for ($i = 0; $i < sizeof($cursors); ++$i) { + try { + $cursorType = $cursors[$i]; + $cursor = printCursor($i); + + $stmt = $conn->prepare($query, $cursorType); + $stmt->execute(); + + $numResultSets = 0; + + // Check the column and row count before and after running through + // each result set, because some cursor types may return the number + // of rows only after fetching all rows in the result set + do { + checkColumnsAndRows($stmt, $cursor, true); + + $row = 0; + while ($res = $stmt->fetch(PDO::FETCH_NUM)) { + if ($res[0] != $data[$numResultSets][$row]) { + fatalError("Wrong result, expected ".$data[$numResultSets][$row].", got $res[0]\n"); + } + ++$row; + } + + checkColumnsAndRows($stmt, $cursor, false); + ++$numResultSets; + } while ($next = $stmt->nextRowset()); + + if ($numResultSets != $expectedResultSets) { + fatalError("Unexpected number of result sets, expected $expectedResultedSets, got $numResultSets\n"); + } + } catch(PDOException $e) { + echo "Exception caught\n"; + print_r($e); + } + +unset($stmt); +} + +dropTable($conn, $tableName); +unset($conn); + +echo "Done.\n"; +?> +--EXPECT-- +Testing with forward cursor... +Testing with dynamic cursor... +Testing with static cursor... +Testing with keyset cursor... +Testing with buffered cursor... +Done. From 7c585c754e4de8d3e8bb6242de0cdce675d32dc8 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 22 May 2019 11:38:46 -0700 Subject: [PATCH 156/249] Added batch query test for pdo (#997) --- .../pdo_sqlsrv/pdo_batch_query.phpt | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_batch_query.phpt b/test/functional/pdo_sqlsrv/pdo_batch_query.phpt index 3fa44cc91..9453e26d9 100644 --- a/test/functional/pdo_sqlsrv/pdo_batch_query.phpt +++ b/test/functional/pdo_sqlsrv/pdo_batch_query.phpt @@ -92,29 +92,28 @@ function checkColumnsAndRows($stmt, $cursor, $before) function printCursor($element) { $cursor = 'forward'; - switch($element) - { + switch($element) { case 0: - echo "Testing with forward cursor...\n"; - break; + echo "Testing with forward cursor...\n"; + break; case 1: - echo "Testing with dynamic cursor...\n"; - $cursor = 'dynamic'; - break; + echo "Testing with dynamic cursor...\n"; + $cursor = 'dynamic'; + break; case 2: - echo "Testing with static cursor...\n"; - $cursor = 'static'; - break; + echo "Testing with static cursor...\n"; + $cursor = 'static'; + break; case 3: - echo "Testing with keyset cursor...\n"; - $cursor = 'keyset'; - break; + echo "Testing with keyset cursor...\n"; + $cursor = 'keyset'; + break; case 4: - echo "Testing with buffered cursor...\n"; - $cursor = 'buffered'; - break; + echo "Testing with buffered cursor...\n"; + $cursor = 'buffered'; + break; default: - fatalError("Unknown cursor type! Exiting\n"); + fatalError("Unknown cursor type! Exiting\n"); } return $cursor; } From ec3a7a44378b51f1bc41cc8021255316d586f332 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 7 Jun 2019 11:13:32 -0700 Subject: [PATCH 157/249] Added a new test and modify a non LOB sqlsrv test (#1000) --- .../pdo_sqlsrv/pdo_test_non_LOB_types.phpt | 78 ++++++++++++++++++ .../sqlsrv/test_sqlsrv_phptype_stream.phpt | Bin 10383 -> 10421 bytes 2 files changed, 78 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt b/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt new file mode 100644 index 000000000..1c287d9ac --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt @@ -0,0 +1,78 @@ +--TEST-- +Test reading non LOB types +--DESCRIPTION-- +A simpler version of sqlsrv test "test_sqlsrv_phptype_stream.phpt" for reading from +pre-populated tables [test_streamable_types] and [test_types] +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // test the allowed non LOB column types + $tsql = "SELECT [char_short_type], [varchar_short_type], [nchar_short_type], [nvarchar_short_type], [binary_short_type], [varbinary_short_type] FROM [test_streamable_types]"; + $stmt = $conn->query($tsql); + + $result = $stmt->fetch(PDO::FETCH_NUM); + verifyResult($result); + + // test not streamable types + $tsql = "SELECT * FROM [test_types]"; + $stmt = $conn->query($tsql); + $result = $stmt->fetch(PDO::FETCH_NUM); + print_r($result); + +} catch (PDOException $e) { + var_dump($e->errorInfo); +} + +unset($stmt); +unset($conn); + +?> +--EXPECT-- +Array +( + [0] => 9223372036854775807 + [1] => 2147483647 + [2] => 32767 + [3] => 255 + [4] => 1 + [5] => 9999999999999999999999999999999999999 + [6] => 922337203685477.5807 + [7] => 214748.3647 + [8] => 1.79E+308 + [9] => 1.1799999E-38 + [10] => 1968-12-12 16:20:00.000 + [11] => +) \ No newline at end of file diff --git a/test/functional/sqlsrv/test_sqlsrv_phptype_stream.phpt b/test/functional/sqlsrv/test_sqlsrv_phptype_stream.phpt index cdf1453510012a233933bed64095435bb02bffb6..908e5d4d4766b54a2d0dc2aca64ad26053a4983d 100644 GIT binary patch delta 143 zcmeAV+!{FHbG?STZ*g#HNoj#zW?r(orZq2@0uZPq=jY`q*eVnk<`fr|#e;aM$t4;p z#i>PQsYN;vd7wyUex82;h%AQb%uLgO>9)01NK4EqPF2uUsD>B|GCD1>Br(Ues3^Zk RL&+_1vLB<&<_t!4bpT!QERg^J delta 80 zcmdlQ*dI9IvqOA-UUI62l5eqder|4lo?d2NvXZ7XFBdPD0uZPq=jY`q*eZai)Z`Kk YkPJvXGfhE55iB`5oY8#q1V&YL02_4|)c^nh From a092523a2efee6a50b5007279f8d87ba4c225369 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 7 Jun 2019 15:45:42 -0700 Subject: [PATCH 158/249] Two index zval functions are macros in php 7.4 (#1001) --- source/shared/core_sqlsrv.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index c6a0eb97a..3e134c343 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -2496,7 +2496,7 @@ namespace core { inline void sqlsrv_add_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zend_ulong index, _In_ zval* value TSRMLS_DC) { - int zr = ::add_index_zval( array, index, value ); + int zr = add_index_zval( array, index, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } @@ -2504,7 +2504,7 @@ namespace core { inline void sqlsrv_add_next_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zval* value TSRMLS_DC) { - int zr = ::add_next_index_zval( array, value ); + int zr = add_next_index_zval( array, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { throw CoreException(); } From 5c4282943833b5451c37d101fb1420b7816e7ccb Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 18 Jun 2019 11:11:48 -0700 Subject: [PATCH 159/249] Replaced uint with size_t (#1004) --- source/shared/core_sqlsrv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 3e134c343..bb7c8d5c3 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -2622,7 +2622,7 @@ namespace core { } } - inline void sqlsrv_zend_hash_next_index_insert_mem( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _In_reads_bytes_(data_size) void* data, _In_ uint data_size TSRMLS_DC) + inline void sqlsrv_zend_hash_next_index_insert_mem( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _In_reads_bytes_(data_size) void* data, _In_ size_t data_size TSRMLS_DC) { int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { From d4387a9ec95c57f796939ac767dd99ec80319197 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 18 Jun 2019 15:59:51 -0700 Subject: [PATCH 160/249] Check compiler version for php 74 (#1005) --- buildscripts/buildtools.py | 73 ++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index c1f9df4cd..17713f242 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -42,7 +42,8 @@ def __init__(self, phpver, driver, arch, thread, no_rename, debug_enabled = Fals self.thread = thread.lower() self.no_rename = no_rename self.debug_enabled = debug_enabled - + self.vc = '' + def major_version(self): """Return the major version number based on the PHP version.""" return self.phpver[0:3] @@ -66,17 +67,56 @@ def driver_new_name(self, driver, suffix): version = self.version_label() return 'php_' + driver + '_' + version + '_' + self.thread + suffix - def compiler_version(self): - """Return the appropriate compiler version based on PHP version.""" - VC = 'vc14' - version = self.version_label() - if version >= '72': # Compiler version for PHP 7.2 or above - VC = 'vc15' - return VC + def determine_compiler(self, sdk_dir, vs_ver): + """Return the compiler version using vswhere.exe.""" + vswhere = os.path.join(sdk_dir, 'php-sdk', 'bin', 'vswhere.exe') + if not os.path.exists(vswhere): + print('Could not find ' + vswhere) + exit(1) + + # If both VS 2017 and VS 2019 are installed, if we check only version 15, + # both versions are returned. + # For example, temp.txt would have the following values (in this order): + # 16.1.29009.5 + # 15.9.28307.344 + # But if only VS 2017 is present, temp.txt will only have one value like this: + # 15.9.28307.344 + # Likewise, if only VS 2019 is present, temp.txt contains only the one for 16. + # We can achieve the above by checking for version [15,16), in which case + # even if both compilers are present, it only returns one. If only VS 2019 + # exists, temp.txt is empty + command = '{0} -version [{1},{2}) -property installationVersion '.format(vswhere, vs_ver, vs_ver + 1) + os.system(command + ' > temp.txt') + + # Read the first line from temp.txt + with open('temp.txt', 'r') as f: + ver = f.readline() + print('Version: ' + ver) + vc = ver[:2] + if vc == '15': + return 'vc15' + else: # For VS2019, it's 'vs' instead of 'vc' + return 'vs16' - def phpsrc_root(self, sdk_dir): + def compiler_version(self, sdk_dir): + """Return the appropriate compiler version based on PHP version.""" + if self.vc is '': + VC = 'vc14' + version = self.version_label() + if version >= '72': # Compiler version for PHP 7.2 or above + VC = 'vc15' + if version == '74': + # Compiler version for PHP 7.4 or above + # Can be compiled using VS 2017 or VS 2019 + print('Checking compiler versions...') + VC = self.determine_compiler(sdk_dir, 15) + self.vc = VC + print('Compiler: ' + self.vc) + return self.vc + + def phpsrc_root(self, sdk_dir): """Return the path to the PHP source folder based on *sdk_dir*.""" - vc = self.compiler_version() + vc = self.compiler_version(sdk_dir) return os.path.join(sdk_dir, 'php-sdk', 'phpdev', vc, self.arch, 'php-'+self.phpver+'-src') def build_abs_path(self, sdk_dir): @@ -97,6 +137,10 @@ def build_abs_path(self, sdk_dir): def remove_old_builds(self, sdk_dir): """Remove the extensions, e.g. the driver subfolders in php-7.*-src\ext.""" + if not os.path.exists(os.path.join(sdk_dir, 'php-sdk')): + print('No old builds to be removed...') + return + print('Removing old builds...') phpsrc = self.phpsrc_root(sdk_dir) @@ -117,6 +161,10 @@ def remove_prev_build(self, sdk_dir): """Remove all binaries and source code in the Release* or Debug* folders according to the current configuration """ + if not os.path.exists(os.path.join(sdk_dir, 'php-sdk')): + print('No old builds to be removed...') + return + print('Removing previous build...') build_dir = self.build_abs_path(sdk_dir) if not os.path.exists(build_dir): @@ -370,13 +418,16 @@ def build_drivers(self, make_clean = False, dest = None, log_file = None): os.system('git clone https://github.com/OSTC/php-sdk-binary-tools.git --branch master --single-branch --depth 1 ' + phpSDK) os.chdir(phpSDK) os.system('git pull ') + print('Done cloning the latest php SDK...') # Move the generated batch file to phpSDK for the php starter script + print('Moving the sdk bath file over...') sdk_batch_file = os.path.join(phpSDK, batch_file) if os.path.exists(sdk_batch_file): os.remove(sdk_batch_file) shutil.move(os.path.join(work_dir, batch_file), phpSDK) + print('Checking if source exists...') sdk_source = os.path.join(phpSDK, 'Source') # Sometimes, for various reasons, the Source folder from previous build # might exist in phpSDK. If so, remove it first @@ -386,7 +437,7 @@ def build_drivers(self, make_clean = False, dest = None, log_file = None): shutil.move(source_dir, phpSDK) # Invoke phpsdk--.bat - vc = self.compiler_version() + vc = self.compiler_version(sdk_dir) starter_script = 'phpsdk-' + vc + '-' + self.arch + '.bat' print('Running starter script: ', starter_script) os.system(starter_script + ' -t ' + batch_file) From c0cf381d6c9643e3ae1b0f754ce1cb6e8750d3b0 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 28 Jun 2019 14:08:18 -0700 Subject: [PATCH 161/249] Fixed tests that failed in php 7.4 (#1006) --- .../pdo_sqlsrv/PDO29_ConnInterface.phpt | 8 +++ .../pdo_sqlsrv/PDO32_StmtInterface.phpt | 8 +++ .../pdo_fetch_datetime_time_as_objects.phpt | 19 +++++- .../pdo_fetch_variants_diff_styles.phpt | 2 +- .../pdo_sqlsrv/pdo_test_non_LOB_types.phpt | 4 +- .../sqlsrv/sqlsrv_ae_insert_datetime.phpt | 68 ++++++------------- .../sqlsrv/sqlsrv_ae_insert_retrieve.phpt | 16 ++--- .../sqlsrv_ae_insert_retrieve_fixed_size.phpt | 25 ++++--- 8 files changed, 79 insertions(+), 71 deletions(-) diff --git a/test/functional/pdo_sqlsrv/PDO29_ConnInterface.phpt b/test/functional/pdo_sqlsrv/PDO29_ConnInterface.phpt index 237a5e5b8..49649bdc5 100644 --- a/test/functional/pdo_sqlsrv/PDO29_ConnInterface.phpt +++ b/test/functional/pdo_sqlsrv/PDO29_ConnInterface.phpt @@ -46,6 +46,14 @@ function CheckInterface($conn) '__sleep' => true, 'inTransaction' => true, ); + + $phpver = substr(phpversion(), 0, 3); + if ($phpver >= '7.4') { + // Reference: https://wiki.php.net/rfc/custom_object_serialization + unset($expected['__wakeup']); + unset($expected['__sleep']); + } + $classname = get_class($conn); $methods = get_class_methods($classname); foreach ($methods as $k => $method) diff --git a/test/functional/pdo_sqlsrv/PDO32_StmtInterface.phpt b/test/functional/pdo_sqlsrv/PDO32_StmtInterface.phpt index 06ca54d13..1bc73c2f0 100644 --- a/test/functional/pdo_sqlsrv/PDO32_StmtInterface.phpt +++ b/test/functional/pdo_sqlsrv/PDO32_StmtInterface.phpt @@ -49,6 +49,14 @@ function checkInterface($stmt) '__wakeup' => true, '__sleep' => true, ); + + $phpver = substr(phpversion(), 0, 3); + if ($phpver >= '7.4') { + // Reference: https://wiki.php.net/rfc/custom_object_serialization + unset($expected['__wakeup']); + unset($expected['__sleep']); + } + $classname = get_class($stmt); $methods = get_class_methods($classname); foreach ($methods as $k => $method) { diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt index ab8ba5ece..f83bc4c0b 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt @@ -15,11 +15,24 @@ require_once("MsCommon_mid-refactor.inc"); function checkStringValues($obj, $columns, $values) { $size = count($values); - $objArray = (array)$obj; // turn the object into an associated array - for ($i = 0; $i < $size; $i++) { $col = $columns[$i]; - $val = $objArray[$col]; + switch ($i) { + case 0: + $val = $obj->c1; break; + case 1: + $val = $obj->c2; break; + case 2: + $val = $obj->c3; break; + case 3: + $val = $obj->c4; break; + case 4: + $val = $obj->c5; break; + case 5: + $val = $obj->c6; break; + default: + echo "Something went wrong!\n"; + } if ($val != $values[$i]) { echo "Expected $values[$i] for column $col but got: "; diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_variants_diff_styles.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_variants_diff_styles.phpt index 311538ab7..e99aae85f 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_variants_diff_styles.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_variants_diff_styles.phpt @@ -200,7 +200,7 @@ function fetchColumns($conn, $tableName, $numRows, $numCols) $res = $stmtTmp->execute(); if (! $res) { - echo "Failed to insert data from column ". $j +1 ."\n"; + echo "Failed to insert data from column ". ($j + 1) ."\n"; } } diff --git a/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt b/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt index 1c287d9ac..cd709eb1d 100644 --- a/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt +++ b/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt @@ -24,12 +24,12 @@ function verifyResult($result) $expectedLen = ($i % 2 == 0) ? $fullLen : $trimmedLen; $len = strlen($result[$i]); if ($len != $expectedLen) { - echo "String length $len for column ". $i + 1 . " is unexpected!\n"; + echo "String length $len for column ". ($i + 1) . " is unexpected!\n"; } $data = rtrim($result[$i]); if ($data !== $input) { - echo "Result for column ". $i + 1 . " is unexpected:"; + echo "Result for column ". ($i + 1) . " is unexpected:"; var_dump($result[$i]); } } diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime.phpt index 6b85cf8f3..0c1c28283 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime.phpt @@ -29,18 +29,20 @@ foreach ($dataTypes as $dataType) { is_incompatible_types_error($dataType, "default type"); } else { echo "****Encrypted default type is compatible with encrypted $dataType****\n"; - if ($dataType != "time") { - AE\fetchAll($conn, $tbname); - } else { - $sql = "SELECT * FROM $tbname"; - $stmt = sqlsrv_query($conn, $sql); - $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); - foreach ($row as $key => $value) { - //var_dump( $row ); + $sql = "SELECT * FROM $tbname"; + $stmt = sqlsrv_query($conn, $sql); + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + foreach ($row as $key => $value) { + if ($dataType == "time") { $t = $value->format('H:i:s'); print "$key: $t\n"; + } else { + $t = date_format($value, "Y-m-d H:i:s.u"); + $tz = $value->getTimezone()->getName(); + print "$key: $t $tz\n"; } } + } dropTable($conn, $tbname); } @@ -51,47 +53,23 @@ sqlsrv_close($conn); Testing date: ****Encrypted default type is compatible with encrypted date**** -c_det: - date: 0001-01-01 00:00:00.000000 - timezone_type: 3 - timezone: Canada/Pacific -c_rand: - date: 9999-12-31 00:00:00.000000 - timezone_type: 3 - timezone: Canada/Pacific +c_det: 0001-01-01 00:00:00.000000 Canada/Pacific +c_rand: 9999-12-31 00:00:00.000000 Canada/Pacific Testing datetime: ****Encrypted default type is compatible with encrypted datetime**** -c_det: - date: 1753-01-01 00:00:00.000000 - timezone_type: 3 - timezone: Canada/Pacific -c_rand: - date: 9999-12-31 23:59:59.997000 - timezone_type: 3 - timezone: Canada/Pacific +c_det: 1753-01-01 00:00:00.000000 Canada/Pacific +c_rand: 9999-12-31 23:59:59.997000 Canada/Pacific Testing datetime2: ****Encrypted default type is compatible with encrypted datetime2**** -c_det: - date: 0001-01-01 00:00:00.000000 - timezone_type: 3 - timezone: Canada/Pacific -c_rand: - date: 9999-12-31 23:59:59.123456 - timezone_type: 3 - timezone: Canada/Pacific +c_det: 0001-01-01 00:00:00.000000 Canada/Pacific +c_rand: 9999-12-31 23:59:59.123456 Canada/Pacific Testing smalldatetime: ****Encrypted default type is compatible with encrypted smalldatetime**** -c_det: - date: 1900-01-01 00:00:00.000000 - timezone_type: 3 - timezone: Canada/Pacific -c_rand: - date: 2079-06-05 23:59:00.000000 - timezone_type: 3 - timezone: Canada/Pacific +c_det: 1900-01-01 00:00:00.000000 Canada/Pacific +c_rand: 2079-06-05 23:59:00.000000 Canada/Pacific Testing time: ****Encrypted default type is compatible with encrypted time**** @@ -100,11 +78,5 @@ c_rand: 23:59:59 Testing datetimeoffset: ****Encrypted default type is compatible with encrypted datetimeoffset**** -c_det: - date: 0001-01-01 00:00:00.000000 - timezone_type: 1 - timezone: -14:00 -c_rand: - date: 9999-12-31 23:59:59.123456 - timezone_type: 1 - timezone: +14:00 +c_det: 0001-01-01 00:00:00.000000 -14:00 +c_rand: 9999-12-31 23:59:59.123456 +14:00 diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve.phpt index e88ef0dfc..36df56700 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve.phpt @@ -34,10 +34,13 @@ foreach ($decrypted_row as $key => $value) { if (!is_object($value)) { print "$key: $value\n"; } else { - print "$key:\n"; - foreach ($value as $dateKey => $dateValue) { - print " $dateKey: $dateValue\n"; - } + // print "$key:\n"; + // foreach ($value as $dateKey => $dateValue) { + // print " $dateKey: $dateValue\n"; + // } + $t = date_format($value, "Y-m-d H:i:s.u"); + $tz = $value->getTimezone()->getName(); + print "$key: $t $tz\n"; } } sqlsrv_free_stmt($stmt); @@ -70,10 +73,7 @@ Retrieving plaintext data: SSN: 795-73-9838 FirstName: Catherine LastName: Abel -BirthDate: - date: 1996-10-19 00:00:00.000000 - timezone_type: 3 - timezone: Canada/Pacific +BirthDate: 1996-10-19 00:00:00.000000 Canada/Pacific Checking ciphertext data: Done diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_fixed_size.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_fixed_size.phpt index d8c272fff..dac610fb2 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_fixed_size.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_retrieve_fixed_size.phpt @@ -38,7 +38,20 @@ if ($r === false) { } print "Decrypted values:\n"; -AE\fetchAll($conn, $tbname); + +$stmt = selectFromTable($conn, $tbname); +while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { + foreach ($row as $key => $value) { + if (is_object($value)) { + // datetime objects + $t = date_format($value,"Y-m-d H:i:s.u"); + $tz = $value->getTimezone()->getName(); + print("$key: $t $tz\n"); + } else { + print("$key: $value\n"); + } + } +} sqlsrv_free_stmt($stmt); @@ -75,12 +88,6 @@ IntData: 2147483647 BigIntData: 92233720368547 DecimalData: 79228162514264 BitData: 1 -DateTimeData: - date: 9999-12-31 23:59:59.997000 - timezone_type: 3 - timezone: Canada/Pacific -DateTime2Data: - date: 9999-12-31 23:59:59.123456 - timezone_type: 3 - timezone: Canada/Pacific +DateTimeData: 9999-12-31 23:59:59.997000 Canada/Pacific +DateTime2Data: 9999-12-31 23:59:59.123456 Canada/Pacific Done From b839ede8784221aa09895e29b2bc91b1787482c3 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 15 Jul 2019 14:21:54 -0700 Subject: [PATCH 162/249] Improve data caching with datetime objects (#1008) --- source/pdo_sqlsrv/pdo_stmt.cpp | 23 +++----- source/shared/core_sqlsrv.h | 2 + source/shared/core_stmt.cpp | 54 +++++++------------ source/shared/core_util.cpp | 34 ++++++++++++ source/sqlsrv/stmt.cpp | 10 ++-- .../pdo_fetch_datetime_time_as_objects.phpt | 35 ++++++++++-- .../pdo_fetch_datetime_time_nulls.phpt | 1 - 7 files changed, 98 insertions(+), 61 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index edc5284d3..9e7aee27b 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -220,7 +220,7 @@ void meta_data_free( _Inout_ field_meta_data* meta ) sqlsrv_free( meta ); } -zval convert_to_zval( _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val, _In_opt_ SQLLEN field_len ) +zval convert_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val, _In_opt_ SQLLEN field_len ) { zval out_zval; ZVAL_UNDEF(&out_zval); @@ -264,15 +264,8 @@ zval convert_to_zval( _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _Inout_ void** in_val break; } case SQLSRV_PHPTYPE_DATETIME: - if (*in_val == NULL) { - - ZVAL_NULL(&out_zval); - } - else { - - out_zval = *(reinterpret_cast(*in_val)); - sqlsrv_free(*in_val); - } + convert_datetime_string_to_zval(stmt, static_cast(*in_val), field_len, out_zval); + sqlsrv_free(*in_val); break; case SQLSRV_PHPTYPE_NULL: ZVAL_NULL(&out_zval); @@ -833,11 +826,11 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast(ptr)), reinterpret_cast( len ), true, &sqlsrv_phptype_out TSRMLS_CC ); - if ( ptr ) { - zval* zval_ptr = reinterpret_cast( sqlsrv_malloc( sizeof( zval ))); - *zval_ptr = convert_to_zval( sqlsrv_phptype_out, reinterpret_cast( ptr ), *len ); - *ptr = reinterpret_cast( zval_ptr ); - *len = sizeof( zval ); + if (ptr) { + zval* zval_ptr = reinterpret_cast(sqlsrv_malloc(sizeof(zval))); + *zval_ptr = convert_to_zval(driver_stmt, sqlsrv_phptype_out, reinterpret_cast(ptr), *len); + *ptr = reinterpret_cast(zval_ptr); + *len = sizeof(zval); } return 1; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index bb7c8d5c3..2caef510a 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1810,6 +1810,8 @@ 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, bool use_strict_conversion = false ); +void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* input, _In_ SQLLEN length, _Inout_ zval& out_zval); + //********************************************************************************************************************************* // Error handling routines and Predefined Errors //********************************************************************************************************************************* diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 87c58a361..faa909d4a 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1867,54 +1867,36 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i break; } - // get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and - // convert it to a DateTime object and return the created object + // Reference: https://docs.microsoft.com/sql/odbc/reference/appendixes/sql-to-c-timestamp + // Retrieve the datetime data as a string, which may be cached for later use. + // The string is converted to a DateTime object only when it is required to + // be returned as a zval. case SQLSRV_PHPTYPE_DATETIME: { - char field_value_temp[MAX_DATETIME_STRING_LEN] = {'\0'}; - zval params[1]; - zval field_value_temp_z; - zval function_z; + char* field_value_temp = NULL; + SQLLEN field_len_temp = 0; - ZVAL_UNDEF( &field_value_temp_z ); - ZVAL_UNDEF( &function_z ); - ZVAL_UNDEF( params ); + field_value_temp = static_cast(sqlsrv_malloc(MAX_DATETIME_STRING_LEN)); + memset(field_value_temp, '\0', MAX_DATETIME_STRING_LEN); - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp, - MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC ); + SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_CHAR, field_value_temp, MAX_DATETIME_STRING_LEN, &field_len_temp, true TSRMLS_CC); - CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { - throw core::CoreException(); + if (r == SQL_NO_DATA || field_len_temp == SQL_NULL_DATA) { + sqlsrv_free(field_value_temp); + field_value_temp = NULL; + field_len_temp = 0; } - zval_auto_ptr return_value_z; - return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval )); - ZVAL_UNDEF( return_value_z ); - - if( *field_len == SQL_NULL_DATA ) { - ZVAL_NULL( return_value_z ); - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - break; + CHECK_CUSTOM_ERROR((r == SQL_NO_DATA), stmt, SQLSRV_ERROR_NO_DATA, field_index) { + throw core::CoreException(); } - // Convert the string date to a DateTime object - core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len ); - core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 ); - params[0] = field_value_temp_z; - - if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1, - params TSRMLS_CC ) == FAILURE) { - THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); - } + field_value = field_value_temp; + *field_len = field_len_temp; - field_value = reinterpret_cast( return_value_z.get()); - return_value_z.transferred(); - zend_string_free( Z_STR( field_value_temp_z )); - zend_string_free( Z_STR( function_z )); break; } - + // create a stream wrapper around the field and return that object to the PHP script. calls to fread // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file // for how these fields are used. diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index e5427f97a..92e9ba1dc 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -189,6 +189,40 @@ SQLWCHAR* utf16_string_from_mbcs_string( _In_ SQLSRV_ENCODING php_encoding, _In_ return utf16_string; } +// Converts an input (assuming a datetime string) to a zval containing a PHP DateTime object. +// If the input is null, this simply returns a NULL zval. If anything wrong occurs during conversion, +// an exception will be thrown. +void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* input, _In_ SQLLEN length, _Inout_ zval& out_zval) +{ + if (input == NULL) { + ZVAL_NULL(&out_zval); + return; + } + + zval params[1]; + zval value_temp_z; + zval function_z; + + // Initialize all zval variables + ZVAL_UNDEF(&out_zval); + ZVAL_UNDEF(&value_temp_z); + ZVAL_UNDEF(&function_z); + ZVAL_UNDEF(params); + + // Convert the datetime string to a PHP DateTime object + core::sqlsrv_zval_stringl(&value_temp_z, input, length); + core::sqlsrv_zval_stringl(&function_z, "date_create", sizeof("date_create") - 1); + params[0] = value_temp_z; + + if (call_user_function(EG(function_table), NULL, &function_z, &out_zval, 1, + params TSRMLS_CC) == FAILURE) { + THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); + } + + zend_string_free(Z_STR(value_temp_z)); + zend_string_free(Z_STR(function_z)); +} + // call to retrieve an error from ODBC. This uses SQLGetDiagRec, so the // errno is 1 based. It returns it as an array with 3 members: // 1/SQLSTATE) sqlstate diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index da2d3c945..08ed482fe 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1513,11 +1513,11 @@ void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_ Z_TRY_ADDREF( out_zval ); break; } - case SQLSRV_PHPTYPE_DATETIME: - { - out_zval = *( static_cast( in_val )); - break; - } + case SQLSRV_PHPTYPE_DATETIME: + { + convert_datetime_string_to_zval(stmt, static_cast(in_val), field_len, out_zval); + break; + } case SQLSRV_PHPTYPE_NULL: ZVAL_NULL(&out_zval); diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt index f83bc4c0b..8463875b8 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_as_objects.phpt @@ -70,6 +70,33 @@ function checkColumnDTValue($index, $column, $values, $dtObj) } } +function randomColumns($conn, $query, $columns, $values) +{ + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + $stmt = $conn->prepare($query); + + // Fetch a random column to trigger caching + $lastCol = count($columns) - 1; + $col = rand(0, $lastCol); + $stmt->execute(); + $dtObj = $stmt->fetchColumn($col); + checkColumnDTValue($col, $columns[$col], $values, $dtObj); + + // Similarly, fetch another column + $col = (++$col) % count($columns); + $stmt->execute(); + $dtObj = $stmt->fetchColumn($col); + checkColumnDTValue($col, $columns[$col], $values, $dtObj); + + // Now fetch all columns in a backward order + $i = $lastCol; + do { + $stmt->execute(); + $dtObj = $stmt->fetchColumn($i); + checkColumnDTValue($i, $columns[$i], $values, $dtObj); + } while (--$i >= 0); +} + function runTest($conn, $query, $columns, $values, $useBuffer = false) { // fetch the date time values as strings or date time objects @@ -103,7 +130,7 @@ function runTest($conn, $query, $columns, $values, $useBuffer = false) $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_BOTH); checkDTObjectValues($row, $columns, $values, PDO::FETCH_BOTH); - + // ATTR_STRINGIFY_FETCHES should have no effect when fetching date time objects // Setting it to true only converts numeric values to strings when fetching // See http://www.php.net/manual/en/pdo.setattribute.php for details @@ -164,7 +191,6 @@ function runTest($conn, $query, $columns, $values, $useBuffer = false) // last test: set statement attribute fetch_datetime on with no change to // prepared statement -- expected datetime objects to be returned $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); - $stmt->execute(); $i = 0; do { $stmt->execute(); @@ -234,8 +260,9 @@ try { $query = "SELECT * FROM $tableName"; - runTest($conn, $query, $columns, $values); - runTest($conn, $query, $columns, $values, true); + runtest($conn, $query, $columns, $values); + runtest($conn, $query, $columns, $values, true); + randomColumns($conn, $query, $columns, $values); dropTable($conn, $tableName); diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt index 9bffdf751..98c7c957d 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_datetime_time_nulls.phpt @@ -110,7 +110,6 @@ function runTest($conn, $query, $columns, $useBuffer = false) // last test: set statement attribute fetch_datetime on with no change to // prepared statement -- expected datetime objects to be returned $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); - $stmt->execute(); $i = 0; do { $stmt->execute(); From 1a2b49393c6ed4e0d3646e4ef1ca373a71fff7da Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 23 Jul 2019 15:12:55 -0700 Subject: [PATCH 163/249] Fixed for issues found by Semmle (#1011) * Removed unneeded constants * Fixed sqlsrv_free_stmt argument info * Fixed brace escape to avoid buffer overflow * Fixed brace escape and added test * Debugging test failure on Bamboo * Removed debugging output * Debugging test failure on Bamboo * Removed debugging output * Added more test cases * Changed range check to use strchr * Added pdo test * Fixed test and formatting --- source/pdo_sqlsrv/pdo_dbh.cpp | 1 - source/shared/core_conn.cpp | 36 ++++++---- source/shared/core_results.cpp | 2 - source/shared/core_sqlsrv.h | 3 - source/sqlsrv/init.cpp | 2 +- source/sqlsrv/stmt.cpp | 4 -- .../pdo_sqlsrv/pdo_escape_braces.phpt | 71 +++++++++++++++++++ .../sqlsrv/sqlsrv_escape_braces.phpt | 70 ++++++++++++++++++ 8 files changed, 165 insertions(+), 24 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_escape_braces.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_escape_braces.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index f0238fb6d..07c4ca51a 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -1692,7 +1692,6 @@ void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_opti ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) { int type = HASH_KEY_NON_EXISTENT; - int result = 0; type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG; CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_LONG ), ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) { throw core::CoreException(); diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 527143557..76a870802 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -702,22 +702,32 @@ void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t value_len ) { - // if the value is already quoted, then only analyse the part inside the quotes and return it as - // unquoted since we quote it when adding it to the connection string. - if( value_len > 0 && value[0] == '{' && value[value_len - 1] == '}' ) { - ++value; + if (value_len == 0) { + return true; + } + + if (value_len == 1) { + return (value[0] != '}'); + } + + const char *pstr = value; + if (value_len > 0 && value[0] == '{' && value[value_len - 1] == '}') { + pstr = ++value; value_len -= 2; } - // check to make sure that all right braces are escaped + + const char *pch = strchr(pstr, '}'); size_t i = 0; - while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) { - // skip both braces - if( value[i] == '}' ) - ++i; - ++i; - } - if( i < value_len && value[i] == '}' ) { - return false; + + while (pch != NULL && i < value_len) { + i = pch - pstr + 1; + + if (i == value_len || (i < value_len && pstr[i] != '}')) { + return false; + } + + i++; // skip the brace + pch = strchr(pch + 2, '}'); // continue searching } return true; diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 93427bd76..3d4caaabf 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -243,14 +243,12 @@ std::string getUTF8StringFromString( _In_z_ const SQLWCHAR* source ) { // convert to regular character string first char c_str[4] = ""; - mbstate_t mbs; SQLLEN i = 0; std::string str; while ( source[i] ) { memset( c_str, 0, sizeof( c_str ) ); - DWORD rc; int cch = 0; errno_t err = mplat_wctomb_s( &cch, c_str, sizeof( c_str ), source[i++] ); if ( cch > 0 && err == ERROR_SUCCESS ) diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 2caef510a..1750a00e1 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -170,7 +170,6 @@ OACR_WARNING_POP // constants for maximums in SQL Server const int SS_MAXCOLNAMELEN = 128; const int SQL_SERVER_MAX_FIELD_SIZE = 8000; -const int SQL_SERVER_MAX_PRECISION = 38; const int SQL_SERVER_MAX_TYPE_SIZE = 0; const int SQL_SERVER_MAX_PARAMS = 2100; const int SQL_SERVER_MAX_MONEY_SCALE = 4; @@ -998,8 +997,6 @@ class sqlsrv_context { SQLSRV_ENCODING encoding_; // encoding of the context }; -const int SQLSRV_OS_VISTA_OR_LATER = 6; // major version for Vista - // maps an IANA encoding to a code page struct sqlsrv_encoding { diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 80014524d..93d7003a7 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -219,7 +219,7 @@ zend_function_entry sqlsrv_functions[] = { PHP_FE( sqlsrv_client_info, sqlsrv_client_info_arginfo ) PHP_FE( sqlsrv_server_info, sqlsrv_server_info_arginfo ) PHP_FE( sqlsrv_cancel, sqlsrv_cancel_arginfo ) - PHP_FE( sqlsrv_free_stmt, sqlsrv_close_arginfo ) + PHP_FE( sqlsrv_free_stmt, sqlsrv_free_stmt_arginfo ) PHP_FE( sqlsrv_field_metadata, sqlsrv_field_metadata_arginfo ) PHP_FE( sqlsrv_send_stream_data, sqlsrv_send_stream_data_arginfo ) PHP_FE( SQLSRV_SQLTYPE_BINARY, sqlsrv_sqltype_size_arginfo ) diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 08ed482fe..69f44de5d 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -42,7 +42,6 @@ unsigned int current_log_subsystem = LOG_STMT; // constants used as invalid types for type errors const zend_uchar PHPTYPE_INVALID = SQLSRV_PHPTYPE_INVALID; -const int SQLTYPE_INVALID = 0; const int SQLSRV_INVALID_PRECISION = -1; const SQLUINTEGER SQLSRV_INVALID_SIZE = (~1U); const int SQLSRV_INVALID_SCALE = -1; @@ -51,8 +50,6 @@ const int SQLSRV_SIZE_MAX_TYPE = -1; // constants for maximums in SQL Server const int SQL_SERVER_MAX_FIELD_SIZE = 8000; const int SQL_SERVER_MAX_PRECISION = 38; -const int SQL_SERVER_DEFAULT_PRECISION = 18; -const int SQL_SERVER_DEFAULT_SCALE = 0; // default class used when no class is specified by sqlsrv_fetch_object const char STDCLASS_NAME[] = "stdclass"; @@ -470,7 +467,6 @@ PHP_FUNCTION( sqlsrv_fetch_array ) PHP_FUNCTION( sqlsrv_field_metadata ) { sqlsrv_stmt* stmt = NULL; - SQLSMALLINT num_cols = -1; LOG_FUNCTION( "sqlsrv_field_metadata" ); diff --git a/test/functional/pdo_sqlsrv/pdo_escape_braces.phpt b/test/functional/pdo_sqlsrv/pdo_escape_braces.phpt new file mode 100644 index 000000000..c8d4f1912 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_escape_braces.phpt @@ -0,0 +1,71 @@ +--TEST-- +Test that right braces are escaped correctly and that error messages are correct when they're not +--SKIPIF-- + +--FILE-- +getMessage(), $test[1]) === false) { + print_r("Wrong error message returned for test string ".$test[0].". Expected ".$test[1].", actual output:\n"); + print_r($e->getMessage); + echo "\n"; + } + } +} + +echo "Done.\n"; +?> +--EXPECT-- +Done. diff --git a/test/functional/sqlsrv/sqlsrv_escape_braces.phpt b/test/functional/sqlsrv/sqlsrv_escape_braces.phpt new file mode 100644 index 000000000..3c3eeae02 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_escape_braces.phpt @@ -0,0 +1,70 @@ +--TEST-- +Test that right braces are escaped correctly and that error messages are correct when they're not +--SKIPIF-- + +--FILE-- +$test[0], 'pwd'=>$password, 'LoginTimeout'=>1)); + + if (strpos(sqlsrv_errors()[0][2], $test[1]) === false) { + print_r("Wrong error message returned for test string ".$test[0].". Expected ".$test[1].", actual output:\n"); + print_r(sqlsrv_errors()); + } + + unset($conn); +} + +echo "Done.\n"; +?> +--EXPECT-- +Done. From 65daa7a481a61908b00a7bb7626334e1467515d0 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 29 Jul 2019 08:02:50 -0700 Subject: [PATCH 164/249] Addressed various issues with PHP 7.4 beta1 (#1015) --- source/pdo_sqlsrv/pdo_stmt.cpp | 4 +- source/shared/core_stmt.cpp | 239 +++++++++--------- source/shared/core_stream.cpp | 23 +- test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt | 2 +- .../pdo_sqlsrv/pdostatement_fetchAll.phpt | 2 +- .../pdostatement_fetch_orientation.phpt | 16 +- .../pdo_sqlsrv/pdostatement_fetch_style.phpt | 2 +- ...lsrv_ae_output_param_sqltype_datetime.phpt | 3 +- ...qlsrv_ae_output_param_sqltype_numeric.phpt | 3 +- ...sqlsrv_ae_output_param_sqltype_string.phpt | 3 +- ...8_sqlsrv_clientbuffermaxkbsize_option.phpt | 3 +- 11 files changed, 161 insertions(+), 139 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 9e7aee27b..909e350e9 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1129,11 +1129,11 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } } catch( core::CoreException& ) { - + zval_ptr_dtor(return_value); return FAILURE; } catch(...) { - + zval_ptr_dtor(return_value); DIE( "pdo_sqlsrv_stmt_get_col_meta: Unknown exception occurred while retrieving metadata." ); } diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index faa909d4a..f1902bb75 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -988,7 +988,7 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) return; } - if (stmt->current_sensitivity_metadata != NULL) { + if (stmt->current_sensitivity_metadata) { // Already cached, so return return; } @@ -1873,7 +1873,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // be returned as a zval. case SQLSRV_PHPTYPE_DATETIME: { - char* field_value_temp = NULL; + sqlsrv_malloc_auto_ptr field_value_temp; SQLLEN field_len_temp = 0; field_value_temp = static_cast(sqlsrv_malloc(MAX_DATETIME_STRING_LEN)); @@ -1882,8 +1882,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_CHAR, field_value_temp, MAX_DATETIME_STRING_LEN, &field_len_temp, true TSRMLS_CC); if (r == SQL_NO_DATA || field_len_temp == SQL_NULL_DATA) { - sqlsrv_free(field_value_temp); - field_value_temp = NULL; + field_value_temp.reset(); field_len_temp = 0; } @@ -1892,6 +1891,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i } field_value = field_value_temp; + field_value_temp.transferred(); *field_len = field_len_temp; break; @@ -2420,142 +2420,151 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { - if( Z_ISUNDEF(stmt->output_params) ) + if (Z_ISUNDEF(stmt->output_params)) return; - HashTable* params_ht = Z_ARRVAL( stmt->output_params ); + HashTable* params_ht = Z_ARRVAL(stmt->output_params); zend_ulong index = -1; zend_string* key = NULL; void* output_param_temp = NULL; - ZEND_HASH_FOREACH_KEY_PTR( params_ht, index, key, output_param_temp ) { - sqlsrv_output_param* output_param = static_cast( output_param_temp ); - zval* value_z = Z_REFVAL_P( output_param->param_z ); - switch( Z_TYPE_P( value_z )) { - case IS_STRING: + try { + ZEND_HASH_FOREACH_KEY_PTR(params_ht, index, key, output_param_temp) { - // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter - char* str = Z_STRVAL_P( value_z ); - SQLLEN str_len = stmt->param_ind_ptrs[output_param->param_num]; - if( str_len == 0 ) { - core::sqlsrv_zval_stringl( value_z, "", 0 ); - continue; - } - if( str_len == SQL_NULL_DATA ) { - zend_string_release( Z_STR_P( value_z )); - ZVAL_NULL( value_z ); - continue; - } - - // if there was more to output than buffer size to hold it, then throw a truncation error - int null_size = 0; - switch( output_param->encoding ) { - case SQLSRV_ENCODING_UTF8: - null_size = sizeof( SQLWCHAR ); // string isn't yet converted to UTF-8, still UTF-16 - break; - case SQLSRV_ENCODING_SYSTEM: - null_size = 1; - break; - case SQLSRV_ENCODING_BINARY: - null_size = 0; - break; - default: - SQLSRV_ASSERT( false, "Invalid encoding in output_param structure." ); - break; - } - CHECK_CUSTOM_ERROR( str_len > ( output_param->original_buffer_len - null_size ), stmt, - SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1 ) { - throw core::CoreException(); - } - - // For ODBC 11+ see https://msdn.microsoft.com/en-us/library/jj219209.aspx - // A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains up to - // output_param->original_buffer_len data and is NULL terminated. - // The IF statement can be true when using connection pooling with unixODBC 2.3.4. - if ( str_len == SQL_NO_TOTAL ) + sqlsrv_output_param* output_param = static_cast(output_param_temp); + zval* value_z = Z_REFVAL_P(output_param->param_z); + switch (Z_TYPE_P(value_z)) { + case IS_STRING: { - str_len = output_param->original_buffer_len - null_size; - } + // adjust the length of the string to the value returned by SQLBindParameter in the ind_ptr parameter + char* str = Z_STRVAL_P(value_z); + SQLLEN str_len = stmt->param_ind_ptrs[output_param->param_num]; + if (str_len == 0) { + core::sqlsrv_zval_stringl(value_z, "", 0); + continue; + } + if (str_len == SQL_NULL_DATA) { + zend_string_release(Z_STR_P(value_z)); + ZVAL_NULL(value_z); + continue; + } - 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. - if (str_len < output_param->original_buffer_len) { - str[str_len] = '\0'; + // if there was more to output than buffer size to hold it, then throw a truncation error + int null_size = 0; + switch (output_param->encoding) { + case SQLSRV_ENCODING_UTF8: + null_size = sizeof(SQLWCHAR); // string isn't yet converted to UTF-8, still UTF-16 + break; + case SQLSRV_ENCODING_SYSTEM: + null_size = 1; + break; + case SQLSRV_ENCODING_BINARY: + null_size = 0; + break; + default: + SQLSRV_ASSERT(false, "Invalid encoding in output_param structure."); + break; + } + CHECK_CUSTOM_ERROR(str_len > (output_param->original_buffer_len - null_size), stmt, + SQLSRV_ERROR_OUTPUT_PARAM_TRUNCATED, output_param->param_num + 1) + { + throw core::CoreException(); } - core::sqlsrv_zval_stringl(value_z, str, str_len); - } - else { - param_meta_data metaData = output_param->getMetaData(); - if (output_param->encoding != SQLSRV_ENCODING_CHAR) { - char* outString = NULL; - SQLLEN outLen = 0; - bool result = convert_string_from_utf16(output_param->encoding, reinterpret_cast(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(); - } + // For ODBC 11+ see https://msdn.microsoft.com/en-us/library/jj219209.aspx + // A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains up to + // output_param->original_buffer_len data and is NULL terminated. + // The IF statement can be true when using connection pooling with unixODBC 2.3.4. + if (str_len == SQL_NO_TOTAL) { + str_len = output_param->original_buffer_len - null_size; + } - if (stmt->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { - format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, outString, &outLen); + 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. + if (str_len < output_param->original_buffer_len) { + str[str_len] = '\0'; } - - core::sqlsrv_zval_stringl(value_z, outString, outLen); - sqlsrv_free(outString); + core::sqlsrv_zval_stringl(value_z, str, str_len); } else { - if (stmt->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { - format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, str, &str_len); + param_meta_data metaData = output_param->getMetaData(); + + if (output_param->encoding != SQLSRV_ENCODING_CHAR) { + char* outString = NULL; + SQLLEN outLen = 0; + bool result = convert_string_from_utf16(output_param->encoding, reinterpret_cast(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->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { + format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, outString, &outLen); + } + + core::sqlsrv_zval_stringl(value_z, outString, outLen); + sqlsrv_free(outString); } + else { + if (stmt->format_decimals && (metaData.sql_type == SQL_DECIMAL || metaData.sql_type == SQL_NUMERIC)) { + format_decimal_numbers(NO_CHANGE_DECIMAL_PLACES, metaData.decimal_digits, str, &str_len); + } - core::sqlsrv_zval_stringl(value_z, str, str_len); + core::sqlsrv_zval_stringl(value_z, str, str_len); + } } } - } - break; - case IS_LONG: - // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if( stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA ) { - ZVAL_NULL( value_z ); - } - else if( output_param->is_bool ) { - convert_to_boolean( value_z ); - } - else { - ZVAL_LONG( value_z, static_cast( Z_LVAL_P( value_z ))); - } break; - case IS_DOUBLE: - // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so - if (stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA) { - ZVAL_NULL(value_z); - } - else if (output_param->php_out_type == SQLSRV_PHPTYPE_INT) { - // first check if its value is out of range - double dval = Z_DVAL_P(value_z); - if (dval > INT_MAX || dval < INT_MIN) { - CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED) { - throw core::CoreException(); - } + case IS_LONG: + // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so + if (stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA) { + ZVAL_NULL(value_z); } - // if the output param is a boolean, still convert to - // a long integer first to take care of rounding - convert_to_long(value_z); - if (output_param->is_bool) { + else if (output_param->is_bool) { convert_to_boolean(value_z); } + else { + ZVAL_LONG(value_z, static_cast(Z_LVAL_P(value_z))); + } + break; + case IS_DOUBLE: + // for a long or a float, simply check if NULL was returned and set the parameter to a PHP null if so + if (stmt->param_ind_ptrs[output_param->param_num] == SQL_NULL_DATA) { + ZVAL_NULL(value_z); + } + else if (output_param->php_out_type == SQLSRV_PHPTYPE_INT) { + // first check if its value is out of range + double dval = Z_DVAL_P(value_z); + if (dval > INT_MAX || dval < INT_MIN) { + CHECK_CUSTOM_ERROR(true, stmt, SQLSRV_ERROR_DOUBLE_CONVERSION_FAILED) + { + throw core::CoreException(); + } + } + // if the output param is a boolean, still convert to + // a long integer first to take care of rounding + convert_to_long(value_z); + if (output_param->is_bool) { + convert_to_boolean(value_z); + } + } + break; + default: + DIE("Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter."); + break; } - break; - default: - DIE( "Illegal or unknown output parameter type. This should have been caught in core_sqlsrv_bind_parameter." ); - break; - } - value_z = NULL; - } ZEND_HASH_FOREACH_END(); - + value_z = NULL; + } ZEND_HASH_FOREACH_END(); + } + catch (core::CoreException&) { + // empty the hash table due to exception caught + zend_hash_clean(Z_ARRVAL(stmt->output_params)); + throw; + } // empty the hash table since it's been processed - zend_hash_clean( Z_ARRVAL( stmt->output_params )); + zend_hash_clean(Z_ARRVAL(stmt->output_params)); return; } diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 7d0e1beb6..b44d514c2 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -44,7 +44,11 @@ int sqlsrv_stream_close( _Inout_ php_stream* stream, int /*close_handle*/ TSRMLS // read from a sqlsrv stream into the buffer provided by Zend. The parameters for binary vs. char are // set when sqlsrv_get_field is called by the user specifying which field type they want. -size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count TSRMLS_DC ) +#if PHP_VERSION_ID >= 70400 +ssize_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count TSRMLS_DC) +#else +size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count TSRMLS_DC) +#endif { SQLLEN read = 0; SQLSMALLINT c_type = SQL_C_CHAR; @@ -184,15 +188,20 @@ size_t sqlsrv_stream_read( _Inout_ php_stream* stream, _Out_writes_bytes_(count) return static_cast( read ); } - - catch( core::CoreException& ) { - + catch (core::CoreException&) { +#if PHP_VERSION_ID >= 70400 + return -1; +#else return 0; +#endif } - catch( ... ) { - - LOG( SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught." ); + catch (...) { + LOG(SEV_ERROR, "sqlsrv_stream_read: Unknown exception caught."); +#if PHP_VERSION_ID >= 70400 + return -1; +#else return 0; +#endif } } diff --git a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt index a5c31e09e..0c4f88f80 100644 --- a/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt +++ b/test/functional/pdo_sqlsrv/pdo_utf8_conn.phpt @@ -24,6 +24,6 @@ if ($c !== false) { Fatal error: Uncaught PDOException: SQLSTATE\[(28000|08001|HYT00)\]: .*\[Microsoft\]\[ODBC Driver 1[0-9] for SQL Server\](\[SQL Server\])?(Named Pipes Provider: Could not open a connection to SQL Server \[2\]\. |TCP Provider: Error code (0x2726|0x2AF9)|Login timeout expired|Login failed for user 'sa'\.) in .+(\/|\\)pdo_utf8_conn\.php:[0-9]+ Stack trace: -#0 .+(\/|\\)pdo_utf8_conn\.php\([0-9]+\): PDO->__construct\('sqlsrv:Server=l\.\.\.', 'sa', 'Sunshine4u'\) +#0 .+(\/|\\)pdo_utf8_conn\.php\([0-9]+\): PDO->__construct(\(\)|\('sqlsrv:Server=l\.\.\.', 'sa', 'Sunshine4u'\)) #1 {main} thrown in .+(\/|\\)pdo_utf8_conn\.php on line [0-9]+ diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt index 62bc542d8..5f1a4f434 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt @@ -431,6 +431,6 @@ Test_9 : FETCH_INVALID : Fatal error: Uncaught Error: Undefined class constant 'FETCH_UNKNOWN' in %s:%x Stack trace: -#0 %s: fetchAllInvalid(Object(PDO), 'PDO_MainTypes') +#0 %s: fetchAllInvalid(%S) #1 {main} thrown in %s on line %x diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt index b8fd577ab..a14d6ddec 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt @@ -35,7 +35,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_PRIOR ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -53,7 +53,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_REL, -1 ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -63,7 +63,7 @@ try { throw new Exception( "Not C" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -73,7 +73,7 @@ try { throw new Exception( "Not C" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_REL, 1 ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -99,7 +99,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_ABS, -1 ); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -125,7 +125,7 @@ try { throw new Exception( "Not C" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -135,7 +135,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_PRIOR); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } @@ -145,7 +145,7 @@ try { throw new Exception( "Not A" ); } $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_REL, -1); - if( $row[ 'val' ] != false ) { + if ($row !== false) { throw new Exception( "Not false" ); } diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt index 000931b6e..5170bd972 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt @@ -257,6 +257,6 @@ Test_9 : FETCH_INVALID : Fatal error: Uncaught Error: Undefined class constant 'FETCH_UNKNOWN' in %s:%x Stack trace: -#0 %s: fetchWithStyle(Object(PDO), 'PDO_MainTypes', 'PDO::FETCH_INVA...') +#0 %s: fetchWithStyle(%S) #1 {main} thrown in %s on line %x 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 7eebb4002..67c601b85 100755 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_datetime.phpt @@ -59,7 +59,8 @@ foreach ($dataTypes as $dataType) { } } // 22018 is the SQLSTATE for any incompatible conversion errors - if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018) { + $errors = sqlsrv_errors(); + if (!empty($errors) && $isCompatible && $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_numeric.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt index 25d7d15fb..ea5285d85 100755 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_numeric.phpt @@ -69,7 +69,8 @@ foreach ($dataTypes as $dataType) { } } // 22018 is the SQLSTATE for any incompatible conversion errors - if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018) { + $errors = sqlsrv_errors(); + if (!empty($errors) && $isCompatible && $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_string.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt index 77d53147e..8bbe96067 100755 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_sqltype_string.phpt @@ -57,7 +57,8 @@ foreach ($dataTypes as $dataType) { } } // 22018 is the SQLSTATE for any incompatible conversion errors - if ($isCompatible && sqlsrv_errors()[0]['SQLSTATE'] == 22018) { + $errors = sqlsrv_errors(); + if (!empty($errors) && $isCompatible && $errors[0]['SQLSTATE'] == 22018) { echo "$sqlType should be compatible with $dataType\n"; $success = false; } diff --git a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt index 459169d93..c60e23def 100644 --- a/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt +++ b/test/functional/sqlsrv/srv_228_sqlsrv_clientbuffermaxkbsize_option.phpt @@ -44,7 +44,8 @@ function fetchData($conn, $table, $size) echo "Expect this to fail\n"; } else { $error = 'Memory limit of 1 KB exceeded for buffered query'; - if (strpos(sqlsrv_errors()[0]['message'], $error) === false) { + $errors = sqlsrv_errors(); + if (!empty($errors) && strpos($errors[0]['message'], $error) === false) { print_r(sqlsrv_errors()); } } From 31a7748fbaf18e072518297e07e0e2000e95eb44 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 29 Jul 2019 11:12:01 -0700 Subject: [PATCH 165/249] Updated dockerfile to use UB 18.04 and PHP 73 (#1016) --- Dockerfile-msphpsql | 48 +++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index ab289479f..4960b5843 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -1,15 +1,17 @@ -#Download base image ubuntu 16.04 +# Download base image ubuntu 18.04 -FROM ubuntu:16.04 +FROM ubuntu:18.04 # Update Ubuntu Software repository RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \ + apt-get install -y software-properties-common && \ + add-apt-repository ppa:ondrej/php -y && \ apt-get -y install \ - apt-transport-https \ + apt-transport-https \ apt-utils \ autoconf \ curl \ - libcurl3 \ + libcurl4 \ g++ \ gcc \ git \ @@ -17,18 +19,26 @@ RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \ libxml2-dev \ locales \ make \ - php7.0 \ - php7.0-dev \ + php7.3 \ + php7.3-dev \ python-pip \ re2c \ unixodbc-dev \ - unzip && apt-get clean - + unzip && apt-get clean && \ + curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \ + curl https://packages.microsoft.com/config/ubuntu/18.04/prod.list > /etc/apt/sources.list.d/mssql-release.list && \ + apt-get -y update && \ + export ACCEPT_EULA=Y && apt-get -y install msodbcsql17 mssql-tools && \ + update-alternatives --set php /usr/bin/php7.3 + ARG PHPSQLDIR=/REPO/msphpsql-dev ENV TEST_PHP_SQL_SERVER sql ENV TEST_PHP_SQL_UID sa ENV TEST_PHP_SQL_PWD Password123 +# update PATH after ODBC driver and tools are installed +ENV PATH="/opt/mssql-tools/bin:${PATH}" + # add locale iso-8859-1 RUN sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen RUN locale-gen en_US @@ -37,19 +47,12 @@ RUN locale-gen en_US RUN locale-gen en_US.UTF-8 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' -#install ODBC driver -RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - -RUN curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list - -RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql17 mssql-tools -ENV PATH="/opt/mssql-tools/bin:${PATH}" - -#install coveralls (upgrade both pip and requests first) +# install coveralls (upgrade both pip and requests first) RUN python -m pip install --upgrade pip RUN python -m pip install --upgrade requests RUN python -m pip install cpp-coveralls -#Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) +# Either Install git / download zip (One can see other strategies : https://ryanfb.github.io/etc/2015/07/29/git_strategies_for_docker.html ) #One option is to get source from zip file of repository. #another option is to copy source to build directory on image RUN mkdir -p $PHPSQLDIR @@ -59,14 +62,17 @@ WORKDIR $PHPSQLDIR/source/ RUN chmod +x ./packagize.sh RUN /bin/bash -c "./packagize.sh" -RUN echo "extension = pdo_sqlsrv.so" >> /etc/php/7.0/cli/conf.d/20-pdo_sqlsrv.ini -RUN echo "extension = sqlsrv.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` +RUN echo "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.3/mods-available/sqlsrv.ini +RUN echo "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.3/mods-available/pdo_sqlsrv.ini WORKDIR $PHPSQLDIR/source/sqlsrv -RUN phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install +RUN /usr/bin/phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install WORKDIR $PHPSQLDIR/source/pdo_sqlsrv -RUN phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install +RUN /usr/bin/phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install + +RUN phpenmod sqlsrv pdo_sqlsrv +RUN php --ri sqlsrv && php --ri pdo_sqlsrv # set name of sql server host to use WORKDIR $PHPSQLDIR/test/functional/pdo_sqlsrv From c1bc593011db92b25c5f1f2c1ab7eba778ea1d43 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 29 Jul 2019 11:12:21 -0700 Subject: [PATCH 166/249] Added survey results (#1017) --- media/os_development.PNG | Bin 0 -> 21753 bytes media/os_production.PNG | Bin 0 -> 22158 bytes media/php_versions.PNG | Bin 0 -> 11432 bytes media/sql_server.PNG | Bin 0 -> 17235 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 media/os_development.PNG create mode 100644 media/os_production.PNG create mode 100644 media/php_versions.PNG create mode 100644 media/sql_server.PNG diff --git a/media/os_development.PNG b/media/os_development.PNG new file mode 100644 index 0000000000000000000000000000000000000000..5bd527901fe02cc98ea93ced0f231a351c8f0e57 GIT binary patch literal 21753 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 Aw*UYD literal 0 HcmV?d00001 diff --git a/media/php_versions.PNG b/media/php_versions.PNG new file mode 100644 index 0000000000000000000000000000000000000000..98416fe81b028903d03f43422c9c075c7b95481d GIT binary patch literal 11432 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 Wpb1Zi;;$iNUocmuIH=4u% literal 0 HcmV?d00001 From bae6930930b8d14d0fb35cba53a4e3c337da1afe Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 8 Aug 2019 11:15:11 -0700 Subject: [PATCH 167/249] Updated ODBC driver 17.4 (#1019) --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b8be2c437..ae779335e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -81,10 +81,10 @@ install: } Else { $env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER; } - - echo Downloading MSODBCSQL 17.3 + - echo Downloading MSODBCSQL 17 # AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it - - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.3.1.1_x64.msi', 'c:\projects\msodbcsql_17.3.1.1_x64.msi') - - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.3.1.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.4.1.1_x64.msi', 'c:\projects\msodbcsql_17.4.1.1_x64.msi') + - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.4.1.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL - echo Checking the version of MSODBCSQL - reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server" - dir %WINDIR%\System32\msodbcsql*.dll From aa03782638ac988a1e88b0e259fa91fa7b15f215 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 14 Aug 2019 17:11:17 -0700 Subject: [PATCH 168/249] Modified output.py to take a new argument and travis yml to use include for coveralls (#1020) --- .travis.yml | 2 +- test/functional/output.py | 33 +++++++++++++++++++++++---------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8e9f1d30a..0618bebfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,7 +43,7 @@ script: - docker exec client bash -c 'for f in ./test/functional/pdo_sqlsrv/*.out; do ls $f 2>/dev/null; cat $f 2>/dev/null; done || true' - docker exec client python ./test/functional/setup/cleanup_dbs.py -dbname $SQLSRV_DBNAME - docker exec client python ./test/functional/setup/cleanup_dbs.py -dbname $PDOSQLSRV_DBNAME - - docker exec client coveralls -e ./source/shared/ --gcov-options '\-lp' + - docker exec client coveralls -i ./source/ -e ./source/shared/ -e ./test/ --gcov-options '\-lp' - docker stop client - docker ps -a diff --git a/test/functional/output.py b/test/functional/output.py index 94c3e5a82..4d19409ec 100644 --- a/test/functional/output.py +++ b/test/functional/output.py @@ -5,13 +5,14 @@ # Requirement of python 3.4 to execute this script and required result log file(s) # are in the same location # Run with command line without options required. Example: py output.py -# This script parse output of PHP Native Test +# This script parse output of PHP Test logs # ############################################################################################# import os import stat import re +import argparse # This module appends an entry to the tests list, may include the test title. # Input: search_pattern - pattern to look for in the line of the log file @@ -46,12 +47,14 @@ def get_test_entry(search_pattern, line, index, tests_list, get_title = False): tests_list.append(entry) # Extract individual test results from the log file and -# enter it in the nativeresult.xml file. -# Input: logfile - the log file -# number - the number for this xml file -def gen_XML(logfile, number): +# enter it in the xml report file. +# Input: logfile - the test log file +# number - the number for this xml file (applicable if using the default report name) +# logfilename - use the log file name for the xml output file Instead +def gen_XML(logfile, number, logfilename): print('================================================') - print("\n" + os.path.splitext(logfile)[0] + "\n" ) + filename = os.path.splitext(logfile)[0] + print("\n" + filename + "\n" ) tests_list = [] with open(os.path.dirname(os.path.realpath(__file__)) + os.sep + logfile) as f: @@ -70,8 +73,12 @@ def gen_XML(logfile, number): print(line) print('================================================') - # Generating the nativeresult.xml file. - file = open('nativeresult' + str(number) + '.xml', 'w') + # Generating the xml report. + if logfilename is True: + file = open(filename + '.xml', 'w') + else: + file = open('nativeresult' + str(number) + '.xml', 'w') + file.write('' + os.linesep) file.write('' + os.linesep) @@ -83,12 +90,18 @@ def gen_XML(logfile, number): # ----------------------- Main Function ----------------------- -# Display results on screen from result log file. +# Generate XML reports from test result log files. if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--LOGFILENAME', action='store_true', help="Generate XML files using log file names (default: False)") + + args = parser.parse_args() + logfilename = args.LOGFILENAME + num = 1 for f in os.listdir(os.path.dirname(os.path.realpath(__file__))): if f.endswith("log"): logfile = f - gen_XML(logfile, num) + gen_XML(logfile, num, logfilename) num = num + 1 From eb8ecbf6f4e32c180ab341bacf715a6eea7fce2f Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 20 Aug 2019 12:38:09 -0700 Subject: [PATCH 169/249] Used constants in memory stress tests for easier configuration (#1022) --- test/functional/output.py | 4 +- .../pdo_sqlsrv/PDO81_MemoryCheck.phpt | 60 ++++++++++++++++++- test/functional/sqlsrv/TC81_MemoryCheck.phpt | 8 ++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/test/functional/output.py b/test/functional/output.py index 4d19409ec..99543bc37 100644 --- a/test/functional/output.py +++ b/test/functional/output.py @@ -76,11 +76,13 @@ def gen_XML(logfile, number, logfilename): # Generating the xml report. if logfilename is True: file = open(filename + '.xml', 'w') + report = filename else: file = open('nativeresult' + str(number) + '.xml', 'w') + report = 'Native Tests' file.write('' + os.linesep) - file.write('' + os.linesep) + file.write('' + os.linesep) index = 1 for test in tests_list: diff --git a/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt b/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt index 5513290b6..1153d6ac4 100644 --- a/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt +++ b/test/functional/pdo_sqlsrv/PDO81_MemoryCheck.phpt @@ -11,6 +11,10 @@ PHPT_EXEC=true columnCount(); + $result = $stmt->fetchAll(); + $rowCount = count($result); + unset($result); + $stmt->closeCursor(); + unset($stmt); + if ($rowCount != $noRows) { + die("$rowCount rows retrieved instead of $noRows\n"); + } + break; + + case 5: // fetchObject + $stmt = ExecuteQueryEx($conn, $tsql, ($prepared ? false : true)); + $fldCount = $stmt->columnCount(); + while ($obj = $stmt->fetchObject()) { + unset($obj); + $rowCount++; + } + $stmt->closeCursor(); + unset($stmt); + if ($rowCount != $noRows) { + die("$rowCount rows retrieved instead of $noRows\n"); + } + break; + + case 6: // fetchColumn + $stmt = ExecuteQueryEx($conn, $tsql, ($prepared ? false : true)); + $fldCount = $stmt->columnCount(); + // Check for "false" to terminate because fetchColumn may return NULL + while (($result = $stmt->fetchColumn()) !== false) { + unset($result); + $rowCount++; + } + $stmt->closeCursor(); + unset($stmt); + if ($rowCount != $noRows) { + die("$rowCount rows retrieved instead of $noRows\n"); + } + break; + default: break; @@ -228,7 +286,7 @@ function Repro() { try { - MemCheck(20, 10, 15, 1, 3, 0); + MemCheck(_NUM_PASSES, _NUM_ROWS1, _NUM_ROWS2, 1, 6, 0); } catch (Exception $e) { diff --git a/test/functional/sqlsrv/TC81_MemoryCheck.phpt b/test/functional/sqlsrv/TC81_MemoryCheck.phpt index 5845da21f..40f84a6a2 100644 --- a/test/functional/sqlsrv/TC81_MemoryCheck.phpt +++ b/test/functional/sqlsrv/TC81_MemoryCheck.phpt @@ -7,11 +7,15 @@ emalloc (which only allocate memory in the memory space allocated for the PHP pr PHPT_EXEC=true --SKIPIF-- - + --FILE-- getMessage(); } From f5c0b63d04e8a21eeead5a268bc77e14c6a422ed Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 3 Sep 2019 12:34:41 -0700 Subject: [PATCH 170/249] Removed KSP related scripts and files (#1030) --- test/functional/setup/build_ksp.py | 122 ------------ test/functional/setup/ksp_app.c | 305 ----------------------------- test/functional/setup/myKSP.c | 132 ------------- test/functional/setup/run_ksp.py | 57 ------ 4 files changed, 616 deletions(-) delete mode 100644 test/functional/setup/build_ksp.py delete mode 100644 test/functional/setup/ksp_app.c delete mode 100644 test/functional/setup/myKSP.c delete mode 100644 test/functional/setup/run_ksp.py diff --git a/test/functional/setup/build_ksp.py b/test/functional/setup/build_ksp.py deleted file mode 100644 index fb524719e..000000000 --- a/test/functional/setup/build_ksp.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/python3 -######################################################################################### -# -# Description: This script builds a custom keystore provider and compiles the app that -# uses this KSP. Their names can be passed as arguments, but the outputs -# are always -# - myKSP.dll (myKSPx64.dll) / myKSP.so -# - ksp_app.exe / ksp_app -# -# Requirement: -# python 3.x -# myKSP.c (or any equivalent) -# ksp_app.c (or any equivalent) -# msodbcsql.h (odbc header file) -# -# Execution: Run with command line with optional options -# py build_ksp.py --KSP myKSP --APP ksp_app -# -############################################################################################# - -import sys -import os -import platform -import argparse - -# This creates a batch *filename*, which compiles a C program according to -# *command* and *arch* (either x86 or x64) -def create_batch_file(arch, filename, command): - root_dir = 'C:' + os.sep - vcvarsall = os.path.join(root_dir, "Program Files (x86)", "Microsoft Visual Studio 14.0", "VC", "vcvarsall.bat") - - try: - file = open(filename, 'w') - file.write('@ECHO OFF' + os.linesep) - if arch == 'x64': - file.write('@CALL "' + vcvarsall + '" amd64' + os.linesep) - else: - file.write('@CALL "' + vcvarsall + '" x86' + os.linesep) - - # compile the code - file.write('@CALL ' + command + os.linesep) - file.close() - except: - print('Cannot create ', filename) - -# This invokes the newly created batch file to compile the code, -# according to *arch* (either x86 or x64). The batch file will be -# removed afterwards -def compile_KSP_windows(arch, ksp_src): - output = 'myKSP' - if arch == 'x64': - output = output + arch + '.dll' - else: - output = output + '.dll' - - command = 'cl {0} /LD /MD /link /out:'.format(ksp_src) + output - batchfile = 'build_KSP.bat' - create_batch_file(arch, batchfile, command) - os.system(batchfile) - os.remove(batchfile) - -# This compiles myKSP.c -# -# In Windows, this will create batch files to compile two dll(s). -# Otherwise, this will compile the code and generate a .so file. -# -# Output: A custom keystore provider created -def compile_KSP(ksp_src): - print('Compiling ', ksp_src) - if platform.system() == 'Windows': - compile_KSP_windows('x64', ksp_src) - compile_KSP_windows('x86', ksp_src) - else: - os.system('gcc -fshort-wchar -fPIC -o myKSP.so -shared {0}'.format(ksp_src)) - -# This compiles ksp app, which assumes the existence of the .dll or the .so file. -# -# In Windows, a batch file is created in order to compile the code. -def configure_KSP(app_src): - print('Compiling ', app_src) - if platform.system() == 'Windows': - command = 'cl /MD {0} /link odbc32.lib /out:ksp_app.exe'.format(app_src) - batchfile = 'build_app.bat' - create_batch_file('x86', batchfile, command) - os.system(batchfile) - os.remove(batchfile) - else: - os.system('gcc -o ksp_app -fshort-wchar {0} -lodbc -ldl'.format(app_src)) - -################################### Main Function ################################### -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('-ksp', '--KSPSRC', default='myKSP.c', help='The source file of KSP (keystore provider)') - parser.add_argument('-app', '--APPSRC', default='ksp_app.c', help='The source file for the app that uses the KSP') - args = parser.parse_args() - - ksp_src = args.KSPSRC - app_src = args.APPSRC - header = 'msodbcsql.h' - - cwd = os.getcwd() - - # make sure all required source and header files are present - work_dir = os.path.dirname(os.path.realpath(__file__)) - os.chdir(work_dir) - - if not os.path.exists(os.path.join(work_dir, header)): - print('Error: {0} not found!'.format(header)) - exit(1) - if not os.path.exists(os.path.join(work_dir, ksp_src)): - print('Error: {0}.c not found!'.format(ksp_src)) - exit(1) - if not os.path.exists(os.path.join(work_dir, app_src)): - print('Error: {0}.c not found!'.format(app_src)) - exit(1) - - compile_KSP(ksp_src) - configure_KSP(app_src) - - os.chdir(cwd) - - \ No newline at end of file diff --git a/test/functional/setup/ksp_app.c b/test/functional/setup/ksp_app.c deleted file mode 100644 index 861078023..000000000 --- a/test/functional/setup/ksp_app.c +++ /dev/null @@ -1,305 +0,0 @@ -/****************************************************************************** - Example application for demonstration of custom keystore provider usage - - Windows: compile with cl /MD ksp_app.c /link odbc32.lib /out:ksp_app.exe - Linux/mac: compile with gcc -o ksp_app -fshort-wchar ksp_app.c -lodbc -ldl - - usage: kspapp connstr - - ******************************************************************************/ - -#define KSPNAME L"MyCustomKSPName" -#define PROV_ENCRYPT_KEY "LPKCWVD07N3RG98J0MBLG4H2" /* this can be any character string */ - -#include -#include -#ifdef _WIN32 -#include -#else -#define __stdcall -#include -#endif -#include -#include -#include "msodbcsql.h" - -enum job { - set_up = 0, - clean_up = 1 -}; - -/* Convenience functions */ - -int checkRC(SQLRETURN rc, char *msg, int ret, SQLHANDLE h, SQLSMALLINT ht) { - if (rc == SQL_ERROR) { - fprintf(stderr, "Error occurred upon %s\n", msg); - if (h) { - SQLSMALLINT i = 0; - SQLSMALLINT outlen = 0; - char errmsg[1024]; - while ((rc = SQLGetDiagField( - ht, h, ++i, SQL_DIAG_MESSAGE_TEXT, errmsg, sizeof(errmsg), &outlen)) == SQL_SUCCESS - || rc == SQL_SUCCESS_WITH_INFO) { - fprintf(stderr, "Err#%d: %s\n", i, errmsg); - } - } - if (ret) - exit(ret); - return 0; - } - else if (rc == SQL_SUCCESS_WITH_INFO && h) { - SQLSMALLINT i = 0; - SQLSMALLINT outlen = 0; - char errmsg[1024]; - printf("Success with info for %s:\n", msg); - while ((rc = SQLGetDiagField( - ht, h, ++i, SQL_DIAG_MESSAGE_TEXT, errmsg, sizeof(errmsg), &outlen)) == SQL_SUCCESS - || rc == SQL_SUCCESS_WITH_INFO) { - fprintf(stderr, "Msg#%d: %s\n", i, errmsg); - } - } - return 1; -} - -void postKspError(CEKEYSTORECONTEXT *ctx, const wchar_t *msg, ...) { - if (msg > (wchar_t*)65535) - wprintf(L"Provider emitted message: %s\n", msg); - else - wprintf(L"Provider emitted message ID %d\n", msg); -} - -int setKSPLibrary(SQLHSTMT stmt) { - unsigned char CEK[32]; - unsigned char *ECEK; - unsigned short ECEKlen; - unsigned char foundProv = 0; - int i; -#ifdef _WIN32 - HMODULE hProvLib; -#else - void *hProvLib; -#endif - CEKEYSTORECONTEXT ctx = {0}; - CEKEYSTOREPROVIDER **ppKsp, *pKsp; - int(__stdcall *pEncryptCEK)(CEKEYSTORECONTEXT *, errFunc *, unsigned char *, unsigned short, unsigned char **, unsigned short *); - - /* Load the provider library */ -#ifdef _WIN32 - if (!(hProvLib = LoadLibrary("myKSP.dll"))) { -#else - if (!(hProvLib = dlopen("./myKSP.so", RTLD_NOW))) { -#endif - fprintf(stderr, "Error loading KSP library\n"); - return 2; - } -#ifdef _WIN32 - if (!(ppKsp = (CEKEYSTOREPROVIDER**)GetProcAddress(hProvLib, "CEKeystoreProvider"))) { -#else - if (!(ppKsp = (CEKEYSTOREPROVIDER**)dlsym(hProvLib, "CEKeystoreProvider"))) { -#endif - fprintf(stderr, "The export CEKeystoreProvider was not found in the KSP library\n"); - return 3; - } - while (pKsp = *ppKsp++) { - if (!memcmp(KSPNAME, pKsp->Name, sizeof(KSPNAME))) { - foundProv = 1; - break; - } - } - if (! foundProv) { - fprintf(stderr, "Could not find provider in the library\n"); - return 4; - } - - if (pKsp->Init && !pKsp->Init(&ctx, postKspError)) { - fprintf(stderr, "Could not initialize provider\n"); - return 5; - } -#ifdef _WIN32 - if (!(pEncryptCEK = (LPVOID)GetProcAddress(hProvLib, "KeystoreEncrypt"))) { -#else - if (!(pEncryptCEK = dlsym(hProvLib, "KeystoreEncrypt"))) { -#endif - fprintf(stderr, "The export KeystoreEncrypt was not found in the KSP library\n"); - return 6; - } - if (!pKsp->Write) { - fprintf(stderr, "Provider does not support configuration\n"); - return 7; - } - - /* Configure the provider with the key */ - if (!pKsp->Write(&ctx, postKspError, PROV_ENCRYPT_KEY, strlen(PROV_ENCRYPT_KEY))) { - fprintf(stderr, "Error writing to KSP\n"); - return 8; - } - - /* Generate a CEK and encrypt it with the provider */ - srand(time(0) ^ getpid()); - for (i = 0; i < sizeof(CEK); i++) - CEK[i] = rand(); - - if (!pEncryptCEK(&ctx, postKspError, CEK, sizeof(CEK), &ECEK, &ECEKlen)) { - fprintf(stderr, "Error encrypting CEK\n"); - return 9; - } - - /* Create a CMK definition on the server */ - { - static char cmkSql[] = "CREATE COLUMN MASTER KEY CustomCMK WITH (" - "KEY_STORE_PROVIDER_NAME = 'MyCustomKSPName'," - "KEY_PATH = 'TheOneAndOnlyKey')"; - printf("Create CMK: %s\n", cmkSql); - SQLExecDirect(stmt, cmkSql, SQL_NTS); - } - - /* Create a CEK definition on the server */ - { - const char cekSqlBefore[] = "CREATE COLUMN ENCRYPTION KEY CustomCEK WITH VALUES (" - "COLUMN_MASTER_KEY = CustomCMK," - "ALGORITHM = 'none'," - "ENCRYPTED_VALUE = 0x"; - char *cekSql = malloc(sizeof(cekSqlBefore) + 2 * ECEKlen + 2); /* 1 for ')', 1 for null terminator */ - strcpy(cekSql, cekSqlBefore); - for (i = 0; i < ECEKlen; i++) - sprintf(cekSql + sizeof(cekSqlBefore) - 1 + 2 * i, "%02x", ECEK[i]); - strcat(cekSql, ")"); - printf("Create CEK: %s\n", cekSql); - SQLExecDirect(stmt, cekSql, SQL_NTS); - free(cekSql); -#ifdef _WIN32 - LocalFree(ECEK); -#else - free(ECEK); -#endif - } - -#ifdef _WIN32 - FreeLibrary(hProvLib); -#else - dlclose(hProvLib); -#endif - - return 0; -} - -void populateTestTable(SQLHDBC dbc, SQLHSTMT stmt) -{ - SQLRETURN rc; - int i, j; - - /* Create a table with encrypted columns */ - { - static char *tableSql = "CREATE TABLE CustomKSPTestTable (" - "c1 int," - "c2 varchar(255) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256')," - "c3 char(5) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256')," - "c4 date ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = CustomCEK, ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'))"; - printf("Create table: %s\n", tableSql); - SQLExecDirect(stmt, tableSql, SQL_NTS); - } - - /* Load provider into the ODBC Driver and configure it */ - { - unsigned char ksd[sizeof(CEKEYSTOREDATA) + sizeof(PROV_ENCRYPT_KEY) - 1]; - CEKEYSTOREDATA *pKsd = (CEKEYSTOREDATA*)ksd; - pKsd->name = L"MyCustomKSPName"; - pKsd->dataSize = sizeof(PROV_ENCRYPT_KEY) - 1; - memcpy(pKsd->data, PROV_ENCRYPT_KEY, sizeof(PROV_ENCRYPT_KEY) - 1); -#ifdef _WIN32 - rc = SQLSetConnectAttr(dbc, SQL_COPT_SS_CEKEYSTOREPROVIDER, "myKSP.dll", SQL_NTS); -#else - rc = SQLSetConnectAttr(dbc, SQL_COPT_SS_CEKEYSTOREPROVIDER, "./myKSP.so", SQL_NTS); -#endif - checkRC(rc, "Loading KSP into ODBC Driver", 7, dbc, SQL_HANDLE_DBC); - rc = SQLSetConnectAttr(dbc, SQL_COPT_SS_CEKEYSTOREDATA, (SQLPOINTER)pKsd, SQL_IS_POINTER); - checkRC(rc, "Configuring the KSP", 7, dbc, SQL_HANDLE_DBC); - } - - /* Insert some data */ - { - int c1; - char c2[256]; - char c3[6]; - SQL_DATE_STRUCT date; - SQLLEN cbdate; - rc = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &c1, 0, 0); - checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); - rc = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 255, 0, c2, 255, 0); - checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); - rc = SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 5, 0, c3, 5, 0); - checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); - checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); - cbdate = sizeof(SQL_DATE_STRUCT); - rc = SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_TYPE_DATE, SQL_TYPE_DATE, 10, 0, &date, 0, &cbdate); - checkRC(rc, "Binding parameters for insert", 9, stmt, SQL_HANDLE_STMT); - - date.year = 2017; - date.month = 8; - for (i = 0; i < 10; i++) { - date.day = i + 10; - - c1 = i * 10 + i + 1; - sprintf(c2, "Sample data %d for column 2", i); - for (j = 0; j < 3; j++) { - c3[j] = 'a' + i + j; - } - c3[3] = '\0'; - rc = SQLExecDirect(stmt, "INSERT INTO CustomKSPTestTable (c1, c2, c3, c4) values (?, ?, ?, ?)", SQL_NTS); - checkRC(rc, "Inserting rows query", 10, stmt, SQL_HANDLE_STMT); - } - printf("(Encrypted) data has been inserted into CustomKSPTestTable. You may inspect the data now.\n"); - } - -} - -int main(int argc, char **argv) { - char sqlbuf[1024]; - SQLHENV env; - SQLHDBC dbc; - SQLHSTMT stmt; - SQLRETURN rc; - int i; - char connStr[1024]; - enum job task; - - if (argc < 6) { - fprintf(stderr, "usage: kspapp job server database uid pwd\n"); - return 1; - } - - task = atoi(argv[1]); - - sprintf(connStr, "DRIVER={ODBC Driver 17 for SQL Server};SERVER=%s;ColumnEncryption=Enabled;DATABASE=%s;UID=%s;PWD=%s", argv[2], argv[3], argv[4], argv[5]); - - /* Connect to Server */ - rc = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &env); - checkRC(rc, "allocating environment handle", 2, 0, 0); - rc = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0); - checkRC(rc, "setting ODBC version to 3.0", 3, env, SQL_HANDLE_ENV); - rc = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc); - checkRC(rc, "allocating connection handle", 4, env, SQL_HANDLE_ENV); - rc = SQLDriverConnect(dbc, 0, connStr, strlen(connStr), NULL, 0, NULL, SQL_DRIVER_NOPROMPT); - checkRC(rc, "connecting to data source", 5, dbc, SQL_HANDLE_DBC); - rc = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt); - checkRC(rc, "allocating statement handle", 6, dbc, SQL_HANDLE_DBC); - - if (task == set_up) { - printf("Setting up KSP...\n"); - setKSPLibrary(stmt); - populateTestTable(dbc, stmt); - } - else if (task == clean_up) { - printf("Cleaning up KSP...\n"); - - SQLExecDirect(stmt, "DROP TABLE CustomKSPTestTable", SQL_NTS); - SQLExecDirect(stmt, "DROP COLUMN ENCRYPTION KEY CustomCEK", SQL_NTS); - SQLExecDirect(stmt, "DROP COLUMN MASTER KEY CustomCMK", SQL_NTS); - printf("Removed table, CEK, and CMK\n"); - } - - SQLDisconnect(dbc); - SQLFreeHandle(SQL_HANDLE_DBC, dbc); - SQLFreeHandle(SQL_HANDLE_ENV, env); - return 0; -} diff --git a/test/functional/setup/myKSP.c b/test/functional/setup/myKSP.c deleted file mode 100644 index d6188842b..000000000 --- a/test/functional/setup/myKSP.c +++ /dev/null @@ -1,132 +0,0 @@ -/****************************************************************************** -Custom Keystore Provider Example - -Windows: compile with cl myKSP.c /LD /MD /link /out:myKSP.dll -Linux/mac: compile with gcc -fshort-wchar -fPIC -o myKSP.so -shared myKSP.c - -******************************************************************************/ - -#ifdef _WIN32 -#include -#else -#define __stdcall -#endif - -#define DEBUG 0 - -#include -#include -#include -#include -#include -#include "msodbcsql.h" - -int wcscmp_short(wchar_t *s1, wchar_t *s2) { - while(*s1 && *s2 && *s1 == *s2) - s1++, s2++; - return *s1 - *s2; -} - -int __stdcall KeystoreInit(CEKEYSTORECONTEXT *ctx, errFunc *onError) { - if (DEBUG) - printf("KSP Init() function called\n"); - return 1; -} - -static unsigned char *g_encryptKey; -static unsigned int g_encryptKeyLen; - -int __stdcall KeystoreWrite(CEKEYSTORECONTEXT *ctx, errFunc *onError, void *data, unsigned int len) { - if (DEBUG) - printf("KSP Write() function called (%d bytes)\n", len); - if (len) { - if (g_encryptKey) - free(g_encryptKey); - g_encryptKey = malloc(len); - if (!g_encryptKey) { - onError(ctx, L"Memory Allocation Error"); - return 0; - } - memcpy(g_encryptKey, data, len); - g_encryptKeyLen = len; - } - return 1; -} - -// Very simple "encryption" scheme - rotating XOR with the key -int __stdcall KeystoreDecrypt(CEKEYSTORECONTEXT *ctx, errFunc *onError, const wchar_t *keyPath, const wchar_t *alg, unsigned char *ecek, unsigned short ecekLen, unsigned char **cekOut, unsigned short *cekLen) { - unsigned int i; - if (DEBUG) - printf("KSP Decrypt() function called (keypath=%S alg=%S ecekLen=%u)\n", keyPath, alg, ecekLen); - if (wcscmp_short(keyPath, L"TheOneAndOnlyKey")) { - onError(ctx, L"Invalid key path"); - return 0; - } - if (wcscmp_short(alg, L"none")) { - onError(ctx, L"Invalid algorithm"); - return 0; - } - if (!g_encryptKey) { - onError(ctx, L"Keystore provider not initialized with key"); - return 0; - } -#ifndef _WIN32 - *cekOut = malloc(ecekLen); -#else - *cekOut = LocalAlloc(LMEM_FIXED, ecekLen); -#endif - if (!*cekOut) { - onError(ctx, L"Memory Allocation Error"); - return 0; - } - *cekLen = ecekLen; - for (i = 0; i < ecekLen; i++) - (*cekOut)[i] = ecek[i] ^ g_encryptKey[i % g_encryptKeyLen]; - return 1; -} - -// Note that in the provider interface, this function would be referenced via the CEKEYSTOREPROVIDER -// structure. However, that does not preclude keystore providers from exporting their own functions, -// as illustrated by this example where the encryption is performed via a separate function (with a -// different prototype than the one in the KSP interface.) -#ifdef _WIN32 -__declspec(dllexport) -#endif -int KeystoreEncrypt(CEKEYSTORECONTEXT *ctx, errFunc *onError, - unsigned char *cek, unsigned short cekLen, - unsigned char **ecekOut, unsigned short *ecekLen) { - unsigned int i; - - if (DEBUG) - printf("KSP Encrypt() function called (cekLen=%u)\n", cekLen); - if (!g_encryptKey) { - onError(ctx, L"Keystore provider not initialized with key"); - return 0; - } - *ecekOut = malloc(cekLen); - if (!*ecekOut) { - onError(ctx, L"Memory Allocation Error"); - return 0; - } - *ecekLen = cekLen; - for (i = 0; i < cekLen; i++) - (*ecekOut)[i] = cek[i] ^ g_encryptKey[i % g_encryptKeyLen]; - return 1; -} - -CEKEYSTOREPROVIDER MyCustomKSPName_desc = { - L"MyCustomKSPName", - KeystoreInit, - 0, - KeystoreWrite, - KeystoreDecrypt, - 0 -}; - -#ifdef _WIN32 -__declspec(dllexport) -#endif -CEKEYSTOREPROVIDER *CEKeystoreProvider[] = { - &MyCustomKSPName_desc, - 0 -}; \ No newline at end of file diff --git a/test/functional/setup/run_ksp.py b/test/functional/setup/run_ksp.py deleted file mode 100644 index 0d0ff897f..000000000 --- a/test/functional/setup/run_ksp.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/python3 -######################################################################################### -# -# Description: This script assumes the existence of the ksp_app executable and will -# invoke it to create / remove the Column Master Key, the Column Encryption key, -# and the table [CustomKSPTestTable] in the test database. -# -# Requirement: -# python 3.x -# ksp_app executable -# -# Execution: Run with command line with required options -# py run_ksp.py --SERVER=server --DBNAME=database --UID=uid --PWD=pwd -# py run_ksp.py --SERVER=server --DBNAME=database --UID=uid --PWD=pwd --REMOVE -# -############################################################################################# - -import sys -import os -import platform -import argparse - -################################### Main Function ################################### -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('-server', '--SERVER', required=True, help='SQL Server') - parser.add_argument('-dbname', '--DBNAME', required=True, help='Name of an existing database') - parser.add_argument('-uid', '--UID', required=True, help='User name') - parser.add_argument('-pwd', '--PWD', required=True, help='User password') - parser.add_argument('-remove', '--REMOVE', action='store_true', help='Clean up KSP related data, false by default') - - args = parser.parse_args() - - app_name = 'ksp_app' - cwd = os.getcwd() - - # first check if the ksp app is present - work_dir = os.path.dirname(os.path.realpath(__file__)) - os.chdir(work_dir) - - if platform.system() == 'Windows': - path = os.path.join(work_dir, app_name + '.exe') - executable = app_name - else: - path = os.path.join(work_dir, app_name) - executable = './' + app_name - - if not os.path.exists(path): - print('Error: {0} not found!'.format(path)) - exit(1) - - if args.REMOVE: - os.system('{0} 1 {1} {2} {3} {4}'.format(executable, args.SERVER, args.DBNAME, args.UID, args.PWD)) - else: - os.system('{0} 0 {1} {2} {3} {4}'.format(executable, args.SERVER, args.DBNAME, args.UID, args.PWD)) - - os.chdir(cwd) From 2792ece85d4cca238ba4b83eecb5286506412ff4 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 3 Sep 2019 12:43:10 -0700 Subject: [PATCH 171/249] Updated version to 5.7.0 preview (#1029) --- source/pdo_sqlsrv/config.m4 | 2 +- source/pdo_sqlsrv/config.w32 | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- source/pdo_sqlsrv/pdo_init.cpp | 2 +- source/pdo_sqlsrv/pdo_parser.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 2 +- source/pdo_sqlsrv/template.rc | 2 +- source/shared/FormattedPrint.cpp | 2 +- source/shared/FormattedPrint.h | 2 +- source/shared/StringFunctions.cpp | 2 +- source/shared/StringFunctions.h | 2 +- source/shared/core_conn.cpp | 2 +- source/shared/core_init.cpp | 2 +- source/shared/core_results.cpp | 2 +- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 2 +- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 2 +- source/shared/globalization.h | 2 +- source/shared/interlockedatomic.h | 2 +- source/shared/interlockedatomic_gcc.h | 2 +- source/shared/interlockedslist.h | 2 +- source/shared/localization.hpp | 2 +- source/shared/localizationimpl.cpp | 2 +- source/shared/msodbcsql.h | 2 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 8 ++++---- source/shared/xplat.h | 2 +- source/shared/xplat_intsafe.h | 2 +- source/shared/xplat_winerror.h | 2 +- source/shared/xplat_winnls.h | 2 +- source/sqlsrv/config.m4 | 2 +- source/sqlsrv/config.w32 | 2 +- source/sqlsrv/conn.cpp | 2 +- source/sqlsrv/init.cpp | 2 +- source/sqlsrv/php_sqlsrv.h | 2 +- source/sqlsrv/php_sqlsrv_int.h | 2 +- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 2 +- 44 files changed, 47 insertions(+), 47 deletions(-) diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index 68dbea0e3..75961ad78 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.6 for PHP for SQL Server +dnl Microsoft Drivers 5.7 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index 95bff567b..1d37593ba 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 07c4ca51a..2a0ea4791 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index e913ca8f1..8f374093d 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index edd333e46..6a96ace21 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 909e350e9..71c426909 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index b64477404..6cfb43acf 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 3e013f95c..a6403219a 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 841b9e43b..6a616da05 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -6,7 +6,7 @@ // // Contents: Internal declarations for the extension // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index fdbeaa576..2db6a2e94 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index b664159c5..185489d7e 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -6,7 +6,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index 907b16479..32f65ebbf 100644 --- a/source/shared/FormattedPrint.h +++ b/source/shared/FormattedPrint.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp index 6aac5a051..ac0a82398 100644 --- a/source/shared/StringFunctions.cpp +++ b/source/shared/StringFunctions.cpp @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.h b/source/shared/StringFunctions.h index a2bfceaa9..0eb011c3e 100644 --- a/source/shared/StringFunctions.h +++ b/source/shared/StringFunctions.h @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 76a870802..cea5a3ddc 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index 504a145b6..63d08ce98 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 3d4caaabf..1fbe005f1 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 1750a00e1..a9e281c47 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index f1902bb75..13bb2e5e1 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index b44d514c2..9ca8e8c06 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 92e9ba1dc..9f2e0eeaa 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/globalization.h b/source/shared/globalization.h index 4ddccc52b..f7e1afd37 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index f46e2b1df..12456143d 100644 --- a/source/shared/interlockedatomic.h +++ b/source/shared/interlockedatomic.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h index 8a1f3732c..171c1ad2d 100644 --- a/source/shared/interlockedatomic_gcc.h +++ b/source/shared/interlockedatomic_gcc.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h index cd71452a6..4bc04c1f7 100644 --- a/source/shared/interlockedslist.h +++ b/source/shared/interlockedslist.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, singly // linked list. // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index a572ab021..cb6249511 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 8f7dd402b..0e7338268 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -5,7 +5,7 @@ // Must be included in one c/cpp file per binary // A build error will occur if this inclusion policy is not followed // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index 3a1555e81..b0496f5d8 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -20,7 +20,7 @@ // pecuniary loss) arising out of the use of or inability to use // this SDK, even if Microsoft has been advised of the possibility // of such damages. -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index 053fb199c..7d4efb172 100644 --- a/source/shared/sal_def.h +++ b/source/shared/sal_def.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index 390d03cc7..82e33eba4 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -1,7 +1,7 @@ //--------------------------------------------------------------------------------------------------------------------------------- // File: typedefs_for_linux.h // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/version.h b/source/shared/version.h index 7424d9001..6bc7e7969 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -4,7 +4,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -26,12 +26,12 @@ // Increase Minor with backward compatible new functionalities and API changes. // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 -#define SQLVERSION_MINOR 6 -#define SQLVERSION_PATCH 1 +#define SQLVERSION_MINOR 7 +#define SQLVERSION_PATCH 0 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 -#define PREVIEW 0 +#define PREVIEW 1 #define SEMVER_PRERELEASE // Semantic versioning build metadata, build meta data is not counted in precedence order. diff --git a/source/shared/xplat.h b/source/shared/xplat.h index 1b1956391..a32cdda0e 100644 --- a/source/shared/xplat.h +++ b/source/shared/xplat.h @@ -3,7 +3,7 @@ // // Contents: include for definition of Windows types for non-Windows platforms // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h index 91db26523..90d2f1509 100644 --- a/source/shared/xplat_intsafe.h +++ b/source/shared/xplat_intsafe.h @@ -4,7 +4,7 @@ // Contents: This module defines helper functions to prevent // integer overflow bugs. // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h index 34e7ffb50..0ebd1f2a5 100644 --- a/source/shared/xplat_winerror.h +++ b/source/shared/xplat_winerror.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h index bc81cf58c..22bfedc90 100644 --- a/source/shared/xplat_winnls.h +++ b/source/shared/xplat_winnls.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index 20d4d8589..a8a4e89d5 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.6 for PHP for SQL Server +dnl Microsoft Drivers 5.7 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 887d1e775..33cac9bd2 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 1275e7234..5500da231 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 93d7003a7..2b394862f 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 4de7f5c9a..36bb9a959 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index bd555fe10..3ebb179fe 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 69f44de5d..bbb011909 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index bbe471322..e95041ae6 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 5f69c5a32..9e545b6aa 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.6 for PHP for SQL Server +// Microsoft Drivers 5.7 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License From fdf029d2694728a9039c34ffecfba2ee94e6cc9e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 4 Sep 2019 15:13:43 -0700 Subject: [PATCH 172/249] Change log for 5.7.0 (#1028) --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ Linux-mac-install.md | 32 +++++++++++++++++--------------- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7916f19b..382717716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.7.0-preview - 2019-09-05 +Updated PECL release packages. Here is the list of updates: + +### Added +- Support for PHP 7.4 RC 1 +- Support for Linux Ubuntu 19.04 and Debian 10 +- Feature Request [#929](https://github.com/microsoft/msphpsql/issues/929) - new [Language option](https://github.com/microsoft/msphpsql/wiki/Features#language) - Pull Request [#930](https://github.com/microsoft/msphpsql/pull/930) +- [Data Classification Sensitivity Metadata Retrieval](https://github.com/microsoft/msphpsql/wiki/Features#data-classification-sensitivity-metadata), which requires [MS ODBC Driver 17.2+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) and [SQL Server 2019 release candidate](https://docs.microsoft.com/sql/sql-server/sql-server-ver15-release-notes?view=sqlallproducts-allversions#-release-candidate-rc) + +### Removed +- Dropped support for Ubuntu 18.10 + +### Fixed +- Issue [#570](https://github.com/microsoft/msphpsql/issues/570) - Fixed fetching varbinary data using client buffer with sqlsrv +- Pull Request [#972](https://github.com/microsoft/msphpsql/pull/972) - Removed redundant calls to retrieve the number of columns or rows in the current query result set +- Pull Request [#978](https://github.com/microsoft/msphpsql/pull/978) - PDO_SQLSRV implementation of PDO::getColumnMeta now references cached metadata rather than making an ODBC call every time +- Pull Request [#979](https://github.com/microsoft/msphpsql/pull/979) - Added support for data classification Sensitivity metadata retrieval +- Pull Request [#985](https://github.com/microsoft/msphpsql/pull/985) - Fixed memory issues with data classification data structures +- Issue [#432](https://github.com/microsoft/msphpsql/issues/432) - Having any invalid UTF-8 name in the connection string will no longer invoke misleading error messages +- Issue [#909](https://github.com/microsoft/msphpsql/issues/909) - Fixed potential exception with locale issues in macOS +- Pull Request [#992](https://github.com/microsoft/msphpsql/pull/992) - Produced the correct error when requesting Data Classification metadata with ODBC drivers prior to 17 +- Pull Request [#1001](https://github.com/microsoft/msphpsql/pull/1001) - Fixed compilation issue with PHP 7.4 alpha +- Pull Request [#1004](https://github.com/microsoft/msphpsql/pull/1004) - Fixed another compilation issue with PHP 7.4 alpha +- Pull Request [#1008](https://github.com/microsoft/msphpsql/pull/1008) - Improved data caching when fetching datetime objects +- Pull Request [#1011](https://github.com/microsoft/msphpsql/pull/1011) - Fixed a potential buffer overflow when parsing for escaped braces in the connection string +- Pull Request [#1015](https://github.com/microsoft/msphpsql/pull/1015) - Fixed compilation issues and addressed various memory leaks detected by PHP 7.4 beta 1 + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Data Classification metadata retrieval is not compatible with ODBC Driver 17.4.1 +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) +- With ColumnEncryption enabled, calling stored procedures with XML parameters does not work (Issue [#674](https://github.com/Microsoft/msphpsql/issues/674)) + ## 5.6.1 - 2019-03-19 Updated PECL release packages. Here is the list of updates: diff --git a/Linux-mac-install.md b/Linux-mac-install.md index e34f07b06..2a96c89c5 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -5,13 +5,13 @@ These instructions install PHP 7.3 by default. Note that some supported Linux di ## Contents of this page: -- [Installing the drivers on Ubuntu 16.04, 18.04, and 18.10](#installing-the-drivers-on-ubuntu-1604-1804-and-1810) +- [Installing the drivers on Ubuntu 16.04, 18.04, and 19.04](#installing-the-drivers-on-ubuntu-1604-1804-and-1904) - [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) -- [Installing the drivers on Debian 8 and 9](#installing-the-drivers-on-debian-8-and-9) +- [Installing the drivers on Debian 8, 9 and 10](#installing-the-drivers-on-debian-8-9-and-10) - [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15) - [Installing the drivers on macOS Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-sierra-high-sierra-and-mojave) -## Installing the drivers on Ubuntu 16.04, 18.04, and 18.10 +## Installing the drivers on Ubuntu 16.04, 18.04, and 19.04 > [!NOTE] > To install PHP 7.1 or 7.2, replace 7.3 with 7.1 or 7.2 in the following commands. @@ -31,10 +31,13 @@ Install the ODBC driver for Ubuntu by following the instructions on the [Linux a sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv sudo su -echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/30-pdo_sqlsrv.ini -echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/20-sqlsrv.ini +printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.3/mods-available/sqlsrv.ini +printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.3/mods-available/pdo_sqlsrv.ini exit +sudo phpenmod -v 7.3 sqlsrv pdo_sqlsrv ``` +If there is only one PHP version in the system then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. + ### Step 4. Install Apache and configure driver loading ``` sudo su @@ -42,8 +45,6 @@ apt-get install libapache2-mod-php7.3 apache2 a2dismod mpm_event a2enmod mpm_prefork a2enmod php7.3 -echo "extension=pdo_sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/30-pdo_sqlsrv.ini -echo "extension=sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/20-sqlsrv.ini exit ``` ### Step 5. Restart Apache and test the sample script @@ -91,8 +92,8 @@ exit An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually (similar steps for pdo_sqlsrv): ``` pecl download sqlsrv -tar xvzf sqlsrv-5.6.1.tgz -cd sqlsrv-5.6.1/ +tar xvzf sqlsrv-5.7.0.tgz +cd sqlsrv-5.7.0/ phpize ./configure --with-php-config=/usr/bin/php-config make @@ -116,7 +117,7 @@ sudo apachectl restart ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on Debian 8 and 9 +## Installing the drivers on Debian 8, 9 and 10 > [!NOTE] > To install PHP 7.1 or 7.2, replace 7.3 in the following commands with 7.1 or 7.2. @@ -145,10 +146,13 @@ locale-gen sudo pecl install sqlsrv sudo pecl install pdo_sqlsrv sudo su -echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/30-pdo_sqlsrv.ini -echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/20-sqlsrv.ini +printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.3/mods-available/sqlsrv.ini +printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.3/mods-available/pdo_sqlsrv.ini exit +sudo phpenmod -v 7.3 sqlsrv pdo_sqlsrv ``` +If there is only one PHP version in the system then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. + ### Step 4. Install Apache and configure driver loading ``` sudo su @@ -156,8 +160,6 @@ apt-get install libapache2-mod-php7.3 apache2 a2dismod mpm_event a2enmod mpm_prefork a2enmod php7.3 -echo "extension=pdo_sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/30-pdo_sqlsrv.ini -echo "extension=sqlsrv.so" >> /etc/php/7.3/apache2/conf.d/20-sqlsrv.ini ``` ### Step 5. Restart Apache and test the sample script ``` @@ -168,7 +170,7 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Suse 12 and 15 > [!NOTE] -> In the following instructions, replace with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15 or SLE_15_SP1, and similarly for other versions. Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or to `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse. +> In the following instructions, replace with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15 or SLE_15_SP1. For Suse 12, use SLE_12_SP4 (or above if applicable). Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or to `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse. > [!NOTE] > Packages for PHP 7.3 are not available for Suse 12. From 255752066dc4180645b07cbdff718899b302df1a Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 18 Sep 2019 07:49:14 -0700 Subject: [PATCH 173/249] Modified how drivers handle query timeout settings (#1037) --- source/pdo_sqlsrv/pdo_dbh.cpp | 7 - source/pdo_sqlsrv/pdo_stmt.cpp | 13 ++ source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 4 + source/shared/core_sqlsrv.h | 3 +- source/shared/core_stmt.cpp | 40 +--- source/sqlsrv/php_sqlsrv_int.h | 3 + source/sqlsrv/stmt.cpp | 23 ++ .../pdo_sqlsrv/pdo_1027_query_timeout.phpt | 198 ++++++++++++++++++ .../sqlsrv/srv_1027_query_timeout.phpt | 120 +++++++++++ 9 files changed, 371 insertions(+), 40 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1027_query_timeout.phpt create mode 100644 test/functional/sqlsrv/srv_1027_query_timeout.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 2a0ea4791..15aec1173 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -720,13 +720,6 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size; } - // if the user didn't set anything in the prepare options, then set the query timeout - // to the value set on the connection. - if(( driver_stmt->query_timeout == QUERY_TIMEOUT_INVALID ) && ( driver_dbh->query_timeout != QUERY_TIMEOUT_INVALID )) { - - core_sqlsrv_set_query_timeout( driver_stmt, driver_dbh->query_timeout TSRMLS_CC ); - } - // rewrite named parameters in the query to positional parameters if we aren't letting PDO do the // parameter substitution for us if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 71c426909..0cd562d9c 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -580,6 +580,11 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) query_len = static_cast(stmt->active_query_stringlen); } + // The query timeout setting is inherited from the corresponding connection attribute, but + // the user may have changed the query timeout setting again before this via + // PDOStatement::setAttribute() + driver_stmt->set_query_timeout(); + SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); if ( execReturn == SQL_NO_DATA ) { @@ -1503,3 +1508,11 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, return sqlsrv_phptype; } +void pdo_sqlsrv_stmt::set_query_timeout() +{ + if (query_timeout == QUERY_TIMEOUT_INVALID || query_timeout < 0) { + return; + } + + core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast((SQLLEN)query_timeout), SQL_IS_UINTEGER TSRMLS_CC); +} \ No newline at end of file diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 6a616da05..e0f5f2201 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -246,6 +246,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { fetch_datetime = db->fetch_datetime; format_decimals = db->format_decimals; decimal_places = db->decimal_places; + query_timeout = db->query_timeout; } virtual ~pdo_sqlsrv_stmt( void ); @@ -254,6 +255,9 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); + // driver specific way to set query timeout + virtual void set_query_timeout(); + bool direct_query; // flag set if the query should be executed directly or prepared const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters size_t direct_query_subst_string_len; // length of query string used for direct queries diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index a9e281c47..f468a7b6b 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1558,6 +1558,8 @@ struct sqlsrv_stmt : public sqlsrv_context { // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ) = 0; + // driver specific way to set query timeout + virtual void set_query_timeout() = 0; }; // *** field metadata struct *** @@ -1616,7 +1618,6 @@ bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool finalize_output_params = true, _In_ bool throw_on_errors = true ); void core_sqlsrv_post_param( _Inout_ sqlsrv_stmt* stmt, _In_ zend_ulong paramno, zval* param_z TSRMLS_DC ); void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout TSRMLS_DC ); void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z TSRMLS_DC ); void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC ); bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 13bb2e5e1..be1b00ec6 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -322,6 +322,11 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm } ZEND_HASH_FOREACH_END(); } + // The query timeout setting is inherited from the corresponding connection attribute, but + // the user may override that the query timeout setting using the statement option. + // In any case, set query timeout using the latest value + stmt->set_query_timeout(); + return_stmt = stmt; stmt.transferred(); } @@ -1361,7 +1366,7 @@ void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLE } -// Overloaded. Extracts the long value and calls the core_sqlsrv_set_query_timeout +// Extracts the long value and calls the core_sqlsrv_set_query_timeout // which accepts timeout parameter as a long. If the zval is not of type long // than throws error. void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z TSRMLS_DC ) @@ -1375,37 +1380,8 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* val THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_QUERY_TIMEOUT_VALUE, Z_STRVAL_P( value_z ) ); } - core_sqlsrv_set_query_timeout( stmt, static_cast( Z_LVAL_P( value_z )) TSRMLS_CC ); - } - catch( core::CoreException& ) { - throw; - } -} - -// Overloaded. Accepts the timeout as a long. -void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout TSRMLS_DC ) -{ - try { - - DEBUG_SQLSRV_ASSERT( timeout >= 0 , "core_sqlsrv_set_query_timeout: The value of query timeout cannot be less than 0." ); - - // set the statement attribute - core::SQLSetStmtAttr( stmt, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)timeout ), SQL_IS_UINTEGER TSRMLS_CC ); - - // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which - // is represented by -1. - int lock_timeout = (( timeout == 0 ) ? -1 : timeout * 1000 /*convert to milliseconds*/ ); - - // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[32] = {'\0'}; - - int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout ); - SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), - "stmt_option_query_timeout: snprintf failed. Shouldn't ever fail." ); - - core::SQLExecDirect( stmt, lock_timeout_sql TSRMLS_CC ); - - stmt->query_timeout = timeout; + // Save the query timeout setting for processing later + stmt->query_timeout = static_cast(Z_LVAL_P(value_z)); } catch( core::CoreException& ) { throw; diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index 3ebb179fe..42148b03d 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -124,6 +124,9 @@ struct ss_sqlsrv_stmt : public sqlsrv_stmt { // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); + // driver specific way to set query timeout + virtual void set_query_timeout(); + bool prepared; // whether the statement has been prepared yet (used for error messages) zend_ulong conn_index; // index into the connection hash that contains this statement structure zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index bbb011909..b67af51a7 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -267,6 +267,29 @@ sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _ return ss_phptype; } +void ss_sqlsrv_stmt::set_query_timeout() +{ + if (query_timeout == QUERY_TIMEOUT_INVALID || query_timeout < 0) { + return; + } + + // set the statement attribute + core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)query_timeout ), SQL_IS_UINTEGER TSRMLS_CC ); + + // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which + // is represented by -1. + int lock_timeout = (( query_timeout == 0 ) ? -1 : query_timeout * 1000 /*convert to milliseconds*/ ); + + // set the LOCK_TIMEOUT on the server. + char lock_timeout_sql[32] = {'\0'}; + + int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout ); + SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), + "stmt_option_query_timeout: snprintf failed. Shouldn't ever fail." ); + + core::SQLExecDirect(this, lock_timeout_sql TSRMLS_CC ); +} + // statement specific parameter proccessing. Uses the generic function specialised to return a statement // resource. #define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ diff --git a/test/functional/pdo_sqlsrv/pdo_1027_query_timeout.phpt b/test/functional/pdo_sqlsrv/pdo_1027_query_timeout.phpt new file mode 100644 index 000000000..703ae7612 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1027_query_timeout.phpt @@ -0,0 +1,198 @@ +--TEST-- +GitHub issue 1027 - PDO::SQLSRV_ATTR_QUERY_TIMEOUT had no effect on PDO::exec() +--DESCRIPTION-- +This test verifies that setting PDO::SQLSRV_ATTR_QUERY_TIMEOUT correctly should affect PDO::exec() as in the case for PDO::prepare() (as statement attribute or option). +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $timeout); + $sql = 'SELECT 1'; + $stmt = $conn->prepare($sql, $options); + } else { + trace("connection attribute expects error: $invalid\n"); + $conn->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, $timeout); + } + } catch (PDOException $e) { + if (!fnmatch($invalid, $e->getMessage())) { + echo "Unexpected error returned setting invalid $timeout for SQLSRV_ATTR_QUERY_TIMEOUT\n"; + var_dump($e->getMessage()); + } + } +} + +function testErrors($conn) +{ + testTimeoutAttribute($conn, 1.8); + testTimeoutAttribute($conn, 'xyz'); + testTimeoutAttribute($conn, -99, true); + testTimeoutAttribute($conn, 'abc', true); +} + +function checkTimeElapsed($message, $t0, $t1, $expectedDelay) +{ + $elapsed = $t1 - $t0; + $diff = abs($elapsed - $expectedDelay); + $leeway = 1.0; + $missed = ($diff > $leeway); + trace("$message $elapsed secs elapsed\n"); + + if ($missed) { + echo $message; + echo "Expected $expectedDelay but $elapsed secs elapsed\n"; + } +} + +function connectionTest($timeout, $asAttribute) +{ + global $query, $error; + $keyword = ''; + + if ($asAttribute) { + $conn = connect($keyword); + $conn->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, $timeout); + } else { + $options = array(PDO::SQLSRV_ATTR_QUERY_TIMEOUT => $timeout); + $conn = connect($keyword, $options); + } + + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // if timeout is 0 it means no timeout + $delay = ($timeout > 0) ? $timeout : _DELAY; + + $result = null; + $t0 = microtime(true); + + try { + $result = $conn->exec($query); + if ($timeout > 0) { + echo "connectionTest $timeout, $asAttribute: "; + echo "this should have timed out!\n"; + } + } catch (PDOException $e) { + if (!fnmatch($error, $e->getMessage())) { + echo "Connection test error expected $timeout, $asAttribute:\n"; + var_dump($e->getMessage()); + } + } + + $t1 = microtime(true); + checkTimeElapsed("connectionTest ($timeout, $asAttribute): ", $t0, $t1, $delay); + + return $conn; +} + +function queryTest($conn, $timeout) +{ + global $query, $error; + + // if timeout is 0 it means no timeout + $delay = ($timeout > 0) ? $timeout : _DELAY; + + $t0 = microtime(true); + try { + $conn->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, $timeout); + $stmt = $conn->query($query); + + if ($timeout > 0) { + echo "Query test $timeout: should have timed out!\n"; + } + } catch (PDOException $e) { + if (!fnmatch($error, $e->getMessage())) { + echo "Query test error expected $timeout:\n"; + var_dump($e->getMessage()); + } + } + + $t1 = microtime(true); + + checkTimeElapsed("Query test ($timeout): ", $t0, $t1, $delay); + + unset($stmt); +} + +function statementTest($conn, $timeout, $asAttribute) +{ + global $query, $error; + + // if timeout is 0 it means no timeout + $delay = ($timeout > 0) ? $timeout : _DELAY; + + $result = null; + $t0 = microtime(true); + + try { + if ($asAttribute) { + $stmt = $conn->prepare($query); + $stmt->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, $timeout); + } else { + $options = array(PDO::SQLSRV_ATTR_QUERY_TIMEOUT => $timeout); + $stmt = $conn->prepare($query, $options); + } + + $result = $stmt->execute(); + + if ($timeout > 0) { + echo "statementTest $timeout: should have timed out!\n"; + } + } catch (PDOException $e) { + if (!fnmatch($error, $e->getMessage())) { + echo "Statement test error expected $timeout, $asAttribute:\n"; + var_dump($e->getMessage()); + } + } + + $t1 = microtime(true); + + checkTimeElapsed("statementTest ($timeout, $asAttribute): ", $t0, $t1, $delay); + + unset($stmt); +} + +try { + $rand = rand(1, 100); + $timeout = $rand % 3; + $asAttribute = $rand % 2; + + $conn = connectionTest($timeout, $asAttribute); + testErrors($conn); + unset($conn); + + $conn = connectionTest(0, !$asAttribute); + queryTest($conn, $timeout); + + for ($i = 0; $i < 2; $i++) { + statementTest($conn, $timeout, $i); + } + unset($conn); + + echo "Done\n"; +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} + +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/srv_1027_query_timeout.phpt b/test/functional/sqlsrv/srv_1027_query_timeout.phpt new file mode 100644 index 000000000..a22fe3a1d --- /dev/null +++ b/test/functional/sqlsrv/srv_1027_query_timeout.phpt @@ -0,0 +1,120 @@ +--TEST-- +GitHub issue 1027 - timeout option +--DESCRIPTION-- +This test is a variant of the corresponding PDO test, and it verifies that setting the query timeout option should affect sqlsrv_query or sqlsrv_prepare correctly. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $timeout); + $sql = 'SELECT 1'; + + if ($prepare) { + $stmt = sqlsrv_prepare($conn, $sql, null, $options); + } else { + $stmt = sqlsrv_query($conn, $sql, null, $options); + } + + if ($stmt !== false) { + echo "Expect this to fail with timeout option $timeout\n"; + } + if (sqlsrv_errors()[0]['message'] !== $error) { + print_r(sqlsrv_errors()); + } +} + +function testErrors($conn) +{ + testTimeout($conn, 1.8); + testTimeout($conn, 'xyz'); + testTimeout($conn, -99, true); + testTimeout($conn, 'abc', true); +} + +function checkTimeElapsed($message, $t0, $t1, $expectedDelay) +{ + $elapsed = $t1 - $t0; + $diff = abs($elapsed - $expectedDelay); + $leeway = 1.0; + $missed = ($diff > $leeway); + trace("$message $elapsed secs elapsed\n"); + + if ($missed) { + echo $message; + echo "Expected $expectedDelay but $elapsed secs elapsed\n"; + } +} + +function statementTest($conn, $timeout, $prepare) +{ + global $query, $expired; + + $options = array('QueryTimeout' => $timeout); + $stmt = null; + $result = null; + + // if timeout is 0 it means no timeout + $delay = ($timeout > 0) ? $timeout : _DELAY; + + $t0 = microtime(true); + if ($prepare) { + $stmt = sqlsrv_prepare($conn, $query, null, $options); + $result = sqlsrv_execute($stmt); + } else { + $stmt = sqlsrv_query($conn, $query, null, $options); + } + + $t1 = microtime(true); + + if ($timeout > 0) { + if ($prepare && $result !== false) { + echo "Prepared statement should fail with timeout $timeout\n"; + } elseif (!$prepare && $stmt !== false) { + echo "Query should fail with timeout $timeout\n"; + } else { + // check error messages + $errors = sqlsrv_errors(); + if (!fnmatch($expired, $errors[0]['message'])) { + echo "Unexpected error returned ($timeout, $prepare):\n"; + print_r(sqlsrv_errors()); + } + } + } + + checkTimeElapsed("statementTest ($timeout, $prepare): ", $t0, $t1, $delay); +} + +$conn = AE\connect(); + +testErrors($conn); + +$rand = rand(1, 100); +$timeout = $rand % 3; + +for ($i = 0; $i < 2; $i++) { + statementTest($conn, $timeout, $i); +} + +sqlsrv_close($conn); + +echo "Done\n"; + +?> +--EXPECT-- +Done \ No newline at end of file From 6a7136d97711b50d67d5d624e7b9a6d385821992 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 23 Oct 2019 15:12:52 -0700 Subject: [PATCH 174/249] Feature request: support extended string types (#1043) --- .travis.yml | 4 +- source/pdo_sqlsrv/pdo_dbh.cpp | 65 +++++++-- source/pdo_sqlsrv/pdo_stmt.cpp | 28 +++- source/pdo_sqlsrv/pdo_util.cpp | 4 + source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 8 +- source/shared/core_sqlsrv.h | 3 + .../pdo_1018_emulate_prepare_natl_char.phpt | 109 +++++++++++++++ .../pdo_1018_quote_param_str_natl_char.phpt | 93 +++++++++++++ .../pdo_1018_real_prepare_natl_char.phpt | 131 ++++++++++++++++++ test/functional/pdo_sqlsrv/skipif_old_php.inc | 10 ++ 10 files changed, 436 insertions(+), 19 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt create mode 100644 test/functional/pdo_sqlsrv/skipif_old_php.inc diff --git a/.travis.yml b/.travis.yml index 0618bebfd..d908eff64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,10 +20,10 @@ env: - TEST_PHP_SQL_PWD=Password123 before_install: - - docker pull mcr.microsoft.com/mssql/server:2019-CTP2.4-ubuntu + - docker pull mcr.microsoft.com/mssql/server:2019-CTP3.2-ubuntu install: - - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-CTP2.4-ubuntu + - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-CTP3.2-ubuntu - docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql . before_script: diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 15aec1173..eedd07b11 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -520,7 +520,8 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo fetch_numeric( false ), fetch_datetime( false ), format_decimals( false ), - decimal_places( NO_CHANGE_DECIMAL_PLACES ) + decimal_places( NO_CHANGE_DECIMAL_PLACES ), + use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED) { if( client_buffer_max_size < 0 ) { client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; @@ -1104,6 +1105,27 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout } break; +#if PHP_VERSION_ID >= 70200 + case PDO_ATTR_DEFAULT_STR_PARAM: + { + if (Z_TYPE_P(val) != IS_LONG) { + THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID); + } + + zend_long value = Z_LVAL_P(val); + if (value == PDO_PARAM_STR_NATL) { + driver_dbh->use_national_characters = 1; + } + else if (value == PDO_PARAM_STR_CHAR) { + driver_dbh->use_national_characters = 0; + } + else { + THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID); + } + } + break; +#endif + // Not supported case PDO_ATTR_FETCH_TABLE_NAMES: case PDO_ATTR_FETCH_CATALOG_NAMES: @@ -1275,6 +1297,14 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout break; } +#if PHP_VERSION_ID >= 70200 + case PDO_ATTR_DEFAULT_STR_PARAM: + { + ZVAL_LONG(return_value, (driver_dbh->use_national_characters == 0) ? PDO_PARAM_STR_CHAR : PDO_PARAM_STR_NATL); + break; + } +#endif + default: { THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_INVALID_DBH_ATTR ); @@ -1425,14 +1455,18 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, // Return: // 0 for failure, 1 for success. int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const char* unquoted, _In_ size_t unquoted_len, _Outptr_result_buffer_(*quoted_len) char **quoted, _Out_ size_t* quoted_len, - enum pdo_param_type /*paramtype*/ TSRMLS_DC ) + enum pdo_param_type paramtype TSRMLS_DC ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; PDO_LOG_DBH_ENTRY; SQLSRV_ENCODING encoding = SQLSRV_ENCODING_CHAR; - + bool use_national_char_set = false; + + pdo_sqlsrv_dbh* driver_dbh = static_cast(dbh->driver_data); + SQLSRV_ASSERT(driver_dbh != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was NULL."); + // get the current object in PHP; this distinguishes pdo_sqlsrv_dbh_quote being called from: // 1. PDO::quote() - object name is PDO // 2. PDOStatement::execute() - object name is PDOStatement @@ -1461,13 +1495,12 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast(stmt->driver_data); SQLSRV_ASSERT(driver_stmt != NULL, "pdo_sqlsrv_dbh_quote: driver_data object was null"); - if (driver_stmt->encoding() != SQLSRV_ENCODING_INVALID) { - encoding = driver_stmt->encoding(); - } - else { - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( stmt->driver_data ); - encoding = driver_dbh->encoding(); + encoding = driver_stmt->encoding(); + if (encoding == SQLSRV_ENCODING_INVALID || encoding == SQLSRV_ENCODING_DEFAULT) { + pdo_sqlsrv_dbh* stmt_driver_dbh = reinterpret_cast(stmt->driver_data); + encoding = stmt_driver_dbh->encoding(); } + // get the placeholder at the current position in driver_stmt->placeholders ht // Normally it's not a good idea to alter the internal pointer in a hashed array // (see pull request 634 on GitHub) but in this case this is for internal use only @@ -1489,6 +1522,16 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const } } + use_national_char_set = (driver_dbh->use_national_characters == 1 || encoding == SQLSRV_ENCODING_UTF8); +#if PHP_VERSION_ID >= 70200 + if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) { + use_national_char_set = true; + } + if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { + use_national_char_set = false; + } +#endif + if ( encoding == SQLSRV_ENCODING_BINARY ) { // convert from char* to hex digits using os std::basic_ostringstream os; @@ -1533,7 +1576,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const // count the number of quotes needed unsigned int quotes_needed = 2; // the initial start and end quotes of course // include the N proceeding the initial quote if encoding is UTF8 - if ( encoding == SQLSRV_ENCODING_UTF8 ) { + if (use_national_char_set) { quotes_needed = 3; } for ( size_t index = 0; index < unquoted_len; ++index ) { @@ -1547,7 +1590,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const unsigned int out_current = 0; // insert N if the encoding is UTF8 - if ( encoding == SQLSRV_ENCODING_UTF8 ) { + if (use_national_char_set) { ( *quoted )[out_current++] = 'N'; } // insert initial quote diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 0cd562d9c..c28aa1bcf 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1276,18 +1276,35 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) { throw pdo::PDOException(); } + // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant + // and the SQLSRV_PHPTYPE_* constant + // vso 2829: derive the pdo_type for input/output parameter as well + // also check if the user has specified PARAM_STR_NATL or PARAM_STR_CHAR for string params + int pdo_type = param->param_type; if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) { if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) { direction = SQL_PARAM_INPUT_OUTPUT; + pdo_type = param->param_type & ~PDO_PARAM_INPUT_OUTPUT; } else { direction = SQL_PARAM_OUTPUT; } } + + // check if the user has specified the character set to use, take it off but ignore +#if PHP_VERSION_ID >= 70200 + if ((pdo_type & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) { + pdo_type = pdo_type & ~PDO_PARAM_STR_NATL; + LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_NATL set but is ignored."); + } + if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { + pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR; + LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_CHAR set but is ignored."); + } +#endif + // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant // and the SQLSRV_PHPTYPE_* constant - // vso 2829: derive the pdo_type for input/output parameter as well - int pdo_type = (direction == SQL_PARAM_OUTPUT) ? param->param_type : param->param_type & ~PDO_PARAM_INPUT_OUTPUT; SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID; switch (pdo_type) { case PDO_PARAM_BOOL: @@ -1354,13 +1371,17 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) { throw pdo::PDOException(); } + // the encoding by default is that set on the statement SQLSRV_ENCODING encoding = driver_stmt->encoding(); // if the statement's encoding is the default, then use the one on the connection if( encoding == SQLSRV_ENCODING_DEFAULT ) { encoding = driver_stmt->conn->encoding(); } - // if the user provided an encoding, use it instead + + // Beginning with PHP7.2 the user can specify whether to use PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL + // But this extended type will be ignored in real prepared statements, so the encoding deliberately + // set in the statement or driver options will still take precedence if( !Z_ISUNDEF(param->driver_params) ) { CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) { @@ -1383,6 +1404,7 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, break; } } + // and bind the parameter core_sqlsrv_bind_param( driver_stmt, static_cast( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, sql_type, column_size, decimal_digits TSRMLS_CC ); diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 6cfb43acf..cff7add5c 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -461,6 +461,10 @@ pdo_error PDO_ERRORS[] = { SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, { IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -96, true} }, + { + PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID, + { IMSSP, (SQLCHAR*) "Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.", -97, false} + }, { UINT_MAX, {} } }; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index e0f5f2201..2b7269c0c 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -139,8 +139,8 @@ class conn_string_parser : private string_parser int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len ); void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC); - protected: - void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC); + protected: + void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC); public: conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ); @@ -183,6 +183,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { bool fetch_datetime; bool format_decimals; short decimal_places; + short use_national_characters; pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); }; @@ -386,7 +387,8 @@ enum PDO_ERROR_CODES { PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED, PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED, - PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED + PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED, + PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID }; extern pdo_error PDO_ERRORS[]; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index f468a7b6b..28f46f689 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -240,6 +240,9 @@ const int SQL_SQLSTATE_BUFSIZE = SQL_SQLSTATE_SIZE + 1; // default value of decimal places (no formatting required) const short NO_CHANGE_DECIMAL_PLACES = -1; +// default value for national character set strings (user did not specify any preference) +const short CHARSET_PREFERENCE_NOT_SPECIFIED = -1; + // buffer size allocated to retrieve data from a PHP stream. This number // was chosen since PHP doesn't return more than 8k at a time even if // the amount requested was more. diff --git a/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt new file mode 100644 index 000000000..5c389bb9f --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt @@ -0,0 +1,109 @@ +--TEST-- +GitHub issue 1018 - Test emulate prepared statements with the extended string types +--DESCRIPTION-- +This test verifies the extended string types, PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL and +PDO::PARAM_STR_CHAR will affect "emulate prepared" statements. If the parameter encoding is specified, +it also matters. The N'' prefix will be used when either it is PDO::PARAM_STR_NATL or the +parameter encoding is UTF-8. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + true); + $stmt = $conn->prepare($sql, $options); + + if ($utf8) { + $stmt->bindParam(':value', $p, $pdoStrParam, 0, PDO::SQLSRV_ENCODING_UTF8); + } else { + $stmt->bindParam(':value', $p, $pdoStrParam); + } + $stmt->execute(); + + $result = $stmt->fetch(PDO::FETCH_NUM); + trace("$testCase: expected $value and returned $result[0]\n"); + if ($result[0] !== $value) { + echo("$testCase: expected $value but returned:\n"); + var_dump($result); + } +} + +try { + $conn = connect(); + + // Test case 1: PDO::PARAM_STR_NATL + $testCase = 'Test case 1: no default but specifies PDO::PARAM_STR_NATL'; + toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase); + + // Test case 2: PDO::PARAM_STR_CHAR + $testCase = 'Test case 2: no default but specifies PDO::PARAM_STR_CHAR'; + toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase); + + // Test case 3: no extended string types + $testCase = 'Test case 3: no default but no extended string types either'; + toEmulatePrepare($conn, PDO::PARAM_STR, $p1, $testCase); + + // Test case 4: no extended string types but specifies UTF 8 encoding + $testCase = 'Test case 4: no default but no extended string types but with UTF-8'; + toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase, true); + + //////////////////////////////////////////////////////////////////////// + // NEXT tests: set the default string type: PDO::PARAM_STR_CHAR first + $conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_CHAR); + + // Test case 5: overrides the default PDO::PARAM_STR_CHAR + $testCase = 'Test case 5: overrides the default PDO::PARAM_STR_CHAR'; + toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase); + + // Test case 6: specifies PDO::PARAM_STR_CHAR directly + $testCase = 'Test case 6: specifies PDO::PARAM_STR_CHAR, same as the default'; + toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase); + + // Test case 7: uses the default PDO::PARAM_STR_CHAR without specifying + $testCase = 'Test case 7: no extended string types (uses the default)'; + toEmulatePrepare($conn, PDO::PARAM_STR, $p1, $testCase); + + // Test case 8: uses the default PDO::PARAM_STR_CHAR without specifying but with UTF 8 encoding + $testCase = 'Test case 8: no extended string types (uses the default) but with UTF-8 '; + toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase, true); + + //////////////////////////////////////////////////////////////////////// + // NEXT tests: set the default string type: PDO::PARAM_STR_NATL + $conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL); + + // Test case 9: overrides the default PDO::PARAM_STR_NATL + $testCase = 'Test case 9: overrides the default PDO::PARAM_STR_NATL'; + toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase); + + // Test case 10: specifies PDO::PARAM_STR_NATL directly + $testCase = 'Test case 10: specifies PDO::PARAM_STR_NATL, same as the default'; + toEmulatePrepare($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase); + + // Test case 11: uses the default PDO::PARAM_STR_NATL without specifying + $testCase = 'Test case 11: no extended string types (uses the default)'; + toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase); + + // Test case 12: uses the default PDO::PARAM_STR_NATL without specifying but with UTF 8 encoding + $testCase = 'Test case 12: no extended string types (uses the default) but with UTF-8'; + toEmulatePrepare($conn, PDO::PARAM_STR, $p, $testCase, true); + + echo "Done\n"; +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} + +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt new file mode 100644 index 000000000..5b39f9f15 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1018_quote_param_str_natl_char.phpt @@ -0,0 +1,93 @@ +--TEST-- +GitHub issue 1018 - Test PDO::quote() with the extended string types +--DESCRIPTION-- +This test verifies the extended string types, PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL and +PDO::PARAM_STR_CHAR will affect how PDO::quote() works. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +query('select 1'); + $error = '*An invalid attribute was designated on the PDOStatement object.'; + $pdoParam = ($isChar) ? PDO::PARAM_STR_CHAR : PDO::PARAM_STR_NATL; + + // This will cause an exception because PDO::ATTR_DEFAULT_STR_PARAM is not a statement attribute + $stmt->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, $pdoParam); + } catch (PDOException $e) { + if (!fnmatch($error, $e->getMessage())) { + echo "Unexpected error returned setting PDO::ATTR_DEFAULT_STR_PARAM on statement\n"; + var_dump($e->getMessage()); + } + } +} + +function testErrorCase($attr) +{ + try { + $conn = connect(); + $error = '*Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.'; + + // This will cause an exception because PDO::ATTR_DEFAULT_STR_PARAM expects either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL only + $conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, $attr); + } catch (PDOException $e) { + if (!fnmatch($error, $e->getMessage())) { + echo "Unexpected error returned setting PDO::ATTR_DEFAULT_STR_PARAM\n"; + var_dump($e->getMessage()); + } + } +} + +try { + testErrorCase(true); + testErrorCase('abc'); + testErrorCase(4); + + $conn = connect(); + testErrorCase2($conn, true); + testErrorCase2($conn, false); + + // Start testing quote function + $conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_CHAR); + + var_dump($conn->quote(null, PDO::PARAM_NULL)); + var_dump($conn->quote('\'', PDO::PARAM_STR)); + var_dump($conn->quote('foo', PDO::PARAM_STR)); + var_dump($conn->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR)); + var_dump($conn->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL)); + + var_dump($conn->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_CHAR); + $conn->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL); + var_dump($conn->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL); + + var_dump($conn->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR)); + var_dump($conn->quote('über', PDO::PARAM_STR)); + var_dump($conn->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL)); + + unset($conn); + + echo "Done\n"; +} catch (PDOException $e) { + echo $e->getMessage() . PHP_EOL; +} + +?> +--EXPECT-- +string(2) "''" +string(4) "''''" +string(5) "'foo'" +string(5) "'foo'" +string(8) "N'über'" +bool(true) +bool(true) +string(5) "'foo'" +string(8) "N'über'" +string(8) "N'über'" +Done diff --git a/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt new file mode 100644 index 000000000..e058994e5 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt @@ -0,0 +1,131 @@ +--TEST-- +GitHub issue 1018 - Test real prepared statements with the extended string types +--DESCRIPTION-- +This test verifies the extended string types, PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL and +PDO::PARAM_STR_CHAR will NOT affect real prepared statements. Unlike emulate prepared statements, +real prepared statements will only be affected by the parameter encoding. If not set, it will use +the statement encoding or the connection one, which is by default UTF-8. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + false); // it's false by default anyway + $stmt = $conn->prepare($sql, $options); + + // Set param encoding only if $encoding is NOT FALSE + if ($encoding !== false) { + $stmt->bindParam(':value', $p, $pdoStrParam, 0, $encoding); + $encOptions = array(PDO::SQLSRV_ATTR_ENCODING => $encoding); + } else { + $stmt->bindParam(':value', $p, $pdoStrParam); + $encOptions = array(); + } + $stmt->execute(); + + // Should also set statement encoding when $encoding is NOT FALSE + // such that data can be fetched with the right encoding + $sql = "SELECT Col1 FROM $tableName WHERE ID = $id"; + $stmt = $conn->prepare($sql, $encOptions); + $stmt->execute(); + + $result = $stmt->fetch(PDO::FETCH_NUM); + trace("$testCase: expected $value and returned $result[0]\n"); + if ($result[0] !== $value) { + echo("$testCase: expected $value but returned:\n"); + var_dump($result); + } +} + +function testUTF8encoding($conn) +{ + global $p, $tableName; + + // Create a NVARCHAR column + $sql = "CREATE TABLE $tableName (ID int identity(1,1), Col1 NVARCHAR(100))"; + $conn->query($sql); + + // The extended string types PDO::PARAM_STR_NATL and PDO::PARAM_STR_CHAR + // will be ignored in the following test cases. Only the statement or + // the connection encoding matters. + + // Test case 1: PDO::PARAM_STR_CHAR + $testCase = 'UTF-8 case 1: no default but specifies PDO::PARAM_STR_CHAR'; + insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p, $testCase, 1); + + // Test case 2: PDO::PARAM_STR_NATL + $testCase = 'UTF-8 case 2: no default but specifies PDO::PARAM_STR_NATL'; + insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p, $testCase, 2); + + // Test case 3: no extended string types + $testCase = 'UTF-8 case 3: no default but no extended string types either'; + insertRead($conn, PDO::PARAM_STR, $p, $testCase, 3); + + // Test case 4: no extended string types but specifies UTF-8 encoding + $testCase = 'UTF-8 case 4: no default but no extended string types but with UTF-8 encoding'; + insertRead($conn, PDO::PARAM_STR, $p, $testCase, 4, PDO::SQLSRV_ENCODING_UTF8); + + dropTable($conn, $tableName); +} + +function testNonUTF8encoding($conn) +{ + global $p, $p1, $tableName; + + // Create a VARCHAR column + $sql = "CREATE TABLE $tableName (ID int identity(1,1), Col1 VARCHAR(100))"; + $conn->query($sql); + + // The extended string types PDO::PARAM_STR_NATL and PDO::PARAM_STR_CHAR + // will be ignored in the following test cases. Only the statement or + // the connection encoding matters. + + // Test case 1: PDO::PARAM_STR_CHAR (expect $p1) + $testCase = 'System case 1: no default but specifies PDO::PARAM_STR_CHAR'; + insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_CHAR, $p1, $testCase, 1); + + // Test case 2: PDO::PARAM_STR_NATL (expect $p1) + $testCase = 'System case 2: no default but specifies PDO::PARAM_STR_NATL'; + insertRead($conn, PDO::PARAM_STR | PDO::PARAM_STR_NATL, $p1, $testCase, 2); + + // Test case 3: no extended string types (expect $p1) + $testCase = 'System case 3: no default but no extended string types either'; + insertRead($conn, PDO::PARAM_STR, $p1, $testCase, 3); + + // Test case 4: no extended string types but specifies UTF-8 encoding (expect $p1) + $testCase = 'System case 4: no default but no extended string types but with UTF-8 encoding'; + insertRead($conn, PDO::PARAM_STR, $p1, $testCase, 4, PDO::SQLSRV_ENCODING_UTF8); + + dropTable($conn, $tableName); +} + +try { + $conn = connect(); + dropTable($conn, $tableName); + + // The connection encoding is by default PDO::SQLSRV_ENCODING_UTF8. For this test + // no change is made to the connection encoding. + testUTF8encoding($conn); + testNonUTF8encoding($conn); + + echo "Done\n"; +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} + +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/skipif_old_php.inc b/test/functional/pdo_sqlsrv/skipif_old_php.inc new file mode 100644 index 000000000..19f97bc56 --- /dev/null +++ b/test/functional/pdo_sqlsrv/skipif_old_php.inc @@ -0,0 +1,10 @@ + From aec733b7644ba996e3d7f51eb2c6666c53cb9a18 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 24 Oct 2019 07:25:55 -0700 Subject: [PATCH 175/249] Added the required file to ansi tests (#1047) --- test/functional/sqlsrv/TC34_PrepAndExec.phpt | 1 + test/functional/sqlsrv/TC42_FetchField.phpt | 1 + test/functional/sqlsrv/TC43_FetchData.phpt | 1 + test/functional/sqlsrv/TC44_FetchArray.phpt | 1 + test/functional/sqlsrv/TC45_FetchObject.phpt | 1 + test/functional/sqlsrv/TC46_FetchNextResult.phpt | 1 + test/functional/sqlsrv/TC48_FetchScrollable.phpt | 1 + test/functional/sqlsrv/TC51_StreamRead.phpt | 4 +++- test/functional/sqlsrv/TC55_StreamScrollable.phpt | 1 + test/functional/sqlsrv/test_stream_large_data.phpt | 1 + 10 files changed, 12 insertions(+), 1 deletion(-) diff --git a/test/functional/sqlsrv/TC34_PrepAndExec.phpt b/test/functional/sqlsrv/TC34_PrepAndExec.phpt index 3e4f56729..ae9365cae 100644 --- a/test/functional/sqlsrv/TC34_PrepAndExec.phpt +++ b/test/functional/sqlsrv/TC34_PrepAndExec.phpt @@ -7,6 +7,7 @@ Validates that a prepared statement can be successfully executed more than once. PHPT_EXEC=true --SKIPIF-- diff --git a/test/functional/sqlsrv/TC55_StreamScrollable.phpt b/test/functional/sqlsrv/TC55_StreamScrollable.phpt index a59f336cd..99e973455 100644 --- a/test/functional/sqlsrv/TC55_StreamScrollable.phpt +++ b/test/functional/sqlsrv/TC55_StreamScrollable.phpt @@ -6,6 +6,7 @@ Verifies the streaming behavior with scrollable resultsets. PHPT_EXEC=true --SKIPIF-- Date: Thu, 31 Oct 2019 16:55:36 -0700 Subject: [PATCH 176/249] Always Encrypted v2 support (#1045) * Change to support ae-v2 * Add support for AE V2 * Added some descriptions and comments * Fixed PDO pattern matching * Updated key generation scripts * Fixed key script * Fixed char/nchar results, fixed formatting issues * Addressed review comments * Updated key scripts * Debugging aev2 keyword failure * Debugging aev2 keyword failure * Debugging aev2 keyword failure * Debugging aev2 keyword failure * Added skipif to ae v2 keyword test * Addressed review comments * Fixed braces and camel caps * Updated test descriptions * Added detail to test descriptions * Tiny change --- source/shared/core_conn.cpp | 47 +- test/functional/pdo_sqlsrv/AE_v2_values.inc | 163 ++++++ test/functional/pdo_sqlsrv/MsSetup.inc | 2 + .../pdo_sqlsrv/pdo_AE_functions.inc | 488 +++++++++++++++++ .../pdo_sqlsrv/pdo_aev2_ce_enabled.phpt | 93 ++++ .../pdo_aev2_encrypt_plaintext.phpt | 136 +++++ .../pdo_sqlsrv/pdo_aev2_keywords.phpt | 60 ++ .../pdo_aev2_reencrypt_encrypted.phpt | 109 ++++ .../pdo_aev2_wrong_attestation.phpt | 95 ++++ test/functional/pdo_sqlsrv/skipif_not_hgs.inc | 36 ++ test/functional/setup/AEV2Cert.pfx | Bin 0 -> 2654 bytes test/functional/setup/ae_keys.sql | 91 ++- test/functional/setup/setup_dbs.py | 2 + test/functional/sqlsrv/AE_v2_values.inc | 163 ++++++ test/functional/sqlsrv/MsSetup.inc | 2 + test/functional/sqlsrv/skipif_not_hgs.inc | 36 ++ .../functional/sqlsrv/sqlsrv_AE_functions.inc | 518 ++++++++++++++++++ .../sqlsrv/sqlsrv_ae_fetch_phptypes.phpt | 1 - .../sqlsrv/sqlsrv_aev2_ce_enabled.phpt | 113 ++++ .../sqlsrv/sqlsrv_aev2_encrypt_plaintext.phpt | 138 +++++ .../sqlsrv/sqlsrv_aev2_keywords.phpt | 71 +++ .../sqlsrv_aev2_reencrypt_encrypted.phpt | 110 ++++ .../sqlsrv/sqlsrv_aev2_wrong_attestation.phpt | 93 ++++ .../functional/sqlsrv/test_ae_keys_setup.phpt | 8 +- 24 files changed, 2534 insertions(+), 41 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/AE_v2_values.inc create mode 100644 test/functional/pdo_sqlsrv/pdo_AE_functions.inc create mode 100644 test/functional/pdo_sqlsrv/pdo_aev2_ce_enabled.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_aev2_encrypt_plaintext.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_aev2_keywords.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_aev2_reencrypt_encrypted.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_aev2_wrong_attestation.phpt create mode 100644 test/functional/pdo_sqlsrv/skipif_not_hgs.inc create mode 100644 test/functional/setup/AEV2Cert.pfx create mode 100644 test/functional/sqlsrv/AE_v2_values.inc create mode 100644 test/functional/sqlsrv/skipif_not_hgs.inc create mode 100644 test/functional/sqlsrv/sqlsrv_AE_functions.inc create mode 100644 test/functional/sqlsrv/sqlsrv_aev2_ce_enabled.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_aev2_encrypt_plaintext.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_aev2_keywords.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_aev2_reencrypt_encrypted.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_aev2_wrong_attestation.phpt diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index cea5a3ddc..10545e42c 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -718,14 +718,14 @@ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t v const char *pch = strchr(pstr, '}'); size_t i = 0; - + while (pch != NULL && i < value_len) { i = pch - pstr + 1; - + if (i == value_len || (i < value_len && pstr[i] != '}')) { return false; } - + i++; // skip the brace pch = strchr(pch + 2, '}'); // continue searching } @@ -783,7 +783,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou try { // Since connection options access token and authentication cannot coexist, check if both of them are used. - // If access token is specified, check UID and PWD as well. + // If access token is specified, check UID and PWD as well. // No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) { bool invalidOptions = false; @@ -801,7 +801,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou access_token_used = true; } - // Check if Authentication is ActiveDirectoryMSI + // Check if Authentication is ActiveDirectoryMSI // https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview bool activeDirectoryMSI = false; if (authentication_option_used) { @@ -813,7 +813,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou if (!stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) { activeDirectoryMSI = true; - // There are two types of managed identities: + // There are two types of managed identities: // (1) A system-assigned managed identity: UID must be NULL // (2) A user-assigned managed identity: UID defined but must not be an empty string // In both cases, PWD must be NULL @@ -832,11 +832,11 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou } } } - + // Add the server name common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); - // If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used, + // If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used, // because they are incompatible if (!access_token_used && !activeDirectoryMSI) { if (uid == NULL || strnlen_s(uid) == 0) { @@ -1153,9 +1153,12 @@ void column_encryption_set_func::func( _In_ connection_option const* option, _In convert_to_string( value ); const char* value_str = Z_STRVAL_P( value ); - // Column Encryption is disabled by default unless it is explicitly 'Enabled' + // Column Encryption is disabled by default, but if it is present and not + // explicitly set to disabled or enabled, the ODBC driver will assume the + // user is providing an attestation protocol and URL for enclave support. + // For our purposes we need only set ce_option.enabled to true if not disabled. conn->ce_option.enabled = false; - if ( !stricmp(value_str, "enabled" )) { + if ( stricmp(value_str, "disabled" )) { conn->ce_option.enabled = true; } @@ -1200,7 +1203,7 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval* char *pValue = static_cast(sqlsrv_malloc(value_len + 1)); memcpy_s(pValue, value_len + 1, value_str, value_len); pValue[value_len] = '\0'; // this makes sure there will be no trailing garbage - + // This will free the existing memory block before assigning the new pointer -- the user might set the value(s) more than once if (option->conn_option_key == SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID) { conn->ce_option.akv_id = pValue; @@ -1262,10 +1265,10 @@ void access_token_set_func::func( _In_ connection_option const* option, _In_ zva } const char* value_str = Z_STRVAL_P( value ); - - // The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from - // an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also - // bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the + + // The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from + // an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also + // bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the // SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure // // typedef struct AccessToken @@ -1276,30 +1279,30 @@ void access_token_set_func::func( _In_ connection_option const* option, _In_ zva // // NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows. // - // A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte, + // A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte, // similar to a UCS-2 string containing only ASCII characters // // See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token size_t dataSize = 2 * value_len; - - sqlsrv_malloc_auto_ptr accToken; + + sqlsrv_malloc_auto_ptr accToken; accToken = reinterpret_cast(sqlsrv_malloc(sizeof(ACCESSTOKEN) + dataSize)); ACCESSTOKEN *pAccToken = accToken.get(); SQLSRV_ASSERT(pAccToken != NULL, "Something went wrong when trying to allocate memory for the access token."); pAccToken->dataSize = dataSize; - + // Expand access token with padding bytes for (size_t i = 0, j = 0; i < dataSize; i += 2, j++) { pAccToken->data[i] = value_str[j]; pAccToken->data[i+1] = 0; } - + core::SQLSetConnectAttr(conn, SQL_COPT_SS_ACCESS_TOKEN, reinterpret_cast(pAccToken), SQL_IS_POINTER); - - // Save the pointer because SQLDriverConnect() will use it to make connection to the server + + // Save the pointer because SQLDriverConnect() will use it to make connection to the server conn->azure_ad_access_token = pAccToken; accToken.transferred(); } diff --git a/test/functional/pdo_sqlsrv/AE_v2_values.inc b/test/functional/pdo_sqlsrv/AE_v2_values.inc new file mode 100644 index 000000000..721295b40 --- /dev/null +++ b/test/functional/pdo_sqlsrv/AE_v2_values.inc @@ -0,0 +1,163 @@ +5', 'fd4$_w@q^@!coe$7', 'abcd', 'ev72#x*fv=u$', '4rfg3sw', 'voi%###i<@@'); +$testValues['nchar'] = array('⽧㘎ⷅ㪋','af㋮ᶄḉㇼ៌ӗඣ','áŠãµ®à´–ᅥ㪮ኸ⮊ߒᙵꇕâ¯áž‚ꉟफ़⻦ꈔꇼŞ','ê·ê¬•','ã¯ã©§ã–ƒâºµã´°Ú‡à½£á§†ê²´ê••ê²‘וֹꔄ若㌉ᒵȅ㗉ꗅᡉ','ʭḪã…ᾔᎀã겶ꅫážã´‰á´³ãœžÒ‚','','בּŬḛʼꃺꌖ㓵ꗛ᧽ഭწ社⾯㬄౧ຸฬã¯ê‹›ã—¾'); +$testValues['varchar'] = array('gop093','*#$@@)%*$@!%','cio4*3do*$','zzz$a#l',' ','v#x%n!k&r@p$f^','6$gt?je#~','0x3dK#?'); +$testValues['nvarchar'] = array('á¾áº´ã”®ã––à­±Üã—㴴៸ழ᷂ᵄ葉អ㺓節','Ó•áµàµ´ê”“ὀ⾼','Ὡ','璉Džꖭ갪ụ⺭','Ӿϰᇬ㭡㇑ᵈᔆ⽹hᙎ՞ꦣ㧼ለͭ','Ĕ㬚㔈♠既','êˆ Ý«','ê†àª«â·Œã½Ì—ૣܯ¢⽳㌭ゴᔓᅄѓⷉꘊⶮáᴗஈԋ≡ㄊହꂈ꓂ꑽრꖾŞ⽉걹ꩰോఫ㒧㒾㑷藍㵀ဲï¤ê§¥'); +$testValues['varchar(max)'] = array('Q0H4@4E%v+ 3*Trx#>*r86-&d$VgjZ','AjEvVABur(A&Q@eG,A$3u"xAzl','z#dFd4z', + '9Dvsg9B?7oktB@|OIqy<\K^\e|*7Y&yH31E-<.hQ:)g Jl`MQV>rdOhjG;B4wQ(WR[`l(pELt0FYu._T3+8tns!}Nqrc1%n@|N|ik C@ 03a/ +H9mBq','SSs$Ie*{:D4;S]',' ','<\K^\e|*7Y&yH31E-<.hQ:','@Kg1Z6XTOgbt?CEJ|M^rkR_L4{1?l', '<=', '>=', '<>', '!<', '!>'); + +// Thresholds against which to use the comparison operators +$thresholds = array('integer' => 0, + 'bigint' => 0, + 'smallint' => 1000, + 'tinyint' => 100, + 'bit' => 0, + 'float' => 1.2, + 'real' => -1.2, + 'numeric' => 45.6789, + 'char' => 'rstuv', + 'nchar' => '㊃ᾞਲ㨴꧶êšê…', + 'varchar' => '6$gt?je#~', + 'nvarchar' => 'Ó•áµàµ´ê”“ὀ⾼', + 'varchar(max)' => 'hijkl', + 'nvarchar(max)' => 'xá•á›™á˜¡', + 'binary' => 0x44E4A, + 'varbinary' => 0xE4300FF, + 'varbinary(max)' => 0xD3EA762C78F, + 'date' => '2010-01-31', + 'time' => '21:45:45.4545', + 'datetime' => '3125-05-31 05:00:32.4', + 'datetime2' => '2384-12-31 12:40:12.5434323', + 'datetimeoffset' => '1984-09-25 10:40:20.0909111+03:00', + 'smalldatetime' => '1998-06-13 04:00:30', + ); + +// String patterns to test with LIKE +$patterns = array('integer' => array('8', '48', '123'), + 'bigint' => array('000','7', '65536'), + 'smallint' => array('4','768','abc'), + 'tinyint' => array('9','0','25'), + 'bit' => array('0','1','100'), + 'float' => array('14159','.','E+','2.3','308'), + 'real' => array('30','.','e-','2.3','38'), + 'numeric' => array('0','0000','12345','abc','.'), + 'char' => array('w','@','x*fv=u$','e3'), + 'nchar' => array('afã‹®','ã¯ê‹›ã—¾','ꦣ㧼ለͭ','123'), + 'varchar' => array(' ','a','#','@@)'), + 'nvarchar' => array('Ó•','Ӿϰᇬ㭡','璉Džꖭ갪ụ⺭','ï¤ê§¥','ꈔꇼŞ'), + 'varchar(max)' => array('A','|*7Y&','4z','@!@','AjE'), + 'nvarchar(max)' => array('t','㧶áቴƯɋ','ᘷ㬡',' ','ê¾É”ᡧãš'), + 'binary' => array('0x44E4A'), + 'varbinary' => array('0xE4300FF'), + 'varbinary(max)' => array('0xD3EA762C78F'), + 'date' => array('20','%','9-','04'), + 'time' => array('4545','.0','20:','12345',':'), + 'datetime' => array('997','12',':5','9999'), + 'datetime2' => array('3125-05-31 05:','.45','$f#','-29 ','0001'), + 'datetimeoffset' => array('+02','96',' ','5092856',':00'), + 'smalldatetime' => array('00','1999','abc',':','06'), + ); +?> diff --git a/test/functional/pdo_sqlsrv/MsSetup.inc b/test/functional/pdo_sqlsrv/MsSetup.inc index 823f283cc..1895f5aad 100644 --- a/test/functional/pdo_sqlsrv/MsSetup.inc +++ b/test/functional/pdo_sqlsrv/MsSetup.inc @@ -49,4 +49,6 @@ $AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPasswo $AKVClientID = 'TARGET_AKV_CLIENT_ID'; // for use with KeyVaultClientSecret $AKVSecret = 'TARGET_AKV_CLIENT_SECRET'; // for use with KeyVaultClientSecret +// for enclave computations +$attestation = 'TARGET_ATTESTATION'; ?> \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_AE_functions.inc b/test/functional/pdo_sqlsrv/pdo_AE_functions.inc new file mode 100644 index 000000000..393554d95 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_AE_functions.inc @@ -0,0 +1,488 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + + // Check that enclave computations are enabled + // See https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/configure-always-encrypted-enclaves?view=sqlallproducts-allversions#configure-a-secure-enclave + $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; + $stmt = $conn->query($query); + $info = $stmt->fetch(); + if ($info['value'] != 1 or $info['value_in_use'] != 1) { + die("Error: enclave computations are not enabled on the server!"); + } + + // Free the encryption cache to avoid spurious 'operand type clash' errors + $conn->exec("DBCC FREEPROCCACHE"); + + return $conn; +} + +// This CREATE TABLE query simply creates a non-encrypted table with +// two columns for each data type side by side +// This produces a query that looks like +// CREATE TABLE aev2test2 ( +// c_integer integer, +// c_integer_AE integer +// ) +function constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength) +{ + $query = "CREATE TABLE ".$tableName." (\n "; + foreach ($dataTypes as $type) { + + if (dataTypeIsString($type)) { + $query = $query.$colNames[$type]." ".$type."(".$slength."), \n "; + $query = $query.$colNamesAE[$type]." ".$type."(".$slength."), \n "; + } else { + $query = $query.$colNames[$type]." ".$type.", \n "; + $query = $query.$colNamesAE[$type]." ".$type.", \n "; + } + } + + // Remove the ", \n " from the end of the query or the comma will cause a syntax error + $query = substr($query, 0, -7)."\n)"; + + return $query; +} + +// The ALTER TABLE query encrypts columns. Each ALTER COLUMN directive must +// be preceded by ALTER TABLE. This query can be used to both encrypt plaintext +// columns and to re-encrypt encrypted columns. +// This produces a query that looks like +// ALTER TABLE [dbo].[aev2test2] +// ALTER COLUMN [c_integer_AE] integer +// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL +// WITH +// (ONLINE = ON); ALTER TABLE [dbo].[aev2test2] +// ALTER COLUMN [c_bigint_AE] bigint +// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL +// WITH +// (ONLINE = ON); ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; +function constructAlterQuery($tableName, $colNames, $dataTypes, $key, $encryptionType, $slength) +{ + $query = ''; + foreach ($dataTypes as $dataType) { + + $plength = dataTypeIsString($dataType) ? "(".$slength.")" : ""; + $collate = dataTypeNeedsCollate($dataType) ? " COLLATE Latin1_General_BIN2" : ""; + $query = $query." ALTER TABLE [dbo].[".$tableName."] + ALTER COLUMN [".$colNames[$dataType]."] ".$dataType.$plength." ".$collate." + ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL + WITH + (ONLINE = ON);"; + } + + $query = $query." ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;"; + + return $query; +} + +// This CREATE TABLE query creates a table with two columns for +// each data type side by side, one plaintext and one encrypted +// This produces a query that looks like +// CREATE TABLE aev2test2 ( +// c_integer integer NULL, +// c_integer_AE integer +// COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL +// ) +function constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType) +{ + $query = "CREATE TABLE ".$tableName." (\n "; + + foreach ($dataTypes as $type) { + + $collate = dataTypeNeedsCollate($type) ? " COLLATE Latin1_General_BIN2" : ""; + + if (dataTypeIsString($type)) { + $query = $query.$colNames[$type]." ".$type."(".$slength.") NULL, \n "; + $query = $query.$colNamesAE[$type]." ".$type."(".$slength.") \n "; + $query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n "; + } else { + $query = $query.$colNames[$type]." ".$type." NULL, \n "; + $query = $query.$colNamesAE[$type]." ".$type." \n "; + $query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n "; + } + } + + // Remove the ",\n " from the end of the query or the comma will cause a syntax error + $query = substr($query, 0, -6)."\n)"; + + return $query; +} + +// The INSERT query for the table +function constructInsertQuery($tableName, &$dataTypes, &$colNames, &$colNamesAE) +{ + $queryTypes = "("; + $valuesString = "VALUES ("; + + foreach ($dataTypes as $type) { + $colName1 = $colNames[$type].", "; + $colName2 = $colNamesAE[$type].", "; + $queryTypes .= $colName1; + $queryTypes .= $colName2; + $valuesString .= "?, ?, "; + } + + // Remove the ", " from the end of the query or the comma will cause a syntax error + $queryTypes = substr($queryTypes, 0, -2).")"; + $valuesString = substr($valuesString, 0, -2).")"; + + $insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString; + + return $insertQuery; +} + +function insertValues($conn, $insertQuery, $dataTypes, $testValues) +{ + for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) { + $insertValues = array(); + + foreach ($dataTypes as $type) { + $insertValues[] = $testValues[$type][$v]; + $insertValues[] = $testValues[$type][$v]; + } + + // Insert the data using PDO::prepare() + try { + $stmt = $conn->prepare($insertQuery); + $stmt->execute($insertValues); + } catch (PDOException $error) { + print_r($error); + die("Inserting values in encrypted table failed\n"); + } + } +} + +// compareResults checks that the results between the encrypted and non-encrypted +// columns are identical if statement execution succeeds. If statement execution +// fails, this function checks for the correct error. +// Arguments: +// statement $AEstmt: Prepared statement fetching encrypted data +// statement $nonAEstmt: Prepared statement fetching non-encrypted data +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl' +// string $comparison: Comparison operator +// string $type: Data type the comparison is operating on +function compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $comparison='', $type='') +{ + try { + $nonAEstmt->execute(); + } catch(Exception $error) { + print_r($error); + die("Executing non-AE statement failed!\n"); + } + + try { + $AEstmt->execute(); + } catch(Exception $error) { + if ($attestation == 'enabled') { + if ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r($error); + die("Equality comparison failed for deterministic encryption!\n"); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } + } elseif (isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33546')); + } elseif (!isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } else { + print_r($error); + die("AE statement execution failed when it shouldn't!"); + } + } elseif ($attestation == 'wrongurl') { + if ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + $e = $error->errorInfo; + die("Equality comparison failed for deterministic encryption!\n"); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } + } elseif (isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('CE405', '0')); + } elseif (!isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } else { + print_r($error); + die("AE statement execution failed when it shouldn't!"); + } + } elseif ($attestation == 'correct') { + if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } elseif ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r($error); + die("Equality comparison failed for deterministic encryption!\n"); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } + } else { + print_r($error); + die("Comparison failed for correct attestation when it shouldn't have!\n"); + } + } else { + print_r($error); + die("Unexpected error occurred in compareResults!\n"); + } + + return; + } + + $AEres = $AEstmt->fetchAll(PDO::FETCH_NUM); + $nonAEres = $nonAEstmt->fetchAll(PDO::FETCH_NUM); + $AEcount = count($AEres); + $nonAEcount = count($nonAEres); + + if ($type == 'char' or $type == 'nchar') { + // char and nchar may not return the same results - at this point + // we've verified that statement execution works so just return + // TODO: Check if this bug is fixed and if so, remove this if block + return; + } elseif ($AEcount > $nonAEcount) { + print_r("Too many AE results for operation $comparison and data type $type!\n"); + print_r($AEres); + print_r($nonAEres); + } elseif ($AEcount < $nonAEcount) { + print_r("Too many non-AE results for operation $comparison and data type $type!\n"); + print_r($AEres); + print_r($nonAEres); + } else { + if ($AEcount != 0) { + $i = 0; + foreach ($AEres as $AEr) { + if ($AEr[0] != $nonAEres[$i][0]) { + print_r("AE and non-AE results are different for operation $comparison and data type $type! For field $i, got AE result ".$AEres[$i][0]." and non-AE result ".$nonAEres[$i][0]."\n"); + } + ++$i; + } + } + } +} + +// testCompare selects based on a comparison in the WHERE clause and compares +// the results between encrypted and non-encrypted columns, checking that the +// results are identical +// Arguments: +// resource $conn: The connection +// string $tableName: Table name +// array $comparisons: Comparison operations from AE_v2_values.inc +// array $dataTypes: Data types from AE_v2_values.inc +// array $colNames: Column names +// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl' +function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, $attestation) +{ + foreach ($comparisons as $comparison) { + foreach ($dataTypes as $type) { + + // Unicode operations with AE require the Latin1_General_BIN2 + // collation. If the COLLATE clause is left out, we get different + // results between the encrypted and non-encrypted columns (probably + // because the collation was only changed in the encryption query). + $string = dataTypeIsStringMax($type); + $collate = $string ? " COLLATE Latin1_General_BIN2" : ""; + $unicode = dataTypeIsUnicode($type); + $PDOType = getPDOType($type); + + $AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE ".$comparison." ?".$collate; + $nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." ".$comparison." ?".$collate; + + try { + $AEstmt = $conn->prepare($AEQuery); + $AEstmt->bindParam(1, $thresholds[$type], $PDOType); + $nonAEstmt = $conn->prepare($nonAEQuery); + $nonAEstmt->bindParam(1, $thresholds[$type], $PDOType); + } catch (PDOException $error) { + print_r($error); + die("Preparing/binding statements for comparison failed"); + } + + compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $comparison, $type); + } + } +} + +// testPatternMatch selects based on a pattern in the WHERE clause and compares +// the results between encrypted and non-encrypted columns, checking that the +// results are identical +// Arguments: +// resource $conn: The connection +// string $tableName: Table name +// array $patterns: Patterns to match against, from AE_v2_values.inc +// array $dataTypes: Data types from AE_v2_values.inc +// array $colNames: Column names +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl' +function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation) +{ + foreach ($dataTypes as $type) { + + // TODO: Pattern matching doesn't work in AE for non-string types + // without an explicit cast + if (!dataTypeIsStringMax($type)) { + continue; + } + + foreach ($patterns[$type] as $pattern) { + + $patternArray = array($pattern, + $pattern."%", + "%".$pattern, + "%".$pattern."%", + ); + + foreach ($patternArray as $spattern) { + + // Unicode operations with AE require the Latin1_General_BIN2 + // collation. If the COLLATE clause is left out, we get different + // results between the encrypted and non-encrypted columns (probably + // because the collation was only changed in the encryption query). + $unicode = dataTypeIsUnicode($type); + $collate = $unicode ? " COLLATE Latin1_General_BIN2" : ""; + $PDOType = getPDOType($type); + + $AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE LIKE ?".$collate; + $nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." LIKE ?".$collate; + + try { + $AEstmt = $conn->prepare($AEQuery); + $AEstmt->bindParam(1, $spattern, $PDOType); + $nonAEstmt = $conn->prepare($nonAEQuery); + $nonAEstmt->bindParam(1, $spattern, $PDOType); + } catch (PDOException $error) { + print_r($error); + die("Preparing/binding statements for comparison failed"); + } + + compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $pattern, $type); + } + } + } +} + +function checkErrors($errors, ...$codes) +{ + $codeFound = false; + + foreach ($codes as $code) { + if ($code[0]==$errors[0] and $code[1]==$errors[1]) { + $codeFound = true; + break; + } + } + + if ($codeFound == false) { + echo "Error: "; + print_r($errors); + echo "\nExpected: "; + print_r($codes); + echo "\n"; + die("Error code not found.\n"); + } +} + +function isEnclaveEnabled($key) +{ + return (strpos($key, '-enclave') !== false); +} + +function dataTypeIsString($dataType) +{ + return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar"])); +} + +function dataTypeIsStringMax($dataType) +{ + return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"])); +} + +function dataTypeNeedsCollate($dataType) +{ + return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"])); +} + +function dataTypeIsUnicode($dataType) +{ + return (in_array($dataType, ["nchar", "nvarchar", "nvarchar(max)"])); +} + +function getPDOType($type) +{ + switch($type) { + case "bigint": + case "integer": + case "smallint": + case "tinyint": + return PDO::PARAM_INT; + case "bit": + return PDO::PARAM_BOOL; + case "real": + case "float": + case "double": + case "numeric": + case "time": + case "date": + case "datetime2": + case "datetime": + case "datetimeoffset": + case "smalldatetime": + case "money": + case "smallmoney"; + case "xml": + case "uniqueidentifier": + case "char": + case "varchar": + case "varchar(max)": + case "nchar": + case "nvarchar": + case "nvarchar(max)": + return PDO::PARAM_STR; + case "binary": + case "varbinary": + case "varbinary(max)": + return PDO::PARAM_LOB; + default: + die("Case is missing for $type type in getPDOType.\n"); + } +} + +?> diff --git a/test/functional/pdo_sqlsrv/pdo_aev2_ce_enabled.phpt b/test/functional/pdo_sqlsrv/pdo_aev2_ce_enabled.phpt new file mode 100644 index 000000000..2b603fe14 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_aev2_ce_enabled.phpt @@ -0,0 +1,93 @@ +--TEST-- +Try re-encrypting a table with ColumnEncryption set to 'enabled', which should fail. +--DESCRIPTION-- +This test cycles through $encryptionTypes and $keys, creating an encrypted table +each time, then cycles through $targetTypes and $targetKeys to try re-encrypting +the table with different combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Connect with correct attestation information. +2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted. +3. Insert some data. +4. Disconnect and reconnect with ColumnEncryption set to 'enabled'. +5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns. + Equality should work with deterministic encryption as in AE v1, but other computations should fail. +6. Try re-encrypting the table. This should fail. +--SKIPIF-- + +--FILE-- +query("DROP TABLE IF EXISTS $tableName"); + $stmt = $conn->query($createQuery); + } catch(Exception $error) { + print_r($error); + die("Creating an encrypted table failed when it shouldn't have!\n"); + } + + insertValues($conn, $insertQuery, $dataTypes, $testValues); + unset($conn); + + // Reconnect with ColumnEncryption set to 'enabled' + $newAttestation = 'enabled'; + $conn = connect($server, $newAttestation); + + if ($count == 0) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'enabled'); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'enabled'); + } + ++$count; + + if ($key == $targetKey and $encryptionType == $targetType) { + continue; + } + + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength); + + try { + $stmt = $conn->query($alterQuery); + + // Query should fail and trigger catch block before getting here + die("Encrypting should have failed with key $targetKey and encryption type $targetType\n"); + } catch (PDOException $error) { + if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33543')); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33546')); + } + } + } + } + } +} + +echo "Done.\n"; + +?> +--EXPECT-- +Done. diff --git a/test/functional/pdo_sqlsrv/pdo_aev2_encrypt_plaintext.phpt b/test/functional/pdo_sqlsrv/pdo_aev2_encrypt_plaintext.phpt new file mode 100644 index 000000000..59d545d38 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_aev2_encrypt_plaintext.phpt @@ -0,0 +1,136 @@ +--TEST-- +Test rich computations and in-place encryption of plaintext with AE v2. +--DESCRIPTION-- +This test cycles through $encryptionTypes and $keys, creating a plaintext table +each time, then trying to encrypt it with different combinations of enclave-enabled and non-enclave keys +and encryption types. It then cycles through $targetTypes and $targetKeys to try re-encrypting +the table with different target combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Create a table in plaintext with two columns for each AE-supported data type. +2. Insert some data in plaintext. +3. Encrypt one column for each data type. +4. Perform rich computations on each AE-enabled column (comparisons and pattern matching) and compare the result + to the same query on the corresponding non-AE column for each data type. +5. Ensure the two results are the same. +6. Re-encrypt the table using new key and/or encryption type. +7. Compare computations as in 4. above. +--SKIPIF-- + +--FILE-- +query("DBCC FREEPROCCACHE"); + + // Create and populate a non-encrypted table + $createQuery = constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength); + $insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE); + + try { + $stmt = $conn->query("DROP TABLE IF EXISTS $tableName"); + $stmt = $conn->query($createQuery); + } catch(Exception $error) { + print_r($error); + die("Creating table failed when it shouldn't have!\n"); + } + + insertValues($conn, $insertQuery, $dataTypes, $testValues); + + if ($count == 0) { + // Split the data type array, because for some reason we get an error + // if the query is too long (>2000 characters) + // TODO: This is a known issue, follow up on it. + $splitDataTypes = array_chunk($dataTypes, 5); + foreach ($splitDataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $key, $encryptionType, $slength); + $encryptionFailed = false; + + try { + $stmt = $conn->query($alterQuery); + if (!isEnclaveEnabled($key)) { + die("Encrypting should have failed with key $key and encryption type $encryptionType\n"); + } + } catch (PDOException $error) { + if (!isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33543')); + $encryptionFailed = true; + continue; + } else { + print_r($error); + die("Encrypting failed when it shouldn't have!\n"); + } + } + } + } + + if ($encryptionFailed) continue; + + if ($count == 0) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'correct'); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct'); + } + ++$count; + + if ($key == $targetKey and $encryptionType == $targetType) { + continue; + } + + // Try re-encrypting the table + foreach ($splitDataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength); + $encryptionFailed = false; + + try { + $stmt = $conn->query($alterQuery); + if (!isEnclaveEnabled($targetKey)) { + die("Encrypting should have failed with key $targetKey and encryption type $targetType\n"); + } + } catch (Exception $error) { + if (!isEnclaveEnabled($targetKey)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33543')); + $encryptionFailed = true; + continue; + } else { + print_r($error); + die("Encrypting failed when it shouldn't have!\n"); + } + } + } + + if ($encryptionFailed) { + continue; + } + + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $targetKey, $targetType, 'correct'); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct'); + } + } + } +} + +echo "Done.\n"; + +?> +--EXPECT-- +Done. diff --git a/test/functional/pdo_sqlsrv/pdo_aev2_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_aev2_keywords.phpt new file mode 100644 index 000000000..c2fa226e3 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_aev2_keywords.phpt @@ -0,0 +1,60 @@ +--TEST-- +Test various settings for the ColumnEncryption keyword. +--DESCRIPTION-- +For AE v2, the Column Encryption keyword must be set to [protocol]/[attestation URL]. +If [protocol] is wrong, connection should fail; if the URL is wrong, connection +should succeed. This test sets ColumnEncryption to three values: +1. Random nonsense, which is interpreted as an incorrect protocol + so connection should fail. +2. Incorrect protocol with a correct attestation URL, connection should fail. +3. Correct protocol and incorrect URL, connection should succeed. +--SKIPIF-- + +--FILE-- +errorInfo; + checkErrors($e, array('CE400', '0')); +} + +// Test with incorrect protocol and good attestation URL. Connection should fail. +// Insert a rogue 'x' into the protocol part of the attestation. +$comma = strpos($attestation, ','); +$badProtocol = substr_replace($attestation, 'x', $comma, 0); +$options = "sqlsrv:Server=$server;database=$databaseName;ColumnEncryption=$badProtocol"; + +try { + $conn = new PDO($options, $uid, $pwd); + die("Connection should have failed!\n"); +} catch(Exception $error) { + $e = $error->errorInfo; + checkErrors($e, array('CE400', '0')); +} + +// Test with good protocol and incorrect attestation URL. Connection should succeed +// because the URL is only checked when an enclave computation is attempted. +$badURL = substr_replace($attestation, 'x', $comma+1, 0); +$options = "sqlsrv:Server=$server;database=$databaseName;ColumnEncryption=$badURL"; + +try { + $conn = new PDO($options, $uid, $pwd); +} catch(Exception $error) { + print_r($error); + die("Connecting with a bad attestation URL should have succeeded!\n"); +} + +echo "Done.\n"; + +?> +--EXPECT-- +Done. diff --git a/test/functional/pdo_sqlsrv/pdo_aev2_reencrypt_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_aev2_reencrypt_encrypted.phpt new file mode 100644 index 000000000..c962ba3e6 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_aev2_reencrypt_encrypted.phpt @@ -0,0 +1,109 @@ +--TEST-- +Test rich computations and in place re-encryption with AE v2. +--DESCRIPTION-- +This test cycles through $encryptionTypes and $keys, creating an encrypted table +each time, then cycles through $targetTypes and $targetKeys to try re-encrypting +the table with different combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted. +2. Insert some data. +3. Perform rich computations on each AE-enabled column (comparisons and pattern matching) and compare the result + to the same query on the corresponding non-AE column for each data type. +4. Ensure the two results are the same. +5. Re-encrypt the table using new key and/or encryption type. +6. Compare computations as in 4. above. +--SKIPIF-- + +--FILE-- +query("DBCC FREEPROCCACHE"); + + // Create an encrypted table + $createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType); + $insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE); + + try { + $stmt = $conn->query("DROP TABLE IF EXISTS $tableName"); + $stmt = $conn->query($createQuery); + } catch(Exception $error) { + print_r($error); + die("Creating an encrypted table failed when it shouldn't have!\n"); + } + + insertValues($conn, $insertQuery, $dataTypes, $testValues); + + if ($count == 0) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'correct'); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct'); + } + ++$count; + + if ($key == $targetKey and $encryptionType == $targetType) { + continue; + } + + // Split the data type array, because for some reason we get an error + // if the query is too long (>2000 characters) + // TODO: This is a known issue, follow up on it. + $splitDataTypes = array_chunk($dataTypes, 5); + $encryptionFailed = false; + foreach ($splitDataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength); + + try { + $stmt = $conn->query($alterQuery); + if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) { + die("Encrypting should have failed with key $targetKey and encryption type $encryptionType\n"); + } + } catch (PDOException $error) { + if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33543')); + $encryptionFailed = true; + continue; + } else { + print_r($error); + die("Encrypting failed when it shouldn't have! key = $targetKey and type = $targetType\n"); + } + + continue; + } + } + + if ($encryptionFailed) { + continue; + } + + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $targetKey, $targetType, 'correct'); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct'); + } + } + } +} + +echo "Done.\n"; + +?> +--EXPECT-- +Done. diff --git a/test/functional/pdo_sqlsrv/pdo_aev2_wrong_attestation.phpt b/test/functional/pdo_sqlsrv/pdo_aev2_wrong_attestation.phpt new file mode 100644 index 000000000..da6708f20 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_aev2_wrong_attestation.phpt @@ -0,0 +1,95 @@ +--TEST-- +Try re-encrypting a table with ColumnEncryption set to the wrong attestation URL, which should fail. +--DESCRIPTION-- +This test cycles through $encryptionTypes and $keys, creating an encrypted table +each time, then cycles through $targetTypes and $targetKeys to try re-encrypting +the table with different combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Connect with correct attestation information. +2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted. +3. Insert some data. +4. Disconnect and reconnect with a faulty attestation URL. +5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns. + Equality should work with deterministic encryption as in AE v1, but other computations should fail. +6. Try re-encrypting the table. This should fail. +--SKIPIF-- + +--FILE-- +query("DROP TABLE IF EXISTS $tableName"); + $stmt = $conn->query($createQuery); + } catch(Exception $error) { + print_r($error); + die("Creating an encrypted table failed when it shouldn't have!\n"); + } + + insertValues($conn, $insertQuery, $dataTypes, $testValues); + unset($conn); + + // Reconnect with a faulty attestation URL + $comma = strpos($attestation, ','); + $newAttestation = substr_replace($attestation, 'x', $comma+1, 0); + + $conn = connect($server, $newAttestation); + + if ($count == 0) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'wrongurl'); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'wrongurl'); + } + ++$count; + + if ($key == $targetKey and $encryptionType == $targetType) { + continue; + } + + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength); + + try { + $stmt = $conn->query($alterQuery); + + // Query should fail and trigger catch block before getting here + die("Encrypting should have failed with key $targetKey and encryption type $targetType\n"); + } catch(Exception $error) { + if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33543')); + } else { + $e = $error->errorInfo; + checkErrors($e, array('CE405', '0')); + } + } + } + } + } +} + +echo "Done.\n"; + +?> +--EXPECT-- +Done. diff --git a/test/functional/pdo_sqlsrv/skipif_not_hgs.inc b/test/functional/pdo_sqlsrv/skipif_not_hgs.inc new file mode 100644 index 000000000..dd4614de8 --- /dev/null +++ b/test/functional/pdo_sqlsrv/skipif_not_hgs.inc @@ -0,0 +1,36 @@ +$uid, "PWD"=>$pwd, "Driver" => $driver); + +$conn = sqlsrv_connect( $server, $connectionInfo ); +if ($conn === false) { + die( "skip Could not connect during SKIPIF." ); +} + +$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + +if ($msodbcsql_maj < 17) { + die("skip Unsupported ODBC driver version"); +} + +if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) { + die("skip Unsupported ODBC driver version"); +} + +// Get SQL Server +$server_info = sqlsrv_server_info($conn); +if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) { + die("skip Server is not HGS enabled"); +} +?> diff --git a/test/functional/setup/AEV2Cert.pfx b/test/functional/setup/AEV2Cert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..4a9fc5bb8ac716acbe38b94c92b5226e410e2a0c GIT binary patch literal 2654 zcmZXUc|6qZ7QlZqeoV%aeI3R!ytHV>nmySSic%cZ_Uf`N8oT@> zeE@$VDr;Y!_G$G1nZ_1(H$7RfGw~K)+A^)La!_n7AW0}{!sKFr6z^?Aq4(5CC434- z{xYqs&-Hp(8`R(LU6vd0ebxpr*W@jvIXJAf@=>0KUi$dNgf%&gWT5^NA6oQ|8QDK_LnyU@_O!>!FZr>yh6hrYeuqg#XSJMU zH73!jn_|UJS!`&c5`*%OX{@K{zlf1wEPG8*W#V$q)afMH-*UtU_^uKB+MAA7aA_Fi zR$lWq(06DsR@g!yT}NW*1w@bS@-B7B?r^`Jw=izTyI##opHrJh(F4k9q}2mgd&1^5 zPWIrZLD@QRCoVm~1|cFlSGr9_FeZI8nq-YEZmMm(3!Qb6c{Qfue^tLK(|Ie1u`Pp2 zcfl#Cy-bf)AsK7m(x^0kFJrd4^=5W}YX&Pcr4mS$KCj}C9OS;g`GsVSqmZw+Opdsk z(tx$4$%pQ{xrr7cvpY+5#`rc=eh?y(9b{bP?kFfa8L?D5*-R%6`(gcysvzdWXNv+` z_vZQLCxm|76YtLDa;Wc#wF^oUOeMTI%ExPVxv8XV1k+BdXdAWb2&sq&K6y(3@_}85 z5D9ko+pl)b2=S-@e%l-5b0u~N$E6e&v8y|(C$0@^X*7-YKWx%k!OL3^b(DN-mb<+g z58xt#e3F7WK*Ep@)G7b{2|Rwv7(?b z*;C5a=h~<_lG3bleAL6oa$^!nK_Zdyc7b#%i}NRH?wJHL`8G zYmEbNL5udMXss51WqDa0Mft|6h{Tj`BlA%U-inAF-s5wbbA)eon zXz0{zesouUl-1%-V%cvUXA+J-S_K1Q z7GiS?R4cC?vQ*UaAgc_{+VVNYbZJ?&ZsrZ~o+qYE4Y^p2N<114`Wzu}jy8Y+ygB3? zpu^D^j-?G~0XR{VS0625*UnCIl;dtg8I|2yiupo|4;*1Ige1BId0LOmkg>gJQ=V>G~g=^#Q z_rrLKDO?{p=sgqyaMa{K5y<~9ewVcz=05WEjbDpDmcm5~U+f5%PU;v)RuXC=-R*aaCkXF<1!4yB)KBd}kSvjXFh zj7<` zKlZtx_wrv9T^Ok#pKZvZW5^4Jp9wb=$C9e|T^VW1Et2kPG3%6!A~-q%cVQ!D`{c?| zS}1J88crg3iP7<7bFqI&@lxLi39h~MJf|iufL~X^D>~sqahzmn#wZ98yk*bWWs{t` zJN7u!)}#TJpNY2lIujWPMv~rm7L>6p%4Ha;uFGWYgXkWdRm|$2i6Jp7Y-ltpqa>?I z!v47tGbdf)Uf0RXjC^9gru)GQP?F_Z`4`VraMS;sxWidb+oG7Qv&JF5l~)J&*M}qN z?VH~}eDgO{_bAlQsOohL5nEP0MOS-tm!1X#ZAJiU|993UeE?OmJW|sf&-R!r^1YYn zoF|Ip6GWbmYSEUNrKxX)u{PRFGDpmakKc=(ep`p^-thkNl_p#d&GU?lRTgh$ryiE| zd{LFdhHQJd>##H1_iI31*ZfQ^Q@cji5z>xw0|+>$eU^Tgqb zT&9@4X0=aial2lAXCWln;vh$o1i$8HWSMUUy8%uM^~r!mLM)pH#G0R7K2>$)F9F+`Ss!d E0&I$_5C8xG literal 0 HcmV?d00001 diff --git a/test/functional/setup/ae_keys.sql b/test/functional/setup/ae_keys.sql index 35c877209..d352a6f83 100644 --- a/test/functional/setup/ae_keys.sql +++ b/test/functional/setup/ae_keys.sql @@ -1,35 +1,98 @@ -/* DROP Column Encryption Key first, Column Master Key cannot be dropped until no encryption depends on it */ -IF EXISTS (SELECT * FROM sys.column_encryption_keys WHERE [name] LIKE '%AEColumnKey%') - +/* DROP Column Encryption Keys first, Column Master Keys cannot be dropped until no CEKs depend on them */ +IF EXISTS (SELECT * FROM sys.column_encryption_keys WHERE [name] LIKE '%AEColumnKey%' OR [name] LIKE '%-win-%') BEGIN DROP COLUMN ENCRYPTION KEY [AEColumnKey] +DROP COLUMN ENCRYPTION KEY [CEK-win-enclave] +DROP COLUMN ENCRYPTION KEY [CEK-win-enclave2] +DROP COLUMN ENCRYPTION KEY [CEK-win-noenclave] +DROP COLUMN ENCRYPTION KEY [CEK-win-noenclave2] END GO -/* Can finally drop Column Master Key after the Encryption Key is dropped */ -IF EXISTS (SELECT * FROM sys.column_master_keys WHERE [name] LIKE '%AEMasterKey%') - +/* Can finally drop Column Master Keys after the Column Encryption Keys are dropped */ +IF EXISTS (SELECT * FROM sys.column_master_keys WHERE [name] LIKE '%AEMasterKey%' OR [name] LIKE '%-win-%') BEGIN DROP COLUMN MASTER KEY [AEMasterKey] +DROP COLUMN MASTER KEY [CMK-win-enclave] +DROP COLUMN MASTER KEY [CMK-win-noenclave] END GO -/* Recreate the Column Master Key */ +/* Create the Column Master Keys */ +/* AKVMasterKey is a non-enclave enabled key for AE v1 testing */ +/* The enclave-enabled master key requires an ENCLAVE_COMPUTATIONS clause */ CREATE COLUMN MASTER KEY [AEMasterKey] WITH ( - KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE', - KEY_PATH = N'CurrentUser/my/237F94738E7F5214D8588006C2269DBC6B370816' + KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE', + KEY_PATH = N'CurrentUser/my/237F94738E7F5214D8588006C2269DBC6B370816' ) GO -/* Create Column Encryption Key using the Column Master Key */ +/* The enclave-enabled master key requires an ENCLAVE_COMPUTATIONS clause */ +CREATE COLUMN MASTER KEY [CMK-win-enclave] +WITH +( + KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE', + KEY_PATH = N'CurrentUser/My/D9C0572FA54B221D6591C473BAEA53FE61AAC854', + ENCLAVE_COMPUTATIONS (SIGNATURE = 0xA1150DE565E9C132D2AAB8FF8B228EAA8DA804F250B5B422874CB608A3B274DDE523E71B655A3EFC6C3018B632701E9205BAD80C178614E1FE821C6807B0E70BCF11168FC4B202638905C5F016EDBADACA23C696B79772C56825F36EB8C0366B130C91D85362E560C9D2FDD20DCAE99619256045CA2725DEC9E0C115CAEB9EA686CCB0DE0D53D2056C01752B17B634FC6DBB51EA043F607349489722DB8A086CBC876649284A8352822DD22B328E7BA3D671CCDF54CDAAF61DFD6AF2EAAC14E03897324234AB103C45AB48131C1CD19040782359FC920A0AF61BA9842ADFB76C3196CBC6EB9C0A679926ED63E092B7C8643232C97A64C7F918104C210787A56F) +) +GO + +CREATE COLUMN MASTER KEY [CMK-win-noenclave] +WITH +( + KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE', + KEY_PATH = N'CurrentUser/My/D9C0572FA54B221D6591C473BAEA53FE61AAC854' +) +GO + +/* Now we can create the Column Encryption Keys */ /* ENCRYPTED_VALUE is generated by SSMS and it is always the same if the same Certificate is imported */ CREATE COLUMN ENCRYPTION KEY [AEColumnKey] WITH VALUES ( - COLUMN_MASTER_KEY = [AEMasterKey], - ALGORITHM = 'RSA_OAEP', - ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00320033003700660039003400370033003800650037006600350032003100340064003800350038003800300030003600630032003200360039006400620063003600620033003700300038003100360039DE2397A08F6313E7820D75382D8469BE1C8F3CD47E3240A5A6D6F82D322F6EB1B103C9C47999A69FFB164D37E7891F60FFDB04ADEADEB990BE88AE488CAFB8774442DF909D2EF8BB5961A5C11B85BA7903E0E453B27B49CE0A30D14FF4F412B5737850A4C564B44C744E690E78FAECF007F9005E3E0FB4F8D6C13B016A6393B84BB3F83FEED397C4E003FF8C5BBDDC1F6156349A8B40EDC26398C9A03920DD81B9197BC83A7378F79ECB430A04B4CFDF3878B0219BB629F5B5BF3C2359A7498AD9A6F5D63EF15E060CDB10A65E6BF059C7A32237F0D9E00C8AC632CCDD68230774477D4F2E411A0E4D9B351E8BAA87793E64456370D91D4420B5FD9A252F6D9178AE3DD02E1ED57B7F7008114272419F505CBCEB109715A6C4331DEEB73653990A7140D7F83089B445C59E4858809D139658DC8B2781CB27A749F1CE349DC43238E1FBEAE0155BF2DBFEF6AFD9FD2BD1D14CEF9AC125523FD1120488F24416679A6041184A2719B0FC32B6C393FF64D353A3FA9BC4FA23DFDD999B0771A547B561D72B92A0B2BB8B266BC25191F2A0E2F8D93648F8750308DCD79BE55A2F8D5FBE9285265BEA66173CD5F5F21C22CC933AE2147F46D22BFF329F6A712B3D19A6488DDEB6FDAA5B136B29ADB0BA6B6D1FD6FBA5D6A14F76491CB000FEE4769D5B268A3BF50EA3FBA713040944558EDE99D38A5828E07B05236A4475DA27915E + COLUMN_MASTER_KEY = [AEMasterKey], + ALGORITHM = 'RSA_OAEP', + ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00320033003700660039003400370033003800650037006600350032003100340064003800350038003800300030003600630032003200360039006400620063003600620033003700300038003100360039DE2397A08F6313E7820D75382D8469BE1C8F3CD47E3240A5A6D6F82D322F6EB1B103C9C47999A69FFB164D37E7891F60FFDB04ADEADEB990BE88AE488CAFB8774442DF909D2EF8BB5961A5C11B85BA7903E0E453B27B49CE0A30D14FF4F412B5737850A4C564B44C744E690E78FAECF007F9005E3E0FB4F8D6C13B016A6393B84BB3F83FEED397C4E003FF8C5BBDDC1F6156349A8B40EDC26398C9A03920DD81B9197BC83A7378F79ECB430A04B4CFDF3878B0219BB629F5B5BF3C2359A7498AD9A6F5D63EF15E060CDB10A65E6BF059C7A32237F0D9E00C8AC632CCDD68230774477D4F2E411A0E4D9B351E8BAA87793E64456370D91D4420B5FD9A252F6D9178AE3DD02E1ED57B7F7008114272419F505CBCEB109715A6C4331DEEB73653990A7140D7F83089B445C59E4858809D139658DC8B2781CB27A749F1CE349DC43238E1FBEAE0155BF2DBFEF6AFD9FD2BD1D14CEF9AC125523FD1120488F24416679A6041184A2719B0FC32B6C393FF64D353A3FA9BC4FA23DFDD999B0771A547B561D72B92A0B2BB8B266BC25191F2A0E2F8D93648F8750308DCD79BE55A2F8D5FBE9285265BEA66173CD5F5F21C22CC933AE2147F46D22BFF329F6A712B3D19A6488DDEB6FDAA5B136B29ADB0BA6B6D1FD6FBA5D6A14F76491CB000FEE4769D5B268A3BF50EA3FBA713040944558EDE99D38A5828E07B05236A4475DA27915E +) +GO + +/* There are two enclave enabled keys and two non-enclave enabled keys to test the case where a user + tries to reencrypt a table from one enclave enabled key to another enclave enabled key, or from a + non-enclave key to another non-enclave key */ +CREATE COLUMN ENCRYPTION KEY [CEK-win-enclave] +WITH VALUES +( + COLUMN_MASTER_KEY = [CMK-win-enclave], + ALGORITHM = 'RSA_OAEP', + ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0064003900630030003500370032006600610035003400620032003200310064003600350039003100630034003700330062006100650061003500330066006500360031006100610063003800350034007382EDDDE3FFCE076D5715B6BBBD22EA64E665899BEFAAD5B329F218EE30BE9F789EB98717B6FD9E50AE496AC9FEED962B23442D4FD3FBFEC9C9B65F40A3BCEC7CFAC198F4CAEE8A255F67988289EF050F9F75D0287F3DF9A9FDA0C674E48DF2CB13298AAAD039930DD909EEE71682CC8A90202D3F2A1F1037BB20B1954C8B6A11F05D104CA9DAF1561C6B2F9DBB08BCE17244157B751C02FC1730E387F372C31327F2834D19AF626D0B46B152615F05FA2F3566350312CDE6DE1160B3C1D0FD35FAF13891C04711DF184DA501AA51D16BF009EA71A2D28E201804C6F8F9100E90234923B2713EA7988861FBA4E292E5518FFC02CCBD2513EDA871F6E03ECDDD309619557277C10A07906E55BA3F59A6A18834B4CD5185DA4B4574A18B8B1AC53A2C36B033D7A72443F1438E76E37306A1F92AC30BC751F6D7ED1633FEE807440E1D6096C53C5E3E33828C9C59E8761E5BAD341C6D9E2BD1F2B5C3992666620CAA38C4645C154976EF62AE80161A9F7700C96875A72995E1C585918B28F65060F1B8B96417328F6DEDFCA79ED9F01EAB19FF4E3163F9963BA26E9B58031A04320CC73702A6ED438513E0F8ABA1966B53114038CC587050F90D9CD0F9E26CA9749723ABA85CF31F963A5E85E04993B2B2869725E734BE8FCFD30A801825582730B49C00A2058C02D3312D6D8E82078FF4F77C5FF9CE6E9D140F1A4517635AB784 ) -GO \ No newline at end of file +GO + +CREATE COLUMN ENCRYPTION KEY [CEK-win-enclave2] +WITH VALUES +( + COLUMN_MASTER_KEY = [CMK-win-enclave], + ALGORITHM = 'RSA_OAEP', + ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0064003900630030003500370032006600610035003400620032003200310064003600350039003100630034003700330062006100650061003500330066006500360031006100610063003800350034006B4D40ABF0975AF7C5CA7D1F4345DE437318556F5A2380DCFE4AB792DC3A424EABC80EA24EE850FACD94F04809C8B32674C6FF2D966FA7F9F9E522990E5F5011515BA4B7EF3603619D8A4BF46AA9B769A8A4417462C4B0303F995F04964A2E328A503D87CD1AB85ECFCB8241D0C815540989DC33E58EDCCBAFF0753E196813E3FCCC5A3C9E4277DD528AE276F1F795973A4DF8D1BB3B1F405B5F35A6A583F0BB86BAD7FCADC1FCF6B14B602890109360FAB67D6A27DE542AE87784C40FEB9071AC34C4C40C92A6C153A4A38B6DA3AD48ED39E32D6D161ACE7EFE516B414139A831D878C13FF178649823C4EFDC8E5DB4C02F2147CC76965C01C2F3624EB809FD4F5C2E291056077B1ABEFF1F5001C1F4248704C7C70CF63DA1EBC2FEC4A3DF919BA4F6B465819BC4587599C2E7499CDE62D7C335CE7BBCFC72242A8F41C1B5C94DEB0A9AF49B723759A8CD9751EE70DDEBAFA1957382287F621790543841EBCCA0007BA030CAF29E9FBF8CEB4FEC88673F47B5EC3B5F759BBDD8ED2EAF572711D78286E4294B89FF6EBFEE4968B4596AF3B5C34985F28E886F6C211F385326F10ED62602007589FC494372902FB32B0E3D67A8C64F43A87B06EE9F2CF074EB6F3EC7A431733EDA8745051B7A4AA4C020797A9492E6A3BA643D031E491497BF17539993871085AC249D0AD82203CD442F69D6C686D26F4D17BA46B69D3CB7E395 +) +GO + +CREATE COLUMN ENCRYPTION KEY [CEK-win-noenclave] +WITH VALUES +( + COLUMN_MASTER_KEY = [CMK-win-noenclave], + ALGORITHM = 'RSA_OAEP', + ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00640039006300300035003700320066006100350034006200320032003100640036003500390031006300340037003300620061006500610035003300660065003600310061006100630038003500340042DC7A3AAAD184E01288C0913EFB6FEC6167CD8EA08A5F46ADCCC34D3AA6A1BDDDA15EA3DD219ED8795AB05C0111E48EA35A82ADDF2A206FACBBF4FD73D01C004DF627012D3950FEBCA4BBEDBDF97BA77033728D8873BA81E1C7BDCBE04BB3AA7EB42A1EDDBEF9B1CA9477ADA33F76711FEDF782CA1BD3C0104FDEB9E0D66DFCEC7D3C236906481B44F04457549658635322447742FB00B6D6F36A7CFCC56BB39F7280736BC25FD499F9CBA2F63CE11D53E536FD4A266929E06CF2BDBAF229894A77EDE140323B674ECF28C58C3E0B6C2E9407AD1A26776CB55D68B8286F64787CE5A468CFA27295D6069EFA5D65CD9A04602E861F4504F2611AAE6A8ADE33038A2BECE8BD7CF5B48567C217E324F11935C552FD25FE1FEFB152684BD1B3F8EB70EC9F6439340CE82CD8E74DD5986A6C4F9E8336ED4AC804FAD800A3EA324F78DCE37832035C3DC92782A06150916D01322A80767D1A36D7A8D9BCF6727DCE6AC67A168FA8B8B5032E60DCB178B21A860F2D98BE09DA9BA5DCCBD0D339369FF3C50C7993463372CF5B1DA9FAA12CD16E76F5961C01EADC5804C7F22227E2095BAD0F90A47B6330B1B43407E01DE5B61CEBD542A93797428AD84376E9362EADE6DDD103B9EC96E616A2ECED7D1D665B5B872E77FC024AD92AB4A8335D12D41BDD152790E87590798C1005956F9F92D4DD0C1C9852D147F7CB55B3224DE8EF593F +) +GO + +CREATE COLUMN ENCRYPTION KEY [CEK-win-noenclave2] +WITH VALUES +( + COLUMN_MASTER_KEY = [CMK-win-noenclave], + ALGORITHM = 'RSA_OAEP', + ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0064003900630030003500370032006600610035003400620032003200310064003600350039003100630034003700330062006100650061003500330066006500360031006100610063003800350034009014CD16FC878CEA2DE91C8C681AE86C7C062D8BD88C4CEE501A89FEAC47356D7181644A350F72B5F6023DA2B9E26C5A2522C08B1910D390068CF26794F4BA7B0298A6676B4DC6DED913E3B077B56224D2E1A3FE4EF33F58FE44CFC3DD67E54FB15BE8E29ABAF8357F378FBEDA3EBF9868A54746074D5E0E798047867E1ABD39AD0645BB8E071C72BFC37C007CBFC58F5690A5253F444E77169B2FE92FD95897A412B2078DA3804A00723D6DF824FCA527208A1DFB377B5BA16B620213F8252E10E7D7A3719A3FBB2F7A8189792B0BCF737236963C7DDCA6366F7B04F127925A1F8DDBB1B5A01D280BD300ECA3B1F31F24C8A0D517AE7BCBC3233A24E83B70A334754098DE373A1C027A4D09BB1D26C930E7501EB02464C519D19CFA0B296238AF11638C2E0688C7599E3DB1714AACF4EBFCEF63E1EE521A8E38E3BEFD4EF4991A15E8DD5CFD94E58E68754F3E90BC117025C01562F6440417A42612BE9C8871A18108CBE3E96DA7E35C45171C03E1DFBB3CA1E35A6D322F2D5B79E2BF2A07F14136DA4A768E08E2A7F1A42E04B717CB6AE3D1A3FA0EACCFC9CEC27DB53761E13DE1F55B410A65FB441D50CF8B2153B64925B1CEBDE062B5CAF4C99C41FED6836327037C46515710F16DC611305A0EBA1943A9BA5CC6889626990879713E9C95BB54D6A8A3C1C05A10AFE142B2487A1F0A07B57841E940CC9816E3F43CAE3CB7 +) +GO diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index 58900526d..ead94a30d 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -31,6 +31,8 @@ def setupAE(conn_options, dbname): # import self signed certificate inst_command = "certutil -user -p '' -importPFX My PHPcert.pfx NoRoot" executeCommmand(inst_command) + inst_command = "certutil -user -p '' -importPFX My AEV2Cert.pfx NoRoot" + executeCommmand(inst_command) # create Column Master Key and Column Encryption Key script_command = 'sqlcmd -I ' + conn_options + ' -i ae_keys.sql -d ' + dbname executeCommmand(script_command) diff --git a/test/functional/sqlsrv/AE_v2_values.inc b/test/functional/sqlsrv/AE_v2_values.inc new file mode 100644 index 000000000..721295b40 --- /dev/null +++ b/test/functional/sqlsrv/AE_v2_values.inc @@ -0,0 +1,163 @@ +5', 'fd4$_w@q^@!coe$7', 'abcd', 'ev72#x*fv=u$', '4rfg3sw', 'voi%###i<@@'); +$testValues['nchar'] = array('⽧㘎ⷅ㪋','af㋮ᶄḉㇼ៌ӗඣ','áŠãµ®à´–ᅥ㪮ኸ⮊ߒᙵꇕâ¯áž‚ꉟफ़⻦ꈔꇼŞ','ê·ê¬•','ã¯ã©§ã–ƒâºµã´°Ú‡à½£á§†ê²´ê••ê²‘וֹꔄ若㌉ᒵȅ㗉ꗅᡉ','ʭḪã…ᾔᎀã겶ꅫážã´‰á´³ãœžÒ‚','','בּŬḛʼꃺꌖ㓵ꗛ᧽ഭწ社⾯㬄౧ຸฬã¯ê‹›ã—¾'); +$testValues['varchar'] = array('gop093','*#$@@)%*$@!%','cio4*3do*$','zzz$a#l',' ','v#x%n!k&r@p$f^','6$gt?je#~','0x3dK#?'); +$testValues['nvarchar'] = array('á¾áº´ã”®ã––à­±Üã—㴴៸ழ᷂ᵄ葉អ㺓節','Ó•áµàµ´ê”“ὀ⾼','Ὡ','璉Džꖭ갪ụ⺭','Ӿϰᇬ㭡㇑ᵈᔆ⽹hᙎ՞ꦣ㧼ለͭ','Ĕ㬚㔈♠既','êˆ Ý«','ê†àª«â·Œã½Ì—ૣܯ¢⽳㌭ゴᔓᅄѓⷉꘊⶮáᴗஈԋ≡ㄊହꂈ꓂ꑽრꖾŞ⽉걹ꩰോఫ㒧㒾㑷藍㵀ဲï¤ê§¥'); +$testValues['varchar(max)'] = array('Q0H4@4E%v+ 3*Trx#>*r86-&d$VgjZ','AjEvVABur(A&Q@eG,A$3u"xAzl','z#dFd4z', + '9Dvsg9B?7oktB@|OIqy<\K^\e|*7Y&yH31E-<.hQ:)g Jl`MQV>rdOhjG;B4wQ(WR[`l(pELt0FYu._T3+8tns!}Nqrc1%n@|N|ik C@ 03a/ +H9mBq','SSs$Ie*{:D4;S]',' ','<\K^\e|*7Y&yH31E-<.hQ:','@Kg1Z6XTOgbt?CEJ|M^rkR_L4{1?l', '<=', '>=', '<>', '!<', '!>'); + +// Thresholds against which to use the comparison operators +$thresholds = array('integer' => 0, + 'bigint' => 0, + 'smallint' => 1000, + 'tinyint' => 100, + 'bit' => 0, + 'float' => 1.2, + 'real' => -1.2, + 'numeric' => 45.6789, + 'char' => 'rstuv', + 'nchar' => '㊃ᾞਲ㨴꧶êšê…', + 'varchar' => '6$gt?je#~', + 'nvarchar' => 'Ó•áµàµ´ê”“ὀ⾼', + 'varchar(max)' => 'hijkl', + 'nvarchar(max)' => 'xá•á›™á˜¡', + 'binary' => 0x44E4A, + 'varbinary' => 0xE4300FF, + 'varbinary(max)' => 0xD3EA762C78F, + 'date' => '2010-01-31', + 'time' => '21:45:45.4545', + 'datetime' => '3125-05-31 05:00:32.4', + 'datetime2' => '2384-12-31 12:40:12.5434323', + 'datetimeoffset' => '1984-09-25 10:40:20.0909111+03:00', + 'smalldatetime' => '1998-06-13 04:00:30', + ); + +// String patterns to test with LIKE +$patterns = array('integer' => array('8', '48', '123'), + 'bigint' => array('000','7', '65536'), + 'smallint' => array('4','768','abc'), + 'tinyint' => array('9','0','25'), + 'bit' => array('0','1','100'), + 'float' => array('14159','.','E+','2.3','308'), + 'real' => array('30','.','e-','2.3','38'), + 'numeric' => array('0','0000','12345','abc','.'), + 'char' => array('w','@','x*fv=u$','e3'), + 'nchar' => array('afã‹®','ã¯ê‹›ã—¾','ꦣ㧼ለͭ','123'), + 'varchar' => array(' ','a','#','@@)'), + 'nvarchar' => array('Ó•','Ӿϰᇬ㭡','璉Džꖭ갪ụ⺭','ï¤ê§¥','ꈔꇼŞ'), + 'varchar(max)' => array('A','|*7Y&','4z','@!@','AjE'), + 'nvarchar(max)' => array('t','㧶áቴƯɋ','ᘷ㬡',' ','ê¾É”ᡧãš'), + 'binary' => array('0x44E4A'), + 'varbinary' => array('0xE4300FF'), + 'varbinary(max)' => array('0xD3EA762C78F'), + 'date' => array('20','%','9-','04'), + 'time' => array('4545','.0','20:','12345',':'), + 'datetime' => array('997','12',':5','9999'), + 'datetime2' => array('3125-05-31 05:','.45','$f#','-29 ','0001'), + 'datetimeoffset' => array('+02','96',' ','5092856',':00'), + 'smalldatetime' => array('00','1999','abc',':','06'), + ); +?> diff --git a/test/functional/sqlsrv/MsSetup.inc b/test/functional/sqlsrv/MsSetup.inc index aec2b4bb1..8335c13b7 100644 --- a/test/functional/sqlsrv/MsSetup.inc +++ b/test/functional/sqlsrv/MsSetup.inc @@ -53,4 +53,6 @@ $AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPasswo $AKVClientID = 'TARGET_AKV_CLIENT_ID'; // for use with KeyVaultClientSecret $AKVSecret = 'TARGET_AKV_CLIENT_SECRET'; // for use with KeyVaultClientSecret +// for enclave computations +$attestation = 'TARGET_ATTESTATION'; ?> diff --git a/test/functional/sqlsrv/skipif_not_hgs.inc b/test/functional/sqlsrv/skipif_not_hgs.inc new file mode 100644 index 000000000..7d7b3ca1d --- /dev/null +++ b/test/functional/sqlsrv/skipif_not_hgs.inc @@ -0,0 +1,36 @@ +$userName, "PWD"=>$userPassword, "Driver" => $driver); + +$conn = sqlsrv_connect( $server, $connectionInfo ); +if ($conn === false) { + die( "skip Could not connect during SKIPIF." ); +} + +$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + +if ($msodbcsql_maj < 17) { + die("skip Unsupported ODBC driver version"); +} + +if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) { + die("skip Unsupported ODBC driver version"); +} + +// Get SQL Server +$server_info = sqlsrv_server_info($conn); +if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) { + die("skip Server is not HGS enabled"); +} +?> diff --git a/test/functional/sqlsrv/sqlsrv_AE_functions.inc b/test/functional/sqlsrv/sqlsrv_AE_functions.inc new file mode 100644 index 000000000..f7ef4e89f --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_AE_functions.inc @@ -0,0 +1,518 @@ +$database, + 'uid'=>$userName, + 'pwd'=>$userPassword, + 'CharacterSet'=>'UTF-8', + 'ColumnEncryption'=>$attestation_info, + ); + + if ($keystore == 'akv') { + if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') { + $security_info = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication, + 'KeyStorePrincipalId'=>$AKVPrincipalName, + 'KeyStoreSecret'=>$AKVPassword, + ); + } elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') { + $security_info = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication, + 'KeyStorePrincipalId'=>$AKVClientID, + 'KeyStoreSecret'=>$AKVSecret, + ); + } else { + die("Incorrect value for KeyStoreAuthentication keyword!\n"); + } + + $options = array_merge($options, $security_info); + } + + $conn = sqlsrv_connect($server, $options); + if (!$conn) { + echo "Connection failed\n"; + print_r(sqlsrv_errors()); + } + + // Check that enclave computations are enabled + // See https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/configure-always-encrypted-enclaves?view=sqlallproducts-allversions#configure-a-secure-enclave + $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; + $stmt = sqlsrv_query($conn, $query); + $info = sqlsrv_fetch_array($stmt); + if ($info['value'] != 1 or $info['value_in_use'] != 1) { + die("Error: enclave computations are not enabled on the server!"); + } + + // Enable rich computations + sqlsrv_query($conn, "DBCC traceon(127,-1);"); + + // Free the encryption cache to avoid spurious 'operand type clash' errors + sqlsrv_query($conn, "DBCC FREEPROCCACHE"); + + return $conn; +} + +// This CREATE TABLE query simply creates a non-encrypted table with +// two columns for each data type side by side +// This produces a query that looks like +// CREATE TABLE aev2test2 ( +// c_integer integer, +// c_integer_AE integer +// ) +function constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength) +{ + $query = "CREATE TABLE ".$tableName." (\n "; + + foreach ($dataTypes as $type) { + if (dataTypeIsString($type)) { + $query = $query.$colNames[$type]." ".$type."(".$slength."), \n "; + $query = $query.$colNamesAE[$type]." ".$type."(".$slength."), \n "; + } else { + $query = $query.$colNames[$type]." ".$type.", \n "; + $query = $query.$colNamesAE[$type]." ".$type.", \n "; + } + } + + // Remove the ", \n " from the end of the query or the comma will cause a syntax error + $query = substr($query, 0, -7)."\n)"; + + return $query; +} + +// The ALTER TABLE query encrypts columns. Each ALTER COLUMN directive must +// be preceded by ALTER TABLE +// This produces a query that looks like +// ALTER TABLE [dbo].[aev2test2] +// ALTER COLUMN [c_integer_AE] integer +// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL +// WITH +// (ONLINE = ON); ALTER TABLE [dbo].[aev2test2] +// ALTER COLUMN [c_bigint_AE] bigint +// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL +// WITH +// (ONLINE = ON); ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; +function constructAlterQuery($tableName, $colNames, $dataTypes, $key, $encryptionType, $slength) +{ + $query = ''; + + foreach ($dataTypes as $dataType) { + $plength = dataTypeIsString($dataType) ? "(".$slength.")" : ""; + $collate = dataTypeNeedsCollate($dataType) ? " COLLATE Latin1_General_BIN2" : ""; + $query = $query." ALTER TABLE [dbo].[".$tableName."] + ALTER COLUMN [".$colNames[$dataType]."] ".$dataType.$plength." ".$collate." + ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL + WITH + (ONLINE = ON);"; + } + + $query = $query." ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;"; + + return $query; +} + +// This CREATE TABLE query creates a table with two columns for +// each data type side by side, one plaintext and one encrypted +// This produces a query that looks like +// CREATE TABLE aev2test2 ( +// c_integer integer NULL, +// c_integer_AE integer +// COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL +// ) +function constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType) +{ + $query = "CREATE TABLE ".$tableName." (\n "; + + foreach ($dataTypes as $type) { + $collate = dataTypeNeedsCollate($type) ? " COLLATE Latin1_General_BIN2" : ""; + + if (dataTypeIsString($type)) { + $query = $query.$colNames[$type]." ".$type."(".$slength.") NULL, \n "; + $query = $query.$colNamesAE[$type]." ".$type."(".$slength.") \n "; + $query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n "; + } else { + $query = $query.$colNames[$type]." ".$type." NULL, \n "; + $query = $query.$colNamesAE[$type]." ".$type." \n "; + $query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n "; + } + } + + // Remove the ",\n " from the end of the query or the comma will cause a syntax error + $query = substr($query, 0, -6)."\n)"; + + return $query; +} + +// The INSERT query for the table +function constructInsertQuery($tableName, &$dataTypes, &$colNames, &$colNamesAE) +{ + $queryTypes = "("; + $valuesString = "VALUES ("; + + foreach ($dataTypes as $type) { + $colName1 = $colNames[$type].", "; + $colName2 = $colNamesAE[$type].", "; + $queryTypes .= $colName1; + $queryTypes .= $colName2; + $valuesString .= "?, ?, "; + } + + // Remove the ", " from the end of the query or the comma will cause a syntax error + $queryTypes = substr($queryTypes, 0, -2).")"; + $valuesString = substr($valuesString, 0, -2).")"; + + $insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString; + + return $insertQuery; +} + +function insertValues($conn, $insertQuery, $dataTypes, $testValues) +{ + for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) { + $insertValues = array(); + + // two copies of each value for the two columns for each data type + foreach ($dataTypes as $type) { + $insertValues[] = $testValues[$type][$v]; + $insertValues[] = $testValues[$type][$v]; + } + + // Insert the data using sqlsrv_prepare() + $stmt = sqlsrv_prepare($conn, $insertQuery, $insertValues); + if ($stmt == false) { + print_r(sqlsrv_errors()); + die("Inserting values in encrypted table failed at prepare\n"); + } + + if (sqlsrv_execute($stmt) == false) { + print_r(sqlsrv_errors()); + die("Inserting values in encrypted table failed at execute\n"); + } + } +} + +// compareResults checks that the results between the encrypted and non-encrypted +// columns are identical if statement execution succeeds. If statement execution +// fails, this function checks for the correct error. +// Arguments: +// statement $AEstmt: Prepared statement fetching encrypted data +// statement $nonAEstmt: Prepared statement fetching non-encrypted data +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl' +// string $comparison: Comparison operator +// string $type: Data type the comparison is operating on +function compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $comparison='', $type='') +{ + if (!sqlsrv_execute($nonAEstmt)) { + print_r(sqlsrv_errors()); + die("Executing non-AE statement failed!\n"); + } + + if(!sqlsrv_execute($AEstmt)) { + if ($attestation == 'enabled') { + if ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r(sqlsrv_errors()); + die("Equality comparison failed for deterministic encryption!\n"); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } + } elseif (isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33546')); + } elseif (!isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } + } elseif ($attestation == 'wrongurl') { + if ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r(sqlsrv_errors()); + die("Equality comparison failed for deterministic encryption!\n"); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } + } elseif (isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('CE405', '0')); + } elseif (!isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } + } elseif ($attestation == 'correct') { + if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } elseif ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r(sqlsrv_errors()); + die("Equality comparison failed for deterministic encryption!\n"); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } + } else { + print_r(sqlsrv_errors()); + die("Comparison failed for correct attestation when it shouldn't have!\n"); + } + } else { + print_r(sqlsrv_errors()); + die("Unexpected error occurred in compareResults!\n"); + } + } else { + // char and nchar may not return the same results - at this point + // we've verified that statement execution works so just return + // TODO: Check if this bug is fixed and if so, remove this if block + if ($type == 'char' or $type == 'nchar') { + return; + } + + while($AEres = sqlsrv_fetch_array($AEstmt, SQLSRV_FETCH_NUMERIC)) { + $nonAEres = sqlsrv_fetch_array($nonAEstmt, SQLSRV_FETCH_NUMERIC); + if (!$nonAEres) { + print_r($AEres); + print_r(sqlsrv_errors()); + print_r("Too many AE results for operation $comparison and data type $type!\n"); + } else { + $i = 0; + foreach ($AEres as $AEr) { + if ($AEr != $nonAEres[$i]) { + print_r("AE and non-AE results are different for operation $comparison and data type $type! For field $i, got AE result ".$AEres[$i]." and non-AE result ".$nonAEres[$i]."\n"); + print_r(sqlsrv_errors()); + } + ++$i; + } + } + } + + if ($rr = sqlsrv_fetch_array($nonAEstmt)) { + print_r($rr); + print_r(sqlsrv_errors()); + print_r("Too many non-AE results for operation $comparison and data type $type!\n"); + } + } +} + +// testCompare selects based on a comparison in the WHERE clause and compares +// the results between encrypted and non-encrypted columns, checking that the +// results are identical +// Arguments: +// resource $conn: The connection +// string $tableName: Thable name +// array $comparisons: Comparison operations from AE_v2_values.inc +// array $dataTypes: Data types from AE_v2_values.inc +// array $colNames: Column names +// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc +// string $key: Name of the encryption key +// integer $length: Length of the string types, from AE_v2_values.inc +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl' +function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestation) +{ + foreach ($comparisons as $comparison) { + foreach ($dataTypes as $type) { + + // Unicode operations with AE require the PHPTYPE to be specified to + // UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE + // clause is left out, we get different results between the + // encrypted and non-encrypted columns (probably because the + // collation was only changed in the encryption query). + $string = dataTypeIsStringMax($type); + $unicode = dataTypeIsUnicode($type); + $collate = $string ? " COLLATE Latin1_General_BIN2" : ""; + $phptype = $unicode ? SQLSRV_PHPTYPE_STRING('UTF-8') : null; + + $param = array(array($thresholds[$type], SQLSRV_PARAM_IN, $phptype, getSQLType($type, $length))); + $AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE ".$comparison." ?".$collate; + $nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." ".$comparison." ?".$collate; + + $AEstmt = sqlsrv_prepare($conn, $AEQuery, $param); + if (!$AEstmt) { + print_r(sqlsrv_errors()); + die("Preparing AE statement for comparison failed! Comparison $comparison, type $type\n"); + } + + $nonAEstmt = sqlsrv_prepare($conn, $nonAEQuery, $param); + if (!$nonAEstmt) { + print_r(sqlsrv_errors()); + die("Preparing non-AE statement for comparison failed! Comparison $comparison, type $type\n"); + } + + compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $comparison, $type); + } + } +} + +// testPatternMatch selects based on a pattern in the WHERE clause and compares +// the results between encrypted and non-encrypted columns, checking that the +// results are identical +function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation) +{ + // TODO: Pattern matching doesn't work in AE for non-string types + // without an explicit cast + foreach ($dataTypes as $type) { + if (!dataTypeIsStringMax($type)) { + continue; + } + + foreach ($patterns[$type] as $pattern) { + $patternarray = array($pattern, + $pattern."%", + "%".$pattern, + "%".$pattern."%", + ); + + foreach ($patternarray as $spattern) { + + // Unicode operations with AE require the PHPTYPE to be specified as + // UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE + // clause is left out, we get different results between the + // encrypted and non-encrypted columns (probably because the + // collation was only changed in the encryption query). + // We must pass the length of the pattern matching string + // to the SQLTYPE instead of the field size, as we usually would, + // because otherwise we would get an empty result set. + // We need iconv_strlen to return the number of characters + // for unicode strings, since strlen returns the number of bytes. + $unicode = dataTypeIsUnicode($type); + $slength = $unicode ? iconv_strlen($spattern) : strlen($spattern); + $collate = $unicode ? " COLLATE Latin1_General_BIN2" : ""; + $phptype = $unicode ? SQLSRV_PHPTYPE_STRING('UTF-8') : null; + $sqltype = $unicode ? SQLSRV_SQLTYPE_NCHAR($slength) : SQLSRV_SQLTYPE_CHAR($slength); + + $param = array(array($spattern, SQLSRV_PARAM_IN, $phptype, $sqltype)); + $AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE LIKE ?".$collate; + $nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." LIKE ?".$collate; + + $AEstmt = sqlsrv_prepare($conn, $AEQuery, $param); + if (!$AEstmt) { + print_r(sqlsrv_errors()); + die("Preparing AE statement for comparison failed! Comparison $comparison, type $type\n"); + } + + $nonAEstmt = sqlsrv_prepare($conn, $nonAEQuery, $param); + if (!$nonAEstmt) { + print_r(sqlsrv_errors()); + die("Preparing non-AE statement for comparison failed! Comparison $comparison, type $type\n"); + } + + compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $pattern, $type); + } + } + } +} + +// Check that the expected errors ($codes) is found in the output of sqlsrv_errors() ($errors) +function checkErrors($errors, ...$codes) +{ + $codeFound = false; + + foreach ($codes as $code) { + if ($code[0]==$errors[0][0] and $code[1]==$errors[0][1]) { + $codeFound = true; + break; + } + } + + if ($codeFound == false) { + echo "Error: "; + print_r($errors); + echo "\nExpected: "; + print_r($codes); + echo "\n"; + die("Error code not found.\n"); + } +} + +function isEnclaveEnabled($key) +{ + return (strpos($key, '-enclave') !== false); +} + +function dataTypeIsString($dataType) +{ + return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar"])); +} + +function dataTypeIsStringMax($dataType) +{ + return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"])); +} + +function dataTypeNeedsCollate($dataType) +{ + return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"])); +} + +function dataTypeIsUnicode($dataType) +{ + return (in_array($dataType, ["nchar", "nvarchar", "nvarchar(max)"])); +} + +function getSQLType($type, $length) +{ + switch($type) + { + case "bigint": + return SQLSRV_SQLTYPE_BIGINT; + case "integer": + return SQLSRV_SQLTYPE_INT; + case "smallint": + return SQLSRV_SQLTYPE_SMALLINT; + case "tinyint": + return SQLSRV_SQLTYPE_TINYINT; + case "bit": + return SQLSRV_SQLTYPE_BIT; + case "real": + return SQLSRV_SQLTYPE_REAL; + case "float": + case "double": + return SQLSRV_SQLTYPE_FLOAT; + case "numeric": + return SQLSRV_SQLTYPE_NUMERIC(18,0); + case "time": + return SQLSRV_SQLTYPE_TIME; + case "date": + return SQLSRV_SQLTYPE_DATE; + case "datetime": + return SQLSRV_SQLTYPE_DATETIME; + case "datetime2": + return SQLSRV_SQLTYPE_DATETIME2; + case "datetimeoffset": + return SQLSRV_SQLTYPE_DATETIMEOFFSET; + case "smalldatetime": + return SQLSRV_SQLTYPE_SMALLDATETIME; + case "money": + return SQLSRV_SQLTYPE_MONEY; + case "smallmoney": + return SQLSRV_SQLTYPE_SMALLMONEY; + case "xml": + return SQLSRV_SQLTYPE_XML; + case "uniqueidentifier": + return SQLSRV_SQLTYPE_UNIQUEIDENTIFIER; + case "char": + return SQLSRV_SQLTYPE_CHAR($length); + case "varchar": + return SQLSRV_SQLTYPE_VARCHAR($length); + case "varchar(max)": + return SQLSRV_SQLTYPE_VARCHAR('max'); + case "nchar": + return SQLSRV_SQLTYPE_NCHAR($length); + case "nvarchar": + return SQLSRV_SQLTYPE_NVARCHAR($length); + case "nvarchar(max)": + return SQLSRV_SQLTYPE_NVARCHAR('max'); + case "binary": + case "varbinary": + case "varbinary(max)": + // Using a binary type here produces a 'Restricted data type attribute violation' + return SQLSRV_SQLTYPE_BIGINT; + default: + die("Case is missing for $type type in getSQLType.\n"); + } +} + +?> diff --git a/test/functional/sqlsrv/sqlsrv_ae_fetch_phptypes.phpt b/test/functional/sqlsrv/sqlsrv_ae_fetch_phptypes.phpt index 59e184478..c492a961b 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_fetch_phptypes.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_fetch_phptypes.phpt @@ -15,7 +15,6 @@ function formulateSetupQuery($tableName, &$dataTypes, &$columns, &$insertQuery) { $columns = array(); $queryTypes = "("; - $queryTypesAE = "("; $valuesString = "VALUES ("; $numTypes = sizeof($dataTypes); diff --git a/test/functional/sqlsrv/sqlsrv_aev2_ce_enabled.phpt b/test/functional/sqlsrv/sqlsrv_aev2_ce_enabled.phpt new file mode 100644 index 000000000..186e93492 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_aev2_ce_enabled.phpt @@ -0,0 +1,113 @@ +--TEST-- +Try re-encrypting a table with ColumnEncryption set to 'enabled', which should fail. +--DESCRIPTION-- +This test cycles through $encryptionTypes and $keys, creating an encrypted table +each time, then cycles through $targetTypes and $targetKeys to try re-encrypting +the table with different combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Connect with correct attestation information. +2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted. +3. Insert some data. +4. Disconnect and reconnect with ColumnEncryption set to 'enabled'. +5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns. + Equality should work with deterministic encryption as in AE v1, but other computations should fail. +6. Try re-encrypting the table. This should fail. +--SKIPIF-- + +--FILE-- +2000 characters) + $splitDataTypes = array_chunk($dataTypes, 5); + $encryptionFailed = false; + + foreach ($splitDataTypes as $split) { + + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength); + $stmt = sqlsrv_query($conn, $alterQuery); + + if(!$stmt) { + if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) { + + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33543')); + $encryptionFailed = true; + continue; + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33546')); + $encryptionFailed = true; + continue; + } + + continue; + } else { + die("Encrypting should have failed with key $targetKey and encryption type $encryptionType!\n"); + } + } + + if ($encryptionFailed) { + continue; + } + } + } + } +} + +echo "Done.\n"; + +?> +--EXPECT-- +Done. diff --git a/test/functional/sqlsrv/sqlsrv_aev2_encrypt_plaintext.phpt b/test/functional/sqlsrv/sqlsrv_aev2_encrypt_plaintext.phpt new file mode 100644 index 000000000..b8daaacf3 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_aev2_encrypt_plaintext.phpt @@ -0,0 +1,138 @@ +--TEST-- +Test rich computations and in-place encryption of plaintext with AE v2. +--DESCRIPTION-- +This test cycles through $encryptionTypes and $keys, creating a plaintext table +each time, then trying to encrypt it with different combinations of enclave-enabled and non-enclave keys +and encryption types. It then cycles through $targetTypes and $targetKeys to try re-encrypting +the table with different target combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Create a table in plaintext with two columns for each AE-supported data type. +2. Insert some data in plaintext. +3. Encrypt one column for each data type. +4. Perform rich computations on each AE-enabled column (comparisons and pattern matching) and compare the result + to the same query on the corresponding non-AE column for each data type. +5. Ensure the two results are the same. +6. Re-encrypt the table using new key and/or encryption type. +7. Compare computations as in 4. above. +--SKIPIF-- + +--FILE-- +2000 characters) + // TODO: This is a known issue, follow up on it. + $splitDataTypes = array_chunk($dataTypes, 5); + foreach ($splitDataTypes as $split) + { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $key, $encryptionType, $slength); + + $stmt = sqlsrv_query($conn, $alterQuery); + $encryptionFailed = false; + + if(!$stmt) { + if (!isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33543')); + $encryptionFailed = true; + continue; + } else { + print_r(sqlsrv_errors()); + die("Encrypting failed when it shouldn't have!\n"); + } + } else { + if (!isEnclaveEnabled($key)) { + die("Encrypting should have failed with key $key and encryption type $encryptionType\n"); + } + } + } + } + + if ($encryptionFailed) continue; + + if ($count == 0) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, 'correct'); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct'); + } + ++$count; + + if ($key == $targetKey and $encryptionType == $targetType) { + continue; + } + + // Try re-encrypting the table + $encryptionFailed = false; + foreach ($splitDataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength); + + $stmt = sqlsrv_query($conn, $alterQuery); + if(!$stmt) { + if (!isEnclaveEnabled($targetKey)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33543')); + $encryptionFailed = true; + continue; + } else { + print_r(sqlsrv_errors()); + die("Encrypting failed when it shouldn't have!\n"); + } + } else { + if (!isEnclaveEnabled($targetKey)) { + die("Encrypting should have failed with key $targetKey and encryption type $targetType\n"); + } + } + } + + if ($encryptionFailed) { + continue; + } + + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, 'correct'); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct'); + } + } + } +} + +echo "Done.\n"; + +?> +--EXPECT-- +Done. diff --git a/test/functional/sqlsrv/sqlsrv_aev2_keywords.phpt b/test/functional/sqlsrv/sqlsrv_aev2_keywords.phpt new file mode 100644 index 000000000..d236a2a4f --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_aev2_keywords.phpt @@ -0,0 +1,71 @@ +--TEST-- +Test various settings for the ColumnEncryption keyword. +--DESCRIPTION-- +For AE v2, the Column Encryption keyword must be set to [protocol]/[attestation URL]. +If [protocol] is wrong, connection should fail; if the URL is wrong, connection +should succeed. This test sets ColumnEncryption to three values: +1. Random nonsense, which is interpreted as an incorrect protocol + so connection should fail. +2. Incorrect protocol with a correct attestation URL, connection should fail. +3. Correct protocol and incorrect URL, connection should succeed. +--SKIPIF-- + +--FILE-- +$database, + 'uid'=>$userName, + 'pwd'=>$userPassword, + 'ColumnEncryption'=>"xyz", + ); + +$conn = sqlsrv_connect($server, $options); +if (!$conn) { + $e = sqlsrv_errors(); + checkErrors($e, array('CE400', '0')); +} else { + die("Connecting with nonsense should have failed!\n"); +} + +// Test with incorrect protocol and good attestation URL. Connection should fail. +// Insert a rogue 'x' into the protocol part of the attestation. +$comma = strpos($attestation, ','); +$badProtocol = substr_replace($attestation, 'x', $comma, 0); +$options = array('database'=>$database, + 'uid'=>$userName, + 'pwd'=>$userPassword, + 'ColumnEncryption'=>$badProtocol, + ); + +$conn = sqlsrv_connect($server, $options); +if (!$conn) { + $e = sqlsrv_errors(); + checkErrors($e, array('CE400', '0')); +} else { + die("Connecting with a bad attestation protocol should have failed!\n"); +} + +// Test with good protocol and incorrect attestation URL. Connection should succeed +// because the URL is only checked when an enclave computation is attempted. +$badURL = substr_replace($attestation, 'x', $comma+1, 0); +$options = array('database'=>$database, + 'uid'=>$userName, + 'pwd'=>$userPassword, + 'ColumnEncryption'=>$badURL, + ); + +$conn = sqlsrv_connect($server, $options); +if (!$conn) { + print_r(sqlsrv_errors()); + die("Connecting with a bad attestation URL should have succeeded!\n"); +} + +echo "Done.\n"; + +?> +--EXPECT-- +Done. diff --git a/test/functional/sqlsrv/sqlsrv_aev2_reencrypt_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_aev2_reencrypt_encrypted.phpt new file mode 100644 index 000000000..002f61ac7 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_aev2_reencrypt_encrypted.phpt @@ -0,0 +1,110 @@ +--TEST-- +Test rich computations and in place re-encryption with AE v2. +--DESCRIPTION-- +This test cycles through $encryptionTypes and $keys, creating an encrypted table +each time, then cycles through $targetTypes and $targetKeys to try re-encrypting +the table with different combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted. +2. Insert some data. +3. Perform rich computations on each AE-enabled column (comparisons and pattern matching) and compare the result + to the same query on the corresponding non-AE column for each data type. +4. Ensure the two results are the same. +5. Re-encrypt the table using new key and/or encryption type. +6. Compare computations as in 4. above. +--SKIPIF-- + +--FILE-- +2000 characters) + // TODO: This is a known issue, follow up on it. + $splitDataTypes = array_chunk($dataTypes, 5); + $encryptionFailed = false; + + foreach ($splitDataTypes as $split) { + + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength); + $stmt = sqlsrv_query($conn, $alterQuery); + + if(!$stmt) { + if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33543')); + $encryptionFailed = true; + continue; + } else { + print_r(sqlsrv_errors()); + die("Encrypting failed when it shouldn't have! key = $targetKey and type = $targetType\n"); + } + + continue; + } else { + if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) { + die("Encrypting should have failed with key $targetKey and encryption type $encryptionType\n"); + } + } + } + + if ($encryptionFailed) { + continue; + } + + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, 'correct'); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct'); + } + } + } +} + +echo "Done.\n"; + +?> +--EXPECT-- +Done. diff --git a/test/functional/sqlsrv/sqlsrv_aev2_wrong_attestation.phpt b/test/functional/sqlsrv/sqlsrv_aev2_wrong_attestation.phpt new file mode 100644 index 000000000..a42cabaf4 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_aev2_wrong_attestation.phpt @@ -0,0 +1,93 @@ +--TEST-- +Try re-encrypting a table with ColumnEncryption set to the wrong attestation URL, which should fail. +--DESCRIPTION-- +This test cycles through $encryptionTypes and $keys, creating an encrypted table +each time, then cycles through $targetTypes and $targetKeys to try re-encrypting +the table with different combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Connect with correct attestation information. +2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted. +3. Insert some data. +4. Disconnect and reconnect with a faulty attestation URL. +5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns. + Equality should work with deterministic encryption as in AE v1, but other computations should fail. +6. Try re-encrypting the table. This should fail. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done. diff --git a/test/functional/sqlsrv/test_ae_keys_setup.phpt b/test/functional/sqlsrv/test_ae_keys_setup.phpt index 53a2c0fa6..d2e3850d7 100644 --- a/test/functional/sqlsrv/test_ae_keys_setup.phpt +++ b/test/functional/sqlsrv/test_ae_keys_setup.phpt @@ -1,8 +1,8 @@ --TEST-- Test the existence of Windows Always Encrypted keys generated in the database setup --DESCRIPTION-- -This test iterates through the rows of sys.column_master_keys and/or -sys.column_encryption_keys to look for the specific column master key and +This test iterates through the rows of sys.column_master_keys and/or +sys.column_encryption_keys to look for the specific column master key and column encryption key generated in the database setup --SKIPIF-- @@ -44,8 +44,8 @@ if (AE\IsQualified($conn)) { sqlsrv_free_stmt($stmt); } -echo "Test Successfully done.\n"; +echo "Test successfully done.\n"; sqlsrv_close($conn); ?> --EXPECT-- -Test Successfully done. +Test successfully done. From e30752fc6c23fc33b779dca403b8c28b48cb199f Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 6 Nov 2019 08:21:38 -0800 Subject: [PATCH 177/249] Modified pdo tests to work with column encryption (#1051) --- .../pdo_1018_emulate_prepare_natl_char.phpt | 13 ++++++++++++- .../pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt index 5c389bb9f..02d37c204 100644 --- a/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1018_emulate_prepare_natl_char.phpt @@ -101,7 +101,18 @@ try { echo "Done\n"; } catch (PdoException $e) { - echo $e->getMessage() . PHP_EOL; + if (isAEConnected()) { + // The Always Encrypted feature does not support emulate prepare for binding parameters + $expected = '*Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection.'; + if (!fnmatch($expected, $e->getMessage())) { + echo "Unexpected exception caught when connecting with Column Encryption enabled:\n"; + echo $e->getMessage() . PHP_EOL; + } else { + echo "Done\n"; + } + } else { + echo $e->getMessage() . PHP_EOL; + } } ?> diff --git a/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt index e058994e5..9ee0dcceb 100644 --- a/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt @@ -22,7 +22,7 @@ function insertRead($conn, $pdoStrParam, $value, $testCase, $id, $encoding = fal { global $p, $tableName; - $sql = "INSERT INTO $tableName VALUES (:value)"; + $sql = "INSERT INTO $tableName (Col1) VALUES (:value)"; $options = array(PDO::ATTR_EMULATE_PREPARES => false); // it's false by default anyway $stmt = $conn->prepare($sql, $options); From b77bfa82f44134a0f610178492146ab3707a2998 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 6 Nov 2019 13:14:28 -0800 Subject: [PATCH 178/249] Saved php types with metadata when fetching (#1049) --- source/pdo_sqlsrv/pdo_stmt.cpp | 11 ++- source/shared/core_sqlsrv.h | 8 ++ source/shared/core_stmt.cpp | 61 +++++++----- .../pdo_569_query_varcharmax_ae.phpt | 81 ++++++++++++++++ .../sqlsrv/srv_569_query_varcharmax_ae.phpt | 96 +++++++++++++++++++ 5 files changed, 231 insertions(+), 26 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_569_query_varcharmax_ae.phpt create mode 100644 test/functional/sqlsrv/srv_569_query_varcharmax_ae.phpt diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index c28aa1bcf..448eee04a 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -781,8 +781,12 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, "Invalid column number in pdo_sqlsrv_stmt_get_col_data" ); // set the encoding if the user specified one via bindColumn, otherwise use the statement's encoding - sqlsrv_php_type = driver_stmt->sql_type_to_php_type( static_cast( driver_stmt->current_meta_data[colno]->field_type ), - static_cast( driver_stmt->current_meta_data[colno]->field_size ), true ); + // save the php type for next use + sqlsrv_php_type = driver_stmt->sql_type_to_php_type( + static_cast(driver_stmt->current_meta_data[colno]->field_type), + static_cast(driver_stmt->current_meta_data[colno]->field_size), + true); + driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type; // if a column is bound to a type different than the column type, figure out a way to convert it to the // type they want @@ -825,6 +829,9 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, break; } } + + // save the php type for the bound column + driver_stmt->current_meta_data[colno]->sqlsrv_php_type = sqlsrv_php_type; } SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 28f46f689..be6ead07b 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1576,15 +1576,23 @@ struct field_meta_data { SQLSMALLINT field_scale; SQLSMALLINT field_is_nullable; bool field_is_money_type; + sqlsrv_phptype sqlsrv_php_type; field_meta_data() : field_name_len(0), field_type(0), field_size(0), field_precision(0), field_scale (0), field_is_nullable(0), field_is_money_type(false) { + reset_php_type(); } ~field_meta_data() { } + + void reset_php_type() + { + sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID; + } }; // *** statement constants *** diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index be1b00ec6..090322a5f 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -244,6 +244,12 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) // delete sensivity data clean_up_sensitivity_metadata(); + // reset sqlsrv php type in meta data + size_t num_fields = this->current_meta_data.size(); + for (size_t f = 0; f < num_fields; f++) { + this->current_meta_data[f]->reset_php_type(); + } + // create a new result set if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { sqlsrv_malloc_auto_ptr result; @@ -1121,9 +1127,6 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i sqlsrv_phptype sqlsrv_php_type = sqlsrv_php_type_in; - SQLLEN sql_field_type = 0; - SQLLEN sql_field_len = 0; - // Make sure that the statement was executed and not just prepared. CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) { throw core::CoreException(); @@ -1132,37 +1135,47 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // if the field is to be cached, and this field is being retrieved out of order, cache prior fields so they // may also be retrieved. if( cache_field && (field_index - stmt->last_field_index ) >= 2 ) { - sqlsrv_phptype invalid; - invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; - for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { - SQLSRV_ASSERT( reinterpret_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), i )) == NULL, "Field already cached." ); - core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out TSRMLS_CC ); - // delete the value returned since we only want it cached, not the actual value - if( field_value ) { - efree( field_value ); - field_value = NULL; - *field_len = 0; - } - } + sqlsrv_phptype invalid; + invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; + for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { + SQLSRV_ASSERT( reinterpret_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), i )) == NULL, "Field already cached." ); + core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out TSRMLS_CC ); + // delete the value returned since we only want it cached, not the actual value + if( field_value ) { + efree( field_value ); + field_value = NULL; + *field_len = 0; + } + } } // If the php type was not specified set the php type to be the default type. if (sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) { SQLSRV_ASSERT(stmt->current_meta_data.size() > field_index, "core_sqlsrv_get_field - meta data vector not in sync" ); - sql_field_type = stmt->current_meta_data[field_index]->field_type; - if (stmt->current_meta_data[field_index]->field_precision > 0) { - sql_field_len = stmt->current_meta_data[field_index]->field_precision; + + // Get the corresponding php type from the sql type and then save the result for later + if (stmt->current_meta_data[field_index]->sqlsrv_php_type.typeinfo.type == SQLSRV_PHPTYPE_INVALID) { + SQLLEN sql_field_type = 0; + SQLLEN sql_field_len = 0; + + sql_field_type = stmt->current_meta_data[field_index]->field_type; + if (stmt->current_meta_data[field_index]->field_precision > 0) { + sql_field_len = stmt->current_meta_data[field_index]->field_precision; + } + else { + sql_field_len = stmt->current_meta_data[field_index]->field_size; + } + sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast(sql_field_type), static_cast(sql_field_len), prefer_string); + stmt->current_meta_data[field_index]->sqlsrv_php_type = sqlsrv_php_type; } else { - sql_field_len = stmt->current_meta_data[field_index]->field_size; + // use the previously saved php type + sqlsrv_php_type = stmt->current_meta_data[field_index]->sqlsrv_php_type; } - - // Get the corresponding php type from the sql type. - sqlsrv_php_type = stmt->sql_type_to_php_type(static_cast(sql_field_type), static_cast(sql_field_len), prefer_string); - } + } // Verify that we have an acceptable type to convert. - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_php_type ), stmt, SQLSRV_ERROR_INVALID_TYPE ) { + CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_phptype(sqlsrv_php_type), stmt, SQLSRV_ERROR_INVALID_TYPE) { throw core::CoreException(); } diff --git a/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax_ae.phpt b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax_ae.phpt new file mode 100644 index 000000000..d41175a24 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_569_query_varcharmax_ae.phpt @@ -0,0 +1,81 @@ +--TEST-- +GitHub issue #569 - direct query on varchar max fields results in function sequence error (Always Encrypted) +--DESCRIPTION-- +This is similar to pdo_569_query_varcharmax.phpt but is not limited to testing the Always Encrypted feature in Windows only. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $tableName = 'pdoTestTable_569_ae'; + createTable($conn, $tableName, array(new ColumnMeta("int", "id", "IDENTITY"), "c1" => "nvarchar(max)")); + + $input = array(); + + $input[0] = 'some very large string'; + $input[1] = '1234567890.1234'; + $input[2] = 'über über'; + + $numRows = 3; + $tsql = "INSERT INTO $tableName (c1) VALUES (?)"; + + $stmt = $conn->prepare($tsql); + for ($i = 0; $i < $numRows; $i++) { + $stmt->bindParam(1, $input[$i]); + $stmt->execute(); + } + + $tsql = "SELECT id, c1 FROM $tableName ORDER BY id"; + $stmt = $conn->prepare($tsql); + $stmt->execute(); + + // Fetch one row each time with different pdo type and/or encoding + $result = $stmt->fetch(PDO::FETCH_NUM); + if ($result[1] !== $input[0]) { + echo "Expected $input[0] but got: "; + var_dump($result[0]); + } + + $stmt->bindColumn(2, $value, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_SYSTEM); + $result = $stmt->fetch(PDO::FETCH_BOUND); + if (!$result || $value !== $input[1]) { + echo "Expected $input[1] but got: "; + var_dump($result); + } + + $stmt->bindColumn(2, $value, PDO::PARAM_STR); + $result = $stmt->fetch(PDO::FETCH_BOUND); + if (!$result || $value !== $input[2]) { + echo "Expected $input[2] but got: "; + var_dump($value); + } + + // Fetch again but all at once + $stmt->execute(); + $rows = $stmt->fetchall(PDO::FETCH_ASSOC); + for ($i = 0; $i < $numRows; $i++) { + $i = $rows[$i]['id'] - 1; + if ($rows[$i]['c1'] !== $input[$i]) { + echo "Expected $input[$i] but got: "; + var_dump($rows[$i]['c1']); + } + } + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + echo $e->getMessage(); +} + +echo "Done\n"; + +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/srv_569_query_varcharmax_ae.phpt b/test/functional/sqlsrv/srv_569_query_varcharmax_ae.phpt new file mode 100644 index 000000000..4aca5a590 --- /dev/null +++ b/test/functional/sqlsrv/srv_569_query_varcharmax_ae.phpt @@ -0,0 +1,96 @@ +--TEST-- +GitHub issue #569 - sqlsrv_query on varchar max fields results in function sequence error +--DESCRIPTION-- +This is similar to srv_569_query_varcharmax.phpt but is not limited to testing the Always Encrypted feature in Windows only. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +'UTF-8')); + +$tableName = 'srvTestTable_569_ae'; + +$columns = array(new AE\ColumnMeta('int', 'id', 'identity'), + new AE\ColumnMeta('nvarchar(max)', 'c1')); +AE\createTable($conn, $tableName, $columns); + +$input = array(); + +$input[0] = 'some very large string'; +$input[1] = '1234567890.1234'; +$input[2] = 'über über'; + +$numRows = 3; +$isql = "INSERT INTO $tableName (c1) VALUES (?)"; +for ($i = 0; $i < $numRows; $i++) { + $stmt = sqlsrv_prepare($conn, $isql, array($input[$i])); + $result = sqlsrv_execute($stmt); + if (!$result) { + fatalError("Failed to insert row $i into $tableName"); + } +} + +// Select all from test table +$tsql = "SELECT id, c1 FROM $tableName ORDER BY id"; +$stmt = sqlsrv_prepare($conn, $tsql); +if (!$stmt) { + fatalError("Failed to read from $tableName"); +} +$result = sqlsrv_execute($stmt); +if (!$result) { + fatalError("Failed to select data from $tableName"); +} + +// Fetch each row as an array +while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { + $i = $row['id'] - 1; + if ($row['c1'] !== $input[$i]) { + echo "Expected $input[$i] but got: "; + var_dump($fieldVal); + } +} + +// Fetch again, one field each time +sqlsrv_execute($stmt); + +$i = 0; +while ($i < $numRows) { + sqlsrv_fetch($stmt); + + switch ($i) { + case 0: + $fieldVal = sqlsrv_get_field($stmt, 1, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); + break; + case 1: + $stream = sqlsrv_get_field($stmt, 1); + while (!feof( $stream)) { + $fieldVal = fread($stream, 50); + } + break; + default: + $fieldVal = sqlsrv_get_field($stmt, 1, SQLSRV_PHPTYPE_STRING('utf-8')); + break; + } + + if ($fieldVal !== $input[$i]) { + echo 'Expected $input[$i] but got: '; + var_dump($fieldVal); + } + + $i++; +} + +dropTable($conn, $tableName); + +echo "Done\n"; + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +Done \ No newline at end of file From 006157a5c2825bf897bb6631b9fb406d0cf1c385 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 14 Nov 2019 14:41:22 -0800 Subject: [PATCH 179/249] Updated survey charts for Nov 2019 (#1057) --- media/os_development.PNG | Bin 21753 -> 25526 bytes media/os_production.PNG | Bin 22158 -> 25457 bytes media/php_versions.PNG | Bin 11432 -> 12929 bytes media/sql_server.PNG | Bin 17235 -> 19816 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/media/os_development.PNG b/media/os_development.PNG index 5bd527901fe02cc98ea93ced0f231a351c8f0e57..6f2bd8611515d0fca455d7ffbd728f6b884735b2 100644 GIT binary patch literal 25526 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 Aw*UYD diff --git a/media/php_versions.PNG b/media/php_versions.PNG index 98416fe81b028903d03f43422c9c075c7b95481d..b241f3a3f67b8be4056f933a62400e035df7dea4 100644 GIT binary patch literal 12929 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 literal 17235 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 Wpb1Zi;;$iNUocmuIH=4u% From 75e421c0a16f3ac9f7692863594716c488b2cf76 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 19 Nov 2019 12:24:35 -0800 Subject: [PATCH 180/249] Updated all CIs (#1058) --- .travis.yml | 2 +- appveyor.yml | 8 ++++---- azure-pipelines.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index d908eff64..e368b84e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ env: - TEST_PHP_SQL_PWD=Password123 before_install: - - docker pull mcr.microsoft.com/mssql/server:2019-CTP3.2-ubuntu + - docker pull mcr.microsoft.com/mssql/server:2019-GA-ubuntu-16.04 install: - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-CTP3.2-ubuntu diff --git a/appveyor.yml b/appveyor.yml index ae779335e..ef9870ef5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,7 +23,7 @@ environment: TEST_PHP_SQL_SERVER: (local)\SQL2017 SQL_INSTANCE: SQL2017 PHP_VC: 15 - PHP_MAJOR_VER: 7.2 + PHP_MAJOR_VER: 7.3 PHP_MINOR_VER: 11 PHP_EXE_PATH: x64\Release_TS THREAD: ts @@ -39,7 +39,7 @@ environment: THREAD: nts platform: x86 -# PHP_MAJOR_VER is PHP major version to build (7.2, 7.1) +# PHP_MAJOR_VER is PHP major version to build (7.2, 7.3) # PHP_MINOR_VER is PHP point release number (or latest for latest release) # PHP_VC is the Visual C++ version # PHP_EXE_PATH is the relative path from php src folder to php executable @@ -83,8 +83,8 @@ install: } - echo Downloading MSODBCSQL 17 # AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it - - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.4.1.1_x64.msi', 'c:\projects\msodbcsql_17.4.1.1_x64.msi') - - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.4.1.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.4.2.1_x64.msi', 'c:\projects\msodbcsql_17.4.2.1_x64.msi') + - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.4.2.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL - echo Checking the version of MSODBCSQL - reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server" - dir %WINDIR%\System32\msodbcsql*.dll diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3f048788b..23b9d72c5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -60,7 +60,7 @@ jobs: - job: Linux pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-18.04' steps: - checkout: self clean: true @@ -85,7 +85,7 @@ jobs: sudo apt-get purge unixodbc sudo apt autoremove sudo curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - - curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > mssql-release.list + curl https://packages.microsoft.com/config/ubuntu/18.04/prod.list > mssql-release.list sudo mv mssql-release.list /etc/apt/sources.list.d/ sudo apt-get update sudo ACCEPT_EULA=Y apt-get install msodbcsql17 mssql-tools From b41135a8508bdf482b7a407e3de3beac32be1a1f Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 25 Nov 2019 15:20:30 -0800 Subject: [PATCH 181/249] Change log 5.7.1 preview (#1060) --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ README.md | 36 ++++++++++++++++++++---------------- source/shared/version.h | 2 +- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 382717716..c18663be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.7.1-preview - 2019-12-03 +Updated PECL release packages. Here is the list of updates: + +### Added +- Support for PHP 7.4 +- Support for Red Hat 8 and macOS Catalina (10.15) +- Feature Request [#1018](https://github.com/microsoft/msphpsql/issues/1018) - support for [PHP extended string types](https://github.com/microsoft/msphpsql/wiki/Features#natlTypes) - Pull Request [#1043](https://github.com/microsoft/msphpsql/pull/1043) +- [Always Encrypted with secure enclaves](https://github.com/microsoft/msphpsql/wiki/Features#alwaysencryptedV2), which requires [MS ODBC Driver 17.4+](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver15) and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019) + +### Removed +- Dropped support for [PHP 7.1](https://www.php.net/supported-versions.php) + +### Fixed +- Issue [#1027](https://github.com/microsoft/msphpsql/issues/1027) - Fixed how drivers handle query timeout settings +- Pull Request [#1049](https://github.com/microsoft/msphpsql/pull/1049) - performance improvement for fetching from tables with many columns - cached the derived php types with column metadata to streamline data retrieval + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Data Classification metadata retrieval requires ODBC Driver 17.4.2.1+ and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019) +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) + ## 5.7.0-preview - 2019-09-05 Updated PECL release packages. Here is the list of updates: diff --git a/README.md b/README.md index 842a99b17..c89e8a249 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ **Welcome to the Microsoft Drivers for PHP for Microsoft SQL Server** -The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2008 R2 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017) to handle the low-level communication with SQL Server. +The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2008 R2 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server][odbcdoc] to handle the low-level communication with SQL Server. -This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.1+ with improvements on both drivers and some [limitations](https://github.com/Microsoft/msphpsql/releases). Upcoming releases will contain additional functionalities, bug fixes, and more. +This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.1+ with improvements on both drivers and some limitations. Upcoming [releases][releases] will contain additional functionalities, bug fixes, and more. ## Take our survey -Thank you for taking the time to participate in our last survey. You can continue to help us improve by letting us know how we are doing and how you use PHP by taking our December pulse survey: +Thank you for taking the time to participate in the [sentiment survey](https://github.com/microsoft/msphpsql/wiki/Survey-Results). You can continue to help us improve by letting us know how we are doing and how you use [PHP][phpweb]: @@ -25,7 +25,7 @@ Azure Pipelines | AppVeyor (Windows) | Travis CI (Linux) | Co [az-image]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_apis/build/status/Microsoft.msphpsql?branchName=dev [Coverage Coveralls]: https://coveralls.io/repos/github/microsoft/msphpsql/badge.svg?branch=dev [coveralls-site]: https://coveralls.io/github/microsoft/msphpsql?branch=dev -[Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/dev/graph/badge.svg +[Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/master/graph/badge.svg [codecov-site]: https://codecov.io/gh/microsoft/msphpsql ## Get Started @@ -40,22 +40,22 @@ Azure Pipelines | AppVeyor (Windows) | Travis CI (Linux) | Co ## Announcements - Please visit the [blog][blog] for more announcements. + Please follow [SQL Server Drivers][sqldrivers] for announcements. ## Prerequisites For full details on the system requirements for the drivers, see the [system requirements](https://docs.microsoft.com/sql/connect/php/system-requirements-for-the-php-sql-driver) on Microsoft Docs. On the client machine: -- PHP 7.1.x, 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows), or 7.3.x -- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server) +- PHP 7.2.x (7.2.0 and up on Unix, 7.2.1 and up on Windows), 7.3.x, or 7.4.x +- [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11][odbcdoc] - If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux. ## Building and Installing the Drivers on Windows -The drivers are distributed as pre-compiled extensions for PHP found on the [releases page](https://github.com/Microsoft/msphpsql/releases). They are available in thread-safe and non thread-safe versions, and in 32-bit and 64-bit versions. The source code for the drivers is also available, and you can compile them as thread safe or non-thread safe versions. The thread safety configuration of your web server will determine which version you need. +The drivers are distributed as pre-compiled extensions for PHP found on the [releases page][releases]. They are available in thread-safe and non thread-safe versions, and in 32-bit and 64-bit versions. The source code for the drivers is also available, and you can compile them as thread safe or non-thread safe versions. The thread safety configuration of your web server will determine which version you need. If you choose to build the drivers, you must be able to build PHP 7.* without including these extensions. For help building PHP on Windows, see the [official PHP website][phpbuild]. For details on compiling the drivers, see the [documentation](https://github.com/Microsoft/msphpsql/tree/dev/buildscripts#windows) -- an example buildscript is provided, but you can also compile the drivers manually. @@ -65,13 +65,13 @@ Finally, if running PHP in a Web server, restart the Web server. ## Install (UNIX) -For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/installation-tutorial-linux-mac). +For full instructions on installing the drivers on all supported Unix platforms, see [the installation instructions on Microsoft Docs][unixinstructions]. ## Sample Code For PHP code samples, please see the [sample](https://github.com/Microsoft/msphpsql/tree/master/sample) folder or the [code samples on Microsoft Docs](https://docs.microsoft.com/sql/connect/php/code-samples-for-php-sql-driver). ## Limitations and Known Issues -Please refer to [Releases](https://github.com/Microsoft/msphpsql/releases) for the latest limitations and known issues. +Please refer to [Releases][releases] for the latest limitations and known issues. ## Version number The version numbers of the PHP drivers follow [semantic versioning](https://semver.org/): @@ -88,7 +88,7 @@ The version number may have trailing pre-release version identifiers to indicate - Build metadata may be denoted by a plus sign followed by 4 or 5 digits, such as `1.2.3-preview+5678` or `1.2.3+5678`. Build metadata does not figure into the precedence order. ## Future Plans -- Expand SQL Server 2016 feature support (example: Azure Active Directory) +- Expand SQL Server feature support (example: Azure Active Directory, Always Encrypted, etc.) - Add more verification/fundamental tests - Improve performance - Bug fixes @@ -109,7 +109,7 @@ Thank you! **Q:** What's next? -**A:** We will continue working on our future plans and releasing previews of upcoming [releases](https://github.com/Microsoft/msphpsql/releases) +**A:** We will continue working on our future plans and releasing previews of upcoming [releases][releases] **Q:** Is Microsoft taking pull requests for this project? @@ -127,20 +127,24 @@ This project has adopted the Microsoft Open Source Code of Conduct. For more inf **Documentation**: [Microsoft Docs Online][phpdoc]. -**Team Blog**: Browse our blog for comments and announcements from the team in the [team blog][blog]. +**SQL Server Drivers**: Please browse the articles for announcements of various [SQL Server Drivers][sqldrivers]. **Known Issues**: Please visit the [project on Github][project] to view outstanding [issues][issues] and report new ones. -[blog]: https://blogs.msdn.com/b/sqlphp/ +[sqldrivers]: https://techcommunity.microsoft.com/t5/SQL-Server/bg-p/SQLServer/label-name/SQLServerDrivers [project]: https://github.com/Microsoft/msphpsql [issues]: https://github.com/Microsoft/msphpsql/issues +[releases]: https://github.com/microsoft/msphpsql/releases + [phpweb]: https://php.net -[phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild +[phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2 [phpdoc]: https://docs.microsoft.com/sql/connect/php/microsoft-php-driver-for-sql-server?view=sql-server-2017 -[PHPMan]: https://php.net/manual/install.unix.php +[odbcdoc]: https://docs.microsoft.com/sql/connect/odbc/microsoft-odbc-driver-for-sql-server?view=sql-server-2017 + +[unixinstructions]: https://docs.microsoft.com/sql/connect/php/installation-tutorial-linux-mac diff --git a/source/shared/version.h b/source/shared/version.h index 6bc7e7969..c875410da 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -27,7 +27,7 @@ // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 #define SQLVERSION_MINOR 7 -#define SQLVERSION_PATCH 0 +#define SQLVERSION_PATCH 1 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 From 48b048830d7b9526166932bc81a8a31e404551bf Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 25 Nov 2019 15:29:32 -0800 Subject: [PATCH 182/249] Fix AKV keyword test for AE v2 behaviour (#1061) * Master (#936) 5.6.0 RTW * 5.6.1 hotfix (#959) * Updated links and versions (#987) * Fixed AKV keyword tests for AE v2 * Added comment * Free proc cache before starting test * Fixed comment --- .../pdo_ae_azure_key_vault_keywords.phpt | 29 ++++++++++++++-- .../sqlsrv_ae_azure_key_vault_keywords.phpt | 34 +++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt index cc49693bd..2e22a203a 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt @@ -48,6 +48,26 @@ $dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nva $tableName = "akv_comparison_table"; +// First determine if the server is AE v2 enabled +$isEnclaveEnabled = false; +$connectionOptions = "sqlsrv:Server=$server;Database=$databaseName"; + +$conn = new PDO($connectionOptions, $uid, $pwd); +if (!$conn) { + fatalError("Initial connection failed\n"); +} else { + $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; + $stmt = $conn->query($query); + $info = $stmt->fetch(); + if ($info['value'] == 1 and $info['value_in_use'] == 1) { + $isEnclaveEnabled = true; + } + + $conn->query("DBCC FREEPROCCACHE"); +} + +unset($conn); + // Test every combination of the keywords above. // Leave out good credentials to ensure that caching does not influence the // results. The cache timeout can only be changed with SQLSetConnectAttr, so @@ -117,8 +137,11 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { unset($stmt); } else { // The INSERT query succeeded with bad credentials, which - // should only happen when encryption is not enabled. - if (isColEncrypted()) { + // should only happen when 1. encryption is not enabled or + // 2. when ColumnEncryption is set to something other than + // enabled or disabled (i.e. $i == 2), and the server is + // not enclave-enabled + if (!(!isColEncrypted() or ($i == 2 and !$isEnclaveEnabled))) { fatalError("Successful insertion with bad credentials\n"); } } @@ -135,6 +158,7 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { $errors, array('CE258', '0'), array('CE275', '0'), + array('CE400', '0'), array('IMSSP', '-85'), array('IMSSP', '-86'), array('IMSSP', '-87'), @@ -147,6 +171,7 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { $errors, array('CE258', '0'), array('CE275', '0'), + array('CE400', '0'), array('IMSSP', '-85'), array('IMSSP', '-86'), array('IMSSP', '-87'), diff --git a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt index 3734e0be0..e6f03d279 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt @@ -47,6 +47,30 @@ $dataTypes = array("char(".SHORT_STRSIZE.")", "varchar(".SHORT_STRSIZE.")", "nva $tableName = "akv_comparison_table"; +// First determine if the server is AE v2 enabled +$isEnclaveEnabled = false; +$connectionOptions = array("CharacterSet"=>"UTF-8", + "database"=>$databaseName, + "uid"=>$uid, + "pwd"=>$pwd, + "ConnectionPooling"=>0); + +$conn = sqlsrv_connect($server, $connectionOptions); +if (!$conn) { + fatalError("Initial connection failed\n"); +} else { + $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; + $stmt = sqlsrv_query($conn, $query); + $info = sqlsrv_fetch_array($stmt); + if ($info['value'] == 1 and $info['value_in_use'] == 1) { + $isEnclaveEnabled = true; + } + + sqlsrv_query($conn, "DBCC FREEPROCCACHE"); +} + +unset($conn); + // Test every combination of the keywords above. // Leave out good credentials to ensure that caching does not influence the // results. The cache timeout can only be changed with SQLSetConnectAttr, so @@ -96,7 +120,8 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { array('IMSSP','-110'), array('IMSSP','-111'), array('IMSSP','-112'), - array('IMSSP','-113') + array('IMSSP','-113'), + array('CE400','0') ); } else { $columns = array(); @@ -148,8 +173,11 @@ for ($i = 0; $i < sizeof($columnEncryption); ++$i) { sqlsrv_free_stmt($stmt); } else { // The INSERT query succeeded with bad credentials, which - // should only happen when encryption is not enabled. - if (AE\isDataEncrypted()) { + // should only happen when 1. encryption is not enabled or + // 2. when ColumnEncryption is set to something other than + // enabled or disabled (i.e. $i == 2), and the server is + // not enclave-enabled + if (!(!AE\isDataEncrypted() or ($i == 2 and !$isEnclaveEnabled))) { fatalError("Successful insertion with bad credentials\n"); } } From efd04e42255c61e1d9baadedc3b5244a431b170d Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 2 Dec 2019 20:37:43 -0800 Subject: [PATCH 183/249] Update linux mac instructions for php 7.4 (#1062) --- Linux-mac-install.md | 88 ++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index 2a96c89c5..fb3175157 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,50 +1,50 @@ # Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server -The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 18.10, RedHat 7, Debian 8 and 9, Suse 12 and 15, and macOS 10.11, 10.12, 10.13, and 10.14. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver##loading-the-driver-at-php-startup). +The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu, RedHat, Debian, Suse, and macOS. These instructions advise installing the drivers using PECL, but you may also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver##loading-the-driver-at-php-startup). -These instructions install PHP 7.3 by default. Note that some supported Linux distros default to PHP 7.0 or earlier, which is not supported for the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.1 or 7.2 instead. +These instructions install PHP 7.4 by default. Note that some supported Linux distros default to PHP 7.1 or earlier, which the PHP drivers for SQL Server no longer support. When installing PHP 7.2 or above, please read the notes at the beginning of each section below. ## Contents of this page: - [Installing the drivers on Ubuntu 16.04, 18.04, and 19.04](#installing-the-drivers-on-ubuntu-1604-1804-and-1904) -- [Installing the drivers on Red Hat 7](#installing-the-drivers-on-red-hat-7) +- [Installing the drivers on Red Hat 7 and 8](#installing-the-drivers-on-red-hat-7-and-8) - [Installing the drivers on Debian 8, 9 and 10](#installing-the-drivers-on-debian-8-9-and-10) - [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15) -- [Installing the drivers on macOS Sierra, High Sierra, and Mojave](#installing-the-drivers-on-macos-sierra-high-sierra-and-mojave) +- [Installing the drivers on macOS Sierra, High Sierra, Mojave, and Catalina](#installing-the-drivers-on-macos-sierra-high-sierra-mojave-and-catalina) ## Installing the drivers on Ubuntu 16.04, 18.04, and 19.04 > [!NOTE] -> To install PHP 7.1 or 7.2, replace 7.3 with 7.1 or 7.2 in the following commands. +> To install PHP 7.3 or 7.2, replace 7.4 with 7.3 or 7.2 in the following commands. ### Step 1. Install PHP ``` sudo su add-apt-repository ppa:ondrej/php -y apt-get update -apt-get install php7.3 php7.3-dev php7.3-xml -y --allow-unauthenticated +apt-get install php7.4 php7.4-dev php7.4-xml -y --allow-unauthenticated ``` ### Step 2. Install prerequisites Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). ### Step 3. Install the PHP drivers for Microsoft SQL Server ``` -sudo pecl install sqlsrv -sudo pecl install pdo_sqlsrv +sudo pecl install sqlsrv-5.7.1preview +sudo pecl install pdo_sqlsrv-5.7.1preview sudo su -printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.3/mods-available/sqlsrv.ini -printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.3/mods-available/pdo_sqlsrv.ini +printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.4/mods-available/sqlsrv.ini +printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.4/mods-available/pdo_sqlsrv.ini exit -sudo phpenmod -v 7.3 sqlsrv pdo_sqlsrv +sudo phpenmod -v 7.4 sqlsrv pdo_sqlsrv ``` If there is only one PHP version in the system then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. ### Step 4. Install Apache and configure driver loading ``` sudo su -apt-get install libapache2-mod-php7.3 apache2 +apt-get install libapache2-mod-php7.4 apache2 a2dismod mpm_event a2enmod mpm_prefork -a2enmod php7.3 +a2enmod php7.4 exit ``` ### Step 5. Restart Apache and test the sample script @@ -53,10 +53,10 @@ sudo service apache2 restart ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on Red Hat 7 +## Installing the drivers on Red Hat 7 and 8 > [!NOTE] -> To install PHP 7.1 or 7.2, replace remi-php73 with remi-php71 or remi-php72 respectively in the following commands. +> To install PHP 7.3 or 7.2, replace remi-php74 with remi-php73 or remi-php72 respectively in the following commands. ### Step 1. Install PHP @@ -67,14 +67,14 @@ wget https://rpms.remirepo.net/enterprise/remi-release-7.rpm rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm subscription-manager repos --enable=rhel-7-server-optional-rpms yum install yum-utils -yum-config-manager --enable remi-php73 +yum-config-manager --enable remi-php74 yum update yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc ``` ### Step 2. Install prerequisites -Install the ODBC driver for Red Hat 7 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). +Install the ODBC driver for Red Hat 7 and 8 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). -Compiling the PHP drivers with PECL with PHP 7.2 or 7.3 requires a more recent GCC than the default: +In some versions of Red Hat 7, compiling the PHP drivers with PECL and PHP 7.2 requires a more recent GCC than the default: ``` sudo yum-config-manager --enable rhel-server-rhscl-7-rpms sudo yum install devtoolset-7 @@ -82,8 +82,8 @@ scl enable devtoolset-7 bash ``` ### Step 3. Install the PHP drivers for Microsoft SQL Server ``` -sudo pecl install sqlsrv -sudo pecl install pdo_sqlsrv +sudo pecl install sqlsrv-5.7.1preview +sudo pecl install pdo_sqlsrv-5.7.1preview sudo su echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/30-pdo_sqlsrv.ini echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/20-sqlsrv.ini @@ -91,9 +91,9 @@ exit ``` An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually (similar steps for pdo_sqlsrv): ``` -pecl download sqlsrv -tar xvzf sqlsrv-5.7.0.tgz -cd sqlsrv-5.7.0/ +pecl download sqlsrv-5.7.1preview +tar xvzf sqlsrv-5.7.1preview.tgz +cd sqlsrv-5.7.1preview/ phpize ./configure --with-php-config=/usr/bin/php-config make @@ -120,7 +120,7 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Debian 8, 9 and 10 > [!NOTE] -> To install PHP 7.1 or 7.2, replace 7.3 in the following commands with 7.1 or 7.2. +> To install PHP 7.3 or 7.2, replace 7.4 in the following commands with 7.3 or 7.2. ### Step 1. Install PHP ``` @@ -129,7 +129,7 @@ apt-get install curl apt-transport-https wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list apt-get update -apt-get install -y php7.3 php7.3-dev php7.3-xml +apt-get install -y php7.4 php7.4-dev php7.4-xml ``` ### Step 2. Install prerequisites Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). @@ -143,23 +143,23 @@ locale-gen ### Step 3. Install the PHP drivers for Microsoft SQL Server ``` -sudo pecl install sqlsrv -sudo pecl install pdo_sqlsrv +sudo pecl install sqlsrv-5.7.1preview +sudo pecl install pdo_sqlsrv-5.7.1preview sudo su -printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.3/mods-available/sqlsrv.ini -printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.3/mods-available/pdo_sqlsrv.ini +printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.4/mods-available/sqlsrv.ini +printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.4/mods-available/pdo_sqlsrv.ini exit -sudo phpenmod -v 7.3 sqlsrv pdo_sqlsrv +sudo phpenmod -v 7.4 sqlsrv pdo_sqlsrv ``` If there is only one PHP version in the system then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. ### Step 4. Install Apache and configure driver loading ``` sudo su -apt-get install libapache2-mod-php7.3 apache2 +apt-get install libapache2-mod-php7.4 apache2 a2dismod mpm_event a2enmod mpm_prefork -a2enmod php7.3 +a2enmod php7.4 ``` ### Step 5. Restart Apache and test the sample script ``` @@ -173,9 +173,9 @@ To test your installation, see [Testing your installation](#testing-your-install > In the following instructions, replace with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15 or SLE_15_SP1. For Suse 12, use SLE_12_SP4 (or above if applicable). Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or to `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse. > [!NOTE] -> Packages for PHP 7.3 are not available for Suse 12. -> To install PHP 7.1, replace the repository URL below with the following URL: - `https://download.opensuse.org/repositories/devel:/languages:/php:/php71//devel:languages:php:php71.repo`. +> Packages for PHP 7.4 are not available for Suse 12. +> To install PHP 7.3, replace the repository URL below with the following URL: + `https://download.opensuse.org/repositories/devel:/languages:/php:/php73//devel:languages:php:php73.repo`. > To install PHP 7.2, replace the repository URL below with the following URL: `https://download.opensuse.org/repositories/devel:/languages:/php:/php72//devel:languages:php:php72.repo`. @@ -194,8 +194,8 @@ Install the ODBC driver for Suse by following the instructions on the [Linux and > If you get an error message saying `Connection to 'pecl.php.net:443' failed: Unable to find the socket transport "ssl"`, edit the pecl script at /usr/bin/pecl and remove the `-n` switch in the last line. This switch prevents PECL from loading ini files when PHP is called, which prevents the OpenSSL extension from loading. ``` -sudo pecl install sqlsrv -sudo pecl install pdo_sqlsrv +sudo pecl install sqlsrv-5.7.1preview +sudo pecl install pdo_sqlsrv-5.7.1preview sudo su echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/pdo_sqlsrv.ini echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/sqlsrv.ini @@ -216,7 +216,7 @@ sudo systemctl restart apache2 ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on macOS Sierra, High Sierra, and Mojave +## Installing the drivers on macOS Sierra, High Sierra, Mojave, and Catalina If you do not already have it, install brew as follows: ``` @@ -224,18 +224,18 @@ If you do not already have it, install brew as follows: ``` > [!NOTE] -> To install PHP 7.1 or 7.2, replace php@7.3 with php@7.1 or php@7.2 respectively in the following commands. +> To install PHP 7.3 or 7.2, replace php@7.4 with php@7.3 or php@7.2 respectively in the following commands. ### Step 1. Install PHP ``` brew tap brew tap homebrew/core -brew install php@7.3 +brew install php@7.4 ``` PHP should now be in your path -- run `php -v` to verify that you are running the correct version of PHP. If PHP is not in your path or it is not the correct version, run the following: ``` -brew link --force --overwrite php@7.3 +brew link --force --overwrite php@7.4 ``` ### Step 2. Install prerequisites @@ -248,8 +248,8 @@ brew install autoconf automake libtool ### Step 3. Install the PHP drivers for Microsoft SQL Server ``` -sudo pecl install sqlsrv -sudo pecl install pdo_sqlsrv +sudo pecl install sqlsrv-5.7.1preview +sudo pecl install pdo_sqlsrv-5.7.1preview ``` ### Step 4. Install Apache and configure driver loading ``` @@ -261,7 +261,7 @@ apachectl -V | grep SERVER_CONFIG_FILE ``` and substitute the path for `httpd.conf` in the following commands: ``` -echo "LoadModule php7_module /usr/local/opt/php@7.3/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf +echo "LoadModule php7_module /usr/local/opt/php@7.4/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf (echo ""; echo "SetHandler application/x-httpd-php"; echo "";) >> /usr/local/etc/httpd/httpd.conf ``` ### Step 5. Restart Apache and test the sample script From afa217f002e29665214ee5c1e56b1b71f533d8a1 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 4 Dec 2019 17:08:52 -0800 Subject: [PATCH 184/249] Updated appveyor yml to build 7.3 and 7.4 (#1065) --- appveyor.yml | 14 +++++++------- .../pdo_ae_azure_key_vault_keywords.phpt | 2 +- .../sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ef9870ef5..a473ab3f4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,22 +24,22 @@ environment: SQL_INSTANCE: SQL2017 PHP_VC: 15 PHP_MAJOR_VER: 7.3 - PHP_MINOR_VER: 11 + PHP_MINOR_VER: latest PHP_EXE_PATH: x64\Release_TS THREAD: ts platform: x64 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 BUILD_PLATFORM: x86 TEST_PHP_SQL_SERVER: (local)\SQL2016 SQL_INSTANCE: SQL2016 - PHP_VC: 14 - PHP_MAJOR_VER: 7.1 + PHP_VC: 15 + PHP_MAJOR_VER: 7.4 PHP_MINOR_VER: latest PHP_EXE_PATH: Release THREAD: nts platform: x86 -# PHP_MAJOR_VER is PHP major version to build (7.2, 7.3) +# PHP_MAJOR_VER is PHP major version to build (7.4, 7.3) # PHP_MINOR_VER is PHP point release number (or latest for latest release) # PHP_VC is the Visual C++ version # PHP_EXE_PATH is the relative path from php src folder to php executable @@ -75,9 +75,9 @@ install: - ps: | $client = New-Object Net.WebClient; $client.Headers.Add("user-agent", "appveyor-ci-build2"); - $client.DownloadFile("http://windows.php.net/downloads/releases/sha1sum.txt", "c:\projects\sha1sum.txt"); + $client.DownloadFile("http://windows.php.net/downloads/releases/sha256sum.txt", "c:\projects\sha256sum.txt"); If ($env:PHP_MINOR_VER -Match "latest") { - $env:PHP_VERSION=type c:\projects\sha1sum.txt | where { $_ -match "php-($env:PHP_MAJOR_VER\.\d+)-src" } | foreach { $matches[1] } ; + $env:PHP_VERSION=type c:\projects\sha256sum.txt | where { $_ -match "php-($env:PHP_MAJOR_VER\.\d+)-src" } | foreach { $matches[1] } ; } Else { $env:PHP_VERSION=$env:PHP_MAJOR_VER + '.' + $env:PHP_MINOR_VER; } diff --git a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt index 2e22a203a..188bb1760 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_azure_key_vault_keywords.phpt @@ -59,7 +59,7 @@ if (!$conn) { $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; $stmt = $conn->query($query); $info = $stmt->fetch(); - if ($info['value'] == 1 and $info['value_in_use'] == 1) { + if (!empty($info) and $info['value'] == 1 and $info['value_in_use'] == 1) { $isEnclaveEnabled = true; } diff --git a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt index e6f03d279..77e459d78 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_azure_key_vault_keywords.phpt @@ -62,7 +62,7 @@ if (!$conn) { $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; $stmt = sqlsrv_query($conn, $query); $info = sqlsrv_fetch_array($stmt); - if ($info['value'] == 1 and $info['value_in_use'] == 1) { + if (!empty($info) and $info['value'] == 1 and $info['value_in_use'] == 1) { $isEnclaveEnabled = true; } From eeec2f838ddb0dd4573375b95d84dc809cf96c06 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Tue, 17 Dec 2019 16:25:57 -0800 Subject: [PATCH 185/249] Fixes suggested by Semmle (#1068) * Fixes suggested by Semmle * Updated azure-pipelines --- azure-pipelines.yml | 30 ++--- source/shared/core_sqlsrv.h | 256 ++++++++++++++++++------------------ source/shared/core_stmt.cpp | 50 +++---- source/sqlsrv/stmt.cpp | 243 +++++++++++++++++----------------- 4 files changed, 286 insertions(+), 293 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 23b9d72c5..6212ef3ff 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -2,7 +2,7 @@ # https://aka.ms/yaml variables: - phpVersion: 7.3 + phpVersion: 7.4 server: 'localhost,1433' host: 'sql1' sqlsrv_db: 'sqlsrv_testdb' @@ -70,7 +70,7 @@ jobs: inputs: versionSpec: '3.6' architecture: 'x64' - + - script: | sudo update-alternatives --set php /usr/bin/php$(phpVersion) sudo update-alternatives --set phar /usr/bin/phar$(phpVersion) @@ -109,13 +109,13 @@ jobs: sudo sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen sudo locale-gen en_US sudo locale-gen en_US.UTF-8 - export LANG='en_US.UTF-8' - export LANGUAGE='en_US:en' + export LANG='en_US.UTF-8' + export LANGUAGE='en_US:en' export LC_ALL='en_US.UTF-8' displayName: 'Generate locales for testing' - script: | - echo setting env variables + echo setting env variables export TEST_PHP_SQL_SERVER='$(server)' export TEST_PHP_SQL_UID='$(uid)' export TEST_PHP_SQL_PWD='$(pwd)' @@ -132,7 +132,7 @@ jobs: ./packagize.sh dest=`php --ini | grep "Scan for additional .ini files" | sudo sed -e "s|.*:\s*||"`/ - + cd $(Build.SourcesDirectory)/source/sqlsrv ls -al phpize && ./configure && make && sudo make install @@ -141,7 +141,7 @@ jobs: echo copying sqlsrv to $dest sudo cp 20-sqlsrv.ini $dest - + cd $(Build.SourcesDirectory)/source/pdo_sqlsrv ls -al phpize && ./configure && make && sudo make install @@ -160,7 +160,7 @@ jobs: sed -i -e 's/TARGET_SERVER/'"$(server)"'/g' MsSetup.inc sed -i -e 's/TARGET_DATABASE/'"$(sqlsrv_db)"'/g' MsSetup.inc sed -i -e 's/TARGET_USERNAME/'"$(uid)"'/g' MsSetup.inc - sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc + sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc php run-tests.php -P ./*.phpt 2>&1 | tee ../sqlsrv.log displayName: 'Run sqlsrv functional tests' @@ -170,7 +170,7 @@ jobs: sed -i -e 's/TARGET_SERVER/'"$(server)"'/g' MsSetup.inc sed -i -e 's/TARGET_DATABASE/'"$(pdo_sqlsrv_db)"'/g' MsSetup.inc sed -i -e 's/TARGET_USERNAME/'"$(uid)"'/g' MsSetup.inc - sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc + sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc php run-tests.php -P ./*.phpt 2>&1 | tee ../pdo_sqlsrv.log displayName: 'Run pdo_sqlsrv functional tests' @@ -194,7 +194,7 @@ jobs: docker stop $(host) docker rm $(host) displayName: 'Stop SQL Server for Linux' - condition: always() + condition: always() - job: Windows pool: @@ -249,7 +249,7 @@ jobs: - script: msiexec /i "msodbcsql_13.1.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL condition: false - # FOR SOME REASON the set up did not set the PATH + # FOR SOME REASON the set up did not set the PATH - script: | msiexec /i "MsSqlCmdLnUtils.msi" /qn IACCEPTMSSQLCMDLNUTILSLICENSETERMS=YES displayName: 'Install SQL command line utilities version 15' @@ -260,7 +260,7 @@ jobs: $client.Headers.Add("user-agent", "azure pipeline build") $client.DownloadFile("https://windows.php.net/downloads/releases/sha256sum.txt", "sha256sum.txt") $env:VERSION=type sha256sum.txt | where { $_ -match "php-($(phpVersion)\.\d+)-src" } | foreach { $matches[1] } - Write-Host "Latest PHP $(phpVersion) is ${env:VERSION}" + Write-Host "Latest PHP $(phpVersion) is ${env:VERSION}" cd $(Build.SourcesDirectory)/buildscripts/ python builddrivers.py --PHPVER=${env:VERSION} --DRIVER=sqlsrv --ARCH=x64 --THREAD=nts --SOURCE=$(Build.SourcesDirectory)/source --TESTING --NO_RENAME dir *sqlsrv*.dll @@ -270,7 +270,7 @@ jobs: dir *sqlsrv*.dll cp *sqlsrv*.dll C:\tools\php\ext\ displayName: 'Build drivers (separately) for the latest version of PHP $(phpVersion)' - + - script: | echo extension=php_sqlsrv.dll >> C:\tools\php\php.ini echo extension=php_pdo_sqlsrv.dll >> C:\tools\php\php.ini @@ -285,7 +285,7 @@ jobs: displayName: 'Run SQL Server for Windows Server' condition: false - - script: | + - script: | set path=C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;%path% sqlcmd -S $(host) -U $(uid) -P $(pwd) -Q "SELECT @@Version" set TEST_PHP_SQL_SERVER=$(host) @@ -309,4 +309,4 @@ jobs: docker stop sqlcontainer docker rm sqlcontainer displayName: 'Stop SQL Server for Windows Server' - condition: false \ No newline at end of file + condition: false diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index be6ead07b..58ccccf93 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -10,13 +10,13 @@ // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, // and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- @@ -28,7 +28,7 @@ #undef SQL_WCHART_CONVERT #endif #ifndef _WCHART_DEFINED -#define _WCHART_DEFINED +#define _WCHART_DEFINED #endif #include "php.h" @@ -54,8 +54,6 @@ #define PHP_SQLSRV_API #endif -// #define MultiByteToWideChar SystemLocale::ToUtf16 - #define stricmp strcasecmp #define strnicmp strncasecmp #define strnlen_s(s) strnlen_s(s, INT_MAX) @@ -103,10 +101,10 @@ extern "C" { #endif #if _MSC_VER >= 1400 -// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. -// php.h defines this constant as unsigned int which causes a compile error +// typedef and macro to prevent a conflict between php.h and ws2tcpip.h. +// php.h defines this constant as unsigned int which causes a compile error // in ws2tcpip.h. Fortunately php.h allows an override by defining -// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define +// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define // socklen_t here and override the php.h version. typedef int socklen_t; #define HAVE_SOCKLEN_T @@ -142,7 +140,7 @@ OACR_WARNING_POP #define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars #endif -// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when +// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when // we use std::copy, which causes compilation to fail since we compile with warnings as errors. #if defined(ZEND_DEBUG) && defined(inline) #undef inline @@ -190,7 +188,7 @@ const long ACTIVE_NUM_ROWS_INVALID = -99; const int SQL_SERVER_2005_DEFAULT_DATETIME_PRECISION = 23; const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; -const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; +const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; namespace AzureADOptions { const char AZURE_AUTH_SQL_PASSWORD[] = "SqlPassword"; @@ -215,7 +213,7 @@ enum SQLSRV_PHPTYPE { }; // encodings supported by this extension. These basically translate into the use of SQL_C_CHAR or SQL_C_BINARY when getting -// information as a string or a stream. +// information as a string or a stream. enum SQLSRV_ENCODING { SQLSRV_ENCODING_INVALID, // unknown or invalid encoding. Used to initialize variables. SQLSRV_ENCODING_DEFAULT, // use what is the connection's default for a statement, use system if a connection @@ -263,7 +261,7 @@ union sqlsrv_sqltype { // SQLSRV PHP types (as opposed to the Zend PHP type constants). Contains the type (see SQLSRV_PHPTYPE) -// and the encoding for strings and streams (see SQLSRV_ENCODING) +// and the encoding for strings and streams (see SQLSRV_ENCODING) union sqlsrv_phptype { @@ -325,8 +323,8 @@ void die( _In_opt_ const char* msg, ... ); #pragma push_macro( "max" ) #undef max -// new memory allocation/free debugging facilities to help us verify that all allocations are being -// released in a timely manner and not just at the end of the script. +// new memory allocation/free debugging facilities to help us verify that all allocations are being +// released in a timely manner and not just at the end of the script. // Zend has memory logging and checking, but it can generate a lot of noise for just one extension. // It's meant for internal use but might be useful for people adding features to our extension. // To use it, uncomment the #define below and compile in Debug NTS. All allocations and releases @@ -435,7 +433,7 @@ struct remove_const { // this allows us to use STL classes that still work with Zend objects template struct sqlsrv_allocator { - + // typedefs used by the STL classes typedef T value_type; typedef value_type* pointer; @@ -459,8 +457,8 @@ struct sqlsrv_allocator { // address (doesn't work if the class defines operator&) inline pointer address( _In_ reference r ) - { - return &r; + { + return &r; } inline const_pointer address( _In_ const_reference r ) @@ -469,20 +467,20 @@ struct sqlsrv_allocator { } // memory allocation/deallocation - inline pointer allocate( _In_ size_type cnt, + inline pointer allocate( _In_ size_type cnt, typename std::allocator::const_pointer = 0 ) { - return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); + return reinterpret_cast( sqlsrv_malloc(cnt, sizeof (T), 0)); } - inline void deallocate( _Inout_ pointer p, size_type ) - { - sqlsrv_free(p); + inline void deallocate( _Inout_ pointer p, size_type ) + { + sqlsrv_free(p); } // size - inline size_type max_size( void ) const - { + inline size_type max_size( void ) const + { return std::numeric_limits::max() / sizeof(T); } @@ -507,11 +505,11 @@ struct sqlsrv_allocator { { return !operator==(a); } -}; +}; -// base class for auto_ptrs that we define below. It provides common operators and functions -// used by all the classes. +// base class for auto_ptrs that we define below. It provides common operators and functions +// used by all the classes. template class sqlsrv_auto_ptr { @@ -576,8 +574,8 @@ class sqlsrv_auto_ptr { return _ptr[index]; } - - #ifdef __WIN64 + + #ifdef __WIN64 // there are a number of places where we allocate a block intended to be accessed as // an array of elements, so this operator allows us to treat the memory as such. T& operator[]( _In_ std::size_t index ) const @@ -616,7 +614,7 @@ class sqlsrv_auto_ptr { protected: sqlsrv_auto_ptr( _In_opt_ T* ptr ) : - _ptr( ptr ) + _ptr( ptr ) { } @@ -637,7 +635,7 @@ class sqlsrv_auto_ptr { return ptr; } - T* _ptr; + T* _ptr; }; // an auto_ptr for sqlsrv_malloc/sqlsrv_free. When allocating a chunk of memory using sqlsrv_malloc, wrap that pointer @@ -684,13 +682,13 @@ class sqlsrv_malloc_auto_ptr : public sqlsrv_auto_ptr >::_ptr = reinterpret_cast( sqlsrv_realloc( sqlsrv_auto_ptr >::_ptr, new_size )); } }; -// auto ptr for Zend hash tables. Used to clean up a hash table allocated when +// auto ptr for Zend hash tables. Used to clean up a hash table allocated when // something caused an early exit from the function. This is used when the hash_table is // allocated in a zval that itself can't be released. Otherwise, use the zval_auto_ptr. @@ -726,8 +724,8 @@ class hash_auto_ptr : public sqlsrv_auto_ptr { }; -// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. -// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed +// an auto_ptr for zvals. When allocating a zval, wrap that pointer in a variable of zval_auto_ptr. +// zval_auto_ptr will "own" that zval and assure that it is freed when the variable is destroyed // (out of scope) or ownership is transferred using the function "transferred". class zval_auto_ptr : public sqlsrv_auto_ptr { @@ -798,7 +796,7 @@ struct sqlsrv_error : public sqlsrv_error_const { native_code = code; format = printf_format; } - + sqlsrv_error( _In_ sqlsrv_error_const const& prototype ) { sqlsrv_error( prototype.sqlstate, prototype.native_message, prototype.native_code, prototype.format ); @@ -865,8 +863,8 @@ class sqlsrv_context; struct sqlsrv_conn; // error_callback -// a driver specific callback for processing errors. -// ctx - the context holding the handles +// a driver specific callback for processing errors. +// ctx - the context holding the handles // sqlsrv_error_code - specific error code to return. typedef bool (*error_callback)( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool error TSRMLS_DC, _In_opt_ va_list* print_args ); @@ -910,7 +908,7 @@ class sqlsrv_context { } virtual ~sqlsrv_context() - { + { } void set_func( _In_z_ const char* f ) @@ -927,7 +925,7 @@ class sqlsrv_context { { return last_error_; } - + // since the primary responsibility of a context is to hold an ODBC handle, we // provide these convenience operators for using them interchangeably operator SQLHANDLE ( void ) const @@ -997,7 +995,7 @@ class sqlsrv_context { error_callback err_; // driver error callback if error occurs in core layer void* driver_; // points back to the driver for PDO sqlsrv_error_auto_ptr last_error_; // last error that happened on this object - SQLSRV_ENCODING encoding_; // encoding of the context + SQLSRV_ENCODING encoding_; // encoding of the context }; // maps an IANA encoding to a code page @@ -1064,7 +1062,7 @@ enum DRIVER_VERSION { struct sqlsrv_stmt; struct stmt_option; -// This holds the various details of column encryption. +// This holds the various details of column encryption. struct col_encryption_option { bool enabled; // column encryption enabled, false by default SQLINTEGER akv_mode; @@ -1105,13 +1103,13 @@ struct sqlsrv_conn : public sqlsrv_context { driver_version = ODBC_DRIVER_UNKNOWN; } - // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be + // sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be // called manually. Instead, we leave it to the allocator to invalidate the handle when an error occurs allocating // the sqlsrv_conn with a connection. }; enum SQLSRV_STMT_OPTIONS { - + SQLSRV_STMT_OPTION_INVALID, SQLSRV_STMT_OPTION_QUERY_TIMEOUT, SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC, @@ -1164,7 +1162,7 @@ const char SERVER[] = "Server"; } enum SQLSRV_CONN_OPTIONS { - + SQLSRV_CONN_OPTION_INVALID, SQLSRV_CONN_OPTION_APP, SQLSRV_CONN_OPTION_ACCESS_TOKEN, @@ -1200,7 +1198,7 @@ enum SQLSRV_CONN_OPTIONS { // Driver specific connection options SQLSRV_CONN_OPTION_DRIVER_SPECIFIC = 1000, - + }; @@ -1220,7 +1218,7 @@ struct connection_option { // the name of the option as passed in by the user const char * sqlsrv_name; unsigned int sqlsrv_len; - + unsigned int conn_option_key; // the name of the option in the ODBC connection string const char * odbc_name; @@ -1247,7 +1245,7 @@ struct column_encryption_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; -struct driver_set_func { +struct driver_set_func { static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); }; @@ -1265,8 +1263,8 @@ typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callba // *** connection functions *** sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_context& henv_ncp, _In_ driver_conn_factory conn_factory, - _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd, - _Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[], + _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd, + _Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[], _In_ void* driver, _In_z_ const char* driver_func TSRMLS_DC ); SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled ); void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC ); @@ -1332,9 +1330,9 @@ struct stmt_option { const char * name; // name of the statement option unsigned int name_len; // name length - unsigned int key; + unsigned int key; std::unique_ptr func; // callback that actually handles the work of the option - + }; // holds the stream param and the encoding that it was assigned @@ -1368,9 +1366,9 @@ extern php_stream_wrapper g_sqlsrv_stream_wrapper; // *** parameter metadata struct *** struct param_meta_data { - SQLSMALLINT sql_type; + SQLSMALLINT sql_type; SQLSMALLINT decimal_digits; - SQLSMALLINT nullable; + SQLSMALLINT nullable; SQLULEN column_size; param_meta_data() : sql_type(0), decimal_digits(0), column_size(0), nullable(0) @@ -1416,16 +1414,16 @@ struct sqlsrv_output_param { { } - void saveMetaData(SQLSMALLINT sql_type, SQLSMALLINT column_size, SQLSMALLINT decimal_digits, SQLSMALLINT nullable = SQL_NULLABLE) - { + void saveMetaData(SQLSMALLINT sql_type, SQLSMALLINT column_size, SQLSMALLINT decimal_digits, SQLSMALLINT nullable = SQL_NULLABLE) + { meta_data.sql_type = sql_type; meta_data.column_size = column_size; meta_data.decimal_digits = decimal_digits; meta_data.nullable = nullable; } - param_meta_data& getMetaData() - { + param_meta_data& getMetaData() + { return meta_data; } }; @@ -1472,13 +1470,13 @@ namespace data_classification { { } - ~column_sensitivity() + ~column_sensitivity() { label_info_pairs.clear(); } }; - struct sensitivity_metadata { + struct sensitivity_metadata { USHORT num_labels; std::vector> labels; USHORT num_infotypes; @@ -1491,7 +1489,7 @@ namespace data_classification { } ~sensitivity_metadata() - { + { reset(); } @@ -1503,7 +1501,7 @@ namespace data_classification { struct sqlsrv_result_set; struct field_meta_data; -// *** Statement resource structure *** +// *** Statement resource structure *** struct sqlsrv_stmt : public sqlsrv_context { void free_param_data( TSRMLS_D ); @@ -1513,13 +1511,13 @@ struct sqlsrv_stmt : public sqlsrv_context { void clean_up_sensitivity_metadata(); sqlsrv_conn* conn; // Connection that created this statement - + bool executed; // Whether the statement has been executed yet (used for error messages) bool past_fetch_end; // Core_sqlsrv_fetch sets this field when the statement goes beyond the last row sqlsrv_result_set* current_results; // Current result set SQLULEN cursor_type; // Type of cursor for the current result set bool has_rows; // Has_rows is set if there are actual rows in the row set - bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called + bool fetch_called; // Used by core_sqlsrv_get_field to return an informative error if fetch not yet called int last_field_index; // last field retrieved by core_sqlsrv_get_field bool past_next_result_end; // core_sqlsrv_next_result sets this to true when the statement goes beyond the last results short column_count; // Number of columns in the current result set obtained from SQLNumResultCols @@ -1529,7 +1527,7 @@ struct sqlsrv_stmt : public sqlsrv_context { bool date_as_string; // false by default but the user can set this to true to retrieve datetime values as strings bool format_decimals; // false by default but the user can set this to true to add the missing leading zeroes and/or control number of decimal digits to show short decimal_places; // indicates number of decimals shown in fetched results (-1 by default, which means no change to number of decimal digits) - bool data_classification; // false by default but the user can set this to true to retrieve data classification sensitivity metadata + bool data_classification; // false by default but the user can set this to true to retrieve data classification sensitivity metadata // holds output pointers for SQLBindParameter // We use a deque because it 1) provides the at/[] access in constant time, and 2) grows dynamically without moving @@ -1541,10 +1539,10 @@ struct sqlsrv_stmt : public sqlsrv_context { zval param_datetime_buffers; // datetime strings to be converted back to DateTime objects bool send_streams_at_exec; // send all stream data right after execution before returning sqlsrv_stream current_stream; // current stream sending data to the server as an input parameter - unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string + unsigned int current_stream_read; // # of bytes read so far. (if we read an empty PHP stream, we send an empty string // to the server) zval field_cache; // cache for a single row of fields, to allow multiple and out of order retrievals - zval col_cache; // Used by get_field_as_string not to call SQLColAttribute() after every fetch. + zval col_cache; // Used by get_field_as_string not to call SQLColAttribute() after every fetch. zval active_stream; // the currently active stream reading data from the database std::vector param_descriptions; @@ -1573,7 +1571,7 @@ struct field_meta_data { SQLSMALLINT field_type; SQLULEN field_size; SQLULEN field_precision; - SQLSMALLINT field_scale; + SQLSMALLINT field_scale; SQLSMALLINT field_is_nullable; bool field_is_money_type; sqlsrv_phptype sqlsrv_php_type; @@ -1584,7 +1582,7 @@ struct field_meta_data { reset_php_type(); } - ~field_meta_data() + ~field_meta_data() { } @@ -1614,7 +1612,7 @@ const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doe typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); // *** statement functions *** -sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht, +sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht, _In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver TSRMLS_DC ); void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z, _In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size, @@ -1660,7 +1658,7 @@ struct sqlsrv_result_set { virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_bytes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC )= 0; - virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, + virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ) = 0; @@ -1677,7 +1675,7 @@ struct sqlsrv_odbc_result_set : public sqlsrv_result_set { virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, _In_ bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, + virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ); @@ -1715,13 +1713,13 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_bytes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, bool handle_warning TSRMLS_DC ); - virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, + virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ); virtual SQLLEN row_count( TSRMLS_D ); - // buffered result set specific + // buffered result set specific SQLSMALLINT column_count( void ) { return col_count; @@ -1758,7 +1756,7 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { // string conversion functions SQLRETURN binary_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); - SQLRETURN binary_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, + SQLRETURN binary_to_system_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN system_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ); @@ -1839,7 +1837,7 @@ enum SQLSRV_ERROR_CODES { SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, - SQLSRV_ERROR_ZEND_STREAM, + SQLSRV_ERROR_ZEND_STREAM, SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION, SQLSRV_ERROR_FETCH_PAST_END, @@ -1910,11 +1908,11 @@ enum error_handling_flags { // 2/code) driver specific error code // 3/message) driver specific error message // The fetch type determines if the indices are numeric, associative, or both. -bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, +bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, _In_ logging_severity severity TSRMLS_DC ); // format and return a driver specfic error -void core_sqlsrv_format_driver_error( _In_ sqlsrv_context& ctx, _In_ sqlsrv_error_const const* custom_error, +void core_sqlsrv_format_driver_error( _In_ sqlsrv_context& ctx, _In_ sqlsrv_error_const const* custom_error, _Out_ sqlsrv_error_auto_ptr& formatted_error, _In_ logging_severity severity TSRMLS_DC, _In_opt_ va_list* args ); @@ -1949,19 +1947,19 @@ inline bool call_error_handler( _Inout_ sqlsrv_context* ctx, _In_ unsigned long // we don't want on a web server #define SQLSRV_ASSERT( condition, msg, ...) if( !(condition)) DIE( msg, ## __VA_ARGS__ ); - -#if defined( PHP_DEBUG ) + +#if defined( PHP_DEBUG ) #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) \ if( !(condition)) { \ DIE (msg, ## __VA_ARGS__ ); \ - } + } #else #define DEBUG_SQLSRV_ASSERT( condition, msg, ... ) ((void)0) -#endif +#endif // check to see if the sqlstate is 01004, truncated field retrieved. Used for retrieving large fields. inline bool is_truncated_warning( _In_ SQLCHAR* state ) @@ -1984,7 +1982,7 @@ inline bool is_truncated_warning( _In_ SQLCHAR* state ) ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, ## __VA_ARGS__ ); \ } \ if( !ignored##unique ) - + #define CHECK_ERROR_UNIQUE( unique, condition, context, ssphp, ...) \ CHECK_ERROR_EX( unique, condition, context, ssphp, ## __VA_ARGS__ ) @@ -2003,11 +2001,11 @@ inline bool is_truncated_warning( _In_ SQLCHAR* state ) if( condition ) { \ ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, ## __VA_ARGS__ ); \ } \ - if( !ignored##unique ) + if( !ignored##unique ) #define CHECK_SQL_WARNING_AS_ERROR( result, context, ... ) \ CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__,( result == SQL_SUCCESS_WITH_INFO ), context, SQLSRV_ERROR_ODBC, ## __VA_ARGS__ ) - + #define CHECK_SQL_WARNING( result, context, ... ) \ if( result == SQL_SUCCESS_WITH_INFO ) { \ (void)call_error_handler( context, 0 TSRMLS_CC, /*warning*/ true, ## __VA_ARGS__ ); \ @@ -2015,7 +2013,7 @@ inline bool is_truncated_warning( _In_ SQLCHAR* state ) #define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ CHECK_WARNING_AS_ERROR_UNIQUE( __COUNTER__, condition, context, ssphp, ## __VA_ARGS__ ) - + #define CHECK_ZEND_ERROR( zr, ctx, error, ... ) \ CHECK_ERROR_UNIQUE( __COUNTER__, ( zr == FAILURE ), ctx, error, ## __VA_ARGS__ ) \ @@ -2029,7 +2027,7 @@ inline bool is_truncated_warning( _In_ SQLCHAR* state ) ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, ##__VA_ARGS__ ); \ } \ if( !ignored ) - + // throw an exception after it has been hooked into the custom error handler #define THROW_CORE_ERROR( ctx, custom, ... ) \ (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, ## __VA_ARGS__ ); \ @@ -2051,25 +2049,25 @@ namespace core { inline void check_for_mars_error( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r TSRMLS_DC ) { - // Skip this if not SQL_ERROR - + // Skip this if not SQL_ERROR - // We check for the 'connection busy' error caused by having MultipleActiveResultSets off // and return a more helpful message prepended to the ODBC errors if that error occurs if (r == SQL_ERROR) { SQLCHAR err_msg[SQL_MAX_MESSAGE_LENGTH + 1] = {'\0'}; SQLSMALLINT len = 0; - - SQLRETURN rtemp = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, + + SQLRETURN rtemp = ::SQLGetDiagField( stmt->handle_type(), stmt->handle(), 1, SQL_DIAG_MESSAGE_TEXT, err_msg, SQL_MAX_MESSAGE_LENGTH, &len ); if (rtemp == SQL_SUCCESS_WITH_INFO && len > SQL_MAX_MESSAGE_LENGTH) { - // if the error message is this long, then it must not be the mars message - // defined as ODBC_CONNECTION_BUSY_ERROR -- so return here and continue the + // if the error message is this long, then it must not be the mars message + // defined as ODBC_CONNECTION_BUSY_ERROR -- so return here and continue the // regular error handling return; } CHECK_SQL_ERROR_OR_WARNING( rtemp, stmt ) { - + throw CoreException(); } @@ -2078,7 +2076,7 @@ namespace core { const std::string returned_error( reinterpret_cast( err_msg )); if(( returned_error.find( connection_busy_error ) != std::string::npos )) { - + THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF ); } } @@ -2092,11 +2090,11 @@ namespace core { // These functions take the sqlsrv_context type. However, since the error handling code can alter // the context to hold the error, they are not passed as const. - inline SQLRETURN SQLGetDiagField( _Inout_ sqlsrv_context* ctx, _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, + inline SQLRETURN SQLGetDiagField( _Inout_ sqlsrv_context* ctx, _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Out_writes_opt_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, _Out_opt_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) { - SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, + SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, diag_info_buffer, buffer_length, out_buffer_length ); CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { @@ -2106,7 +2104,7 @@ namespace core { return r; } - inline void SQLAllocHandle( _In_ SQLSMALLINT HandleType, _Inout_ sqlsrv_context& InputHandle, + inline void SQLAllocHandle( _In_ SQLSMALLINT HandleType, _Inout_ sqlsrv_context& InputHandle, _Out_ SQLHANDLE* OutputHandlePtr TSRMLS_DC ) { SQLRETURN r; @@ -2116,7 +2114,7 @@ namespace core { } } - inline void SQLBindParameter( _Inout_ sqlsrv_stmt* stmt, + inline void SQLBindParameter( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT ParameterNumber, _In_ SQLSMALLINT InputOutputType, _In_ SQLSMALLINT ValueType, @@ -2129,9 +2127,9 @@ namespace core { TSRMLS_DC ) { SQLRETURN r; - r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, + r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, DecimalDigits, ParameterValuePtr, BufferLength, StrLen_Or_IndPtr ); - + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } @@ -2146,7 +2144,7 @@ namespace core { } } - inline void SQLColAttribute( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier, + inline void SQLColAttribute( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier, _Out_writes_bytes_opt_(buffer_length) SQLPOINTER field_type_char, _In_ SQLSMALLINT buffer_length, _Out_opt_ SQLSMALLINT* out_buffer_length, _Out_opt_ SQLLEN* field_type_num TSRMLS_DC ) { @@ -2175,9 +2173,9 @@ namespace core { _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable TSRMLS_DC ) { SQLRETURN r; - r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, + r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, data_type, col_size, decimal_digits, nullable); - + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } @@ -2220,17 +2218,17 @@ namespace core { inline void SQLEndTran( _In_ SQLSMALLINT handleType, _Inout_ sqlsrv_conn* conn, _In_ SQLSMALLINT completionType TSRMLS_DC ) { SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); - + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { throw CoreException(); } } - // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success + // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success inline SQLRETURN SQLExecDirect( _Inout_ sqlsrv_stmt* stmt, _In_ char* sql TSRMLS_DC ) { SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); - + check_for_mars_error( stmt, r TSRMLS_CC ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { @@ -2258,7 +2256,7 @@ namespace core { { SQLRETURN r; r = ::SQLExecute( stmt->handle() ); - + check_for_mars_error( stmt, r TSRMLS_CC ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { @@ -2271,7 +2269,7 @@ namespace core { inline SQLRETURN SQLFetchScroll( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLLEN fetch_offset TSRMLS_DC ) { SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); - + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } @@ -2304,7 +2302,7 @@ namespace core { if( r == SQL_NO_DATA ) return r; - + CHECK_SQL_ERROR( r, stmt ) { throw CoreException(); } @@ -2318,13 +2316,13 @@ namespace core { return r; } - + inline void SQLGetInfo( _Inout_ sqlsrv_conn* conn, _In_ SQLUSMALLINT info_type, _Out_writes_bytes_opt_(buffer_len) SQLPOINTER info_value, _In_ SQLSMALLINT buffer_len, _Out_opt_ SQLSMALLINT* str_len TSRMLS_DC ) { SQLRETURN r; r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); - + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { throw CoreException(); } @@ -2335,7 +2333,7 @@ namespace core { { SQLRETURN r; r = ::SQLGetTypeInfo( stmt->handle(), data_type ); - + CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } @@ -2363,7 +2361,7 @@ namespace core { CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); } - + return num_cols; } @@ -2405,14 +2403,14 @@ namespace core { SQLLEN rows_affected; r = ::SQLRowCount( stmt->handle(), &rows_affected ); - + // On Linux platform // DriverName: libmsodbcsql-13.0.so.0.0 // DriverODBCVer: 03.52 // DriverVer: 13.00.0000 // unixODBC: 2.3.1 - // r = ::SQLRowCount( stmt->handle(), &rows_affected ); - // returns r=-1 for an empty result set. + // r = ::SQLRowCount( stmt->handle(), &rows_affected ); + // returns r=-1 for an empty result set. #ifndef _WIN32 if( r == -1 && rows_affected == -1 ) return 0; @@ -2430,7 +2428,7 @@ namespace core { { SQLRETURN r; r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); - + CHECK_SQL_ERROR_OR_WARNING( r, ctx ) { throw CoreException(); } @@ -2460,13 +2458,13 @@ namespace core { inline void SQLSetConnectAttr( _Inout_ sqlsrv_conn* conn, _In_ SQLINTEGER attribute, _In_reads_bytes_opt_(value_len) SQLPOINTER value_ptr, _In_ SQLINTEGER value_len TSRMLS_DC ) { - SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); - + SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); + CHECK_SQL_ERROR_OR_WARNING( r, conn ) { throw CoreException(); } } - + inline void SQLSetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _In_reads_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len TSRMLS_DC ) { SQLRETURN r; @@ -2505,7 +2503,7 @@ namespace core { // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error // that can be thrown from it. - inline void sqlsrv_add_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zend_ulong index, _In_ zval* value TSRMLS_DC) + inline void sqlsrv_add_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zend_ulong index, _In_ zval* value TSRMLS_DC) { int zr = add_index_zval( array, index, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2513,7 +2511,7 @@ namespace core { } } - inline void sqlsrv_add_next_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zval* value TSRMLS_DC) + inline void sqlsrv_add_next_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zval* value TSRMLS_DC) { int zr = add_next_index_zval( array, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2556,7 +2554,7 @@ namespace core { } } - inline void sqlsrv_array_init( _Inout_ sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) + inline void sqlsrv_array_init( _Inout_ sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) { #if PHP_VERSION_ID < 70300 CHECK_ZEND_ERROR(::array_init(new_array), ctx, SQLSRV_ERROR_ZEND_HASH) { @@ -2599,7 +2597,7 @@ namespace core { throw CoreException(); } } - + inline void sqlsrv_zend_hash_index_update( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_ zval* data_z TSRMLS_DC ) { int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; @@ -2624,7 +2622,7 @@ namespace core { throw CoreException(); } } - + inline void sqlsrv_zend_hash_next_index_insert( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zval* data TSRMLS_DC ) { int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; @@ -2648,7 +2646,7 @@ namespace core { throw CoreException(); } } - + inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, _Inout_ HashTable* ht, _Inout_ uint32_t initial_size, _In_ dtor_func_t dtor_fn, _In_ zend_bool persistent TSRMLS_DC ) { diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 090322a5f..d2502c81c 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -329,7 +329,7 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm } // The query timeout setting is inherited from the corresponding connection attribute, but - // the user may override that the query timeout setting using the statement option. + // the user may override that the query timeout setting using the statement option. // In any case, set query timeout using the latest value stmt->set_query_timeout(); @@ -850,7 +850,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient CHECK_CUSTOM_ERROR( stmt->past_fetch_end, stmt, SQLSRV_ERROR_FETCH_PAST_END ) { throw core::CoreException(); } - + // First time only if ( !stmt->fetch_called ) { SQLSMALLINT has_fields; @@ -860,7 +860,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); stmt->column_count = has_fields; } - + CHECK_CUSTOM_ERROR( has_fields == 0, stmt, SQLSRV_ERROR_NO_FIELDS ) { throw core::CoreException(); } @@ -1009,7 +1009,7 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } // Reference: https://docs.microsoft.com/sql/connect/odbc/data-classification - // To retrieve sensitivity classfication data, the first step is to retrieve the IRD(Implementation Row Descriptor) handle by + // To retrieve sensitivity classfication data, the first step is to retrieve the IRD(Implementation Row Descriptor) handle by // calling SQLGetStmtAttr with SQL_ATTR_IMP_ROW_DESC statement attribute r = ::SQLGetStmtAttr(stmt->handle(), SQL_ATTR_IMP_ROW_DESC, (SQLPOINTER)&ird, SQL_IS_POINTER, 0); CHECK_SQL_ERROR_OR_WARNING(r, stmt) { @@ -1074,7 +1074,7 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) CHECK_CUSTOM_ERROR(dcptr != dcend, stmt, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, "Metadata parsing ends unexpectedly") { throw core::CoreException(); } - + stmt->current_sensitivity_metadata = sensitivity_meta; sensitivity_meta.transferred(); } catch (core::CoreException& e) { @@ -1172,7 +1172,7 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // use the previously saved php type sqlsrv_php_type = stmt->current_meta_data[field_index]->sqlsrv_php_type; } - } + } // Verify that we have an acceptable type to convert. CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_phptype(sqlsrv_php_type), stmt, SQLSRV_ERROR_INVALID_TYPE) { @@ -1209,7 +1209,7 @@ bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) { SQLSMALLINT num_cols; SQLLEN rows_affected; - + if (stmt->column_count != ACTIVE_NUM_COLS_INVALID) { num_cols = stmt->column_count; } @@ -1218,7 +1218,7 @@ bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); stmt->column_count = num_cols; } - + if (stmt->row_count != ACTIVE_NUM_ROWS_INVALID) { rows_affected = stmt->row_count; } @@ -1227,7 +1227,7 @@ bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); stmt->row_count = rows_affected; } - + return (num_cols != 0) || (rows_affected > 0); } @@ -1266,7 +1266,7 @@ void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool fin if( r == SQL_NO_DATA ) { - if( &(stmt->output_params) && finalize_output_params ) { + if( finalize_output_params ) { // if we're finished processing result sets, handle the output parameters finalize_output_parameters( stmt TSRMLS_CC ); } @@ -1416,7 +1416,7 @@ void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_ } stmt->decimal_places = static_cast(decimal_places); - } + } catch( core::CoreException& ) { throw; } @@ -1858,7 +1858,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // Reference: https://docs.microsoft.com/sql/odbc/reference/appendixes/sql-to-c-timestamp // Retrieve the datetime data as a string, which may be cached for later use. - // The string is converted to a DateTime object only when it is required to + // The string is converted to a DateTime object only when it is required to // be returned as a zval. case SQLSRV_PHPTYPE_DATETIME: { @@ -1885,7 +1885,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i break; } - + // create a stream wrapper around the field and return that object to the PHP script. calls to fread // on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file // for how these fields are used. @@ -2012,7 +2012,7 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve // conversion, to avoid the performance penalty of calling ToUtf16 wchar_size = buffer_len; #else - // Calculate the size of the necessary buffer from the length of the string - + // Calculate the size of the necessary buffer from the length of the string - // no performance penalty because MultiByteToWidechar is highly optimised wchar_size = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, reinterpret_cast( buffer ), static_cast( buffer_len ), NULL, 0 ); #endif // !_WIN32 @@ -2275,7 +2275,7 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f // number of decimals adheres to the column field scale. If smaller, the output value may be rounded up. // // Note: it's possible that the decimal data does not contain a decimal dot because the field scale is 0. - // Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of + // Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of // format_decimals and decimals_places // std::string str = field_value; @@ -2292,8 +2292,8 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f } // We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php - // as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is - // followed by 5 or above. + // as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is + // followed by 5 or above. bool isNegative = false; @@ -2313,16 +2313,16 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f str = oss.str(); pos++; } - + if (num_decimals == NO_CHANGE_DECIMAL_PLACES) { // Add the minus sign back if negative if (isNegative) { std::ostringstream oss; oss << '-' << str.substr(0); str = oss.str(); - } + } } else { - // Start formatting + // Start formatting size_t last = 0; if (num_decimals == 0) { // Chop all decimal digits, including the decimal dot @@ -2532,7 +2532,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) throw core::CoreException(); } } - // if the output param is a boolean, still convert to + // if the output param is a boolean, still convert to // a long integer first to take care of rounding convert_to_long(value_z); if (output_param->is_bool) { @@ -2911,9 +2911,9 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* // account for the NULL terminator returned by ODBC and needed by Zend to avoid a "String not null terminated" debug warning SQLULEN field_size = column_size; - // with AE on, when column_size is retrieved from SQLDescribeParam, column_size + // with AE on, when column_size is retrieved from SQLDescribeParam, column_size // does not include the negative sign or decimal place for numeric values - // VSO Bug 2913: without AE, the same can happen as well, in particular to decimals + // VSO Bug 2913: without AE, the same can happen as well, in particular to decimals // and numerics with precision/scale specified if (sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC || sql_type == SQL_BIGINT || sql_type == SQL_INTEGER || sql_type == SQL_SMALLINT) { // include the possible negative sign @@ -2948,8 +2948,8 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* // A zval string len doesn't include the null. This calculates the length it should be // regardless of whether the ODBC type contains the NULL or not. - // initialize the newly allocated space - char *p = ZSTR_VAL(param_z_string); + // initialize the newly allocated space + char *p = ZSTR_VAL(param_z_string); p = p + original_len; memset(p, '\0', expected_len - original_len); ZVAL_NEW_STR(param_z, param_z_string); diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index b67af51a7..ff72a6fa9 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -7,13 +7,13 @@ // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), -// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, // and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //--------------------------------------------------------------------------------------------------------------------------------- @@ -85,11 +85,6 @@ const char* NULLABLE = "Nullable"; } -// warning message printed when a parameter variable is not passed by reference -const char SS_SQLSRV_WARNING_PARAM_VAR_NOT_REF[] = "Variable parameter %d not passed by reference (prefaced with an &). " - "Variable parameters passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value. " - "For more information, see sqlsrv_prepare or sqlsrv_query in the API Reference section of the product documentation."; - /* internal functions */ void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval ); @@ -103,7 +98,7 @@ void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ); bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ); bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype type ); void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, - _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, + _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ); void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ); @@ -156,7 +151,7 @@ ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) if( fetch_field_names != NULL ) { for( int i=0; i < fetch_fields_count; ++i ) { - + sqlsrv_free( fetch_field_names[i].name ); } sqlsrv_free( fetch_field_names ); @@ -165,16 +160,16 @@ ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) zval_ptr_dtor( params_z ); sqlsrv_free(params_z); } -} +} // to be called whenever a new result set is created, such as after an // execute or next_result. Resets the state variables and calls the subclass. -void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) +void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) { if( fetch_field_names != NULL ) { for( int i=0; i < fetch_fields_count; ++i ) { - + sqlsrv_free( fetch_field_names[i].name ); } sqlsrv_free( fetch_field_names ); @@ -185,7 +180,7 @@ void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) sqlsrv_stmt::new_result_set( TSRMLS_C ); } -// Returns a php type for a given sql type. Also sets the encoding wherever applicable. +// Returns a php type for a given sql type. Also sets the encoding wherever applicable. sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ) { sqlsrv_phptype ss_phptype; @@ -299,13 +294,13 @@ void ss_sqlsrv_stmt::set_query_timeout() } // sqlsrv_execute( resource $stmt ) -// +// // Executes a previously prepared statement. See sqlsrv_prepare for information // on preparing a statement for execution. -// +// // This function is ideal for executing a prepared statement multiple times with // different parameter values. See the MSDN documentation -// +// // Parameters // $stmt: A resource specifying the statement to be executed. For more // information about how to create a statement resource, see sqlsrv_prepare. @@ -316,12 +311,12 @@ void ss_sqlsrv_stmt::set_query_timeout() PHP_FUNCTION( sqlsrv_execute ) { LOG_FUNCTION( "sqlsrv_execute" ); - + ss_sqlsrv_stmt* stmt = NULL; - + try { - PROCESS_PARAMS( stmt, "r", _FN_, 0 ); + PROCESS_PARAMS( stmt, "r", _FN_, 0 ); CHECK_CUSTOM_ERROR(( !stmt->prepared ), stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED ) { throw ss::SSException(); } @@ -340,11 +335,11 @@ PHP_FUNCTION( sqlsrv_execute ) bind_params( stmt TSRMLS_CC ); core_sqlsrv_execute( stmt TSRMLS_CC ); - + RETURN_TRUE; } catch( core::CoreException& ) { - + RETURN_FALSE; } catch( ... ) { @@ -382,8 +377,8 @@ PHP_FUNCTION( sqlsrv_fetch ) PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset ); try { - - CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, + + CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { throw ss::SSException(); } @@ -406,7 +401,7 @@ PHP_FUNCTION( sqlsrv_fetch ) } // sqlsrv_fetch_array( resource $stmt [, int $fetchType] ) -// +// // Retrieves the next row of data as an array. // // Parameters @@ -425,7 +420,7 @@ PHP_FUNCTION( sqlsrv_fetch ) PHP_FUNCTION( sqlsrv_fetch_array ) { LOG_FUNCTION( "sqlsrv_fetch_array" ); - + ss_sqlsrv_stmt* stmt = NULL; zend_long fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied @@ -436,13 +431,13 @@ PHP_FUNCTION( sqlsrv_fetch_array ) PROCESS_PARAMS( stmt, "r|lll", _FN_, 3, &fetch_type, &fetch_style, &fetch_offset ); try { - - CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt, + + CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE ) { throw ss::SSException(); } - CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, + CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { throw ss::SSException(); } @@ -467,7 +462,7 @@ PHP_FUNCTION( sqlsrv_fetch_array ) } // sqlsrv_field_metadata( resource $stmt ) -// +// // Retrieves metadata for the fields of a prepared statement. For information // about preparing a statement, see sqlsrv_query or sqlsrv_prepare. Note that // sqlsrv_field_metadata can be called on any prepared statement, pre- or @@ -477,7 +472,7 @@ PHP_FUNCTION( sqlsrv_fetch_array ) // $stmt: A statement resource for which field metadata is sought. // // Return Value -// retrieve an array of metadata for the current result set on a statement. Each element of the +// retrieve an array of metadata for the current result set on a statement. Each element of the // array is a sub-array containing 5 elements accessed by key: // name - name of the field. // type - integer of the type. Can be compared against the SQLSRV_SQLTYPE constants. @@ -507,10 +502,10 @@ PHP_FUNCTION( sqlsrv_field_metadata ) zval result_meta_data; ZVAL_UNDEF( &result_meta_data ); core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); - + for( SQLSMALLINT f = 0; f < num_cols; ++f ) { field_meta_data* core_meta_data = stmt->current_meta_data[f]; - + // initialize the array zval field_array; ZVAL_UNDEF( &field_array ); @@ -555,7 +550,7 @@ PHP_FUNCTION( sqlsrv_field_metadata ) // add the nullability to the array core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable TSRMLS_CC ); - + if (stmt->data_classification) { data_classification::fill_column_sensitivity_array(stmt, f, &field_array TSRMLS_CC); } @@ -580,7 +575,7 @@ PHP_FUNCTION( sqlsrv_field_metadata ) // sqlsrv_next_result( resource $stmt ) -// +// // Makes the next result (result set, row count, or output parameter) of the // specified statement active. The first (or only) result returned by a batch // query or stored procedure is active without a call to sqlsrv_next_result. @@ -619,7 +614,7 @@ PHP_FUNCTION( sqlsrv_next_result ) RETURN_TRUE; } catch( core::CoreException& ) { - + RETURN_FALSE; } catch( ... ) { @@ -708,7 +703,7 @@ PHP_FUNCTION( sqlsrv_num_rows ) // make sure that the statement is scrollable and the cursor is not dynamic. // if the cursor is dynamic, then the number of rows returned is always -1. - CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt, + CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE ) { throw ss::SSException(); } @@ -750,10 +745,10 @@ PHP_FUNCTION( sqlsrv_num_fields ) PROCESS_PARAMS( stmt, "r", _FN_, 0 ); try { - + // retrieve the number of columns from ODBC fields = core::SQLNumResultCols( stmt TSRMLS_CC ); - + RETURN_LONG( fields ); } @@ -768,7 +763,7 @@ PHP_FUNCTION( sqlsrv_num_fields ) } // sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams]]) -// +// // Retrieves the next row of data as a PHP object. // // Parameters @@ -801,7 +796,7 @@ PHP_FUNCTION( sqlsrv_num_fields ) // object and the result set value is applied to the property. For more // information about calling sqlsrv_fetch_object with the $className parameter, // see How to: Retrieve Data as an Object (Microsoft Drivers for PHP for SQL Server). -// +// // If a field with no name is returned, sqlsrv_fetch_object will discard the // field value and issue a warning. @@ -824,19 +819,19 @@ PHP_FUNCTION( sqlsrv_fetch_object ) // retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE), // fetch style (see SQLSRV_SCROLL_* constants) and fetch offset - // we also use z! instead of s and a so that null may be passed in as valid values for + // we also use z! instead of s and a so that null may be passed in as valid values for // the class name and ctor params PROCESS_PARAMS( stmt, "r|z!z!ll", _FN_, 4, &class_name_z, &ctor_params_z, &fetch_style, &fetch_offset ); - + try { - - CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, + + CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) { throw ss::SSException(); } if( class_name_z ) { - + CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { throw ss::SSException(); } @@ -847,7 +842,7 @@ PHP_FUNCTION( sqlsrv_fetch_object ) if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) { THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); } - + // fetch the data bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); if( !result ) { @@ -855,8 +850,8 @@ PHP_FUNCTION( sqlsrv_fetch_object ) } fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ TSRMLS_CC ); - properties_ht = Z_ARRVAL( retval_z ); - + properties_ht = Z_ARRVAL( retval_z ); + // find the zend_class_entry of the class the user requested (stdClass by default) for use below zend_class_entry* class_entry = NULL; zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 ); @@ -885,13 +880,13 @@ PHP_FUNCTION( sqlsrv_fetch_object ) // find and call the object's constructor // The header files (zend.h and zend_API.h) declare - // these functions and structures, so by working with those, we were able to + // these functions and structures, so by working with those, we were able to // develop this as a suitable snippet for calling constructors. Some observations: // params must be an array of zval**, not a zval** to an array as we originally // thought. Also, a constructor doesn't show up in the function table, but // is put into the "magic methods" section of the class entry. - // - // The default values of the fci and fcic structures were determined by + // + // The default values of the fci and fcic structures were determined by // calling zend_fcall_info_init with a test callable. // if there is a constructor (e.g., stdClass doesn't have one) @@ -919,7 +914,7 @@ PHP_FUNCTION( sqlsrv_fetch_object ) i++; } ZEND_HASH_FOREACH_END(); } //if( !Z_ISUNDEF( ctor_params_z )) - + // call the constructor function itself. zend_fcall_info fci; zend_fcall_info_cache fcic; @@ -933,7 +928,7 @@ PHP_FUNCTION( sqlsrv_fetch_object ) fci.retval = &ctor_retval_z; fci.param_count = num_params; fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred. - + fci.object = Z_OBJ_P( &retval_z ); memset( &fcic, 0, sizeof( fcic )); @@ -950,7 +945,7 @@ PHP_FUNCTION( sqlsrv_fetch_object ) throw ss::SSException(); } - } //if( class_entry->constructor ) + } //if( class_entry->constructor ) RETURN_ZVAL( &retval_z, 1, 1 ); } @@ -985,7 +980,7 @@ PHP_FUNCTION( sqlsrv_fetch_object ) // for using a function like this: // 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this. // The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least -// one row of data. +// one row of data. // 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as // output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed // until sqlsrv_fetch returns NULL. @@ -1048,8 +1043,8 @@ PHP_FUNCTION( sqlsrv_has_rows ) PHP_FUNCTION( sqlsrv_send_stream_data ) { sqlsrv_stmt* stmt = NULL; - - LOG_FUNCTION( "sqlsrv_send_stream_data" ); + + LOG_FUNCTION( "sqlsrv_send_stream_data" ); // get the statement resource that we've bound streams to PROCESS_PARAMS( stmt, "r", _FN_, 0 ); @@ -1079,14 +1074,14 @@ PHP_FUNCTION( sqlsrv_send_stream_data ) RETURN_FALSE; } catch( ... ) { - + DIE( "sqlsrv_send_stream_data: Unknown exception caught." ); } } // sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType] ) -// +// // Retrieves data from the specified field of the current row. Field data must // be accessed in order. For example, data from the first field cannot be // accessed after data from the second field has been accessed. @@ -1111,7 +1106,7 @@ PHP_FUNCTION( sqlsrv_send_stream_data ) PHP_FUNCTION( sqlsrv_get_field ) { LOG_FUNCTION( "sqlsrv_get_field" ); - + ss_sqlsrv_stmt* stmt = NULL; sqlsrv_phptype sqlsrv_php_type; sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID; @@ -1121,7 +1116,7 @@ PHP_FUNCTION( sqlsrv_get_field ) SQLLEN field_len = -1; zval retval_z; ZVAL_UNDEF(&retval_z); - + // get the statement, the field index and the optional type PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type ); @@ -1136,7 +1131,7 @@ PHP_FUNCTION( sqlsrv_get_field ) core_sqlsrv_get_field( stmt, static_cast( field_index ), sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC ); - convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z ); + convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z ); sqlsrv_free( field_value ); RETURN_ZVAL( &retval_z, 1, 1 ); } @@ -1232,9 +1227,9 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) stmt->executed = false; zval* params_z = stmt->params_z; - + HashTable* params_ht = Z_ARRVAL_P( params_z ); - + zend_ulong index = -1; zend_string *key = NULL; zval* param_z = NULL; @@ -1256,7 +1251,7 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) { throw ss::SSException(); } - + // if it's a parameter array if( Z_TYPE_P( param_z ) == IS_ARRAY ) { @@ -1279,7 +1274,7 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) } // bind the parameter SQLSRV_ASSERT( value_z != NULL, "bind_params: value_z is null." ); - core_sqlsrv_bind_param( stmt, static_cast( index ), direction, value_z, php_out_type, encoding, sql_type, column_size, + core_sqlsrv_bind_param( stmt, static_cast( index ), direction, value_z, php_out_type, encoding, sql_type, column_size, decimal_digits TSRMLS_CC ); } ZEND_HASH_FOREACH_END(); @@ -1294,7 +1289,7 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) } // sqlsrv_cancel( resource $stmt ) -// +// // Cancels a statement. This means that any pending results for the statement // are discarded. After this function is called, the statement can be // re-executed if it was prepared with sqlsrv_prepare. Calling this function is @@ -1313,12 +1308,12 @@ PHP_FUNCTION( sqlsrv_cancel ) LOG_FUNCTION( "sqlsrv_cancel" ); ss_sqlsrv_stmt* stmt = NULL; PROCESS_PARAMS( stmt, "r", _FN_, 0 ); - + try { // close the stream to release the resource close_active_stream( stmt TSRMLS_CC ); - + SQLRETURN r = SQLCancel( stmt->handle() ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw ss::SSException(); @@ -1360,7 +1355,7 @@ void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ) // cannot be used again after this function has been called. // // Parameters -// $stmt: The statement to be closed. +// $stmt: The statement to be closed. // // Return Value // The Boolean value true unless the function is called with an invalid @@ -1384,7 +1379,7 @@ PHP_FUNCTION( sqlsrv_free_stmt ) sqlsrv_context_auto_ptr error_ctx; reset_errors( TSRMLS_C ); - + try { // dummy context to pass to the error handler @@ -1393,14 +1388,14 @@ PHP_FUNCTION( sqlsrv_free_stmt ) // take only the statement resource if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) { - + // Check if it was a zval int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &stmt_r ); CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { throw ss::SSException(); - } - + } + if( Z_TYPE_P( stmt_r ) == IS_NULL ) { RETURN_TRUE; @@ -1413,19 +1408,19 @@ PHP_FUNCTION( sqlsrv_free_stmt ) // verify the resource so we know we're deleting a statement stmt = static_cast(zend_fetch_resource_ex(stmt_r TSRMLS_CC, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor)); - + // if sqlsrv_free_stmt was called on an already closed statment then we just return success. // zend_list_close sets the type of the closed statment to -1. SQLSRV_ASSERT( stmt_r != NULL, "sqlsrv_free_stmt: stmt_r is null." ); if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) { RETURN_TRUE; } - + if( stmt == NULL ) { THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ); } - + // delete the resource from Zend's master list, which will trigger the statement's destructor if( zend_list_close( Z_RES_P(stmt_r) ) == FAILURE ) { LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P( stmt_r )->handle); @@ -1436,17 +1431,17 @@ PHP_FUNCTION( sqlsrv_free_stmt ) // zend_list_close only destroy the resource pointed to by Z_RES_P( stmt_r ), not the zend_resource itself Z_TRY_DELREF_P(stmt_r); ZVAL_NULL( stmt_r ); - + RETURN_TRUE; - + } catch( core::CoreException& ) { - + RETURN_FALSE; } - + catch( ... ) { - + DIE( "sqlsrv_free_stmt: Unknown exception caught." ); } } @@ -1456,16 +1451,16 @@ void stmt_option_ss_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) { throw ss::SSException(); } - + const char* scroll_type = Z_STRVAL_P( value_z ); unsigned long cursor_type = -1; - + // find which cursor type they would like and set the ODBC statement attribute as such - if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) { + if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) { cursor_type = SQL_CURSOR_STATIC; } - + else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_DYNAMIC )) { cursor_type = SQL_CURSOR_DYNAMIC; @@ -1477,12 +1472,12 @@ void stmt_option_ss_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt } else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_FORWARD )) { - + cursor_type = SQL_CURSOR_FORWARD_ONLY; } else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_BUFFERED )) { - + cursor_type = SQLSRV_CURSOR_BUFFERED; } @@ -1553,7 +1548,7 @@ void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_ // put in the column size and scale/decimal digits of the sql server type // these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx // for SQL_VARBINARY, SQL_VARCHAR, and SQL_WLONGVARCHAR types, see https://msdn.microsoft.com/en-CA/library/ms187993.aspx -bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size, +bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size, _Out_ SQLSMALLINT* decimal_digits ) { *decimal_digits = 0; @@ -1608,7 +1603,7 @@ bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sq } break; case SQL_WCHAR: - case SQL_WVARCHAR: + case SQL_WVARCHAR: *column_size = sqlsrv_type.typeinfo.size; if( *column_size == SQLSRV_SIZE_MAX_TYPE ) { *column_size = SQL_SS_LENGTH_UNLIMITED; @@ -1731,7 +1726,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ case SQL_SS_TIME2: case SQL_TYPE_TIMESTAMP: { - if (stmt->date_as_string) { + if (stmt->date_as_string) { sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; sqlsrv_phptype.typeinfo.encoding = stmt->encoding(); } @@ -1749,7 +1744,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { sqlsrv_phptype.typeinfo.encoding = stmt->conn->encoding(); } - + return sqlsrv_phptype; } @@ -1793,9 +1788,9 @@ void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) } } else { - + // otherwise, we fetch the first row, but record that we did. sqlsrv_fetch checks this - // flag and simply skips the first fetch, knowing it was already done. It records its own + // flag and simply skips the first fetch, knowing it was already done. It records its own // flags to know if it should fetch on subsequent calls. r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); @@ -1812,7 +1807,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) { // get the numer of columns in the result set SQLSMALLINT num_cols = -1; - + num_cols = stmt->current_meta_data.size(); bool getMetaData = false; @@ -1887,8 +1882,8 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ throw ss::SSException(); } #else - array_init(&fields); -#endif + array_init(&fields); +#endif for( int i = 0; i < num_cols; ++i ) { SQLLEN field_len = -1; @@ -1924,7 +1919,7 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ } } //only addref when the fetch_type is BOTH because this is the only case when fields(hashtable) - //has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because + //has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because //fields now only has 1 element pointing to field and we want the ref count to be only 1 if (fetch_type == SQLSRV_FETCH_BOTH) { Z_TRY_ADDREF(field); @@ -1934,7 +1929,7 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ } void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, - _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, + _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) { @@ -1954,7 +1949,7 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, // handle the array parameters that contain the value/var, direction, php_type, sql_type zend_hash_internal_pointer_reset_ex( param_ht, &pos ); - if( zend_hash_has_more_elements_ex( param_ht, &pos ) == FAILURE || + if( zend_hash_has_more_elements_ex( param_ht, &pos ) == FAILURE || (var_or_val = zend_hash_get_current_data_ex(param_ht, &pos)) == NULL) { THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ); @@ -1977,7 +1972,7 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, CHECK_CUSTOM_ERROR( !Z_ISREF_P( var_or_val ) && ( direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT ), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1 ) { throw ss::SSException(); } - + } else { direction = SQL_PARAM_INPUT; @@ -1986,7 +1981,7 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, // extract the php type and encoding from the 3rd parameter if ( zend_hash_move_forward_ex( param_ht, &pos ) == SUCCESS && ( temp = zend_hash_get_current_data_ex( param_ht, &pos )) != NULL && Z_TYPE_P( temp ) != IS_NULL ) { - + php_type_param_was_null = false; sqlsrv_phptype sqlsrv_phptype; @@ -1997,7 +1992,7 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, sqlsrv_phptype.value = Z_LVAL_P( temp ); - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_phptype ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_phptype ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) { throw ss::SSException(); @@ -2005,7 +2000,7 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, php_out_type = static_cast( sqlsrv_phptype.typeinfo.type ); encoding = ( SQLSRV_ENCODING ) sqlsrv_phptype.typeinfo.encoding; - // if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established + // if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established // by the connection if( encoding == SQLSRV_ENCODING_DEFAULT ) { encoding = stmt->conn->encoding(); @@ -2013,7 +2008,7 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, } // set default for php type and encoding if not supplied else { - + php_type_param_was_null = true; if ( Z_ISREF_P( var_or_val )){ @@ -2025,7 +2020,7 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, encoding = stmt->encoding(); if( encoding == SQLSRV_ENCODING_DEFAULT ) { encoding = stmt->conn->encoding(); - } + } } // get the server type, column size/precision and the decimal digits if provided @@ -2042,12 +2037,12 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, sqlsrv_sql_type.value = Z_LVAL_P( temp ); // since the user supplied this type, make sure it's valid - CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_sqltype( sqlsrv_sql_type ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, + CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_sqltype( sqlsrv_sql_type ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1 ) { throw ss::SSException(); - } - + } + bool size_okay = determine_column_size_or_precision( stmt, sqlsrv_sql_type, &column_size, &decimal_digits ); CHECK_CUSTOM_ERROR( !size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1 ) { @@ -2076,7 +2071,7 @@ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, sqlsrv_phptype sqlsrv_phptype; sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, (SQLUINTEGER)column_size, true ); - + // we DIE here since everything should have been validated already and to return the user an error // for our own logic error would be confusing/misleading. SQLSRV_ASSERT( sqlsrv_phptype.typeinfo.type != PHPTYPE_INVALID, "An invalid php type was returned with (supposed) " @@ -2121,7 +2116,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ) case SQLSRV_PHPTYPE_STRING: case SQLSRV_PHPTYPE_STREAM: { - if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR + if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR || type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { return true; } @@ -2152,7 +2147,7 @@ bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype sql_type ) case SQL_BINARY: case SQL_CHAR: case SQL_WCHAR: - case SQL_WVARCHAR: + case SQL_WVARCHAR: case SQL_VARBINARY: case SQL_VARCHAR: case SQL_DECIMAL: @@ -2202,7 +2197,7 @@ void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) int size = 0; if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &size_p, &size_len ) == FAILURE ) { - + return; } if (size_p) { @@ -2226,7 +2221,7 @@ void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) } int max_size = SQL_SERVER_MAX_FIELD_SIZE; - // size is actually the number of characters, not the number of bytes, so if they ask for a + // size is actually the number of characters, not the number of bytes, so if they ask for a // 2 byte per character type, then we half the maximum size allowed. if( type == SQL_WVARCHAR || type == SQL_WCHAR ) { max_size >>= 1; @@ -2236,7 +2231,7 @@ void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) LOG( SEV_ERROR, "invalid size. size must be > 0 and <= %1!d! characters or 'max'", max_size ); size = SQLSRV_INVALID_SIZE; } - + sqlsrv_sqltype sql_type; sql_type.typeinfo.type = type; sql_type.typeinfo.size = size; @@ -2253,15 +2248,15 @@ void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) zend_long scale = SQLSRV_INVALID_SCALE; if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) { - + return; } - + if( prec > SQL_SERVER_MAX_PRECISION ) { LOG( SEV_ERROR, "Invalid precision. Precision can't be > 38" ); prec = SQLSRV_INVALID_PRECISION; } - + if( prec < 0 ) { LOG( SEV_ERROR, "Invalid precision. Precision can't be negative" ); prec = SQLSRV_INVALID_PRECISION; @@ -2285,11 +2280,11 @@ void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) { - SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." ); + SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." ); char* encoding_param; size_t encoding_param_len = 0; - + // set the default encoding values to invalid so that // if the encoding isn't validated, it will return the invalid setting. sqlsrv_phptype sqlsrv_php_type; @@ -2297,7 +2292,7 @@ void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID; if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoding_param, &encoding_param_len ) == FAILURE ) { - + ZVAL_LONG( return_value, sqlsrv_php_type.value ); } From 23f92effab048ccd69eeab6667f1b61b87283bbd Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 19 Dec 2019 11:03:25 -0800 Subject: [PATCH 186/249] Added configurable options for setting locales (#1069) #1063 --- Dockerfile-msphpsql | 12 +- azure-pipelines.yml | 10 +- source/pdo_sqlsrv/pdo_init.cpp | 16 +++ source/pdo_sqlsrv/php_pdo_sqlsrv.h | 4 + source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 8 ++ source/shared/core_init.cpp | 6 - source/sqlsrv/init.cpp | 19 ++++ source/sqlsrv/php_sqlsrv.h | 4 + source/sqlsrv/php_sqlsrv_int.h | 9 ++ .../pdo_sqlsrv/pdo_1063_locale_configs.phpt | 103 ++++++++++++++++++ .../pdo_sqlsrv/pdo_1063_test_locale.php | 61 +++++++++++ .../pdo_sqlsrv/skipif_unix_locales.inc | 29 +++++ test/functional/sqlsrv/TC34_PrepAndExec.phpt | 3 - test/functional/sqlsrv/TC42_FetchField.phpt | 3 - test/functional/sqlsrv/TC43_FetchData.phpt | 3 - test/functional/sqlsrv/TC44_FetchArray.phpt | 3 - test/functional/sqlsrv/TC45_FetchObject.phpt | 3 - .../sqlsrv/TC46_FetchNextResult.phpt | 3 - .../sqlsrv/TC48_FetchScrollable.phpt | 3 - test/functional/sqlsrv/TC51_StreamRead.phpt | 3 - .../sqlsrv/TC55_StreamScrollable.phpt | 3 - .../functional/sqlsrv/skipif_unix_locales.inc | 29 +++++ .../sqlsrv/srv_1063_locale_configs.phpt | 103 ++++++++++++++++++ .../sqlsrv/srv_1063_test_locale.php | 75 +++++++++++++ .../sqlsrv/test_stream_large_data.phpt | 3 - 25 files changed, 477 insertions(+), 41 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_1063_test_locale.php create mode 100644 test/functional/pdo_sqlsrv/skipif_unix_locales.inc create mode 100644 test/functional/sqlsrv/skipif_unix_locales.inc create mode 100644 test/functional/sqlsrv/srv_1063_locale_configs.phpt create mode 100644 test/functional/sqlsrv/srv_1063_test_locale.php diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 4960b5843..512d3cf54 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -39,12 +39,14 @@ ENV TEST_PHP_SQL_PWD Password123 # update PATH after ODBC driver and tools are installed ENV PATH="/opt/mssql-tools/bin:${PATH}" -# add locale iso-8859-1 +# add locales for testing RUN sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen -RUN locale-gen en_US +RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen +RUN sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen +RUN locale-gen # set locale to utf-8 -RUN locale-gen en_US.UTF-8 +# RUN locale-gen en_US.UTF-8 ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' # install coveralls (upgrade both pip and requests first) @@ -65,6 +67,10 @@ RUN /bin/bash -c "./packagize.sh" RUN echo "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.3/mods-available/sqlsrv.ini RUN echo "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.3/mods-available/pdo_sqlsrv.ini +# create a writable ini file for testing locales +RUN echo '' > `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/99-overrides.ini +RUN chmod 666 `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/99-overrides.ini + WORKDIR $PHPSQLDIR/source/sqlsrv RUN /usr/bin/phpize && ./configure LDFLAGS="-lgcov" CXXFLAGS="-O0 --coverage" && make && make install diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6212ef3ff..ef8f34b47 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -107,8 +107,9 @@ jobs: - script: | sudo sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen - sudo locale-gen en_US - sudo locale-gen en_US.UTF-8 + sudo sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen + sudo sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen + sudo locale-gen export LANG='en_US.UTF-8' export LANGUAGE='en_US:en' export LC_ALL='en_US.UTF-8' @@ -151,6 +152,9 @@ jobs: echo copying pdo_sqlsrv to $dest sudo cp 30-pdo_sqlsrv.ini $dest + sudo touch $dest/99-overrides.ini + sudo chmod 666 $dest/99-overrides.ini + php --ri sqlsrv php --ri pdo_sqlsrv displayName: 'Build and install drivers' @@ -162,6 +166,7 @@ jobs: sed -i -e 's/TARGET_USERNAME/'"$(uid)"'/g' MsSetup.inc sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc + export LC_ALL='en_US.UTF-8' php run-tests.php -P ./*.phpt 2>&1 | tee ../sqlsrv.log displayName: 'Run sqlsrv functional tests' @@ -172,6 +177,7 @@ jobs: sed -i -e 's/TARGET_USERNAME/'"$(uid)"'/g' MsSetup.inc sed -i -e 's/TARGET_PASSWORD/'"$(pwd)"'/g' MsSetup.inc + export LC_ALL='en_US.UTF-8' php run-tests.php -P ./*.phpt 2>&1 | tee ../pdo_sqlsrv.log displayName: 'Run pdo_sqlsrv functional tests' diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 8f374093d..de9f33eec 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -220,6 +220,22 @@ PHP_RINIT_FUNCTION(pdo_sqlsrv) ZEND_TSRMLS_CACHE_UPDATE(); #endif +#ifndef _WIN32 + // if necessary, set locale from the environment for ODBC, which MUST be done before any connection + int set_locale = PDO_SQLSRV_G(set_locale_info); + if (set_locale == 2) { + setlocale(LC_ALL, ""); + LOG(SEV_NOTICE, "pdo_sqlsrv: setlocale LC_ALL"); + } + else if (set_locale == 1) { + setlocale(LC_CTYPE, ""); + LOG(SEV_NOTICE, "pdo_sqlsrv: setlocale LC_CTYPE"); + } + else { + LOG(SEV_NOTICE, "pdo_sqlsrv: setlocale NONE"); + } +#endif + LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" ); return SUCCESS; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index a6403219a..f3d13b857 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -32,6 +32,10 @@ ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) unsigned int log_severity; zend_long client_buffer_max_size; +#ifndef _WIN32 +zend_long set_locale_info; +#endif + ZEND_END_MODULE_GLOBALS(pdo_sqlsrv) ZEND_EXTERN_MODULE_GLOBALS(pdo_sqlsrv); diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 2b7269c0c..2ddd3961a 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -55,11 +55,19 @@ extern HMODULE g_sqlsrv_hmodule; #define INI_PDO_SQLSRV_LOG "log_severity" #define INI_PREFIX "pdo_sqlsrv." +#ifndef _WIN32 +#define INI_PDO_SET_LOCALE_INFO "set_locale_info" +#endif + PHP_INI_BEGIN() STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) +#ifndef _WIN32 + STD_PHP_INI_ENTRY(INI_PREFIX INI_PDO_SET_LOCALE_INFO, "2", PHP_INI_ALL, OnUpdateLong, set_locale_info, + zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals) +#endif PHP_INI_END() diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index 63d08ce98..d2c386c9b 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -39,12 +39,6 @@ void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_contex SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); -#ifndef _WIN32 - // set locale from environment - // this is necessary for ODBC and MUST be done before connection - setlocale(LC_ALL, ""); -#endif - *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL try { diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 2b394862f..f12e23bb8 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -654,6 +654,25 @@ PHP_RINIT_FUNCTION(sqlsrv) SQLSRV_G( log_subsystems ) = INI_INT( subsystems ); SQLSRV_G( buffered_query_limit ) = INI_INT( buffered_limit ); +#ifndef _WIN32 + char set_locale_info[] = INI_PREFIX INI_SET_LOCALE_INFO; + SQLSRV_G(set_locale_info) = INI_INT(set_locale_info); + + // if necessary, set locale from the environment for ODBC, which MUST be done before any connection + int set_locale = SQLSRV_G(set_locale_info); + if (set_locale == 2) { + setlocale(LC_ALL, ""); + } + else if (set_locale == 1) { + setlocale(LC_CTYPE, ""); + } + else { + // Do nothing + } + + LOG(SEV_NOTICE, INI_PREFIX INI_SET_LOCALE_INFO " = %1!d!", set_locale); +#endif + LOG( SEV_NOTICE, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); LOG( SEV_NOTICE, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 36bb9a959..01a19b39b 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -42,6 +42,10 @@ zend_long current_subsystem; zend_bool warnings_return_as_errors; zend_long buffered_query_limit; +#ifndef _WIN32 +zend_long set_locale_info; +#endif + ZEND_END_MODULE_GLOBALS(sqlsrv) ZEND_EXTERN_MODULE_GLOBALS(sqlsrv); diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index 42148b03d..1866e37b9 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -37,6 +37,10 @@ #define INI_BUFFERED_QUERY_LIMIT "ClientBufferMaxKBSize" #define INI_PREFIX "sqlsrv." +#ifndef _WIN32 +#define INI_SET_LOCALE_INFO "SetLocaleInfo" +#endif + PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN( INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS , "1", PHP_INI_ALL, OnUpdateBool, warnings_return_as_errors, zend_sqlsrv_globals, sqlsrv_globals ) @@ -46,6 +50,11 @@ PHP_INI_BEGIN() sqlsrv_globals ) STD_PHP_INI_ENTRY( INI_PREFIX INI_BUFFERED_QUERY_LIMIT, INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, buffered_query_limit, zend_sqlsrv_globals, sqlsrv_globals ) +#ifndef _WIN32 + STD_PHP_INI_ENTRY(INI_PREFIX INI_SET_LOCALE_INFO, "2", PHP_INI_ALL, OnUpdateLong, set_locale_info, + zend_sqlsrv_globals, sqlsrv_globals) +#endif + PHP_INI_END() diff --git a/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt b/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt new file mode 100644 index 000000000..8d4fa73bf --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt @@ -0,0 +1,103 @@ +--TEST-- +GitHub issue 1063 - make setting locale info configurable +--DESCRIPTION-- +This test verifies that the users can configure using ini file to set application locale using the system locale or not. This test is valid for Linux and macOS systems only. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $file"); + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/pdo_1063_test_locale.php ")); + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/pdo_1063_test_locale.php $locale")); +} + +$inifile = PHP_CONFIG_FILE_SCAN_DIR."/99-overrides.ini"; + +$locale1 = strtoupper(PHP_OS) === 'LINUX' ? "en_US.ISO-8859-1" : "en_US.ISO8859-1"; +$locale2 = 'de_DE.UTF-8'; + +runTest(0, $inifile, $locale1); +runTest(1, $inifile, $locale2); +runTest(2, $inifile, $locale2); +?> +--EXPECT-- + +***sqlsrv.SetLocaleInfo = 0 +pdo_sqlsrv.set_locale_info = 0*** + +**Begin** +Current LC_MONETARY: C +Current LC_CTYPE: en_US.UTF-8 +Currency symbol: +Thousands_sep: +Amount formatted: 10000.99 +Friday +December +3.14159 +**End** +**Begin** +Current LC_MONETARY: C +Current LC_CTYPE: en_US.UTF-8 +Setting LC_ALL: en_US.ISO-8859-1 +Currency symbol: $ +Thousands_sep: , +Amount formatted: USD 10,000.99 +Friday +December +3.14159 +**End** + +***sqlsrv.SetLocaleInfo = 1 +pdo_sqlsrv.set_locale_info = 1*** + +**Begin** +Current LC_MONETARY: C +Current LC_CTYPE: en_US.UTF-8 +Currency symbol: +Thousands_sep: +Amount formatted: 10000.99 +Friday +December +3.14159 +**End** +**Begin** +Current LC_MONETARY: C +Current LC_CTYPE: en_US.UTF-8 +Setting LC_ALL: de_DE.UTF-8 +Currency symbol: € +Thousands_sep: . +Amount formatted: 10.000,99 EUR +Freitag +Dezember +3,14159 +**End** + +***sqlsrv.SetLocaleInfo = 2 +pdo_sqlsrv.set_locale_info = 2*** + +**Begin** +Current LC_MONETARY: en_US.UTF-8 +Current LC_CTYPE: en_US.UTF-8 +Currency symbol: $ +Thousands_sep: , +Amount formatted: USD 10,000.99 +Friday +December +3.14159 +**End** +**Begin** +Current LC_MONETARY: en_US.UTF-8 +Current LC_CTYPE: en_US.UTF-8 +Setting LC_ALL: de_DE.UTF-8 +Currency symbol: € +Thousands_sep: . +Amount formatted: 10.000,99 EUR +Freitag +Dezember +3,14159 +**End** diff --git a/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php b/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php new file mode 100644 index 000000000..b789d0509 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php @@ -0,0 +1,61 @@ +exec($tsql); +} + + +// This test is invoked by pdo_1063_locale_configs.phpt +require_once('MsSetup.inc'); + +$locale = ($_SERVER['argv'][1] ?? ''); + +echo "**Begin**" . PHP_EOL; +echo "Current LC_MONETARY: " . setlocale(LC_MONETARY, 0) . PHP_EOL; +echo "Current LC_CTYPE: " . setlocale(LC_CTYPE, 0) . PHP_EOL; + +if (!empty($locale)) { + $loc = setlocale(LC_ALL, $locale); + echo "Setting LC_ALL: " . $loc . PHP_EOL; +} + +$info = localeconv(); +echo "Currency symbol: " . $info['currency_symbol'] . PHP_EOL; +echo "Thousands_sep: " . $info['thousands_sep'] . PHP_EOL; + +$n1 = 10000.98765; +echo "Amount formatted: " . money_format("%i", $n1) . PHP_EOL; +echo strftime("%A", strtotime("12/25/2020")) . PHP_EOL; +echo strftime("%B", strtotime("12/25/2020")) . PHP_EOL; + +try { + $conn = new PDO("sqlsrv:server = $server; database=$databaseName; driver=$driver", $uid, $pwd ); + + $tableName = "[" . "pdo1063" . $locale . "]"; + + dropTable($conn, $tableName); + + $pi = "3.14159"; + + $stmt = $conn->query("CREATE TABLE $tableName (c1 FLOAT)"); + $stmt = $conn->query("INSERT INTO $tableName (c1) VALUES ($pi)"); + + $sql = "SELECT c1 FROM $tableName"; + $stmt = $conn->prepare($sql, array(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true)); + $stmt->execute(); + + $row = $stmt->fetch(PDO::FETCH_NUM); + echo ($row[0]) . PHP_EOL; + unset($stmt); + + dropTable($conn, $tableName); + + unset($conn); +} catch( PDOException $e ) { + print_r( $e->getMessage() ); +} + +echo "**End**" . PHP_EOL; +?> \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/skipif_unix_locales.inc b/test/functional/pdo_sqlsrv/skipif_unix_locales.inc new file mode 100644 index 000000000..5bf6eac24 --- /dev/null +++ b/test/functional/pdo_sqlsrv/skipif_unix_locales.inc @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/TC34_PrepAndExec.phpt b/test/functional/sqlsrv/TC34_PrepAndExec.phpt index ae9365cae..6364f22fe 100644 --- a/test/functional/sqlsrv/TC34_PrepAndExec.phpt +++ b/test/functional/sqlsrv/TC34_PrepAndExec.phpt @@ -7,9 +7,6 @@ Validates that a prepared statement can be successfully executed more than once. PHPT_EXEC=true --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/TC42_FetchField.phpt b/test/functional/sqlsrv/TC42_FetchField.phpt index 21240a669..01a4d9935 100644 --- a/test/functional/sqlsrv/TC42_FetchField.phpt +++ b/test/functional/sqlsrv/TC42_FetchField.phpt @@ -7,9 +7,6 @@ retrieving fields from a table including rows with all supported SQL types (28 t PHPT_EXEC=true --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/TC43_FetchData.phpt b/test/functional/sqlsrv/TC43_FetchData.phpt index 023c77daa..7228d2d4c 100644 --- a/test/functional/sqlsrv/TC43_FetchData.phpt +++ b/test/functional/sqlsrv/TC43_FetchData.phpt @@ -4,9 +4,6 @@ Fetch Field Data Test verifies the data retrieved via sqlsrv_get_field PHPT_EXEC=true --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/TC44_FetchArray.phpt b/test/functional/sqlsrv/TC44_FetchArray.phpt index 0c7ce0296..70a9366e4 100644 --- a/test/functional/sqlsrv/TC44_FetchArray.phpt +++ b/test/functional/sqlsrv/TC44_FetchArray.phpt @@ -7,9 +7,6 @@ by checking all fetch type modes. PHPT_EXEC=true --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/TC45_FetchObject.phpt b/test/functional/sqlsrv/TC45_FetchObject.phpt index c5b9865b0..589c45400 100644 --- a/test/functional/sqlsrv/TC45_FetchObject.phpt +++ b/test/functional/sqlsrv/TC45_FetchObject.phpt @@ -6,9 +6,6 @@ Verifies data retrieval via "sqlsrv_fetch_object". PHPT_EXEC=true --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/TC46_FetchNextResult.phpt b/test/functional/sqlsrv/TC46_FetchNextResult.phpt index 4c3dc4bf9..46c6d43b5 100644 --- a/test/functional/sqlsrv/TC46_FetchNextResult.phpt +++ b/test/functional/sqlsrv/TC46_FetchNextResult.phpt @@ -6,9 +6,6 @@ Verifies the functionality of "sqlsrv_next_result" PHPT_EXEC=true --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/TC48_FetchScrollable.phpt b/test/functional/sqlsrv/TC48_FetchScrollable.phpt index 381ca6f66..84d15a7b7 100644 --- a/test/functional/sqlsrv/TC48_FetchScrollable.phpt +++ b/test/functional/sqlsrv/TC48_FetchScrollable.phpt @@ -6,9 +6,6 @@ Verifies data retrieval with scrollable result sets. PHPT_EXEC=true --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/TC51_StreamRead.phpt b/test/functional/sqlsrv/TC51_StreamRead.phpt index dd9a0a431..1b6c834c0 100644 --- a/test/functional/sqlsrv/TC51_StreamRead.phpt +++ b/test/functional/sqlsrv/TC51_StreamRead.phpt @@ -7,9 +7,6 @@ can be successfully retrieved as streams. PHPT_EXEC=true --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/TC55_StreamScrollable.phpt b/test/functional/sqlsrv/TC55_StreamScrollable.phpt index 99e973455..be4872ee8 100644 --- a/test/functional/sqlsrv/TC55_StreamScrollable.phpt +++ b/test/functional/sqlsrv/TC55_StreamScrollable.phpt @@ -6,9 +6,6 @@ Verifies the streaming behavior with scrollable resultsets. PHPT_EXEC=true --SKIPIF-- --FILE-- diff --git a/test/functional/sqlsrv/skipif_unix_locales.inc b/test/functional/sqlsrv/skipif_unix_locales.inc new file mode 100644 index 000000000..5bf6eac24 --- /dev/null +++ b/test/functional/sqlsrv/skipif_unix_locales.inc @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/srv_1063_locale_configs.phpt b/test/functional/sqlsrv/srv_1063_locale_configs.phpt new file mode 100644 index 000000000..b3a26d6bf --- /dev/null +++ b/test/functional/sqlsrv/srv_1063_locale_configs.phpt @@ -0,0 +1,103 @@ +--TEST-- +GitHub issue 1063 - make setting locale info configurable +--DESCRIPTION-- +This test verifies that the users can configure using ini file to set application locale using the system locale or not. This test is valid for Linux and macOS systems only. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $file"); + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/srv_1063_test_locale.php ")); + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/srv_1063_test_locale.php $locale")); +} + +$inifile = PHP_CONFIG_FILE_SCAN_DIR."/99-overrides.ini"; + +$locale1 = strtoupper(PHP_OS) === 'LINUX' ? "en_US.ISO-8859-1" : "en_US.ISO8859-1"; +$locale2 = 'de_DE.UTF-8'; + +runTest(0, $inifile, $locale1); +runTest(1, $inifile, $locale2); +runTest(2, $inifile, $locale2); +?> +--EXPECT-- + +***sqlsrv.SetLocaleInfo = 0 +pdo_sqlsrv.set_locale_info = 0*** + +**Begin** +Current LC_MONETARY: C +Current LC_CTYPE: en_US.UTF-8 +Currency symbol: +Thousands_sep: +Amount formatted: 10000.99 +Friday +December +3.14159 +**End** +**Begin** +Current LC_MONETARY: C +Current LC_CTYPE: en_US.UTF-8 +Setting LC_ALL: en_US.ISO-8859-1 +Currency symbol: $ +Thousands_sep: , +Amount formatted: USD 10,000.99 +Friday +December +3.14159 +**End** + +***sqlsrv.SetLocaleInfo = 1 +pdo_sqlsrv.set_locale_info = 1*** + +**Begin** +Current LC_MONETARY: C +Current LC_CTYPE: en_US.UTF-8 +Currency symbol: +Thousands_sep: +Amount formatted: 10000.99 +Friday +December +3.14159 +**End** +**Begin** +Current LC_MONETARY: C +Current LC_CTYPE: en_US.UTF-8 +Setting LC_ALL: de_DE.UTF-8 +Currency symbol: € +Thousands_sep: . +Amount formatted: 10.000,99 EUR +Freitag +Dezember +3,14159 +**End** + +***sqlsrv.SetLocaleInfo = 2 +pdo_sqlsrv.set_locale_info = 2*** + +**Begin** +Current LC_MONETARY: en_US.UTF-8 +Current LC_CTYPE: en_US.UTF-8 +Currency symbol: $ +Thousands_sep: , +Amount formatted: USD 10,000.99 +Friday +December +3.14159 +**End** +**Begin** +Current LC_MONETARY: en_US.UTF-8 +Current LC_CTYPE: en_US.UTF-8 +Setting LC_ALL: de_DE.UTF-8 +Currency symbol: € +Thousands_sep: . +Amount formatted: 10.000,99 EUR +Freitag +Dezember +3,14159 +**End** diff --git a/test/functional/sqlsrv/srv_1063_test_locale.php b/test/functional/sqlsrv/srv_1063_test_locale.php new file mode 100644 index 000000000..fb9588b8b --- /dev/null +++ b/test/functional/sqlsrv/srv_1063_test_locale.php @@ -0,0 +1,75 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/test_stream_large_data.phpt b/test/functional/sqlsrv/test_stream_large_data.phpt index eacf853a1..414baa225 100644 --- a/test/functional/sqlsrv/test_stream_large_data.phpt +++ b/test/functional/sqlsrv/test_stream_large_data.phpt @@ -2,9 +2,6 @@ streaming large amounts of data into a database and getting it out as a string exactly the same. --SKIPIF-- --FILE-- From f64df0497f2304cf2341a6c403377edf9576c9e6 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 6 Jan 2020 10:57:55 -0800 Subject: [PATCH 187/249] Fixed the skipif wordings and styles (#1070) --- Dockerfile-msphpsql | 1 + azure-pipelines.yml | 15 +++--- .../pdo_sqlsrv/pdo_1063_locale_configs.phpt | 8 +-- .../pdo_sqlsrv/pdo_1063_test_locale.php | 28 +++++++++-- test/functional/pdo_sqlsrv/skipif.inc | 6 +-- test/functional/pdo_sqlsrv/skipif_azure.inc | 12 ++--- .../pdo_sqlsrv/skipif_mid-refactor.inc | 34 ++++++------- test/functional/pdo_sqlsrv/skipif_not_akv.inc | 49 ++++++++++--------- test/functional/pdo_sqlsrv/skipif_unix.inc | 11 +++-- .../pdo_sqlsrv/skipif_unix_locales.inc | 13 +++-- .../pdo_sqlsrv/skipif_versions_old.inc | 21 ++++---- test/functional/sqlsrv/skipif_not_akv.inc | 48 +++++++++--------- test/functional/sqlsrv/skipif_unix.inc | 6 +-- .../functional/sqlsrv/skipif_unix_locales.inc | 9 ++-- .../functional/sqlsrv/skipif_versions_old.inc | 33 ++++++------- .../sqlsrv/srv_1063_locale_configs.phpt | 8 +-- .../sqlsrv/srv_1063_test_locale.php | 31 +++++++++--- 17 files changed, 185 insertions(+), 148 deletions(-) diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 512d3cf54..31eba77a1 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -21,6 +21,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && \ make \ php7.3 \ php7.3-dev \ + php7.3-intl \ python-pip \ re2c \ unixodbc-dev \ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ef8f34b47..b4acecbf0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -59,6 +59,8 @@ jobs: displayName: 'Build and install drivers' - job: Linux + variables: + phpver: 7.3 pool: vmImage: 'ubuntu-18.04' steps: @@ -72,13 +74,13 @@ jobs: architecture: 'x64' - script: | - sudo update-alternatives --set php /usr/bin/php$(phpVersion) - sudo update-alternatives --set phar /usr/bin/phar$(phpVersion) - sudo update-alternatives --set phpdbg /usr/bin/phpdbg$(phpVersion) - sudo update-alternatives --set php-cgi /usr/bin/php-cgi$(phpVersion) - sudo update-alternatives --set phar.phar /usr/bin/phar.phar$(phpVersion) + sudo update-alternatives --set php /usr/bin/php$(phpver) + sudo update-alternatives --set phar /usr/bin/phar$(phpver) + sudo update-alternatives --set phpdbg /usr/bin/phpdbg$(phpver) + sudo update-alternatives --set php-cgi /usr/bin/php-cgi$(phpver) + sudo update-alternatives --set phar.phar /usr/bin/phar.phar$(phpver) php -version - displayName: 'Use PHP version $(phpVersion)' + displayName: 'Use PHP version $(phpver)' - script: | echo install ODBC and dependencies @@ -128,6 +130,7 @@ jobs: - script: | echo ready to build extensions + sudo apt-get install -y php$(phpver)-intl cd $(Build.SourcesDirectory)/source chmod a+x packagize.sh ./packagize.sh diff --git a/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt b/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt index 8d4fa73bf..8438cb7f4 100644 --- a/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt @@ -46,7 +46,7 @@ Current LC_CTYPE: en_US.UTF-8 Setting LC_ALL: en_US.ISO-8859-1 Currency symbol: $ Thousands_sep: , -Amount formatted: USD 10,000.99 +Amount formatted: $10,000.99 Friday December 3.14159 @@ -71,7 +71,7 @@ Current LC_CTYPE: en_US.UTF-8 Setting LC_ALL: de_DE.UTF-8 Currency symbol: € Thousands_sep: . -Amount formatted: 10.000,99 EUR +Amount formatted: 10.000,99 € Freitag Dezember 3,14159 @@ -85,7 +85,7 @@ Current LC_MONETARY: en_US.UTF-8 Current LC_CTYPE: en_US.UTF-8 Currency symbol: $ Thousands_sep: , -Amount formatted: USD 10,000.99 +Amount formatted: $10,000.99 Friday December 3.14159 @@ -96,7 +96,7 @@ Current LC_CTYPE: en_US.UTF-8 Setting LC_ALL: de_DE.UTF-8 Currency symbol: € Thousands_sep: . -Amount formatted: 10.000,99 EUR +Amount formatted: 10.000,99 € Freitag Dezember 3,14159 diff --git a/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php b/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php index b789d0509..58be4d0dc 100644 --- a/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php +++ b/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php @@ -6,6 +6,27 @@ function dropTable($conn, $tableName) $conn->exec($tsql); } +function printMoney($amt) +{ + // The money_format() function is deprecated in PHP 7.4, so use intl NumberFormatter + $info = localeconv(); + echo "Currency symbol: " . $info['currency_symbol'] . PHP_EOL; + echo "Thousands_sep: " . $info['thousands_sep'] . PHP_EOL; + + $loc = setlocale(LC_MONETARY, 0); + $symbol = $info['int_curr_symbol']; + + echo "Amount formatted: "; + if (empty($symbol)) { + echo number_format($amt, 2, '.', ''); + } else { + $fmt = new NumberFormatter($loc, NumberFormatter::CURRENCY); + $fmt->setTextAttribute(NumberFormatter::CURRENCY_CODE, $symbol); + $fmt->setAttribute(NumberFormatter::FRACTION_DIGITS, 2); + echo $fmt->format($amt); + } + echo PHP_EOL; +} // This test is invoked by pdo_1063_locale_configs.phpt require_once('MsSetup.inc'); @@ -21,12 +42,9 @@ function dropTable($conn, $tableName) echo "Setting LC_ALL: " . $loc . PHP_EOL; } -$info = localeconv(); -echo "Currency symbol: " . $info['currency_symbol'] . PHP_EOL; -echo "Thousands_sep: " . $info['thousands_sep'] . PHP_EOL; - $n1 = 10000.98765; -echo "Amount formatted: " . money_format("%i", $n1) . PHP_EOL; +printMoney($n1); + echo strftime("%A", strtotime("12/25/2020")) . PHP_EOL; echo strftime("%B", strtotime("12/25/2020")) . PHP_EOL; diff --git a/test/functional/pdo_sqlsrv/skipif.inc b/test/functional/pdo_sqlsrv/skipif.inc index bf4ac502b..ab29e6c59 100644 --- a/test/functional/pdo_sqlsrv/skipif.inc +++ b/test/functional/pdo_sqlsrv/skipif.inc @@ -1,4 +1,4 @@ +if (!extension_loaded("pdo_sqlsrv")) { + die("skip Extension not loaded"); +} diff --git a/test/functional/pdo_sqlsrv/skipif_azure.inc b/test/functional/pdo_sqlsrv/skipif_azure.inc index aec21ea56..e154005b6 100644 --- a/test/functional/pdo_sqlsrv/skipif_azure.inc +++ b/test/functional/pdo_sqlsrv/skipif_azure.inc @@ -1,8 +1,8 @@ +if ($daasMode) { + die("skip test not applicable in Azure\n"); +} diff --git a/test/functional/pdo_sqlsrv/skipif_mid-refactor.inc b/test/functional/pdo_sqlsrv/skipif_mid-refactor.inc index 5c1e87a05..a0c6032d3 100644 --- a/test/functional/pdo_sqlsrv/skipif_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/skipif_mid-refactor.inc @@ -1,17 +1,17 @@ - \ No newline at end of file +if (!extension_loaded("pdo") || !extension_loaded('pdo_sqlsrv')) { + die("skip extension not loaded"); +} diff --git a/test/functional/pdo_sqlsrv/skipif_unix_locales.inc b/test/functional/pdo_sqlsrv/skipif_unix_locales.inc index 5bf6eac24..938307ba1 100644 --- a/test/functional/pdo_sqlsrv/skipif_unix_locales.inc +++ b/test/functional/pdo_sqlsrv/skipif_unix_locales.inc @@ -1,29 +1,28 @@ \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/skipif_versions_old.inc b/test/functional/pdo_sqlsrv/skipif_versions_old.inc index b6f0a5a42..00e3861d0 100644 --- a/test/functional/pdo_sqlsrv/skipif_versions_old.inc +++ b/test/functional/pdo_sqlsrv/skipif_versions_old.inc @@ -1,18 +1,15 @@ \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_not_akv.inc b/test/functional/sqlsrv/skipif_not_akv.inc index 42387d0a8..f7064bd3c 100644 --- a/test/functional/sqlsrv/skipif_not_akv.inc +++ b/test/functional/sqlsrv/skipif_not_akv.inc @@ -1,24 +1,24 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_unix_locales.inc b/test/functional/sqlsrv/skipif_unix_locales.inc index 5bf6eac24..dc44f973e 100644 --- a/test/functional/sqlsrv/skipif_unix_locales.inc +++ b/test/functional/sqlsrv/skipif_unix_locales.inc @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_versions_old.inc b/test/functional/sqlsrv/skipif_versions_old.inc index 2b9f8dedb..0dd04429d 100644 --- a/test/functional/sqlsrv/skipif_versions_old.inc +++ b/test/functional/sqlsrv/skipif_versions_old.inc @@ -1,17 +1,16 @@ - \ No newline at end of file +setTextAttribute(NumberFormatter::CURRENCY_CODE, $symbol); + $fmt->setAttribute(NumberFormatter::FRACTION_DIGITS, 2); + echo $fmt->format($amt); + } + echo PHP_EOL; +} + // This test is invoked by srv_1063_locale_configs.phpt require_once('MsSetup.inc'); @@ -26,12 +48,9 @@ function fatalError($message) echo "Setting LC_ALL: " . $loc . PHP_EOL; } -$info = localeconv(); -echo "Currency symbol: " . $info['currency_symbol'] . PHP_EOL; -echo "Thousands_sep: " . $info['thousands_sep'] . PHP_EOL; - $n1 = 10000.98765; -echo "Amount formatted: " . money_format("%i", $n1) . PHP_EOL; +printMoney($n1); + echo strftime("%A", strtotime("12/25/2020")) . PHP_EOL; echo strftime("%B", strtotime("12/25/2020")) . PHP_EOL; @@ -72,4 +91,4 @@ function fatalError($message) sqlsrv_close($conn); echo "**End**" . PHP_EOL; -?> \ No newline at end of file +?> From 9c9c04a43dab7de211393cce07a7757744fc5f5c Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 15 Jan 2020 07:42:17 -0800 Subject: [PATCH 188/249] Modified locale tests to work in both linux and mac (#1074) --- .../pdo_sqlsrv/pdo_1063_locale_configs.phpt | 33 +-------- .../pdo_sqlsrv/pdo_1063_test_locale.php | 64 ++++++++++++++--- test/functional/sqlsrv/TC43_FetchData.phpt | 4 +- test/functional/sqlsrv/TC44_FetchArray.phpt | 4 +- .../sqlsrv/TC46_FetchNextResult.phpt | 4 +- test/functional/sqlsrv/TC51_StreamRead.phpt | 4 +- .../sqlsrv/TC55_StreamScrollable.phpt | 4 +- .../sqlsrv/srv_1063_locale_configs.phpt | 33 +-------- .../sqlsrv/srv_1063_test_locale.php | 70 +++++++++++++++---- .../sqlsrv/test_stream_large_data.phpt | 4 +- 10 files changed, 121 insertions(+), 103 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt b/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt index 8438cb7f4..32c35f7a4 100644 --- a/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt @@ -1,7 +1,7 @@ --TEST-- GitHub issue 1063 - make setting locale info configurable --DESCRIPTION-- -This test verifies that the users can configure using ini file to set application locale using the system locale or not. This test is valid for Linux and macOS systems only. +This test assumes LC_ALL is 'en_US.UTF-8' and verifies that the users can configure using ini file to set application locale using the system locale or not. This test is valid for Linux and macOS systems only. --ENV-- PHPT_EXEC=true --SKIPIF-- @@ -12,8 +12,8 @@ function runTest($val, $file, $locale) { print("\n***sqlsrv.SetLocaleInfo = $val\npdo_sqlsrv.set_locale_info = $val***\n\n"); shell_exec("echo 'sqlsrv.SetLocaleInfo = $val\npdo_sqlsrv.set_locale_info = $val' > $file"); - print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/pdo_1063_test_locale.php ")); - print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/pdo_1063_test_locale.php $locale")); + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/pdo_1063_test_locale.php $val")); + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/pdo_1063_test_locale.php $val $locale")); } $inifile = PHP_CONFIG_FILE_SCAN_DIR."/99-overrides.ini"; @@ -31,21 +31,12 @@ runTest(2, $inifile, $locale2); pdo_sqlsrv.set_locale_info = 0*** **Begin** -Current LC_MONETARY: C -Current LC_CTYPE: en_US.UTF-8 -Currency symbol: -Thousands_sep: Amount formatted: 10000.99 Friday December 3.14159 **End** **Begin** -Current LC_MONETARY: C -Current LC_CTYPE: en_US.UTF-8 -Setting LC_ALL: en_US.ISO-8859-1 -Currency symbol: $ -Thousands_sep: , Amount formatted: $10,000.99 Friday December @@ -56,21 +47,12 @@ December pdo_sqlsrv.set_locale_info = 1*** **Begin** -Current LC_MONETARY: C -Current LC_CTYPE: en_US.UTF-8 -Currency symbol: -Thousands_sep: Amount formatted: 10000.99 Friday December 3.14159 **End** **Begin** -Current LC_MONETARY: C -Current LC_CTYPE: en_US.UTF-8 -Setting LC_ALL: de_DE.UTF-8 -Currency symbol: € -Thousands_sep: . Amount formatted: 10.000,99 € Freitag Dezember @@ -81,21 +63,12 @@ Dezember pdo_sqlsrv.set_locale_info = 2*** **Begin** -Current LC_MONETARY: en_US.UTF-8 -Current LC_CTYPE: en_US.UTF-8 -Currency symbol: $ -Thousands_sep: , Amount formatted: $10,000.99 Friday December 3.14159 **End** **Begin** -Current LC_MONETARY: en_US.UTF-8 -Current LC_CTYPE: en_US.UTF-8 -Setting LC_ALL: de_DE.UTF-8 -Currency symbol: € -Thousands_sep: . Amount formatted: 10.000,99 € Freitag Dezember diff --git a/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php b/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php index 58be4d0dc..b37174494 100644 --- a/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php +++ b/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php @@ -1,4 +1,5 @@ exec($tsql); } -function printMoney($amt) +function printMoney($amt, $info) { // The money_format() function is deprecated in PHP 7.4, so use intl NumberFormatter - $info = localeconv(); - echo "Currency symbol: " . $info['currency_symbol'] . PHP_EOL; - echo "Thousands_sep: " . $info['thousands_sep'] . PHP_EOL; - $loc = setlocale(LC_MONETARY, 0); $symbol = $info['int_curr_symbol']; @@ -28,22 +25,67 @@ function printMoney($amt) echo PHP_EOL; } -// This test is invoked by pdo_1063_locale_configs.phpt require_once('MsSetup.inc'); -$locale = ($_SERVER['argv'][1] ?? ''); +$setLocaleInfo = ($_SERVER['argv'][1]); +$locale = ($_SERVER['argv'][2] ?? ''); echo "**Begin**" . PHP_EOL; -echo "Current LC_MONETARY: " . setlocale(LC_MONETARY, 0) . PHP_EOL; -echo "Current LC_CTYPE: " . setlocale(LC_CTYPE, 0) . PHP_EOL; +// Assuming LC_ALL is 'en_US.UTF-8', so is LC_CTYPE +// But default LC_MONETARY varies +$ctype = 'en_US.UTF-8'; +switch ($setLocaleInfo) { + case 0: + case 1: + $m = 'C'; $symbol = ''; $sep = ''; + break; + case 2: + $m = 'en_US.UTF-8'; $symbol = '$'; $sep = ','; + break; + default: + die("Unexpected $setLocaleInfo\n"); + break; +} + +$m1 = setlocale(LC_MONETARY, 0); +if ($m !== $m1) { + echo "Unexpected LC_MONETARY: $m1" . PHP_EOL; +} +$c1 = setlocale(LC_CTYPE, 0); +if ($ctype !== $c1) { + echo "Unexpected LC_CTYPE: $c1" . PHP_EOL; +} + +// Set a different locale, if the input is not empty if (!empty($locale)) { $loc = setlocale(LC_ALL, $locale); - echo "Setting LC_ALL: " . $loc . PHP_EOL; + if ($loc !== $locale) { + echo "Unexpected $loc for LC_ALL " . PHP_EOL; + } + + // Currency symbol and thousands separator in Linux and macOS may be different + if ($loc === 'de_DE.UTF-8') { + $symbol = strtoupper(PHP_OS) === 'LINUX' ? '€' : 'Eu'; + $sep = strtoupper(PHP_OS) === 'LINUX' ? '.' : ''; + } else { + $symbol = '$'; + $sep = ','; + } +} + +$info = localeconv(); +if ($symbol !== $info['currency_symbol']) { + echo "$locale: Expected currency symbol '$symbol' but get '" . $info['currency_symbol'] . "'"; + echo PHP_EOL; +} +if ($sep !== $info['thousands_sep']) { + echo "$locale: Expected thousands separator '$sep' but get '" . $info['currency_symbol'] . "'"; + echo PHP_EOL; } $n1 = 10000.98765; -printMoney($n1); +printMoney($n1, $info); echo strftime("%A", strtotime("12/25/2020")) . PHP_EOL; echo strftime("%B", strtotime("12/25/2020")) . PHP_EOL; diff --git a/test/functional/sqlsrv/TC43_FetchData.phpt b/test/functional/sqlsrv/TC43_FetchData.phpt index 7228d2d4c..ad446f0c9 100644 --- a/test/functional/sqlsrv/TC43_FetchData.phpt +++ b/test/functional/sqlsrv/TC43_FetchData.phpt @@ -3,9 +3,7 @@ Fetch Field Data Test verifies the data retrieved via sqlsrv_get_field --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- + --FILE-- + --FILE-- + --FILE-- + --FILE-- $file"); - print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/srv_1063_test_locale.php ")); - print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/srv_1063_test_locale.php $locale")); + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/srv_1063_test_locale.php $val")); + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/srv_1063_test_locale.php $val $locale")); } $inifile = PHP_CONFIG_FILE_SCAN_DIR."/99-overrides.ini"; @@ -31,21 +31,12 @@ runTest(2, $inifile, $locale2); pdo_sqlsrv.set_locale_info = 0*** **Begin** -Current LC_MONETARY: C -Current LC_CTYPE: en_US.UTF-8 -Currency symbol: -Thousands_sep: Amount formatted: 10000.99 Friday December 3.14159 **End** **Begin** -Current LC_MONETARY: C -Current LC_CTYPE: en_US.UTF-8 -Setting LC_ALL: en_US.ISO-8859-1 -Currency symbol: $ -Thousands_sep: , Amount formatted: $10,000.99 Friday December @@ -56,21 +47,12 @@ December pdo_sqlsrv.set_locale_info = 1*** **Begin** -Current LC_MONETARY: C -Current LC_CTYPE: en_US.UTF-8 -Currency symbol: -Thousands_sep: Amount formatted: 10000.99 Friday December 3.14159 **End** **Begin** -Current LC_MONETARY: C -Current LC_CTYPE: en_US.UTF-8 -Setting LC_ALL: de_DE.UTF-8 -Currency symbol: € -Thousands_sep: . Amount formatted: 10.000,99 € Freitag Dezember @@ -81,21 +63,12 @@ Dezember pdo_sqlsrv.set_locale_info = 2*** **Begin** -Current LC_MONETARY: en_US.UTF-8 -Current LC_CTYPE: en_US.UTF-8 -Currency symbol: $ -Thousands_sep: , Amount formatted: $10,000.99 Friday December 3.14159 **End** **Begin** -Current LC_MONETARY: en_US.UTF-8 -Current LC_CTYPE: en_US.UTF-8 -Setting LC_ALL: de_DE.UTF-8 -Currency symbol: € -Thousands_sep: . Amount formatted: 10.000,99 € Freitag Dezember diff --git a/test/functional/sqlsrv/srv_1063_test_locale.php b/test/functional/sqlsrv/srv_1063_test_locale.php index 7efe524e5..06ab387da 100644 --- a/test/functional/sqlsrv/srv_1063_test_locale.php +++ b/test/functional/sqlsrv/srv_1063_test_locale.php @@ -1,4 +1,5 @@ setTextAttribute(NumberFormatter::CURRENCY_CODE, $symbol); $fmt->setAttribute(NumberFormatter::FRACTION_DIGITS, 2); - echo $fmt->format($amt); + echo $fmt->format($amt) . PHP_EOL; } - echo PHP_EOL; } -// This test is invoked by srv_1063_locale_configs.phpt require_once('MsSetup.inc'); -$locale = ($_SERVER['argv'][1] ?? ''); +$setLocaleInfo = ($_SERVER['argv'][1]); +$locale = ($_SERVER['argv'][2] ?? ''); echo "**Begin**" . PHP_EOL; -echo "Current LC_MONETARY: " . setlocale(LC_MONETARY, 0) . PHP_EOL; -echo "Current LC_CTYPE: " . setlocale(LC_CTYPE, 0) . PHP_EOL; +// Assuming LC_ALL is 'en_US.UTF-8', so is LC_CTYPE +// But default LC_MONETARY varies +$ctype = 'en_US.UTF-8'; +switch ($setLocaleInfo) { + case 0: + case 1: + $m = 'C'; $symbol = ''; $sep = ''; + break; + case 2: + $m = 'en_US.UTF-8'; $symbol = '$'; $sep = ','; + break; + default: + fatalError("Unexpected $setLocaleInfo\n"); + break; +} + +$m1 = setlocale(LC_MONETARY, 0); +if ($m !== $m1) { + echo "Unexpected LC_MONETARY: $m1" . PHP_EOL; +} +$c1 = setlocale(LC_CTYPE, 0); +if ($ctype !== $c1) { + echo "Unexpected LC_CTYPE: $c1" . PHP_EOL; +} + +// Set a different locale, if the input is not empty if (!empty($locale)) { $loc = setlocale(LC_ALL, $locale); - echo "Setting LC_ALL: " . $loc . PHP_EOL; + if ($loc !== $locale) { + echo "Unexpected $loc for LC_ALL " . PHP_EOL; + } + + // Currency symbol and thousands separator in Linux and macOS may be different + if ($loc === 'de_DE.UTF-8') { + $symbol = strtoupper(PHP_OS) === 'LINUX' ? '€' : 'Eu'; + $sep = strtoupper(PHP_OS) === 'LINUX' ? '.' : ''; + } else { + $symbol = '$'; + $sep = ','; + } +} + +$info = localeconv(); + +if ($symbol !== $info['currency_symbol']) { + echo "$locale: Expected currency symbol '$symbol' but get '" . $info['currency_symbol'] . "'"; + echo PHP_EOL; +} +if ($sep !== $info['thousands_sep']) { + echo "$locale: Expected thousands separator '$sep' but get '" . $info['currency_symbol'] . "'"; + echo PHP_EOL; } $n1 = 10000.98765; -printMoney($n1); +printMoney($n1, $info); echo strftime("%A", strtotime("12/25/2020")) . PHP_EOL; echo strftime("%B", strtotime("12/25/2020")) . PHP_EOL; diff --git a/test/functional/sqlsrv/test_stream_large_data.phpt b/test/functional/sqlsrv/test_stream_large_data.phpt index 414baa225..f73594e40 100644 --- a/test/functional/sqlsrv/test_stream_large_data.phpt +++ b/test/functional/sqlsrv/test_stream_large_data.phpt @@ -1,9 +1,7 @@ --TEST-- streaming large amounts of data into a database and getting it out as a string exactly the same. --SKIPIF-- - + --FILE-- Date: Wed, 22 Jan 2020 08:01:59 -0800 Subject: [PATCH 189/249] Include sql_variant type for buffered queries (#1080) --- .travis.yml | 4 +- azure-pipelines.yml | 2 + source/shared/core_results.cpp | 2 + .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 18 +++--- ...pdo_1079_sql_variant_buffered_queries.phpt | 59 ++++++++++++++++++ .../pdo_fetch_variants_diff_styles.phpt | 13 +++- .../pdo_simple_update_variants.phpt | 13 ++-- .../sqlsrv/sqlsrv_param_input_variants.phpt | 3 +- .../sqlsrv/sqlsrv_simple_fetch_variants.phpt | 2 +- .../sqlsrv/sqlsrv_simple_update_variants.phpt | 13 ++-- ...srv_1079_sql_variant_buffered_queries.phpt | 62 +++++++++++++++++++ 11 files changed, 169 insertions(+), 22 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1079_sql_variant_buffered_queries.phpt create mode 100644 test/functional/sqlsrv/srv_1079_sql_variant_buffered_queries.phpt diff --git a/.travis.yml b/.travis.yml index e368b84e5..86cc31ae3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,10 +20,10 @@ env: - TEST_PHP_SQL_PWD=Password123 before_install: - - docker pull mcr.microsoft.com/mssql/server:2019-GA-ubuntu-16.04 + - docker pull mcr.microsoft.com/mssql/server:2019-latest install: - - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-CTP3.2-ubuntu + - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password123' -p 1433:1433 --name=$TEST_PHP_SQL_SERVER -d mcr.microsoft.com/mssql/server:2019-latest - docker build --build-arg PHPSQLDIR=$PHPSQLDIR -t msphpsql-dev -f Dockerfile-msphpsql . before_script: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b4acecbf0..68c85525a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -75,10 +75,12 @@ jobs: - script: | sudo update-alternatives --set php /usr/bin/php$(phpver) + sudo update-alternatives --set phpize /usr/bin/phpize$(phpver) sudo update-alternatives --set phar /usr/bin/phar$(phpver) sudo update-alternatives --set phpdbg /usr/bin/phpdbg$(phpver) sudo update-alternatives --set php-cgi /usr/bin/php-cgi$(phpver) sudo update-alternatives --set phar.phar /usr/bin/phar.phar$(phpver) + sudo update-alternatives --set php-config /usr/bin/php-config$(phpver) php -version displayName: 'Use PHP version $(phpver)' diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 1fbe005f1..5141ecc98 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -508,6 +508,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm break; case SQL_CHAR: case SQL_VARCHAR: + case SQL_SS_VARIANT: if ( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) { offset += sizeof( void* ); } @@ -610,6 +611,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm case SQL_CHAR: case SQL_VARCHAR: + case SQL_SS_VARIANT: case SQL_LONGVARCHAR: // If encoding is set to UTF-8, the following types are not necessarily column size. // We need to call SQLGetData with c_type SQL_C_WCHAR and set the size accordingly. diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index 969d14469..1f5d997bc 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -1707,19 +1707,21 @@ function CallProcEx($conn, $procName, $procPrefix, $procArgs, $procValues) function CreateFunc($conn, $funcName, $funcArgs, $retType, $funcCode) { DropFunc($conn, $funcName); - $stmt = sqlsrv_query($conn, "CREATE FUNCTION [$funcName] ($funcArgs) RETURNS $retType AS BEGIN $funcCode END"); - if ($stmt === false) { - FatalError("Failed to create test function"); + try { + $stmt = $conn->query("CREATE FUNCTION [$funcName] ($funcArgs) RETURNS $retType AS BEGIN $funcCode END"); + } catch (PDOException $e) { + echo "Failed to create test function\n"; + var_dump($e); } - sqlsrv_free_stmt($stmt); + unset($stmt); } function DropFunc($conn, $funcName) { - $stmt = sqlsrv_query($conn, "DROP FUNCTION [$funcName]"); - if ($stmt === false) { - } else { - sqlsrv_free_stmt($stmt); + try { + $conn->query("DROP FUNCTION [$funcName]"); + } catch (PDOException $e) { + ; // do nothing } } diff --git a/test/functional/pdo_sqlsrv/pdo_1079_sql_variant_buffered_queries.phpt b/test/functional/pdo_sqlsrv/pdo_1079_sql_variant_buffered_queries.phpt new file mode 100644 index 000000000..1ba60606b --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1079_sql_variant_buffered_queries.phpt @@ -0,0 +1,59 @@ +--TEST-- +GitHub issue 1079 - fetching sql_variant types using client buffers +--DESCRIPTION-- +This test verifies that fetching sql_variant types using client buffers is supported. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- += @OP2 THEN @OP1 ELSE @OP2 END RETURN @Result END"; + + $conn->exec($tsql); + + $tsql = "SELECT [dbo].[$funcName](5, 6) AS RESULT"; + $stmt = $conn->prepare($tsql, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + $metadata = $stmt->getColumnMeta(0); + var_dump($metadata); + + dropFunc($conn, $funcName); + + unset($stmt); + unset($conn); +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} + +?> +--EXPECT-- +array(8) { + ["flags"]=> + int(0) + ["sqlsrv:decl_type"]=> + string(11) "sql_variant" + ["native_type"]=> + string(6) "string" + ["table"]=> + string(0) "" + ["pdo_type"]=> + int(2) + ["name"]=> + string(6) "RESULT" + ["len"]=> + int(10) + ["precision"]=> + int(0) +} \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_variants_diff_styles.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_variants_diff_styles.phpt index e99aae85f..954efbdc3 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_variants_diff_styles.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_variants_diff_styles.phpt @@ -170,11 +170,15 @@ function doValuesMatched($value1, $value2, $row, $col) } } -function fetchColumns($conn, $tableName, $numRows, $numCols) +function fetchColumns($conn, $tableName, $numRows, $numCols, $buffered = false) { try { // insert column data from a row of the original table - $stmtOriginal = $conn->prepare("SELECT * FROM $tableName WHERE c1_int = :row"); + if ($buffered) { + $stmtOriginal = $conn->prepare("SELECT * FROM $tableName WHERE c1_int = :row", array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + } else { + $stmtOriginal = $conn->prepare("SELECT * FROM $tableName WHERE c1_int = :row"); + } for ($i = 1; $i <= $numRows; $i++) { $c1_int = $i; @@ -263,6 +267,7 @@ try { $numCols = fetchBoundMixed($conn, $tableName, $numRows); fetchColumns($conn, $tableName, $numRows, $numCols); + fetchColumns($conn, $tableName, $numRows, $numCols, true); dropTable($conn, $tableName); unset($conn); @@ -278,3 +283,7 @@ Insert all columns from row 1 into one column of type sql_variant string(11) "sql_variant" Insert all columns from row 2 into one column of type sql_variant string(11) "sql_variant" +Insert all columns from row 1 into one column of type sql_variant +string(11) "sql_variant" +Insert all columns from row 2 into one column of type sql_variant +string(11) "sql_variant" diff --git a/test/functional/pdo_sqlsrv/pdo_simple_update_variants.phpt b/test/functional/pdo_sqlsrv/pdo_simple_update_variants.phpt index 866c5e2c2..86c5e3a7e 100644 --- a/test/functional/pdo_sqlsrv/pdo_simple_update_variants.phpt +++ b/test/functional/pdo_sqlsrv/pdo_simple_update_variants.phpt @@ -76,10 +76,15 @@ function updateFood($conn, $tableName, $id, $food, $category) } } -function fetchRows($conn, $tableName) +function fetchRows($conn, $tableName, $buffered = false) { $query = "SELECT * FROM $tableName ORDER BY id"; - $stmt = $conn->query($query); + if ($buffered) { + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + } else { + $stmt = $conn->query($query); + } $stmt->setFetchMode(PDO::FETCH_CLASS, 'Food'); while ($food = $stmt->fetch()) { @@ -108,7 +113,7 @@ try { updateID($conn, $tableName, 4, 'Milk', 'Diary Products'); - fetchRows($conn, $tableName); + fetchRows($conn, $tableName, true); updateFood($conn, $tableName, 4, 'Cheese', 'Diary Products'); @@ -118,7 +123,7 @@ try { insertData($conn, $tableName, 6, 'Salmon', 'Fish'); insertData($conn, $tableName, 2, 'Broccoli', 'Vegetables'); - fetchRows($conn, $tableName); + fetchRows($conn, $tableName, true); dropTable($conn, $tableName); unset($conn); diff --git a/test/functional/sqlsrv/sqlsrv_param_input_variants.phpt b/test/functional/sqlsrv/sqlsrv_param_input_variants.phpt index ee5dbcde1..8adc251cd 100644 --- a/test/functional/sqlsrv/sqlsrv_param_input_variants.phpt +++ b/test/functional/sqlsrv/sqlsrv_param_input_variants.phpt @@ -65,7 +65,8 @@ function insertData($conn, $tableName, $index) function fetchData($conn, $tableName, $numRows) { $select = "SELECT * FROM $tableName ORDER BY c1_int"; - $stmt = sqlsrv_query($conn, $select); + + $stmt = sqlsrv_query($conn, $select, array(), array("Scrollable"=>"buffered")); $stmt2 = sqlsrv_query($conn, $select); $metadata = sqlsrv_field_metadata($stmt); diff --git a/test/functional/sqlsrv/sqlsrv_simple_fetch_variants.phpt b/test/functional/sqlsrv/sqlsrv_simple_fetch_variants.phpt index 07a598205..0022509b8 100644 --- a/test/functional/sqlsrv/sqlsrv_simple_fetch_variants.phpt +++ b/test/functional/sqlsrv/sqlsrv_simple_fetch_variants.phpt @@ -39,7 +39,7 @@ function Fetch($conn, $tableName, $numRows) { $select = "SELECT * FROM $tableName ORDER BY c1_int"; $stmt = sqlsrv_query($conn, $select); - $stmt2 = sqlsrv_query($conn, $select); + $stmt2 = sqlsrv_query($conn, $select, array(), array("Scrollable"=>"buffered")); $stmt3 = sqlsrv_query($conn, $select); $metadata = sqlsrv_field_metadata($stmt); diff --git a/test/functional/sqlsrv/sqlsrv_simple_update_variants.phpt b/test/functional/sqlsrv/sqlsrv_simple_update_variants.phpt index 70fa79557..b4b3b66b2 100644 --- a/test/functional/sqlsrv/sqlsrv_simple_update_variants.phpt +++ b/test/functional/sqlsrv/sqlsrv_simple_update_variants.phpt @@ -89,10 +89,15 @@ function updateCountry($conn, $tableName, $id, $country, $continent) } } -function fetch($conn, $tableName) +function fetch($conn, $tableName, $buffered = false) { $select = "SELECT * FROM $tableName ORDER BY id"; - $stmt = sqlsrv_query($conn, $select); + + if ($buffered) { + $stmt = sqlsrv_query($conn, $select, array(), array("Scrollable"=>"buffered")); + } else { + $stmt = sqlsrv_query($conn, $select); + } while ($country = sqlsrv_fetch_object($stmt, "Country")) { echo "\nID: " . $country->id . " "; @@ -125,13 +130,13 @@ try { updateID($conn, $tableName, 4, 'Canada', 'North America'); // Read data - fetch($conn, $tableName); + fetch($conn, $tableName, true); // Update country updateCountry($conn, $tableName, 4, 'Mexico', 'North America'); // Read data - fetch($conn, $tableName); + fetch($conn, $tableName, true); // Add two more countries addCountry($conn, $tableName, 6, 'Brazil', 'South America'); diff --git a/test/functional/sqlsrv/srv_1079_sql_variant_buffered_queries.phpt b/test/functional/sqlsrv/srv_1079_sql_variant_buffered_queries.phpt new file mode 100644 index 000000000..51a98553d --- /dev/null +++ b/test/functional/sqlsrv/srv_1079_sql_variant_buffered_queries.phpt @@ -0,0 +1,62 @@ +--TEST-- +GitHub issue 1079 - fetching sql_variant types using client buffers +--DESCRIPTION-- +This test verifies that fetching sql_variant types using client buffers is supported. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- += @OP2 THEN @OP1 ELSE @OP2 END RETURN @Result END"; + +$stmt = sqlsrv_query($conn, $tsql); +if (!$stmt) { + fatalError('Could not create function\n'); +} + +$tsql = "SELECT [dbo].[$funcName](5, 6) AS RESULT"; +$stmt = sqlsrv_prepare($conn, $tsql, array(), array("Scrollable" => SQLSRV_CURSOR_CLIENT_BUFFERED, "ClientBufferMaxKBSize" => 1000)); + +if (!$stmt) { + fatalError('Could not prepare query\n'); +} + +$result = sqlsrv_execute($stmt); +if (!$result) { + fatalError('Executing the query failed\n'); +} + +foreach (sqlsrv_field_metadata($stmt) as $fieldMetadata) { + var_dump($fieldMetadata); +} + +dropFunc($conn, $funcName); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +array(6) { + ["Name"]=> + string(6) "RESULT" + ["Type"]=> + int(-150) + ["Size"]=> + int(10) + ["Precision"]=> + NULL + ["Scale"]=> + NULL + ["Nullable"]=> + int(1) +} \ No newline at end of file From ba05b247bf18dcaed88baf9f8bf455d97eb5f583 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 23 Jan 2020 11:55:33 -0800 Subject: [PATCH 190/249] Updated versions and year (#1082) --- LICENSE | 2 +- source/pdo_sqlsrv/config.m4 | 2 +- source/pdo_sqlsrv/config.w32 | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 18 +++++++++--------- source/pdo_sqlsrv/pdo_init.cpp | 2 +- source/pdo_sqlsrv/pdo_parser.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 2 +- source/pdo_sqlsrv/template.rc | 2 +- source/shared/FormattedPrint.cpp | 2 +- source/shared/FormattedPrint.h | 2 +- source/shared/StringFunctions.cpp | 2 +- source/shared/StringFunctions.h | 2 +- source/shared/core_conn.cpp | 2 +- source/shared/core_init.cpp | 2 +- source/shared/core_results.cpp | 2 +- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 2 +- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 2 +- source/shared/globalization.h | 2 +- source/shared/interlockedatomic.h | 2 +- source/shared/interlockedatomic_gcc.h | 2 +- source/shared/interlockedslist.h | 2 +- source/shared/localization.hpp | 2 +- source/shared/localizationimpl.cpp | 2 +- source/shared/msodbcsql.h | 2 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 8 ++++---- source/shared/xplat.h | 2 +- source/shared/xplat_intsafe.h | 2 +- source/shared/xplat_winerror.h | 2 +- source/shared/xplat_winnls.h | 2 +- source/sqlsrv/config.m4 | 2 +- source/sqlsrv/config.w32 | 2 +- source/sqlsrv/conn.cpp | 2 +- source/sqlsrv/init.cpp | 2 +- source/sqlsrv/php_sqlsrv.h | 2 +- source/sqlsrv/php_sqlsrv_int.h | 2 +- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 2 +- 45 files changed, 56 insertions(+), 56 deletions(-) diff --git a/LICENSE b/LICENSE index 6fa366d5d..a58c37dba 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright(c) 2019 Microsoft Corporation +Copyright(c) 2020 Microsoft Corporation All rights reserved. MIT License diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index 75961ad78..92bfc4145 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.7 for PHP for SQL Server +dnl Microsoft Drivers 5.8 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index 1d37593ba..a82fbb713 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index eedd07b11..d950ac3bf 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -1105,7 +1105,7 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout } break; -#if PHP_VERSION_ID >= 70200 +#if PHP_VERSION_ID >= 70200 case PDO_ATTR_DEFAULT_STR_PARAM: { if (Z_TYPE_P(val) != IS_LONG) { @@ -1297,7 +1297,7 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout break; } -#if PHP_VERSION_ID >= 70200 +#if PHP_VERSION_ID >= 70200 case PDO_ATTR_DEFAULT_STR_PARAM: { ZVAL_LONG(return_value, (driver_dbh->use_national_characters == 0) ? PDO_PARAM_STR_CHAR : PDO_PARAM_STR_NATL); @@ -1523,12 +1523,12 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const } use_national_char_set = (driver_dbh->use_national_characters == 1 || encoding == SQLSRV_ENCODING_UTF8); -#if PHP_VERSION_ID >= 70200 - if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) { - use_national_char_set = true; - } - if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { - use_national_char_set = false; +#if PHP_VERSION_ID >= 70200 + if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) { + use_national_char_set = true; + } + if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { + use_national_char_set = false; } #endif diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index de9f33eec..4f1a88458 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 6a96ace21..48b591be9 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 448eee04a..65f5a7daa 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index cff7add5c..560483302 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index f3d13b857..ab1aaf262 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 2ddd3961a..2934e11fc 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -6,7 +6,7 @@ // // Contents: Internal declarations for the extension // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 2db6a2e94..350a2f825 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index 185489d7e..dd076078c 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -6,7 +6,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index 32f65ebbf..79b824b41 100644 --- a/source/shared/FormattedPrint.h +++ b/source/shared/FormattedPrint.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp index ac0a82398..7426644f6 100644 --- a/source/shared/StringFunctions.cpp +++ b/source/shared/StringFunctions.cpp @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.h b/source/shared/StringFunctions.h index 0eb011c3e..7593a534f 100644 --- a/source/shared/StringFunctions.h +++ b/source/shared/StringFunctions.h @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 10545e42c..c328a9d58 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index d2c386c9b..1d4830780 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 5141ecc98..0b9c8fd94 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 58ccccf93..ae27400e8 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index d2502c81c..32b10ad58 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 9ca8e8c06..e0177652c 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 9f2e0eeaa..2853a22a8 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/globalization.h b/source/shared/globalization.h index f7e1afd37..81091fc5f 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index 12456143d..c2ae4d26a 100644 --- a/source/shared/interlockedatomic.h +++ b/source/shared/interlockedatomic.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h index 171c1ad2d..9eff99f5b 100644 --- a/source/shared/interlockedatomic_gcc.h +++ b/source/shared/interlockedatomic_gcc.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h index 4bc04c1f7..8def1dc2d 100644 --- a/source/shared/interlockedslist.h +++ b/source/shared/interlockedslist.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, singly // linked list. // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index cb6249511..fae23e2fa 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 0e7338268..6a69aaa84 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -5,7 +5,7 @@ // Must be included in one c/cpp file per binary // A build error will occur if this inclusion policy is not followed // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index b0496f5d8..bcca30870 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -20,7 +20,7 @@ // pecuniary loss) arising out of the use of or inability to use // this SDK, even if Microsoft has been advised of the possibility // of such damages. -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index 7d4efb172..7b191eb76 100644 --- a/source/shared/sal_def.h +++ b/source/shared/sal_def.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index 82e33eba4..b4fb63a6a 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -1,7 +1,7 @@ //--------------------------------------------------------------------------------------------------------------------------------- // File: typedefs_for_linux.h // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/version.h b/source/shared/version.h index c875410da..4369a3c8d 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -4,7 +4,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -26,12 +26,12 @@ // Increase Minor with backward compatible new functionalities and API changes. // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 -#define SQLVERSION_MINOR 7 -#define SQLVERSION_PATCH 1 +#define SQLVERSION_MINOR 8 +#define SQLVERSION_PATCH 0 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 -#define PREVIEW 1 +#define PREVIEW 0 #define SEMVER_PRERELEASE // Semantic versioning build metadata, build meta data is not counted in precedence order. diff --git a/source/shared/xplat.h b/source/shared/xplat.h index a32cdda0e..dbf862b13 100644 --- a/source/shared/xplat.h +++ b/source/shared/xplat.h @@ -3,7 +3,7 @@ // // Contents: include for definition of Windows types for non-Windows platforms // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h index 90d2f1509..e64e4c518 100644 --- a/source/shared/xplat_intsafe.h +++ b/source/shared/xplat_intsafe.h @@ -4,7 +4,7 @@ // Contents: This module defines helper functions to prevent // integer overflow bugs. // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h index 0ebd1f2a5..8ae8ee402 100644 --- a/source/shared/xplat_winerror.h +++ b/source/shared/xplat_winerror.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h index 22bfedc90..f6d75a614 100644 --- a/source/shared/xplat_winnls.h +++ b/source/shared/xplat_winnls.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index a8a4e89d5..aa460ef94 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.7 for PHP for SQL Server +dnl Microsoft Drivers 5.8 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 33cac9bd2..57cae3e7f 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 5500da231..c4ed63626 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index f12e23bb8..bc0692057 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 01a19b39b..45fb8baec 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index 1866e37b9..9ca59d9e0 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index ff72a6fa9..05d46b6c9 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index e95041ae6..7aa9c0cf7 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 9e545b6aa..671f6bf46 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.7 for PHP for SQL Server +// Microsoft Drivers 5.8 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License From 6e312d1addd298401af51fd1d1405c693071db79 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 29 Jan 2020 10:46:09 -0800 Subject: [PATCH 191/249] Change log for version 5.8.0 (#1083) --- CHANGELOG.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 6 +++--- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c18663be0..12d475107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,61 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.8.0 - 2020-01-31 +Updated PECL release packages. Here is the list of updates: + +### Added +- Support for PHP 7.4 +- Support for [Microsoft ODBC Driver 17.5]( +https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver15) on all platforms +- Support for Debian 10 and Red Hat 8 - require MS ODBC Driver 17.4+ +- Support for macOS Catalina, Alpine Linux 3.11 and Ubuntu 19.10 - require ODBC Driver 17.5+ +- Feature Request [#929](https://github.com/microsoft/msphpsql/issues/929) - new [Language option](https://github.com/microsoft/msphpsql/wiki/Features#language) - Pull Request [#930](https://github.com/microsoft/msphpsql/pull/930) +- [Data Classification Sensitivity Metadata Retrieval](https://github.com/microsoft/msphpsql/wiki/Features#data-classification-sensitivity-metadata) - requires ODBC Driver 17.4.2+ and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019) +- Feature Request [#1018](https://github.com/microsoft/msphpsql/issues/1018) - support for [PHP extended string types](https://github.com/microsoft/msphpsql/wiki/Features#natlTypes) - Pull Request [#1043](https://github.com/microsoft/msphpsql/pull/1043) +- [Always Encrypted with secure enclaves](https://github.com/microsoft/msphpsql/wiki/Features#alwaysencryptedV2) - requires ODBC Driver 17.4+ and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019) +- Feature Request [#1063](https://github.com/microsoft/msphpsql/issues/1063) - add configurable options for locale settings in Linux and macOS - Pull Request [#1069](https://github.com/microsoft/msphpsql/pull/1069) + +### Removed +- Dropped support for [PHP 7.1](https://www.php.net/supported-versions.php) +- Dropped support for SQL Server 2008 R2, macOS Sierra, Ubuntu 18.10 and Ubuntu 19.04. + +### Fixed +- Issue [#570](https://github.com/microsoft/msphpsql/issues/570) - Fixed fetching varbinary data using client buffer with sqlsrv +- Pull Request [#972](https://github.com/microsoft/msphpsql/pull/972) - Removed redundant calls to retrieve the number of columns or rows in the current query result set +- Pull Request [#978](https://github.com/microsoft/msphpsql/pull/978) - PDO_SQLSRV implementation of PDO::getColumnMeta now references cached metadata rather than making an ODBC call every time +- Pull Request [#979](https://github.com/microsoft/msphpsql/pull/979) - Added support for Data Classification Sensitivity metadata retrieval +- Pull Request [#985](https://github.com/microsoft/msphpsql/pull/985) - Fixed memory issues with Data Classification data structures +- Issue [#432](https://github.com/microsoft/msphpsql/issues/432) - Having any invalid UTF-8 name in the connection string will no longer invoke misleading error messages +- Issue [#909](https://github.com/microsoft/msphpsql/issues/909) - Fixed potential exception with locale issues in macOS +- Pull Request [#992](https://github.com/microsoft/msphpsql/pull/992) - Produced the correct error when requesting Data Classification metadata with ODBC drivers prior to 17 +- Pull Request [#1001](https://github.com/microsoft/msphpsql/pull/1001) - Fixed compilation issue with PHP 7.4 alpha +- Pull Request [#1004](https://github.com/microsoft/msphpsql/pull/1004) - Fixed another compilation issue with PHP 7.4 alpha +- Pull Request [#1008](https://github.com/microsoft/msphpsql/pull/1008) - Improved data caching when fetching datetime objects +- Pull Request [#1011](https://github.com/microsoft/msphpsql/pull/1011) - Fixed a potential buffer overflow when parsing for escaped braces in the connection string +- Pull Request [#1015](https://github.com/microsoft/msphpsql/pull/1015) - Fixed compilation issues and addressed various memory leaks detected by PHP 7.4 beta 1 +- Issue [#1027](https://github.com/microsoft/msphpsql/issues/1027) - Fixed how drivers handle query timeout settings +- Pull Request [#1049](https://github.com/microsoft/msphpsql/pull/1049) - Performance improvement for fetching from tables with many columns - cached the derived php types with column metadata to streamline data retrieval +- Pull Request [#1068](https://github.com/microsoft/msphpsql/pull/1068) - Some cosmetic changes to source code as per suggestions from a static analysis tool +- Issue [#1079](https://github.com/microsoft/msphpsql/issues/1079) - Support sql_variant types when using client buffers + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- In Alpine Linux, the Client-Side Cursors feature may cause an access violation if both sqlsrv and pdo_sqlsrv are enabled. Either enable only sqlsrv or pdo_sqlsrv, or build PHP from source by compiling the drivers statically. +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) + ## 5.7.1-preview - 2019-12-03 Updated PECL release packages. Here is the list of updates: diff --git a/README.md b/README.md index c89e8a249..41feaf15c 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ **Welcome to the Microsoft Drivers for PHP for Microsoft SQL Server** -The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2008 R2 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server][odbcdoc] to handle the low-level communication with SQL Server. +The Microsoft Drivers for PHP for Microsoft SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PHP Data Objects (PDO) for accessing data in all editions of SQL Server 2012 and later (including Azure SQL DB). These drivers rely on the [Microsoft ODBC Driver for SQL Server][odbcdoc] to handle the low-level communication with SQL Server. -This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.1+ with improvements on both drivers and some limitations. Upcoming [releases][releases] will contain additional functionalities, bug fixes, and more. +This release contains the SQLSRV and PDO_SQLSRV drivers for PHP 7.2+ with improvements on both drivers and some limitations. Upcoming [releases][releases] will contain additional functionalities, bug fixes, and more. ## Take our survey @@ -51,7 +51,7 @@ On the client machine: - [Microsoft ODBC Driver 17, Microsoft ODBC Driver 13, or Microsoft ODBC Driver 11][odbcdoc] - If using a Web server such as Internet Information Services (IIS) or Apache, it must be configured to run PHP -On the server side, Microsoft SQL Server 2008 R2 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux. +On the server side, Microsoft SQL Server 2012 and above on Windows are supported, as are Microsoft SQL Server 2016 and above on Linux. ## Building and Installing the Drivers on Windows From e7b5a8836477b91bd129bda53b1362efe2fac21c Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 30 Jan 2020 22:50:30 -0800 Subject: [PATCH 192/249] 5.8.0 rtw docs (#1086) * updated install instructions and changelog * removed md extensions * Addressed review comments * added path * Fixed link --- CHANGELOG.md | 3 +- Linux-mac-install.md | 231 ++++++++++++++++++++++++++++++++----------- 2 files changed, 177 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12d475107..9eab1ff0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Updated PECL release packages. Here is the list of updates: - Support for [Microsoft ODBC Driver 17.5]( https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver15) on all platforms - Support for Debian 10 and Red Hat 8 - require MS ODBC Driver 17.4+ -- Support for macOS Catalina, Alpine Linux 3.11 and Ubuntu 19.10 - require ODBC Driver 17.5+ +- Support for macOS Catalina, Alpine Linux 3.11 (experimental), and Ubuntu 19.10 - require ODBC Driver 17.5+ - Feature Request [#929](https://github.com/microsoft/msphpsql/issues/929) - new [Language option](https://github.com/microsoft/msphpsql/wiki/Features#language) - Pull Request [#930](https://github.com/microsoft/msphpsql/pull/930) - [Data Classification Sensitivity Metadata Retrieval](https://github.com/microsoft/msphpsql/wiki/Features#data-classification-sensitivity-metadata) - requires ODBC Driver 17.4.2+ and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019) - Feature Request [#1018](https://github.com/microsoft/msphpsql/issues/1018) - support for [PHP extended string types](https://github.com/microsoft/msphpsql/wiki/Features#natlTypes) - Pull Request [#1043](https://github.com/microsoft/msphpsql/pull/1043) @@ -50,6 +50,7 @@ https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server? - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported - Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) +- Alpine Linux support is currently experimental. More robust support will be added in future releases ### Known Issues - In Alpine Linux, the Client-Side Cursors feature may cause an access violation if both sqlsrv and pdo_sqlsrv are enabled. Either enable only sqlsrv or pdo_sqlsrv, or build PHP from source by compiling the drivers statically. diff --git a/Linux-mac-install.md b/Linux-mac-install.md index fb3175157..b3decb936 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,23 +1,27 @@ # Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server -The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, Apache, and the Microsoft Drivers for PHP for SQL Server on Ubuntu, RedHat, Debian, Suse, and macOS. These instructions advise installing the drivers using PECL, but you may also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver##loading-the-driver-at-php-startup). +The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 19.10, RedHat 7 and 8, Debian 8, 9, and 10, Suse 12 and 15, Alpine 3.11 (experimental), and macOS 10.13, 10.14, and 10.15. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup). -These instructions install PHP 7.4 by default. Note that some supported Linux distros default to PHP 7.1 or earlier, which the PHP drivers for SQL Server no longer support. When installing PHP 7.2 or above, please read the notes at the beginning of each section below. +These instructions install PHP 7.4 by default. Note that some supported Linux distros default to PHP 7.1 or earlier, which is not supported for the latest version of the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.2 or 7.3 instead. + +Also included are instructions for installing the PHP FastCGI Process Manager, PHP-FPM, on Ubuntu. This is needed if using the nginx web server instead of Apache. ## Contents of this page: -- [Installing the drivers on Ubuntu 16.04, 18.04, and 19.04](#installing-the-drivers-on-ubuntu-1604-1804-and-1904) +- [Installing the drivers on Ubuntu 16.04, 18.04, and 19.10](#installing-the-drivers-on-ubuntu-1604-1804-and-1910) +- [Installing the drivers with PHP-FPM on Ubuntu](#installing-the-drivers-with-php-fpm-on-ubuntu) - [Installing the drivers on Red Hat 7 and 8](#installing-the-drivers-on-red-hat-7-and-8) -- [Installing the drivers on Debian 8, 9 and 10](#installing-the-drivers-on-debian-8-9-and-10) +- [Installing the drivers on Debian 8, 9, and 10](#installing-the-drivers-on-debian-8-9-and-10) - [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15) -- [Installing the drivers on macOS Sierra, High Sierra, Mojave, and Catalina](#installing-the-drivers-on-macos-sierra-high-sierra-mojave-and-catalina) +- [Installing the drivers on Alpine 3.11](#installing-the-drivers-on-alpine-311) +- [Installing the drivers on macOS High Sierra, Mojave, and Catalina](#installing-the-drivers-on-macos-high-sierra-mojave-and-catalina) -## Installing the drivers on Ubuntu 16.04, 18.04, and 19.04 +## Installing the drivers on Ubuntu 16.04, 18.04, and 19.10 > [!NOTE] -> To install PHP 7.3 or 7.2, replace 7.4 with 7.3 or 7.2 in the following commands. +> To install PHP 7.2 or 7.3, replace 7.4 with 7.2 or 7.3 in the following commands. ### Step 1. Install PHP -``` +```bash sudo su add-apt-repository ppa:ondrej/php -y apt-get update @@ -28,15 +32,16 @@ Install the ODBC driver for Ubuntu by following the instructions on the [Linux a ### Step 3. Install the PHP drivers for Microsoft SQL Server ``` -sudo pecl install sqlsrv-5.7.1preview -sudo pecl install pdo_sqlsrv-5.7.1preview +sudo pecl install sqlsrv +sudo pecl install pdo_sqlsrv sudo su printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.4/mods-available/sqlsrv.ini printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.4/mods-available/pdo_sqlsrv.ini exit sudo phpenmod -v 7.4 sqlsrv pdo_sqlsrv ``` -If there is only one PHP version in the system then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. + +If there is only one PHP version in the system, then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. ### Step 4. Install Apache and configure driver loading ``` @@ -53,53 +58,120 @@ sudo service apache2 restart ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on Red Hat 7 and 8 +## Installing the drivers with PHP-FPM on Ubuntu > [!NOTE] -> To install PHP 7.3 or 7.2, replace remi-php74 with remi-php73 or remi-php72 respectively in the following commands. +> To install PHP 7.2 or 7.3, replace 7.4 with 7.2 or 7.3 in the following commands. + +### Step 1. Install PHP +```bash +sudo su +add-apt-repository ppa:ondrej/php -y +apt-get update +apt-get install php7.4 php7.4-dev php7.4-xml php7.4-fpm -y --allow-unauthenticated +``` +Verify the status of the PHP-FPM service by running +``` +systemctl status php7.4-fpm +``` +### Step 2. Install prerequisites +Install the ODBC driver for Ubuntu by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). + +### Step 3. Install the PHP drivers for Microsoft SQL Server +``` +sudo pecl config-set php_ini /etc/php/7.3/fpm/php.ini +sudo pecl install sqlsrv +sudo pecl install pdo_sqlsrv +sudo su +printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.4/mods-available/sqlsrv.ini +printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.4/mods-available/pdo_sqlsrv.ini +exit +sudo phpenmod -v 7.4 sqlsrv pdo_sqlsrv +``` +If there is only one PHP version in the system, then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. + +Verify that `sqlsrv.ini` and `pdo_sqlsrv.ini` are located in `/etc/php/7.4/fpm/conf.d/`: +``` +ls /etc/php/7.4/fpm/conf.d/*sqlsrv.ini +``` +Restart the PHP-FPM service: +``` +sudo systemctl restart php7.4-fpm +``` + +### Step 4. Install and configure nginx +``` +sudo apt-get update +sudo apt-get install nginx +sudo systemctl status nginx +``` +To configure nginx, you must edit the `/etc/nginx/sites-available/default` file. Add `index.php` to the list below the section that says `# Add index.php to the list if you are using PHP`: +``` +# Add index.php to the list if you are using PHP +index index.html index.htm index.nginx-debian.html index.php; +``` +Next, modify the section following `# pass PHP scripts to FastCGI server` as follows: +``` +# pass PHP scripts to FastCGI server +# +location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/run/php/php7.4-fpm.sock; +} +``` +### Step 5. Restart nginx and test the sample script +``` +sudo systemctl restart nginx.service +``` +To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. + +## Installing the drivers on Red Hat 7 and 8 ### Step 1. Install PHP +To install PHP on Red Hat 7, run the following: +> [!NOTE] +> To install PHP 7.2 or 7.3, replace remi-php74 with remi-php72 or remi-php73 respectively in the following commands. ``` sudo su -wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -wget https://rpms.remirepo.net/enterprise/remi-release-7.rpm -rpm -Uvh remi-release-7.rpm epel-release-latest-7.noarch.rpm +yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm +yum install https://rpms.remirepo.net/enterprise/remi-release-7.rpm subscription-manager repos --enable=rhel-7-server-optional-rpms yum install yum-utils yum-config-manager --enable remi-php74 yum update yum install php php-pdo php-xml php-pear php-devel re2c gcc-c++ gcc ``` -### Step 2. Install prerequisites -Install the ODBC driver for Red Hat 7 and 8 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). -In some versions of Red Hat 7, compiling the PHP drivers with PECL and PHP 7.2 requires a more recent GCC than the default: +To install PHP on Red Hat 8, run the following: +> [!NOTE] +> To install PHP 7.2 or 7.3, replace remi-7.4 with remi-7.2 or remi-7.3 respectively in the following commands. ``` -sudo yum-config-manager --enable rhel-server-rhscl-7-rpms -sudo yum install devtoolset-7 -scl enable devtoolset-7 bash +sudo su +dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm +dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm +dnf install yum-utils +dnf module reset php +dnf module install php:remi-7.4 +subscription-manager repos --enable codeready-builder-for-rhel-8-x86_64-rpms +dnf update +dnf install php-pdo php-pear php-devel ``` + +### Step 2. Install prerequisites +Install the ODBC driver for Red Hat 7 or 8 by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). + ### Step 3. Install the PHP drivers for Microsoft SQL Server ``` -sudo pecl install sqlsrv-5.7.1preview -sudo pecl install pdo_sqlsrv-5.7.1preview +sudo pecl install sqlsrv +sudo pecl install pdo_sqlsrv sudo su echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/30-pdo_sqlsrv.ini echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/20-sqlsrv.ini exit ``` -An issue in PECL may prevent correct installation of the latest version of the drivers even if you have upgraded GCC. To install, download the packages and compile manually (similar steps for pdo_sqlsrv): -``` -pecl download sqlsrv-5.7.1preview -tar xvzf sqlsrv-5.7.1preview.tgz -cd sqlsrv-5.7.1preview/ -phpize -./configure --with-php-config=/usr/bin/php-config -make -sudo make install -``` -You can alternatively download the prebuilt binaries from the [Github project page](https://github.com/Microsoft/msphpsql/releases), or install from the Remi repo: + +You can alternatively install from the Remi repo: ``` sudo yum install php-sqlsrv ``` @@ -117,10 +189,10 @@ sudo apachectl restart ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on Debian 8, 9 and 10 +## Installing the drivers on Debian 8, 9, and 10 > [!NOTE] -> To install PHP 7.3 or 7.2, replace 7.4 in the following commands with 7.3 or 7.2. +> To install PHP 7.2 or 7.3, replace 7.4 in the following commands with 7.2 or 7.3. ### Step 1. Install PHP ``` @@ -129,7 +201,7 @@ apt-get install curl apt-transport-https wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list apt-get update -apt-get install -y php7.4 php7.4-dev php7.4-xml +apt-get install -y php7.4 php7.4-dev php7.4-xml php7.4-intl ``` ### Step 2. Install prerequisites Install the ODBC driver for Debian by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). @@ -140,18 +212,20 @@ sudo su sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen locale-gen ``` +You may need to add `/usr/sbin` to your `$PATH`, as the `locale-gen` executable is located there. ### Step 3. Install the PHP drivers for Microsoft SQL Server ``` -sudo pecl install sqlsrv-5.7.1preview -sudo pecl install pdo_sqlsrv-5.7.1preview +sudo pecl install sqlsrv +sudo pecl install pdo_sqlsrv sudo su printf "; priority=20\nextension=sqlsrv.so\n" > /etc/php/7.4/mods-available/sqlsrv.ini printf "; priority=30\nextension=pdo_sqlsrv.so\n" > /etc/php/7.4/mods-available/pdo_sqlsrv.ini exit sudo phpenmod -v 7.4 sqlsrv pdo_sqlsrv ``` -If there is only one PHP version in the system then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. + +If there is only one PHP version in the system, then the last step can be simplified to `phpenmod sqlsrv pdo_sqlsrv`. As with `locale-gen`, `phpenmod` is located in `/usr/sbin` so you may need to add this directory to your `$PATH`. ### Step 4. Install Apache and configure driver loading ``` @@ -170,21 +244,21 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Suse 12 and 15 > [!NOTE] -> In the following instructions, replace with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15 or SLE_15_SP1. For Suse 12, use SLE_12_SP4 (or above if applicable). Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or to `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse. +> In the following instructions, replace `` with your version of Suse - if you are using Suse Enterprise Linux 15, it will be SLE_15 or SLE_15_SP1. For Suse 12, use SLE_12_SP4 (or above if applicable). Not all versions of PHP are available for all versions of Suse Linux - please refer to `http://download.opensuse.org/repositories/devel:/languages:/php` to see which versions of Suse have the default version PHP available, or to `http://download.opensuse.org/repositories/devel:/languages:/php:/` to see which other versions of PHP are available for which versions of Suse. > [!NOTE] > Packages for PHP 7.4 are not available for Suse 12. -> To install PHP 7.3, replace the repository URL below with the following URL: - `https://download.opensuse.org/repositories/devel:/languages:/php:/php73//devel:languages:php:php73.repo`. > To install PHP 7.2, replace the repository URL below with the following URL: `https://download.opensuse.org/repositories/devel:/languages:/php:/php72//devel:languages:php:php72.repo`. +> To install PHP 7.3, replace the repository URL below with the following URL: + `https://download.opensuse.org/repositories/devel:/languages:/php:/php73//devel:languages:php:php73.repo`. ### Step 1. Install PHP ``` sudo su zypper -n ar -f https://download.opensuse.org/repositories/devel:languages:php//devel:languages:php.repo zypper --gpg-auto-import-keys refresh -zypper -n install php7 php7-pear php7-devel php7-openssl +zypper -n install php7 php7-devel php7-openssl ``` ### Step 2. Install prerequisites Install the ODBC driver for Suse by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). @@ -194,8 +268,8 @@ Install the ODBC driver for Suse by following the instructions on the [Linux and > If you get an error message saying `Connection to 'pecl.php.net:443' failed: Unable to find the socket transport "ssl"`, edit the pecl script at /usr/bin/pecl and remove the `-n` switch in the last line. This switch prevents PECL from loading ini files when PHP is called, which prevents the OpenSSL extension from loading. ``` -sudo pecl install sqlsrv-5.7.1preview -sudo pecl install pdo_sqlsrv-5.7.1preview +sudo pecl install sqlsrv +sudo pecl install pdo_sqlsrv sudo su echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/pdo_sqlsrv.ini echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/sqlsrv.ini @@ -216,7 +290,52 @@ sudo systemctl restart apache2 ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on macOS Sierra, High Sierra, Mojave, and Catalina +## Installing the drivers on Alpine 3.11 + +> [!NOTE] +> Alpine support is experimental. + +> [!NOTE] +> The default version of PHP is 7.3. Alternate versions of PHP are not available from other repositories for Alpine 3.11. You can instead compile PHP from source. + +### Step 1. Install PHP +PHP packages for Alpine are found in the `edge/community` repository. Add the following line to `/etc/apt/repositories`, replacing `` with the URL of an Alpine repository mirror: +``` +http:///alpine/edge/community +``` +Then run: +``` +sudo su +apk update +apk add php7 php7-dev php7-pear php7-pdo php7-openssl autoconf make g++ +``` +### Step 2. Install prerequisites +Install the ODBC driver for Alpine by following the instructions on the [Linux and macOS installation page](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server). + +### Step 3. Install the PHP drivers for Microsoft SQL Server +``` +sudo pecl install sqlsrv +sudo pecl install pdo_sqlsrv +sudo su +echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/10_pdo_sqlsrv.ini +echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/00_sqlsrv.ini +``` +You may need to define a locale: +``` +export LC_ALL=C +``` +### Step 4. Install Apache and configure driver loading +``` +sudo apk add php7-apache2 apache2 +``` +### Step 5. Restart Apache and test the sample script +``` +sudo rc-service apache2 restart +``` +To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. + + +## Installing the drivers on macOS High Sierra, Mojave, and Catalina If you do not already have it, install brew as follows: ``` @@ -224,7 +343,7 @@ If you do not already have it, install brew as follows: ``` > [!NOTE] -> To install PHP 7.3 or 7.2, replace php@7.4 with php@7.3 or php@7.2 respectively in the following commands. +> To install PHP 7.2 or 7.3, replace php@7.4 with php@7.2 or php@7.3 respectively in the following commands. ### Step 1. Install PHP @@ -248,18 +367,18 @@ brew install autoconf automake libtool ### Step 3. Install the PHP drivers for Microsoft SQL Server ``` -sudo pecl install sqlsrv-5.7.1preview -sudo pecl install pdo_sqlsrv-5.7.1preview +sudo pecl install sqlsrv +sudo pecl install pdo_sqlsrv ``` ### Step 4. Install Apache and configure driver loading ``` brew install apache2 ``` -To find the Apache configuration file for your Apache installation, run +To find the Apache configuration file, `httpd.conf`, for your Apache installation, run ``` -apachectl -V | grep SERVER_CONFIG_FILE +/usr/local/bin/apachectl -V | grep SERVER_CONFIG_FILE ``` -and substitute the path for `httpd.conf` in the following commands: +The following commands append the required configuration to `httpd.conf`. Be sure to substitute the path returned by the preceding command in place of `/usr/local/etc/httpd/httpd.conf`: ``` echo "LoadModule php7_module /usr/local/opt/php@7.4/lib/httpd/modules/libphp7.so" >> /usr/local/etc/httpd/httpd.conf (echo ""; echo "SetHandler application/x-httpd-php"; echo "";) >> /usr/local/etc/httpd/httpd.conf @@ -272,7 +391,7 @@ To test your installation, see [Testing your installation](#testing-your-install ## Testing Your Installation -To test this sample script, create a file called testsql.php in your system's document root. This is `/var/www/html/` on Ubuntu, Debian, and Redhat, `/srv/www/htdocs` on SUSE, or `/usr/local/var/www` on macOS. Copy the following script to it, replacing the server, database, username, and password as appropriate. +To test this sample script, create a file called testsql.php in your system's document root. This is `/var/www/html/` on Ubuntu, Debian, and Redhat, `/srv/www/htdocs` on SUSE, `/var/www/localhost/htdocs` on Alpine, or `/usr/local/var/www` on macOS. Copy the following script to it, replacing the server, database, username, and password as appropriate. On Alpine 3.11, you may also need to specify the **CharacterSet** as 'UTF-8' in the `$connectionOptions` array. ``` ``` -Point your browser to https://localhost/testsql.php (https://localhost:8080/testsql.php on macOS). You should now be able to connect to your SQL Server/Azure SQL database. \ No newline at end of file +Point your browser to https://localhost/testsql.php (https://localhost:8080/testsql.php on macOS). You should now be able to connect to your SQL Server/Azure SQL database. From 71b9d40711773001b5bf485c9cf0745723d9323b Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Mon, 3 Feb 2020 14:39:28 -0800 Subject: [PATCH 193/249] Ae v2 extended tests (#1077) * Added extended AE v2 tests * Added binary types to error check * Updated test descriptions * Added the test matrix * Refactored tests * Added else check for keystore * Debugging connection failures * Debugging connection failures * Debugging connection failures * Addressed review comments * Fixed parse error * Fixed parse error * Fixed parse error * Addressed review comments --- test/extended/AE_v2_values.inc | 220 ++++ test/extended/MsSetup.inc | 58 + test/extended/pdo_AE_functions.inc | 911 ++++++++++++++++ .../pdo_aev2_plaintext_nonstring.phpt | 41 + test/extended/pdo_aev2_plaintext_string.phpt | 41 + ...do_aev2_reencrypt_encrypted_nonstring.phpt | 39 + .../pdo_aev2_reencrypt_encrypted_string.phpt | 39 + test/extended/skipif_not_hgs.inc | 36 + test/extended/sqlsrv_AE_functions.inc | 991 ++++++++++++++++++ .../sqlsrv_aev2_plaintext_nonstring.phpt | 41 + .../sqlsrv_aev2_plaintext_string.phpt | 41 + ...rv_aev2_reencrypt_encrypted_nonstring.phpt | 39 + ...qlsrv_aev2_reencrypt_encrypted_string.phpt | 39 + test/functional/setup/setup_dbs.py | 5 + 14 files changed, 2541 insertions(+) create mode 100644 test/extended/AE_v2_values.inc create mode 100644 test/extended/MsSetup.inc create mode 100644 test/extended/pdo_AE_functions.inc create mode 100644 test/extended/pdo_aev2_plaintext_nonstring.phpt create mode 100644 test/extended/pdo_aev2_plaintext_string.phpt create mode 100644 test/extended/pdo_aev2_reencrypt_encrypted_nonstring.phpt create mode 100644 test/extended/pdo_aev2_reencrypt_encrypted_string.phpt create mode 100644 test/extended/skipif_not_hgs.inc create mode 100644 test/extended/sqlsrv_AE_functions.inc create mode 100644 test/extended/sqlsrv_aev2_plaintext_nonstring.phpt create mode 100644 test/extended/sqlsrv_aev2_plaintext_string.phpt create mode 100644 test/extended/sqlsrv_aev2_reencrypt_encrypted_nonstring.phpt create mode 100644 test/extended/sqlsrv_aev2_reencrypt_encrypted_string.phpt diff --git a/test/extended/AE_v2_values.inc b/test/extended/AE_v2_values.inc new file mode 100644 index 000000000..d4f326385 --- /dev/null +++ b/test/extended/AE_v2_values.inc @@ -0,0 +1,220 @@ +$attestation, + 'enabled' =>'enabled', + 'disabled'=>'disabled', + 'invalid' =>$wrongProtocol, + 'wrongurl'=>$wrongAttestation, + ); + +$targetCeValues = array('correct' =>$attestation, + 'enabled' =>'enabled', + 'disabled'=>'disabled', + 'invalid' =>$wrongProtocol, + 'wrongurl'=>$wrongAttestation, + ); + +// Names of the encryption keys, depending on whether we are using Windows +// or AKV authentication (defined in MsSetup.inc). -enclave keys are enclave +// enabled, -noenclave keys are not enclave enabled. +// $targetKeys are the keys used for re-encrypting encrypted columns +if ($keystore == 'win') { + $keys = array("CEK-win-enclave", + "CEK-win-noenclave" + ); + $targetKeys = array("CEK-win-enclave", + "CEK-win-noenclave", + "CEK-win-enclave2", + "CEK-win-noenclave2" + ); +} elseif ($keystore == 'akv') { + $keys = array("CEK-akv-enclave", + "CEK-akv-noenclave" + ); + $targetKeys = array("CEK-akv-enclave", + "CEK-akv-noenclave", + "CEK-akv-enclave2", + "CEK-akv-noenclave2" + ); +} else { + die("No keystore specified! Aborting...\n"); +} + +// $targetTypes are the encryption types used for re-encrypting encrypted columns +$encryptionTypes = array("Deterministic", + "Randomized", + ); +$targetTypes = array("Deterministic", + "Randomized", + ); + + +// Length of the string-type columns. $slength is length as a string instead of integer +$length = 64; +$slength = '64'; + +// Testing the following data types, split into two arrays because if we try one array, +// at some point we get a CE405 error for no clear reason (might be a memory issue?). +// TODO: Follow up and see if we can use a single type array. +$dataTypes1 = array('integer', + 'bigint', + 'smallint', + 'tinyint', + 'bit', + 'float', + 'real', + 'numeric', + 'date', + 'time', + 'datetime', + 'datetime2', + 'datetimeoffset', + 'smalldatetime', + ); + +$dataTypes2 = array('char', + 'nchar', + 'varchar', + 'nvarchar', + 'varchar(max)', + 'nvarchar(max)', + 'binary', + 'varbinary', + 'varbinary(max)', + ); + +// Construct the array of column names. Two columns for each data type, +// one encrypted (suffixed _AE) and one not encrypted. +$colNames1 = array(); +$colNamesAE1 = array(); +$colNames2 = array(); +$colNamesAE2 = array(); + +foreach ($dataTypes1 as $type) { + $column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type); + $colNames1[$type] = "c_".$column; + $colNamesAE1[$type] = "c_".$column."_AE"; +} + +foreach ($dataTypes2 as $type) { + $column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type); + $colNames2[$type] = "c_".$column; + $colNamesAE2[$type] = "c_".$column."_AE"; +} + +// The test data below is a mixture of random data and edge cases +$testValues = array(); + +// integers +$testValues['integer'] = array(0,-1,1,2147483647,-2147483648,65536,-100000000,128,9); +$testValues['bigint'] = array(9223372036854775807,-40,0,1,2147483647,-2147483648,65536,-100000000000000); +$testValues['smallint'] = array(4,-4,-32768,-99,32767,-30000,-12,-1); +$testValues['tinyint'] = array(2,0,255,254,99,101,100,32); +$testValues['bit'] = array(1,1,0,0,0,0,1,0); + +// floating point +$testValues['float'] = array(3.14159,2.3e+12,-2.3e+12,2.23e-308,1,-1.79e+308,892.3098234234001,1.2); +$testValues['real'] = array(3.14159,2.3e+12,-2.3e+12,1.18e-38,1,-3.4e+38,892.3098234234001,1.2); +$testValues['numeric'] = array(-3.14159,1.003456789,45.6789,0.0001,987987.12345,-987987.12345,100000000000,-100000000000); + +// dates and times +$testValues['date'] = array('2010-01-31','0485-03-31','7825-07-23','9999-12-31','1956-02-27','2018-09-01','5401-11-02','1031-10-04'); +$testValues['time'] = array('12:40:40','08:14:54.3096','23:59:59.9999','01:00:34.0101','21:45:45.4545','00:23:45.6','17:48:00.0000','20:31:49.0001'); +$testValues['datetime2'] = array('9801-01-29 11:45:23.5092856','2384-12-31 12:40:12.5434323','1984-09-25 10:40:20.0909111','9999-12-31 23:59:59.999999', + '1259-04-29 23:59:59.9999999','1748-09-21 17:48:54.723','3125-05-31 05:00:32.4','0001-01-01 00:00:00'); +$testValues['datetimeoffset'] = array('9801-01-29 11:45:23.5092856-12:45','0001-01-01 00:00:00-02:30','1984-09-25 10:40:20.0909111+03:00','1748-09-21 17:48:54.723-09:21', + '4896-05-18 23:17:58.3-02:00','1657-08-04 18:14:27.4','2022-03-17 07:31:45.890342+09:30','1987-10-25 14:27:34.6320945-06:00'); +$testValues['datetime'] = array('9801-01-29 11:45:23.509','2384-12-31 12:40:12.543','1984-09-25 10:40:20.090','9999-12-31 23:59:59.997', + '2753-04-29 23:59:59.997','1948-09-21 17:48:54.723','3125-05-31 05:00:32.4','2001-01-01 00:00:00'); +$testValues['smalldatetime'] = array('1998-06-13 04:00:00','1985-03-31 12:40:00','2025-07-23 05:00:00','1999-12-31 00:00:00', + '1956-02-27 23:59:00','2018-09-01 14:35:00','2079-06-06 23:59:00','1931-10-04 19:52:00'); + +// strings, ascii and unicode +$testValues['char'] = array('wvyxz', 'tposw', '%c@kj>5', 'fd4$_w@q^@!coe$7', 'abcd', 'ev72#x*fv=u$', '4rfg3sw', 'voi%###i<@@'); +$testValues['nchar'] = array('⽧㘎ⷅ㪋','af㋮ᶄḉㇼ៌ӗඣ','áŠãµ®à´–ᅥ㪮ኸ⮊ߒᙵꇕâ¯áž‚ꉟफ़⻦ꈔꇼŞ','ê·ê¬•','ã¯ã©§ã–ƒâºµã´°Ú‡à½£á§†ê²´ê••ê²‘וֹꔄ若㌉ᒵȅ㗉ꗅᡉ','ʭḪã…ᾔᎀã겶ꅫážã´‰á´³ãœžÒ‚','','בּŬḛʼꃺꌖ㓵ꗛ᧽ഭწ社⾯㬄౧ຸฬã¯ê‹›ã—¾'); +$testValues['varchar'] = array('gop093','*#$@@)%*$@!%','cio4*3do*$','zzz$a#l',' ','v#x%n!k&r@p$f^','6$gt?je#~','0x3dK#?'); +$testValues['nvarchar'] = array('á¾áº´ã”®ã––à­±Üã—㴴៸ழ᷂ᵄ葉អ㺓節','Ó•áµàµ´ê”“ὀ⾼','Ὡ','璉Džꖭ갪ụ⺭','Ӿϰᇬ㭡㇑ᵈᔆ⽹hᙎ՞ꦣ㧼ለͭ','Ĕ㬚㔈♠既','êˆ Ý«','ê†àª«â·Œã½Ì—ૣܯ¢⽳㌭ゴᔓᅄѓⷉꘊⶮáᴗஈԋ≡ㄊହꂈ꓂ꑽრꖾŞ⽉걹ꩰോఫ㒧㒾㑷藍㵀ဲï¤ê§¥'); +$testValues['varchar(max)'] = array('Q0H4@4E%v+ 3*Trx#>*r86-&d$VgjZ','AjEvVABur(A&Q@eG,A$3u"xAzl','z#dFd4z', + '9Dvsg9B?7oktB@|OIqy<\K^\e|*7Y&yH31E-<.hQ:)g Jl`MQV>rdOhjG;B4wQ(WR[`l(pELt0FYu._T3+8tns!}Nqrc1%n@|N|ik C@ 03a/ +H9mBq', + 'SSs$Ie*{:D4;S]',' ','<\K^\e|*7Y&yH31E-<.hQ:','@Kg1Z6XTOgbt?CEJ|M^rkR_L4{1?l', '<=', '>=', '<>', '!<', '!>'); + +// Thresholds against which to use the comparison operators +$thresholds = array('integer' => 0, + 'bigint' => 0, + 'smallint' => 1000, + 'tinyint' => 100, + 'bit' => 0, + 'float' => 1.2, + 'real' => -1.2, + 'numeric' => 45.6789, + 'char' => 'rstuv', + 'nchar' => '㊃ᾞਲ㨴꧶êšê…', + 'varchar' => '6$gt?je#~', + 'nvarchar' => 'Ó•áµàµ´ê”“ὀ⾼', + 'varchar(max)' => 'hijkl', + 'nvarchar(max)' => 'xá•á›™á˜¡', + 'binary' => '44EE4A', + 'varbinary' => 'E43004FF', + 'varbinary(max)' => 'D3EA762C78FC', + 'date' => '2010-01-31', + 'time' => '21:45:45.4545', + 'datetime' => '3125-05-31 05:00:32.4', + 'datetime2' => '2384-12-31 12:40:12.5434323', + 'datetimeoffset' => '1984-09-25 10:40:20.0909111+03:00', + 'smalldatetime' => '1998-06-13 04:00:00', + ); + +// String patterns to test with LIKE +// For AE, LIKE only works with string types for now. Additional types +// are listed here because eventually the type conversions required for +// pattern matching non-string types should be supported. +$patterns = array('integer' => array('8', '48', '123'), + 'bigint' => array('000','7', '65536'), + 'smallint' => array('4','768','abc'), + 'tinyint' => array('9','0','25'), + 'bit' => array('0','1','100'), + 'float' => array('14159','.','E+','2.3','308'), + 'real' => array('30','.','e-','2.3','38'), + 'numeric' => array('0','0000','12345','abc','.'), + 'char' => array('w','@','x*fv=u$','e3'), + 'nchar' => array('afã‹®','ã¯ê‹›ã—¾','ꦣ㧼ለͭ','123'), + 'varchar' => array(' ','a','#','@@)'), + 'nvarchar' => array(' ','Ӿϰᇬ㭡','璉Džꖭ갪ụ⺭','ï¤ê§¥','ꈔꇼŞ'), + 'varchar(max)' => array('A','|*7Y&','4z','@!@','AjE'), + 'nvarchar(max)' => array('t','㧶áቴƯɋ','ᘷ㬡',' ','ê¾É”ᡧãš'), + 'binary' => array('44EE4A'), + 'varbinary' => array('E43004FF'), + 'varbinary(max)' => array('D3EA762C78FC'), + 'date' => array('20','%','9-','04'), + 'time' => array('4545','.0','20:','12345',':'), + 'datetime' => array('997','12',':5','9999'), + 'datetime2' => array('3125-05-31 05:','.45','$f#','-29 ','0001'), + 'datetimeoffset' => array('+02','96',' ','5092856',':00'), + 'smalldatetime' => array('00','1999','abc',':','06'), + ); +?> diff --git a/test/extended/MsSetup.inc b/test/extended/MsSetup.inc new file mode 100644 index 000000000..36be43084 --- /dev/null +++ b/test/extended/MsSetup.inc @@ -0,0 +1,58 @@ + $database, "UID" => $userName, "PWD" => $userPassword, "TraceOn" => false, "Driver" => $driver); +$daasMode = false; +$marsMode = true; + +$traceEnabled = false; + +$adServer = 'TARGET_AD_SERVER'; +$adDatabase = 'TARGET_AD_DATABASE'; +$adUser = 'TARGET_AD_USERNAME'; +$adPassword = 'TARGET_AD_PASSWORD'; + +if (isset($_ENV['MSSQL_SERVER']) || isset($_ENV['MSSQL_USER']) || isset($_ENV['MSSQL_PASSWORD'])) { + $server = $_ENV['MSSQL_SERVER']; + $uid = $_ENV['MSSQL_USER']; + $pwd = $_ENV['MSSQL_PASSWORD']; + $databaseName = $_ENV['MSSQL_DATABASE_NAME']; +} else { + $uid = $userName; + $pwd = $userPassword; + $databaseName = $database; +} + +// column encryption variables +$keystore = "none"; // key store provider, acceptable values are none, win, ksp, akv +$dataEncrypted = false; // whether data is to be encrypted + +// for Azure Key Vault +$AKVKeyStoreAuthentication = 'TARGET_AKV_AUTH'; // can be KeyVaultPassword or KeyVaultClientSecret +$AKVPrincipalName = 'TARGET_AKV_PRINCIPAL_NAME'; // for use with KeyVaultPassword +$AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPassword +$AKVClientID = 'TARGET_AKV_CLIENT_ID'; // for use with KeyVaultClientSecret +$AKVSecret = 'TARGET_AKV_CLIENT_SECRET'; // for use with KeyVaultClientSecret + +// for enclave computations +$attestation = 'TARGET_ATTESTATION'; +?> \ No newline at end of file diff --git a/test/extended/pdo_AE_functions.inc b/test/extended/pdo_AE_functions.inc new file mode 100644 index 000000000..7f52c939c --- /dev/null +++ b/test/extended/pdo_AE_functions.inc @@ -0,0 +1,911 @@ +$ceValue) { + foreach ($keys as $key) { + foreach ($encryptionTypes as $encryptionType) { + + // $count is used to ensure we only run testCompare and + // testPatternMatch once for the initial table + $count = 0; + + foreach ($targetCeValues as $targetAttestationType=>$targetCeValue) { + foreach ($targetKeys as $targetKey) { + foreach ($targetTypes as $targetType) { + + $conn = connect($ceValue); + if (!$conn) { + if ($attestationType == 'invalid') { + continue; + } else { + die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + } elseif ($attestationType == 'invalid') { + die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + + // Free the encryption cache to avoid spurious 'operand type clash' errors + $conn->query("DBCC FREEPROCCACHE"); + + // Create and populate a non-encrypted table + $createQuery = constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength); + $insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE); + + try { + $stmt = $conn->query("DROP TABLE IF EXISTS $tableName"); + $stmt = $conn->query($createQuery); + } catch(Exception $error) { + print_r($error); + die("Creating a plaintext table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + + insertValues($conn, $insertQuery, $dataTypes, $testValues); + + // Encrypt the table + // Split the data type array, because for some reason we get an error + // if the query is too long (>2000 characters) + // TODO: This is a known issue, follow up on it. + $splitdataTypes = array_chunk($dataTypes, 5); + foreach ($splitdataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $key, $encryptionType, $slength); + $isEncrypted = encryptTable($conn, $alterQuery, $key, $encryptionType, $attestationType); + } + + // Test rich computations + if ($count == 0) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted); + } + ++$count; + + // $sameKeyAndType is used when checking re-encryption, because no error is returned + $sameKeyAndType = false; + if (($key == $targetKey) and ($encryptionType == $targetType) and $isEncrypted) { + $sameKeyAndType = true; + } + + // Disconnect and reconnect with the target ColumnEncryption keyword value + unset($conn); + + $conn = connect($targetCeValue); + if (!$conn) { + if ($targetAttestationType == 'invalid') { + continue; + } else { + die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + } elseif ($targetAttestationType == 'invalid') { + continue; + } + + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted); + + // Re-encrypt the table + // Split the data type array, because for some reason we get an error + // if the query is too long (>2000 characters) + // TODO: This is a known issue, follow up on it. + $splitdataTypes = array_chunk($dataTypes, 5); + foreach ($splitdataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength); + $encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType); + } + + // Test rich computations + if ($encryptionSucceeded) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true); + } else { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted); + } + + unset($conn); + } + } + } + } + } + } +} + +// runEncryptedTest is the main function that cycles through the +// ColumnEncryption keywords, keys, and encryption types, testing +// in-place re-encryption and rich computations. The arguments +// all come from AE_v2_values.inc. +// Arguments: +// array $ceValues: ColumnEncryption keywords/attestation URLs +// array $keys: Encryption keys +// array $encryptionTypes: Encryption types (Deterministic, Randomized) +// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection +// array $targetKeys: Encryption keys on reconnection +// array $targetTypes: Encryption types on reconnection +// string $tableName: Name of table used for testing +// array $dataTypes: Data types going into the table +// array $colNames: Plaintext column names +// array $colNamesAE: Encrypted column names +// integer $length: Size of string columns +// string $slength: $length as a string +// array $testValues: Data to be inserted into the table +// array $comparisons: The comparison operators +// array $patterns: Values to pattern match against +// array $thresholds: Values to use comparison operators against +function runEncryptedTest($ceValues, $keys, $encryptionTypes, + $targetCeValues, $targetKeys, $targetTypes, + $tableName, $dataTypes, $colNames, $colNamesAE, + $length, $slength, $testValues, + $comparisons, $patterns, $thresholds) +{ + // Create a table for each key and encryption type, re-encrypt using each + // combination of target key and target encryption + foreach ($ceValues as $attestationType=>$ceValue) { + + // Cannot create a table with encrypted data if CE is disabled + // TODO: Since we can create an empty encrypted table with + // CE disabled, account for the case where CE is disabled. + if ($ceValue == 'disabled') continue; + + foreach ($keys as $key) { + foreach ($encryptionTypes as $encryptionType) { + + // $count is used to ensure we only run testCompare and + // testPatternMatch once for the initial table + $count = 0; + + foreach ($targetCeValues as $targetAttestationType=>$targetCeValue) { + foreach ($targetKeys as $targetKey) { + foreach ($targetTypes as $targetType) { + + $conn = connect($ceValue); + if (!$conn) { + if ($attestationType == 'invalid') { + continue; + } else { + die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + } elseif ($attestationType == 'invalid') { + die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + + // Free the encryption cache to avoid spurious 'operand type clash' errors + $conn->query("DBCC FREEPROCCACHE"); + + // Create and populate an encrypted table + $createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType); + $insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE); + + try { + $stmt = $conn->query("DROP TABLE IF EXISTS $tableName"); + $stmt = $conn->query($createQuery); + } catch(Exception $error) { + print_r($error); + die("Creating an encrypted table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + + $ceDisabled = ($attestationType == 'disabled') ? true : false; + insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled); + + $isEncrypted = true; + + // Test rich computations + if ($count == 0) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted); + } + ++$count; + + // $sameKeyAndType is used when checking re-encryption, because no error is returned + $sameKeyAndType = false; + if (($key == $targetKey) and ($encryptionType == $targetType) and $isEncrypted) { + $sameKeyAndType = true; + } + + // Disconnect and reconnect with the target ColumnEncryption keyword value + unset($conn); + + $conn = connect($targetCeValue); + if (!$conn) { + if ($targetAttestationType == 'invalid') { + continue; + } else { + die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + } elseif ($targetAttestationType == 'invalid') { + continue; + } + + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted); + + // Re-encrypt the table + $initiallyEnclaveEncryption = isEnclaveEnabled($key); + + // Split the data type array, because for some reason we get an error + // if the query is too long (>2000 characters) + // TODO: This is a known issue, follow up on it. + $splitdataTypes = array_chunk($dataTypes, 5); + foreach ($splitdataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength); + $encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType, true, $initiallyEnclaveEncryption); + } + + // Test rich computations + if ($encryptionSucceeded) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true); + } else { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted); + } + + unset($conn); + } + } + } + } + } + } +} + +// Connect and clear the procedure cache +function connect($attestationInfo) +{ + require("MsSetup.inc"); + $options = "sqlsrv:Server=$server;Database=$databaseName;ColumnEncryption=$attestationInfo"; + + if ($keystore == 'akv') { + + $securityInfo = ''; + if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') { + $securityInfo .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication"; + $securityInfo .= ";KeyStorePrincipalId=$AKVPrincipalName"; + $securityInfo .= ";KeyStoreSecret=$AKVPassword"; + } elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') { + $securityInfo .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication"; + $securityInfo .= ";KeyStorePrincipalId=$AKVClientID"; + $securityInfo .= ";KeyStoreSecret=$AKVSecret"; + } else { + die("Incorrect value for KeyStoreAuthentication keyword!\n"); + } + + $options .= $securityInfo; + } + + try { + $conn = new PDO($options, $uid, $pwd); + } catch (PDOException $error) { + $e = $error->errorInfo; + checkErrors($e, array('CE400', '0')); + return false; + } + + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true); + + // Check that enclave computations are enabled + // See https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/configure-always-encrypted-enclaves?view=sqlallproducts-allversions#configure-a-secure-enclave + $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; + + $stmt = $conn->query($query); + if (!$stmt) { + print_r($conn->errorInfo()); + die("Error when checking if enclave computations are enabled. This should never happen! Non-HGS servers should have been skipped.\n"); + } else { + $info = $stmt->fetch(); + if (empty($info) or ($info['value'] != 1) or ($info['value_in_use'] != 1)) { + die("Error: enclave computations are not enabled on the server!"); + } + } + + // Free the encryption cache to avoid spurious 'operand type clash' errors + $conn->exec("DBCC FREEPROCCACHE"); + + unset($stmt); + + return $conn; +} + +// This CREATE TABLE query simply creates a non-encrypted table with +// two columns for each data type side by side +// This produces a query that looks like +// CREATE TABLE aev2test2 ( +// c_integer integer, +// c_integer_AE integer +// ) +function constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength) +{ + $query = "CREATE TABLE ".$tableName." (\n "; + + foreach ($dataTypes as $type) { + if (dataTypeIsString($type)) { + $query = $query.$colNames[$type]." ".$type."(".$slength."), \n "; + $query = $query.$colNamesAE[$type]." ".$type."(".$slength."), \n "; + } else { + $query = $query.$colNames[$type]." ".$type.", \n "; + $query = $query.$colNamesAE[$type]." ".$type.", \n "; + } + } + + // Remove the ", \n " from the end of the query or the comma will cause a syntax error + $query = substr($query, 0, -7)."\n)"; + + return $query; +} + +// The ALTER TABLE query encrypts columns. Each ALTER COLUMN directive must +// be preceded by ALTER TABLE +// This produces a query that looks like +// ALTER TABLE [dbo].[aev2test2] +// ALTER COLUMN [c_integer_AE] integer +// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL +// WITH +// (ONLINE = ON); ALTER TABLE [dbo].[aev2test2] +// ALTER COLUMN [c_bigint_AE] bigint +// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL +// WITH +// (ONLINE = ON); ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; +function constructAlterQuery($tableName, $colNames, $dataTypes, $key, $encryptionType, $slength) +{ + $query = ''; + + foreach ($dataTypes as $dataType) { + $plength = dataTypeIsString($dataType) ? "(".$slength.")" : ""; + $collate = dataTypeNeedsCollate($dataType) ? " COLLATE Latin1_General_BIN2" : ""; + $query = $query." ALTER TABLE [dbo].[".$tableName."] + ALTER COLUMN [".$colNames[$dataType]."] ".$dataType.$plength." ".$collate." + ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL + WITH + (ONLINE = ON);"; + } + + $query = $query." ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;"; + + return $query; +} + +// This CREATE TABLE query creates a table with two columns for +// each data type side by side, one plaintext and one encrypted +// This produces a query that looks like +// CREATE TABLE aev2test2 ( +// c_integer integer NULL, +// c_integer_AE integer +// COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL +// ) +function constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType) +{ + $query = "CREATE TABLE ".$tableName." (\n "; + + foreach ($dataTypes as $type) { + $collate = dataTypeNeedsCollate($type) ? " COLLATE Latin1_General_BIN2" : ""; + + if (dataTypeIsString($type)) { + $query = $query.$colNames[$type]." ".$type."(".$slength.") NULL, \n "; + $query = $query.$colNamesAE[$type]." ".$type."(".$slength.") \n "; + $query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n "; + } else { + $query = $query.$colNames[$type]." ".$type." NULL, \n "; + $query = $query.$colNamesAE[$type]." ".$type." \n "; + $query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n "; + } + } + + // Remove the ",\n " from the end of the query or the comma will cause a syntax error + $query = substr($query, 0, -6)."\n)"; + + return $query; +} + +// The INSERT query for the table +function constructInsertQuery($tableName, &$dataTypes, &$colNames, &$colNamesAE) +{ + $queryTypes = "("; + $valuesString = "VALUES ("; + + foreach ($dataTypes as $type) { + $colName1 = $colNames[$type].", "; + $colName2 = $colNamesAE[$type].", "; + $queryTypes .= $colName1; + $queryTypes .= $colName2; + $valuesString .= "?, ?, "; + } + + // Remove the ", " from the end of the query or the comma will cause a syntax error + $queryTypes = substr($queryTypes, 0, -2).")"; + $valuesString = substr($valuesString, 0, -2).")"; + + $insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString; + + return $insertQuery; +} + +function insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled=false) +{ + if (empty($testValues)) { + die("$testValues is empty or non-existent. Please check the required values file.\n"); + } + + for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) { + $insertValues = array(); + + // Insert the data using PDO::prepare() + try { + $stmt = $conn->prepare($insertQuery); + $i=1; + foreach ($dataTypes as $type) { + $PDOType = getPDOType($type); + if (!dataTypeIsBinary($type)) { + $stmt->bindParam($i, $testValues[$type][$v], $PDOType); + $stmt->bindParam($i+1, $testValues[$type][$v], $PDOType); + } else { + // unset() is necessary because otherwise the same data may be + // inserted into multiple binary columns. + unset($val); + $val=pack('H*', $testValues[$type][$v]); + $stmt->bindParam($i, $val, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam($i+1, $val, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY); + } + $i+=2; + } + $stmt->execute(); + } catch (PDOException $error) { + if (!$ceDisabled) { + print_r($error); + die("Inserting values in encrypted table failed\n"); + } else { + $e = $error->errorInfo; + checkErrors($e, array('22018', '206')); + } + } + } + + unset($stmt); +} + +// encryptTable attempts to encrypt the table in place and verifies +// if it works given the attestation info and key type. +// Arguments: +// resource $conn: The connection +// string $alterQuery: The query to encrypt the table +// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', 'disabled', or 'wrongurl' +// bool $sameKeyAndType: Whether the key and encryption type are same for re-encrypting +// as for initial encryption. +// bool $initialEncryption: Whether we are testing with table initially encrypted, instead +// of plaintext being encrypted after creation +// bool $initiallyEnclaveEncrypted: Whether the table was initally encrypted with an +// enclave-enabled key +function encryptTable($conn, $alterQuery, $key, $encryptionType, $attestation, $sameKeyAndType=false, $initialEncryption=false, $initallyEnclaveEncrypted=false) +{ + try { + $stmt = $conn->query($alterQuery); + if ((!isEnclaveEnabled($key) or $attestation != 'correct') and !$sameKeyAndType) { + die("Encrypting should have failed with attestation $attestation, key $key and encryption type $encryptionType\n"); + } + } catch (PDOException $error) { + if ($sameKeyAndType) { + print_r($error); + die("Encrypting table should not fail when target encryption key and type are the same as source: attestation $attestation, key $key and encryption type $encryptionType\n"); + } elseif ($initialEncryption and !$initallyEnclaveEncrypted) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33543')); + } elseif ($attestation == 'correct') { + if (isEnclaveEnabled($key)) { + print_r($error); + die("Encrypting with correct attestation failed when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n"); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33543')); + } + } elseif ($attestation == 'enabled' or $attestation == 'disabled') { + if (isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33546')); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33543')); + } + } elseif ($attestation == 'wrongurl') { + if (isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('CE405', '0')); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33543')); + } + } elseif ($attestation == 'invalid') { + die("Encrypting table with invalid protocol! Should not get here!\n"); + } else { + die("Error! This is no-man's-land\n"); + } + + return false; + } + + unset($stmt); + + return true; +} + +// compareResults checks that the results between the encrypted and non-encrypted +// columns are identical if statement execution succeeds. If statement execution +// fails, this function checks for the correct error. +// Arguments: +// statement $AEstmt: Prepared statement fetching encrypted data +// statement $nonAEstmt: Prepared statement fetching non-encrypted data +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl' +// string $comparison: Comparison operator +// string $type: Data type the comparison is operating on +function compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $comparison='', $type='') +{ + try { + $nonAEstmt->execute(); + } catch(Exception $error) { + print_r($error); + die("Executing non-AE computation statement failed!\n"); + } + + try { + $AEstmt->execute(); + } catch(Exception $error) { + if (!$isEncrypted) { + die("Computation statement execution should not have failed for an unencrypted table: attestation $attestation, key $key and encryption type $encryptionType\n"); + } + + if ($attestation == 'enabled') { + if ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r($error); + die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n"); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } + } elseif (isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33546')); + } elseif (!isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } else { + print_r($error); + die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType"); + } + } elseif ($attestation == 'wrongurl') { + if ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + $e = $error->errorInfo; + die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n"); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } + } elseif (isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('CE405', '0')); + } elseif (!isEnclaveEnabled($key)) { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } else { + print_r($error); + die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType"); + } + } elseif ($attestation == 'correct') { + if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } elseif ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r($error); + die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n"); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } + } else { + print_r($error); + die("Comparison failed for correct attestation when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n"); + } + } elseif ($attestation == 'disabled') { + if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } elseif ($comparison == '=' or $comparison == '<>' or $encryptionType == 'Randomized') { + $e = $error->errorInfo; + checkErrors($e, array('22018', '206')); + } else { + $e = $error->errorInfo; + checkErrors($e, array('42000', '33277')); + } + } else { + print_r($error); + die("Unexpected error occurred in compareResults: attestation $attestation, key $key and encryption type $encryptionType\n"); + } + + return; + } + + $AEres = $AEstmt->fetchAll(PDO::FETCH_NUM); + $nonAEres = $nonAEstmt->fetchAll(PDO::FETCH_NUM); + $AEcount = count($AEres); + $nonAEcount = count($nonAEres); + + if ($type == 'char' or $type == 'nchar' or $type == 'binary') { + // char and nchar may not return the same results - at this point + // we've verified that statement execution works so just return + // TODO: Check if this bug is fixed and if so, remove this if block + return; + } elseif ($AEcount > $nonAEcount) { + print_r("Too many AE results for operation $comparison and data type $type!\n"); + print_r($AEres); + print_r($nonAEres); + } elseif ($AEcount < $nonAEcount) { + print_r("Too many non-AE results for operation $comparison and data type $type!\n"); + print_r($AEres); + print_r($nonAEres); + } else { + if ($AEcount != 0) { + $i = 0; + foreach ($AEres as $AEr) { + if ($AEr[0] != $nonAEres[$i][0]) { + print_r("AE and non-AE results are different for operation $comparison and data type $type! For field $i, got AE result ".$AEres[$i][0]." and non-AE result ".$nonAEres[$i][0]."\n"); + } + ++$i; + } + } + } +} + +// testCompare selects based on a comparison in the WHERE clause and compares +// the results between encrypted and non-encrypted columns, checking that the +// results are identical +// Arguments: +// resource $conn: The connection +// string $tableName: Table name +// array $comparisons: Comparison operations from AE_v2_values.inc +// array $dataTypes: Data types from AE_v2_values.inc +// array $colNames: Column names +// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc +// string $key: Name of the encryption key +// integer $length: Length of the string types, from AE_v2_values.inc +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl' +// bool $isEncrypted: Whether the table is encrypted +function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestation, $isEncrypted) +{ + foreach ($comparisons as $comparison) { + foreach ($dataTypes as $type) { + + // Unicode operations with AE require the Latin1_General_BIN2 + // collation. If the COLLATE clause is left out, we get different + // results between the encrypted and non-encrypted columns (probably + // because the collation was only changed in the encryption query). + $string = dataTypeIsStringMax($type); + $collate = $string ? " COLLATE Latin1_General_BIN2" : ""; + $unicode = dataTypeIsUnicode($type); + $PDOType = getPDOType($type); + unset($threshold); + $threshold = dataTypeIsBinary($type) ? pack('H*', $thresholds[$type]) : $thresholds[$type]; + + $AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE ".$comparison." ?".$collate; + $nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." ".$comparison." ?".$collate; + + try { + $AEstmt = $conn->prepare($AEQuery); + $nonAEstmt = $conn->prepare($nonAEQuery); + + if (!dataTypeIsBinary($type)) { + $AEstmt->bindParam(1, $threshold, $PDOType); + $nonAEstmt->bindParam(1, $threshold, $PDOType); + } else { + $AEstmt->bindParam(1, $threshold, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY); + $nonAEstmt->bindParam(1, $threshold, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY); + } + } catch (PDOException $error) { + print_r($error); + die("Preparing/binding statements for comparison failed! Comparison $comparison, type $type"); + } + + compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $comparison, $type); + + unset($AEstmt); + unset($nonAEstmt); + } + } +} + +// testPatternMatch selects based on a pattern in the WHERE clause and compares +// the results between encrypted and non-encrypted columns, checking that the +// results are identical +// Arguments: +// resource $conn: The connection +// string $tableName: Table name +// array $patterns: Strings to pattern match, from AE_v2_values.inc +// array $dataTypes: Data types from AE_v2_values.inc +// array $colNames: Column names +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', 'disabled', or 'wrongurl' +// bool $isEncrypted: Whether the table is encrypted +function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation, $isEncrypted) +{ + foreach ($dataTypes as $type) { + + // TODO: Pattern matching doesn't work in AE for non-string types. + // This is for security reasons, follow up on it. + if (!dataTypeIsStringMax($type)) { + continue; + } + + foreach ($patterns[$type] as $pattern) { + + $patternArray = array($pattern, + $pattern."%", + "%".$pattern, + "%".$pattern."%", + ); + + foreach ($patternArray as $spattern) { + + // Unicode operations with AE require the PHPTYPE to be specified as + // UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE + // clause is left out, we get different results between the + // encrypted and non-encrypted columns (probably because the + // collation was only changed in the encryption query). + // We must pass the length of the pattern matching string + // to the SQLTYPE instead of the field size, as we usually would, + // because otherwise we would get an empty result set. + // We need iconv_strlen to return the number of characters + // for unicode strings, since strlen returns the number of bytes. + $unicode = dataTypeIsUnicode($type); + $collate = $unicode ? " COLLATE Latin1_General_BIN2" : ""; + $PDOType = getPDOType($type); + + $AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE LIKE ?".$collate; + $nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." LIKE ?".$collate; + + // TODO: Add binary type support below. May need to use unset() + // as in insertValues(). + try { + $AEstmt = $conn->prepare($AEQuery); + $AEstmt->bindParam(1, $spattern, $PDOType); + $nonAEstmt = $conn->prepare($nonAEQuery); + $nonAEstmt->bindParam(1, $spattern, $PDOType); + } catch (PDOException $error) { + print_r($error); + die("Preparing/binding statements for comparison failed! Comparison $comparison, type $type\n"); + } + + compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $pattern, $type); + + unset($AEstmt); + unset($nonAEstmt); + } + } + } +} + +// Check that the expected errors ($codes) is found in the PDOException ($errors) +function checkErrors($errors, ...$codes) +{ + $codeFound = false; + + foreach ($codes as $code) { + if ($code[0]==$errors[0] and $code[1]==$errors[1]) { + $codeFound = true; + break; + } + } + + if ($codeFound == false) { + echo "Error: "; + print_r($errors); + echo "\nExpected: "; + print_r($codes); + echo "\n"; + die("Error code not found.\n"); + } +} + +function isEnclaveEnabled($key) +{ + return (strpos($key, '-enclave') !== false); +} + +function dataTypeIsString($dataType) +{ + return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar"])); +} + +function dataTypeIsStringMax($dataType) +{ + return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"])); +} + +function dataTypeNeedsCollate($dataType) +{ + return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"])); +} + +function dataTypeIsUnicode($dataType) +{ + return (in_array($dataType, ["nchar", "nvarchar", "nvarchar(max)"])); +} + +function dataTypeIsBinary($dataType) +{ + return (in_array($dataType, ["binary", "varbinary", "varbinary(max)"])); +} + +function getPDOType($type) +{ + switch($type) { + case "bigint": + case "integer": + case "smallint": + case "tinyint": + return PDO::PARAM_INT; + case "bit": + return PDO::PARAM_BOOL; + case "real": + case "float": + case "double": + case "numeric": + case "time": + case "date": + case "datetime2": + case "datetime": + case "datetimeoffset": + case "smalldatetime": + case "money": + case "smallmoney"; + case "xml": + case "uniqueidentifier": + case "char": + case "varchar": + case "varchar(max)": + case "nchar": + case "nvarchar": + case "nvarchar(max)": + return PDO::PARAM_STR; + case "binary": + case "varbinary": + case "varbinary(max)": + return PDO::PARAM_LOB; + default: + die("Case is missing for $type type in getPDOType.\n"); + } +} + +?> diff --git a/test/extended/pdo_aev2_plaintext_nonstring.phpt b/test/extended/pdo_aev2_plaintext_nonstring.phpt new file mode 100644 index 000000000..81e47198b --- /dev/null +++ b/test/extended/pdo_aev2_plaintext_nonstring.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test rich computations and in-place encryption of plaintext with AE v2. +--DESCRIPTION-- +This test cycles through $ceValues, $encryptionTypes, and $keys, creating a +plaintext table each time, then trying to encrypt it with different combinations +of enclave-enabled and non-enclave keys and encryption types. It then reconnects +and cycles through $targetCeValues, $targetTypes and $targetKeys to try re-encrypting +the table with different target combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Create a table in plaintext with two columns for each AE-supported data type. +2. Insert some data in plaintext. +3. Encrypt one column for each data type. +4. Perform rich computations on each AE-enabled column (comparisons and pattern matching) + and compare the result to the same query on the corresponding non-AE column for each data type. +5. Ensure the two results are the same. +6. Disconnect and reconnect with a new value for ColumnEncryption. +7. Compare computations as in 4. above. +8. Re-encrypt the table using a new key and/or encryption type. +9. Compare computations as in 4. above. +This test only tests nonstring types, because if we try to tests all types at +once, eventually a CE405 error is returned. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done. diff --git a/test/extended/pdo_aev2_plaintext_string.phpt b/test/extended/pdo_aev2_plaintext_string.phpt new file mode 100644 index 000000000..14bc276f8 --- /dev/null +++ b/test/extended/pdo_aev2_plaintext_string.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test rich computations and in-place encryption of plaintext with AE v2. +--DESCRIPTION-- +This test cycles through $ceValues, $encryptionTypes, and $keys, creating a +plaintext table each time, then trying to encrypt it with different combinations +of enclave-enabled and non-enclave keys and encryption types. It then reconnects +and cycles through $targetCeValues, $targetTypes and $targetKeys to try re-encrypting +the table with different target combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Create a table in plaintext with two columns for each AE-supported data type. +2. Insert some data in plaintext. +3. Encrypt one column for each data type. +4. Perform rich computations on each AE-enabled column (comparisons and pattern matching) + and compare the result to the same query on the corresponding non-AE column for each data type. +5. Ensure the two results are the same. +6. Disconnect and reconnect with a new value for ColumnEncryption. +7. Compare computations as in 4. above. +8. Re-encrypt the table using a new key and/or encryption type. +9. Compare computations as in 4. above. +This test only tests string types, because if we try to tests all types at +once, eventually a CE405 error is returned. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done. diff --git a/test/extended/pdo_aev2_reencrypt_encrypted_nonstring.phpt b/test/extended/pdo_aev2_reencrypt_encrypted_nonstring.phpt new file mode 100644 index 000000000..0821e8e35 --- /dev/null +++ b/test/extended/pdo_aev2_reencrypt_encrypted_nonstring.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test rich computations and in place re-encryption with AE v2. +--DESCRIPTION-- +This test cycles through $ceValues, $encryptionTypes, and $keys, creating +an encrypted table each time, then cycles through $targetCeValues, $targetTypes, +and $targetKeys to try re-encrypting the table with different combinations of +enclave-enabled and non-enclave keys and encryption types. +The sequence of operations is the following: +1. Create an encrypted table with two columns for each AE-supported data type, + one encrypted and one not encrypted. +2. Insert some data. +3. Perform rich computations on each AE-enabled column (comparisons and pattern matching) + and compare the result to the same query on the corresponding non-AE column for each data type. +4. Ensure the two results are the same. +5. Disconnect and reconnect with a new value for ColumnEncryption. +6. Compare computations as in 3. above. +7. Re-encrypt the table using a new key and/or encryption type. +8. Compare computations as in 3. above. +This test only tests nonstring types, because if we try to tests all types at +once, eventually a CE405 error is returned. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done. diff --git a/test/extended/pdo_aev2_reencrypt_encrypted_string.phpt b/test/extended/pdo_aev2_reencrypt_encrypted_string.phpt new file mode 100644 index 000000000..e016fbfd3 --- /dev/null +++ b/test/extended/pdo_aev2_reencrypt_encrypted_string.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test rich computations and in place re-encryption with AE v2. +--DESCRIPTION-- +This test cycles through $ceValues, $encryptionTypes, and $keys, creating +an encrypted table each time, then cycles through $targetCeValues, $targetTypes, +and $targetKeys to try re-encrypting the table with different combinations of +enclave-enabled and non-enclave keys and encryption types. +The sequence of operations is the following: +1. Create an encrypted table with two columns for each AE-supported data type, + one encrypted and one not encrypted. +2. Insert some data. +3. Perform rich computations on each AE-enabled column (comparisons and pattern matching) + and compare the result to the same query on the corresponding non-AE column for each data type. +4. Ensure the two results are the same. +5. Disconnect and reconnect with a new value for ColumnEncryption. +6. Compare computations as in 3. above. +7. Re-encrypt the table using a new key and/or encryption type. +8. Compare computations as in 3. above. +This test only tests string types, because if we try to tests all types at +once, eventually a CE405 error is returned. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done. diff --git a/test/extended/skipif_not_hgs.inc b/test/extended/skipif_not_hgs.inc new file mode 100644 index 000000000..dd4614de8 --- /dev/null +++ b/test/extended/skipif_not_hgs.inc @@ -0,0 +1,36 @@ +$uid, "PWD"=>$pwd, "Driver" => $driver); + +$conn = sqlsrv_connect( $server, $connectionInfo ); +if ($conn === false) { + die( "skip Could not connect during SKIPIF." ); +} + +$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; +$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; +$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; + +if ($msodbcsql_maj < 17) { + die("skip Unsupported ODBC driver version"); +} + +if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) { + die("skip Unsupported ODBC driver version"); +} + +// Get SQL Server +$server_info = sqlsrv_server_info($conn); +if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) { + die("skip Server is not HGS enabled"); +} +?> diff --git a/test/extended/sqlsrv_AE_functions.inc b/test/extended/sqlsrv_AE_functions.inc new file mode 100644 index 000000000..5760b65f5 --- /dev/null +++ b/test/extended/sqlsrv_AE_functions.inc @@ -0,0 +1,991 @@ +$ceValue) { + foreach ($keys as $key) { + foreach ($encryptionTypes as $encryptionType) { + + // $count is used to ensure we only run testCompare and + // testPatternMatch once for the initial table + $count = 0; + + foreach ($targetCeValues as $targetAttestationType=>$targetCeValue) { + foreach ($targetKeys as $targetKey) { + foreach ($targetTypes as $targetType) { + + $conn = connect($ceValue); + if (!$conn) { + if ($attestationType == 'invalid') { + continue; + } else { + print_r(sqlsrv_errors()); + die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + } elseif ($attestationType == 'invalid') { + die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + + // Free the encryption cache to avoid spurious 'operand type clash' errors + sqlsrv_query($conn, "DBCC FREEPROCCACHE"); + + // Create and populate a non-encrypted table + $createQuery = constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength); + $insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE); + + $stmt = sqlsrv_query($conn, "DROP TABLE IF EXISTS $tableName"); + $stmt = sqlsrv_query($conn, $createQuery); + if(!$stmt) { + print_r(sqlsrv_errors()); + die("Creating a plaintext table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + + insertValues($conn, $insertQuery, $dataTypes, $testValues); + + // Encrypt the table + // Split the data type array, because for some reason we get an error + // if the query is too long (>2000 characters) + // TODO: This is a known issue, follow up on it. + $splitdataTypes = array_chunk($dataTypes, 5); + foreach ($splitdataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $key, $encryptionType, $slength); + $isEncrypted = encryptTable($conn, $alterQuery, $key, $encryptionType, $attestationType); + } + + // Test rich computations + if ($count == 0) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted); + } + ++$count; + + // $sameKeyAndType is used when checking re-encryption, because no error is returned + $sameKeyAndType = false; + if ($key == $targetKey and $encryptionType == $targetType and $isEncrypted) { + $sameKeyAndType = true; + } + + // Disconnect and reconnect with the target ColumnEncryption keyword value + unset($conn); + + $conn = connect($targetCeValue); + if (!$conn) { + if ($targetAttestationType == 'invalid') { + continue; + } else { + print_r(sqlsrv_errors()); + die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + } elseif ($targetAttestationType == 'invalid') { + continue; + } + + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted); + + // Re-encrypt the table + // Split the data type array, because for some reason we get an error + // if the query is too long (>2000 characters) + // TODO: This is a known issue, follow up on it. + $splitdataTypes = array_chunk($dataTypes, 5); + foreach ($splitdataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength); + $encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType); + } + + // Test rich computations + if ($encryptionSucceeded) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true); + } else { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted); + } + + unset($conn); + } + } + } + } + } + } +} + +// runEncryptedTest is the main function that cycles through the +// ColumnEncryption keywords, keys, and encryption types, testing +// in-place re-encryption and rich computations. The arguments +// all come from AE_v2_values.inc. +// Arguments: +// array $ceValues: ColumnEncryption keywords/attestation URLs +// array $keys: Encryption keys +// array $encryptionTypes: Encryption types (Deterministic, Randomized) +// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection +// array $targetKeys: Encryption keys on reconnection +// array $targetTypes: Encryption types on reconnection +// string $tableName: Name of table used for testing +// array $dataTypes: Data types going into the table +// array $colNames: Plaintext column names +// array $colNamesAE: Encrypted column names +// integer $length: Size of string columns +// string $slength: $length as a string +// array $testValues: Data to be inserted into the table +// array $comparisons: The comparison operators +// array $patterns: Values to pattern match against +// array $thresholds: Values to use comparison operators against +function runEncryptedTest($ceValues, $keys, $encryptionTypes, + $targetCeValues, $targetKeys, $targetTypes, + $tableName, $dataTypes, $colNames, $colNamesAE, + $length, $slength, $testValues, + $comparisons, $patterns, $thresholds) +{ + // Create a table for each key and encryption type, re-encrypt using each + // combination of target key and target encryption + foreach ($ceValues as $attestationType=>$ceValue) { + + // Cannot create a table with encrypted data if CE is disabled + // TODO: Since we can create an empty encrypted table with + // CE disabled, account for the case where CE is disabled. + if ($ceValue == 'disabled') continue; + + foreach ($keys as $key) { + foreach ($encryptionTypes as $encryptionType) { + + // $count is used to ensure we only run testCompare and + // testPatternMatch once for the initial table + $count = 0; + + foreach ($targetCeValues as $targetAttestationType=>$targetCeValue) { + foreach ($targetKeys as $targetKey) { + foreach ($targetTypes as $targetType) { + + $conn = connect($ceValue); + if (!$conn) { + if ($attestationType == 'invalid') { + continue; + } else { + print_r(sqlsrv_errors()); + die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + } elseif ($attestationType == 'invalid') { + die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + + // Free the encryption cache to avoid spurious 'operand type clash' errors + sqlsrv_query($conn, "DBCC FREEPROCCACHE"); + + // Create and populate an encrypted table + $createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType); + $insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE); + + $stmt = sqlsrv_query($conn, "DROP TABLE IF EXISTS $tableName"); + $stmt = sqlsrv_query($conn, $createQuery); + if(!$stmt) { + print_r(sqlsrv_errors()); + die("Creating an encrypted table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + + $ceDisabled = $attestationType == 'disabled' ? true : false; + insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled); + + $isEncrypted = true; + + // Test rich computations + if ($count == 0) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted); + } + ++$count; + + // $sameKeyAndType is used when checking re-encryption, because no error is returned + $sameKeyAndType = false; + if (($key == $targetKey) and ($encryptionType == $targetType) and $isEncrypted) { + $sameKeyAndType = true; + } + + // Disconnect and reconnect with the target ColumnEncryption keyword value + unset($conn); + + $conn = connect($targetCeValue); + if (!$conn) { + if ($targetAttestationType == 'invalid') { + continue; + } else { + print_r(sqlsrv_errors()); + die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n"); + } + } elseif ($targetAttestationType == 'invalid') { + continue; + } + + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted); + + // Re-encrypt the table + $initiallyEnclaveEncryption = isEnclaveEnabled($key); + + // Split the data type array, because for some reason we get an error + // if the query is too long (>2000 characters) + // TODO: This is a known issue, follow up on it. + $splitdataTypes = array_chunk($dataTypes, 5); + foreach ($splitdataTypes as $split) { + $alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength); + $encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType, true, $initiallyEnclaveEncryption); + } + + // Test rich computations + if ($encryptionSucceeded) { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true); + } else { + testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted); + testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted); + } + + unset($conn); + } + } + } + } + } + } +} + +// Connect and clear the procedure cache +function connect($attestationInfo) +{ + require("MsSetup.inc"); + $options = array('database'=>$database, + 'uid'=>$userName, + 'pwd'=>$userPassword, + 'CharacterSet'=>'UTF-8', + 'ColumnEncryption'=>$attestationInfo, + 'TraceOn'=>true, + 'TraceOn'=>'c:\Users\davidp\Documents\SQL.LOG', + ); + + if ($keystore == 'akv') { + if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') { + $securityInfo = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication, + 'KeyStorePrincipalId'=>$AKVPrincipalName, + 'KeyStoreSecret'=>$AKVPassword, + ); + } elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') { + $securityInfo = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication, + 'KeyStorePrincipalId'=>$AKVClientID, + 'KeyStoreSecret'=>$AKVSecret, + ); + } else { + die("Incorrect value for KeyStoreAuthentication keyword!\n"); + } + + $options = array_merge($options, $securityInfo); + } + + $conn = sqlsrv_connect($server, $options); + if (!$conn) { + $e = sqlsrv_errors(); + checkErrors($e, array('CE400', '0')); + return false; + } + else + { + // Check that enclave computations are enabled + // See https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/configure-always-encrypted-enclaves?view=sqlallproducts-allversions#configure-a-secure-enclave + $query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';"; + $stmt = sqlsrv_query($conn, $query); + if (!$stmt) { + print_r(sqlsrv_errors()); + die("Error when checking if enclave computations are enabled. This should never happen! Non-HGS servers should have been skipped.\n"); + } else { + $info = sqlsrv_fetch_array($stmt); + if (empty($info) or ($info['value'] != 1) or ($info['value_in_use'] != 1)) { + die("Error: enclave computations are not enabled on the server!"); + } + } + + // Enable rich computations + sqlsrv_query($conn, "DBCC traceon(127,-1);"); + + // Free the encryption cache to avoid spurious 'operand type clash' errors + sqlsrv_query($conn, "DBCC FREEPROCCACHE"); + } + + return $conn; +} + +// This CREATE TABLE query simply creates a non-encrypted table with +// two columns for each data type side by side +// This produces a query that looks like +// CREATE TABLE aev2test2 ( +// c_integer integer, +// c_integer_AE integer +// ) +function constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength) +{ + $query = "CREATE TABLE ".$tableName." (\n "; + + foreach ($dataTypes as $type) { + if (dataTypeIsString($type)) { + $query = $query.$colNames[$type]." ".$type."(".$slength."), \n "; + $query = $query.$colNamesAE[$type]." ".$type."(".$slength."), \n "; + } else { + $query = $query.$colNames[$type]." ".$type.", \n "; + $query = $query.$colNamesAE[$type]." ".$type.", \n "; + } + } + + // Remove the ", \n " from the end of the query or the comma will cause a syntax error + $query = substr($query, 0, -7)."\n)"; + + return $query; +} + +// The ALTER TABLE query encrypts columns. Each ALTER COLUMN directive must +// be preceded by ALTER TABLE +// This produces a query that looks like +// ALTER TABLE [dbo].[aev2test2] +// ALTER COLUMN [c_integer_AE] integer +// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL +// WITH +// (ONLINE = ON); ALTER TABLE [dbo].[aev2test2] +// ALTER COLUMN [c_bigint_AE] bigint +// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL +// WITH +// (ONLINE = ON); ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; +function constructAlterQuery($tableName, $colNames, $dataTypes, $key, $encryptionType, $slength) +{ + $query = ''; + + foreach ($dataTypes as $dataType) { + $plength = dataTypeIsString($dataType) ? "(".$slength.")" : ""; + $collate = dataTypeNeedsCollate($dataType) ? " COLLATE Latin1_General_BIN2" : ""; + $query = $query." ALTER TABLE [dbo].[".$tableName."] + ALTER COLUMN [".$colNames[$dataType]."] ".$dataType.$plength." ".$collate." + ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL + WITH + (ONLINE = ON);"; + } + + $query = $query." ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;"; + + return $query; +} + +// This CREATE TABLE query creates a table with two columns for +// each data type side by side, one plaintext and one encrypted +// This produces a query that looks like +// CREATE TABLE aev2test2 ( +// c_integer integer NULL, +// c_integer_AE integer +// COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL +// ) +function constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType) +{ + $query = "CREATE TABLE ".$tableName." (\n "; + + foreach ($dataTypes as $type) { + $collate = dataTypeNeedsCollate($type) ? " COLLATE Latin1_General_BIN2" : ""; + + if (dataTypeIsString($type)) { + $query = $query.$colNames[$type]." ".$type."(".$slength.") NULL, \n "; + $query = $query.$colNamesAE[$type]." ".$type."(".$slength.") \n "; + $query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n "; + } else { + $query = $query.$colNames[$type]." ".$type." NULL, \n "; + $query = $query.$colNamesAE[$type]." ".$type." \n "; + $query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n "; + } + } + + // Remove the ",\n " from the end of the query or the comma will cause a syntax error + $query = substr($query, 0, -6)."\n)"; + + return $query; +} + +// The INSERT query for the table +function constructInsertQuery($tableName, &$dataTypes, &$colNames, &$colNamesAE) +{ + $queryTypes = "("; + $valuesString = "VALUES ("; + + foreach ($dataTypes as $type) { + $colName1 = $colNames[$type].", "; + $colName2 = $colNamesAE[$type].", "; + $queryTypes .= $colName1; + $queryTypes .= $colName2; + $valuesString .= "?, ?, "; + } + + // Remove the ", " from the end of the query or the comma will cause a syntax error + $queryTypes = substr($queryTypes, 0, -2).")"; + $valuesString = substr($valuesString, 0, -2).")"; + + $insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString; + + return $insertQuery; +} + +function insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled=false) +{ + global $length; + + if (empty($testValues)) { + die("$testValues is empty or non-existent. Please check the required values file.\n"); + } + + for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) { + $insertValues = array(); + + // Use pack() on binary data + $params = array(); + foreach ($dataTypes as $type) { + $SQLType = getSQLType($type, $length); + $PHPType = getPHPType($type); + $val = dataTypeIsBinary($type) ? pack('H*', $testValues[$type][$v]) : $testValues[$type][$v]; + $params[] = array($val, SQLSRV_PARAM_IN, $PHPType, $SQLType); + $params[] = array($val, SQLSRV_PARAM_IN, $PHPType, $SQLType); + } + + // Insert the data using sqlsrv_prepare() + $stmt = sqlsrv_prepare($conn, $insertQuery, $params); + if ($stmt == false) { + if (!$ceDisabled) { + print_r(sqlsrv_errors()); + die("Inserting values in encrypted table failed at prepare\n"); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('22018', '206')); + } + } + + if (sqlsrv_execute($stmt) == false) { + if (!$ceDisabled) { + print_r(sqlsrv_errors()); + die("Inserting values in encrypted table failed at execute\n"); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('22018', '206')); + } + } + + sqlsrv_free_stmt($stmt); + } +} + +// encryptTable attempts to encrypt the table in place and verifies +// if it works given the attestation info and key type. +// Arguments: +// resource $conn: The connection +// string $alterQuery: The query to encrypt the table +// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', 'disabled', or 'wrongurl' +// bool $sameKeyAndType: Whether the key and encryption type are same for re-encrypting +// as for initial encryption. +// bool $initialEncryption: Whether we are testing with table initially encrypted, instead +// of plaintext being encrypted after creation +// bool $initiallyEnclaveEncrypted: Whether the table was initally encrypted with an +// enclave-enabled key +function encryptTable($conn, $alterQuery, $key, $encryptionType, $attestation, $sameKeyAndType=false, $initialEncryption=false, $initallyEnclaveEncrypted=false) +{ + $stmt = sqlsrv_query($conn, $alterQuery); + + if(!$stmt) { + if ($sameKeyAndType) { + print_r(sqlsrv_errors()); + die("Encrypting table should not fail when target encryption key and type are the same as source: attestation $attestation, key $key and encryption type $encryptionType\n"); + } elseif ($initialEncryption and !$initallyEnclaveEncrypted) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33543')); + } elseif ($attestation == 'correct') { + if (isEnclaveEnabled($key)) { + print_r(sqlsrv_errors()); + die("Encrypting with correct attestation failed when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n"); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33543')); + } + } elseif ($attestation == 'enabled' or $attestation == 'disabled') { + if (isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33546')); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33543')); + } + } elseif ($attestation == 'wrongurl') { + if (isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('CE405', '0')); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33543')); + } + } elseif ($attestation == 'invalid') { + die("Encrypting table with invalid protocol! Should not get here!\n"); + } else { + die("Error! This is no-man's-land\n"); + } + + return false; + } else { + if ((!isEnclaveEnabled($key) or $attestation != 'correct') and !$sameKeyAndType) { + die("Encrypting should have failed with attestation $attestation, key $key and encryption type $encryptionType\n"); + } + + unset($stmt); + + return true; + } +} + +// compareResults checks that the results between the encrypted and non-encrypted +// columns are identical if statement execution succeeds. If statement execution +// fails, this function checks for the correct error. +// Arguments: +// statement $AEstmt: Prepared statement fetching encrypted data +// statement $nonAEstmt: Prepared statement fetching non-encrypted data +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl' +// string $comparison: Comparison operator +// string $type: Data type the comparison is operating on +function compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $comparison='', $type='') +{ + if (!sqlsrv_execute($nonAEstmt)) { + print_r(sqlsrv_errors()); + die("Executing non-AE computation statement failed!\n"); + } + + if(!sqlsrv_execute($AEstmt)) { + if (!$isEncrypted) { + die("Computation statement execution should not have failed for an unencrypted table: attestation $attestation, key $key and encryption type $encryptionType\n"); + } + + if ($attestation == 'enabled') { + if ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r(sqlsrv_errors()); + die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n"); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } + } elseif (isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33546')); + } elseif (!isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } else { + print_r(sqlsrv_errors()); + die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType"); + } + } elseif ($attestation == 'wrongurl') { + if ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r(sqlsrv_errors()); + die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n"); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } + } elseif (isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('CE405', '0')); + } elseif (!isEnclaveEnabled($key)) { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } else { + print_r(sqlsrv_errors()); + die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType"); + } + } elseif ($attestation == 'correct') { + if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } elseif ($encryptionType == 'Deterministic') { + if ($comparison == '=') { + print_r(sqlsrv_errors()); + die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\n"); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } + } else { + print_r(sqlsrv_errors()); + die("Comparison failed for correct attestation when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n"); + } + } elseif ($attestation == 'disabled') { + if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } elseif ($comparison == '=' or $comparison == '<>' or $encryptionType == 'Randomized') { + $e = sqlsrv_errors(); + checkErrors($e, array('22018', '206')); + } else { + $e = sqlsrv_errors(); + checkErrors($e, array('42000', '33277')); + } + } else { + print_r(sqlsrv_errors()); + die("Unexpected error occurred in compareResults: attestation $attestation, key $key and encryption type $encryptionType\n"); + } + } else { + // char and nchar may not return the same results - at this point + // we've verified that statement execution works so just return + // TODO: Check if this bug is fixed and if so, remove this if block + if ($type == 'char' or $type == 'nchar' or $type == 'binary') { + return; + } + + while($AEres = sqlsrv_fetch_array($AEstmt, SQLSRV_FETCH_NUMERIC)) { + $nonAEres = sqlsrv_fetch_array($nonAEstmt, SQLSRV_FETCH_NUMERIC); + if (!$nonAEres) { + print_r($AEres); + print_r(sqlsrv_errors()); + print_r("Too many AE results for operation $comparison and data type $type!\n"); + } else { + $i = 0; + foreach ($AEres as $AEr) { + if ($AEr != $nonAEres[$i]) { + print_r("AE and non-AE results are different for operation $comparison and data type $type! For field $i, got AE result ".$AEres[$i]." and non-AE result ".$nonAEres[$i]."\n"); + print_r(sqlsrv_errors()); + } + ++$i; + } + } + } + + if ($rr = sqlsrv_fetch_array($nonAEstmt)) { + print_r($rr); + print_r(sqlsrv_errors()); + print_r("Too many non-AE results for operation $comparison and data type $type!\n"); + } + } +} + +// testCompare selects based on a comparison in the WHERE clause and compares +// the results between encrypted and non-encrypted columns, checking that the +// results are identical +// Arguments: +// resource $conn: The connection +// string $tableName: Table name +// array $comparisons: Comparison operations from AE_v2_values.inc +// array $dataTypes: Data types from AE_v2_values.inc +// array $colNames: Column names +// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc +// string $key: Name of the encryption key +// integer $length: Length of the string types, from AE_v2_values.inc +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl' +// bool $isEncrypted: Whether the table is encrypted +function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestation, $isEncrypted) +{ + foreach ($comparisons as $comparison) { + foreach ($dataTypes as $type) { + + // Unicode operations with AE require the PHPTYPE to be specified to + // UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE + // clause is left out, we get different results between the + // encrypted and non-encrypted columns (probably because the + // collation was only changed in the encryption query). + $string = dataTypeIsStringMax($type); + $unicode = dataTypeIsUnicode($type); + $collate = $string ? " COLLATE Latin1_General_BIN2" : ""; + $phptype = getPHPType($type); + $threshold = dataTypeIsBinary($type) ? pack('H*', $thresholds[$type]) : $thresholds[$type]; + + $param = array(array($threshold, SQLSRV_PARAM_IN, $phptype, getSQLType($type, $length))); + $AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE ".$comparison." ?".$collate; + $nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." ".$comparison." ?".$collate; + + $AEstmt = sqlsrv_prepare($conn, $AEQuery, $param); + if (!$AEstmt) { + print_r(sqlsrv_errors()); + die("Preparing AE statement for comparison failed! Comparison $comparison, type $type\n"); + } + + $nonAEstmt = sqlsrv_prepare($conn, $nonAEQuery, $param); + if (!$nonAEstmt) { + print_r(sqlsrv_errors()); + die("Preparing non-AE statement for comparison failed! Comparison $comparison, type $type\n"); + } + + compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $comparison, $type); + + unset($AEstmt); + unset($nonAEstmt); + } + } +} + +// testPatternMatch selects based on a pattern in the WHERE clause and compares +// the results between encrypted and non-encrypted columns, checking that the +// results are identical +// Arguments: +// resource $conn: The connection +// string $tableName: Table name +// array $patterns: Strings to pattern match, from AE_v2_values.inc +// array $dataTypes: Data types from AE_v2_values.inc +// array $colNames: Column names +// string $key: Name of the encryption key +// string $encryptionType: Type of encryption, randomized or deterministic +// string $attestation: Type of attestation - 'correct', 'enabled', 'disabled', or 'wrongurl' +// bool $isEncrypted: Whether the table is encrypted +function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation, $isEncrypted) +{ + foreach ($dataTypes as $type) { + + // TODO: Pattern matching doesn't work in AE for non-string types. + // This is for security reasons, follow up on it. + if (!dataTypeIsStringMax($type)) { + continue; + } + + foreach ($patterns[$type] as $pattern) { + $patternarray = array($pattern, + $pattern."%", + "%".$pattern, + "%".$pattern."%", + ); + + foreach ($patternarray as $spattern) { + + // Unicode operations with AE require the PHPTYPE to be specified as + // UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE + // clause is left out, we get different results between the + // encrypted and non-encrypted columns (probably because the + // collation was only changed in the encryption query). + // We must pass the length of the pattern matching string + // to the SQLTYPE instead of the field size, as we usually would, + // because otherwise we would get an empty result set. + // We need iconv_strlen to return the number of characters + // for unicode strings, since strlen returns the number of bytes. + $unicode = dataTypeIsUnicode($type); + $slength = $unicode ? iconv_strlen($spattern) : strlen($spattern); + $collate = $unicode ? " COLLATE Latin1_General_BIN2" : ""; + $phptype = $unicode ? SQLSRV_PHPTYPE_STRING('UTF-8') : null; + $sqltype = $unicode ? SQLSRV_SQLTYPE_NCHAR($slength) : SQLSRV_SQLTYPE_CHAR($slength); + + $param = array(array($spattern, SQLSRV_PARAM_IN, $phptype, $sqltype)); + $AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE LIKE ?".$collate; + $nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." LIKE ?".$collate; + + // TODO: Add binary type support below. May need to use unset() + // as in insertValues(). + $AEstmt = sqlsrv_prepare($conn, $AEQuery, $param); + if (!$AEstmt) { + print_r(sqlsrv_errors()); + die("Preparing AE statement for comparison failed! Comparison $comparison, type $type\n"); + } + + $nonAEstmt = sqlsrv_prepare($conn, $nonAEQuery, $param); + if (!$nonAEstmt) { + print_r(sqlsrv_errors()); + die("Preparing non-AE statement for comparison failed! Comparison $comparison, type $type\n"); + } + + compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $pattern, $type); + + unset($AEstmt); + unset($nonAEstmt); + } + } + } +} + +// Check that the expected errors ($codes) is found in the output of sqlsrv_errors() ($errors) +function checkErrors($errors, ...$codes) +{ + $codeFound = false; + + foreach ($codes as $code) { + if ($code[0]==$errors[0][0] and $code[1]==$errors[0][1]) { + $codeFound = true; + break; + } + } + + if ($codeFound == false) { + echo "Error: "; + print_r($errors); + echo "\nExpected: "; + print_r($codes); + echo "\n"; + die("Error code not found.\n"); + } +} + +function isEnclaveEnabled($key) +{ + return (strpos($key, '-enclave') !== false); +} + +function dataTypeIsString($dataType) +{ + return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar"])); +} + +function dataTypeIsStringMax($dataType) +{ + return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"])); +} + +function dataTypeNeedsCollate($dataType) +{ + return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"])); +} + +function dataTypeIsUnicode($dataType) +{ + return (in_array($dataType, ["nchar", "nvarchar", "nvarchar(max)"])); +} + +function dataTypeIsBinary($dataType) +{ + return (in_array($dataType, ["binary", "varbinary", "varbinary(max)"])); +} + +function getPHPType($type) +{ + switch($type) { + case "bigint": + case "integer": + case "smallint": + case "tinyint": + case "bit": + return SQLSRV_PHPTYPE_INT; + break; + case "real": + case "float": + case "double": + return SQLSRV_PHPTYPE_FLOAT; + break; + case "numeric": + case "money": + case "smallmoney": + case "time": + case "date": + case "datetime": + case "datetime2": + case "datetimeoffset": + case "smalldatetime": + case "xml": + case "uniqueidentifier": + case "char": + case "varchar": + case "varchar(max)": + return SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR); + break; + case "nchar": + case "nvarchar": + case "nvarchar(max)": + return SQLSRV_PHPTYPE_STRING('UTF-8'); + break; + case "binary": + case "varbinary": + case "varbinary(max)": + return SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY); + break; + default: + die("Case is missing for $type type in GetPHPType.\n"); + } +} + +function getSQLType($type, $length) +{ + switch($type) { + case "bigint": + return SQLSRV_SQLTYPE_BIGINT; + case "integer": + return SQLSRV_SQLTYPE_INT; + case "smallint": + return SQLSRV_SQLTYPE_SMALLINT; + case "tinyint": + return SQLSRV_SQLTYPE_TINYINT; + case "bit": + return SQLSRV_SQLTYPE_BIT; + case "real": + return SQLSRV_SQLTYPE_REAL; + case "float": + case "double": + return SQLSRV_SQLTYPE_FLOAT; + case "numeric": + return SQLSRV_SQLTYPE_NUMERIC(18,0); + case "time": + return SQLSRV_SQLTYPE_TIME; + case "date": + return SQLSRV_SQLTYPE_DATE; + case "datetime": + return SQLSRV_SQLTYPE_DATETIME; + case "datetime2": + return SQLSRV_SQLTYPE_DATETIME2; + case "datetimeoffset": + return SQLSRV_SQLTYPE_DATETIMEOFFSET; + case "smalldatetime": + return SQLSRV_SQLTYPE_SMALLDATETIME; + case "money": + return SQLSRV_SQLTYPE_MONEY; + case "smallmoney": + return SQLSRV_SQLTYPE_SMALLMONEY; + case "xml": + return SQLSRV_SQLTYPE_XML; + case "uniqueidentifier": + return SQLSRV_SQLTYPE_UNIQUEIDENTIFIER; + case "char": + return SQLSRV_SQLTYPE_CHAR($length); + case "varchar": + return SQLSRV_SQLTYPE_VARCHAR($length); + case "varchar(max)": + return SQLSRV_SQLTYPE_VARCHAR('max'); + case "nchar": + return SQLSRV_SQLTYPE_NCHAR($length); + case "nvarchar": + return SQLSRV_SQLTYPE_NVARCHAR($length); + case "nvarchar(max)": + return SQLSRV_SQLTYPE_NVARCHAR('max'); + case "binary": + return SQLSRV_SQLTYPE_BINARY($length); + break; + case "varbinary": + return SQLSRV_SQLTYPE_VARBINARY($length); + break; + case "varbinary(max)": + return SQLSRV_SQLTYPE_VARBINARY('max'); + break; + default: + die("Case is missing for $type type in getSQLType.\n"); + } +} + +?> diff --git a/test/extended/sqlsrv_aev2_plaintext_nonstring.phpt b/test/extended/sqlsrv_aev2_plaintext_nonstring.phpt new file mode 100644 index 000000000..a4f520f9b --- /dev/null +++ b/test/extended/sqlsrv_aev2_plaintext_nonstring.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test rich computations and in-place encryption of plaintext with AE v2. +--DESCRIPTION-- +This test cycles through $ceValues, $encryptionTypes, and $keys, creating a +plaintext table each time, then trying to encrypt it with different combinations +of enclave-enabled and non-enclave keys and encryption types. It then reconnects +and cycles through $targetCeValues, $targetTypes and $targetKeys to try re-encrypting +the table with different target combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Create a table in plaintext with two columns for each AE-supported data type. +2. Insert some data in plaintext. +3. Encrypt one column for each data type. +4. Perform rich computations on each AE-enabled column (comparisons and pattern matching) + and compare the result to the same query on the corresponding non-AE column for each data type. +5. Ensure the two results are the same. +6. Disconnect and reconnect with a new value for ColumnEncryption. +7. Compare computations as in 4. above. +8. Re-encrypt the table using a new key and/or encryption type. +9. Compare computations as in 4. above. +This test only tests nonstring types, because if we try to tests all types at +once, eventually a CE405 error is returned. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done. diff --git a/test/extended/sqlsrv_aev2_plaintext_string.phpt b/test/extended/sqlsrv_aev2_plaintext_string.phpt new file mode 100644 index 000000000..b258e534f --- /dev/null +++ b/test/extended/sqlsrv_aev2_plaintext_string.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test rich computations and in-place encryption of plaintext with AE v2. +--DESCRIPTION-- +This test cycles through $ceValues, $encryptionTypes, and $keys, creating a +plaintext table each time, then trying to encrypt it with different combinations +of enclave-enabled and non-enclave keys and encryption types. It then reconnects +and cycles through $targetCeValues, $targetTypes and $targetKeys to try re-encrypting +the table with different target combinations of enclave-enabled and non-enclave keys +and encryption types. +The sequence of operations is the following: +1. Create a table in plaintext with two columns for each AE-supported data type. +2. Insert some data in plaintext. +3. Encrypt one column for each data type. +4. Perform rich computations on each AE-enabled column (comparisons and pattern matching) + and compare the result to the same query on the corresponding non-AE column for each data type. +5. Ensure the two results are the same. +6. Disconnect and reconnect with a new value for ColumnEncryption. +7. Compare computations as in 4. above. +8. Re-encrypt the table using a new key and/or encryption type. +9. Compare computations as in 4. above. +This test only tests string types, because if we try to tests all types at +once, eventually a CE405 error is returned. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done. diff --git a/test/extended/sqlsrv_aev2_reencrypt_encrypted_nonstring.phpt b/test/extended/sqlsrv_aev2_reencrypt_encrypted_nonstring.phpt new file mode 100644 index 000000000..5574b4251 --- /dev/null +++ b/test/extended/sqlsrv_aev2_reencrypt_encrypted_nonstring.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test rich computations and in place re-encryption with AE v2. +--DESCRIPTION-- +This test cycles through $ceValues, $encryptionTypes, and $keys, creating +an encrypted table each time, then cycles through $targetCeValues, $targetTypes, +and $targetKeys to try re-encrypting the table with different combinations of +enclave-enabled and non-enclave keys and encryption types. +The sequence of operations is the following: +1. Create an encrypted table with two columns for each AE-supported data type, + one encrypted and one not encrypted. +2. Insert some data. +3. Perform rich computations on each AE-enabled column (comparisons and pattern matching) + and compare the result to the same query on the corresponding non-AE column for each data type. +4. Ensure the two results are the same. +5. Disconnect and reconnect with a new value for ColumnEncryption. +6. Compare computations as in 3. above. +7. Re-encrypt the table using a new key and/or encryption type. +8. Compare computations as in 3. above. +This test only tests nonstring types, because if we try to tests all types at +once, eventually a CE405 error is returned. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done. diff --git a/test/extended/sqlsrv_aev2_reencrypt_encrypted_string.phpt b/test/extended/sqlsrv_aev2_reencrypt_encrypted_string.phpt new file mode 100644 index 000000000..6d2635eb1 --- /dev/null +++ b/test/extended/sqlsrv_aev2_reencrypt_encrypted_string.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test rich computations and in place re-encryption with AE v2. +--DESCRIPTION-- +This test cycles through $ceValues, $encryptionTypes, and $keys, creating +an encrypted table each time, then cycles through $targetCeValues, $targetTypes, +and $targetKeys to try re-encrypting the table with different combinations of +enclave-enabled and non-enclave keys and encryption types. +The sequence of operations is the following: +1. Create an encrypted table with two columns for each AE-supported data type, + one encrypted and one not encrypted. +2. Insert some data. +3. Perform rich computations on each AE-enabled column (comparisons and pattern matching) + and compare the result to the same query on the corresponding non-AE column for each data type. +4. Ensure the two results are the same. +5. Disconnect and reconnect with a new value for ColumnEncryption. +6. Compare computations as in 3. above. +7. Re-encrypt the table using a new key and/or encryption type. +8. Compare computations as in 3. above. +This test only tests string types, because if we try to tests all types at +once, eventually a CE405 error is returned. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done. diff --git a/test/functional/setup/setup_dbs.py b/test/functional/setup/setup_dbs.py index ead94a30d..698441691 100644 --- a/test/functional/setup/setup_dbs.py +++ b/test/functional/setup/setup_dbs.py @@ -42,6 +42,8 @@ def setupAE(conn_options, dbname): parser.add_argument('-dbname', '--DBNAME', required=True) parser.add_argument('-azure', '--AZURE', required=False, default='no') args = parser.parse_args() + + print("Start\n") try: server = os.environ['TEST_PHP_SQL_SERVER'] @@ -61,10 +63,13 @@ def setupAE(conn_options, dbname): if (args.AZURE.lower() == 'no'): manageTestDB('create_db.sql', conn_options, args.DBNAME) + print("About to set up databases...\n") # create tables in the new database setupTestDatabase(conn_options, args.DBNAME, args.AZURE) + print("About to populate tables...\n") # populate these tables populateTables(conn_options, args.DBNAME) + print("About to set up encryption...\n") # setup AE (certificate, column master key and column encryption key) setupAE(conn_options, args.DBNAME) From e90fd2c4a402ceeb7211d517735e11fb0096795f Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 5 Feb 2020 14:20:21 -0800 Subject: [PATCH 194/249] Added codecov yml file (#1090) --- README.md | 2 +- codecov.yml | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 codecov.yml diff --git a/README.md b/README.md index 41feaf15c..94953e96e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Azure Pipelines | AppVeyor (Windows) | Travis CI (Linux) | Co [az-image]: https://dev.azure.com/sqlclientdrivers-ci/msphpsql/_apis/build/status/Microsoft.msphpsql?branchName=dev [Coverage Coveralls]: https://coveralls.io/repos/github/microsoft/msphpsql/badge.svg?branch=dev [coveralls-site]: https://coveralls.io/github/microsoft/msphpsql?branch=dev -[Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/master/graph/badge.svg +[Coverage Codecov]: https://codecov.io/gh/microsoft/msphpsql/branch/dev/graph/badge.svg [codecov-site]: https://codecov.io/gh/microsoft/msphpsql ## Get Started diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..3ec06c1c4 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,21 @@ +codecov: + require_ci_to_pass: yes + max_report_age: off + +coverage: + precision: 2 + round: down + range: "70...100" + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "reach,diff,flags,tree" + behavior: default + require_changes: no From 5455b4dcb8a621fada62289de122db6091da028e Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Thu, 6 Feb 2020 07:42:20 -0800 Subject: [PATCH 195/249] Conn res fix (#1091) --- test/functional/pdo_sqlsrv/break_pdo.php | 22 ++++--- test/functional/sqlsrv/break.php | 82 ++++++++++++------------ 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/test/functional/pdo_sqlsrv/break_pdo.php b/test/functional/pdo_sqlsrv/break_pdo.php index c5287a331..0bddb36b5 100644 --- a/test/functional/pdo_sqlsrv/break_pdo.php +++ b/test/functional/pdo_sqlsrv/break_pdo.php @@ -22,24 +22,24 @@ function generateTables($server, $uid, $pwd, $dbName, $tableName1, $tableName2) $stmt = $conn->query($sql); // Insert data - $sql = "INSERT INTO $tableName1 VALUES ( ?, ? )"; + $sql = "INSERT INTO $tableName1 VALUES (?, ?)"; for ($t = 100; $t < 116; $t++) { $stmt = $conn->prepare($sql); $ts = substr(sha1($t), 0, 5); - $params = array( $t,$ts ); + $params = array($t, $ts); $stmt->execute($params); } // Create table - $sql = "CREATE TABLE $tableName2 ( c1 INT, c2 VARCHAR(40) )"; + $sql = "CREATE TABLE $tableName2 (c1 INT, c2 VARCHAR(40))"; $stmt = $conn->query($sql); // Insert data - $sql = "INSERT INTO $tableName2 VALUES ( ?, ? )"; + $sql = "INSERT INTO $tableName2 VALUES (?, ?)"; for ($t = 200; $t < 209; $t++) { $stmt = $conn->prepare($sql); $ts = substr(sha1($t), 0, 5); - $params = array( $t,$ts ); + $params = array($t, $ts); $stmt->execute($params); } @@ -52,8 +52,12 @@ function generateTables($server, $uid, $pwd, $dbName, $tableName1, $tableName2) // Break connection by getting the session ID and killing it. // Note that breaking a connection and testing reconnection requires a // TCP/IP protocol connection (as opposed to a Shared Memory protocol). +// Wait one second before and after breaking to ensure the break occurs +// in the correct order, otherwise there may be timing issues in Linux +// that can cause tests to fail intermittently and unpredictably. function breakConnection($conn, $conn_break) { + sleep(1); $stmt1 = $conn->query("SELECT @@SPID"); $obj = $stmt1->fetch(PDO::FETCH_NUM); $spid = $obj[0]; @@ -69,11 +73,11 @@ function dropTables($server, $uid, $pwd, $tableName1, $tableName2) $conn = new PDO("sqlsrv:server = $server ; Database = $dbName ;", $uid, $pwd); - $query="IF OBJECT_ID('$tableName1', 'U') IS NOT NULL DROP TABLE $tableName1"; - $stmt=$conn->query($query); + $query = "IF OBJECT_ID('$tableName1', 'U') IS NOT NULL DROP TABLE $tableName1"; + $stmt = $conn->query($query); - $query="IF OBJECT_ID('$tableName2', 'U') IS NOT NULL DROP TABLE $tableName2"; - $stmt=$conn->query($query); + $query = "IF OBJECT_ID('$tableName2', 'U') IS NOT NULL DROP TABLE $tableName2"; + $stmt = $conn->query($query); } dropTables($server, $uid, $pwd, $tableName1, $tableName2); diff --git a/test/functional/sqlsrv/break.php b/test/functional/sqlsrv/break.php index 97abcccf5..b7bdc43bd 100644 --- a/test/functional/sqlsrv/break.php +++ b/test/functional/sqlsrv/break.php @@ -12,79 +12,79 @@ // Using generated tables will eventually allow us to put the // connection resiliency tests on Github, since the integrated testing // from AppVeyor does not have AdventureWorks. -function GenerateTables( $server, $uid, $pwd, $dbName, $tableName1, $tableName2 ) +function GenerateTables($server, $uid, $pwd, $dbName, $tableName1, $tableName2) { - $connectionInfo = array( "Database"=>$dbName, "uid"=>$uid, "pwd"=>$pwd ); + $connectionInfo = array("Database"=>$dbName, "uid"=>$uid, "pwd"=>$pwd); - $conn = sqlsrv_connect( $server, $connectionInfo ); - if ( $conn === false ) - { - die ( print_r( sqlsrv_errors() ) ); + $conn = sqlsrv_connect($server, $connectionInfo); + if ($conn === false) { + die (print_r(sqlsrv_errors())); } // Create table - $sql = "CREATE TABLE $tableName1 ( c1 INT, c2 VARCHAR(40) )"; - $stmt = sqlsrv_query( $conn, $sql ); + $sql = "CREATE TABLE $tableName1 (c1 INT, c2 VARCHAR(40))"; + $stmt = sqlsrv_query($conn, $sql); // Insert data - $sql = "INSERT INTO $tableName1 VALUES ( ?, ? )"; - for( $t = 100; $t < 116; $t++ ) - { - $ts = substr( sha1( $t ),0,5 ); - $params = array( $t,$ts ); - $stmt = sqlsrv_prepare( $conn, $sql, $params ); - sqlsrv_execute( $stmt ); + $sql = "INSERT INTO $tableName1 VALUES (?, ?)"; + for ($t = 100; $t < 116; $t++) { + $ts = substr(sha1($t), 0, 5); + $params = array($t, $ts); + $stmt = sqlsrv_prepare($conn, $sql, $params); + sqlsrv_execute($stmt); } // Create table - $sql = "CREATE TABLE $tableName2 ( c1 INT, c2 VARCHAR(40) )"; - $stmt = sqlsrv_query( $conn, $sql ); + $sql = "CREATE TABLE $tableName2 (c1 INT, c2 VARCHAR(40))"; + $stmt = sqlsrv_query($conn, $sql); // Insert data - $sql = "INSERT INTO $tableName2 VALUES ( ?, ? )"; - for( $t = 200; $t < 209; $t++ ) - { - $ts = substr( sha1( $t ),0,5 ); - $params = array( $t,$ts ); - $stmt = sqlsrv_prepare( $conn, $sql, $params ); - sqlsrv_execute( $stmt ); + $sql = "INSERT INTO $tableName2 VALUES (?, ?)"; + for ($t = 200; $t < 209; $t++) { + $ts = substr(sha1($t), 0, 5); + $params = array($t, $ts); + $stmt = sqlsrv_prepare($conn, $sql, $params); + sqlsrv_execute($stmt); } - sqlsrv_close( $conn ); + sqlsrv_close($conn); } // Break connection by getting the session ID and killing it. // Note that breaking a connection and testing reconnection requires a // TCP/IP protocol connection (as opposed to a Shared Memory protocol). -function BreakConnection( $conn, $conn_break ) +// Wait one second before and after breaking to ensure the break occurs +// in the correct order, otherwise there may be timing issues in Linux +// that can cause tests to fail intermittently and unpredictably. +function BreakConnection($conn, $conn_break) { - $stmt1 = sqlsrv_query( $conn, "SELECT @@SPID" ); - if ( sqlsrv_fetch( $stmt1 ) ) - { - $spid=sqlsrv_get_field( $stmt1, 0 ); + sleep(1); + $stmt1 = sqlsrv_query($conn, "SELECT @@SPID"); + if (sqlsrv_fetch($stmt1)) { + $spid=sqlsrv_get_field($stmt1, 0); } - $stmt2 = sqlsrv_prepare( $conn_break, "KILL ".$spid ); - sqlsrv_execute( $stmt2 ); + $stmt2 = sqlsrv_prepare($conn_break, "KILL ".$spid); + sqlsrv_execute($stmt2); sleep(1); } // Remove the tables generated by GenerateTables -function DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ) +function DropTables($server, $uid, $pwd, $tableName1, $tableName2) { global $dbName; - $connectionInfo = array( "Database"=>$dbName, "UID"=>$uid, "PWD"=>$pwd ); - $conn = sqlsrv_connect( $server, $connectionInfo ); + $connectionInfo = array("Database"=>$dbName, "UID"=>$uid, "PWD"=>$pwd); + $conn = sqlsrv_connect($server, $connectionInfo); - $query="IF OBJECT_ID('$tableName1', 'U') IS NOT NULL DROP TABLE $tableName1"; - $stmt=sqlsrv_query( $conn, $query ); + $query = "IF OBJECT_ID('$tableName1', 'U') IS NOT NULL DROP TABLE $tableName1"; + $stmt = sqlsrv_query($conn, $query); - $query="IF OBJECT_ID('$tableName2', 'U') IS NOT NULL DROP TABLE $tableName2"; - $stmt=sqlsrv_query( $conn, $query ); + $query = "IF OBJECT_ID('$tableName2', 'U') IS NOT NULL DROP TABLE $tableName2"; + $stmt = sqlsrv_query($conn, $query); } -DropTables( $server, $uid, $pwd, $tableName1, $tableName2 ); -GenerateTables( $server, $uid, $pwd, $dbName, $tableName1, $tableName2 ); +DropTables($server, $uid, $pwd, $tableName1, $tableName2); +GenerateTables($server, $uid, $pwd, $dbName, $tableName1, $tableName2); ?> From 9534f7b96dd6d3cd5f9d387498b1b425e0d81213 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 11 Feb 2020 11:39:31 -0800 Subject: [PATCH 196/249] Made some tests more robusts across platforms (#1092) --- .../pdo_sqlsrv/pdo_ae_insert_numeric.phpt | 45 ++++++++++--------- .../pdo_prepare_emulatePrepare_decimal.phpt | 10 ++--- .../pdo_prepare_emulatePrepare_float.phpt | 10 ++--- .../pdo_prepare_emulatePrepare_money.phpt | 10 ++--- .../pdo_sqlsrv/pdo_test_non_LOB_types.phpt | 34 +++++++------- .../pdo_sqlsrv/pdostatement_fetchAll.phpt | 10 ++--- .../pdo_sqlsrv/pdostatement_nextRowset.phpt | 6 +-- test/functional/sqlsrv/53_0021.phpt | 33 ++++++++++---- test/functional/sqlsrv/MsCommon.inc | 19 +++++--- test/functional/sqlsrv/MsSetup.inc | 1 + test/functional/sqlsrv/sqlsrv_get_field.phpt | 4 +- .../sqlsrv/srv_007_login_timeout.phpt | 11 +++-- ...srv_230_sqlsrv_buffered_numeric_types.phpt | 41 +++++++++-------- 13 files changed, 134 insertions(+), 100 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt index 7d5627a22..60273f342 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt @@ -9,6 +9,9 @@ No PDO::PARAM_ tpe specified when binding parameters require_once("MsCommon_mid-refactor.inc"); require_once("AEData.inc"); $dataTypes = array("bit", "tinyint", "smallint", "int", "bigint", "decimal(18,5)", "numeric(10,5)", "float", "real"); + +// Note the size of a float is platform dependent, with a precision of roughly 14 digits +// http://php.net/manual/en/language.types.float.php try { $conn = connect(); foreach ($dataTypes as $dataType) { @@ -26,7 +29,7 @@ try { if ($r === false) { isIncompatibleTypesError($stmt, $dataType, "default type"); } else { - echo "****Encrypted default type is compatible with encrypted $dataType****\n"; + echo "-----Encrypted default type is compatible with encrypted $dataType-----\n"; fetchAll($conn, $tbname); } dropTable($conn, $tbname); @@ -37,49 +40,49 @@ try { echo $e->getMessage(); } ?> ---EXPECT-- +--EXPECTREGEX-- Testing bit: -****Encrypted default type is compatible with encrypted bit**** +-----Encrypted default type is compatible with encrypted bit----- c_det: 1 c_rand: 0 Testing tinyint: -****Encrypted default type is compatible with encrypted tinyint**** +-----Encrypted default type is compatible with encrypted tinyint----- c_det: 0 c_rand: 255 Testing smallint: -****Encrypted default type is compatible with encrypted smallint**** +-----Encrypted default type is compatible with encrypted smallint----- c_det: -32767 c_rand: 32767 Testing int: -****Encrypted default type is compatible with encrypted int**** +-----Encrypted default type is compatible with encrypted int----- c_det: -2147483647 c_rand: 2147483647 Testing bigint: -****Encrypted default type is compatible with encrypted bigint**** +-----Encrypted default type is compatible with encrypted bigint----- c_det: -922337203685479936 c_rand: 922337203685479936 -Testing decimal(18,5): -****Encrypted default type is compatible with encrypted decimal(18,5)**** -c_det: -9223372036854.80000 -c_rand: 9223372036854.80000 +Testing decimal\(18,5\): +-----Encrypted default type is compatible with encrypted decimal\(18,5\)----- +c_det: -9223372036854\.80000 +c_rand: 9223372036854\.80000 -Testing numeric(10,5): -****Encrypted default type is compatible with encrypted numeric(10,5)**** -c_det: -21474.83647 -c_rand: 21474.83647 +Testing numeric\(10,5\): +-----Encrypted default type is compatible with encrypted numeric\(10,5\)----- +c_det: -21474\.83647 +c_rand: 21474\.83647 Testing float: -****Encrypted default type is compatible with encrypted float**** -c_det: -9223372036.8547993 -c_rand: 9223372036.8547993 +-----Encrypted default type is compatible with encrypted float----- +c_det: (-9223372036\.8547993|-9223372036\.8547992) +c_rand: (9223372036\.8547993|9223372036\.8547992) Testing real: -****Encrypted default type is compatible with encrypted real**** -c_det: -2147.4829 -c_rand: 2147.4829 +-----Encrypted default type is compatible with encrypted real----- +c_det: (-2147\.4829|-2147\.483) +c_rand: (2147\.4829|2147\.483) diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_decimal.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_decimal.phpt index 93899043f..d433b911c 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_decimal.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_decimal.phpt @@ -80,34 +80,34 @@ try { } ?> ---EXPECT-- +--EXPECTF-- Prepare without emulate prepare: Array ( [c1_decimal] => 422 [c2_money] => 132.2220 - [c3_float] => 622.22000000000003 + [c3_float] => 622.22%S ) Prepare with emulate prepare and no bind param options: Array ( [c1_decimal] => 422 [c2_money] => 132.2220 - [c3_float] => 622.22000000000003 + [c3_float] => 622.22%S ) Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM: Array ( [c1_decimal] => 422 [c2_money] => 132.2220 - [c3_float] => 622.22000000000003 + [c3_float] => 622.22%S ) Prepare with emulate prepare and SQLSRV_ENCODING_UTF8: Array ( [c1_decimal] => 422 [c2_money] => 132.2220 - [c3_float] => 622.22000000000003 + [c3_float] => 622.22%S ) Prepare with emulate prepare and SQLSRV_ENCODING_BINARY: No results for this query diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_float.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_float.phpt index eea439025..dee4ff599 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_float.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_float.phpt @@ -79,34 +79,34 @@ try { } ?> ---EXPECT-- +--EXPECTF-- Prepare without emulate prepare: Array ( [c1_decimal] => 411 [c2_money] => 131.1100 - [c3_float] => 611.11099999999999 + [c3_float] => 611.1109999999999%d ) Prepare with emulate prepare and no bind param options: Array ( [c1_decimal] => 411 [c2_money] => 131.1100 - [c3_float] => 611.11099999999999 + [c3_float] => 611.1109999999999%d ) Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM: Array ( [c1_decimal] => 411 [c2_money] => 131.1100 - [c3_float] => 611.11099999999999 + [c3_float] => 611.1109999999999%d ) Prepare with emulate prepare and SQLSRV_ENCODING_UTF8: Array ( [c1_decimal] => 411 [c2_money] => 131.1100 - [c3_float] => 611.11099999999999 + [c3_float] => 611.1109999999999%d ) Prepare with emulate prepare and SQLSRV_ENCODING_BINARY: No results for this query diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_money.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_money.phpt index ee4b3699b..ae581204a 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_money.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_money.phpt @@ -80,34 +80,34 @@ try { } ?> ---EXPECT-- +--EXPECTF-- Prepare without emulate prepare: Array ( [c1_decimal] => 433 [c2_money] => 133.3333 - [c3_float] => 633.33333000000005 + [c3_float] => 633.3333300000000%d ) Prepare with emulate prepare and no bind param options: Array ( [c1_decimal] => 433 [c2_money] => 133.3333 - [c3_float] => 633.33333000000005 + [c3_float] => 633.3333300000000%d ) Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM: Array ( [c1_decimal] => 433 [c2_money] => 133.3333 - [c3_float] => 633.33333000000005 + [c3_float] => 633.3333300000000%d ) Prepare with emulate prepare and SQLSRV_ENCODING_UTF8: Array ( [c1_decimal] => 433 [c2_money] => 133.3333 - [c3_float] => 633.33333000000005 + [c3_float] => 633.3333300000000%d ) Prepare with emulate prepare and SQLSRV_ENCODING_BINARY: No results for this query diff --git a/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt b/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt index cd709eb1d..378662358 100644 --- a/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt +++ b/test/functional/pdo_sqlsrv/pdo_test_non_LOB_types.phpt @@ -47,6 +47,10 @@ try { verifyResult($result); // test not streamable types + // The size of a float is platform dependent, with a precision of roughly 14 digits + // http://php.net/manual/en/language.types.float.php + // For example, the input value for column [real_type] in setup\test_types.sql is 1.18E-38 + // but in some distros the fetched value is 1.1799999E-38 $tsql = "SELECT * FROM [test_types]"; $stmt = $conn->query($tsql); $result = $stmt->fetch(PDO::FETCH_NUM); @@ -60,19 +64,19 @@ unset($stmt); unset($conn); ?> ---EXPECT-- +--EXPECTREGEX-- Array -( - [0] => 9223372036854775807 - [1] => 2147483647 - [2] => 32767 - [3] => 255 - [4] => 1 - [5] => 9999999999999999999999999999999999999 - [6] => 922337203685477.5807 - [7] => 214748.3647 - [8] => 1.79E+308 - [9] => 1.1799999E-38 - [10] => 1968-12-12 16:20:00.000 - [11] => -) \ No newline at end of file +\( + \[0\] => 9223372036854775807 + \[1\] => 2147483647 + \[2\] => 32767 + \[3\] => 255 + \[4\] => 1 + \[5\] => 9999999999999999999999999999999999999 + \[6\] => 922337203685477\.5807 + \[7\] => 214748\.3647 + \[8\] => 1\.79E\+308 + \[9\] => (1\.18E-38|1\.1799999E-38) + \[10\] => 1968-12-12 16:20:00.000 + \[11\] => +\) \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt index 5f1a4f434..6a30232c8 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt @@ -180,9 +180,9 @@ array(2) { [5]=> string(10) "STRINGCOL2" ["FloatCol"]=> - string(18) "222.22200000000001" + string(%d) "222.222%S" [6]=> - string(18) "222.22200000000001" + string(%d) "222.222%S" ["XmlCol"]=> string(431) " 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." [7]=> @@ -395,7 +395,7 @@ object(stdClass)#%x (%x) { ["NVarCharCol"]=> string(10) "STRINGCOL2" ["FloatCol"]=> - string(18) "222.22200000000001" + string(%d) "222.222%S" ["XmlCol"]=> string(431) " 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } @@ -414,7 +414,7 @@ array(8) { [5]=> string(10) "STRINGCOL2" [6]=> - string(18) "222.22200000000001" + string(%d) "222.222%S" [7]=> string(431) " 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } @@ -425,7 +425,7 @@ string(10) "STRINGCOL2" string(23) "2000-11-11 11:11:11.223" string(10) "STRINGCOL2" string(10) "STRINGCOL2" -string(18) "222.22200000000001" +string(%d) "222.222%S" string(431) " 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." Test_9 : FETCH_INVALID : diff --git a/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt b/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt index f235ed94b..fa63c225c 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt @@ -31,7 +31,7 @@ try { var_dump($e); } ?> ---EXPECT-- +--EXPECTF-- array(1) { [0]=> array(62) { @@ -224,9 +224,9 @@ array(2) { [5]=> string(10) "STRINGCOL2" ["FloatCol"]=> - string(18) "222.22200000000001" + string(%d) "222.222%S" [6]=> - string(18) "222.22200000000001" + string(%d) "222.222%S" ["XmlCol"]=> string(431) " 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." [7]=> diff --git a/test/functional/sqlsrv/53_0021.phpt b/test/functional/sqlsrv/53_0021.phpt index 8d010299d..c9bbf2016 100644 --- a/test/functional/sqlsrv/53_0021.phpt +++ b/test/functional/sqlsrv/53_0021.phpt @@ -12,7 +12,12 @@ Test for integer, float, and datetime types vs various sql server types. require( 'MsCommon.inc' ); -function get_fields( $stmt ) { +$epsilon = 0.00001; +$decimals = ['9999999999999999999999999999999999999', '-10000000000000000000000000000000000001', '0']; + +function get_fields( $stmt, $round ) { + + global $epsilon, $decimals; // bigint $field = sqlsrv_get_field( $stmt, 0, SQLSRV_PHPTYPE_INT ); @@ -71,7 +76,22 @@ function get_fields( $stmt ) { } else { var_dump( sqlsrv_errors( SQLSRV_ERR_WARNINGS ) ); - echo "$field\n"; + // The size of a float is platform dependent, with a precision of roughly 14 digits + // http://php.net/manual/en/language.types.float.php + // For example, in Ubuntu 18.04 or macOS Mojave the returned value is 1.0E+37 or -1.0E+37 + // but in Alpine Linux it is 9.9999999999997E+36 or -9.9999999999997E+36 + if ($decimals[$round] == '0') { + if ($field != 0) { + echo "Expected 0 but got $field\n"; + } + } else { + $expected = floatval($decimals[$round]); + $diff = abs(($field - $expected) / $expected); + + if ($diff > $epsilon) { + echo "Expected $expected but got $field -- difference is $diff\n"; + } + } } // datetime @@ -147,7 +167,7 @@ function get_fields( $stmt ) { } // maximum values - get_fields( $stmt ); + get_fields( $stmt, 0 ); $success = sqlsrv_fetch( $stmt ); if( !$success ) { @@ -156,7 +176,7 @@ function get_fields( $stmt ) { } // minimum values - get_fields( $stmt ); + get_fields( $stmt, 1 ); $success = sqlsrv_fetch( $stmt ); if( !$success ) { @@ -165,7 +185,7 @@ function get_fields( $stmt ) { } // zero values - get_fields( $stmt ); + get_fields( $stmt, 2 ); $stmt = sqlsrv_query( $conn, "SELECT int_type, decimal_type, datetime_type, real_type FROM [test_types]" ); if( !$stmt ) { @@ -258,7 +278,6 @@ NULL NULL 1 NULL -1.0E+37 NULL 12/12/1968 04:20:00 NULL @@ -296,7 +315,6 @@ NULL NULL 0 NULL --1.0E+37 NULL 12/12/1968 04:20:00 NULL @@ -319,7 +337,6 @@ NULL NULL 0 NULL -0 NULL 12/12/1968 04:20:00 NULL diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index dec27c554..195430d57 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -84,6 +84,12 @@ function isDaasMode() return ($daasMode ? true : false); } +function isLocaleDisabled() +{ + global $daasMode, $localeDisabled; + return ($daasMode || $localeDisabled); +} + function isSQLAzure() { // 'SQL Azure' indicates SQL Database or SQL Data Warehouse @@ -491,10 +497,11 @@ function handleErrors() function setUSAnsiLocale() { - // Do not run locale tests in Azure - if (isDaasMode()) { + // Do not run locale tests if locale disabled + if (isLocaleDisabled()) { return; } + if (!isWindows()) { // macOS the locale names are different in Linux or macOS $locale = strtoupper(PHP_OS) === 'LINUX' ? "en_US.ISO-8859-1" : "en_US.ISO8859-1"; @@ -505,8 +512,8 @@ function setUSAnsiLocale() function resetLocaleToDefault() { - // Do not run locale tests in Azure - if (isDaasMode()) { + // Do not run locale tests if locale disabled + if (isLocaleDisabled()) { return; } // Like setUSAnsiLocale() above, this method is only needed in non-Windows environment @@ -522,8 +529,8 @@ function isLocaleSupported() if (isWindows()) { return true; } - // Do not run locale tests in Azure - if (isDaasMode()) { + // Do not run locale tests if locale disabled + if (isLocaleDisabled()) { return false; } if (AE\isDataEncrypted()) { diff --git a/test/functional/sqlsrv/MsSetup.inc b/test/functional/sqlsrv/MsSetup.inc index 8335c13b7..f328e17c7 100644 --- a/test/functional/sqlsrv/MsSetup.inc +++ b/test/functional/sqlsrv/MsSetup.inc @@ -25,6 +25,7 @@ $daasMode = false; $marsMode = true; $traceEnabled = false; +$localeDisabled = false; $adServer = 'TARGET_AD_SERVER'; $adDatabase = 'TARGET_AD_DATABASE'; diff --git a/test/functional/sqlsrv/sqlsrv_get_field.phpt b/test/functional/sqlsrv/sqlsrv_get_field.phpt index 9247d0ba3..9d63a1f54 100644 --- a/test/functional/sqlsrv/sqlsrv_get_field.phpt +++ b/test/functional/sqlsrv/sqlsrv_get_field.phpt @@ -268,7 +268,7 @@ NULL NULL 1 NULL -1\.0E\+37 +(1\.0E\+37|9.9999999999997E\+36) NULL 12\/12\/1968 04\:20\:00 NULL @@ -306,7 +306,7 @@ NULL NULL 0 NULL -\-1\.0E\+37 +(\-1\.0E\+37|-9.9999999999997E\+36) NULL 12\/12\/1968 04\:20\:00 NULL diff --git a/test/functional/sqlsrv/srv_007_login_timeout.phpt b/test/functional/sqlsrv/srv_007_login_timeout.phpt index eda8cf3ac..8145c370f 100644 --- a/test/functional/sqlsrv/srv_007_login_timeout.phpt +++ b/test/functional/sqlsrv/srv_007_login_timeout.phpt @@ -7,21 +7,24 @@ Intentionally provide an invalid server name and set LoginTimeout. Verify the ti --FILE-- $timeout)); $numAttempts++; @@ -38,7 +41,7 @@ do { echo "Connection failed at $elapsed secs. Leeway is $leeway sec but the difference is $diff\n"; } else { // The test will fail but this helps us decide if this test should be redesigned - echo "$numAttempts\t"; + echo "Attempts: $numAttempts, Time difference: $diff\n"; sleep(5); } } diff --git a/test/functional/sqlsrv/srv_230_sqlsrv_buffered_numeric_types.phpt b/test/functional/sqlsrv/srv_230_sqlsrv_buffered_numeric_types.phpt index 99a915da4..1725d3b3c 100644 --- a/test/functional/sqlsrv/srv_230_sqlsrv_buffered_numeric_types.phpt +++ b/test/functional/sqlsrv/srv_230_sqlsrv_buffered_numeric_types.phpt @@ -67,6 +67,10 @@ var_dump($array); $array = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC); var_dump($array); +// The size of a float is platform dependent, with a precision of roughly 14 digits +// http://php.net/manual/en/language.types.float.php +$epsilon = 0.00001; + $numFields = sqlsrv_num_fields($stmt); $meta = sqlsrv_field_metadata($stmt); $rowcount = sqlsrv_num_rows($stmt); @@ -81,8 +85,21 @@ for ($i = 0; $i < $rowcount; $i++) { $field = sqlsrv_get_field($stmt, $j, SQLSRV_PHPTYPE_INT); var_dump($field); } - $field = sqlsrv_get_field($stmt, $j, SQLSRV_PHPTYPE_FLOAT); - var_dump($field); + $field1 = sqlsrv_get_field($stmt, $j, SQLSRV_PHPTYPE_FLOAT); + if ($j > 5) { + // these are the zero fields + $expected = 0.0; + if ($field1 !== $expected) { + echo "Expected $expected but got $field1\n"; + } + } else { + $expected = floatval($field); + $diff = abs(($field1 - $expected) / $expected); + + if ($diff > $epsilon) { + echo "Expected $expected but got $field1 -- difference is $diff\n"; + } + } } } @@ -136,78 +153,60 @@ array(9) { column: a string(15) "1234567890.1234" -float(1234567890.1234) column: neg_a string(16) "-1234567890.1234" -float(-1234567890.1234) column: b string(1) "1" int(1) -float(1) column: neg_b string(2) "-1" int(-1) -float(-1) column: c string(7) ".500000" -float(0.5) column: neg_c string(8) "-.550000" -float(-0.55) column: zero string(1) "0" int(0) -float(0) column: zerof string(1) "0" -float(0) column: zerod string(7) ".000000" -float(0) column: a string(3) "0.5" -float(0.5) column: neg_a string(5) "-0.55" -float(-0.55) column: b string(6) "100000" int(100000) -float(100000) column: neg_b string(8) "-1234567" int(-1234567) -float(-1234567) column: c string(17) "1234567890.123400" -float(1234567890.1234) column: neg_c string(18) "-1234567890.123400" -float(-1234567890.1234) column: zero string(1) "0" int(0) -float(0) column: zerof string(1) "0" -float(0) column: zerod -string(7) ".000000" -float(0) +string(7) ".000000" \ No newline at end of file From af3097d5cfa903fef13f8fdb94f3acaf1f3cae0d Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 19 Feb 2020 14:27:36 -0800 Subject: [PATCH 197/249] Removed the use of a conversion matrix (#1095) --- source/shared/core_results.cpp | 101 ++++--- source/shared/core_sqlsrv.h | 7 - source/shared/core_stmt.cpp | 6 +- .../pdo_sqlsrv/pdo_buffered_fetch_types.phpt | 229 +++++++++++++++ .../sqlsrv/sqlsrv_buffered_fetch_types.phpt | 278 ++++++++++++++++++ 5 files changed, 570 insertions(+), 51 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 0b9c8fd94..afa3bf56e 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -30,11 +30,6 @@ using namespace core; -// conversion matrix -// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported -// this is initialized the first time the buffered result set is created. -sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix; - namespace { // *** internal types *** @@ -454,34 +449,6 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm meta = static_cast( sqlsrv_malloc( col_count * sizeof( sqlsrv_buffered_result_set::meta_data ))); - // set up the conversion matrix if this is the first time we're called - if( conv_matrix.size() == 0 ) { - - conv_matrix[SQL_C_CHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[SQL_C_CHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::system_to_wide_string; - conv_matrix[SQL_C_CHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[SQL_C_CHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::string_to_double; - conv_matrix[SQL_C_CHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::string_to_long; - conv_matrix[SQL_C_WCHAR][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[SQL_C_WCHAR][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_binary_string; - conv_matrix[SQL_C_WCHAR][SQL_C_CHAR] = &sqlsrv_buffered_result_set::wide_to_system_string; - conv_matrix[SQL_C_WCHAR][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::wstring_to_double; - conv_matrix[SQL_C_WCHAR][SQL_C_LONG] = &sqlsrv_buffered_result_set::wstring_to_long; - conv_matrix[SQL_C_BINARY][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_same_string; - conv_matrix[SQL_C_BINARY][SQL_C_CHAR] = &sqlsrv_buffered_result_set::binary_to_system_string; - conv_matrix[SQL_C_BINARY][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::binary_to_wide_string; - conv_matrix[SQL_C_LONG][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::long_to_double; - conv_matrix[SQL_C_LONG][SQL_C_LONG] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[SQL_C_LONG][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_long; - conv_matrix[SQL_C_LONG][SQL_C_CHAR] = &sqlsrv_buffered_result_set::long_to_system_string; - conv_matrix[SQL_C_LONG][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::long_to_wide_string; - conv_matrix[SQL_C_DOUBLE][SQL_C_DOUBLE] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[SQL_C_DOUBLE][SQL_C_BINARY] = &sqlsrv_buffered_result_set::to_double; - conv_matrix[SQL_C_DOUBLE][SQL_C_CHAR] = &sqlsrv_buffered_result_set::double_to_system_string; - conv_matrix[SQL_C_DOUBLE][SQL_C_LONG] = &sqlsrv_buffered_result_set::double_to_long; - conv_matrix[SQL_C_DOUBLE][SQL_C_WCHAR] = &sqlsrv_buffered_result_set::double_to_wide_string; - } - SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() : stmt->encoding()); @@ -844,18 +811,70 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ *out_buffer_length = SQL_NULL_DATA; return SQL_SUCCESS; } - // check to make sure the conversion type is valid - conv_matrix_t::const_iterator conv_iter = conv_matrix.find( meta[field_index].c_type ); - if( conv_iter == conv_matrix.end() || conv_iter->second.find( target_type ) == conv_iter->second.end() ) { - last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) - sqlsrv_error( (SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0 ); - return SQL_ERROR; + switch (meta[field_index].c_type) { + case SQL_C_CHAR: + switch (target_type) { + case SQL_C_CHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::system_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::string_to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_LONG: return sqlsrv_buffered_result_set::string_to_long(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + case SQL_C_WCHAR: + switch (target_type) { + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_CHAR: return sqlsrv_buffered_result_set::wide_to_system_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::wstring_to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_LONG: return sqlsrv_buffered_result_set::wstring_to_long(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + case SQL_C_BINARY: + switch (target_type) { + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_same_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_CHAR: return sqlsrv_buffered_result_set::binary_to_system_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::binary_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + case SQL_C_LONG: + switch (target_type) { + case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::long_to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_LONG: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_CHAR: return sqlsrv_buffered_result_set::long_to_system_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::long_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + case SQL_C_DOUBLE: + switch (target_type) { + case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_CHAR: return sqlsrv_buffered_result_set::double_to_system_string(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_LONG: return sqlsrv_buffered_result_set::double_to_long(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_WCHAR: return sqlsrv_buffered_result_set::double_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); + default: + break; + } + break; + default: + break; } - return (( this )->*( conv_matrix[meta[field_index].c_type][target_type] ))( field_index, buffer, buffer_length, - out_buffer_length ); + // Should not have reached here, return an error + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) + sqlsrv_error((SQLCHAR*) "07006", (SQLCHAR*) "Restricted data type attribute violation", 0); + return SQL_ERROR; } SQLRETURN sqlsrv_buffered_result_set::get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index ae27400e8..d576993d9 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1746,13 +1746,6 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { sqlsrv_malloc_auto_ptr temp_string; // temp buffer to hold a converted field while in use SQLLEN temp_length; // number of bytes in the temp conversion buffer - typedef SQLRETURN (sqlsrv_buffered_result_set::*conv_fn)( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, - _Inout_ SQLLEN* out_buffer_length ); - typedef std::map< SQLINTEGER, std::map< SQLINTEGER, conv_fn > > conv_matrix_t; - - // two dimentional sparse matrix that holds the [from][to] functions that do conversions - static conv_matrix_t conv_matrix; - // string conversion functions SQLRETURN binary_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 32b10ad58..7fac8e5b3 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1799,11 +1799,11 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i case SQLSRV_PHPTYPE_INT: { - sqlsrv_malloc_auto_ptr field_value_temp; - field_value_temp = static_cast( sqlsrv_malloc( sizeof( long ))); + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( SQLLEN ))); *field_value_temp = 0; - SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ), + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( SQLLEN ), field_len, true /*handle_warning*/ TSRMLS_CC ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { diff --git a/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt new file mode 100644 index 000000000..a10bf6c38 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt @@ -0,0 +1,229 @@ +--TEST-- +Prepare with cursor buffered and fetch a variety of types converted to different types +--DESCRIPTION-- +Test various conversion functionalites for buffered queries with PDO_SQLSRV. +--SKIPIF-- + +--FILE-- +prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + + // Fetch all fields as UTF-8 strings + for ($i = 0; $i < count($inputs); $i++) { + $stmt->execute(); + $f = $stmt->fetchColumn($i); + + if ($f !== $inputs[$i]) { + var_dump($f); + } + } + } catch (PdoException $e) { + echo "Caught exception in fetchAsUTF8:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +function fetchArray($conn, $tableName, $inputs) +{ + $query = "SELECT * FROM $tableName"; + try { + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + // By default, even numeric or datetime fields are fetched as strings + $result = $stmt->fetch(PDO::FETCH_NUM); + for ($i = 0; $i < count($inputs); $i++) { + if ($result[$i] !== $inputs[$i]) { + var_dump($f); + } + } + } catch (PdoException $e) { + echo "Caught exception in fetchArray:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +function fetchBinaryAsNumber($conn, $tableName, $inputs) +{ + global $violation; + + $query = "SELECT c1 FROM $tableName"; + + try { + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED, PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE=>true)); + $stmt->execute(); + + $stmt->bindColumn('c1', $binaryValue, PDO::PARAM_INT); + $row = $stmt->fetch(PDO::FETCH_BOUND); + echo "in fetchBinaryAsNumber: exception should have been thrown!\n"; + } catch (PdoException $e) { + // The varbinary field - expect the violation error + if (strpos($e->getMessage(), $violation) === false) { + echo "in fetchBinaryAsNumber: expected '$violation' but caught this:\n"; + echo $e->getMessage() . PHP_EOL; + } + } +} + +function fetchBinaryAsBinary($conn, $tableName, $inputs) +{ + try { + $query = "SELECT c1 FROM $tableName"; + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + $stmt->bindColumn('c1', $binaryValue, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + $row = $stmt->fetch(PDO::FETCH_BOUND); + + if ($binaryValue !== $inputs[0]) { + echo "Fetched binary value unexpected: $binaryValue\n"; + } + } catch (PdoException $e) { + echo "Caught exception in fetchBinaryAsBinary:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +function fetchFloatAsInt($conn, $tableName) +{ + global $truncation; + + try { + $query = "SELECT c3 FROM $tableName"; + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + $stmt->bindColumn('c3', $floatValue, PDO::PARAM_INT); + $row = $stmt->fetch(PDO::FETCH_BOUND); + + // This should return SQL_SUCCESS_WITH_INFO with the truncation error + $info = $stmt->errorInfo(); + if ($info[0] != '01S07' || $info[2] !== $truncation) { + print_r($stmt->errorInfo()); + } + } catch (PdoException $e) { + echo "Caught exception in fetchFloatAsInt:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +function fetchCharAsInt($conn, $tableName, $column) +{ + global $outOfRange; + + try { + $query = "SELECT $column FROM $tableName"; + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + $stmt->bindColumn($column, $value, PDO::PARAM_INT); + $row = $stmt->fetch(PDO::FETCH_BOUND); + + // TODO 11297: fix this part outside Windows later + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + echo "in fetchCharAsInt: exception should have been thrown!\n"; + } else { + if ($value != 0) { + var_dump($value); + } + } + } catch (PdoException $e) { + // The (n)varchar field - expect the outOfRange error + if (strpos($e->getMessage(), $outOfRange) === false) { + echo "in fetchCharAsInt ($column): expected '$outOfRange' but caught this:\n"; + echo $e->getMessage() . PHP_EOL; + } + } +} + +function fetchAsNumerics($conn, $tableName, $inputs) +{ + // The following calls expect different errors + fetchFloatAsInt($conn, $tableName); + fetchCharAsInt($conn, $tableName, 'c6'); + fetchCharAsInt($conn, $tableName, 'c7'); + + // The following should work + try { + $query = "SELECT c2, c4 FROM $tableName"; + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->execute(); + + $stmt->bindColumn('c2', $intValue, PDO::PARAM_INT); + $stmt->bindColumn('c4', $decValue, PDO::PARAM_INT); + + $row = $stmt->fetch(PDO::FETCH_BOUND); + + if ($intValue !== intval($inputs[1])) { + var_dump($intValue); + } + if ($decValue !== intval($inputs[3])) { + var_dump($decValue); + } + } catch (PdoException $e) { + echo "Caught exception in fetchAsNumerics:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + +try { + $conn = connect(); + $conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + + $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7'); + $types = array('varbinary(10)', 'int', 'float(53)', 'decimal(16, 6)', 'datetime2', 'varchar(50)', 'nvarchar(50)'); + $inputs = array('abcdefghij', '34567', '9876.5432', '123456789.012340', '2020-02-02 20:20:20.2220000', 'This is a test', 'Şơмė śäáƒÑ€Å€á»'); + + // Create table + $colMeta = array(new ColumnMeta($types[0], $columns[0]), + new ColumnMeta($types[1], $columns[1]), + new ColumnMeta($types[2], $columns[2]), + new ColumnMeta($types[3], $columns[3]), + new ColumnMeta($types[4], $columns[4]), + new ColumnMeta($types[5], $columns[5]), + new ColumnMeta($types[6], $columns[6])); + createTable($conn, $tableName, $colMeta); + + // Prepare the input values and insert one row + $query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?, ?)"; + $stmt = $conn->prepare($query); + for ($i = 0; $i < count($columns); $i++) { + if ($i == 0) { + $stmt->bindParam($i+1, $inputs[$i], PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + } else { + $stmt->bindParam($i+1, $inputs[$i]); + } + } + $stmt->execute(); + unset($stmt); + + // Starting fetching using client buffers + fetchAsUTF8($conn, $tableName, $inputs); + fetchArray($conn, $tableName, $inputs); + fetchBinaryAsNumber($conn, $tableName, $inputs); + fetchBinaryAsBinary($conn, $tableName, $inputs); + fetchAsNumerics($conn, $tableName, $inputs); + + // dropTable($conn, $tableName); + echo "Done\n"; + unset($conn); +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt new file mode 100644 index 000000000..425d7f58f --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt @@ -0,0 +1,278 @@ +--TEST-- +Prepare with cursor buffered and fetch a variety of types converted to different types +--DESCRIPTION-- +Test various conversion functionalites for buffered queries with SQLSRV. +--SKIPIF-- + +--FILE-- +SQLSRV_CURSOR_CLIENT_BUFFERED)); + if (!$stmt) { + fatalError("In fetchAsUTF8: failed to run query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsUTF8: failed to fetch the row from $tableName!"); + } + + // Fetch all fields as UTF-8 strings + for ($i = 0; $i < count($inputs); $i++) { + $f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING('utf-8')); + if ($i == 0) { + if ($inputs[$i] !== hex2bin($f)) { + var_dump($f); + } + } else { + if ($f !== $inputs[$i]) { + var_dump($f); + } + } + } +} + +function fetchArray($conn, $tableName, $inputs) +{ + $query = "SELECT * FROM $tableName"; + + $stmt = sqlsrv_prepare($conn, $query, array(), array('Scrollable'=>SQLSRV_CURSOR_CLIENT_BUFFERED, 'ReturnDatesAsStrings' => true)); + if (!$stmt) { + fatalError("In fetchArray: failed to prepare query!"); + } + $res = sqlsrv_execute($stmt); + if (!$res) { + fatalError("In fetchArray: failed to execute query!"); + } + + // Fetch fields as an array + $results = sqlsrv_fetch_array($stmt); + if ($results === false) { + fatalError("In fetchArray: failed to fetch the row from $tableName!"); + } + + for ($i = 0; $i < count($inputs); $i++) { + if ($i == 1) { + $expected = intval($inputs[$i]); + } elseif ($i == 2) { + $expected = floatval($inputs[$i]); + } else { + $expected = $inputs[$i]; + } + + if ($results[$i] !== $expected) { + echo "in fetchArray: for column $i expected $expected but got: "; + var_dump($results[$i]); + } + } +} + +function fetchAsFloats($conn, $tableName, $inputs) +{ + global $violation, $outOfRange, $epsilon; + + $query = "SELECT * FROM $tableName"; + $stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED, 'ReturnDatesAsStrings' => true)); + if (!$stmt) { + fatalError("In fetchAsFloats: failed to run query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsFloats: failed to fetch the row from $tableName!"); + } + + // Fetch all fields as floats + for ($i = 0; $i < count($inputs); $i++) { + $f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_FLOAT); + + if ($i == 0) { + // The varbinary field - expect the violation error + if (strpos(sqlsrv_errors()[0]['message'], $violation) === false) { + var_dump($f); + fatalError("in fetchAsFloats: expected $violation for column $i\n"); + } + } elseif ($i < 5) { + $expected = floatval($inputs[$i]); + $diff = abs(($f - $expected) / $expected); + + if ($diff > $epsilon) { + echo "in fetchAsFloats: for column $i expected $expected but got: "; + var_dump($f); + } + } else { + // The char fields will get errors too + // TODO 11297: fix this part outside Windows later + if (isWindows()) { + if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { + var_dump($f); + fatalError("in fetchAsFloats: expected $outOfRange for column $i\n"); + } + } else { + if ($f != 0.0) { + var_dump($f); + } + } + } + } +} + +function fetchAsInts($conn, $tableName, $inputs) +{ + global $violation, $outOfRange, $truncation; + + $query = "SELECT * FROM $tableName"; + $stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED, 'ReturnDatesAsStrings' => true)); + if (!$stmt) { + fatalError("In fetchAsInts: failed to run query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsInts: failed to fetch the row from $tableName!"); + } + + // Fetch all fields as integers + for ($i = 0; $i < count($inputs); $i++) { + $f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_INT); + + if ($i == 0) { + // The varbinary field - expect the violation error + if (strpos(sqlsrv_errors()[0]['message'], $violation) === false) { + var_dump($f); + fatalError("in fetchAsInts: expected $violation for column $i\n"); + } + } elseif ($i == 2) { + // The float field - expect truncation + if (strpos(sqlsrv_errors()[0]['message'], $truncation) === false) { + var_dump($f); + fatalError("in fetchAsInts: expected $truncation for column $i\n"); + } + } elseif ($i >= 5) { + // The char fields will get errors too + // TODO 11297: fix this part outside Windows later + if (isWindows()) { + if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { + var_dump($f); + fatalError("in fetchAsInts: expected $outOfRange for column $i\n"); + } + } else { + if ($f != 0) { + var_dump($f); + } + } + } else { + $expected = floor($inputs[$i]); + if ($f != $expected) { + echo "in fetchAsInts: for column $i expected $expected but got: "; + var_dump($f); + } + } + } +} + +function fetchAsBinary($conn, $tableName, $inputs) +{ + $query = "SELECT c_varbinary FROM $tableName"; + + $stmt = sqlsrv_prepare($conn, $query, array(), array('Scrollable'=>SQLSRV_CURSOR_CLIENT_BUFFERED)); + if (!$stmt) { + fatalError("In fetchAsBinary: failed to prepare query!"); + } + $res = sqlsrv_execute($stmt); + if (!$res) { + fatalError("In fetchAsBinary: failed to execute query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsInts: failed to fetch the row from $tableName!"); + } + + // Fetch the varbinary field as is + $f = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STREAM("binary")); + if (gettype($f) !== 'resource') { + var_dump($f); + } + // Do not expect errors + $errs = sqlsrv_errors(); + if (!empty($errs)) { + var_dump($errs); + } + + // Check its value + while (!feof($f)) { + $str = fread($f, 80); + } + if (trim($str) !== $inputs[0]) { + echo "Fetched binary value unexpected: $str\n"; + } +} + +require_once('MsCommon.inc'); + +$conn = AE\connect(array('CharacterSet' => 'UTF-8')); +$tableName = 'srvFetchingClientBuffer'; + +// Create table +$names = array('c_varbinary', 'c_int', 'c_float', 'c_decimal', 'c_datetime2', 'c_varchar', 'c_nvarchar'); + +$columns = array(new AE\ColumnMeta('varbinary(10)', $names[0]), + new AE\ColumnMeta('int', $names[1]), + new AE\ColumnMeta('float(53)', $names[2]), + new AE\ColumnMeta('decimal(16, 6)', $names[3]), + new AE\ColumnMeta('datetime2', $names[4]), + new AE\ColumnMeta('varchar(50)', $names[5]), + new AE\ColumnMeta('nvarchar(50)', $names[6])); +$stmt = AE\createTable($conn, $tableName, $columns); +if (!$stmt) { + fatalError("Failed to create $tableName!"); +} + +// Prepare the input values +$inputs = array('abcdefghij', '34567', '9876.5432', '123456789.012340', '2020-02-02 20:20:20.2220000', 'This is a test', 'Şơмė śäáƒÑ€Å€á»'); + +$params = array(array(bin2hex($inputs[0]), SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_BINARY(10)), + $inputs[1], $inputs[2], $inputs[3], $inputs[4], $inputs[5], + array($inputs[6], SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING('utf-8'))); + +// Form the insert query +$colStr = '('; +foreach ($names as $name) { + $colStr .= $name . ", "; +} +$colStr = rtrim($colStr, ", ") . ") "; +$insertSql = "INSERT INTO [$tableName] " . $colStr . 'VALUES (?,?,?,?,?,?,?)'; + +// Insert one row only +$stmt = sqlsrv_prepare($conn, $insertSql, $params); +if ($stmt) { + $res = sqlsrv_execute($stmt); + if (!$res) { + fatalError("Failed to execute insert statement to $tableName!"); + } +} else { + fatalError("Failed to prepare insert statement to $tableName!"); +} + +// Starting fetching using client buffers +fetchAsUTF8($conn, $tableName, $inputs); +fetchArray($conn, $tableName, $inputs); +fetchAsFloats($conn, $tableName, $inputs); +fetchAsInts($conn, $tableName, $inputs); +fetchAsBinary($conn, $tableName, $inputs); + +dropTable($conn, $tableName); + +echo "Done\n"; + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +?> +--EXPECT-- +Done From cd64173f956c181e2f730d3e9b585b2bc8ccf608 Mon Sep 17 00:00:00 2001 From: David Puglielli Date: Wed, 19 Feb 2020 16:09:10 -0800 Subject: [PATCH 198/249] Xplat alpine changes (#1094) --- source/pdo_sqlsrv/config.m4 | 11 ++- source/shared/localization.hpp | 3 + source/shared/localizationimpl.cpp | 108 ++++++++++++++++++----------- source/sqlsrv/config.m4 | 20 ++++-- 4 files changed, 93 insertions(+), 49 deletions(-) diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index 92bfc4145..b2149a753 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -78,10 +78,15 @@ if test "$PHP_PDO_SQLSRV" != "no"; then HOST_OS_ARCH=`uname` if test "${HOST_OS_ARCH}" = "Darwin"; then - PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-bind_at_load" - MACOSX_DEPLOYMENT_TARGET=`sw_vers -productVersion` + PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-bind_at_load" + MACOSX_DEPLOYMENT_TARGET=`sw_vers -productVersion` else - PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-z,now" + PDO_SQLSRV_SHARED_LIBADD="$PDO_SQLSRV_SHARED_LIBADD -Wl,-z,now" + IS_ALPINE_1=`uname -a | cut -f 4 -d ' ' | cut -f 2 -d '-'` + IS_ALPINE_2=`cat /etc/os-release | grep ID | grep alpine | cut -f 2 -d '='` + if test "${IS_ALPINE_1}" = "Alpine" || test "${IS_ALPINE_2}" = "alpine"; then + AC_DEFINE(__MUSL__, 1, [ ]) + fi fi PHP_REQUIRE_CXX() diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index fae23e2fa..ec339d8c6 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -41,6 +41,9 @@ #define CP_UTF16 1200 #define CP_ACP 0 // default to ANSI code page +bool _setLocale(const char * localeName, std::locale ** pLocale); +void setDefaultLocale(const char ** localeName, std::locale ** pLocale); + // This class provides allocation policies for the SystemLocale and AutoArray classes. // This is primarily needed for the self-allocating ToUtf16/FromUtf16 methods. // SNI needs all its allocations to use its own allocator so it would create a separate diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 6a69aaa84..22cc8bf6c 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -50,38 +50,44 @@ struct cp_iconv // CodePage 2 corresponds to binary. If the attribute PDO::SQLSRV_ENCODING_BINARY // is set, GetIndex() above hits the assert(false) directive unless we include // CodePage 2 below and assign an empty string to it. +#ifdef __MUSL__ +#define TRANSLIT "" +#else +#define TRANSLIT "//TRANSLIT" +#endif + const cp_iconv cp_iconv::g_cp_iconv[] = { { 65001, "UTF-8" }, { 1200, "UTF-16LE" }, { 3, "UTF-8" }, { 2, "" }, - { 1252, "CP1252//TRANSLIT" }, - { 850, "CP850//TRANSLIT" }, - { 437, "CP437//TRANSLIT" }, - { 874, "CP874//TRANSLIT" }, - { 932, "CP932//TRANSLIT" }, - { 936, "CP936//TRANSLIT" }, - { 949, "CP949//TRANSLIT" }, - { 950, "CP950//TRANSLIT" }, - { 1250, "CP1250//TRANSLIT" }, - { 1251, "CP1251//TRANSLIT" }, - { 1253, "CP1253//TRANSLIT" }, - { 1254, "CP1254//TRANSLIT" }, - { 1255, "CP1255//TRANSLIT" }, - { 1256, "CP1256//TRANSLIT" }, - { 1257, "CP1257//TRANSLIT" }, - { 1258, "CP1258//TRANSLIT" }, - { CP_ISO8859_1, "ISO8859-1//TRANSLIT" }, - { CP_ISO8859_2, "ISO8859-2//TRANSLIT" }, - { CP_ISO8859_3, "ISO8859-3//TRANSLIT" }, - { CP_ISO8859_4, "ISO8859-4//TRANSLIT" }, - { CP_ISO8859_5, "ISO8859-5//TRANSLIT" }, - { CP_ISO8859_6, "ISO8859-6//TRANSLIT" }, - { CP_ISO8859_7, "ISO8859-7//TRANSLIT" }, - { CP_ISO8859_8, "ISO8859-8//TRANSLIT" }, - { CP_ISO8859_9, "ISO8859-9//TRANSLIT" }, - { CP_ISO8859_13, "ISO8859-13//TRANSLIT" }, - { CP_ISO8859_15, "ISO8859-15//TRANSLIT" }, + { 1252, "CP1252" TRANSLIT }, + { 850, "CP850" TRANSLIT }, + { 437, "CP437" TRANSLIT }, + { 874, "CP874" TRANSLIT }, + { 932, "CP932" TRANSLIT }, + { 936, "CP936" TRANSLIT }, + { 949, "CP949" TRANSLIT }, + { 950, "CP950" TRANSLIT }, + { 1250, "CP1250" TRANSLIT }, + { 1251, "CP1251" TRANSLIT }, + { 1253, "CP1253" TRANSLIT }, + { 1254, "CP1254" TRANSLIT }, + { 1255, "CP1255" TRANSLIT }, + { 1256, "CP1256" TRANSLIT }, + { 1257, "CP1257" TRANSLIT }, + { 1258, "CP1258" TRANSLIT }, + { CP_ISO8859_1, "ISO8859-1" TRANSLIT }, + { CP_ISO8859_2, "ISO8859-2" TRANSLIT }, + { CP_ISO8859_3, "ISO8859-3" TRANSLIT }, + { CP_ISO8859_4, "ISO8859-4" TRANSLIT }, + { CP_ISO8859_5, "ISO8859-5" TRANSLIT }, + { CP_ISO8859_6, "ISO8859-6" TRANSLIT }, + { CP_ISO8859_7, "ISO8859-7" TRANSLIT }, + { CP_ISO8859_8, "ISO8859-8" TRANSLIT }, + { CP_ISO8859_9, "ISO8859-9" TRANSLIT }, + { CP_ISO8859_13, "ISO8859-13" TRANSLIT }, + { CP_ISO8859_15, "ISO8859-15" TRANSLIT }, { 12000, "UTF-32LE" } }; const size_t cp_iconv::g_cp_iconv_count = ARRAYSIZE(cp_iconv::g_cp_iconv); @@ -279,22 +285,46 @@ bool EncodingConverter::Initialize() using namespace std; -SystemLocale::SystemLocale( const char * localeName ) - : m_uAnsiCP(CP_UTF8) - , m_pLocale(NULL) -{ - const char* DEFAULT_LOCALE = "en_US.UTF-8"; +#ifndef _countof + #define _countof(obj) (sizeof(obj)/sizeof(obj[0])) +#endif + +const char* DEFAULT_LOCALES[] = {"en_US.UTF-8", "C"}; - try { - m_pLocale = new std::locale(localeName); +bool _setLocale(const char * localeName, std::locale ** pLocale) +{ + try + { + *pLocale = new std::locale(localeName); } - catch(const std::exception& e) { - localeName = DEFAULT_LOCALE; + catch(const std::exception& e) + { + return false; } - - if(!m_pLocale) { - m_pLocale = new std::locale(localeName); + + return true; +} + +void setDefaultLocale(const char ** localeName, std::locale ** pLocale) +{ + if(!localeName || !_setLocale(*localeName, pLocale)) + { + int count = 0; + while(!_setLocale(DEFAULT_LOCALES[count], pLocale) && count < _countof(DEFAULT_LOCALES)) + { + count++; + } + + if(localeName) + *localeName = count < _countof(DEFAULT_LOCALES)?DEFAULT_LOCALES[count]:NULL; } +} + +SystemLocale::SystemLocale( const char * localeName ) + : m_uAnsiCP(CP_UTF8) + , m_pLocale(NULL) +{ + setDefaultLocale(&localeName, &m_pLocale); // Mapping from locale charset to codepage struct LocaleCP diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index aa460ef94..240c09cf6 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -44,13 +44,13 @@ if test "$PHP_SQLSRV" != "no"; then pdo_sqlsrv_inc_path=$srcdir/ext/pdo_sqlsrv/shared/ shared_src_class="" elif test -f $srcdir/ext/sqlsrv/shared/core_sqlsrv.h; then - sqlsrv_inc_path=$srcdir/ext/sqlsrv/shared/ + sqlsrv_inc_path=$srcdir/ext/sqlsrv/shared/ elif test -f $srcdir/shared/core_sqlsrv.h; then - sqlsrv_inc_path=$srcdir/shared/ + sqlsrv_inc_path=$srcdir/shared/ else - AC_MSG_ERROR([Cannot find SQLSRV headers]) + AC_MSG_ERROR([Cannot find SQLSRV headers]) fi - AC_MSG_RESULT($sqlsrv_inc_path) + AC_MSG_RESULT($sqlsrv_inc_path) CXXFLAGS="$CXXFLAGS -std=c++11" CXXFLAGS="$CXXFLAGS -D_FORTIFY_SOURCE=2 -O2" @@ -58,11 +58,17 @@ if test "$PHP_SQLSRV" != "no"; then HOST_OS_ARCH=`uname` if test "${HOST_OS_ARCH}" = "Darwin"; then - SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-bind_at_load" - MACOSX_DEPLOYMENT_TARGET=`sw_vers -productVersion` + SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-bind_at_load" + MACOSX_DEPLOYMENT_TARGET=`sw_vers -productVersion` else - SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-z,now" + SQLSRV_SHARED_LIBADD="$SQLSRV_SHARED_LIBADD -Wl,-z,now" + IS_ALPINE_1=`uname -a | cut -f 4 -d ' ' | cut -f 2 -d '-'` + IS_ALPINE_2=`cat /etc/os-release | grep ID | grep alpine | cut -f 2 -d '='` + if test "${IS_ALPINE_1}" = "Alpine" || test "${IS_ALPINE_2}" = "alpine"; then + AC_DEFINE(__MUSL__, 1, [ ]) + fi fi + PHP_REQUIRE_CXX() PHP_ADD_LIBRARY(stdc++, 1, SQLSRV_SHARED_LIBADD) From 8bb6cef33c22e3923117b4d73cb0a39d396f0fc1 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 2 Mar 2020 09:51:02 -0800 Subject: [PATCH 199/249] 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 | 252 +++++++++------ 6 files changed, 486 insertions(+), 350 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 68c85525a..a5d636adc 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 3879efce3..46dfc6c53 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 cb7aaf667..6fd5c3be4 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 67c601b85..020dce750 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 ea5285d85..1fd65641d 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 8bbe96067..659191432 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"); - } - +$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; + + $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); + $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); - } - } - } - } - - if (AE\isColEncrypted()) { - dropProc($conn, $spname); - } - if ($success) { - echo "Test successfully done.\n"; - } - dropTable($conn, $tbname); -} - -sqlsrv_close($conn); + 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-- From fb335c0b0db9112ada221f74d4528c62f386f228 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 16 Mar 2020 13:25:09 -0700 Subject: [PATCH 200/249] Modified some BVT tests to be configurable (#1106) --- appveyor.yml | 4 ++-- test/bvt/sqlsrv/break.inc | 15 +++++++++------ test/bvt/sqlsrv/connect.inc | 2 ++ test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt | 5 +++-- .../msdn_sqlsrv_get_field_stream_binary.php | 4 ++-- test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable.phpt | 5 +---- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a473ab3f4..cf87f520f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -83,8 +83,8 @@ install: } - echo Downloading MSODBCSQL 17 # AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it - - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/msodbcsql_17.4.2.1_x64.msi', 'c:\projects\msodbcsql_17.4.2.1_x64.msi') - - cmd /c start /wait msiexec /i "c:\projects\msodbcsql_17.4.2.1_x64.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/17.5.2.1/x64/msodbcsql.msi', 'c:\projects\msodbcsql.msi') + - cmd /c start /wait msiexec /i "c:\projects\msodbcsql.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL - echo Checking the version of MSODBCSQL - reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server" - dir %WINDIR%\System32\msodbcsql*.dll diff --git a/test/bvt/sqlsrv/break.inc b/test/bvt/sqlsrv/break.inc index 6a2e190e0..5e6429f77 100644 --- a/test/bvt/sqlsrv/break.inc +++ b/test/bvt/sqlsrv/break.inc @@ -1,15 +1,18 @@ "$databaseName", "username"=>"$username", "password"=>"$password" ); $conn = sqlsrv_connect( $serverName, $connectionInfo ); diff --git a/test/bvt/sqlsrv/connect.inc b/test/bvt/sqlsrv/connect.inc index 07f9289af..6ac30a9c3 100644 --- a/test/bvt/sqlsrv/connect.inc +++ b/test/bvt/sqlsrv/connect.inc @@ -4,6 +4,8 @@ $databaseName = 'TARGET_DATABASE'; $uid = 'TARGET_USERNAME'; $pwd = 'TARGET_PASSWORD'; +$server2 = 'ANOTHER_SERVER'; + // RevisionNumber in SalesOrderHeader is subject to a trigger incrementing it whenever // changes are made to SalesOrderDetail. Since RevisionNumber is a tinyint, it can // overflow quickly if the BVT tests often run. So we change it directly here first diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt index 9b3577320..6e6adfb24 100644 --- a/test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt +++ b/test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt @@ -10,8 +10,9 @@ $conn = sqlsrv_connect( $server, $connectionInfo); /* Connect to the local server using Windows Authentication and specify the AdventureWorks database as the database in use. */ -$serverName = "sql-2k14-sp1-1.galaxy.ad"; -$connectionInfo = array( "Database"=>"AdventureWorks2014", "UID"=>"sa", "PWD"=>"Moonshine4me", 'MultipleActiveResultSets'=> false); +$serverName = $server2; +$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd, 'MultipleActiveResultSets'=> false); + $conn = sqlsrv_connect( $serverName, $connectionInfo); if( $conn === false ) { diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_binary.php b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_binary.php index 751229f1e..ea810678f 100644 --- a/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_binary.php +++ b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_stream_binary.php @@ -1,8 +1,8 @@ "AdventureWorks2014", "UID"=>"sa", "PWD"=>"Moonshine4me"); +$serverName = $server2; +$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd); $conn = sqlsrv_connect( $serverName, $connectionInfo); if( $conn === false ) { diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable.phpt index e9444ea97..a42c9d2f2 100644 --- a/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable.phpt +++ b/test/bvt/sqlsrv/msdn_sqlsrv_query_scrollable.phpt @@ -4,11 +4,8 @@ server side cursor specified when querying --FILE-- "tempdb", "UID"=>"sa", "PWD"=>"Moonshine4me")); - require('connect.inc'); -$connectionInfo = array( "Database"=>"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); +$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd); $conn = sqlsrv_connect( $server, $connectionInfo); if ( $conn === false ) { die( print_r( sqlsrv_errors(), true )); From 7214e8d5536a7845fae3cb404626a34d8025969e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 25 Mar 2020 09:53:18 -0700 Subject: [PATCH 201/249] Set logger for driver API (#1107) --- azure-pipelines.yml | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 21 +--- source/pdo_sqlsrv/pdo_init.cpp | 16 +-- source/pdo_sqlsrv/pdo_stmt.cpp | 19 +-- source/pdo_sqlsrv/pdo_util.cpp | 25 +--- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 10 +- source/shared/core_sqlsrv.h | 10 +- source/shared/core_util.cpp | 39 +++++- source/sqlsrv/conn.cpp | 15 +-- source/sqlsrv/init.cpp | 4 +- source/sqlsrv/php_sqlsrv_int.h | 12 +- source/sqlsrv/stmt.cpp | 3 +- source/sqlsrv/util.cpp | 28 +---- .../pdo_sqlsrv/pdo_connection_logs.phpt | 65 ++++++++++ .../pdo_sqlsrv/pdo_errorMode_logs.phpt | 116 ++++++++++++++++++ .../functional/sqlsrv/sqlsrv_commit_logs.phpt | 63 ++++++++++ test/functional/sqlsrv/sqlsrv_connect.phpt | 2 +- .../sqlsrv/sqlsrv_connect_log_to_file.phpt | 56 +++++++++ .../sqlsrv/sqlsrv_connect_logs.phpt | 49 ++++++++ 20 files changed, 434 insertions(+), 123 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_connection_logs.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_commit_logs.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_connect_log_to_file.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_connect_logs.phpt diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a5d636adc..4444203fe 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -105,7 +105,7 @@ jobs: docker pull mcr.microsoft.com/mssql/server:2017-latest docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=$(pwd)' -p 1433:1433 -h $(host) --name=$(host) -d mcr.microsoft.com/mssql/server:2017-latest docker ps -a - sleep 5 + sleep 10 docker exec -t $(host) /opt/mssql-tools/bin/sqlcmd -S $(server) -U $(uid) -P $(pwd) -Q 'select @@Version' displayName: 'Run SQL Server for Linux' diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index d950ac3bf..9667500e3 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -489,26 +489,13 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { // log a function entry point -#ifndef _WIN32 #define PDO_LOG_DBH_ENTRY \ { \ pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ - driver_dbh->set_func( __FUNCTION__ ); \ - int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ - char func[length+1]; \ - memset(func, '\0', length+1); \ - strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ - strcat_s( func, length+1, ": entering" ); \ - LOG( SEV_NOTICE, func ); \ + if (driver_dbh != NULL) driver_dbh->set_func(__FUNCTION__); \ + core_sqlsrv_register_severity_checker(pdo_severity_check); \ + LOG(SEV_NOTICE, "%1!s!: entering", __FUNCTION__); \ } -#else -#define PDO_LOG_DBH_ENTRY \ -{ \ - pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast( dbh->driver_data ); \ - driver_dbh->set_func( __FUNCTION__ ); \ - LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ -} -#endif // constructor for the internal object for connections pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ) : @@ -547,7 +534,7 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo // 0 for failure, 1 for success. int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options TSRMLS_DC) { - LOG( SEV_NOTICE, "pdo_sqlsrv_db_handle_factory: entering" ); + PDO_LOG_DBH_ENTRY; hash_auto_ptr pdo_conn_options_ht; pdo_error_mode prev_err_mode = dbh->error_mode; diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 4f1a88458..e471561f0 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -128,11 +128,11 @@ PHP_MINIT_FUNCTION(pdo_sqlsrv) ZEND_TSRMLS_CACHE_UPDATE(); #endif - core_sqlsrv_register_logger( pdo_sqlsrv_log ); + core_sqlsrv_register_severity_checker(pdo_severity_check); REGISTER_INI_ENTRIES(); - LOG( SEV_NOTICE, "pdo_sqlsrv: entering minit" ); + PDO_LOG_NOTICE("pdo_sqlsrv: entering minit"); // initialize list of pdo errors g_pdo_errors_ht = reinterpret_cast( pemalloc( sizeof( HashTable ), 1 )); @@ -200,7 +200,7 @@ PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv) } catch( ... ) { - LOG( SEV_NOTICE, "Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)" ); + PDO_LOG_NOTICE("Unknown exception caught in PHP_MSHUTDOWN_FUNCTION(pdo_sqlsrv)"); return FAILURE; } @@ -225,18 +225,18 @@ PHP_RINIT_FUNCTION(pdo_sqlsrv) int set_locale = PDO_SQLSRV_G(set_locale_info); if (set_locale == 2) { setlocale(LC_ALL, ""); - LOG(SEV_NOTICE, "pdo_sqlsrv: setlocale LC_ALL"); + PDO_LOG_NOTICE("pdo_sqlsrv: setlocale LC_ALL"); } else if (set_locale == 1) { setlocale(LC_CTYPE, ""); - LOG(SEV_NOTICE, "pdo_sqlsrv: setlocale LC_CTYPE"); + PDO_LOG_NOTICE("pdo_sqlsrv: setlocale LC_CTYPE"); } else { - LOG(SEV_NOTICE, "pdo_sqlsrv: setlocale NONE"); + PDO_LOG_NOTICE("pdo_sqlsrv: setlocale NONE"); } #endif - LOG( SEV_NOTICE, "pdo_sqlsrv: entering rinit" ); + PDO_LOG_NOTICE("pdo_sqlsrv: entering rinit"); return SUCCESS; } @@ -250,7 +250,7 @@ PHP_RSHUTDOWN_FUNCTION(pdo_sqlsrv) SQLSRV_UNUSED( module_number ); SQLSRV_UNUSED( type ); - LOG( SEV_NOTICE, "pdo_sqlsrv: entering rshutdown" ); + PDO_LOG_NOTICE("pdo_sqlsrv: entering rshutdown"); return SUCCESS; } diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 65f5a7daa..37afbf380 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -351,26 +351,13 @@ void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op } // log a function entry point -#ifndef _WIN32 #define PDO_LOG_STMT_ENTRY \ { \ pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ - driver_stmt->set_func( __FUNCTION__ ); \ - int length = strlen( __FUNCTION__ ) + strlen( ": entering" ); \ - char func[length+1]; \ - memset(func, '\0', length+1); \ - strcpy_s( func, sizeof( __FUNCTION__ ), __FUNCTION__ ); \ - strcat_s( func, length+1, ": entering" ); \ - LOG( SEV_NOTICE, func ); \ + if (driver_stmt != NULL) driver_stmt->set_func( __FUNCTION__ ); \ + core_sqlsrv_register_severity_checker(pdo_severity_check); \ + LOG(SEV_NOTICE, "%1!s!: entering", __FUNCTION__); \ } -#else -#define PDO_LOG_STMT_ENTRY \ -{ \ - pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); \ - driver_stmt->set_func( __FUNCTION__ ); \ - LOG( SEV_NOTICE, __FUNCTION__ ## ": entering" ); \ -} -#endif // PDO SQLSRV statement destructor pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void ) diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 560483302..34e1a4ec5 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -39,13 +39,6 @@ const int MAX_DIGITS = 11; // +-2 billion = 10 digits + 1 for the sign if negati // the warning message is not the error message alone; it must take WARNING_TEMPLATE above into consideration without the formats const int WARNING_MIN_LENGTH = static_cast( strlen( WARNING_TEMPLATE ) - strlen( "%1!s!%2!d!%3!s!" )); -// buffer used to hold a formatted log message prior to actually logging it. -const int LOG_MSG_SIZE = 2048; -char log_msg[LOG_MSG_SIZE] = {'\0'}; - -// internal error that says that FormatMessage failed -SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; - // Returns a sqlsrv_error for a given error code. sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code); @@ -623,22 +616,10 @@ void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Ou } } -// Formats the error message and writes to the php error log. -void pdo_sqlsrv_log( _In_opt_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ) +// check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro +bool pdo_severity_check(_In_ unsigned int severity TSRMLS_DC) { - if( (severity & PDO_SQLSRV_G( log_severity )) == 0 ) { - return; - } - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); - - // if an error occurs for FormatMessage, we just output an internal error occurred. - if( rc == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); - } - - php_log_err( log_msg TSRMLS_CC ); + return ((severity & PDO_SQLSRV_G(pdo_log_severity))); } namespace { diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index ab1aaf262..79a294f95 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -29,7 +29,7 @@ // request level variables ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) -unsigned int log_severity; +unsigned int pdo_log_severity; zend_long client_buffer_max_size; #ifndef _WIN32 diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 2934e11fc..2caf19858 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -60,7 +60,7 @@ extern HMODULE g_sqlsrv_hmodule; #endif PHP_INI_BEGIN() - STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, log_severity, + STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_LOG , "0", PHP_INI_ALL, OnUpdateLong, pdo_log_severity, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) @@ -326,6 +326,10 @@ inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh TSRMLS_DC ) } } +#define PDO_LOG_NOTICE(message) \ + core_sqlsrv_register_severity_checker(pdo_severity_check); \ + LOG(SEV_NOTICE, message); + #define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); inline void pdo_reset_stmt_error( _Inout_ pdo_stmt_t* stmt ) @@ -417,8 +421,8 @@ namespace pdo { } // namespace pdo -// logger for pdo_sqlsrv called by the core layer when it wants to log something with the LOG macro -void pdo_sqlsrv_log( _In_opt_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); +// check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro +bool pdo_severity_check(_In_ unsigned int severity TSRMLS_DC); #endif /* PHP_PDO_SQLSRV_INT_H */ diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index d576993d9..e91b44c51 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -287,14 +287,12 @@ struct sqlsrv_static_assert { _In_ static const int value = 1; }; // Logging //********************************************************************************************************************************* // log_callback -// a driver specific callback for logging messages +// a driver specific callback for checking if the messages are qualified to be logged: // severity - severity of the message: notice, warning, or error -// msg - the message to log in a FormatMessage style formatting -// print_args - args to the message -typedef void (*log_callback)( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, _In_opt_ va_list* print_args ); +typedef bool (*severity_callback)(_In_ unsigned int severity TSRMLS_DC); -// each driver must register a log callback. This should be the first thing a driver does. -void core_sqlsrv_register_logger( _In_ log_callback ); +// each driver must register a severity checker callback for logging to work according to the INI settings +void core_sqlsrv_register_severity_checker(_In_ severity_callback driver_checker); // a simple wrapper around a PHP error logging function. void write_to_log( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, ... ); diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 2853a22a8..bc51b34bd 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -23,10 +23,16 @@ namespace { +severity_callback g_driver_severity; + // *** internal constants *** -log_callback g_driver_log; + +// buffer used to hold a formatted log message prior to actually logging it. +const int LOG_MSG_SIZE = 2048; + // internal error that says that FormatMessage failed SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; + // buffer used to hold a formatted log message prior to actually logging it. char last_err_msg[2048] = {'\0'}; // 2k to hold the error messages @@ -35,6 +41,25 @@ unsigned int convert_string_from_default_encoding( _In_ unsigned int php_encodin _In_ unsigned int mbcs_len, _Out_writes_(utf16_len) __transfer( mbcs_in_string ) SQLWCHAR* utf16_out_string, _In_ unsigned int utf16_len, bool use_strict_conversion = false ); + +// invoked by write_to_log() when the message severity qualifies to be logged +// msg - the message to log in a FormatMessage style formatting +// print_args - args to the message +void log_activity(_In_opt_ const char* msg, _In_opt_ va_list* print_args) +{ + char log_msg[LOG_MSG_SIZE] = { '\0' }; + + DWORD rc = FormatMessage(FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args); + + // if an error occurs for FormatMessage, we just output an internal error occurred. + if (rc == 0) { + SQLSRV_STATIC_ASSERT(sizeof(INTERNAL_FORMAT_ERROR) < sizeof(log_msg)); + std::copy(INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof(INTERNAL_FORMAT_ERROR), log_msg); + } + + php_log_err(log_msg TSRMLS_CC); +} + } // SQLSTATE for all internal errors @@ -47,22 +72,24 @@ SQLCHAR SSPWARN[] = "01SSP"; // the script (sqlsrv_configure). void write_to_log( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, ...) { - SQLSRV_ASSERT( !(g_driver_log == NULL), "Must register a driver log function." ); + SQLSRV_ASSERT( !(g_driver_severity == NULL), "Must register a driver checker function." ); + if (!g_driver_severity(severity TSRMLS_CC)) { + return; + } va_list args; va_start( args, msg ); - g_driver_log( severity TSRMLS_CC, msg, &args ); + log_activity(msg, &args); va_end( args ); } -void core_sqlsrv_register_logger( _In_ log_callback driver_logger ) +void core_sqlsrv_register_severity_checker(_In_ severity_callback driver_checker) { - g_driver_log = driver_logger; + g_driver_severity = driver_checker; } - // convert a string from utf-16 to the encoding and return the new string in the pointer parameter and new // length in the len parameter. If no errors occurred during convertion, true is returned and the original // utf-16 string is released by this function if no errors occurred. Otherwise the parameters are not changed diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index c4ed63626..58aaf14bf 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -629,10 +629,9 @@ const connection_option SS_CONN_OPTS[] = { PHP_FUNCTION ( sqlsrv_connect ) { - LOG_FUNCTION( "sqlsrv_connect" ); - SET_FUNCTION_NAME( *g_ss_henv_cp ); - SET_FUNCTION_NAME( *g_ss_henv_ncp ); + g_ss_henv_cp->set_func(_FN_); + g_ss_henv_ncp->set_func(_FN_); reset_errors( TSRMLS_C ); @@ -785,7 +784,7 @@ PHP_FUNCTION( sqlsrv_close ) // dummy context to pass to the error handler error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); + error_ctx->set_func(_FN_); if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r) == FAILURE ) { @@ -816,7 +815,7 @@ PHP_FUNCTION( sqlsrv_close ) throw ss::SSException(); } - SET_FUNCTION_NAME( *conn ); + conn->set_func(_FN_); // cause any variables still holding a reference to this to be invalid so they cause // an error when passed to a sqlsrv function. There's nothing we can do if the @@ -845,13 +844,15 @@ PHP_FUNCTION( sqlsrv_close ) void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ) { - LOG_FUNCTION( "sqlsrv_conn_dtor" ); + // Without sqlsrv_close(), this function is invoked by php during the final clean up stage. + // To prevent memory/resource leaks, no more logging at this point. + //LOG_FUNCTION( "sqlsrv_conn_dtor" ); // get the structure ss_sqlsrv_conn *conn = static_cast( rsrc->ptr ); SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null"); - SET_FUNCTION_NAME( *conn ); + conn->set_func(__func__); // close all statements associated with the connection. sqlsrv_conn_close_stmts( conn TSRMLS_CC ); diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index bc0692057..96f5338f1 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -271,8 +271,8 @@ PHP_MINIT_FUNCTION(sqlsrv) { SQLSRV_UNUSED( type ); - core_sqlsrv_register_logger( ss_sqlsrv_log ); - + core_sqlsrv_register_severity_checker(ss_severity_check); + // our global variables are initialized in the RINIT function #if defined(ZTS) if( ts_allocate_id( &sqlsrv_globals_id, diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index 9ca59d9e0..fae7082e5 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -313,15 +313,11 @@ class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_c #define LOG_FUNCTION( function_name ) \ const char* _FN_ = function_name; \ SQLSRV_G( current_subsystem ) = current_log_subsystem; \ - LOG( SEV_NOTICE, "%1!s!: entering", _FN_ ); + core_sqlsrv_register_severity_checker(ss_severity_check); \ + LOG(SEV_NOTICE, "%1!s!: entering", _FN_); -#define SET_FUNCTION_NAME( context ) \ -{ \ - (context).set_func( _FN_ ); \ -} - -// logger for ss_sqlsrv called by the core layer when it wants to log something with the LOG macro -void ss_sqlsrv_log( _In_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ); +// check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro +bool ss_severity_check(_In_ unsigned int severity TSRMLS_DC); // subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise. enum logging_subsystems { diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 05d46b6c9..c7f002dce 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1371,7 +1371,6 @@ void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ) PHP_FUNCTION( sqlsrv_free_stmt ) { - LOG_FUNCTION( "sqlsrv_free_stmt" ); zval* stmt_r = NULL; @@ -1384,7 +1383,7 @@ PHP_FUNCTION( sqlsrv_free_stmt ) // dummy context to pass to the error handler error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); + error_ctx->set_func(_FN_); // take only the statement resource if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) { diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index 671f6bf46..b91f17c2d 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -30,13 +30,6 @@ namespace { // current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros unsigned int current_log_subsystem = LOG_UTIL; -// buffer used to hold a formatted log message prior to actually logging it. -const int LOG_MSG_SIZE = 2048; -char log_msg[LOG_MSG_SIZE] = {'\0'}; - -// internal error that says that FormatMessage failed -SQLCHAR INTERNAL_FORMAT_ERROR[] = "An internal error occurred. FormatMessage failed writing an error message."; - // *** internal functions *** sqlsrv_error_const* get_error_message( _In_ unsigned int sqlsrv_error_code ); @@ -457,21 +450,10 @@ ss_error SS_ERRORS[] = { { UINT_MAX, {} } }; -// Formats an error message and finally writes it to the php log. -void ss_sqlsrv_log( _In_ unsigned int severity TSRMLS_DC, _In_opt_ const char* msg, _In_opt_ va_list* print_args ) +// check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro +bool ss_severity_check(_In_ unsigned int severity TSRMLS_DC) { - if(( severity & SQLSRV_G( log_severity )) && ( SQLSRV_G( current_subsystem ) & SQLSRV_G( log_subsystems ))) { - - DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, print_args ); - - // if an error occurs for FormatMessage, we just output an internal error occurred. - if( rc == 0 ) { - SQLSRV_STATIC_ASSERT( sizeof( INTERNAL_FORMAT_ERROR ) < sizeof( log_msg )); - std::copy( INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof( INTERNAL_FORMAT_ERROR ), log_msg ); - } - - php_log_err( log_msg TSRMLS_CC ); - } + return ((severity & SQLSRV_G(log_severity)) && (SQLSRV_G(current_subsystem) & SQLSRV_G(log_subsystems))); } bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool warning TSRMLS_DC, _In_opt_ va_list* print_args ) @@ -598,7 +580,7 @@ PHP_FUNCTION( sqlsrv_configure ) // dummy context to pass onto the error handler error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); + error_ctx->set_func(_FN_); int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z ); CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { @@ -718,7 +700,7 @@ PHP_FUNCTION( sqlsrv_get_config ) // dummy context to pass onto the error handler error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); - SET_FUNCTION_NAME( *error_ctx ); + error_ctx->set_func(_FN_); int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len ); CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { diff --git a/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt b/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt new file mode 100644 index 000000000..46ea1cdfc --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt @@ -0,0 +1,65 @@ +--TEST-- +Test simple logging with connection, simple query and then close +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + return $conn; +} + +try { + ini_set('log_errors', '1'); + + $logFilename = 'php_errors.log'; + $logFilepath = dirname(__FILE__).'/'.$logFilename; + if (file_exists($logFilepath)) { + unlink($logFilepath); + } + + ini_set('error_log', $logFilepath); + ini_set('pdo_sqlsrv.log_severity', '-1'); + + $conn = toConnect(); + $stmt = $conn->query("SELECT @@Version"); + + // Ignore the fetch results + $stmt->fetchAll(); + + unset($conn); + + if (file_exists($logFilepath)) { + echo file_get_contents($logFilepath); + unlink($logFilepath); + } else { + echo "$logFilepath is missing!\n"; + } + + echo "Done\n"; +} catch (Exception $e) { + var_dump($e); +} + +?> +--EXPECTF-- +[%s UTC] pdo_sqlsrv_db_handle_factory: entering +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5701 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to '%s'. +[%s UTC] pdo_sqlsrv_dbh_prepare: entering +[%s UTC] pdo_sqlsrv_stmt_execute: entering +[%s UTC] pdo_sqlsrv_stmt_describe_col: entering +[%s UTC] pdo_sqlsrv_stmt_fetch: entering +[%s UTC] pdo_sqlsrv_stmt_get_col_data: entering +[%s UTC] pdo_sqlsrv_stmt_fetch: entering +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt b/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt new file mode 100644 index 000000000..a0169696f --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt @@ -0,0 +1,116 @@ +--TEST-- +Test different error modes. The queries will try to do a select on a non-existing table +--DESCRIPTION-- +This is similar to pdo_errorMode.phpt but will display the contents of php +error logs based on log severity. +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + global $sql; + try { + $q = $conn->query($sql); + } catch (Exception $e) { + // do nothing + } +} + +function testWarning($conn) +{ + // This forces PHP to log errors rather than displaying errors + // on screen -- only required for PDO::ERRMODE_WARNING + ini_set('display_errors', '0'); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + global $sql; + $q = $conn->query($sql); +} + +function runtests($severity) +{ + global $conn; + + $logFilename = 'php_errors' . $severity . '.log'; + $logFilepath = dirname(__FILE__).'/'.$logFilename; + if (file_exists($logFilepath)) { + unlink($logFilepath); + } + + ini_set('error_log', $logFilepath); + ini_set('pdo_sqlsrv.log_severity', $severity); + + if ($severity === '2' ) { + testWarning($conn); + } else { + testException($conn); + } + + if (file_exists($logFilepath)) { + if ($severity == '0') { + echo "$logFilepath should not exist\n"; + } + echo file_get_contents($logFilepath); + unlink($logFilepath); + } + + // Now reset logging by disabling it + ini_set('pdo_sqlsrv.log_severity', '0'); + echo "Done with $severity\n\n"; +} + +try { + ini_set('log_errors', '1'); + ini_set('pdo_sqlsrv.log_severity', '0'); + + $conn = toConnect(); + $sql = "SELECT * FROM temp_table"; + + runtests('0'); + runtests('1'); + runtests('2'); + runtests('4'); + runtests('-1'); +} catch (Exception $e) { + var_dump($e); +} + +?> +--EXPECTF-- +Done with 0 + +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42S02 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 208 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Invalid object name 'temp_table'. +Done with 1 + +[%s UTC] PHP Warning: PDO::query(): SQLSTATE[42S02]: Base table or view not found: 208 %s[SQL Server]Invalid object name 'temp_table'. in %spdo_errorMode_logs.php on line %d +Done with 2 + +[%s UTC] pdo_sqlsrv_stmt_dtor: entering +[%s UTC] pdo_sqlsrv_dbh_prepare: entering +[%s UTC] pdo_sqlsrv_stmt_execute: entering +Done with 4 + +[%s UTC] pdo_sqlsrv_stmt_dtor: entering +[%s UTC] pdo_sqlsrv_dbh_prepare: entering +[%s UTC] pdo_sqlsrv_stmt_execute: entering +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42S02 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 208 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Invalid object name 'temp_table'. +Done with -1 + diff --git a/test/functional/sqlsrv/sqlsrv_commit_logs.phpt b/test/functional/sqlsrv/sqlsrv_commit_logs.phpt new file mode 100644 index 000000000..ba17483eb --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_commit_logs.phpt @@ -0,0 +1,63 @@ +--TEST-- +Test sqlsrv_commit method with logging +--DESCRIPTION-- +Similar to sqlsrv_commit.phpt but also test some basic logging activities +By adding integer values together, we can specify more than one logging option at a time. +SQLSRV_LOG_SYSTEM_CONN (2) Turns on logging of connection activity. +SQLSRV_LOG_SYSTEM_STMT (4) Turns on logging of statement activity. + +For example, sqlsrv.LogSubsystems = 6 +turns on logging of connection and statement activities +--SKIPIF-- + +--FILE-- + +--EXPECT-- +sqlsrv_connect: entering +sqlsrv_query: entering +sqlsrv_query: entering +sqlsrv_stmt_dtor: entering +sqlsrv_free_stmt: entering +sqlsrv_stmt_dtor: entering +sqlsrv_query: entering +sqlsrv_query: entering +sqlsrv_commit: entering +sqlsrv_query: entering +sqlsrv_free_stmt: entering +sqlsrv_stmt_dtor: entering +sqlsrv_free_stmt: entering +sqlsrv_stmt_dtor: entering +sqlsrv_free_stmt: entering +sqlsrv_stmt_dtor: entering +sqlsrv_close: entering diff --git a/test/functional/sqlsrv/sqlsrv_connect.phpt b/test/functional/sqlsrv/sqlsrv_connect.phpt index 083e74fac..2dcacb7d1 100644 --- a/test/functional/sqlsrv/sqlsrv_connect.phpt +++ b/test/functional/sqlsrv/sqlsrv_connect.phpt @@ -14,7 +14,7 @@ functions return FALSE for errors. fatalError("sqlsrv_connect should have returned false."); } - $conn = sqlsrv_connect("_!@#$", array( "Driver" => "Danica Patrick" )); + $conn = sqlsrv_connect("_!@#$", array( "Driver" => "Wrong Driver" )); if ($conn !== false) { fatalError("sqlsrv_connect should have returned false."); } diff --git a/test/functional/sqlsrv/sqlsrv_connect_log_to_file.phpt b/test/functional/sqlsrv/sqlsrv_connect_log_to_file.phpt new file mode 100644 index 000000000..4422294fe --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_log_to_file.phpt @@ -0,0 +1,56 @@ +--TEST-- +Test functions return FALSE for errors with logging +--DESCRIPTION-- +Similar to sqlsrv_connect_logs.phpt but this time test logging to a log file +--SKIPIF-- + +--FILE-- + "Wrong Driver" )); + if ($conn !== false) { + fatalError("sqlsrv_connect should have returned false."); + } + + ini_set("sqlsrv.LogSeverity", SQLSRV_LOG_SEVERITY_NOTICE); + $conn = sqlsrv_connect($server, array( "uid" => $uid , "pwd" => $pwd )); + + if ($conn === false) { + fatalError("sqlsrv_connect should have connected."); + } + + ini_set("sqlsrv.LogSeverity", SQLSRV_LOG_SEVERITY_ERROR); + $stmt = sqlsrv_query($conn, "SELECT * FROM some_bogus_table"); + if ($stmt !== false) { + fatalError("sqlsrv_query should have returned false."); + } + + ini_set("sqlsrv.LogSeverity", SQLSRV_LOG_SEVERITY_ALL); + if (file_exists($logFilepath)) { + echo file_get_contents($logFilepath); + unlink($logFilepath); + } + + sqlsrv_close($conn); +?> +--EXPECTF-- +[%s UTC] sqlsrv_connect: entering +[%s UTC] sqlsrv_connect: SQLSTATE = IMSSP +[%s UTC] sqlsrv_connect: error code = -106 +[%s UTC] sqlsrv_connect: message = Invalid value Wrong Driver was specified for Driver option. +[%s UTC] sqlsrv_connect: entering +[%s UTC] sqlsrv_query: SQLSTATE = 42S02 +[%s UTC] sqlsrv_query: error code = 208 +[%s UTC] sqlsrv_query: message = %s[SQL Server]Invalid object name 'some_bogus_table'. \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_connect_logs.phpt b/test/functional/sqlsrv/sqlsrv_connect_logs.phpt new file mode 100644 index 000000000..1ba654bda --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_connect_logs.phpt @@ -0,0 +1,49 @@ +--TEST-- +Test functions return FALSE for errors with logging +--DESCRIPTION-- +Similar to sqlsrv_connect.phpt but also test different settings of logging activities +--SKIPIF-- + +--FILE-- + "Wrong Driver" )); + if ($conn !== false) { + fatalError("sqlsrv_connect should have returned false."); + } + + sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_NOTICE); + $conn = sqlsrv_connect($server, array( "uid" => $uid , "pwd" => $pwd )); + + if ($conn === false) { + fatalError("sqlsrv_connect should have connected."); + } + + sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ERROR); + $stmt = sqlsrv_query($conn, "SELECT * FROM some_bogus_table"); + if ($stmt !== false) { + fatalError("sqlsrv_query should have returned false."); + } + + sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_WARNING); + + sqlsrv_close($conn); +?> +--EXPECTF-- +sqlsrv.LogSubsystems = -1 +sqlsrv_connect: entering +sqlsrv_connect: SQLSTATE = IMSSP +sqlsrv_connect: error code = -106 +sqlsrv_connect: message = Invalid value Wrong Driver was specified for Driver option. +sqlsrv_configure: entering +sqlsrv.LogSeverity = 4 +sqlsrv_connect: entering +sqlsrv_configure: entering +sqlsrv_query: SQLSTATE = 42S02 +sqlsrv_query: error code = 208 +sqlsrv_query: message = %s[SQL Server]Invalid object name 'some_bogus_table'. From e8fef2278a486b2dab9f0e11c55ee66d21f95bf5 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 3 Apr 2020 09:43:39 -0700 Subject: [PATCH 202/249] Updated skipifs and modified tests with HGS enabled servers (#1111) --- .../pdo_sqlsrv/pdo_connect_encrypted.phpt | 116 +++++++++--------- test/functional/pdo_sqlsrv/skipif_not_hgs.inc | 30 ++--- test/functional/sqlsrv/skipif_not_hgs.inc | 6 +- .../sqlsrv/sqlsrv_connect_encrypted.phpt | 111 +++++++++-------- 4 files changed, 132 insertions(+), 131 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt index 124df22b7..4ca738b52 100644 --- a/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connect_encrypted.phpt @@ -1,57 +1,63 @@ --TEST-- Test new connection keyword ColumnEncryption +--DESCRIPTION-- +Some test cases return errors as expected. For testing purposes, an enclave enabled +SQL Server and the HGS server are the same instance. If the server is HGS enabled, +the error message of one test case is not the same. --SKIPIF-- --FILE-- getAttribute( PDO::ATTR_CLIENT_VERSION )['DriverVer']; - $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; -} -catch( PDOException $e ) -{ +try { + $conn = new PDO("sqlsrv:server = $server", $uid, $pwd); + $msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)['DriverVer']; + $version = explode(".", $msodbcsqlVer); + $msodbcsqlMaj = $version[0]; + + // Next, check if the server is HGS enabled + $serverInfo = $conn->getAttribute(PDO::ATTR_SERVER_INFO); + if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) { + $hgsEnabled = false; + } +} catch (PDOException $e) { echo "Failed to connect\n"; - print_r( $e->getMessage() ); + print_r($e->getMessage()); echo "\n"; } -test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj ); +testColumnEncryption($server, $uid, $pwd, $msodbcsqlMaj); echo "Done"; -function verify_output( $PDOerror, $expected ) +function verifyOutput($PDOerror, $expected, $caseNum) { - if( strpos( $PDOerror->getMessage(), $expected ) === false ) - { - print_r( $PDOerror->getMessage() ); + if (strpos($PDOerror->getMessage(), $expected) === false) { + echo "Test case $caseNum failed:\n"; + print_r($PDOerror->getMessage()); echo "\n"; } } -function test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj ) +function testColumnEncryption($server, $uid, $pwd, $msodbcsqlMaj) { + global $hgsEnabled; + // Only works for ODBC 17 //////////////////////////////////////// $connectionInfo = "ColumnEncryption = Enabled;"; - try - { - $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd ); - } - catch( PDOException $e ) - { - if($msodbcsql_maj < 17) - { + try { + $conn = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); + } catch (PDOException $e) { + if ($msodbcsqlMaj < 17) { $expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server."; - verify_output( $e, $expected ); - } - else - { - print_r( $e->getMessage() ); + verifyOutput($e, $expected, "1"); + } else { + echo "Test case 1 failed:\n"; + print_r($e->getMessage()); echo "\n"; } } @@ -59,50 +65,42 @@ function test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj ) // Works for ODBC 17, ODBC 13 //////////////////////////////////////// $connectionInfo = "ColumnEncryption = Disabled;"; - try - { - $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd ); - } - catch( PDOException $e ) - { - if($msodbcsql_maj < 13) - { + try { + $conn = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); + } catch (PDOException $e) { + if ($msodbcsqlMaj < 13) { $expected = "Invalid connection string attribute"; - verify_output( $e, $expected ); - } - else - { - print_r( $e->getMessage() ); + verifyOutput($e, $expected, "2"); + } else { + echo "Test case 2 failed:\n"; + print_r($e->getMessage()); echo "\n"; } } // should fail for all ODBC drivers + $expected = "Invalid value specified for connection string attribute 'ColumnEncryption'"; + if ($hgsEnabled) { + $expected = "Requested attestation protocol is invalid."; + } + //////////////////////////////////////// $connectionInfo = "ColumnEncryption = false;"; - try - { - $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd ); - } - catch( PDOException $e ) - { - $expected = "Invalid value specified for connection string attribute 'ColumnEncryption'"; - verify_output( $e, $expected ); + try { + $conn = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); + } catch (PDOException $e) { + verifyOutput($e, $expected, "3"); } // should fail for all ODBC drivers //////////////////////////////////////// $connectionInfo = "ColumnEncryption = 1;"; - try - { - $conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd ); + try { + $conn = new PDO("sqlsrv:server = $server ; $connectionInfo", $uid, $pwd); + } catch (PDOException $e) { + verifyOutput($e, $expected, "4"); } - catch( PDOException $e ) - { - $expected = "Invalid value specified for connection string attribute 'ColumnEncryption'"; - verify_output( $e, $expected ); - } -} +} ?> --EXPECT-- Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/skipif_not_hgs.inc b/test/functional/pdo_sqlsrv/skipif_not_hgs.inc index dd4614de8..80ad95f06 100644 --- a/test/functional/pdo_sqlsrv/skipif_not_hgs.inc +++ b/test/functional/pdo_sqlsrv/skipif_not_hgs.inc @@ -3,34 +3,34 @@ // SQL Server, and a HGS server. The HGS server and SQL Server // are the same for testing purposes. -if (!extension_loaded("sqlsrv")) { +if (!extension_loaded("pdo_sqlsrv")) { die("skip Extension not loaded"); } -require_once("MsSetup.inc"); +require_once('MsSetup.inc'); -$connectionInfo = array("UID"=>$uid, "PWD"=>$pwd, "Driver" => $driver); - -$conn = sqlsrv_connect( $server, $connectionInfo ); -if ($conn === false) { - die( "skip Could not connect during SKIPIF." ); +$conn = new PDO("sqlsrv:server = $server", $uid, $pwd); +if (!$conn) { + die("skip Could not connect during SKIPIF."); } -$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; -$msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; -$msodbcsql_min = explode(".", $msodbcsql_ver)[1]; +$msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)['DriverVer']; +$version = explode(".", $msodbcsqlVer); + +$msodbcsqlMaj = $version[0]; +$msodbcsqlMin = $version[1]; -if ($msodbcsql_maj < 17) { +if ($msodbcsqlMaj < 17) { die("skip Unsupported ODBC driver version"); } -if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) { +if ($msodbcsqlMin < 4 and $msodbcsqlMaj == 17) { die("skip Unsupported ODBC driver version"); } // Get SQL Server -$server_info = sqlsrv_server_info($conn); -if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) { +$serverInfo = $conn->getAttribute(PDO::ATTR_SERVER_INFO); +if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) { die("skip Server is not HGS enabled"); } -?> +?> \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_not_hgs.inc b/test/functional/sqlsrv/skipif_not_hgs.inc index 7d7b3ca1d..a25d8cbcc 100644 --- a/test/functional/sqlsrv/skipif_not_hgs.inc +++ b/test/functional/sqlsrv/skipif_not_hgs.inc @@ -9,11 +9,11 @@ if (!extension_loaded("sqlsrv")) { require_once("MsSetup.inc"); -$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver); +$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword); -$conn = sqlsrv_connect( $server, $connectionInfo ); +$conn = sqlsrv_connect($server, $connectionInfo); if ($conn === false) { - die( "skip Could not connect during SKIPIF." ); + die("skip Could not connect during SKIPIF."); } $msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"]; diff --git a/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt index e6a5bbb6d..f65b58100 100644 --- a/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt +++ b/test/functional/sqlsrv/sqlsrv_connect_encrypted.phpt @@ -1,98 +1,101 @@ --TEST-- -Test new connection keyword ColumnEncryption +Test new connection keyword ColumnEncryption with different input values +--DESCRIPTION-- +Some test cases return errors as expected. For testing purposes, an enclave enabled +SQL Server and the HGS server are the same instance. If the server is HGS enabled, +the error message of one test case is not the same. --SKIPIF-- --FILE-- $database,"UID"=>$userName, "PWD"=>$userPassword); -test_ColumnEncryption($server, $connectionOptions); +testColumnEncryption($server, $connectionOptions); echo "Done"; -function test_ColumnEncryption($server ,$connectionOptions){ +function testColumnEncryption($server, $connectionOptions) +{ $conn = sqlsrv_connect($server, $connectionOptions); - if ($conn === false) - { + if ($conn === false) { print_r(sqlsrv_errors()); } $msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer']; - $msodbcsql_maj = explode(".", $msodbcsql_ver)[0]; + $msodbcsqlMaj = explode(".", $msodbcsql_ver)[0]; + // Next, check if the server is HGS enabled + $hgsEnabled = true; + $serverInfo = sqlsrv_server_info($conn); + if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) { + $hgsEnabled = false; + } + // Only works for ODBC 17 - $connectionOptions['ColumnEncryption']='Enabled'; - $conn = sqlsrv_connect( $server, $connectionOptions ); - if( $conn === false ) - { - if($msodbcsql_maj < 17){ + $connectionOptions['ColumnEncryption'] = 'Enabled'; + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) { + if ($msodbcsqlMaj < 17) { $expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server."; - if( strcasecmp(sqlsrv_errors($conn)[0]['message'], $expected ) != 0 ) - { + if (strcasecmp(sqlsrv_errors($conn)[0]['message'], $expected) != 0) { print_r(sqlsrv_errors()); } - } - else - { + } else { + echo "Test case 1 failed:\n"; print_r(sqlsrv_errors()); } } - + // Works for ODBC 17, ODBC 13 $connectionOptions['ColumnEncryption']='Disabled'; - $conn = sqlsrv_connect( $server, $connectionOptions ); - if( $conn === false ) - { - if($msodbcsql_maj < 13) - { - $expected_substr = "Invalid connection string attribute"; - if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false ) - { + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) { + if ($msodbcsqlMaj < 13) { + $expected = "Invalid connection string attribute"; + if (strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false) { print_r(sqlsrv_errors()); } - } - else - { + } else { + echo "Test case 2 failed:\n"; print_r(sqlsrv_errors()); } - } - else - { + } else { sqlsrv_close($conn); } + + // Should fail for all ODBC drivers - but the error message returned depends on the server + $expected = "Invalid value specified for connection string attribute 'ColumnEncryption'"; + if ($hgsEnabled) { + $expected = "Requested attestation protocol is invalid."; + } - // should fail for all ODBC drivers $connectionOptions['ColumnEncryption']='false'; - $conn = sqlsrv_connect( $server, $connectionOptions ); - if( $conn === false ) - { - $expected_substr = "Invalid value specified for connection string attribute 'ColumnEncryption'"; - if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false ) - { + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) { + if (strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false) { + echo "Test case 3 failed:\n"; print_r(sqlsrv_errors()); } } - // should fail for all ODBC drivers + $expected = "Invalid value type for option ColumnEncryption was specified. String type was expected."; + + // should fail for all ODBC drivers with the above error message $connectionOptions['ColumnEncryption']=true; - $conn = sqlsrv_connect( $server, $connectionOptions ); - if( $conn === false ) - { - $expected_substr = "Invalid value type for option ColumnEncryption was specified. String type was expected."; - if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false ) - { + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) { + if (strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false) { + echo "Test case 4 failed:\n"; print_r(sqlsrv_errors()); } } - // should fail for all ODBC drivers + // should fail for all ODBC drivers with the above error message $connectionOptions['ColumnEncryption']=false; - $conn = sqlsrv_connect( $server, $connectionOptions ); - if( $conn === false ) - { - $expected_substr = "Invalid value type for option ColumnEncryption was specified. String type was expected."; - if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false ) - { + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) { + if (strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false) { + echo "Test case 5 failed:\n"; print_r(sqlsrv_errors()); } } From 3d2f631a5a696f77163141d8fdbc3779425c6385 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 7 Apr 2020 10:58:28 -0700 Subject: [PATCH 203/249] Change log 5.8.1 (#1116) --- CHANGELOG.md | 24 ++++++++++++++++++++++++ source/shared/version.h | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eab1ff0f..0f2552503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.8.1 - 2020-04-15 +Updated PECL release packages. Here is the list of updates: + +### Fixed +- Pull Request [#1094](https://github.com/microsoft/msphpsql/pull/1094) - Fixed default locale issues in Alpine Linux +- Pull Request [#1095](https://github.com/microsoft/msphpsql/pull/1095) - Removed unnecessary data structure to support Client-Side Cursors feature in Alpine Linux +- Pull Request [#1095](https://github.com/microsoft/msphpsql/pull/1107) - Fixed logging issues when both drivers are enabled in Alpine Linux + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) + ## 5.8.0 - 2020-01-31 Updated PECL release packages. Here is the list of updates: diff --git a/source/shared/version.h b/source/shared/version.h index 4369a3c8d..40ae57f62 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -27,7 +27,7 @@ // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 #define SQLVERSION_MINOR 8 -#define SQLVERSION_PATCH 0 +#define SQLVERSION_PATCH 1 #define SQLVERSION_BUILD 0 // For previews, set this constant to 1. Otherwise, set it to 0 From 55e1715f7650a6a92a8bdffa88c4d934f7e31b41 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 8 Apr 2020 15:06:33 -0700 Subject: [PATCH 204/249] Updated instructions for Alpine Linux (#1117) --- Linux-mac-install.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Linux-mac-install.md b/Linux-mac-install.md index b3decb936..86f275675 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,7 +1,7 @@ # Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server -The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 19.10, RedHat 7 and 8, Debian 8, 9, and 10, Suse 12 and 15, Alpine 3.11 (experimental), and macOS 10.13, 10.14, and 10.15. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup). +The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 19.10, RedHat 7 and 8, Debian 8, 9, and 10, Suse 12 and 15, Alpine 3.11, and macOS 10.13, 10.14, and 10.15. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup). -These instructions install PHP 7.4 by default. Note that some supported Linux distros default to PHP 7.1 or earlier, which is not supported for the latest version of the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.2 or 7.3 instead. +These instructions install PHP 7.4 by default using `pecl install`. You may need to run `pecl channel-update pecl.php.net` first. Note that some supported Linux distros default to PHP 7.1 or earlier, which is not supported for the latest version of the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.2 or 7.3 instead. Also included are instructions for installing the PHP FastCGI Process Manager, PHP-FPM, on Ubuntu. This is needed if using the nginx web server instead of Apache. @@ -293,13 +293,10 @@ To test your installation, see [Testing your installation](#testing-your-install ## Installing the drivers on Alpine 3.11 > [!NOTE] -> Alpine support is experimental. - -> [!NOTE] -> The default version of PHP is 7.3. Alternate versions of PHP are not available from other repositories for Alpine 3.11. You can instead compile PHP from source. +> The default version of PHP is 7.3. Alternate versions of PHP may be available from other repositories for Alpine 3.11. You can instead compile PHP from source. ### Step 1. Install PHP -PHP packages for Alpine are found in the `edge/community` repository. Add the following line to `/etc/apt/repositories`, replacing `` with the URL of an Alpine repository mirror: +PHP packages for Alpine can be found in the `edge/community` repository. Please check [Enable Community Repository](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository) on their WIKI page. Add the following line to `/etc/apt/repositories`, replacing `` with the URL of an Alpine repository mirror: ``` http:///alpine/edge/community ``` @@ -320,10 +317,7 @@ sudo su echo extension=pdo_sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/10_pdo_sqlsrv.ini echo extension=sqlsrv.so >> `php --ini | grep "Scan for additional .ini files" | sed -e "s|.*:\s*||"`/00_sqlsrv.ini ``` -You may need to define a locale: -``` -export LC_ALL=C -``` + ### Step 4. Install Apache and configure driver loading ``` sudo apk add php7-apache2 apache2 @@ -391,7 +385,7 @@ To test your installation, see [Testing your installation](#testing-your-install ## Testing Your Installation -To test this sample script, create a file called testsql.php in your system's document root. This is `/var/www/html/` on Ubuntu, Debian, and Redhat, `/srv/www/htdocs` on SUSE, `/var/www/localhost/htdocs` on Alpine, or `/usr/local/var/www` on macOS. Copy the following script to it, replacing the server, database, username, and password as appropriate. On Alpine 3.11, you may also need to specify the **CharacterSet** as 'UTF-8' in the `$connectionOptions` array. +To test this sample script, create a file called testsql.php in your system's document root. This is `/var/www/html/` on Ubuntu, Debian, and Redhat, `/srv/www/htdocs` on SUSE, `/var/www/localhost/htdocs` on Alpine, or `/usr/local/var/www` on macOS. Copy the following script to it, replacing the server, database, username, and password as appropriate. ``` Date: Thu, 16 Apr 2020 13:45:39 -0700 Subject: [PATCH 205/249] Updated keys and certificates for AE tests with secure enclave (#1122) --- test/functional/pdo_sqlsrv/skipif_not_hgs.inc | 4 ++++ test/functional/setup/AEV2Cert.pfx | Bin 2654 -> 2582 bytes test/functional/setup/ae_keys.sql | 20 ++++++++++++------ test/functional/sqlsrv/skipif_not_hgs.inc | 4 ++++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/test/functional/pdo_sqlsrv/skipif_not_hgs.inc b/test/functional/pdo_sqlsrv/skipif_not_hgs.inc index 80ad95f06..ccf177ab0 100644 --- a/test/functional/pdo_sqlsrv/skipif_not_hgs.inc +++ b/test/functional/pdo_sqlsrv/skipif_not_hgs.inc @@ -9,6 +9,10 @@ if (!extension_loaded("pdo_sqlsrv")) { require_once('MsSetup.inc'); +if ($attestation == 'TARGET_ATTESTATION') { + die("skip Not set up for testing with secure enclave."); +} + $conn = new PDO("sqlsrv:server = $server", $uid, $pwd); if (!$conn) { die("skip Could not connect during SKIPIF."); diff --git a/test/functional/setup/AEV2Cert.pfx b/test/functional/setup/AEV2Cert.pfx index 4a9fc5bb8ac716acbe38b94c92b5226e410e2a0c..4f99b279e354db0573fd00f0c690f00bdab04df6 100644 GIT binary patch delta 2415 zcmV-#36S>Q6qXbsFoFsa0s#Xsf(gzB2`Yw2hW8Bt2LYgh3BLq_3A-?Y3Ad3VMt_aN z9>sbHx}5?72haq91lT@aY|4R49ihn_4y-BA*QVi0yExWnvF_242GbHuX9*KGbS5U% zP;W>4=C2?J%mh=)gCc+YP-!|ME9){e+j$wKDX2s;4w5QZkfF02xa$0e!8p(*KW*29 z+D4ZZlk`oPgZ5wSCBrFc#j*f;6Mu>XhQ7W)8WGTp&?&2{Ty=QvQigG7&0BaahwaTs zFp8xApsvdD>cBAZWqGFdE&-pHF$lgIyjw_ha+7N>c_D6?*_s;&Wevnw!T)zQ6wwD< z@6GX3o-Q!hZW>1pxchnMVXXkB7t18?Z9We2Z4TySFLStjemjJ8{1k9mx__6w|ArST z(F$RH;qH`WeOgnv&jT?{;~6?-u!fn>SJIf|4E5py-iH&6sX}p)gN}}IwAczFr<|g% zBdMU>&C5|ZcuZc?3(h_>uh^snll)si;!$3k!4T?^5hX^cMyXKy)c?D*HItI;b0Ksr zd=O^~xm1mf;!i8Mef$JLhkwXBIYkc@n^0l1z^uZydGta)#$xEtkte;p3Yc&VRh|^V zHs)3M>daLR!i+~Iu0iU9SCA{Ij8cSIWZ@KLynTY7@!xFLjx*Z1Wq_tunz+)iTcN4c z3lwzQ-Bgt~yAlz*vfSUkHE;Mz4FnxrS7Kk`Oh)BcWrrOBh{gMU5#VLL;Mm1}gu;NWn-aRfNKT;>9dIEd0tOQzmN2Bfc zg8|a;e2NIOWa|_06o2A6JHbm55g3Uz4O5Ze(eZ(U%~DT~FKN}w)wTSMku#?C;jgVb z-=NlJ&mu0EZ7~u%&5%Eu_w4mw%bEI6*NtkENIv2&dGVtv_(YGPw@PFRP&yl!JNDPY zVRe(Iv0_R&d0`I|U$X=B*X=PhR2|5Gf@m06bD$P({zHAkGJpM~t1)}H%~dNI$_3rC;nWEY+?(VB8|&@ z&A@{&c5t}?eCrWl+VDqUK|m5pk$Xa9k-iu-2Z7B6KLPAlK>+pYZzMcdo*3M;E_5E1 z=mi2QEUlbTR4h6qZR&Wn*p=!HrV}sKy+pg9@OBc*QBCzP9hmhd5FDH!gqbNVl%(w*F!*0>F{xM>+_TxE-&LNQU-A~v%ck7g)LOhs z>pR-@*dqr1gy21Bxj}Qc%g7I%L^HS@jtZIAa({|eFKP*#?OF+&(M6vP!@u62AzHAA z(WCOB1US>F-_UZyR5bx=wxed&006pz6B|gE!td_4Wx|e7QmRi80A0xa5={W5$#i`32T^QRO4(`H7Azs6?$e7d$ zDM=*r`Rk6O54AwZ)e_KFRSsft&JcgG8s=dk0Q{6V05{RApiF=as;9I1xHv~ptPndk z;D!PEAA#QqEbfTTj)$|MBV($A3I@?~0Dl6xkj-Gr_}mxDB^NxDuhEQq6Vd^iM54e3 z0_5b;Is3)Bh~c<%8+>NH+2@}*M6YfQ(06Yp7t$(0;9vJoVE%uwdV0$Bq}4(k=d!#q z(c66qLYW$D!t~V@u7qKtkMp?Al>vo7P?T!^8@6i*KxWxVmj2EdI8_<4pCF9G`b@j#IJ^%do7<1o*$g2i zhyA)?D@XVnV$6PL4xQR!OstcqCrc?8?wNAl4iBSH!hAW@GHgE)@oWvlw|>36;nec+>SDZ8T z6Bjy(!p=oKZnbn(1l_e9O@uUe3>~YQqe*s4qj0**`j3zI<7&rzwvu(?dDpQ+vogb* z;(NePoLaD|n6Nbu$d3~wr+P&$XI z0$FLFajTu}21Qgo?1N)OzbXbg4h$!NoLj@#4lV}102hjW9beRAE delta 2488 zcmV;p2}kyp6y6jfFoFtN0s#Xsf(jM}2`Yw2hW8Bt2LYgh3I_y&3Ii~L3ICBIMt`x= zPVlbG>LCIG2haq91lWF`Ux=h&(-z|0Fz#^$h{(~|(|<_Y3# z2Rk4Rx1|?sdvNp8-!Tc5W`kt=VK+d`i$?L`TNAIHYAtu{Y~qg+($KvGtVDD6+j*AD zaH1m(NeRZ=+}uZgc5*r?`($*V2VCE;^X1 zhh{oCPPjcF{Tv@z)_umCY+nAQ$tf4gcxlVw2L;XJ&N8ZJSNTjZ(Ib#@^e=|XcIGZs z%VggK8?prdfsuts91yjyu%l+PY`Hk8kZF) zBGE@hKIh7)n21CY;{WZkJ!HZpnr|F>{BJ$0TTc!X-FfY?-Mq$Z)qiPUYM-kCL)BfM zZow;=Xqts|=qiL;16DxKyGK?eG6ale+Dr=xL4UfTq}DOSu&2V;O2u=hd2m&p4FTu; zog5NyTWKvQhddm$n+l_51PfVg#SwvvouB-y#jMe}9(!b%U7tlj-nl)92piREZo-qv#V>XyIC!I;ia+H?~>7W*^7TPps(}Y21v7#eo*(Ceb;ejDJn+BMd~VzHo})(o-8q z_uwDy+e#8_TDsgfa?_gfx1oA&7=pQ^uo2;luWYj^(FLrmV5Q}2bKBL)Yvp#;Wt#zS z>m^GnqWn1n_pVEeyd_cBUS~!3-5-Jl+&+cpXdKjxcD4ubvB?^wN z8^SjAxzP#^M1RD-kOO6o=K$|W&D#Y#D}m)-OVV*Qh`X<94Jy5 z-W-rT{g-ia zgkUzyVbpZu-mp1RtKQ*?Jz&81{|*mauW?R zRwgNOF@M$Iy$7HTtE2{rG)4erU&HHU*B{5A-{8MAqCHaI!9i*MXLM?tBPfJbU;N=u z;Du4O^aN{t>wuV>CaQ-(MIkd@ekIc4Nefba$uma5`I*!SZETC))K*5<8NWt{?v9oQ z`vPrX^B)B5#?`!W#PYB)fzvP(1_>&LNQUwng#eqFeg#xZ{DJL+YeA=rtmEQ8jHNbul#ZUlOA+F+Zc`b4D~%_dpzet$GLFw0>^ee{xubA9ss0(>Nsp_RH8O~5?U zmysZ;$yI;NnrxafTR3q7az*u}?qG}r`D$VnPF-M0pMCvZJ#%QPhS3c6)q2Ln`Sa)T zYC9=jojH@M&QEg`?vS{9fgsXsL)(nST=> z3l1M`d%ZLo;;&i5RDHnY;)&ZOB|VuY(GAbw0|bjmKn~~lzcugRgGzPOvX5VWffq?-xTqm^`U;U zyc58myQn6|)$$6nUr>6b?7kSuX_;HHweIH6w?mO+KR%gr55W7ZU++&H6m|IBvfEs| zA`!vIgm>nQL6zB%j2S;JpFg3ukqA^$&tW!{mlMiz0`|$=k3V5X*k3e44SzKZ(FzL< z+e|vP>x%xwxmqy;k1lR|FT*~Q)-_GKPCc1PDeyQjpbt9j$userName, "PWD"=>$userPassword); $conn = sqlsrv_connect($server, $connectionInfo); From d4c112e9af1c6f8de482c4766867f528ddecac07 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 16 Apr 2020 15:12:52 -0700 Subject: [PATCH 206/249] Modified bvt tests to work with AdventureWorks2017 as well (#1128) --- test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt | 4 ++-- .../sqlsrv/msdn_sqlsrv_connect_returnDatesAsStrings_utf8.phpt | 4 ++-- test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt | 4 ++-- test/bvt/sqlsrv/msdn_sqlsrv_get_field_string_utf8.phpt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt index de78bcfff..8e8ff1be8 100644 --- a/test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt +++ b/test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt @@ -169,10 +169,10 @@ function DisplayWarnings() } ?> --EXPECTREGEX-- -Warning: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Changed database context to 'AdventureWorks2014'. +Warning: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Changed database context to 'AdventureWorks201[4|7]'. Warning: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Changed language setting to us_english. BusinessEntityId 7 has 57 remaining vacation hours. BusinessEntityId 8 has 57 remaining vacation hours. BusinessEntityId 9 has 55 remaining vacation hours. -Error: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]The UPDATE statement conflicted with the CHECK constraint "CK_Employee_VacationHours". The conflict occurred in database "AdventureWorks2014", table "HumanResources.Employee", column 'VacationHours'. +Error: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]The UPDATE statement conflicted with the CHECK constraint "CK_Employee_VacationHours". The conflict occurred in database "AdventureWorks201[4|7]", table "HumanResources.Employee", column 'VacationHours'. Error: \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]The statement has been terminated. \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDatesAsStrings_utf8.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDatesAsStrings_utf8.phpt index 7bf2a839f..a6de76830 100644 --- a/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDatesAsStrings_utf8.phpt +++ b/test/bvt/sqlsrv/msdn_sqlsrv_connect_returnDatesAsStrings_utf8.phpt @@ -34,5 +34,5 @@ if ( $date === false ) { echo $date; sqlsrv_close( $conn); ?> ---EXPECT-- -2014-02-20 04:26:00.000 \ No newline at end of file +--EXPECTREGEX-- +2014-02-20 04:26:00.000|2017-08-22 19:39:35.643 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt index a0013c49a..0e5f6c0b5 100644 --- a/test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt +++ b/test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt @@ -36,5 +36,5 @@ echo "Date = $date_string\n"; sqlsrv_close( $conn); ?> ---EXPECT-- -Date = 20th, February 2014 \ No newline at end of file +--EXPECTREGEX-- +Date = 20th, February 2014|Date = 22nd, August 2017 \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_get_field_string_utf8.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_string_utf8.phpt index 22ab346cc..e28258d10 100644 --- a/test/bvt/sqlsrv/msdn_sqlsrv_get_field_string_utf8.phpt +++ b/test/bvt/sqlsrv/msdn_sqlsrv_get_field_string_utf8.phpt @@ -36,5 +36,5 @@ echo $date; sqlsrv_close( $conn); ?> ---EXPECT-- -2014-02-20 04:26:00.000 \ No newline at end of file +--EXPECTREGEX-- +2014-02-20 04:26:00.000|2017-08-22 19:39:35.643 \ No newline at end of file From 92f9810edda7962bc671c1494708510cd05cd5ae Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 16 Apr 2020 15:29:37 -0700 Subject: [PATCH 207/249] More locale cases (#1115) --- Dockerfile-msphpsql | 1 + azure-pipelines.yml | 1 + source/shared/localization.hpp | 1 + source/shared/localizationimpl.cpp | 6 ++ .../pdo_sqlsrv/pdo_ansi_locale_fr.phpt | 79 ++++++++++++++++ .../pdo_sqlsrv/skipif_unix_ansitests.inc | 16 ++++ .../sqlsrv/skipif_unix_ansitests.inc | 16 ++++ .../sqlsrv/sqlsrv_ansi_locale_fr.phpt | 93 +++++++++++++++++++ 8 files changed, 213 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt create mode 100644 test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc create mode 100644 test/functional/sqlsrv/skipif_unix_ansitests.inc create mode 100644 test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 31eba77a1..477740a00 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -42,6 +42,7 @@ ENV PATH="/opt/mssql-tools/bin:${PATH}" # add locales for testing RUN sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen +RUN sed -i 's/# fr_FR@euro ISO-8859-15/fr_FR@euro ISO-8859-15/g' /etc/locale.gen RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen RUN sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen RUN locale-gen diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4444203fe..23579eab0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -111,6 +111,7 @@ jobs: - script: | sudo sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen + sudo sed -i 's/# fr_FR@euro ISO-8859-15/fr_FR@euro ISO-8859-15/g' /etc/locale.gen sudo sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen sudo sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen sudo locale-gen diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index ec339d8c6..4f2df190e 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -247,6 +247,7 @@ inline UINT SystemLocale::MaxCharCchSize( UINT codepage ) switch ( codepage ) { case CP_UTF8: + case 54936: return 4; case 932: case 936: diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index 22cc8bf6c..f3ca7f5cb 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -77,6 +77,7 @@ const cp_iconv cp_iconv::g_cp_iconv[] = { { 1256, "CP1256" TRANSLIT }, { 1257, "CP1257" TRANSLIT }, { 1258, "CP1258" TRANSLIT }, + { 54936, "GB18030" TRANSLIT}, { CP_ISO8859_1, "ISO8859-1" TRANSLIT }, { CP_ISO8859_2, "ISO8859-2" TRANSLIT }, { CP_ISO8859_3, "ISO8859-3" TRANSLIT }, @@ -342,6 +343,11 @@ SystemLocale::SystemLocale( const char * localeName ) const LocaleCP lcpTable[] = { { "utf8", CP_UTF8 }, { "UTF-8", CP_UTF8 }, + { "BIG5", 950 }, + { "BIG5-HKSCS", 950 }, + { "gb18030", 54936 }, + { "gb2312", 936 }, + { "gbk", 936 }, CPxxx(1252), CPxxx(850), CPxxx(437), CPxxx(874), CPxxx(932), CPxxx(936), CPxxx(949), CPxxx(950), CPxxx(1250), CPxxx(1251), CPxxx(1253), CPxxx(1254), CPxxx(1255), CPxxx(1256), CPxxx(1257), CPxxx(1258), ISO8859(1), ISO8859(2), ISO8859(3), ISO8859(4), ISO8859(5), ISO8859(6), diff --git a/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt b/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt new file mode 100644 index 000000000..d54790538 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt @@ -0,0 +1,79 @@ +--TEST-- +Test another ANSI encoding fr_FR euro locale outside Windows +--DESCRIPTION-- +This file must be saved in ANSI encoding and the required locale must be present +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +prepare($tsql); + + for ($i = 0; $i < count($inputs); $i++) { + $stmt->execute(array($i, $inputs[$i])); + } + } catch( PDOException $e ) { + echo "Failed to insert data\n"; + print_r( $e->getMessage() ); + } +} + +function dropTable($conn, $tableName) +{ + $tsql = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" . $tableName . "') AND type in (N'U')) DROP TABLE $tableName"; + $conn->exec($tsql); +} + +require_once('MsSetup.inc'); + +try { + $locale = 'fr_FR@euro'; + setlocale(LC_ALL, $locale); + + $conn = new PDO("sqlsrv:server = $server; database=$databaseName; driver=$driver", $uid, $pwd); + $conn->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_SYSTEM); + $tableName = "pdo_ansitest_FR"; + + dropTable($conn, $tableName); + + $tsql = "CREATE TABLE $tableName([id] [int] NOT NULL, [phrase] [varchar](50) NULL)"; + $conn->exec($tsql); + + $inputs = array("À tout à l'heure!", + "Je suis désolé.", + "À plus!", + " Je dois aller à l'école."); + + // Next, insert the strings + insertData($conn, $tableName, $inputs); + + // Next, fetch the strings + $tsql = "SELECT phrase FROM $tableName ORDER by id"; + $stmt = $conn->query($tsql); + + $results = $stmt->fetchAll(PDO::FETCH_NUM); + for ($i = 0; $i < count($inputs); $i++) { + if ($results[$i][0] !== $inputs[$i]) { + echo "Unexpected phrase retrieved:\n"; + var_dump($results[$i][0]); + } + } + + dropTable($conn, $tableName); + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + print_r($e->getMessage()); +} + +echo "Done" . PHP_EOL; +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc b/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc new file mode 100644 index 000000000..8029c9a33 --- /dev/null +++ b/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_unix_ansitests.inc b/test/functional/sqlsrv/skipif_unix_ansitests.inc new file mode 100644 index 000000000..84d4776f7 --- /dev/null +++ b/test/functional/sqlsrv/skipif_unix_ansitests.inc @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt b/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt new file mode 100644 index 000000000..c8bd131f6 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt @@ -0,0 +1,93 @@ +--TEST-- +Test another ansi encoding fr_FR euro locale outside Windows +--DESCRIPTION-- +This file must be saved in ANSI encoding and the required locale must be present +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done + From 14dbf79d395d7536f4155c21b2201c6f5b49fe79 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 17 Apr 2020 07:31:17 -0700 Subject: [PATCH 208/249] Added additional check in skipif for ansi locale tests (#1129) --- test/functional/pdo_sqlsrv/MsSetup.inc | 1 + test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc | 5 +++++ test/functional/sqlsrv/skipif_unix_ansitests.inc | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/test/functional/pdo_sqlsrv/MsSetup.inc b/test/functional/pdo_sqlsrv/MsSetup.inc index 1895f5aad..deea76597 100644 --- a/test/functional/pdo_sqlsrv/MsSetup.inc +++ b/test/functional/pdo_sqlsrv/MsSetup.inc @@ -37,6 +37,7 @@ $daasMode = false; $marsMode = true; $dsnMode = true; $traceEnabled = false; +$localeDisabled = false; // column encryption variables $keystore = "none"; // key store provider, acceptable values are none, win, ksp, akv diff --git a/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc b/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc index 8029c9a33..3e8b436fd 100644 --- a/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc +++ b/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc @@ -8,6 +8,11 @@ if (!extension_loaded("pdo_sqlsrv")) { die("skip Extension not loaded"); } +require_once('MsSetup.inc'); +if ($localeDisabled) { + die("skip not set up to test ansi locale"); +} + $loc = setlocale(LC_ALL, 'fr_FR@euro'); if (empty($loc)) { die("skip required French locale not available"); diff --git a/test/functional/sqlsrv/skipif_unix_ansitests.inc b/test/functional/sqlsrv/skipif_unix_ansitests.inc index 84d4776f7..42b0d7491 100644 --- a/test/functional/sqlsrv/skipif_unix_ansitests.inc +++ b/test/functional/sqlsrv/skipif_unix_ansitests.inc @@ -8,6 +8,11 @@ if (!extension_loaded("sqlsrv")) { die("skip extension not loaded"); } +require_once('MsSetup.inc'); +if ($localeDisabled) { + die("skip not set up to test ansi locale"); +} + $loc = setlocale(LC_ALL, 'fr_FR@euro'); if (empty($loc)) { die("skip required French locale not available"); From 1aca27824502a7202d70372b4e45e456e285b378 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 20 Apr 2020 15:17:21 -0700 Subject: [PATCH 209/249] Remicollet issue tsrmls (#1130) --- source/pdo_sqlsrv/pdo_dbh.cpp | 144 ++++++------ source/pdo_sqlsrv/pdo_init.cpp | 26 +-- source/pdo_sqlsrv/pdo_parser.cpp | 30 +-- source/pdo_sqlsrv/pdo_stmt.cpp | 126 +++++----- source/pdo_sqlsrv/pdo_util.cpp | 38 ++-- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 55 ++--- source/shared/core_conn.cpp | 128 +++++------ source/shared/core_init.cpp | 10 +- source/shared/core_results.cpp | 60 ++--- source/shared/core_sqlsrv.h | 304 ++++++++++++------------- source/shared/core_stmt.cpp | 283 +++++++++++------------ source/shared/core_stream.cpp | 14 +- source/shared/core_util.cpp | 40 ++-- source/sqlsrv/conn.cpp | 140 ++++++------ source/sqlsrv/init.cpp | 8 +- source/sqlsrv/php_sqlsrv_int.h | 61 +++-- source/sqlsrv/stmt.cpp | 165 +++++++------- source/sqlsrv/util.cpp | 54 ++--- 18 files changed, 826 insertions(+), 860 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 9667500e3..21d81e706 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -113,19 +113,18 @@ const stmt_option PDO_STMT_OPTS[] = { // boolean connection string struct pdo_bool_conn_str_func { - static void func( _In_ connection_option const* option, _Inout_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC ); + static void func( _In_ connection_option const* option, _Inout_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str ); }; struct pdo_txn_isolation_conn_attr_func { - static void func( connection_option const* /*option*/, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ); + static void func( connection_option const* /*option*/, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ ); }; struct pdo_int_conn_str_func { - static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC ) + static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str ) { - TSRMLS_C; SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "Wrong zval type for this keyword" ) std::string val_str = Z_STRVAL_P( value ); @@ -140,14 +139,14 @@ struct pdo_int_conn_str_func { template struct pdo_int_conn_attr_func { - static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ ) { try { SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "pdo_int_conn_attr_func: Unexpected zval type." ); size_t val = static_cast( atoi( Z_STRVAL_P( value )) ); - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( val ), SQL_IS_UINTEGER TSRMLS_CC ); + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( val ), SQL_IS_UINTEGER ); } catch( core::CoreException& ) { throw; @@ -158,12 +157,12 @@ struct pdo_int_conn_attr_func { template struct pdo_bool_conn_attr_func { - static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ ) { try { core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( core_str_zval_is_true( value )), - SQL_IS_UINTEGER TSRMLS_CC ); + SQL_IS_UINTEGER ); } catch( core::CoreException& ) { throw; @@ -173,8 +172,8 @@ struct pdo_bool_conn_attr_func { // statement options related functions void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ HashTable* options_ht, - _Inout_ zval** data TSRMLS_DC ); -void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ); + _Inout_ zval** data ); +void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht ); } // namespace @@ -438,35 +437,35 @@ const connection_option PDO_CONN_OPTS[] = { // close the connection -int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ); +int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh ); // execute queries int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql, - _Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options TSRMLS_DC ); -zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len TSRMLS_DC ); + _Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options ); +zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len ); // transaction support functions -int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ); -int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ); -int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ); +int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh ); +int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh ); +int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh ); // attribute functions -int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val TSRMLS_DC ); -int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value TSRMLS_DC ); +int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val ); +int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value ); // return more information int pdo_sqlsrv_dbh_return_error( _In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, - _Out_ zval *info TSRMLS_DC); + _Out_ zval *info); // return the last id generated by an executed SQL statement -char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len TSRMLS_DC ); +char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len ); // additional methods are supported in this function -pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh, int kind TSRMLS_DC ); +pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh, int kind ); // quote a string, meaning put quotes around it and escape any quotes within it int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquotedlen) const char* unquoted, _In_ size_t unquotedlen, _Outptr_result_buffer_(*quotedlen) char **quoted, _Out_ size_t* quotedlen, - enum pdo_param_type paramtype TSRMLS_DC ); + enum pdo_param_type paramtype ); struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { @@ -498,8 +497,8 @@ struct pdo_dbh_methods pdo_sqlsrv_dbh_methods = { } // constructor for the internal object for connections -pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ) : - sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 TSRMLS_CC ), +pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver ) : + sqlsrv_conn( h, e, driver, SQLSRV_ENCODING_UTF8 ), stmts( NULL ), direct_query( false ), query_timeout( QUERY_TIMEOUT_INVALID ), @@ -532,7 +531,7 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo // driver_options - A HashTable (within the zval) of options to use when creating the connection. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options TSRMLS_DC) +int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options) { PDO_LOG_DBH_ENTRY; @@ -570,12 +569,12 @@ int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_ ALLOC_HASHTABLE( pdo_conn_options_ht ); core::sqlsrv_zend_hash_init( *g_pdo_henv_cp, pdo_conn_options_ht, 10 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_PTR_DTOR, 0 /*persistent*/ ); // Either of g_pdo_henv_cp or g_pdo_henv_ncp can be used to propogate the error. dsn_parser = new ( sqlsrv_malloc( sizeof( conn_string_parser ))) conn_string_parser( *g_pdo_henv_cp, dbh->data_source, static_cast( dbh->data_source_len ), pdo_conn_options_ht ); - dsn_parser->parse_conn_string( TSRMLS_C ); + dsn_parser->parse_conn_string(); // Extract the server name temp_server_z = zend_hash_index_find( pdo_conn_options_ht, PDO_CONN_OPTION_SERVER ); @@ -593,7 +592,7 @@ int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_ sqlsrv_conn* conn = core_sqlsrv_connect( *g_pdo_henv_cp, *g_pdo_henv_ncp, core::allocate_conn, Z_STRVAL( server_z ), dbh->username, dbh->password, pdo_conn_options_ht, pdo_sqlsrv_handle_dbh_error, - PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" TSRMLS_CC ); + PDO_CONN_OPTS, dbh, "pdo_sqlsrv_db_handle_factory" ); // Free the string in server_z after being used zend_string_release( Z_STR( server_z )); @@ -634,7 +633,7 @@ int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_ // dbh - The PDO managed connection object. // Return: // Always returns 1 for success. -int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) +int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh ) { LOG( SEV_NOTICE, "pdo_sqlsrv_dbh_close: entering" ); @@ -647,7 +646,7 @@ int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) PDO_RESET_DBH_ERROR; // call the core layer close - core_sqlsrv_close( reinterpret_cast( dbh->driver_data ) TSRMLS_CC ); + core_sqlsrv_close( reinterpret_cast( dbh->driver_data ) ); dbh->driver_data = NULL; // always return success that the connection is closed @@ -666,7 +665,7 @@ int pdo_sqlsrv_dbh_close( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) // Return: // 0 for failure, 1 for success. int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql, - _Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options TSRMLS_DC ) + _Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -692,14 +691,14 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( pdo_stmt_options_ht ); core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_PTR_DTOR, 0 /*persistent*/ ); // Either of g_pdo_henv_cp or g_pdo_henv_ncp can be used to propogate the error. - validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht TSRMLS_CC ); + validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht ); driver_stmt = static_cast( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, pdo_stmt_options_ht, PDO_STMT_OPTS, - pdo_sqlsrv_handle_stmt_error, stmt TSRMLS_CC )); + pdo_sqlsrv_handle_stmt_error, stmt )); // if the user didn't set anything in the prepare options, then set the buffer limit // to the value set on the connection. @@ -714,7 +713,7 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch // rewrite the query to map named parameters to positional parameters. We do this rather than use the ODBC named // parameters for consistency with the PDO MySQL and PDO ODBC drivers. - int zr = pdo_parse_params( stmt, const_cast( sql ), sql_len, &sql_rewrite, &sql_rewrite_len TSRMLS_CC ); + int zr = pdo_parse_params( stmt, const_cast( sql ), sql_len, &sql_rewrite, &sql_rewrite_len ); CHECK_ZEND_ERROR( zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) { throw core::CoreException(); @@ -728,7 +727,7 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) { - core_sqlsrv_prepare( driver_stmt, sql, sql_len TSRMLS_CC ); + core_sqlsrv_prepare( driver_stmt, sql, sql_len ); } else if( driver_stmt->direct_query ) { @@ -745,10 +744,10 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) { // parse placeholders in the sql query into the placeholders ht ALLOC_HASHTABLE( placeholders ); - core::sqlsrv_zend_hash_init( *driver_dbh, placeholders, 5, ZVAL_PTR_DTOR /* dtor */, 0 /* persistent */ TSRMLS_CC ); + core::sqlsrv_zend_hash_init( *driver_dbh, placeholders, 5, ZVAL_PTR_DTOR /* dtor */, 0 /* persistent */ ); sql_parser = new ( sqlsrv_malloc( sizeof( sql_string_parser ))) sql_string_parser( *driver_dbh, stmt->query_string, static_cast(stmt->query_stringlen), placeholders ); - sql_parser->parse_sql_string( TSRMLS_C ); + sql_parser->parse_sql_string(); driver_stmt->placeholders = placeholders; placeholders.transferred(); } @@ -796,7 +795,7 @@ int pdo_sqlsrv_dbh_prepare( _Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const ch // sql_len - length of sql query // Return // # of rows affected, -1 for an error. -zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len TSRMLS_DC ) +zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) const char *sql, _In_ size_t sql_len ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -821,23 +820,23 @@ zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) c temp_stmt.dbh = dbh; // allocate a full driver statement to take advantage of the error handling driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, - NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); + NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt ); driver_stmt->set_func( __FUNCTION__ ); - SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt TSRMLS_CC, sql, static_cast( sql_len ) ); + SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt, sql, static_cast( sql_len ) ); // since the user can give us a compound statement, we return the row count for the last set, and since the row count // isn't guaranteed to be valid until all the results have been fetched, we fetch them all first. - if ( execReturn != SQL_NO_DATA && core_sqlsrv_has_any_result( driver_stmt TSRMLS_CC )) { + if ( execReturn != SQL_NO_DATA && core_sqlsrv_has_any_result( driver_stmt )) { SQLRETURN r = SQL_SUCCESS; do { - rows = core::SQLRowCount( driver_stmt TSRMLS_CC ); + rows = core::SQLRowCount( driver_stmt ); - r = core::SQLMoreResults( driver_stmt TSRMLS_CC ); + r = core::SQLMoreResults( driver_stmt ); } while ( r != SQL_NO_DATA ); } @@ -885,7 +884,7 @@ zend_long pdo_sqlsrv_dbh_do( _Inout_ pdo_dbh_t *dbh, _In_reads_bytes_(sql_len) c // dbh - The PDO managed connection object. // Return: // 0 for failure and 1 for success. -int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) +int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -901,7 +900,7 @@ int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) DEBUG_SQLSRV_ASSERT( !dbh->in_txn, "pdo_sqlsrv_dbh_begin: Already in transaction" ); - core_sqlsrv_begin_transaction( driver_conn TSRMLS_CC ); + core_sqlsrv_begin_transaction( driver_conn ); return 1; } @@ -927,7 +926,7 @@ int pdo_sqlsrv_dbh_begin( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) // dbh - The PDO managed connection object. // Return: // 0 for failure and 1 for success. -int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) +int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -943,7 +942,7 @@ int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_commit: Not in transaction" ); - core_sqlsrv_commit( driver_conn TSRMLS_CC ); + core_sqlsrv_commit( driver_conn ); return 1; } @@ -967,7 +966,7 @@ int pdo_sqlsrv_dbh_commit( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) // dbh - The PDO managed connection object. // Return: // 0 for failure and 1 for success. -int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) +int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -982,7 +981,7 @@ int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) DEBUG_SQLSRV_ASSERT( dbh->in_txn, "pdo_sqlsrv_dbh_rollback: Not in transaction" ); - core_sqlsrv_rollback( driver_conn TSRMLS_CC ); + core_sqlsrv_rollback( driver_conn ); return 1; } @@ -1006,7 +1005,7 @@ int pdo_sqlsrv_dbh_rollback( _Inout_ pdo_dbh_t *dbh TSRMLS_DC ) // val - The value of the attribute to be set. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val TSRMLS_DC ) +int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *val ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -1169,7 +1168,7 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout // return_value - zval in which to return the attribute value. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value TSRMLS_DC ) +int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ zval *return_value ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -1213,26 +1212,26 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout case PDO_ATTR_SERVER_INFO: { - core_sqlsrv_get_server_info( driver_dbh, return_value TSRMLS_CC ); + core_sqlsrv_get_server_info( driver_dbh, return_value ); break; } case PDO_ATTR_SERVER_VERSION: { - core_sqlsrv_get_server_version( driver_dbh, return_value TSRMLS_CC ); + core_sqlsrv_get_server_version( driver_dbh, return_value ); break; } case PDO_ATTR_CLIENT_VERSION: { - core_sqlsrv_get_client_info( driver_dbh, return_value TSRMLS_CC ); + core_sqlsrv_get_client_info( driver_dbh, return_value ); //Add the PDO SQLSRV driver's file version //Declarations below eliminate compiler warnings about string constant to char* conversions const char* extver = "ExtensionVer"; std::string filever = VER_FILEVERSION_STR; core::sqlsrv_add_assoc_string( *driver_dbh, return_value, extver, &filever[0], 1 /*duplicate*/ - TSRMLS_CC ); + ); break; } @@ -1315,7 +1314,7 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout // Return: // 0 for failure, 1 for success. int pdo_sqlsrv_dbh_return_error( _In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, - _Out_ zval *info TSRMLS_DC) + _Out_ zval *info) { SQLSRV_ASSERT( dbh != NULL || stmt != NULL, "Either dbh or stmt must not be NULL to dereference the error." ); @@ -1341,7 +1340,7 @@ int pdo_sqlsrv_dbh_return_error( _In_ pdo_dbh_t *dbh, _In_opt_ pdo_stmt_t *stmt, // len - Length of the name. // Return: // Returns the last insert id as a string. -char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len TSRMLS_DC ) +char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, _Out_ size_t* len ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -1368,7 +1367,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, else { char* quoted_table = NULL; size_t quoted_len = 0; - int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strnlen_s( name ), "ed_table, "ed_len, PDO_PARAM_NULL TSRMLS_CC ); + int quoted = pdo_sqlsrv_dbh_quote( dbh, name, strnlen_s( name ), "ed_table, "ed_len, PDO_PARAM_NULL ); SQLSRV_ASSERT( quoted, "PDO::lastInsertId failed to quote the table name."); snprintf( last_insert_id_query, LAST_INSERT_ID_QUERY_MAX_LEN, SEQUENCE_CURRENT_VALUE_QUERY, quoted_table ); sqlsrv_free( quoted_table ); @@ -1379,7 +1378,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, temp_stmt.dbh = dbh; // allocate a full driver statement to take advantage of the error handling - driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt TSRMLS_CC ); + driver_stmt = core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt, NULL /*options_ht*/, NULL /*valid_stmt_opts*/, pdo_sqlsrv_handle_stmt_error, &temp_stmt ); driver_stmt->set_func( __FUNCTION__ ); @@ -1392,11 +1391,11 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, } // execute the last insert id query - core::SQLExecDirectW( driver_stmt, wsql_string TSRMLS_CC ); + core::SQLExecDirectW( driver_stmt, wsql_string ); - core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); + core::SQLFetchScroll( driver_stmt, SQL_FETCH_NEXT, 0 ); SQLRETURN r = core::SQLGetData( driver_stmt, 1, SQL_C_CHAR, id_str, LAST_INSERT_ID_BUFF_LEN, - reinterpret_cast( len ), false TSRMLS_CC ); + reinterpret_cast( len ), false ); CHECK_CUSTOM_ERROR( (!SQL_SUCCEEDED( r ) || *len == SQL_NULL_DATA || *len == SQL_NO_TOTAL), driver_stmt, PDO_SQLSRV_ERROR_LAST_INSERT_ID ) { @@ -1442,7 +1441,7 @@ char * pdo_sqlsrv_dbh_last_id( _Inout_ pdo_dbh_t *dbh, _In_z_ const char *name, // Return: // 0 for failure, 1 for success. int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const char* unquoted, _In_ size_t unquoted_len, _Outptr_result_buffer_(*quoted_len) char **quoted, _Out_ size_t* quoted_len, - enum pdo_param_type paramtype TSRMLS_DC ) + enum pdo_param_type paramtype ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -1602,7 +1601,7 @@ int pdo_sqlsrv_dbh_quote( _Inout_ pdo_dbh_t* dbh, _In_reads_(unquoted_len) const } // This method is not implemented by this driver. -pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh, int kind TSRMLS_DC ) +pdo_sqlsrv_function_entry *pdo_sqlsrv_get_driver_methods( _Inout_ pdo_dbh_t *dbh, int kind ) { PDO_RESET_DBH_ERROR; PDO_VALIDATE_CONN; @@ -1622,7 +1621,7 @@ namespace { // Maps the PDO driver specific statement option/attribute constants to the core layer // statement option/attribute constants. void add_stmt_option_key(_Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ HashTable* options_ht, - _Inout_ zval* data TSRMLS_DC) + _Inout_ zval* data) { zend_ulong option_key = -1; switch (key) { @@ -1689,7 +1688,7 @@ void add_stmt_option_key(_Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ H // if a PDO handled option makes it through (such as PDO_ATTR_STATEMENT_CLASS, just skip it if (option_key != -1) { zval_add_ref(data); - core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data TSRMLS_CC); + core::sqlsrv_zend_hash_index_update(ctx, options_ht, option_key, data); } } @@ -1702,7 +1701,7 @@ void add_stmt_option_key(_Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ H // ctx - The current context. // stmt_options - The user provided list of statement options. // pdo_stmt_options_ht - Output hashtable of statement options. -void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht TSRMLS_DC ) +void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* pdo_stmt_options_ht ) { try { @@ -1720,7 +1719,7 @@ void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_opti throw core::CoreException(); } - add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data TSRMLS_CC ); + add_stmt_option_key( ctx, int_key, pdo_stmt_options_ht, data ); } ZEND_HASH_FOREACH_END(); } } @@ -1731,9 +1730,8 @@ void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_opti } -void pdo_bool_conn_str_func::func( _In_ connection_option const* option, _Inout_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC ) +void pdo_bool_conn_str_func::func( _In_ connection_option const* option, _Inout_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str ) { - TSRMLS_C; char const* val_str = "no"; if( core_str_zval_is_true( value ) ) { @@ -1748,7 +1746,7 @@ void pdo_bool_conn_str_func::func( _In_ connection_option const* option, _Inout_ } void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/, _In_ zval* value_z, _Inout_ sqlsrv_conn* conn, - std::string& /*conn_str*/ TSRMLS_DC ) + std::string& /*conn_str*/ ) { try { @@ -1800,7 +1798,7 @@ void pdo_txn_isolation_conn_attr_func::func( connection_option const* /*option*/ } } - core::SQLSetConnectAttr( conn, SQL_COPT_SS_TXN_ISOLATION, reinterpret_cast( out_val ), SQL_IS_UINTEGER TSRMLS_CC ); + core::SQLSetConnectAttr( conn, SQL_COPT_SS_TXN_ISOLATION, reinterpret_cast( out_val ), SQL_IS_UINTEGER ); } catch( core::CoreException& ) { diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index e471561f0..b0ceaf4cf 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -53,8 +53,8 @@ pdo_driver_t pdo_sqlsrv_driver = { // functions to register SQLSRV constants with the PDO class // (It's in all CAPS so it looks like the Zend macros that do similar work) -void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( _In_z_ char const* name, _In_ long value TSRMLS_DC ); -void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( _In_z_ char const* name, _In_z_ char const* value TSRMLS_DC ); +void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( _In_z_ char const* name, _In_ long value ); +void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( _In_z_ char const* name, _In_z_ char const* value ); struct sqlsrv_attr_pdo_constant { const char *name; @@ -154,18 +154,18 @@ PHP_MINIT_FUNCTION(pdo_sqlsrv) // register all attributes supported by this driver. for( int i= 0; pdo_attr_constants[i].name != NULL; ++i ) { - REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( pdo_attr_constants[i].name, pdo_attr_constants[i].value TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( pdo_attr_constants[i].name, pdo_attr_constants[i].value ); } - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE TSRMLS_CC ); - REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT TSRMLS_CC ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_UNCOMMITTED", PDOTxnIsolationValues::READ_UNCOMMITTED ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_READ_COMMITTED", PDOTxnIsolationValues::READ_COMMITTED ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_REPEATABLE_READ", PDOTxnIsolationValues::REPEATABLE_READ ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SERIALIZABLE", PDOTxnIsolationValues::SERIALIZABLE ); + REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( "SQLSRV_TXN_SNAPSHOT", PDOTxnIsolationValues::SNAPSHOT ); // retrieve the handles for the environments - core_sqlsrv_minit( &g_pdo_henv_cp, &g_pdo_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" TSRMLS_CC ); + core_sqlsrv_minit( &g_pdo_henv_cp, &g_pdo_henv_ncp, pdo_sqlsrv_handle_env_error, "PHP_MINIT_FUNCTION for pdo_sqlsrv" ); } catch( ... ) { @@ -274,23 +274,23 @@ namespace { // mimic the functionality of the REGISTER_PDO_CLASS_CONST_LONG. We use this instead of the macro because // we dynamically link the pdo_get_dbh_class function rather than use the static php_pdo_get_dbh_ce (see MINIT) - void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( _In_z_ char const* name, _In_ long value TSRMLS_DC ) + void REGISTER_PDO_SQLSRV_CLASS_CONST_LONG( _In_z_ char const* name, _In_ long value ) { zend_class_entry* zend_class = php_pdo_get_dbh_ce(); SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_LONG: php_pdo_get_dbh_ce failed"); - int zr = zend_declare_class_constant_long( zend_class, const_cast( name ), strlen( name ), value TSRMLS_CC ); + int zr = zend_declare_class_constant_long( zend_class, const_cast( name ), strlen( name ), value ); if( zr == FAILURE ) { throw core::CoreException(); } } - void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( _In_z_ char const* name, _In_z_ char const* value TSRMLS_DC ) + void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( _In_z_ char const* name, _In_z_ char const* value ) { zend_class_entry* zend_class = php_pdo_get_dbh_ce(); SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_STRING: php_pdo_get_dbh_ce failed"); - int zr = zend_declare_class_constant_string( zend_class, const_cast( name ), strlen( name ), const_cast( value ) TSRMLS_CC ); + int zr = zend_declare_class_constant_string( zend_class, const_cast( name ), strlen( name ), const_cast( value ) ); if( zr == FAILURE ) { throw core::CoreException(); diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 48b591be9..fed2e2ca5 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -122,7 +122,7 @@ bool string_parser::discard_white_spaces() } // Add a key-value pair to the hashtable -void string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC ) +void string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ int len ) { zval value_z; ZVAL_UNDEF( &value_z ); @@ -136,19 +136,19 @@ void string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ ZVAL_STRINGL( &value_z, const_cast( value ), len ); } - core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z TSRMLS_CC ); + core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z ); } // Add a key-value pair to the hashtable with int value -void sql_string_parser::add_key_int_value_pair( _In_ unsigned int value TSRMLS_DC ) { +void sql_string_parser::add_key_int_value_pair( _In_ unsigned int value ) { zval value_z; ZVAL_LONG( &value_z, value ); - core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z TSRMLS_CC ); + core::sqlsrv_zend_hash_index_update( *ctx, this->element_ht, this->current_key, &value_z ); } // Validate a given DSN keyword. -void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC ) +void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len ) { int new_len = discard_trailing_white_spaces( key, key_len ); @@ -173,7 +173,7 @@ void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Ino THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast( key_name ) ); } -void conn_string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC ) +void conn_string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ int len ) { // if the keyword is 'Authentication', check whether the user specified option is supported bool valid = true; @@ -208,7 +208,7 @@ inline bool sql_string_parser::is_placeholder_char( char c ) } // Primary function which parses the connection string/DSN. -void conn_string_parser:: parse_conn_string( TSRMLS_D ) +void conn_string_parser:: parse_conn_string( void ) { States state = FirstKeyValuePair; // starting state int start_pos = -1; @@ -244,7 +244,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } } - this->validate_key( &( this->orig_str[start_pos] ), ( pos - start_pos ) TSRMLS_CC ); + this->validate_key( &( this->orig_str[start_pos] ), ( pos - start_pos ) ); state = Value; @@ -261,7 +261,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) // if EOS encountered after 0 or more spaces OR semi-colon encountered. if( !discard_white_spaces() || this->orig_str[pos] == ';' ) { - add_key_value_pair( NULL, 0 TSRMLS_CC ); + add_key_value_pair( NULL, 0 ); if( this->is_eos() ) { @@ -323,7 +323,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) state = NextKeyValuePair; } - add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos ); SQLSRV_ASSERT((( state == NextKeyValuePair ) || ( this->is_eos() )), "conn_string_parser::parse_conn_string: Invalid state encountered " ); @@ -338,7 +338,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) if( !next() ) { // EOS - add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos ); break; } @@ -365,7 +365,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) if( ! this->discard_white_spaces() ) { //EOS - add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos ); break; } } @@ -373,7 +373,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) // if semi-colon than go to next key-value pair if ( this->orig_str[pos] == ';' ) { - add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), end_pos - start_pos ); state = NextKeyValuePair; break; } @@ -417,7 +417,7 @@ void conn_string_parser:: parse_conn_string( TSRMLS_D ) } // Primary function which parses out the named placeholders from a sql string. -void sql_string_parser::parse_sql_string( TSRMLS_D ) { +void sql_string_parser::parse_sql_string( void ) { try { int start_pos = -1; while ( !this->is_eos() ) { @@ -447,7 +447,7 @@ void sql_string_parser::parse_sql_string( TSRMLS_D ) { while ( is_placeholder_char( this->orig_str[pos] )) { next(); } - add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos TSRMLS_CC ); + add_key_value_pair( &( this->orig_str[start_pos] ), this->pos - start_pos ); discard_white_spaces(); // if an '=' is right after a placeholder, it means the placeholder is for output parameters // and emulate prepare does not support output parameters diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 37afbf380..faf78fcc1 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -53,7 +53,7 @@ inline SQLSMALLINT pdo_fetch_ori_to_odbc_fetch_ori ( _In_ enum pdo_fetch_orienta // Returns SQLSRV data type for a given PDO type. See pdo_param_type // for list of supported pdo types. -SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _In_ enum pdo_param_type pdo_type TSRMLS_DC ) +SQLSRV_PHPTYPE pdo_type_to_sqlsrv_php_type( _Inout_ sqlsrv_stmt* driver_stmt, _In_ enum pdo_param_type pdo_type ) { pdo_sqlsrv_stmt *pdo_stmt = static_cast(driver_stmt); SQLSRV_ASSERT(pdo_stmt != NULL, "pdo_type_to_sqlsrv_php_type: pdo_stmt object was null"); @@ -137,7 +137,7 @@ inline pdo_param_type sql_type_to_pdo_type( _In_ SQLSMALLINT sql_type ) // Calls core_sqlsrv_set_scrollable function to set cursor. // PDO supports two cursor types: PDO_CURSOR_FWDONLY, PDO_CURSOR_SCROLL. -void set_stmt_cursors( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC ) +void set_stmt_cursors( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z ) { if( Z_TYPE_P( value_z ) != IS_LONG ) { @@ -161,10 +161,10 @@ void set_stmt_cursors( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC ) THROW_PDO_ERROR( stmt, PDO_SQLSRV_ERROR_INVALID_CURSOR_TYPE ); } - core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); + core_sqlsrv_set_scrollable( stmt, odbc_cursor_type ); } -void set_stmt_cursor_scroll_type( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC ) +void set_stmt_cursor_scroll_type( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z ) { if( Z_TYPE_P( value_z ) != IS_LONG ) { @@ -178,14 +178,14 @@ void set_stmt_cursor_scroll_type( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z long odbc_cursor_type = static_cast( Z_LVAL_P( value_z ) ); - core_sqlsrv_set_scrollable( stmt, odbc_cursor_type TSRMLS_CC ); + core_sqlsrv_set_scrollable( stmt, odbc_cursor_type ); return; } // Sets the statement encoding. Default encoding on the statement // implies use the connection's encoding. -void set_stmt_encoding( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC ) +void set_stmt_encoding( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z ) { // validate the value if( Z_TYPE_P( value_z ) != IS_LONG ) { @@ -280,20 +280,20 @@ zval convert_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_t } // namespace -int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ); -int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ); +int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt ); +int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt ); int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orientation ori, - _In_ zend_long offset TSRMLS_DC ); + _In_ zend_long offset ); int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, - _Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type TSRMLS_DC ); -int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno TSRMLS_DC ); + _Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type ); +int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno ); int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, - _Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees TSRMLS_DC ); -int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val TSRMLS_DC ); -int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value TSRMLS_DC ); -int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value TSRMLS_DC ); -int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ); -int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ); + _Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees ); +int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val ); +int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value ); +int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value ); +int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt ); +int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt ); struct pdo_stmt_methods pdo_sqlsrv_stmt_methods = { @@ -311,40 +311,40 @@ struct pdo_stmt_methods pdo_sqlsrv_stmt_methods = { }; -void stmt_option_pdo_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_pdo_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { - set_stmt_cursors( stmt, value_z TSRMLS_CC ); + set_stmt_cursors( stmt, value_z ); } -void stmt_option_encoding:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_encoding:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { - set_stmt_encoding( stmt, value_z TSRMLS_CC ); + set_stmt_encoding( stmt, value_z ); } -void stmt_option_direct_query:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_direct_query:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); pdo_stmt->direct_query = ( zend_is_true( value_z )) ? true : false; } -void stmt_option_cursor_scroll_type:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_cursor_scroll_type:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { - set_stmt_cursor_scroll_type( stmt, value_z TSRMLS_CC ); + set_stmt_cursor_scroll_type( stmt, value_z ); } -void stmt_option_emulate_prepares:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_emulate_prepares:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { pdo_stmt_t *pdo_stmt = static_cast( stmt->driver() ); pdo_stmt->supports_placeholders = ( zend_is_true( value_z )) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; } -void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); pdo_stmt->fetch_numeric = ( zend_is_true( value_z )) ? true : false; } -void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); pdo_stmt->fetch_datetime = ( zend_is_true( value_z )) ? true : false; @@ -384,7 +384,7 @@ pdo_sqlsrv_stmt::~pdo_sqlsrv_stmt( void ) // *stmt - Pointer to current statement // Return: // Returns 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) +int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt ) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -404,7 +404,7 @@ int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) if ( driver_stmt && driver_stmt->executed == true ) { while( driver_stmt && driver_stmt->past_next_result_end == false ) { - core_sqlsrv_next_result( driver_stmt TSRMLS_CC ); + core_sqlsrv_next_result( driver_stmt ); } } } @@ -428,7 +428,7 @@ int pdo_sqlsrv_stmt_close_cursor( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) // colno - Index of the column which requires description. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno TSRMLS_DC) +int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -441,7 +441,7 @@ int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno TSRML try { - core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast( stmt->driver_data ), colno TSRMLS_CC ); + core_meta_data = core_sqlsrv_field_metadata( reinterpret_cast( stmt->driver_data ), colno ); } catch( core::CoreException& ) { @@ -485,7 +485,7 @@ int pdo_sqlsrv_stmt_describe_col( _Inout_ pdo_stmt_t *stmt, _In_ int colno TSRML // *stmt - pointer to current statement // Return: // 1 for success. -int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) +int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt ) { pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast( stmt->driver_data ); @@ -523,7 +523,7 @@ int pdo_sqlsrv_stmt_dtor( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) // *stmt - pointer to the current statement. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) +int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt ) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -540,7 +540,7 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) while( driver_stmt->past_next_result_end == false ) { - core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); + core_sqlsrv_next_result( driver_stmt, false ); } } @@ -572,7 +572,7 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) // PDOStatement::setAttribute() driver_stmt->set_query_timeout(); - SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt TSRMLS_CC, query, query_len ); + SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt, query, query_len ); if ( execReturn == SQL_NO_DATA ) { stmt->column_count = 0; @@ -582,7 +582,7 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) } else { if (driver_stmt->column_count == ACTIVE_NUM_COLS_INVALID) { - stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + stmt->column_count = core::SQLNumResultCols( driver_stmt ); driver_stmt->column_count = stmt->column_count; } else { @@ -591,7 +591,7 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) if (driver_stmt->row_count == ACTIVE_NUM_ROWS_INVALID) { // return the row count regardless if there are any rows or not - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + stmt->row_count = core::SQLRowCount( driver_stmt ); driver_stmt->row_count = stmt->row_count; } else { @@ -642,7 +642,7 @@ int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) // Return: // 0 for failure, 1 for success. int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orientation ori, - _In_ zend_long offset TSRMLS_DC) + _In_ zend_long offset) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -688,7 +688,7 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta } SQLSMALLINT odbc_fetch_ori = pdo_fetch_ori_to_odbc_fetch_ori( ori ); - bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset TSRMLS_CC ); + bool data = core_sqlsrv_fetch( driver_stmt, odbc_fetch_ori, offset ); // support for the PDO rowCount method. Since rowCount doesn't call a // method, PDO relies on us to fill the pdo_stmt_t::row_count member @@ -698,7 +698,7 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta // which is unnecessary and a performance hit if( driver_stmt->past_fetch_end || driver_stmt->cursor_type == SQL_CURSOR_DYNAMIC) { - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + stmt->row_count = core::SQLRowCount( driver_stmt ); driver_stmt->row_count = stmt->row_count; // a row_count of -1 means no rows, but we change it to 0 @@ -741,7 +741,7 @@ int pdo_sqlsrv_stmt_fetch( _Inout_ pdo_stmt_t *stmt, _In_ enum pdo_fetch_orienta // Return: // 0 for failure, 1 for success. int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, - _Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees TSRMLS_DC) + _Out_writes_bytes_opt_(*len) char **ptr, _Inout_ size_t *len, _Out_opt_ int *caller_frees) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -781,7 +781,7 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, sqlsrv_php_type.typeinfo.type = pdo_type_to_sqlsrv_php_type( driver_stmt, driver_stmt->bound_column_param_types[colno] - TSRMLS_CC ); + ); pdo_bound_param_data* bind_data = NULL; bind_data = reinterpret_cast(zend_hash_index_find_ptr(stmt->bound_columns, colno)); @@ -823,7 +823,7 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, SQLSRV_PHPTYPE sqlsrv_phptype_out = SQLSRV_PHPTYPE_INVALID; core_sqlsrv_get_field( driver_stmt, colno, sqlsrv_php_type, false, *(reinterpret_cast(ptr)), - reinterpret_cast( len ), true, &sqlsrv_phptype_out TSRMLS_CC ); + reinterpret_cast( len ), true, &sqlsrv_phptype_out ); if (ptr) { zval* zval_ptr = reinterpret_cast(sqlsrv_malloc(sizeof(zval))); @@ -851,7 +851,7 @@ int pdo_sqlsrv_stmt_get_col_data( _Inout_ pdo_stmt_t *stmt, _In_ int colno, // val - Attribute value. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val TSRMLS_DC) +int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *val) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -869,7 +869,7 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; case SQLSRV_ATTR_ENCODING: - set_stmt_encoding( driver_stmt, val TSRMLS_CC ); + set_stmt_encoding( driver_stmt, val ); break; case PDO_ATTR_CURSOR: @@ -877,7 +877,7 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; case SQLSRV_ATTR_QUERY_TIMEOUT: - core_sqlsrv_set_query_timeout( driver_stmt, val TSRMLS_CC ); + core_sqlsrv_set_query_timeout( driver_stmt, val ); break; case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: @@ -885,7 +885,7 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; case SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE: - core_sqlsrv_set_buffered_query_limit( driver_stmt, val TSRMLS_CC ); + core_sqlsrv_set_buffered_query_limit( driver_stmt, val ); break; case SQLSRV_ATTR_FETCHES_NUMERIC_TYPE: @@ -901,7 +901,7 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; case SQLSRV_ATTR_DECIMAL_PLACES: - core_sqlsrv_set_decimal_places(driver_stmt, val TSRMLS_CC); + core_sqlsrv_set_decimal_places(driver_stmt, val); break; case SQLSRV_ATTR_DATA_CLASSIFICATION: @@ -933,7 +933,7 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In // return_value - Attribute value. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value TSRMLS_DC ) +int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _Inout_ zval *return_value ) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -1041,7 +1041,7 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In // return_value - zval* consisting of the metadata. // Return: // FAILURE for failure, SUCCESS for success. -int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value TSRMLS_DC) +int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno, _Inout_ zval *return_value) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -1065,7 +1065,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } // initialize the array to nothing, as PDO requires us to create it - core::sqlsrv_array_init( *driver_stmt, return_value TSRMLS_CC ); + core::sqlsrv_array_init( *driver_stmt, return_value ); field_meta_data* core_meta_data; @@ -1080,7 +1080,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno // initialize the column data classification array zval data_classification; ZVAL_UNDEF(&data_classification); - core::sqlsrv_array_init(*driver_stmt, &data_classification TSRMLS_CC ); + core::sqlsrv_array_init(*driver_stmt, &data_classification ); data_classification::fill_column_sensitivity_array(driver_stmt, (SQLSMALLINT)colno, &data_classification); @@ -1095,7 +1095,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno SQLSMALLINT out_buff_len; SQLLEN not_used; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TYPE_NAME, field_type_name, - sizeof( field_type_name ), &out_buff_len, ¬_used TSRMLS_CC ); + sizeof( field_type_name ), &out_buff_len, ¬_used ); add_assoc_string( return_value, "sqlsrv:decl_type", field_type_name ); // get the PHP type of the column. The types returned here mirror the types returned by debug_zval_dump when @@ -1120,7 +1120,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno char table_name[SQL_SERVER_IDENT_SIZE_MAX] = {'\0'}; SQLLEN field_type_num; core::SQLColAttribute( driver_stmt, (SQLUSMALLINT) colno + 1, SQL_DESC_TABLE_NAME, table_name, SQL_SERVER_IDENT_SIZE_MAX, - &out_buff_len, &field_type_num TSRMLS_CC ); + &out_buff_len, &field_type_num ); add_assoc_string( return_value, "table", table_name ); if( stmt->columns && stmt->columns[colno].param_type == PDO_PARAM_ZVAL ) { @@ -1150,7 +1150,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno // stmt - PDOStatement object containing the result set. // Return: // 0 for failure, 1 for success. -int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) +int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt ) { PDO_RESET_STMT_ERROR; PDO_VALIDATE_STMT; @@ -1164,7 +1164,7 @@ int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_next_rowset: driver_data object was null" ); - core_sqlsrv_next_result( static_cast( stmt->driver_data ) TSRMLS_CC ); + core_sqlsrv_next_result( static_cast( stmt->driver_data ) ); // clear the current meta data since the new result will generate new meta data std::for_each( driver_stmt->current_meta_data.begin(), driver_stmt->current_meta_data.end(), meta_data_free ); @@ -1175,10 +1175,10 @@ int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) return 0; } - stmt->column_count = core::SQLNumResultCols( driver_stmt TSRMLS_CC ); + stmt->column_count = core::SQLNumResultCols( driver_stmt ); // return the row count regardless if there are any rows or not - stmt->row_count = core::SQLRowCount( driver_stmt TSRMLS_CC ); + stmt->row_count = core::SQLRowCount( driver_stmt ); driver_stmt->column_count = stmt->column_count; driver_stmt->row_count = stmt->row_count; @@ -1209,7 +1209,7 @@ int pdo_sqlsrv_stmt_next_rowset( _Inout_ pdo_stmt_t *stmt TSRMLS_DC ) // Return: // Returns 0 for failure, 1 for success. int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, - _Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type TSRMLS_DC) + _Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type) { PDO_RESET_STMT_ERROR; @@ -1255,7 +1255,7 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, while( driver_stmt->past_next_result_end == false ) { - core_sqlsrv_next_result( driver_stmt TSRMLS_CC, false ); + core_sqlsrv_next_result( driver_stmt, false ); } } @@ -1401,7 +1401,7 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, // and bind the parameter core_sqlsrv_bind_param( driver_stmt, static_cast( param->paramno ), direction, &(param->parameter) , php_out_type, encoding, - sql_type, column_size, decimal_digits TSRMLS_CC ); + sql_type, column_size, decimal_digits ); } break; // undo any work done by the core layer after the statement is executed @@ -1416,7 +1416,7 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt, } core_sqlsrv_post_param( reinterpret_cast( stmt->driver_data ), param->paramno, - &(param->parameter) TSRMLS_CC ); + &(param->parameter) ); } break; case PDO_PARAM_EVT_FETCH_PRE: @@ -1530,5 +1530,5 @@ void pdo_sqlsrv_stmt::set_query_timeout() return; } - core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast((SQLLEN)query_timeout), SQL_IS_UINTEGER TSRMLS_CC); + core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast((SQLLEN)query_timeout), SQL_IS_UINTEGER); } \ No newline at end of file diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 34e1a4ec5..58f49efa1 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -43,7 +43,7 @@ const int WARNING_MIN_LENGTH = static_cast( strlen( WARNING_TEMPLATE sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code); // build the object and throw the PDO exception -void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error TSRMLS_DC ); +void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ); } @@ -463,7 +463,7 @@ pdo_error PDO_ERRORS[] = { }; // PDO error handler for the environment context. -bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, +bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, _In_opt_ va_list* print_args ) { SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" ); @@ -474,11 +474,11 @@ bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args ); } else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR ); SQLSRV_ASSERT( err == true, "No ODBC error was found" ); } @@ -489,7 +489,7 @@ bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned case PDO_ERRMODE_EXCEPTION: if( !warning ) { - pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + pdo_sqlsrv_throw_exception( error ); } ctx.set_last_error( error ); break; @@ -506,7 +506,7 @@ bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned } // pdo error handler for the dbh context. -bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, +bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, _In_opt_ va_list* print_args ) { pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); @@ -516,10 +516,10 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args ); } else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR ); SQLSRV_ASSERT( err == true, "No ODBC error was found" ); } @@ -530,7 +530,7 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned case PDO_ERRMODE_EXCEPTION: if( !warning ) { - pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + pdo_sqlsrv_throw_exception( error ); } ctx.set_last_error( error ); break; @@ -559,7 +559,7 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned } // PDO error handler for the statement context. -bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, +bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, _In_opt_ va_list* print_args ) { pdo_stmt_t* pdo_stmt = reinterpret_cast( ctx.driver()); @@ -568,10 +568,10 @@ bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigne sqlsrv_error_auto_ptr error; if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR TSRMLS_CC, print_args ); + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args ); } else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR TSRMLS_CC ); + bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR ); SQLSRV_ASSERT( err == true, "No ODBC error was found" ); } @@ -582,7 +582,7 @@ bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigne case PDO_ERRMODE_EXCEPTION: if( !warning ) { - pdo_sqlsrv_throw_exception( error TSRMLS_CC ); + pdo_sqlsrv_throw_exception( error ); } ctx.set_last_error( error ); break; @@ -617,7 +617,7 @@ void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Ou } // check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro -bool pdo_severity_check(_In_ unsigned int severity TSRMLS_DC) +bool pdo_severity_check(_In_ unsigned int severity) { return ((severity & PDO_SQLSRV_G(pdo_log_severity))); } @@ -639,7 +639,7 @@ sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code) return error_message; } -void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error TSRMLS_DC ) +void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) { zval ex_obj; ZVAL_UNDEF( &ex_obj ); @@ -655,9 +655,9 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error TSRMLS_DC ) ex_msg = reinterpret_cast( sqlsrv_malloc( ex_msg_len )); snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message ); zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, - ex_msg TSRMLS_CC ); + ex_msg ); zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, - reinterpret_cast( error->sqlstate ) TSRMLS_CC ); + reinterpret_cast( error->sqlstate ) ); zval ex_error_info; ZVAL_UNDEF( &ex_error_info ); @@ -668,13 +668,13 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error TSRMLS_DC ) //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) //and the refcount of the zend_array is incremented by 1 zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, - &ex_error_info TSRMLS_CC ); + &ex_error_info ); //DELREF ex_error_info here to decrement the refcount of the zend_array is 1 //the global hashtable EG(exception) then points to the zend_object in ex_obj in zend_throw_exception_object; //this ensure when EG(exception) cleans itself at php shutdown, the zend_array allocated is properly destroyed Z_DELREF( ex_error_info ); - zend_throw_exception_object( &ex_obj TSRMLS_CC ); + zend_throw_exception_object( &ex_obj ); } } diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 2caf19858..abf711ceb 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -120,7 +120,7 @@ class string_parser inline bool is_eos(void); inline bool is_white_space( _In_ char c ); bool discard_white_spaces(void); - void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC ); + void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len ); }; @@ -145,14 +145,14 @@ class conn_string_parser : private string_parser private: const char* current_key_name; int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len ); - void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len TSRMLS_DC); + void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len); protected: - void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len TSRMLS_DC); + void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len); public: conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ); - void parse_conn_string( TSRMLS_D ); + void parse_conn_string( void ); }; @@ -166,9 +166,9 @@ class sql_string_parser : private string_parser private: bool is_placeholder_char(char); public: - void add_key_int_value_pair( _In_ unsigned int value TSRMLS_DC ); + void add_key_int_value_pair( _In_ unsigned int value ); sql_string_parser(_In_ sqlsrv_context& ctx, _In_ const char* sql_str, _In_ int len, _In_ HashTable* placeholder_ht); - void parse_sql_string(TSRMLS_D); + void parse_sql_string(void); }; @@ -178,7 +178,7 @@ class sql_string_parser : private string_parser extern const connection_option PDO_CONN_OPTS[]; -int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options TSRMLS_DC); +int pdo_sqlsrv_db_handle_factory( _Inout_ pdo_dbh_t *dbh, _In_opt_ zval *driver_options); // a core layer pdo dbh object. This object inherits and overrides the statement factory struct pdo_sqlsrv_dbh : public sqlsrv_conn { @@ -193,7 +193,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { short decimal_places; short use_national_characters; - pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ); + pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver ); }; @@ -202,45 +202,39 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { //********************************************************************************************************************************* struct stmt_option_encoding : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; struct stmt_option_pdo_scrollable : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; struct stmt_option_direct_query : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; struct stmt_option_cursor_scroll_type : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; struct stmt_option_emulate_prepares : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; struct stmt_option_fetch_numeric : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; struct stmt_option_fetch_datetime : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; extern struct pdo_stmt_methods pdo_sqlsrv_stmt_methods; // a core layer pdo stmt object. This object inherits and overrides the callbacks necessary struct pdo_sqlsrv_stmt : public sqlsrv_stmt { - - pdo_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv TSRMLS_DC ) : - sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), + pdo_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv ) : + sqlsrv_stmt( c, handle, e, drv ), direct_query( false ), direct_query_subst_string( NULL ), direct_query_subst_string_len( 0 ), @@ -292,18 +286,18 @@ struct pdo_error { // called when an error occurs in the core layer. These routines are set as the error_callback in a // context. The context is passed to this function since it contains the function -bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, +bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, _In_opt_ va_list* print_args ); -bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, +bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, _In_opt_ va_list* print_args ); -bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning TSRMLS_DC, +bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, _In_opt_ va_list* print_args ); // common routine to transfer a sqlsrv_context's error to a PDO zval void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Out_ zval* pdo_zval ); // reset the errors from the last operation -inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh TSRMLS_DC ) +inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh ) { strcpy_s( dbh->error_code, sizeof( dbh->error_code ), "00000" ); // 00000 means no error @@ -330,7 +324,7 @@ inline void pdo_reset_dbh_error( _Inout_ pdo_dbh_t* dbh TSRMLS_DC ) core_sqlsrv_register_severity_checker(pdo_severity_check); \ LOG(SEV_NOTICE, message); -#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh TSRMLS_CC ); +#define PDO_RESET_DBH_ERROR pdo_reset_dbh_error( dbh ); inline void pdo_reset_stmt_error( _Inout_ pdo_stmt_t* stmt ) { @@ -406,7 +400,7 @@ enum PDO_ERROR_CODES { extern pdo_error PDO_ERRORS[]; #define THROW_PDO_ERROR( ctx, custom, ... ) \ - call_error_handler( ctx, custom TSRMLS_CC, false, ## __VA_ARGS__ ); \ + call_error_handler( ctx, custom, false, ## __VA_ARGS__ ); \ throw pdo::PDOException(); namespace pdo { @@ -422,7 +416,6 @@ namespace pdo { } // namespace pdo // check the global variable of pdo_sqlsrv severity whether the message qualifies to be logged with the LOG macro -bool pdo_severity_check(_In_ unsigned int severity TSRMLS_DC); - +bool pdo_severity_check(_In_ unsigned int severity); #endif /* PHP_PDO_SQLSRV_INT_H */ diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index c328a9d58..d8d2f5066 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -68,13 +68,13 @@ const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};"; void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd, _Inout_opt_ HashTable* options_ht, _In_ const connection_option valid_conn_opts[], - void* driver,_Inout_ std::string& connection_string TSRMLS_DC ); -void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); + void* driver,_Inout_ std::string& connection_string ); +void determine_server_version( _Inout_ sqlsrv_conn* conn ); const char* get_processor_arch( void ); -void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len TSRMLS_DC ); -connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ const char* key, _In_ SQLULEN key_len TSRMLS_DC ); -void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str TSRMLS_DC ); -void load_azure_key_vault( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); +void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len ); +connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ const char* key, _In_ SQLULEN key_len ); +void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str ); +void load_azure_key_vault( _Inout_ sqlsrv_conn* conn ); void configure_azure_key_vault( sqlsrv_conn* conn, BYTE config_attr, const DWORD config_value, size_t key_size); void configure_azure_key_vault( sqlsrv_conn* conn, BYTE config_attr, const char* config_value, size_t key_size); } @@ -97,7 +97,7 @@ void configure_azure_key_vault( sqlsrv_conn* conn, BYTE config_attr, const char* sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_context& henv_ncp, _In_ driver_conn_factory conn_factory, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd, _Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[], - _In_ void* driver, _In_z_ const char* driver_func TSRMLS_DC ) + _In_ void* driver, _In_z_ const char* driver_func ) { SQLRETURN r; @@ -149,11 +149,11 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont #endif // !_WIN32 SQLHANDLE temp_conn_h; - core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC ); - conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC ); + core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h ); + conn = conn_factory( temp_conn_h, err, driver ); conn->set_func( driver_func ); - build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC ); + build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str ); // If column encryption is enabled, must use ODBC driver 17 if( conn->ce_option.enabled && conn->driver_version != ODBC_DRIVER_UNKNOWN) { @@ -271,7 +271,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont #ifndef _WIN32 if ( r == SQL_SUCCESS_WITH_INFO ) { #endif // !_WIN32 - determine_server_version( conn TSRMLS_CC ); + determine_server_version( conn ); #ifndef _WIN32 } #endif // !_WIN32 @@ -426,14 +426,14 @@ SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& con // Parameters: // sqlsrv_conn*: The connection with which the transaction is associated. -void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) +void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn ) { try { DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." ); core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); + SQL_IS_UINTEGER ); } catch ( core::CoreException& ) { throw; @@ -449,16 +449,16 @@ void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) // Parameters: // sqlsrv_conn*: The connection on which the transaction is active. -void core_sqlsrv_commit( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) +void core_sqlsrv_commit( _Inout_ sqlsrv_conn* conn ) { try { DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." ); - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC ); + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT ); core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); + SQL_IS_UINTEGER ); } catch ( core::CoreException& ) { throw; @@ -474,16 +474,16 @@ void core_sqlsrv_commit( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) // Parameters: // sqlsrv_conn*: The connection on which the transaction is active. -void core_sqlsrv_rollback( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) +void core_sqlsrv_rollback( _Inout_ sqlsrv_conn* conn ) { try { DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." ); - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK ); core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ), - SQL_IS_UINTEGER TSRMLS_CC ); + SQL_IS_UINTEGER ); } catch ( core::CoreException& ) { @@ -495,7 +495,7 @@ void core_sqlsrv_rollback( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) // Called when a connection resource is destroyed by the Zend engine. // Parameters: // conn - The current active connection. -void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC ) +void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn ) { // if the connection wasn't successful, just return. if( conn == NULL ) @@ -504,7 +504,7 @@ void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC ) try { // rollback any transaction in progress (we don't care about the return result) - core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC ); + core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK ); } catch( core::CoreException& ) { LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." ); @@ -529,7 +529,7 @@ void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC ) // sql - T-SQL command to prepare // sql_len - length of the T-SQL string -void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len TSRMLS_DC ) +void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len ) { try { @@ -557,7 +557,7 @@ void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) c } // prepare our wide char query string - core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len TSRMLS_CC ); + core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len ); stmt->param_descriptions.clear(); @@ -587,14 +587,14 @@ void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) c // conn - The connection resource by which the client and server are connected. // *server_version - zval for returning results. -void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval* server_version TSRMLS_DC ) +void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval* server_version ) { try { sqlsrv_malloc_auto_ptr buffer; SQLSMALLINT buffer_len = 0; - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); + get_server_version( conn, &buffer, buffer_len ); core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); if ( buffer != 0 ) { sqlsrv_free( buffer ); @@ -614,7 +614,7 @@ void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval* se // conn - The connection resource by which the client and server are connected. // *server_info - zval for returning results. -void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *server_info TSRMLS_DC ) +void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *server_info ) { try { @@ -623,23 +623,23 @@ void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *server_ // Get the database name buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len ); // initialize the array - core::sqlsrv_array_init( *conn, server_info TSRMLS_CC ); + core::sqlsrv_array_init( *conn, server_info ); - core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ ); buffer.transferred(); // Get the server version - get_server_version( conn, &buffer, buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC ); + get_server_version( conn, &buffer, buffer_len ); + core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ ); buffer.transferred(); // Get the server name buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC ); + core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len ); + core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ ); buffer.transferred(); } @@ -654,7 +654,7 @@ void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *server_ // conn - The connection resource by which the client and server are connected. // *client_info - zval for returning the results. -void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ) +void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_info ) { try { @@ -663,28 +663,28 @@ void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_ // Get the ODBC driver's dll name buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len ); // initialize the array - core::sqlsrv_array_init( *conn, client_info TSRMLS_CC ); + core::sqlsrv_array_init( *conn, client_info ); #ifndef _WIN32 - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverName", buffer, 0 /*duplicate*/ TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverName", buffer, 0 /*duplicate*/ ); #else - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ ); #endif // !_WIN32 buffer.transferred(); // Get the ODBC driver's ODBC version buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); + core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ ); buffer.transferred(); // Get the OBDC driver's version buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC ); + core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len ); + core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ ); buffer.transferred(); } @@ -753,7 +753,7 @@ bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t namespace { connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ SQLULEN key, - _In_ const connection_option conn_opts[] TSRMLS_DC ) + _In_ const connection_option conn_opts[] ) { for( int opt_idx = 0; conn_opts[opt_idx].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) { @@ -774,7 +774,7 @@ connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ SQLULEN void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd, _Inout_opt_ HashTable* options, _In_ const connection_option valid_conn_opts[], - void* driver, _Inout_ std::string& connection_string TSRMLS_DC ) + void* driver, _Inout_ std::string& connection_string ) { bool mars_mentioned = false; connection_option const* conn_opt; @@ -834,7 +834,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou } // Add the server name - common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC ); + common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string ); // If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used, // because they are incompatible @@ -848,7 +848,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou throw core::CoreException(); } - common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string TSRMLS_CC); + common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string); // if no password was given, then don't add a password to the connection string. Perhaps the UID // given doesn't have a password? @@ -858,7 +858,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou throw core::CoreException(); } - common_conn_str_append_func(ODBCConnOptions::PWD, pwd, strnlen_s(pwd), connection_string TSRMLS_CC); + common_conn_str_append_func(ODBCConnOptions::PWD, pwd, strnlen_s(pwd), connection_string); } } } @@ -894,13 +894,13 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou // The driver layer should ensure a valid key. DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." ); - conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC ); + conn_opt = get_connection_option( conn, index, valid_conn_opts ); if( index == SQLSRV_CONN_OPTION_MARS ) { mars_mentioned = true; } - conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC ); + conn_opt->func( conn_opt, data, conn, connection_string ); } ZEND_HASH_FOREACH_END(); // MARS on if not explicitly turned off @@ -919,7 +919,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou // get_server_version // Helper function which returns the version of the SQL Server we are connected to. -void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len TSRMLS_DC ) +void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len ) { try { @@ -927,7 +927,7 @@ void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) SQLSMALLINT buffer_len = 0; buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC ); + core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len ); *server_version = buffer; len = buffer_len; buffer.transferred(); @@ -989,11 +989,11 @@ const char* get_processor_arch( void ) // Exception is thrown when the server version is either undetermined // or is invalid (< 2000). -void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) +void determine_server_version( _Inout_ sqlsrv_conn* conn ) { SQLSMALLINT info_len; char p[INFO_BUFFER_LEN] = {'\0'}; - core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC ); + core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len ); errno = 0; char version_major_str[3] = {'\0'}; @@ -1013,7 +1013,7 @@ void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC ) conn->server_version = version_major; } -void load_azure_key_vault(_Inout_ sqlsrv_conn* conn TSRMLS_DC) +void load_azure_key_vault(_Inout_ sqlsrv_conn* conn) { // If column encryption is not enabled simply do nothing. Otherwise, check if Azure Key Vault // is required for encryption or decryption. Note, in order to load and configure Azure Key Vault, @@ -1092,11 +1092,10 @@ void configure_azure_key_vault(sqlsrv_conn* conn, BYTE config_attr, const char* core::SQLSetConnectAttr(conn, SQL_COPT_SS_CEKEYSTOREDATA, reinterpret_cast(pData), SQL_IS_POINTER); } -void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str TSRMLS_DC ) +void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str ) { // wrap a connection option in a quote. It is presumed that any character that need to be escaped will // be escaped, such as a closing }. - TSRMLS_C; if( val_len > 0 && val[0] == '{' && val[val_len - 1] == '}' ) { ++val; @@ -1111,26 +1110,25 @@ void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_l } // namespace // simply add the parsed value to the connection string -void conn_str_append_func::func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str TSRMLS_DC ) +void conn_str_append_func::func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str ) { const char* val_str = Z_STRVAL_P( value ); size_t val_len = Z_STRLEN_P( value ); - common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC ); + common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str ); } // do nothing for connection pooling since we handled it earlier when // deciding which environment handle to use. -void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ TSRMLS_DC ) +void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ ) { - TSRMLS_C; } -void driver_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ) +void driver_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str ) { const char* val_str = Z_STRVAL_P( value ); size_t val_len = Z_STRLEN_P( value ); std::string driver_option( "" ); - common_conn_str_append_func( option->odbc_name, val_str, val_len, driver_option TSRMLS_CC ); + common_conn_str_append_func( option->odbc_name, val_str, val_len, driver_option ); conn->driver_version = ODBC_DRIVER_UNKNOWN; for ( short i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST && conn->driver_version == ODBC_DRIVER_UNKNOWN; ++i ) { @@ -1148,7 +1146,7 @@ void driver_set_func::func( _In_ connection_option const* option, _In_ zval* val conn_str += driver_option; } -void column_encryption_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ) +void column_encryption_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str ) { convert_to_string( value ); const char* value_str = Z_STRVAL_P( value ); @@ -1168,7 +1166,7 @@ void column_encryption_set_func::func( _In_ connection_option const* option, _In conn_str += ";"; } -void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC) +void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str) { SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "Azure Key Vault keywords accept only strings."); @@ -1254,7 +1252,7 @@ size_t core_str_zval_is_true( _Inout_ zval* value_z ) return 0; // false } -void access_token_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ) +void access_token_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str ) { SQLSRV_ASSERT(Z_TYPE_P(value) == IS_STRING, "An access token must be a byte string."); diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index 1d4830780..9c3616d5e 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -34,7 +34,7 @@ bool isVistaOrGreater; // henv_cp - Environment handle for pooled connection. // henv_ncp - Environment handle for non-pooled connection. // err - Driver specific error handler which handles any errors during initialization. -void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_context** henv_ncp, _In_ error_callback err, _In_z_ const char* driver_func TSRMLS_DC ) +void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_context** henv_ncp, _In_ error_callback err, _In_z_ const char* driver_func ) { SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( zend_long ) ); SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( zend_long )); @@ -65,11 +65,11 @@ void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_contex // set to ODBC 3 core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER - TSRMLS_CC ); + ); // disable connection pooling core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ), - SQL_IS_UINTEGER TSRMLS_CC ); + SQL_IS_UINTEGER ); // allocate the pooled envrionment handle // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so @@ -83,11 +83,11 @@ void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_contex (*henv_cp)->set_func( driver_func ); // set to ODBC 3 - core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC); + core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER); // enable connection pooling core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ), - SQL_IS_UINTEGER TSRMLS_CC ); + SQL_IS_UINTEGER ); } catch( core::CoreException& e ) { diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index afa3bf56e..fd55c2947 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -78,7 +78,7 @@ bool get_bit( _In_ void* ptr, _In_ unsigned int bit ) // read in LOB field during buffered result creation SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_buffered_result_set::meta_data& meta, - _In_ zend_long mem_used TSRMLS_DC ); + _In_ zend_long mem_used ); // dtor for each row in the cache void cache_row_dtor( _In_ zval* data ); @@ -391,27 +391,27 @@ sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void ) { } -SQLRETURN sqlsrv_odbc_result_set::fetch( _In_ SQLSMALLINT orientation, _In_ SQLLEN offset TSRMLS_DC ) +SQLRETURN sqlsrv_odbc_result_set::fetch( _In_ SQLSMALLINT orientation, _In_ SQLLEN offset ) { SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC ); + return core::SQLFetchScroll( odbc, orientation, offset ); } SQLRETURN sqlsrv_odbc_result_set::get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_opt_(buffer_length) SQLPOINTER buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, - _In_ bool handle_warning TSRMLS_DC ) + _In_ bool handle_warning ) { SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC ); + return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning ); } SQLRETURN sqlsrv_odbc_result_set::get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, - _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) + _Inout_ SQLSMALLINT* out_buffer_length ) { SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length, - out_buffer_length TSRMLS_CC ); + out_buffer_length ); } sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( _In_ SQLSMALLINT record_number ) @@ -420,17 +420,17 @@ sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( _In_ SQLSMALLINT record_numb return odbc_get_diag_rec( odbc, record_number ); } -SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D ) +SQLLEN sqlsrv_odbc_result_set::row_count( void ) { SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" ); - return core::SQLRowCount( odbc TSRMLS_CC ); + return core::SQLRowCount( odbc ); } // Buffered result set // This class holds a result set in memory -sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) : +sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stmt ) : sqlsrv_result_set( stmt ), cache(NULL), col_count(0), @@ -439,7 +439,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm read_so_far(0), temp_length(0) { - col_count = core::SQLNumResultCols( stmt TSRMLS_CC ); + col_count = core::SQLNumResultCols( stmt ); // there is no result set to buffer if( col_count == 0 ) { return; @@ -456,7 +456,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm SQLULEN offset = null_bytes; for( SQLSMALLINT i = 0; i < col_count; ++i ) { - core::SQLDescribeColW( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC ); + core::SQLDescribeColW( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL ); offset = align_to( offset ); meta[i].offset = offset; @@ -469,7 +469,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm case SQL_GUID: case SQL_NUMERIC: core::SQLColAttributeW( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); + reinterpret_cast( &meta[i].length ) ); meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space offset += meta[i].length; break; @@ -536,7 +536,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm case SQL_SS_TIMESTAMPOFFSET: case SQL_TYPE_TIMESTAMP: core::SQLColAttributeW( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, - reinterpret_cast( &meta[i].length ) TSRMLS_CC ); + reinterpret_cast( &meta[i].length ) ); meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space offset += meta[i].length; break; @@ -628,10 +628,10 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm size_t row_count = 0; // 10 is an arbitrary number for now for the initial size of the cache ALLOC_HASHTABLE( cache ); - core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC ); + core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ ); try { - while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) { + while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 ) != SQL_NO_DATA ) { // allocate the row buffer sqlsrv_malloc_auto_ptr rowAuto; @@ -655,7 +655,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm out_buffer_length = &out_buffer_temp; SQLPOINTER* lob_addr = reinterpret_cast( &row[meta[i].offset] ); - *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC ); + *lob_addr = read_lob_field( stmt, i, meta[i], mem_used ); // a NULL pointer means NULL field if( *lob_addr == NULL ) { *out_buffer_length = SQL_NULL_DATA; @@ -677,7 +677,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm buffer = row + meta[i].offset + sizeof( SQLULEN ); out_buffer_length = reinterpret_cast( row + meta[i].offset ); core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); + false ); } break; @@ -693,7 +693,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm buffer = row + meta[i].offset; out_buffer_length = &out_buffer_temp; core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length, - false TSRMLS_CC ); + false ); } break; @@ -712,7 +712,7 @@ sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* stm // add it to the cache row_dtor_closure cl( this, row ); - sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC ); + sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) ); rowAuto.transferred(); } } @@ -738,7 +738,7 @@ sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void ) } } -SQLRETURN sqlsrv_buffered_result_set::fetch( _Inout_ SQLSMALLINT orientation, _Inout_opt_ SQLLEN offset TSRMLS_DC ) +SQLRETURN sqlsrv_buffered_result_set::fetch( _Inout_ SQLSMALLINT orientation, _Inout_opt_ SQLLEN offset ) { last_error = NULL; last_field_index = -1; @@ -762,7 +762,7 @@ SQLRETURN sqlsrv_buffered_result_set::fetch( _Inout_ SQLSMALLINT orientation, _I current = 1; break; case SQL_FETCH_LAST: - current = row_count( TSRMLS_C ); + current = row_count(); break; case SQL_FETCH_ABSOLUTE: current = offset; @@ -783,8 +783,8 @@ SQLRETURN sqlsrv_buffered_result_set::fetch( _Inout_ SQLSMALLINT orientation, _I } // the cursor can never get further away than just after the last row - if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) { - current = row_count( TSRMLS_C ) + 1; + if( current > row_count() || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) { + current = row_count() + 1; return SQL_NO_DATA; } @@ -793,7 +793,7 @@ SQLRETURN sqlsrv_buffered_result_set::fetch( _Inout_ SQLSMALLINT orientation, _I SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_bytes_opt_(buffer_length) SQLPOINTER buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ) + bool handle_warning ) { last_error = NULL; field_index--; // convert from 1 based to 0 based @@ -879,7 +879,7 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ SQLRETURN sqlsrv_buffered_result_set::get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, - _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) + _Inout_ SQLSMALLINT* out_buffer_length ) { SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" ); SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE, @@ -923,7 +923,7 @@ sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( _In_ SQLSMALLINT record_ sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code ); } -SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D ) +SQLLEN sqlsrv_buffered_result_set::row_count( void ) { last_error = NULL; @@ -1518,7 +1518,7 @@ void cache_row_dtor( _In_ zval* data ) } SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_buffered_result_set::meta_data& meta, - _In_ zend_long mem_used TSRMLS_DC ) + _In_ zend_long mem_used ) { SQLSMALLINT extra = 0; SQLULEN* output_buffer_len = NULL; @@ -1553,7 +1553,7 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in output_buffer_len = reinterpret_cast( buffer.get() ); r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ), - to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC ); + to_read - already_read + extra, &last_field_len, false /*handle_warning*/ ); // if the field is NULL, then return a NULL pointer if( last_field_len == SQL_NULL_DATA ) { @@ -1574,7 +1574,7 @@ SQLPOINTER read_lob_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_in else if( r == SQL_SUCCESS_WITH_INFO ) { SQLSMALLINT len; core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); + ); if( !is_truncated_warning( state )) { break; diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index e91b44c51..adb4a5500 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -289,16 +289,16 @@ struct sqlsrv_static_assert { _In_ static const int value = 1; }; // log_callback // a driver specific callback for checking if the messages are qualified to be logged: // severity - severity of the message: notice, warning, or error -typedef bool (*severity_callback)(_In_ unsigned int severity TSRMLS_DC); +typedef bool (*severity_callback)(_In_ unsigned int severity); // each driver must register a severity checker callback for logging to work according to the INI settings void core_sqlsrv_register_severity_checker(_In_ severity_callback driver_checker); // a simple wrapper around a PHP error logging function. -void write_to_log( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, ... ); +void write_to_log( _In_ unsigned int severity, _In_ const char* msg, ... ); // a macro to make it convenient to use the function. -#define LOG( severity, msg, ...) write_to_log( severity TSRMLS_CC, msg, ## __VA_ARGS__ ) +#define LOG( severity, msg, ...) write_to_log( severity, msg, ## __VA_ARGS__ ) // mask for filtering which severities are written to the log enum logging_severity { @@ -864,7 +864,7 @@ struct sqlsrv_conn; // a driver specific callback for processing errors. // ctx - the context holding the handles // sqlsrv_error_code - specific error code to return. -typedef bool (*error_callback)( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool error TSRMLS_DC, _In_opt_ va_list* print_args ); +typedef bool (*error_callback)( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool error, _In_opt_ va_list* print_args ); // sqlsrv_context // a context holds relevant information to be passed with a connection and statement objects. @@ -1019,7 +1019,7 @@ struct sqlsrv_encoding { extern bool isVistaOrGreater; // used to determine if OS is Vista or Greater extern HashTable* g_encodings; // encodings supported by this driver -void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_context** henv_ncp, _In_ error_callback err, _In_z_ const char* driver_func TSRMLS_DC ); +void core_sqlsrv_minit( _Outptr_ sqlsrv_context** henv_cp, _Inout_ sqlsrv_context** henv_ncp, _In_ error_callback err, _In_z_ const char* driver_func ); void core_sqlsrv_mshutdown( _Inout_ sqlsrv_context& henv_cp, _Inout_ sqlsrv_context& henv_ncp ); // environment context used by sqlsrv_connect for when a connection error occurs. @@ -1094,7 +1094,7 @@ struct sqlsrv_conn : public sqlsrv_context { sqlsrv_malloc_auto_ptr azure_ad_access_token; // initialize with default values - sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) : + sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding ) : sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding ) { server_version = SERVER_VERSION_UNKNOWN; @@ -1225,54 +1225,54 @@ struct connection_option { // process the connection type // return whether or not the function was successful in processing the connection option - void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC ); + void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str ); }; // connection attribute functions // simply add the parsed value to the connection string struct conn_str_append_func { - static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str TSRMLS_DC ); + static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str ); }; struct conn_null_func { - static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ TSRMLS_DC ); + static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ ); }; struct column_encryption_set_func { - static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); + static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str ); }; struct driver_set_func { - static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); + static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str ); }; struct ce_akv_str_set_func { - static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); + static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str ); }; struct access_token_set_func { - static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC ); + static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str ); }; // factory to create a connection (since they are subclassed to instantiate statements) -typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC ); +typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv ); // *** connection functions *** sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_context& henv_ncp, _In_ driver_conn_factory conn_factory, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd, _Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[], - _In_ void* driver, _In_z_ const char* driver_func TSRMLS_DC ); + _In_ void* driver, _In_z_ const char* driver_func ); SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _In_ bool is_pooled ); -void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len TSRMLS_DC ); -void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_commit( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_rollback( _Inout_ sqlsrv_conn* conn TSRMLS_DC ); -void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval* server_info TSRMLS_DC ); -void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval *server_version TSRMLS_DC ); -void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_info TSRMLS_DC ); +void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn ); +void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len ); +void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn ); +void core_sqlsrv_commit( _Inout_ sqlsrv_conn* conn ); +void core_sqlsrv_rollback( _Inout_ sqlsrv_conn* conn ); +void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval* server_info ); +void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval *server_version ); +void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_info ); bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t value_len ); size_t core_str_zval_is_true( _Inout_ zval* str_zval ); bool core_is_authentication_option_valid( _In_z_ const char* value, _In_ size_t value_len ); @@ -1285,42 +1285,42 @@ bool core_compare_error_state( _In_ sqlsrv_conn* conn, _In_ SQLRETURN r, _In_ c struct stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, _In_ zval* /*value_z*/ TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, _In_ zval* /*value_z*/ ); }; struct stmt_option_query_timeout : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z ); }; struct stmt_option_send_at_exec : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z ); }; struct stmt_option_buffered_query_limit : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z ); }; struct stmt_option_date_as_string : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z ); }; struct stmt_option_format_decimals : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z ); }; struct stmt_option_decimal_places : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z ); }; struct stmt_option_data_classification : public stmt_option_functor { - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* opt, _In_ zval* value_z ); }; // used to hold the table for statment options @@ -1353,7 +1353,7 @@ struct sqlsrv_stream { }; // close any active stream -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); +void close_active_stream( _Inout_ sqlsrv_stmt* stmt ); extern php_stream_wrapper g_sqlsrv_stream_wrapper; @@ -1432,9 +1432,9 @@ namespace data_classification { struct sensitivity_metadata; void name_id_pair_free(name_id_pair * pair); - void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector>* pairs, _Inout_ unsigned char **pptr TSRMLS_CC); + void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector>* pairs, _Inout_ unsigned char **pptr); void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr); - USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *column_data TSRMLS_CC); + USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *column_data); struct name_id_pair { UCHAR name_len; @@ -1502,8 +1502,8 @@ struct field_meta_data; // *** Statement resource structure *** struct sqlsrv_stmt : public sqlsrv_context { - void free_param_data( TSRMLS_D ); - virtual void new_result_set( TSRMLS_D ); + void free_param_data( void ); + virtual void new_result_set( void ); // free sensitivity classification metadata void clean_up_sensitivity_metadata(); @@ -1551,7 +1551,8 @@ struct sqlsrv_stmt : public sqlsrv_context { // meta data for data classification sqlsrv_malloc_auto_ptr current_sensitivity_metadata; - sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv TSRMLS_DC ); + sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv ); + virtual ~sqlsrv_stmt( void ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants @@ -1607,31 +1608,31 @@ const size_t SQLSRV_CURSOR_BUFFERED = 0xfffffffeUL; // arbitrary number that doe #endif // !_WIN32 // factory to create a statement -typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv TSRMLS_DC ); +typedef sqlsrv_stmt* (*driver_stmt_factory)( sqlsrv_conn* conn, SQLHANDLE h, error_callback e, void* drv ); // *** statement functions *** sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht, - _In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver TSRMLS_DC ); + _In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver ); void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z, _In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size, - _Inout_ SQLSMALLINT decimal_digits TSRMLS_DC ); -SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_reads_bytes_(sql_len) const char* sql = NULL, _In_ int sql_len = 0 ); -field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno TSRMLS_DC ); -bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLULEN fetch_offset TSRMLS_DC ); + _Inout_ SQLSMALLINT decimal_digits ); +SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql = NULL, _In_ int sql_len = 0 ); +field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno ); +bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLULEN fetch_offset ); void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_phptype sqlsrv_phptype, _In_ bool prefer_string, - _Outref_result_bytebuffer_maybenull_(*field_length) void*& field_value, _Inout_ SQLLEN* field_length, _In_ bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC); -bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); -void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool finalize_output_params = true, _In_ bool throw_on_errors = true ); -void core_sqlsrv_post_param( _Inout_ sqlsrv_stmt* stmt, _In_ zend_ulong paramno, zval* param_z TSRMLS_DC ); -void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type TSRMLS_DC ); -void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z TSRMLS_DC ); -void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC ); -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_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC); -void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); + _Outref_result_bytebuffer_maybenull_(*field_length) void*& field_value, _Inout_ SQLLEN* field_length, _In_ bool cache_field, + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out); +bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt ); +void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt, _In_ bool finalize_output_params = true, _In_ bool throw_on_errors = true ); +void core_sqlsrv_post_param( _Inout_ sqlsrv_stmt* stmt, _In_ zend_ulong paramno, zval* param_z ); +void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type ); +void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z ); +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 ); +void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z ); +void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit ); +void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z); +void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt ); //********************************************************************************************************************************* // Result Set @@ -1652,15 +1653,15 @@ struct sqlsrv_result_set { virtual ~sqlsrv_result_set( void ) { } virtual bool cached( int field_index ) = 0; - virtual SQLRETURN fetch( _Inout_ SQLSMALLINT fetch_orientation, _Inout_opt_ SQLLEN fetch_offset TSRMLS_DC ) = 0; + virtual SQLRETURN fetch( _Inout_ SQLSMALLINT fetch_orientation, _Inout_opt_ SQLLEN fetch_offset ) = 0; virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_bytes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC )= 0; + bool handle_warning )= 0; virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, - _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) = 0; + _Inout_ SQLSMALLINT* out_buffer_length ) = 0; virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ) = 0; - virtual SQLLEN row_count( TSRMLS_D ) = 0; + virtual SQLLEN row_count( void ) = 0; }; struct sqlsrv_odbc_result_set : public sqlsrv_result_set { @@ -1669,15 +1670,15 @@ struct sqlsrv_odbc_result_set : public sqlsrv_result_set { virtual ~sqlsrv_odbc_result_set( void ); virtual bool cached( int field_index ) { return false; } - virtual SQLRETURN fetch( _In_ SQLSMALLINT fetch_orientation, _In_ SQLLEN fetch_offset TSRMLS_DC ); + virtual SQLRETURN fetch( _In_ SQLSMALLINT fetch_orientation, _In_ SQLLEN fetch_offset ); virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, - _In_ bool handle_warning TSRMLS_DC ); + _In_ bool handle_warning ); virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, - _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); + _Inout_ SQLSMALLINT* out_buffer_length ); virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); + virtual SQLLEN row_count( void ); private: // prevent invalid instantiations and assignments @@ -1703,19 +1704,19 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { static const zend_long BUFFERED_QUERY_LIMIT_DEFAULT = 10240; // measured in KB static const zend_long BUFFERED_QUERY_LIMIT_INVALID = 0; - explicit sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* odbc TSRMLS_DC ); + explicit sqlsrv_buffered_result_set( _Inout_ sqlsrv_stmt* odbc ); virtual ~sqlsrv_buffered_result_set( void ); virtual bool cached( int field_index ) { return true; } - virtual SQLRETURN fetch( _Inout_ SQLSMALLINT fetch_orientation, _Inout_opt_ SQLLEN fetch_offset TSRMLS_DC ); + virtual SQLRETURN fetch( _Inout_ SQLSMALLINT fetch_orientation, _Inout_opt_ SQLLEN fetch_offset ); virtual SQLRETURN get_data( _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_bytes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length, - bool handle_warning TSRMLS_DC ); + bool handle_warning ); virtual SQLRETURN get_diag_field( _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Inout_updates_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, - _Inout_ SQLSMALLINT* out_buffer_length TSRMLS_DC ); + _Inout_ SQLSMALLINT* out_buffer_length ); virtual sqlsrv_error* get_diag_rec( _In_ SQLSMALLINT record_number ); - virtual SQLLEN row_count( TSRMLS_D ); + virtual SQLLEN row_count( void ); // buffered result set specific SQLSMALLINT column_count( void ) @@ -1900,12 +1901,11 @@ enum error_handling_flags { // 3/message) driver specific error message // The fetch type determines if the indices are numeric, associative, or both. bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, - _In_ logging_severity severity TSRMLS_DC ); + _In_ logging_severity severity ); // format and return a driver specfic error void core_sqlsrv_format_driver_error( _In_ sqlsrv_context& ctx, _In_ sqlsrv_error_const const* custom_error, - _Out_ sqlsrv_error_auto_ptr& formatted_error, _In_ logging_severity severity TSRMLS_DC, _In_opt_ va_list* args ); - + _Out_ sqlsrv_error_auto_ptr& formatted_error, _In_ logging_severity severity, _In_opt_ va_list* args ); // return the message for the HRESULT returned by GetLastError. Some driver errors use this to // return the Windows error, e.g, when a UTF-8 <-> UTF-16 conversion fails. @@ -1916,20 +1916,20 @@ DWORD core_sqlsrv_format_message( _Out_ char* output_buffer, _In_ unsigned outpu // convenience functions that overload either a reference or a pointer so we can use // either in the CHECK_* functions. -inline bool call_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned long sqlsrv_error_code TSRMLS_DC, _In_ bool warning, ... ) +inline bool call_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned long sqlsrv_error_code, _In_ bool warning, ... ) { va_list print_params; va_start( print_params, warning ); - bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); + bool ignored = ctx.error_handler()( ctx, sqlsrv_error_code, warning, &print_params ); va_end( print_params ); return ignored; } -inline bool call_error_handler( _Inout_ sqlsrv_context* ctx, _In_ unsigned long sqlsrv_error_code TSRMLS_DC, _In_ bool warning, ... ) +inline bool call_error_handler( _Inout_ sqlsrv_context* ctx, _In_ unsigned long sqlsrv_error_code, _In_ bool warning, ... ) { va_list print_params; va_start( print_params, warning ); - bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning TSRMLS_CC, &print_params ); + bool ignored = ctx->error_handler()( *ctx, sqlsrv_error_code, warning, &print_params ); va_end( print_params ); return ignored; } @@ -1970,7 +1970,7 @@ inline bool is_truncated_warning( _In_ SQLCHAR* state ) bool flag##unique = (condition); \ bool ignored##unique = true; \ if (flag##unique) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/false, ## __VA_ARGS__ ); \ + ignored##unique = call_error_handler( context, ssphp, /*warning*/false, ## __VA_ARGS__ ); \ } \ if( !ignored##unique ) @@ -1990,7 +1990,7 @@ inline bool is_truncated_warning( _In_ SQLCHAR* state ) #define CHECK_WARNING_AS_ERROR_UNIQUE( unique, condition, context, ssphp, ... ) \ bool ignored##unique = true; \ if( condition ) { \ - ignored##unique = call_error_handler( context, ssphp TSRMLS_CC, /*warning*/true, ## __VA_ARGS__ ); \ + ignored##unique = call_error_handler( context, ssphp, /*warning*/true, ## __VA_ARGS__ ); \ } \ if( !ignored##unique ) @@ -1999,7 +1999,7 @@ inline bool is_truncated_warning( _In_ SQLCHAR* state ) #define CHECK_SQL_WARNING( result, context, ... ) \ if( result == SQL_SUCCESS_WITH_INFO ) { \ - (void)call_error_handler( context, 0 TSRMLS_CC, /*warning*/ true, ## __VA_ARGS__ ); \ + (void)call_error_handler( context, 0, /*warning*/ true, ## __VA_ARGS__ ); \ } #define CHECK_CUSTOM_WARNING_AS_ERROR( condition, context, ssphp, ... ) \ @@ -2012,16 +2012,16 @@ inline bool is_truncated_warning( _In_ SQLCHAR* state ) SQLSRV_ASSERT( result != SQL_INVALID_HANDLE, "Invalid handle returned." ); \ bool ignored = true; \ if( result == SQL_ERROR ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, false, ##__VA_ARGS__ ); \ + ignored = call_error_handler( context, SQLSRV_ERROR_ODBC, false, ##__VA_ARGS__ ); \ } \ else if( result == SQL_SUCCESS_WITH_INFO ) { \ - ignored = call_error_handler( context, SQLSRV_ERROR_ODBC TSRMLS_CC, true TSRMLS_CC, ##__VA_ARGS__ ); \ + ignored = call_error_handler( context, SQLSRV_ERROR_ODBC, true, ##__VA_ARGS__ ); \ } \ if( !ignored ) // throw an exception after it has been hooked into the custom error handler #define THROW_CORE_ERROR( ctx, custom, ... ) \ - (void)call_error_handler( ctx, custom TSRMLS_CC, /*warning*/ false, ## __VA_ARGS__ ); \ + (void)call_error_handler( ctx, custom, /*warning*/ false, ## __VA_ARGS__ ); \ throw core::CoreException(); //********************************************************************************************************************************* @@ -2038,7 +2038,7 @@ namespace core { } }; - inline void check_for_mars_error( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r TSRMLS_DC ) + inline void check_for_mars_error( _Inout_ sqlsrv_stmt* stmt, _In_ SQLRETURN r ) { // Skip this if not SQL_ERROR - // We check for the 'connection busy' error caused by having MultipleActiveResultSets off @@ -2083,7 +2083,7 @@ namespace core { inline SQLRETURN SQLGetDiagField( _Inout_ sqlsrv_context* ctx, _In_ SQLSMALLINT record_number, _In_ SQLSMALLINT diag_identifier, _Out_writes_opt_(buffer_length) SQLPOINTER diag_info_buffer, _In_ SQLSMALLINT buffer_length, - _Out_opt_ SQLSMALLINT* out_buffer_length TSRMLS_DC ) + _Out_opt_ SQLSMALLINT* out_buffer_length ) { SQLRETURN r = ::SQLGetDiagField( ctx->handle_type(), ctx->handle(), record_number, diag_identifier, diag_info_buffer, buffer_length, out_buffer_length ); @@ -2096,7 +2096,7 @@ namespace core { } inline void SQLAllocHandle( _In_ SQLSMALLINT HandleType, _Inout_ sqlsrv_context& InputHandle, - _Out_ SQLHANDLE* OutputHandlePtr TSRMLS_DC ) + _Out_ SQLHANDLE* OutputHandlePtr ) { SQLRETURN r; r = ::SQLAllocHandle( HandleType, InputHandle.handle(), OutputHandlePtr ); @@ -2115,7 +2115,7 @@ namespace core { _Inout_opt_ SQLPOINTER ParameterValuePtr, _Inout_ SQLLEN BufferLength, _Inout_ SQLLEN * StrLen_Or_IndPtr - TSRMLS_DC ) + ) { SQLRETURN r; r = ::SQLBindParameter( stmt->handle(), ParameterNumber, InputOutputType, ValueType, ParameterType, ColumnSize, @@ -2126,7 +2126,7 @@ namespace core { } } - inline void SQLCloseCursor( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) + inline void SQLCloseCursor( _Inout_ sqlsrv_stmt* stmt ) { SQLRETURN r = ::SQLCloseCursor( stmt->handle() ); @@ -2137,7 +2137,7 @@ namespace core { inline void SQLColAttribute( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier, _Out_writes_bytes_opt_(buffer_length) SQLPOINTER field_type_char, _In_ SQLSMALLINT buffer_length, - _Out_opt_ SQLSMALLINT* out_buffer_length, _Out_opt_ SQLLEN* field_type_num TSRMLS_DC ) + _Out_opt_ SQLSMALLINT* out_buffer_length, _Out_opt_ SQLLEN* field_type_num ) { SQLRETURN r = ::SQLColAttribute( stmt->handle(), field_index, field_identifier, field_type_char, buffer_length, out_buffer_length, field_type_num ); @@ -2149,7 +2149,7 @@ namespace core { inline void SQLColAttributeW( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLUSMALLINT field_identifier, _Out_writes_bytes_opt_(buffer_length) SQLPOINTER field_type_char, _In_ SQLSMALLINT buffer_length, - _Out_opt_ SQLSMALLINT* out_buffer_length, _Out_opt_ SQLLEN* field_type_num TSRMLS_DC ) + _Out_opt_ SQLSMALLINT* out_buffer_length, _Out_opt_ SQLLEN* field_type_num ) { SQLRETURN r = ::SQLColAttributeW( stmt->handle(), field_index, field_identifier, field_type_char, buffer_length, out_buffer_length, field_type_num ); @@ -2161,7 +2161,7 @@ namespace core { inline void SQLDescribeCol( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Out_writes_opt_(col_name_length) SQLCHAR* col_name, _In_ SQLSMALLINT col_name_length, _Out_opt_ SQLSMALLINT* col_name_length_out, _Out_opt_ SQLSMALLINT* data_type, _Out_opt_ SQLULEN* col_size, - _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable TSRMLS_DC ) + _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable ) { SQLRETURN r; r = ::SQLDescribeCol( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, @@ -2174,7 +2174,7 @@ namespace core { inline void SQLDescribeColW( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Out_writes_opt_(col_name_length) SQLWCHAR* col_name, _In_ SQLSMALLINT col_name_length, _Out_opt_ SQLSMALLINT* col_name_length_out, _Out_opt_ SQLSMALLINT* data_type, _Out_opt_ SQLULEN* col_size, - _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable TSRMLS_DC ) + _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable ) { SQLRETURN r; r = ::SQLDescribeColW( stmt->handle(), colno, col_name, col_name_length, col_name_length_out, @@ -2186,7 +2186,7 @@ namespace core { } inline void SQLDescribeParam( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT paramno, _Out_opt_ SQLSMALLINT* data_type, _Out_opt_ SQLULEN* col_size, - _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable TSRMLS_DC ) + _Out_opt_ SQLSMALLINT* decimal_digits, _Out_opt_ SQLSMALLINT* nullable ) { SQLRETURN r; r = ::SQLDescribeParam( stmt->handle(), paramno, data_type, col_size, decimal_digits, nullable ); @@ -2206,7 +2206,7 @@ namespace core { } } - inline void SQLEndTran( _In_ SQLSMALLINT handleType, _Inout_ sqlsrv_conn* conn, _In_ SQLSMALLINT completionType TSRMLS_DC ) + inline void SQLEndTran( _In_ SQLSMALLINT handleType, _Inout_ sqlsrv_conn* conn, _In_ SQLSMALLINT completionType ) { SQLRETURN r = ::SQLEndTran( handleType, conn->handle(), completionType ); @@ -2216,11 +2216,11 @@ namespace core { } // SQLExecDirect returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecDirect( _Inout_ sqlsrv_stmt* stmt, _In_ char* sql TSRMLS_DC ) + inline SQLRETURN SQLExecDirect( _Inout_ sqlsrv_stmt* stmt, _In_ char* sql ) { SQLRETURN r = ::SQLExecDirect( stmt->handle(), reinterpret_cast( sql ), SQL_NTS ); - check_for_mars_error( stmt, r TSRMLS_CC ); + check_for_mars_error( stmt, r ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { @@ -2229,12 +2229,12 @@ namespace core { return r; } - inline SQLRETURN SQLExecDirectW( _Inout_ sqlsrv_stmt* stmt, _In_ SQLWCHAR* wsql TSRMLS_DC ) + inline SQLRETURN SQLExecDirectW( _Inout_ sqlsrv_stmt* stmt, _In_ SQLWCHAR* wsql ) { SQLRETURN r; r = ::SQLExecDirectW( stmt->handle(), reinterpret_cast( wsql ), SQL_NTS ); - check_for_mars_error( stmt, r TSRMLS_CC ); + check_for_mars_error( stmt, r ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); @@ -2243,12 +2243,12 @@ namespace core { } // SQLExecute returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA besides just errors/success - inline SQLRETURN SQLExecute( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) + inline SQLRETURN SQLExecute( _Inout_ sqlsrv_stmt* stmt ) { SQLRETURN r; r = ::SQLExecute( stmt->handle() ); - check_for_mars_error( stmt, r TSRMLS_CC ); + check_for_mars_error( stmt, r ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw CoreException(); @@ -2257,7 +2257,7 @@ namespace core { return r; } - inline SQLRETURN SQLFetchScroll( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLLEN fetch_offset TSRMLS_DC ) + inline SQLRETURN SQLFetchScroll( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLLEN fetch_offset ) { SQLRETURN r = ::SQLFetchScroll( stmt->handle(), fetch_orientation, fetch_offset ); @@ -2269,14 +2269,14 @@ namespace core { // wrap SQLFreeHandle and report any errors, but don't actually signal an error to the calling routine - inline void SQLFreeHandle( _Inout_ sqlsrv_context& ctx TSRMLS_DC ) + inline void SQLFreeHandle( _Inout_ sqlsrv_context& ctx ) { SQLRETURN r; r = ::SQLFreeHandle( ctx.handle_type(), ctx.handle() ); CHECK_SQL_ERROR_OR_WARNING( r, ctx ) {} } - inline void SQLGetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _Out_writes_opt_(buf_len) void* value_ptr, _In_ SQLINTEGER buf_len, _Out_opt_ SQLINTEGER* str_len TSRMLS_DC) + inline void SQLGetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _Out_writes_opt_(buf_len) void* value_ptr, _In_ SQLINTEGER buf_len, _Out_opt_ SQLINTEGER* str_len) { SQLRETURN r; r = ::SQLGetStmtAttr( stmt->handle(), attr, value_ptr, buf_len, str_len ); @@ -2287,7 +2287,7 @@ namespace core { inline SQLRETURN SQLGetData( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLSMALLINT target_type, _Out_writes_opt_(buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_opt_ SQLLEN* out_buffer_length, - _In_ bool handle_warning TSRMLS_DC ) + _In_ bool handle_warning ) { SQLRETURN r = ::SQLGetData( stmt->handle(), field_index, target_type, buffer, buffer_length, out_buffer_length ); @@ -2309,7 +2309,7 @@ namespace core { inline void SQLGetInfo( _Inout_ sqlsrv_conn* conn, _In_ SQLUSMALLINT info_type, _Out_writes_bytes_opt_(buffer_len) SQLPOINTER info_value, _In_ SQLSMALLINT buffer_len, - _Out_opt_ SQLSMALLINT* str_len TSRMLS_DC ) + _Out_opt_ SQLSMALLINT* str_len ) { SQLRETURN r; r = ::SQLGetInfo( conn->handle(), info_type, info_value, buffer_len, str_len ); @@ -2320,7 +2320,7 @@ namespace core { } - inline void SQLGetTypeInfo( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT data_type TSRMLS_DC ) + inline void SQLGetTypeInfo( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT data_type ) { SQLRETURN r; r = ::SQLGetTypeInfo( stmt->handle(), data_type ); @@ -2332,7 +2332,7 @@ namespace core { // SQLMoreResults returns the status code since it returns SQL_NO_DATA when there is no more data in a result set. - inline SQLRETURN SQLMoreResults( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) + inline SQLRETURN SQLMoreResults( _Inout_ sqlsrv_stmt* stmt ) { SQLRETURN r = ::SQLMoreResults( stmt->handle() ); @@ -2343,7 +2343,7 @@ namespace core { return r; } - inline SQLSMALLINT SQLNumResultCols( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) + inline SQLSMALLINT SQLNumResultCols( _Inout_ sqlsrv_stmt* stmt ) { SQLRETURN r; SQLSMALLINT num_cols; @@ -2358,7 +2358,7 @@ namespace core { // SQLParamData returns the status code since it returns either SQL_NEED_DATA or SQL_NO_DATA when there are more // parameters or when the parameters are all processed. - inline SQLRETURN SQLParamData( _Inout_ sqlsrv_stmt* stmt, _Out_opt_ SQLPOINTER* value_ptr_ptr TSRMLS_DC ) + inline SQLRETURN SQLParamData( _Inout_ sqlsrv_stmt* stmt, _Out_opt_ SQLPOINTER* value_ptr_ptr ) { SQLRETURN r; r = ::SQLParamData( stmt->handle(), value_ptr_ptr ); @@ -2368,7 +2368,7 @@ namespace core { return r; } - inline void SQLPrepareW( _Inout_ sqlsrv_stmt* stmt, _In_reads_(sql_len) SQLWCHAR * sql, _In_ SQLINTEGER sql_len TSRMLS_DC ) + inline void SQLPrepareW( _Inout_ sqlsrv_stmt* stmt, _In_reads_(sql_len) SQLWCHAR * sql, _In_ SQLINTEGER sql_len ) { SQLRETURN r; r = ::SQLPrepareW( stmt->handle(), sql, sql_len ); @@ -2378,7 +2378,7 @@ namespace core { } - inline void SQLPutData( _Inout_ sqlsrv_stmt* stmt, _In_reads_(strlen_or_ind) SQLPOINTER data_ptr, _In_ SQLLEN strlen_or_ind TSRMLS_DC ) + inline void SQLPutData( _Inout_ sqlsrv_stmt* stmt, _In_reads_(strlen_or_ind) SQLPOINTER data_ptr, _In_ SQLLEN strlen_or_ind ) { SQLRETURN r; r = ::SQLPutData( stmt->handle(), data_ptr, strlen_or_ind ); @@ -2388,7 +2388,7 @@ namespace core { } - inline SQLLEN SQLRowCount( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) + inline SQLLEN SQLRowCount( _Inout_ sqlsrv_stmt* stmt ) { SQLRETURN r; SQLLEN rows_affected; @@ -2415,7 +2415,7 @@ namespace core { } - inline void SQLSetConnectAttr( _Inout_ sqlsrv_context& ctx, _In_ SQLINTEGER attr, _In_reads_bytes_opt_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len TSRMLS_DC ) + inline void SQLSetConnectAttr( _Inout_ sqlsrv_context& ctx, _In_ SQLINTEGER attr, _In_reads_bytes_opt_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len ) { SQLRETURN r; r = ::SQLSetConnectAttr( ctx.handle(), attr, value_ptr, str_len ); @@ -2425,7 +2425,7 @@ namespace core { } } - inline void SQLSetDescField( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT rec_num, _In_ SQLSMALLINT fld_id, _In_reads_bytes_opt_( str_len ) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len TSRMLS_DC ) + inline void SQLSetDescField( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT rec_num, _In_ SQLSMALLINT fld_id, _In_reads_bytes_opt_( str_len ) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len ) { SQLRETURN r; SQLHDESC hIpd = NULL; @@ -2438,7 +2438,7 @@ namespace core { } } - inline void SQLSetEnvAttr( _Inout_ sqlsrv_context& ctx, _In_ SQLINTEGER attr, _In_reads_bytes_opt_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len TSRMLS_DC ) + inline void SQLSetEnvAttr( _Inout_ sqlsrv_context& ctx, _In_ SQLINTEGER attr, _In_reads_bytes_opt_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len ) { SQLRETURN r; r = ::SQLSetEnvAttr( ctx.handle(), attr, value_ptr, str_len ); @@ -2447,7 +2447,7 @@ namespace core { } } - inline void SQLSetConnectAttr( _Inout_ sqlsrv_conn* conn, _In_ SQLINTEGER attribute, _In_reads_bytes_opt_(value_len) SQLPOINTER value_ptr, _In_ SQLINTEGER value_len TSRMLS_DC ) + inline void SQLSetConnectAttr( _Inout_ sqlsrv_conn* conn, _In_ SQLINTEGER attribute, _In_reads_bytes_opt_(value_len) SQLPOINTER value_ptr, _In_ SQLINTEGER value_len ) { SQLRETURN r = ::SQLSetConnectAttr( conn->handle(), attribute, value_ptr, value_len ); @@ -2456,7 +2456,7 @@ namespace core { } } - inline void SQLSetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _In_reads_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len TSRMLS_DC ) + inline void SQLSetStmtAttr( _Inout_ sqlsrv_stmt* stmt, _In_ SQLINTEGER attr, _In_reads_(str_len) SQLPOINTER value_ptr, _In_ SQLINTEGER str_len ) { SQLRETURN r; r = ::SQLSetStmtAttr( stmt->handle(), attr, value_ptr, str_len ); @@ -2494,7 +2494,7 @@ namespace core { // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error // that can be thrown from it. - inline void sqlsrv_add_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zend_ulong index, _In_ zval* value TSRMLS_DC) + inline void sqlsrv_add_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zend_ulong index, _In_ zval* value) { int zr = add_index_zval( array, index, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2502,7 +2502,7 @@ namespace core { } } - inline void sqlsrv_add_next_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zval* value TSRMLS_DC) + inline void sqlsrv_add_next_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zval* value) { int zr = add_next_index_zval( array, value ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2510,7 +2510,7 @@ namespace core { } } - inline void sqlsrv_add_assoc_null( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key TSRMLS_DC ) + inline void sqlsrv_add_assoc_null( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key ) { int zr = ::add_assoc_null( array_z, key ); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2518,7 +2518,7 @@ namespace core { } } - inline void sqlsrv_add_assoc_long( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _In_ zend_long val TSRMLS_DC ) + inline void sqlsrv_add_assoc_long( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _In_ zend_long val ) { int zr = ::add_assoc_long( array_z, key, val ); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2526,7 +2526,7 @@ namespace core { } } - inline void sqlsrv_add_assoc_string( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _Inout_z_ char* val, _In_ bool duplicate TSRMLS_DC ) + inline void sqlsrv_add_assoc_string( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _Inout_z_ char* val, _In_ bool duplicate ) { int zr = ::add_assoc_string(array_z, key, val); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2537,7 +2537,7 @@ namespace core { } } - inline void sqlsrv_add_assoc_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _In_ zval* val TSRMLS_DC ) + inline void sqlsrv_add_assoc_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _In_ zval* val ) { int zr = ::add_assoc_zval(array_z, key, val); CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2545,7 +2545,7 @@ namespace core { } } - inline void sqlsrv_array_init( _Inout_ sqlsrv_context& ctx, _Out_ zval* new_array TSRMLS_DC) + inline void sqlsrv_array_init( _Inout_ sqlsrv_context& ctx, _Out_ zval* new_array) { #if PHP_VERSION_ID < 70300 CHECK_ZEND_ERROR(::array_init(new_array), ctx, SQLSRV_ERROR_ZEND_HASH) { @@ -2556,7 +2556,7 @@ namespace core { #endif } - inline void sqlsrv_php_stream_from_zval_no_verify( _Inout_ sqlsrv_context& ctx, _Outref_result_maybenull_ php_stream*& stream, _In_opt_ zval* stream_z TSRMLS_DC ) + inline void sqlsrv_php_stream_from_zval_no_verify( _Inout_ sqlsrv_context& ctx, _Outref_result_maybenull_ php_stream*& stream, _In_opt_ zval* stream_z ) { // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment php_stream_from_zval_no_verify( stream, stream_z ); @@ -2565,7 +2565,7 @@ namespace core { } } - inline void sqlsrv_zend_hash_get_current_data( _In_ sqlsrv_context& ctx, _In_ HashTable* ht, _Outref_result_maybenull_ zval*& output_data TSRMLS_DC) + inline void sqlsrv_zend_hash_get_current_data( _In_ sqlsrv_context& ctx, _In_ HashTable* ht, _Outref_result_maybenull_ zval*& output_data) { int zr = (output_data = ::zend_hash_get_current_data(ht)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2573,7 +2573,7 @@ namespace core { } } - inline void sqlsrv_zend_hash_get_current_data_ptr( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _Outref_result_maybenull_ void*& output_data TSRMLS_DC) + inline void sqlsrv_zend_hash_get_current_data_ptr( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _Outref_result_maybenull_ void*& output_data) { int zr = (output_data = ::zend_hash_get_current_data_ptr(ht)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { @@ -2581,7 +2581,7 @@ namespace core { } } - inline void sqlsrv_zend_hash_index_del( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index TSRMLS_DC ) + inline void sqlsrv_zend_hash_index_del( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index ) { int zr = ::zend_hash_index_del( ht, index ); CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2589,7 +2589,7 @@ namespace core { } } - inline void sqlsrv_zend_hash_index_update( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_ zval* data_z TSRMLS_DC ) + inline void sqlsrv_zend_hash_index_update( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_ zval* data_z ) { int zr = (data_z = ::zend_hash_index_update(ht, index, data_z)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2597,7 +2597,7 @@ namespace core { } } - inline void sqlsrv_zend_hash_index_update_ptr( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_ void* pData TSRMLS_DC) + inline void sqlsrv_zend_hash_index_update_ptr( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_ void* pData) { int zr = (pData = ::zend_hash_index_update_ptr(ht, index, pData)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { @@ -2606,15 +2606,15 @@ namespace core { } - inline void sqlsrv_zend_hash_index_update_mem( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_reads_bytes_(size) void* pData, _In_ std::size_t size TSRMLS_DC) - { - int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } + inline void sqlsrv_zend_hash_index_update_mem( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zend_ulong index, _In_reads_bytes_(size) void* pData, _In_ std::size_t size) + { + int zr = (pData = ::zend_hash_index_update_mem(ht, index, pData, size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } - inline void sqlsrv_zend_hash_next_index_insert( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zval* data TSRMLS_DC ) + inline void sqlsrv_zend_hash_next_index_insert( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ zval* data ) { int zr = (data = ::zend_hash_next_index_insert(ht, data)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { @@ -2622,15 +2622,15 @@ namespace core { } } - inline void sqlsrv_zend_hash_next_index_insert_mem( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _In_reads_bytes_(data_size) void* data, _In_ size_t data_size TSRMLS_DC) - { - int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; - CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } - } + inline void sqlsrv_zend_hash_next_index_insert_mem( _Inout_ sqlsrv_context& ctx, _In_ HashTable* ht, _In_reads_bytes_(data_size) void* data, _In_ size_t data_size) + { + int zr = (data = ::zend_hash_next_index_insert_mem(ht, data, data_size)) != NULL ? SUCCESS : FAILURE; + CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { + throw CoreException(); + } + } - inline void sqlsrv_zend_hash_next_index_insert_ptr( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ void* data TSRMLS_DC) + inline void sqlsrv_zend_hash_next_index_insert_ptr( _Inout_ sqlsrv_context& ctx, _Inout_ HashTable* ht, _In_ void* data) { int zr = (data = ::zend_hash_next_index_insert_ptr(ht, data)) != NULL ? SUCCESS : FAILURE; CHECK_ZEND_ERROR(zr, ctx, SQLSRV_ERROR_ZEND_HASH) { @@ -2639,21 +2639,21 @@ namespace core { } inline void sqlsrv_zend_hash_init(sqlsrv_context& ctx, _Inout_ HashTable* ht, _Inout_ uint32_t initial_size, - _In_ dtor_func_t dtor_fn, _In_ zend_bool persistent TSRMLS_DC ) + _In_ dtor_func_t dtor_fn, _In_ zend_bool persistent ) { ::zend_hash_init(ht, initial_size, NULL, dtor_fn, persistent); } template -sqlsrv_stmt* allocate_stmt( _In_ sqlsrv_conn* conn, _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ) +sqlsrv_stmt* allocate_stmt( _In_ sqlsrv_conn* conn, _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver ) { - return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver TSRMLS_CC ); + return new ( sqlsrv_malloc( sizeof( Statement ))) Statement( conn, h, e, driver ); } template -sqlsrv_conn* allocate_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver TSRMLS_DC ) +sqlsrv_conn* allocate_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver ) { - return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver TSRMLS_CC ); + return new ( sqlsrv_malloc( sizeof( Connection ))) Connection( h, e, driver ); } } // namespace core @@ -2661,10 +2661,10 @@ sqlsrv_conn* allocate_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* template struct str_conn_attr_func { - static void func( connection_option const* /*option*/, zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + static void func( connection_option const* /*option*/, zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ ) { try { - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), static_cast( Z_STRLEN_P( value )) TSRMLS_CC ); + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_STRVAL_P( value )), static_cast( Z_STRLEN_P( value )) ); } catch ( core::CoreException& ) { throw; diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 7fac8e5b3..18f51e91c 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -92,35 +92,35 @@ const size_t DATE_FORMAT_LEN = sizeof( DATE_FORMAT ); // *** internal functions *** // Only declarations are put here. Functions contain the documentation they need at their definition sites. -void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size TSRMLS_DC ); -size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) const char* buffer, _In_ size_t buffer_end TSRMLS_DC ); -bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); +void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size ); +size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) const char* buffer, _In_ size_t buffer_end ); +bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt ); bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* convert_param_z ); void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype - sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC); + sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len); // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ); +SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding ); void default_sql_size_and_scale( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned int paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits ); // given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ); + _Out_ SQLSMALLINT& sql_type ); void col_cache_dtor( _Inout_ zval* data_z ); void field_cache_dtor( _Inout_ zval* data_z ); void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len); -void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); +void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt ); void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, - _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC ); -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] TSRMLS_DC ); + _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len ); +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] ); bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ); // assure there is enough space for the output parameter string void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z, _In_ SQLULEN paramno, SQLSRV_ENCODING encoding, _In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _In_ SQLSMALLINT decimal_digits, - _Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len TSRMLS_DC ); + _Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len ); void adjustInputPrecision( _Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits ); -void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param TSRMLS_DC ); +void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param ); // send all the stream data -void send_param_streams( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ); +void send_param_streams( _Inout_ sqlsrv_stmt* stmt ); // called when a bound output string parameter is to be destroyed void sqlsrv_output_param_dtor( _Inout_ zval* data ); // called when a bound stream parameter is to be destroyed. @@ -129,7 +129,7 @@ void sqlsrv_stream_dtor( _Inout_ zval* data ); } // constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. -sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv TSRMLS_DC ) : +sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_opt_ void* drv ) : sqlsrv_context( handle, SQL_HANDLE_STMT, e, drv, SQLSRV_ENCODING_DEFAULT ), conn( c ), executed( false ), @@ -155,34 +155,33 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error { ZVAL_UNDEF( &active_stream ); // initialize the input string parameters array (which holds zvals) - core::sqlsrv_array_init( *conn, ¶m_input_strings TSRMLS_CC ); + core::sqlsrv_array_init( *conn, ¶m_input_strings ); // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) ZVAL_NEW_ARR( ¶m_streams ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/ TSRMLS_CC); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( param_streams ), 5 /* # of buckets */, sqlsrv_stream_dtor, 0 /*persistent*/); // initialize the (input only) datetime parameters of converted date time objects to strings array_init( ¶m_datetime_buffers ); // initialize the output string parameters (which holds sqlsrv_output_param structures) ZVAL_NEW_ARR( &output_params ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/ TSRMLS_CC); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL( output_params ), 5 /* # of buckets */, sqlsrv_output_param_dtor, 0 /*persistent*/); // initialize the col cache ZVAL_NEW_ARR( &col_cache ); - core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL(col_cache), 5 /* # of buckets */, col_cache_dtor, 0 /*persistent*/ TSRMLS_CC ); + core::sqlsrv_zend_hash_init( *conn, Z_ARRVAL(col_cache), 5 /* # of buckets */, col_cache_dtor, 0 /*persistent*/ ); // initialize the field cache ZVAL_NEW_ARR( &field_cache ); - core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/ TSRMLS_CC); + core::sqlsrv_zend_hash_init(*conn, Z_ARRVAL(field_cache), 5 /* # of buckets */, field_cache_dtor, 0 /*persistent*/); } // desctructor for sqlsrv statement. sqlsrv_stmt::~sqlsrv_stmt( void ) { if( Z_TYPE( active_stream ) != IS_UNDEF ) { - TSRMLS_FETCH(); - close_active_stream( this TSRMLS_CC ); + close_active_stream( this ); } // delete any current results @@ -208,7 +207,7 @@ sqlsrv_stmt::~sqlsrv_stmt( void ) // centralized place to release (without destroying the hash tables // themselves) all the parameter data that accrues during the // execution phase. -void sqlsrv_stmt::free_param_data( TSRMLS_D ) +void sqlsrv_stmt::free_param_data( void ) { SQLSRV_ASSERT(Z_TYPE( param_input_strings ) == IS_ARRAY && Z_TYPE( param_streams ) == IS_ARRAY, "sqlsrv_stmt::free_param_data: Param zvals aren't arrays." ); @@ -224,7 +223,7 @@ void sqlsrv_stmt::free_param_data( TSRMLS_D ) // to be called whenever a new result set is created, such as after an // execute or next_result. Resets the state variables. -void sqlsrv_stmt::new_result_set( TSRMLS_D ) +void sqlsrv_stmt::new_result_set( void ) { this->fetch_called = false; this->has_rows = false; @@ -254,7 +253,7 @@ void sqlsrv_stmt::new_result_set( TSRMLS_D ) if( cursor_type == SQLSRV_CURSOR_BUFFERED ) { sqlsrv_malloc_auto_ptr result; result = reinterpret_cast ( sqlsrv_malloc( sizeof( sqlsrv_buffered_result_set ) ) ); - new ( result.get() ) sqlsrv_buffered_result_set( this TSRMLS_CC ); + new ( result.get() ) sqlsrv_buffered_result_set( this ); current_results = result.get(); result.transferred(); } @@ -286,7 +285,7 @@ void sqlsrv_stmt::clean_up_sensitivity_metadata() // Returns the created statement sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stmt_factory stmt_factory, _In_opt_ HashTable* options_ht, - _In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver TSRMLS_DC ) + _In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver ) { sqlsrv_malloc_auto_ptr stmt; SQLHANDLE stmt_h = SQL_NULL_HANDLE; @@ -294,9 +293,9 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm try { - core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h TSRMLS_CC ); + core::SQLAllocHandle( SQL_HANDLE_STMT, *conn, &stmt_h ); - stmt = stmt_factory( conn, stmt_h, err, driver TSRMLS_CC ); + stmt = stmt_factory( conn, stmt_h, err, driver ); stmt->conn = conn; @@ -317,14 +316,14 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm // The driver layer should ensure a valid key. DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "allocate_stmt: Invalid statment option key provided." ); - const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts TSRMLS_CC ); + const stmt_option* stmt_opt = get_stmt_option( stmt->conn, index, valid_stmt_opts ); // if the key didn't match, then return the error to the script. // The driver layer should ensure that the key is valid. DEBUG_SQLSRV_ASSERT( stmt_opt != NULL, "allocate_stmt: unexpected null value for statement option." ); // perform the actions the statement option needs done. - (*stmt_opt->func)( stmt, stmt_opt, value_z TSRMLS_CC ); + (*stmt_opt->func)( stmt, stmt_opt, value_z ); } ZEND_HASH_FOREACH_END(); } @@ -377,7 +376,7 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z, _In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size, - _Inout_ SQLSMALLINT decimal_digits TSRMLS_DC ) + _Inout_ SQLSMALLINT decimal_digits ) { SQLSMALLINT c_type; SQLPOINTER buffer = NULL; @@ -493,16 +492,16 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ else{ // if the sql type is unknown, then set the default based on the PHP type passed in if( sql_type == SQL_UNKNOWN_TYPE ){ - default_sql_type( stmt, param_num, param_z, encoding, sql_type TSRMLS_CC ); + default_sql_type( stmt, param_num, param_z, encoding, sql_type ); } // if the size is unknown, then set the default based on the PHP type passed in if( column_size == SQLSRV_UNKNOWN_SIZE ){ - default_sql_size_and_scale( stmt, static_cast(param_num), param_z, encoding, column_size, decimal_digits TSRMLS_CC ); + default_sql_size_and_scale( stmt, static_cast(param_num), param_z, encoding, column_size, decimal_digits ); } } // determine the ODBC C type - c_type = default_c_type( stmt, param_num, param_z, encoding TSRMLS_CC ); + c_type = default_c_type( stmt, param_num, param_z, encoding ); // set the buffer based on the PHP parameter type switch( Z_TYPE_P( param_z )){ @@ -527,7 +526,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ if( direction != SQL_PARAM_INPUT ){ // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool, php_out_type); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); + save_output_param_for_later( stmt, output_param ); } } break; @@ -539,7 +538,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ if( direction != SQL_PARAM_INPUT ){ // save the parameter so that 1) the buffer doesn't go away, and 2) we can set it to NULL if returned sqlsrv_output_param output_param( param_ref, static_cast( param_num ), zval_was_bool, php_out_type); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); + save_output_param_for_later( stmt, output_param ); } } break; @@ -565,7 +564,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ } buffer = Z_STRVAL_P( &wbuffer_z ); buffer_len = Z_STRLEN_P( &wbuffer_z ); - core::sqlsrv_add_index_zval( *stmt, &( stmt->param_input_strings ), param_num, &wbuffer_z TSRMLS_CC ); + core::sqlsrv_add_index_zval( *stmt, &( stmt->param_input_strings ), param_num, &wbuffer_z ); } ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ){ @@ -600,14 +599,14 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ // since this is an output string, assure there is enough space to hold the requested size and // set all the variables necessary (param_z, buffer, buffer_len, and ind_ptr) resize_output_buffer_if_necessary( stmt, param_z, param_num, encoding, c_type, sql_type, column_size, decimal_digits, - buffer, buffer_len TSRMLS_CC ); + buffer, buffer_len ); // save the parameter to be adjusted and/or converted after the results are processed sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len ) ); output_param.saveMetaData(sql_type, column_size, decimal_digits); - save_output_param_for_later( stmt, output_param TSRMLS_CC ); + save_output_param_for_later( stmt, output_param ); // For output parameters, if we set the column_size to be same as the buffer_len, // then if there is a truncation due to the data coming from the server being @@ -638,7 +637,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ SQLSRV_ASSERT( direction == SQL_PARAM_INPUT, "Invalid output param type. The driver layer should catch this." ); sqlsrv_stream stream_encoding( param_z, encoding ); HashTable* streams_ht = Z_ARRVAL( stmt->param_streams ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) TSRMLS_CC ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, streams_ht, param_num, &stream_encoding, sizeof(stream_encoding) ); buffer = reinterpret_cast( param_num ); Z_TRY_ADDREF_P( param_z ); // so that it doesn't go away while we're using it buffer_len = 0; @@ -659,7 +658,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ bool valid_class_name_found = false; - zend_class_entry *class_entry = Z_OBJCE_P( param_z TSRMLS_CC ); + zend_class_entry *class_entry = Z_OBJCE_P( param_z ); while( class_entry != NULL ){ SQLSRV_ASSERT( class_entry->name != NULL, "core_sqlsrv_bind_param: class_entry->name is NULL." ); @@ -698,7 +697,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ params[0] = format_z; // This is equivalent to the PHP code: $param_z->format( $format_z ); where param_z is the // DateTime object and $format_z is the format string. - int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params TSRMLS_CC ); + int zr = call_user_function( EG( function_table ), param_z, &function_z, &buffer_z, 1, params ); zend_string_release( Z_STR( format_z )); zend_string_release( Z_STR( function_z )); CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_num + 1 ){ @@ -727,7 +726,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ } core::SQLBindParameter( stmt, param_num + 1, direction, - c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr TSRMLS_CC ); + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr ); if ( stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP ) { if( decimal_digits == 3 ) @@ -737,7 +736,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ } } catch( core::CoreException& e ){ - stmt->free_param_data( TSRMLS_C ); + stmt->free_param_data(); SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); throw e; } @@ -751,14 +750,14 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ // Return: // true if there is data, false if there is not -SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_reads_bytes_(sql_len) const char* sql, _In_ int sql_len ) +SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ int sql_len ) { SQLRETURN r = SQL_ERROR; try { // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); + close_active_stream( stmt ); if( sql ) { @@ -778,25 +777,25 @@ SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_reads_by throw core::CoreException(); } } - r = core::SQLExecDirectW( stmt, wsql_string TSRMLS_CC ); + r = core::SQLExecDirectW( stmt, wsql_string ); } else { - r = core::SQLExecute( stmt TSRMLS_CC ); + r = core::SQLExecute( stmt ); } // if data is needed (streams were bound) and they should be sent at execute time, then do so now if( r == SQL_NEED_DATA && stmt->send_streams_at_exec ) { - send_param_streams( stmt TSRMLS_CC ); + send_param_streams( stmt ); } - stmt->new_result_set( TSRMLS_C ); + stmt->new_result_set(); stmt->executed = true; // if all the data has been sent and no data was returned then finalize the output parameters - if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt TSRMLS_CC ))) { + if( stmt->send_streams_at_exec && ( r == SQL_NO_DATA || !core_sqlsrv_has_any_result( stmt ))) { - finalize_output_parameters( stmt TSRMLS_CC ); + finalize_output_parameters( stmt ); } // stream parameters are sent, clean the Hashtable if ( stmt->send_streams_at_exec ) { @@ -809,7 +808,7 @@ SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_reads_by // if the statement executed but failed in a subsequent operation before returning, // we need to cancel the statement and deref the output and stream parameters if ( stmt->send_streams_at_exec ) { - finalize_output_parameters( stmt TSRMLS_CC ); + finalize_output_parameters( stmt ); zend_hash_clean( Z_ARRVAL( stmt->param_streams )); } if( stmt->executed ) { @@ -832,7 +831,7 @@ SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_reads_by // Nothing, exception thrown if an error. stmt->past_fetch_end is set to true if the // user scrolls past a non-scrollable result set -bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLULEN fetch_offset TSRMLS_DC ) +bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLULEN fetch_offset ) { // pre-condition check SQLSRV_ASSERT( fetch_orientation >= SQL_FETCH_NEXT || fetch_orientation <= SQL_FETCH_RELATIVE, @@ -857,7 +856,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient if (stmt->column_count != ACTIVE_NUM_COLS_INVALID) { has_fields = stmt->column_count; } else { - has_fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + has_fields = core::SQLNumResultCols( stmt ); stmt->column_count = has_fields; } @@ -867,7 +866,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient } // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); + close_active_stream( stmt ); // if the statement has rows and is not scrollable but doesn't yet have // fetch_called, this must be the first time we've called sqlsrv_fetch. @@ -878,7 +877,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient // move to the record requested. For absolute records, we use a 0 based offset, so +1 since // SQLFetchScroll uses a 1 based offset, otherwise for relative, just use the fetch_offset provided. - SQLRETURN r = stmt->current_results->fetch( fetch_orientation, ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 TSRMLS_CC ); + SQLRETURN r = stmt->current_results->fetch( fetch_orientation, ( fetch_orientation == SQL_FETCH_RELATIVE ) ? fetch_offset : fetch_offset + 1 ); if( r == SQL_NO_DATA ) { // if this is a forward only cursor, mark that we've passed the end so future calls result in an error @@ -911,7 +910,7 @@ bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orient // Return: // A field_meta_data* consisting of the field metadata. -field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno TSRMLS_DC ) +field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno ) { // pre-condition check SQLSRV_ASSERT( colno >= 0, "core_sqlsrv_field_metadata: Invalid column number provided." ); @@ -927,7 +926,7 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL try{ core::SQLDescribeColW( stmt, colno + 1, field_name_temp, SS_MAXCOLNAMELEN + 1, &field_len_temp, &( meta_data->field_type ), & ( meta_data->field_size ), & ( meta_data->field_scale ), - &( meta_data->field_is_nullable ) TSRMLS_CC ); + &( meta_data->field_is_nullable ) ); } catch ( core::CoreException& e ) { throw e; @@ -971,7 +970,7 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL SQLSMALLINT out_buff_len; SQLLEN not_used; core::SQLColAttribute(stmt, colno + 1, SQL_DESC_TYPE_NAME, field_type_name, - sizeof( field_type_name ), &out_buff_len, ¬_used TSRMLS_CC); + sizeof( field_type_name ), &out_buff_len, ¬_used); if (!strcmp(field_type_name, "money") || !strcmp(field_type_name, "smallmoney")) { meta_data->field_is_money_type = true; @@ -986,7 +985,7 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL return result_field_meta_data; } -void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt ) { sqlsrv_malloc_auto_ptr dcbuf; SQLINTEGER dclen = 0; @@ -1027,7 +1026,7 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) SQLRETURN rc; SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {'\0'}; SQLSMALLINT len; - rc = ::SQLGetDiagField(SQL_HANDLE_DESC, ird, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC); + rc = ::SQLGetDiagField(SQL_HANDLE_DESC, ird, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len); CHECK_SQL_ERROR_OR_WARNING(rc, stmt) { throw core::CoreException(); @@ -1095,12 +1094,12 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ sqlsrv_phptype sqlsrv_php_type_in, _In_ bool prefer_string, _Outref_result_bytebuffer_maybenull_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len, _In_ bool cache_field, - _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out TSRMLS_DC) + _Out_ SQLSRV_PHPTYPE *sqlsrv_php_type_out) { try { // close the stream to release the resource - close_active_stream(stmt TSRMLS_CC); + close_active_stream(stmt); // if the field has been retrieved before, return the previous result field_cache* cached = NULL; @@ -1139,7 +1138,7 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i invalid.typeinfo.type = SQLSRV_PHPTYPE_INVALID; for( int i = stmt->last_field_index + 1; i < field_index; ++i ) { SQLSRV_ASSERT( reinterpret_cast( zend_hash_index_find_ptr( Z_ARRVAL( stmt->field_cache ), i )) == NULL, "Field already cached." ); - core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out TSRMLS_CC ); + core_sqlsrv_get_field( stmt, i, invalid, prefer_string, field_value, field_len, cache_field, sqlsrv_php_type_out ); // delete the value returned since we only want it cached, not the actual value if( field_value ) { efree( field_value ); @@ -1183,12 +1182,12 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i *sqlsrv_php_type_out = static_cast( sqlsrv_php_type.typeinfo.type ); // Retrieve the data - core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + core_get_field_common( stmt, field_index, sqlsrv_php_type, field_value, field_len ); // if the user wants us to cache the field, we'll do it if( cache_field ) { field_cache cache( field_value, *field_len, sqlsrv_php_type ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) TSRMLS_CC ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->field_cache ), field_index, &cache, sizeof(field_cache) ); } } @@ -1205,7 +1204,7 @@ void core_sqlsrv_get_field( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // Return: // true if any results are present, false otherwise. -bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt ) { SQLSMALLINT num_cols; SQLLEN rows_affected; @@ -1215,7 +1214,7 @@ bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } else { // Use SQLNumResultCols to determine if we have rows or not - num_cols = core::SQLNumResultCols( stmt TSRMLS_CC ); + num_cols = core::SQLNumResultCols( stmt ); stmt->column_count = num_cols; } @@ -1224,7 +1223,7 @@ bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } else { // Use SQLRowCount to determine if there is a rows status waiting - rows_affected = core::SQLRowCount( stmt TSRMLS_CC ); + rows_affected = core::SQLRowCount( stmt ); stmt->row_count = rows_affected; } @@ -1238,7 +1237,7 @@ bool core_sqlsrv_has_any_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // Returns // Nothing, exception thrown if problem occurs -void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool finalize_output_params, _In_ bool throw_on_errors ) +void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt, _In_ bool finalize_output_params, _In_ bool throw_on_errors ) { try { @@ -1251,14 +1250,14 @@ void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool fin throw core::CoreException(); } - close_active_stream( stmt TSRMLS_CC ); + close_active_stream( stmt ); //Clear column sql types and sql display sizes. zend_hash_clean( Z_ARRVAL( stmt->col_cache )); SQLRETURN r; if( throw_on_errors ) { - r = core::SQLMoreResults( stmt TSRMLS_CC ); + r = core::SQLMoreResults( stmt ); } else { r = SQLMoreResults( stmt->handle() ); @@ -1268,7 +1267,7 @@ void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool fin if( finalize_output_params ) { // if we're finished processing result sets, handle the output parameters - finalize_output_parameters( stmt TSRMLS_CC ); + finalize_output_parameters( stmt ); } // mark we are past the end of all results @@ -1276,7 +1275,7 @@ void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool fin return; } - stmt->new_result_set( TSRMLS_C ); + stmt->new_result_set(); } catch( core::CoreException& e ) { @@ -1295,26 +1294,26 @@ void core_sqlsrv_next_result( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC, _In_ bool fin // Returns: // Nothing, exception thrown if problem occurs -void core_sqlsrv_post_param( _Inout_ sqlsrv_stmt* stmt, _In_ zend_ulong param_num, zval* param_z TSRMLS_DC ) +void core_sqlsrv_post_param( _Inout_ sqlsrv_stmt* stmt, _In_ zend_ulong param_num, zval* param_z ) { SQLSRV_ASSERT( Z_TYPE( stmt->param_input_strings ) == IS_ARRAY, "Statement input parameter UTF-16 buffers array invalid." ); SQLSRV_ASSERT( Z_TYPE( stmt->param_streams ) == IS_ARRAY, "Statement input parameter streams array invalid." ); // if the parameter was an input string, delete it from the array holding input parameter strings if( zend_hash_index_exists( Z_ARRVAL( stmt->param_input_strings ), param_num )) { - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num TSRMLS_CC ); + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_input_strings ), param_num ); } // if the parameter was an input stream, decrement our reference to it and delete it from the array holding input streams // PDO doesn't need the reference count, but sqlsrv does since the stream can be live after sqlsrv_execute by sending it // with sqlsrv_send_stream_data. if( zend_hash_index_exists( Z_ARRVAL( stmt->param_streams ), param_num )) { - core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num TSRMLS_CC ); + core::sqlsrv_zend_hash_index_del( *stmt, Z_ARRVAL( stmt->param_streams ), param_num ); } } //Calls SQLSetStmtAttr to set a cursor. -void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type TSRMLS_DC ) +void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long cursor_type ) { try { @@ -1322,27 +1321,27 @@ void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long c case SQL_CURSOR_STATIC: core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER TSRMLS_CC ); + reinterpret_cast( SQL_CURSOR_STATIC ), SQL_IS_UINTEGER ); break; case SQL_CURSOR_DYNAMIC: core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER TSRMLS_CC ); + reinterpret_cast( SQL_CURSOR_DYNAMIC ), SQL_IS_UINTEGER ); break; case SQL_CURSOR_KEYSET_DRIVEN: core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER TSRMLS_CC ); + reinterpret_cast( SQL_CURSOR_KEYSET_DRIVEN ), SQL_IS_UINTEGER ); break; case SQL_CURSOR_FORWARD_ONLY: core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); + reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER ); break; case SQLSRV_CURSOR_BUFFERED: core::SQLSetStmtAttr( stmt, SQL_ATTR_CURSOR_TYPE, - reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER TSRMLS_CC ); + reinterpret_cast( SQL_CURSOR_FORWARD_ONLY ), SQL_IS_UINTEGER ); break; default: @@ -1358,17 +1357,17 @@ void core_sqlsrv_set_scrollable( _Inout_ sqlsrv_stmt* stmt, _In_ unsigned long c } } -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_ zval* value_z ) { if( Z_TYPE_P( value_z ) != IS_LONG ) { THROW_CORE_ERROR( stmt, SQLSRV_ERROR_INVALID_BUFFER_LIMIT ); } - core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) TSRMLS_CC ); + core_sqlsrv_set_buffered_query_limit( stmt, Z_LVAL_P( value_z ) ); } -void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit TSRMLS_DC ) +void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit ) { if( limit <= 0 ) { @@ -1382,7 +1381,7 @@ void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLE // Extracts the long value and calls the core_sqlsrv_set_query_timeout // which accepts timeout parameter as a long. If the zval is not of type long // than throws error. -void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z TSRMLS_DC ) +void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* value_z ) { try { @@ -1401,7 +1400,7 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* val } } -void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC) +void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z) { try { // first check if the input is an integer @@ -1422,10 +1421,8 @@ void core_sqlsrv_set_decimal_places(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_ } } -void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC ) +void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z ) { - TSRMLS_C; - // zend_is_true does not fail. It either returns true or false. stmt->send_streams_at_exec = ( zend_is_true( value_z )) ? true : false; } @@ -1442,13 +1439,13 @@ void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z // Returns: // true if more data remains to be sent, false if all data processed -bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt ) { // if there no current parameter to process, get the next one // (probably because this is the first call to sqlsrv_send_stream_data) if( stmt->current_stream.stream_z == NULL ) { - if( check_for_next_stream_parameter( stmt TSRMLS_CC ) == false ) { + if( check_for_next_stream_parameter( stmt ) == false ) { stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_CHAR ); stmt->current_stream_read = 0; @@ -1460,7 +1457,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // get the stream from the zval we bound php_stream* param_stream = NULL; - core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z TSRMLS_CC ); + core::sqlsrv_php_stream_from_zval_no_verify( *stmt, param_stream, stmt->current_stream.stream_z ); // if we're at the end, then reset both current_stream and current_stream_read if (php_stream_eof(param_stream)) { @@ -1489,7 +1486,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) if (read == 0) { // send an empty string, which is what a 0 length does. char buff[1]; // temp storage to hand to SQLPutData - core::SQLPutData(stmt, buff, 0 TSRMLS_CC); + core::SQLPutData(stmt, buff, 0); } else if (read > 0) { // if this is a UTF-8 stream, then we will use the UTF-8 encoding to determine if we're in the middle of a character @@ -1516,7 +1513,7 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) // this will calculate how many bytes were cut off from the last UTF-8 character and read that many more // in, then reattempt the conversion. If it fails the second time, then an error is returned. - size_t need_to_read = calc_utf8_missing( stmt, buffer, read TSRMLS_CC ); + size_t need_to_read = calc_utf8_missing( stmt, buffer, read ); // read the missing bytes size_t new_read = php_stream_read( param_stream, static_cast( buffer ) + read, need_to_read ); @@ -1535,17 +1532,17 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) throw core::CoreException(); } } - core::SQLPutData( stmt, wbuffer, wsize * sizeof( SQLWCHAR ) TSRMLS_CC ); + core::SQLPutData( stmt, wbuffer, wsize * sizeof( SQLWCHAR ) ); } else { - core::SQLPutData( stmt, buffer, read TSRMLS_CC ); + core::SQLPutData( stmt, buffer, read ); } } } } catch( core::CoreException& e ) { - stmt->free_param_data( TSRMLS_C ); + stmt->free_param_data(); SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); SQLCancel( stmt->handle() ); stmt->current_stream = sqlsrv_stream( NULL, SQLSRV_ENCODING_DEFAULT ); @@ -1556,30 +1553,28 @@ bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) return true; } -void stmt_option_functor::operator()( _Inout_ sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, _In_ zval* /*value_z*/ TSRMLS_DC ) +void stmt_option_functor::operator()( _Inout_ sqlsrv_stmt* /*stmt*/, stmt_option const* /*opt*/, _In_ zval* /*value_z*/ ) { - TSRMLS_C; - // This implementation should never get called. DIE( "Not implemented." ); } -void stmt_option_query_timeout:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_query_timeout:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z ) { - core_sqlsrv_set_query_timeout( stmt, value_z TSRMLS_CC ); + core_sqlsrv_set_query_timeout( stmt, value_z ); } -void stmt_option_send_at_exec:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_send_at_exec:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { - core_sqlsrv_set_send_at_exec( stmt, value_z TSRMLS_CC ); + core_sqlsrv_set_send_at_exec( stmt, value_z ); } -void stmt_option_buffered_query_limit:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_buffered_query_limit:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { - core_sqlsrv_set_buffered_query_limit( stmt, value_z TSRMLS_CC ); + core_sqlsrv_set_buffered_query_limit( stmt, value_z ); } -void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z ) { if (zend_is_true(value_z)) { stmt->date_as_string = true; @@ -1589,7 +1584,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 ) +void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z ) { if (zend_is_true(value_z)) { stmt->format_decimals = true; @@ -1599,12 +1594,12 @@ void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_o } } -void stmt_option_decimal_places:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_decimal_places:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z ) { - core_sqlsrv_set_decimal_places(stmt, value_z TSRMLS_CC); + core_sqlsrv_set_decimal_places(stmt, value_z); } -void stmt_option_data_classification:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_data_classification:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z ) { if (zend_is_true(value_z)) { stmt->data_classification = true; @@ -1616,7 +1611,7 @@ void stmt_option_data_classification:: operator()( _Inout_ sqlsrv_stmt* stmt, st // internal function to release the active stream. Called by each main API function // that will alter the statement and cancel any retrieval of data from a stream. -void close_active_stream( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +void close_active_stream( _Inout_ sqlsrv_stmt* stmt ) { // if there is no active stream, return if( Z_TYPE( stmt->active_stream ) == IS_UNDEF ) { @@ -1678,7 +1673,7 @@ bool is_a_numeric_type(_In_ SQLSMALLINT sql_type) return false; } -void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size TSRMLS_DC ) +void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size ) { try { @@ -1712,7 +1707,7 @@ void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, case SQL_SS_VARIANT: { // unixODBC 2.3.1 requires wide calls to support pooling - core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size TSRMLS_CC ); + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &size ); break; } @@ -1722,7 +1717,7 @@ void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, case SQL_WVARCHAR: { // unixODBC 2.3.1 requires wide calls to support pooling - core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size TSRMLS_CC ); + core::SQLColAttributeW( stmt, field_index + 1, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &size ); break; } @@ -1739,7 +1734,7 @@ void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, // calculates how many characters were cut off from the end of a buffer when reading // in UTF-8 encoded text -size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) const char* buffer, _In_ size_t buffer_end TSRMLS_DC ) +size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) const char* buffer, _In_ size_t buffer_end ) { const char* last_char = buffer + buffer_end - 1; size_t need_to_read = 0; @@ -1778,11 +1773,11 @@ size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) cons // the driver layer would have to calculate size of the field_value // to decide the amount of memory allocation. void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype - sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC ) + sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len ) { try { - close_active_stream( stmt TSRMLS_CC ); + close_active_stream( stmt ); // make sure that fetch is called before trying to retrieve. CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) { @@ -1804,7 +1799,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i *field_value_temp = 0; SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( SQLLEN ), - field_len, true /*handle_warning*/ TSRMLS_CC ); + field_len, true /*handle_warning*/ ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw core::CoreException(); @@ -1830,7 +1825,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), - field_len, true /*handle_warning*/ TSRMLS_CC ); + field_len, true /*handle_warning*/ ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { throw core::CoreException(); @@ -1852,7 +1847,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i case SQLSRV_PHPTYPE_STRING: { - get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC ); + get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len ); break; } @@ -1868,7 +1863,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i field_value_temp = static_cast(sqlsrv_malloc(MAX_DATETIME_STRING_LEN)); memset(field_value_temp, '\0', MAX_DATETIME_STRING_LEN); - SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_CHAR, field_value_temp, MAX_DATETIME_STRING_LEN, &field_len_temp, true TSRMLS_CC); + SQLRETURN r = stmt->current_results->get_data(field_index + 1, SQL_C_CHAR, field_value_temp, MAX_DATETIME_STRING_LEN, &field_len_temp, true); if (r == SQL_NO_DATA || field_len_temp == SQL_NULL_DATA) { field_value_temp.reset(); @@ -1950,7 +1945,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i // check_for_next_stream_parameter // see if there is another stream to be sent. Returns true and sets the stream as current in the statement structure, otherwise // returns false -bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt ) { zend_ulong stream_index = 0; SQLRETURN r = SQL_SUCCESS; @@ -1958,7 +1953,7 @@ bool check_for_next_stream_parameter( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) zval* param_z = NULL; // get the index into the streams_ht from the parameter data we set in core_sqlsrv_bind_param - r = core::SQLParamData( stmt, reinterpret_cast( &stream_index ) TSRMLS_CC ); + r = core::SQLParamData( stmt, reinterpret_cast( &stream_index ) ); // if no more data, we've exhausted the bound parameters, so return that we're done if( SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { @@ -2046,7 +2041,7 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) +SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding ) { SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; int php_type = Z_TYPE_P( param_z ); @@ -2117,7 +2112,7 @@ SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, // given a zval and encoding, determine the appropriate sql type void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding, - _Out_ SQLSMALLINT& sql_type TSRMLS_DC ) + _Out_ SQLSMALLINT& sql_type ) { sql_type = SQL_UNKNOWN_TYPE; int php_type = Z_TYPE_P(param_z); @@ -2195,7 +2190,7 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ // given a zval and encoding, determine the appropriate column size, and decimal scale (if appropriate) void default_sql_size_and_scale( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned int paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits ) { int php_type = Z_TYPE_P( param_z ); column_size = 0; @@ -2407,7 +2402,7 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f // parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary. // For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server -void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt ) { if (Z_ISUNDEF(stmt->output_params)) return; @@ -2558,7 +2553,7 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) } void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, - _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC ) + _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len ) { SQLRETURN r; SQLSMALLINT c_type; @@ -2584,10 +2579,10 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind sql_field_type = stmt->current_meta_data[field_index]->field_type; // Calculate the field size. - calc_string_size( stmt, field_index, sql_field_type, sql_display_size TSRMLS_CC ); + calc_string_size( stmt, field_index, sql_field_type, sql_display_size ); col_cache cache( sql_field_type, sql_display_size ); - core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) TSRMLS_CC ); + core::sqlsrv_zend_hash_index_update_mem( *stmt, Z_ARRVAL( stmt->col_cache ), field_index, &cache, sizeof( col_cache ) ); } // Determine the correct encoding @@ -2626,7 +2621,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind field_value_temp = static_cast( sqlsrv_malloc( field_len_temp + extra + 1 )); r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, ( field_len_temp + extra ), - &field_len_temp, false /*handle_warning*/ TSRMLS_CC ); + &field_len_temp, false /*handle_warning*/ ); CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) { throw core::CoreException(); @@ -2643,7 +2638,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; SQLSMALLINT len = 0; - stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); + stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); // with Linux connection pooling may not get a truncated warning back but the actual field_len_temp // can be greater than the initallen value. @@ -2673,7 +2668,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind // Get the rest of the data. r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + initial_field_len, - field_len_temp + extra, &dummy_field_len, false /*handle_warning*/ TSRMLS_CC ); + field_len_temp + extra, &dummy_field_len, false /*handle_warning*/ ); // the last packet will contain the actual amount retrieved, not SQL_NO_TOTAL // so we calculate the actual length of the string with that. if ( dummy_field_len != SQL_NO_TOTAL ) @@ -2683,7 +2678,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind if( r == SQL_SUCCESS_WITH_INFO ) { core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len - TSRMLS_CC ); + ); } } while( r == SQL_SUCCESS_WITH_INFO && is_truncated_warning( state )); @@ -2698,7 +2693,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind // Get the rest of the data. r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp + intial_field_len, - field_len_temp + extra, &dummy_field_len, true /*handle_warning*/ TSRMLS_CC ); + field_len_temp + extra, &dummy_field_len, true /*handle_warning*/ ); field_len_temp += intial_field_len; if( dummy_field_len == SQL_NULL_DATA ) { @@ -2750,7 +2745,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind // get the data r = stmt->current_results->get_data( field_index + 1, c_type, field_value_temp, sql_display_size, - &field_len_temp, true /*handle_warning*/ TSRMLS_CC ); + &field_len_temp, true /*handle_warning*/ ); CHECK_SQL_ERROR( r, stmt ) { throw core::CoreException(); } @@ -2828,7 +2823,7 @@ field_value = field_value_temp; // return the option from the stmt_opts array that matches the key. If no option found, // NULL is returned. -stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] TSRMLS_DC ) +stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] ) { for( int i = 0; stmt_opts[i].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { @@ -2896,7 +2891,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ) void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z, _In_ SQLULEN paramno, SQLSRV_ENCODING encoding, _In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _In_ SQLSMALLINT decimal_digits, - _Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len TSRMLS_DC ) + _Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len ) { SQLSRV_ASSERT( column_size != SQLSRV_UNKNOWN_SIZE, "column size should be set to a known value." ); buffer_len = Z_STRLEN_P( param_z ); @@ -3124,7 +3119,7 @@ void adjustInputPrecision( _Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digit // while the query is executed and processed. They are saved in the statement so that // their reference count may be decremented later (after results are processed) -void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param TSRMLS_DC ) +void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param ) { HashTable* param_ht = Z_ARRVAL( stmt->output_params ); zend_ulong paramno = static_cast( param.param_num ); @@ -3135,9 +3130,9 @@ void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_outp // send all the stream data -void send_param_streams( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC ) +void send_param_streams( _Inout_ sqlsrv_stmt* stmt ) { - while( core_sqlsrv_send_stream_packet( stmt TSRMLS_CC )) { } + while( core_sqlsrv_send_stream_packet( stmt )) { } } diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index e0177652c..4dbd1bdb3 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -23,7 +23,7 @@ namespace { // close a stream and free the PHP resources used by it -int sqlsrv_stream_close( _Inout_ php_stream* stream, int /*close_handle*/ TSRMLS_DC ) +int sqlsrv_stream_close( _Inout_ php_stream* stream, int /*close_handle*/ ) { sqlsrv_stream* ss = static_cast( stream->abstract ); SQLSRV_ASSERT( ss != NULL && ss->stmt != NULL, "sqlsrv_stream_close: sqlsrv_stream* ss was null." ); @@ -45,9 +45,9 @@ int sqlsrv_stream_close( _Inout_ php_stream* stream, int /*close_handle*/ TSRMLS // set when sqlsrv_get_field is called by the user specifying which field type they want. #if PHP_VERSION_ID >= 70400 -ssize_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count TSRMLS_DC) +ssize_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count) #else -size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count TSRMLS_DC) +size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) char* buf, _Inout_ size_t count) #endif { SQLLEN read = 0; @@ -94,7 +94,7 @@ size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) } // Warnings will be handled below - SQLRETURN r = ss->stmt->current_results->get_data(ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read, false /*handle_warning*/ TSRMLS_CC); + SQLRETURN r = ss->stmt->current_results->get_data(ss->field_index + 1, c_type, get_data_buffer, count /*BufferLength*/, &read, false /*handle_warning*/); CHECK_SQL_ERROR( r, ss->stmt ) { stream->eof = 1; @@ -114,7 +114,7 @@ size_t sqlsrv_stream_read(_Inout_ php_stream* stream, _Out_writes_bytes_(count) SQLCHAR state[SQL_SQLSTATE_BUFSIZE] = {L'\0'}; SQLSMALLINT len = 0; - ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len TSRMLS_CC ); + ss->stmt->current_results->get_diag_field( 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len ); if( read == SQL_NO_TOTAL ) { SQLSRV_ASSERT( is_truncated_warning( state ), "sqlsrv_stream_read: truncation warning was expected but it " @@ -222,7 +222,7 @@ php_stream_ops sqlsrv_stream_ops = { // return value. There is only one valid way to open a stream, using sqlsrv_get_field on // certain field types. A sqlsrv stream may only be opened in read mode. static php_stream* sqlsrv_stream_opener( _In_opt_ php_stream_wrapper* wrapper, _In_ const char*, _In_ const char* mode, - _In_opt_ int options, _In_ zend_string **, php_stream_context* STREAMS_DC TSRMLS_DC ) + _In_opt_ int options, _In_ zend_string **, php_stream_context* STREAMS_DC ) { #if ZEND_DEBUG @@ -240,7 +240,7 @@ static php_stream* sqlsrv_stream_opener( _In_opt_ php_stream_wrapper* wrapper, _ // check for valid options if( options != REPORT_ERRORS ) { - php_stream_wrapper_log_error( wrapper, options TSRMLS_CC, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" ); + php_stream_wrapper_log_error( wrapper, options, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" ); return NULL; } diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index bc51b34bd..417b70274 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -57,7 +57,7 @@ void log_activity(_In_opt_ const char* msg, _In_opt_ va_list* print_args) std::copy(INTERNAL_FORMAT_ERROR, INTERNAL_FORMAT_ERROR + sizeof(INTERNAL_FORMAT_ERROR), log_msg); } - php_log_err(log_msg TSRMLS_CC); + php_log_err(log_msg); } } @@ -70,10 +70,10 @@ SQLCHAR SSPWARN[] = "01SSP"; // write to the php log if the severity and subsystem match the filters currently set in the INI or // the script (sqlsrv_configure). -void write_to_log( _In_ unsigned int severity TSRMLS_DC, _In_ const char* msg, ...) +void write_to_log( _In_ unsigned int severity, _In_ const char* msg, ...) { SQLSRV_ASSERT( !(g_driver_severity == NULL), "Must register a driver checker function." ); - if (!g_driver_severity(severity TSRMLS_CC)) { + if (!g_driver_severity(severity)) { return; } @@ -242,7 +242,7 @@ void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* i params[0] = value_temp_z; if (call_user_function(EG(function_table), NULL, &function_z, &out_zval, 1, - params TSRMLS_CC) == FAILURE) { + params) == FAILURE) { THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED); } @@ -258,7 +258,7 @@ void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* i // The fetch type determines if the indices are numeric, associative, or both. bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, _In_ logging_severity severity - TSRMLS_DC ) + ) { SQLHANDLE h = ctx.handle(); SQLSMALLINT h_type = ctx.handle_type(); @@ -361,7 +361,7 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu // format and return a driver specfic error void core_sqlsrv_format_driver_error( _In_ sqlsrv_context& ctx, _In_ sqlsrv_error_const const* custom_error, - _Out_ sqlsrv_error_auto_ptr& formatted_error, _In_ logging_severity severity TSRMLS_DC, _In_opt_ va_list* args ) + _Out_ sqlsrv_error_auto_ptr& formatted_error, _In_ logging_severity severity, _In_opt_ va_list* args ) { // allocate space for the formatted message formatted_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(); @@ -606,7 +606,7 @@ namespace data_classification { *pptr = ptr; } - USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *return_array TSRMLS_CC) + USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *return_array) { sensitivity_metadata* meta = stmt->current_sensitivity_metadata; if (meta == NULL) { @@ -617,27 +617,27 @@ namespace data_classification { zval data_classification; ZVAL_UNDEF(&data_classification); - core::sqlsrv_array_init(*stmt, &data_classification TSRMLS_CC ); + core::sqlsrv_array_init(*stmt, &data_classification ); USHORT num_pairs = meta->columns_sensitivity[colno].num_pairs; if (num_pairs == 0) { - core::sqlsrv_add_assoc_zval(*stmt, return_array, DATA_CLASS, &data_classification TSRMLS_CC); + core::sqlsrv_add_assoc_zval(*stmt, return_array, DATA_CLASS, &data_classification); return 0; } zval sensitivity_properties; ZVAL_UNDEF(&sensitivity_properties); - core::sqlsrv_array_init(*stmt, &sensitivity_properties TSRMLS_CC); + core::sqlsrv_array_init(*stmt, &sensitivity_properties); for (USHORT j = 0; j < num_pairs; j++) { zval label_array, infotype_array; ZVAL_UNDEF(&label_array); ZVAL_UNDEF(&infotype_array); - core::sqlsrv_array_init(*stmt, &label_array TSRMLS_CC); - core::sqlsrv_array_init(*stmt, &infotype_array TSRMLS_CC); + core::sqlsrv_array_init(*stmt, &label_array); + core::sqlsrv_array_init(*stmt, &infotype_array); USHORT labelidx = meta->columns_sensitivity[colno].label_info_pairs[j].label_idx; USHORT typeidx = meta->columns_sensitivity[colno].label_info_pairs[j].infotype_idx; @@ -647,22 +647,22 @@ namespace data_classification { char *infotype = meta->infotypes[typeidx]->name; char *infotype_id = meta->infotypes[typeidx]->id; - core::sqlsrv_add_assoc_string(*stmt, &label_array, NAME, label, 1 TSRMLS_CC); - core::sqlsrv_add_assoc_string(*stmt, &label_array, ID, label_id, 1 TSRMLS_CC); + core::sqlsrv_add_assoc_string(*stmt, &label_array, NAME, label, 1); + core::sqlsrv_add_assoc_string(*stmt, &label_array, ID, label_id, 1); - core::sqlsrv_add_assoc_zval(*stmt, &sensitivity_properties, LABEL, &label_array TSRMLS_CC); + core::sqlsrv_add_assoc_zval(*stmt, &sensitivity_properties, LABEL, &label_array); - core::sqlsrv_add_assoc_string(*stmt, &infotype_array, NAME, infotype, 1 TSRMLS_CC); - core::sqlsrv_add_assoc_string(*stmt, &infotype_array, ID, infotype_id, 1 TSRMLS_CC); + core::sqlsrv_add_assoc_string(*stmt, &infotype_array, NAME, infotype, 1); + core::sqlsrv_add_assoc_string(*stmt, &infotype_array, ID, infotype_id, 1); - core::sqlsrv_add_assoc_zval(*stmt, &sensitivity_properties, INFOTYPE, &infotype_array TSRMLS_CC); + core::sqlsrv_add_assoc_zval(*stmt, &sensitivity_properties, INFOTYPE, &infotype_array); // add the pair of sensitivity properties to data_classification - core::sqlsrv_add_next_index_zval(*stmt, &data_classification, &sensitivity_properties TSRMLS_CC ); + core::sqlsrv_add_next_index_zval(*stmt, &data_classification, &sensitivity_properties ); } // add data classfication as associative array - core::sqlsrv_add_assoc_zval(*stmt, return_array, DATA_CLASS, &data_classification TSRMLS_CC); + core::sqlsrv_add_assoc_zval(*stmt, return_array, DATA_CLASS, &data_classification); return num_pairs; } diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 58aaf14bf..d8336e7c7 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -35,10 +35,8 @@ unsigned int current_log_subsystem = LOG_CONN; struct date_as_string_func { - static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ ) { - TSRMLS_C; // show as used to avoid a warning - ss_sqlsrv_conn* ss_conn = static_cast( conn ); if( zend_is_true( value )) { @@ -52,10 +50,8 @@ struct date_as_string_func { struct format_decimals_func { - static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC) + static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/) { - TSRMLS_C; // show as used to avoid a warning - ss_sqlsrv_conn* ss_conn = static_cast(conn); if (zend_is_true(value)) { @@ -70,10 +66,8 @@ struct format_decimals_func struct decimal_places_func { - static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC) + static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/) { - TSRMLS_C; // show as used to avoid a warning - // first check if the input is an integer if (Z_TYPE_P(value) != IS_LONG) { THROW_SS_ERROR(conn, SQLSRV_ERROR_INVALID_DECIMAL_PLACES); @@ -91,7 +85,7 @@ struct decimal_places_func struct conn_char_set_func { - static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ ) { convert_to_string( value ); const char* encoding = Z_STRVAL_P( value ); @@ -121,9 +115,8 @@ struct conn_char_set_func { struct bool_conn_str_func { - static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC ) + static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str ) { - TSRMLS_C; char const* val_str; if( zend_is_true( value )) { val_str = "yes"; @@ -140,9 +133,8 @@ struct bool_conn_str_func { struct int_conn_str_func { - static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC ) + static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str ) { - TSRMLS_C; SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_LONG, "An integer is expected for this keyword" ) std::string val_str = std::to_string( Z_LVAL_P( value )); @@ -157,11 +149,11 @@ struct int_conn_str_func { template struct int_conn_attr_func { - static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ ) { try { - core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_LVAL_P( value )), SQL_IS_UINTEGER TSRMLS_CC ); + core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_LVAL_P( value )), SQL_IS_UINTEGER ); } catch( core::CoreException& ) { throw; @@ -172,10 +164,10 @@ struct int_conn_attr_func { template struct bool_conn_attr_func { - static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC ) + static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ ) { try { - core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((zend_long)zend_is_true(value)), SQL_IS_UINTEGER TSRMLS_CC); + core::SQLSetConnectAttr(conn, Attr, reinterpret_cast((zend_long)zend_is_true(value)), SQL_IS_UINTEGER); } catch( core::CoreException& ) { @@ -187,15 +179,15 @@ struct bool_conn_attr_func { //// *** internal functions *** -void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn TSRMLS_DC ); +void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn ); void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options_z, _Inout_ char** uid, _Inout_ char** pwd, - _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ); -void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ); + _Inout_ HashTable* ss_conn_options_ht ); +void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht ); void add_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, - _Inout_ HashTable* options_ht, _Inout_ zval* data TSRMLS_DC ); -void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ HashTable* options_ht, _Inout_ zval* data TSRMLS_DC ); -int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z TSRMLS_DC ); -int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len TSRMLS_DC ); + _Inout_ HashTable* options_ht, _Inout_ zval* data ); +void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ HashTable* options_ht, _Inout_ zval* data ); +int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z ); +int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len ); } @@ -633,7 +625,7 @@ PHP_FUNCTION ( sqlsrv_connect ) g_ss_henv_cp->set_func(_FN_); g_ss_henv_ncp->set_func(_FN_); - reset_errors( TSRMLS_C ); + reset_errors(); const char* server = NULL; zval* options_z = NULL; @@ -643,7 +635,7 @@ PHP_FUNCTION ( sqlsrv_connect ) zval conn_z; ZVAL_UNDEF(&conn_z); // get the server name and connection options - int result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &server, &server_len, &options_z ); + int result = zend_parse_parameters( ZEND_NUM_ARGS(), "s|a", &server, &server_len, &options_z ); CHECK_CUSTOM_ERROR(( result == FAILURE ), *g_ss_henv_cp, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, "sqlsrv_connect" ) { RETURN_FALSE; @@ -659,26 +651,26 @@ PHP_FUNCTION ( sqlsrv_connect ) ALLOC_HASHTABLE( ss_conn_options_ht ); core::sqlsrv_zend_hash_init( *g_ss_henv_cp, ss_conn_options_ht, 10 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_PTR_DTOR, 0 /*persistent*/ ); // Either of g_ss_henv_cp or g_ss_henv_ncp can be used to propagate the error. - ::validate_conn_options( *g_ss_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht TSRMLS_CC ); + ::validate_conn_options( *g_ss_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht ); // call the core connect function conn = static_cast( core_sqlsrv_connect( *g_ss_henv_cp, *g_ss_henv_ncp, &core::allocate_conn, server, uid, pwd, ss_conn_options_ht, ss_error_handler, - SS_CONN_OPTS, NULL, "sqlsrv_connect" TSRMLS_CC )); + SS_CONN_OPTS, NULL, "sqlsrv_connect" )); SQLSRV_ASSERT( conn != NULL, "sqlsrv_connect: Invalid connection returned. Exception should have been thrown." ); // create a bunch of statements ALLOC_HASHTABLE( stmts ); - core::sqlsrv_zend_hash_init( *g_ss_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ TSRMLS_CC ); + core::sqlsrv_zend_hash_init( *g_ss_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ ); // register the connection with the PHP runtime - ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC); + ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name); conn->stmts = stmts; stmts.transferred(); @@ -739,7 +731,7 @@ PHP_FUNCTION( sqlsrv_begin_transaction ) try { - core_sqlsrv_begin_transaction( conn TSRMLS_CC ); + core_sqlsrv_begin_transaction( conn ); conn->in_transaction = true; RETURN_TRUE; } @@ -778,7 +770,7 @@ PHP_FUNCTION( sqlsrv_close ) ss_sqlsrv_conn* conn = NULL; sqlsrv_context_auto_ptr error_ctx; - reset_errors( TSRMLS_C ); + reset_errors(); try { @@ -786,10 +778,10 @@ PHP_FUNCTION( sqlsrv_close ) error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); error_ctx->set_func(_FN_); - if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r) == FAILURE ) { + if( zend_parse_parameters(ZEND_NUM_ARGS(), "r", &conn_r) == FAILURE ) { // Check if it was a zval - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &conn_r ); + int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "z", &conn_r ); CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { throw ss::SSException(); } @@ -803,7 +795,7 @@ PHP_FUNCTION( sqlsrv_close ) } } SQLSRV_ASSERT( conn_r != NULL, "sqlsrv_close: conn_r was null" ); - conn = static_cast( zend_fetch_resource( Z_RES_P( conn_r ) TSRMLS_CC, ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor )); + conn = static_cast( zend_fetch_resource( Z_RES_P( conn_r ), ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor )); // if sqlsrv_close was called on an already closed connection then we just return success. if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) { @@ -842,7 +834,7 @@ PHP_FUNCTION( sqlsrv_close ) } } -void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ) +void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc ) { // Without sqlsrv_close(), this function is invoked by php during the final clean up stage. // To prevent memory/resource leaks, no more logging at this point. @@ -855,10 +847,10 @@ void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ) conn->set_func(__func__); // close all statements associated with the connection. - sqlsrv_conn_close_stmts( conn TSRMLS_CC ); + sqlsrv_conn_close_stmts( conn ); // close the connection itself. - core_sqlsrv_close( conn TSRMLS_CC ); + core_sqlsrv_close( conn ); rsrc->ptr = NULL; } @@ -901,7 +893,7 @@ PHP_FUNCTION( sqlsrv_commit ) try { conn->in_transaction = false; - core_sqlsrv_commit( conn TSRMLS_CC ); + core_sqlsrv_commit( conn ); RETURN_TRUE; } @@ -955,7 +947,7 @@ PHP_FUNCTION( sqlsrv_rollback ) try { conn->in_transaction = false; - core_sqlsrv_rollback( conn TSRMLS_CC ); + core_sqlsrv_rollback( conn ); RETURN_TRUE; } catch( core::CoreException& ){ @@ -982,13 +974,13 @@ PHP_FUNCTION( sqlsrv_client_info ) try { - core_sqlsrv_get_client_info( conn, return_value TSRMLS_CC ); + core_sqlsrv_get_client_info( conn, return_value ); // Add the sqlsrv driver's file version //Declarations below eliminate compiler warnings about string constant to char* conversions const char* extver = "ExtensionVer"; std::string filever = VER_FILEVERSION_STR; - core::sqlsrv_add_assoc_string( *conn, return_value, extver, &filever[0], 1 /*duplicate*/ TSRMLS_CC ); + core::sqlsrv_add_assoc_string( *conn, return_value, extver, &filever[0], 1 /*duplicate*/ ); } catch( core::CoreException& ) { @@ -1024,7 +1016,7 @@ PHP_FUNCTION( sqlsrv_server_info ) ss_sqlsrv_conn* conn = NULL; PROCESS_PARAMS( conn, "r", _FN_, 0 ); - core_sqlsrv_get_server_info( conn, return_value TSRMLS_CC ); + core_sqlsrv_get_server_info( conn, return_value ); } catch( core::CoreException& ) { @@ -1093,9 +1085,9 @@ PHP_FUNCTION( sqlsrv_prepare ) // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( ss_stmt_options_ht ); core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, - ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC ); + ZVAL_PTR_DTOR, 0 /*persistent*/ ); - validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); + validate_stmt_options( *conn, options_z, ss_stmt_options_ht ); } @@ -1114,9 +1106,9 @@ PHP_FUNCTION( sqlsrv_prepare ) stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, ss_stmt_options_ht, SS_STMT_OPTS, - ss_error_handler, NULL TSRMLS_CC ) ); + ss_error_handler, NULL ) ); - core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC ); + core_sqlsrv_prepare( stmt, sql, sql_len ); if (params_z) { stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); @@ -1126,13 +1118,13 @@ PHP_FUNCTION( sqlsrv_prepare ) stmt->prepared = true; // register the statement with the PHP runtime - ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC ); + ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name ); // store the resource id with the connection so the connection // can release this statement when it closes. zend_long next_index = zend_hash_next_free_element( conn->stmts ); - core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); + core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z); stmt->conn_index = next_index; @@ -1150,7 +1142,7 @@ PHP_FUNCTION( sqlsrv_prepare ) stmt->~ss_sqlsrv_stmt(); } if (!Z_ISUNDEF(stmt_z)) { - free_stmt_resource(&stmt_z TSRMLS_CC); + free_stmt_resource(&stmt_z); } RETURN_FALSE; @@ -1216,9 +1208,9 @@ PHP_FUNCTION( sqlsrv_query ) // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( ss_stmt_options_ht ); core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, ZVAL_PTR_DTOR, - 0 /*persistent*/ TSRMLS_CC ); + 0 /*persistent*/ ); - validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC ); + validate_stmt_options( *conn, options_z, ss_stmt_options_ht ); } if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) { @@ -1236,7 +1228,7 @@ PHP_FUNCTION( sqlsrv_query ) stmt = static_cast( core_sqlsrv_create_stmt( conn, core::allocate_stmt, ss_stmt_options_ht, SS_STMT_OPTS, - ss_error_handler, NULL TSRMLS_CC ) ); + ss_error_handler, NULL ) ); if( params_z ) { stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval)); @@ -1245,18 +1237,18 @@ PHP_FUNCTION( sqlsrv_query ) stmt->set_func( "sqlsrv_query" ); - bind_params( stmt TSRMLS_CC ); + bind_params( stmt ); // execute the statement - core_sqlsrv_execute( stmt TSRMLS_CC, sql, static_cast( sql_len ) ); + core_sqlsrv_execute( stmt, sql, static_cast( sql_len ) ); // register the statement with the PHP runtime - ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC); + ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name); // store the resource id with the connection so the connection // can release this statement when it closes. zend_ulong next_index = zend_hash_next_free_element( conn->stmts ); - core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC); + core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z); stmt->conn_index = next_index; stmt.transferred(); @@ -1271,7 +1263,7 @@ PHP_FUNCTION( sqlsrv_query ) stmt->~ss_sqlsrv_stmt(); } if (!Z_ISUNDEF(stmt_z)) { - free_stmt_resource(&stmt_z TSRMLS_CC); + free_stmt_resource(&stmt_z); } RETURN_FALSE; @@ -1282,7 +1274,7 @@ PHP_FUNCTION( sqlsrv_query ) } } -void free_stmt_resource( _Inout_ zval* stmt_z TSRMLS_DC ) +void free_stmt_resource( _Inout_ zval* stmt_z ) { if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) { LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z)); @@ -1298,7 +1290,7 @@ namespace { // must close all statement handles opened by this connection before closing the connection // no errors are returned, since close should always succeed -void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn TSRMLS_DC ) +void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn ) { //pre-condition check SQLSRV_ASSERT(( conn->handle() != NULL ), "sqlsrv_conn_close_stmts: Connection handle is NULL. Trying to destroy an " @@ -1346,7 +1338,7 @@ void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn TSRMLS_DC ) conn->stmts = NULL; } -int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z TSRMLS_DC ) +int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z ) { for( int i=0; SS_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i ) { @@ -1407,7 +1399,7 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In return SQLSRV_CONN_OPTION_INVALID; } -int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len TSRMLS_DC ) +int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len ) { for( int i = 0; SS_STMT_OPTS[i].key != SQLSRV_STMT_OPTION_INVALID; ++i ) { @@ -1419,9 +1411,9 @@ int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len TSRMLS_DC ) } void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, - _Inout_ HashTable* options_ht, _Inout_ zval* data TSRMLS_DC ) + _Inout_ HashTable* options_ht, _Inout_ zval* data ) { - int option_key = ::get_stmt_option_key( key, key_len TSRMLS_CC ); + int option_key = ::get_stmt_option_key( key, key_len ); CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) { @@ -1429,27 +1421,27 @@ void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _I } Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. - core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); + core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data ); } void add_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, - _Inout_ HashTable* options_ht, _Inout_ zval* data TSRMLS_DC ) + _Inout_ HashTable* options_ht, _Inout_ zval* data ) { - int option_key = ::get_conn_option_key( ctx, key, key_len, data TSRMLS_CC ); + int option_key = ::get_conn_option_key( ctx, key, key_len, data ); CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, ZSTR_VAL( key ) ) { throw ss::SSException(); } Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too. - core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC ); + core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data ); } // Iterates through the list of statement options provided by the user and validates them // against the list of supported statement options by this driver. After validation // creates a Hashtable of statement options to be sent to the core layer for processing. -void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC ) +void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht ) { try { if( stmt_options ) { @@ -1471,7 +1463,7 @@ void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_opti } else if ( key != NULL ) { key_len = ZSTR_LEN( key ) + 1; - add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC ); + add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data ); } else { DIE( "validate_stmt_options: key was null." ); @@ -1489,7 +1481,7 @@ void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_opti // against the predefined list of supported connection options by this driver. After validation // creates a Hashtable of connection options to be sent to the core layer for processing. -void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options_z, _Inout_ char** uid, _Inout_ char** pwd, _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC ) +void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options_z, _Inout_ char** uid, _Inout_ char** pwd, _Inout_ HashTable* ss_conn_options_ht ) { try { @@ -1524,7 +1516,7 @@ void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options } else { - ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC ); + ::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data ); } } else { diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 96f5338f1..7c6fcc196 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -551,14 +551,14 @@ PHP_MINIT_FUNCTION(sqlsrv) } } - if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper TSRMLS_CC ) == FAILURE ) { + if( php_register_url_stream_wrapper( SQLSRV_STREAM_WRAPPER, &g_sqlsrv_stream_wrapper ) == FAILURE ) { LOG( SEV_ERROR, "%1!s!: stream registration failed", _FN_ ); return FAILURE; } try { // retrieve the handles for the environments - core_sqlsrv_minit( &g_ss_henv_cp, &g_ss_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" TSRMLS_CC ); + core_sqlsrv_minit( &g_ss_henv_cp, &g_ss_henv_ncp, ss_error_handler, "PHP_MINIT_FUNCTION for sqlsrv" ); } catch( core::CoreException& ) { @@ -610,7 +610,7 @@ PHP_MSHUTDOWN_FUNCTION(sqlsrv) core_sqlsrv_mshutdown( *g_ss_henv_cp, *g_ss_henv_ncp ); - if( php_unregister_url_stream_wrapper( SQLSRV_STREAM_WRAPPER TSRMLS_CC ) == FAILURE ) { + if( php_unregister_url_stream_wrapper( SQLSRV_STREAM_WRAPPER ) == FAILURE ) { return FAILURE; } @@ -692,7 +692,7 @@ PHP_RSHUTDOWN_FUNCTION(sqlsrv) SQLSRV_UNUSED( type ); LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" ); - reset_errors( TSRMLS_C ); + reset_errors(); // TODO - destruction zval_ptr_dtor( &SQLSRV_G( errors )); diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index fae7082e5..e0cea7bb7 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -91,8 +91,8 @@ struct ss_sqlsrv_conn : sqlsrv_conn static int descriptor; // initialize with default values - ss_sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC ) : - sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM TSRMLS_CC ), + ss_sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv ) : + sqlsrv_conn( h, e, drv, SQLSRV_ENCODING_SYSTEM ), stmts( NULL ), date_as_string( false ), format_decimals( false ), @@ -103,8 +103,7 @@ struct ss_sqlsrv_conn : sqlsrv_conn }; // resource destructor -void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ); - +void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc ); //********************************************************************************************************************************* // Statement @@ -117,18 +116,16 @@ struct sqlsrv_fetch_field_name { }; struct stmt_option_ss_scrollable : public stmt_option_functor { - - virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ); + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; // This object inherits and overrides the callbacks necessary struct ss_sqlsrv_stmt : public sqlsrv_stmt { - - ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv TSRMLS_DC ); + ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv ); virtual ~ss_sqlsrv_stmt( void ); - void new_result_set( TSRMLS_D ); + void new_result_set( void ); // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); @@ -137,7 +134,7 @@ struct ss_sqlsrv_stmt : public sqlsrv_stmt { virtual void set_query_timeout(); bool prepared; // whether the statement has been prepared yet (used for error messages) - zend_ulong conn_index; // index into the connection hash that contains this statement structure + zend_ulong conn_index; // index into the connection hash that contains this statement structure zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute sqlsrv_fetch_field_name* fetch_field_names; // field names for current results used by sqlsrv_fetch_array/object as keys int fetch_fields_count; @@ -166,14 +163,14 @@ struct sqlsrv_stream_encoding { }; // resource destructor -void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ); +void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc ); // "internal" statement functions shared by functions in conn.cpp and stmt.cpp -void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ); +void bind_params( _Inout_ ss_sqlsrv_stmt* stmt ); bool sqlsrv_stmt_common_execute( sqlsrv_stmt* s, const SQLCHAR* sql_string, int sql_len, bool direct, const char* function - TSRMLS_DC ); -void free_odbc_resources( ss_sqlsrv_stmt* stmt TSRMLS_DC ); -void free_stmt_resource( _Inout_ zval* stmt_z TSRMLS_DC ); + ); +void free_odbc_resources( ss_sqlsrv_stmt* stmt ); +void free_stmt_resource( _Inout_ zval* stmt_z ); //********************************************************************************************************************************* @@ -218,7 +215,7 @@ enum SS_ERROR_CODES { extern ss_error SS_ERRORS[]; -bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool warning TSRMLS_DC, _In_opt_ va_list* print_args ); +bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool warning, _In_opt_ va_list* print_args ); // convert from the default encoding specified by the "CharacterSet" // connection option to UTF-16. mbcs_len and utf16_len are sizes in @@ -235,13 +232,13 @@ SQLWCHAR* utf16_string_from_mbcs_string( _In_ unsigned int php_encoding, _In_rea // *** internal error macros and functions *** bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); + sqlsrv_error const* ssphp, ... ); void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* function, - sqlsrv_error const* ssphp TSRMLS_DC, ... ); -void __cdecl sqlsrv_error_dtor( zend_resource *rsrc TSRMLS_DC ); + sqlsrv_error const* ssphp, ... ); +void __cdecl sqlsrv_error_dtor( zend_resource *rsrc ); // release current error lists and set to NULL -inline void reset_errors( TSRMLS_D ) +inline void reset_errors( void ) { if( Z_TYPE( SQLSRV_G( errors )) != IS_ARRAY && Z_TYPE( SQLSRV_G( errors )) != IS_NULL ) { DIE( "sqlsrv_errors contains an invalid type" ); @@ -264,7 +261,7 @@ inline void reset_errors( TSRMLS_D ) } #define THROW_SS_ERROR( ctx, error_code, ... ) \ - (void)call_error_handler( ctx, error_code TSRMLS_CC, false /*warning*/, ## __VA_ARGS__ ); \ + (void)call_error_handler( ctx, error_code, false /*warning*/, ## __VA_ARGS__ ); \ throw ss::SSException(); @@ -317,7 +314,7 @@ class sqlsrv_context_auto_ptr : public sqlsrv_auto_ptr< sqlsrv_context, sqlsrv_c LOG(SEV_NOTICE, "%1!s!: entering", _FN_); // check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro -bool ss_severity_check(_In_ unsigned int severity TSRMLS_DC); +bool ss_severity_check(_In_ unsigned int severity); // subsystems that may report log messages. These may be used to filter which systems write to the log to prevent noise. enum logging_subsystems { @@ -345,7 +342,7 @@ namespace ss { } }; - inline void zend_register_resource( _Inout_ zval& rsrc_result, _Inout_ void* rsrc_pointer, _In_ int rsrc_type, _In_opt_ const char* rsrc_name TSRMLS_DC) + inline void zend_register_resource( _Inout_ zval& rsrc_result, _Inout_ void* rsrc_pointer, _In_ int rsrc_type, _In_opt_ const char* rsrc_name) { int zr = (NULL != (Z_RES(rsrc_result) = ::zend_register_resource(rsrc_pointer, rsrc_type)) ? SUCCESS : FAILURE); CHECK_CUSTOM_ERROR(( zr == FAILURE ), reinterpret_cast( rsrc_pointer ), SS_SQLSRV_ERROR_REGISTER_RESOURCE, @@ -372,7 +369,7 @@ inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, _In_ char const* param_s H* h; // reset the errors from the previous API call - reset_errors( TSRMLS_C ); + reset_errors(); if( ZEND_NUM_ARGS() > param_count + 1 ) { DIE( "Param count and argument count don't match." ); @@ -406,35 +403,35 @@ inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, _In_ char const* param_s switch( param_count ) { case 0: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc ); + result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast( param_spec ), &rsrc ); break; case 1: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0] ); + result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast( param_spec ), &rsrc, arr[0] ); break; case 2: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast( param_spec ), &rsrc, arr[0], arr[1] ); break; case 3: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast( param_spec ), &rsrc, arr[0], arr[1], arr[2] ); break; case 4: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast( param_spec ), &rsrc, arr[0], arr[1], arr[2], arr[3] ); break; case 5: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast( param_spec ), &rsrc, arr[0], arr[1], arr[2], arr[3], arr[4] ); break; case 6: - result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, const_cast( param_spec ), &rsrc, arr[0], + result = zend_parse_parameters( ZEND_NUM_ARGS(), const_cast( param_spec ), &rsrc, arr[0], arr[1], arr[2], arr[3], arr[4], arr[5] ); break; @@ -451,7 +448,7 @@ inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, _In_ char const* param_s } // get the resource registered - h = static_cast( zend_fetch_resource(Z_RES_P(rsrc) TSRMLS_CC, H::resource_name, H::descriptor )); + h = static_cast( zend_fetch_resource(Z_RES_P(rsrc), H::resource_name, H::descriptor )); CHECK_CUSTOM_ERROR(( h == NULL ), &error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, calling_func ) { diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index c7f002dce..0e19771dc 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -89,21 +89,20 @@ const char* NULLABLE = "Nullable"; void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval ); SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt* stmt); -void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names - TSRMLS_DC ); +void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names ); bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size, _Out_ SQLSMALLINT* decimal_digits ); sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string ); -void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ); +void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt ); bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ); bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype type ); void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ); + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits ); void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ); void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ); void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ); -bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ); +bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding ); } @@ -126,15 +125,15 @@ namespace SSCursorTypes { const char QUERY_OPTION_SCROLLABLE_BUFFERED[] = "buffered"; } -ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv TSRMLS_DC ) : - sqlsrv_stmt( c, handle, e, drv TSRMLS_CC ), +ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv ) : + sqlsrv_stmt( c, handle, e, drv ), prepared( false ), conn_index( -1 ), params_z( NULL ), fetch_field_names( NULL ), fetch_fields_count ( 0 ) { - core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) TSRMLS_CC ); + core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) ); // inherit other values based on the corresponding connection options ss_sqlsrv_conn* ss_conn = static_cast(conn); @@ -164,7 +163,7 @@ ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void ) // to be called whenever a new result set is created, such as after an // execute or next_result. Resets the state variables and calls the subclass. -void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) +void ss_sqlsrv_stmt::new_result_set( void ) { if( fetch_field_names != NULL ) { @@ -177,7 +176,7 @@ void ss_sqlsrv_stmt::new_result_set( TSRMLS_D ) fetch_field_names = NULL; fetch_fields_count = 0; - sqlsrv_stmt::new_result_set( TSRMLS_C ); + sqlsrv_stmt::new_result_set(); } // Returns a php type for a given sql type. Also sets the encoding wherever applicable. @@ -269,7 +268,7 @@ void ss_sqlsrv_stmt::set_query_timeout() } // set the statement attribute - core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)query_timeout ), SQL_IS_UINTEGER TSRMLS_CC ); + core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)query_timeout ), SQL_IS_UINTEGER ); // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which // is represented by -1. @@ -282,7 +281,7 @@ void ss_sqlsrv_stmt::set_query_timeout() SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), "stmt_option_query_timeout: snprintf failed. Shouldn't ever fail." ); - core::SQLExecDirect(this, lock_timeout_sql TSRMLS_CC ); + core::SQLExecDirect(this, lock_timeout_sql ); } // statement specific parameter proccessing. Uses the generic function specialised to return a statement @@ -327,14 +326,14 @@ PHP_FUNCTION( sqlsrv_execute ) // to prepare to execute the next statement, we skip any remaining results (and skip parameter finalization too) while( stmt->past_next_result_end == false ) { - core_sqlsrv_next_result( stmt TSRMLS_CC, false, false ); + core_sqlsrv_next_result( stmt, false, false ); } } // bind parameters before executing - bind_params( stmt TSRMLS_CC ); + bind_params( stmt ); - core_sqlsrv_execute( stmt TSRMLS_CC ); + core_sqlsrv_execute( stmt ); RETURN_TRUE; } @@ -383,7 +382,7 @@ PHP_FUNCTION( sqlsrv_fetch ) throw ss::SSException(); } - bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); + bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset ); if( !result ) { RETURN_NULL(); } @@ -442,13 +441,13 @@ PHP_FUNCTION( sqlsrv_fetch_array ) throw ss::SSException(); } - bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); + bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset ); if( !result ) { RETURN_NULL(); } zval fields; ZVAL_UNDEF( &fields ); - fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ TSRMLS_CC ); + fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ ); RETURN_ARR( Z_ARRVAL( fields )); } @@ -501,7 +500,7 @@ PHP_FUNCTION( sqlsrv_field_metadata ) zval result_meta_data; ZVAL_UNDEF( &result_meta_data ); - core::sqlsrv_array_init( *stmt, &result_meta_data TSRMLS_CC ); + core::sqlsrv_array_init( *stmt, &result_meta_data ); for( SQLSMALLINT f = 0; f < num_cols; ++f ) { field_meta_data* core_meta_data = stmt->current_meta_data[f]; @@ -509,13 +508,13 @@ PHP_FUNCTION( sqlsrv_field_metadata ) // initialize the array zval field_array; ZVAL_UNDEF( &field_array ); - core::sqlsrv_array_init( *stmt, &field_array TSRMLS_CC ); + core::sqlsrv_array_init( *stmt, &field_array ); // add the field name to the associative array but keep a copy core::sqlsrv_add_assoc_string(*stmt, &field_array, FieldMetaData::NAME, - reinterpret_cast(core_meta_data->field_name.get()), 1 TSRMLS_CC); + reinterpret_cast(core_meta_data->field_name.get()), 1); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type ); switch( core_meta_data->field_type ) { case SQL_DECIMAL: @@ -524,9 +523,9 @@ PHP_FUNCTION( sqlsrv_field_metadata ) case SQL_TYPE_DATE: case SQL_SS_TIME2: case SQL_SS_TIMESTAMPOFFSET: - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SCALE, core_meta_data->field_scale TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SCALE, core_meta_data->field_scale ); break; case SQL_BIT: case SQL_TINYINT: @@ -536,27 +535,26 @@ PHP_FUNCTION( sqlsrv_field_metadata ) case SQL_REAL: case SQL_FLOAT: case SQL_DOUBLE: - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE TSRMLS_CC ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE ); break; default: - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SIZE, core_meta_data->field_size TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::PREC TSRMLS_CC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE TSRMLS_CC ); + core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SIZE, core_meta_data->field_size ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::PREC ); + core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE ); break; } // add the nullability to the array - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable - TSRMLS_CC ); + core::sqlsrv_add_assoc_long(*stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable); if (stmt->data_classification) { - data_classification::fill_column_sensitivity_array(stmt, f, &field_array TSRMLS_CC); + data_classification::fill_column_sensitivity_array(stmt, f, &field_array); } // add this field's meta data to the result set meta data - core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array TSRMLS_CC ); + core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array ); } // return our built collection and transfer ownership @@ -600,7 +598,7 @@ PHP_FUNCTION( sqlsrv_next_result ) try { - core_sqlsrv_next_result( stmt TSRMLS_CC, true ); + core_sqlsrv_next_result( stmt, true ); // clear the current meta data since the new result will generate new meta data std::for_each(stmt->current_meta_data.begin(), stmt->current_meta_data.end(), meta_data_free); @@ -659,7 +657,7 @@ PHP_FUNCTION( sqlsrv_rows_affected ) throw ss::SSException(); } - rows = stmt->current_results->row_count( TSRMLS_C ); + rows = stmt->current_results->row_count(); RETURN_LONG( rows ); } @@ -708,7 +706,7 @@ PHP_FUNCTION( sqlsrv_num_rows ) throw ss::SSException(); } - rows = stmt->current_results->row_count( TSRMLS_C ); + rows = stmt->current_results->row_count(); RETURN_LONG( rows ); } @@ -747,7 +745,7 @@ PHP_FUNCTION( sqlsrv_num_fields ) try { // retrieve the number of columns from ODBC - fields = core::SQLNumResultCols( stmt TSRMLS_CC ); + fields = core::SQLNumResultCols( stmt ); RETURN_LONG( fields ); } @@ -844,18 +842,18 @@ PHP_FUNCTION( sqlsrv_fetch_object ) } // fetch the data - bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset TSRMLS_CC ); + bool result = core_sqlsrv_fetch( stmt, static_cast(fetch_style), fetch_offset ); if( !result ) { RETURN_NULL(); } - fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ TSRMLS_CC ); + fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ ); properties_ht = Z_ARRVAL( retval_z ); // find the zend_class_entry of the class the user requested (stdClass by default) for use below zend_class_entry* class_entry = NULL; zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 ); - int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z TSRMLS_CC ))) ? SUCCESS : FAILURE; + int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z ))) ? SUCCESS : FAILURE; zend_string_release( class_name_str_z ); CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name ) { throw ss::SSException(); @@ -873,7 +871,7 @@ PHP_FUNCTION( sqlsrv_fetch_object ) // causes duplicate properties when the visibilities are different and also references the // default parameters directly in the object, meaning the default property value is changed when // the object's property is changed. - zend_merge_properties( &retval_z, properties_ht TSRMLS_CC ); + zend_merge_properties( &retval_z, properties_ht ); zend_hash_destroy( properties_ht ); FREE_HASHTABLE( properties_ht ); @@ -940,7 +938,7 @@ PHP_FUNCTION( sqlsrv_fetch_object ) fcic.object = Z_OBJ_P( &retval_z ); - zr = zend_call_function( &fci, &fcic TSRMLS_CC ); + zr = zend_call_function( &fci, &fcic ); CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name ) { throw ss::SSException(); } @@ -1004,7 +1002,7 @@ PHP_FUNCTION( sqlsrv_has_rows ) if( !stmt->has_rows && !stmt->fetch_called ) { - determine_stmt_has_rows( stmt TSRMLS_CC ); + determine_stmt_has_rows( stmt ); } if( stmt->has_rows ) { @@ -1057,7 +1055,7 @@ PHP_FUNCTION( sqlsrv_send_stream_data ) } // send the next packet - bool more = core_sqlsrv_send_stream_packet( stmt TSRMLS_CC ); + bool more = core_sqlsrv_send_stream_packet( stmt ); // if more to send, return true if( more ) { @@ -1130,7 +1128,7 @@ PHP_FUNCTION( sqlsrv_get_field ) } core_sqlsrv_get_field( stmt, static_cast( field_index ), sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/, - &sqlsrv_php_type_out TSRMLS_CC ); + &sqlsrv_php_type_out ); convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z ); sqlsrv_free( field_value ); RETURN_ZVAL( &retval_z, 1, 1 ); @@ -1213,7 +1211,7 @@ PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR) type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARCHAR ); } -void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) +void bind_params( _Inout_ ss_sqlsrv_stmt* stmt ) { // if there's nothing to do, just return if( stmt->params_z == NULL ) { @@ -1222,7 +1220,7 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) try { - stmt->free_param_data( TSRMLS_C ); + stmt->free_param_data(); stmt->executed = false; @@ -1263,7 +1261,7 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) // parse the parameter array that the user gave parse_param_array( stmt, param_z, index, direction, php_out_type, encoding, sql_type, column_size, - decimal_digits TSRMLS_CC ); + decimal_digits ); value_z = var; } else { @@ -1275,14 +1273,14 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) // bind the parameter SQLSRV_ASSERT( value_z != NULL, "bind_params: value_z is null." ); core_sqlsrv_bind_param( stmt, static_cast( index ), direction, value_z, php_out_type, encoding, sql_type, column_size, - decimal_digits TSRMLS_CC ); + decimal_digits ); - } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); } catch( core::CoreException& ) { SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); zval_ptr_dtor( stmt->params_z ); - sqlsrv_free( stmt->params_z ); + sqlsrv_free( stmt->params_z ); stmt->params_z = NULL; throw; } @@ -1312,7 +1310,7 @@ PHP_FUNCTION( sqlsrv_cancel ) try { // close the stream to release the resource - close_active_stream( stmt TSRMLS_CC ); + close_active_stream( stmt ); SQLRETURN r = SQLCancel( stmt->handle() ); CHECK_SQL_ERROR_OR_WARNING( r, stmt ) { @@ -1331,7 +1329,7 @@ PHP_FUNCTION( sqlsrv_cancel ) } } -void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC ) +void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc ) { LOG_FUNCTION( "sqlsrv_stmt_dtor" ); @@ -1377,7 +1375,7 @@ PHP_FUNCTION( sqlsrv_free_stmt ) ss_sqlsrv_stmt* stmt = NULL; sqlsrv_context_auto_ptr error_ctx; - reset_errors( TSRMLS_C ); + reset_errors(); try { @@ -1386,10 +1384,10 @@ PHP_FUNCTION( sqlsrv_free_stmt ) error_ctx->set_func(_FN_); // take only the statement resource - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "r", &stmt_r ) == FAILURE ) { + if( zend_parse_parameters( ZEND_NUM_ARGS(), "r", &stmt_r ) == FAILURE ) { // Check if it was a zval - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &stmt_r ); + int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "z", &stmt_r ); CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { throw ss::SSException(); @@ -1406,14 +1404,14 @@ PHP_FUNCTION( sqlsrv_free_stmt ) } // verify the resource so we know we're deleting a statement - stmt = static_cast(zend_fetch_resource_ex(stmt_r TSRMLS_CC, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor)); + stmt = static_cast(zend_fetch_resource_ex(stmt_r, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor)); - // if sqlsrv_free_stmt was called on an already closed statment then we just return success. - // zend_list_close sets the type of the closed statment to -1. + // if sqlsrv_free_stmt was called on an already closed statment then we just return success. + // zend_list_close sets the type of the closed statment to -1. SQLSRV_ASSERT( stmt_r != NULL, "sqlsrv_free_stmt: stmt_r is null." ); - if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) { - RETURN_TRUE; - } + if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) { + RETURN_TRUE; + } if( stmt == NULL ) { @@ -1445,7 +1443,7 @@ PHP_FUNCTION( sqlsrv_free_stmt ) } } -void stmt_option_ss_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z TSRMLS_DC ) +void stmt_option_ss_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) { throw ss::SSException(); @@ -1485,7 +1483,7 @@ void stmt_option_ss_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt THROW_SS_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ); } - core_sqlsrv_set_scrollable( stmt, cursor_type TSRMLS_CC ); + core_sqlsrv_set_scrollable( stmt, cursor_type ); } @@ -1753,7 +1751,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ // The return value simply states whether or not if an error occurred during the determination. // (All errors are posted here before returning.) -void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) +void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt ) { SQLRETURN r = SQL_SUCCESS; @@ -1766,7 +1764,7 @@ void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) stmt->has_rows = false; // if there are no columns then there are no rows - if( core::SQLNumResultCols( stmt TSRMLS_CC ) == 0 ) { + if( core::SQLNumResultCols( stmt ) == 0 ) { return; } @@ -1775,13 +1773,13 @@ void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) // fetch the first row, and then roll the cursor back to be prior to the first row if( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) { - r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 TSRMLS_CC ); + r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 ); if( SQL_SUCCEEDED( r )) { stmt->has_rows = true; CHECK_SQL_WARNING( r, stmt ); // restore the cursor to its original position. - r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 TSRMLS_CC ); + r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 ); SQLSRV_ASSERT(( r == SQL_NO_DATA ), "core_sqlsrv_has_rows: Should have scrolled the cursor to the beginning " "of the result set." ); } @@ -1792,7 +1790,7 @@ void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt TSRMLS_DC ) // flag and simply skips the first fetch, knowing it was already done. It records its own // flags to know if it should fetch on subsequent calls. - r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ); + r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 ); if( SQL_SUCCEEDED( r )) { stmt->has_rows = true; @@ -1813,7 +1811,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) if (num_cols == 0) { getMetaData = true; if (stmt->column_count == ACTIVE_NUM_COLS_INVALID) { - num_cols = core::SQLNumResultCols(stmt TSRMLS_CC); + num_cols = core::SQLNumResultCols(stmt); stmt->column_count = num_cols; } else { num_cols = stmt->column_count; @@ -1824,7 +1822,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) if (getMetaData) { for (int i = 0; i < num_cols; i++) { sqlsrv_malloc_auto_ptr core_meta_data; - core_meta_data = core_sqlsrv_field_metadata(stmt, i TSRMLS_CC); + core_meta_data = core_sqlsrv_field_metadata(stmt, i); stmt->current_meta_data.push_back(core_meta_data.get()); core_meta_data.transferred(); } @@ -1838,8 +1836,7 @@ SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt) return num_cols; } -void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names - TSRMLS_DC ) +void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names ) { void* field_value = NULL; sqlsrv_phptype sqlsrv_php_type; @@ -1888,7 +1885,7 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ SQLLEN field_len = -1; core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/, - field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out TSRMLS_CC ); + field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out ); zval field; ZVAL_UNDEF( &field ); @@ -1929,8 +1926,7 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction, _Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type, - _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits TSRMLS_DC ) - + _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits ) { zval* var_or_val = NULL; zval* temp = NULL; @@ -2165,7 +2161,7 @@ bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype sql_type ) // verify an encoding given to type_and_encoding by looking through the list // of standard encodings created at module initialization time -bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding TSRMLS_DC ) +bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding ) { void* encoding_temp = NULL; zend_ulong index = -1; @@ -2195,8 +2191,7 @@ void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) size_t size_len = 0; int size = 0; - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &size_p, &size_len ) == FAILURE ) { - + if( zend_parse_parameters( ZEND_NUM_ARGS(), "s", &size_p, &size_len ) == FAILURE ) { return; } if (size_p) { @@ -2246,8 +2241,7 @@ void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) zend_long prec = SQLSRV_INVALID_PRECISION; zend_long scale = SQLSRV_INVALID_SCALE; - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &prec, &scale ) == FAILURE ) { - + if( zend_parse_parameters( ZEND_NUM_ARGS(), "|ll", &prec, &scale ) == FAILURE ) { return; } @@ -2290,12 +2284,11 @@ void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type ) sqlsrv_php_type.typeinfo.type = type; sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID; - if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &encoding_param, &encoding_param_len ) == FAILURE ) { - + if( zend_parse_parameters( ZEND_NUM_ARGS(), "s", &encoding_param, &encoding_param_len ) == FAILURE ) { ZVAL_LONG( return_value, sqlsrv_php_type.value ); } - if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type TSRMLS_CC )) { + if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type )) { LOG( SEV_ERROR, "Invalid encoding for php type." ); } diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index b91f17c2d..b593b4604 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -34,13 +34,13 @@ unsigned int current_log_subsystem = LOG_UTIL; sqlsrv_error_const* get_error_message( _In_ unsigned int sqlsrv_error_code ); void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, - _In_ bool warning TSRMLS_DC ); -bool ignore_warning( _In_ char* sql_state, _In_ int native_code TSRMLS_DC ); + _In_ bool warning ); +bool ignore_warning( _In_ char* sql_state, _In_ int native_code ); bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, _In_ logging_severity log_severity, - _In_ unsigned int sqlsrv_error_code, _In_ bool warning, _In_opt_ va_list* print_args TSRMLS_DC ); + _In_ unsigned int sqlsrv_error_code, _In_ bool warning, _In_opt_ va_list* print_args ); -int sqlsrv_merge_zend_hash_dtor( _Inout_ zval* dest TSRMLS_DC ); -bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ); +int sqlsrv_merge_zend_hash_dtor( _Inout_ zval* dest ); +bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z ); } @@ -451,12 +451,12 @@ ss_error SS_ERRORS[] = { }; // check the global variables of sqlsrv severity whether the message qualifies to be logged with the LOG macro -bool ss_severity_check(_In_ unsigned int severity TSRMLS_DC) +bool ss_severity_check(_In_ unsigned int severity) { return ((severity & SQLSRV_G(log_severity)) && (SQLSRV_G(current_subsystem) & SQLSRV_G(log_subsystems))); } -bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool warning TSRMLS_DC, _In_opt_ va_list* print_args ) +bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_error_code, _In_ bool warning, _In_opt_ va_list* print_args ) { logging_severity severity = SEV_ERROR; if( warning && !SQLSRV_G( warnings_return_as_errors )) { @@ -464,7 +464,7 @@ bool ss_error_handler( _Inout_ sqlsrv_context& ctx, _In_ unsigned int sqlsrv_err } return handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), severity, sqlsrv_error_code, warning, - print_args TSRMLS_CC ); + print_args ); } // sqlsrv_errors( [int $errorsAndOrWarnings] ) @@ -512,7 +512,7 @@ PHP_FUNCTION( sqlsrv_errors ) LOG_FUNCTION( "sqlsrv_errors" ); - if(( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) || + if(( zend_parse_parameters( ZEND_NUM_ARGS(), "|l", &flags ) == FAILURE ) || ( flags != SQLSRV_ERR_ALL && flags != SQLSRV_ERR_ERRORS && flags != SQLSRV_ERR_WARNINGS )) { LOG( SEV_ERROR, "An invalid parameter was passed to %1!s!.", _FN_ ); RETURN_FALSE; @@ -528,13 +528,13 @@ PHP_FUNCTION( sqlsrv_errors ) #endif if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_ERRORS ) { - if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( errors ) TSRMLS_CC )) { + if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( errors ) )) { zval_ptr_dtor(&err_z); RETURN_FALSE; } } if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_WARNINGS ) { - if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( warnings ) TSRMLS_CC )) { + if( Z_TYPE( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( warnings ) )) { zval_ptr_dtor(&err_z); RETURN_FALSE; } @@ -574,7 +574,7 @@ PHP_FUNCTION( sqlsrv_configure ) RETVAL_FALSE; - reset_errors( TSRMLS_C ); + reset_errors(); try { @@ -582,7 +582,7 @@ PHP_FUNCTION( sqlsrv_configure ) error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); error_ctx->set_func(_FN_); - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z ); + int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "sz", &option, &option_len, &value_z ); CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { throw ss::SSException(); @@ -694,7 +694,7 @@ PHP_FUNCTION( sqlsrv_get_config ) LOG_FUNCTION( "sqlsrv_get_config" ); - reset_errors( TSRMLS_C ); + reset_errors(); try { @@ -702,7 +702,7 @@ PHP_FUNCTION( sqlsrv_get_config ) error_ctx = new ( sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL ); error_ctx->set_func(_FN_); - int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len ); + int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "s", &option, &option_len ); CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) { throw ss::SSException(); @@ -761,7 +761,7 @@ sqlsrv_error_const* get_error_message( _In_ unsigned int sqlsrv_error_code ) { } void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, - _In_ bool warning TSRMLS_DC ) + _In_ bool warning ) { #if PHP_VERSION_ID < 70300 if (array_init(error_z) == FAILURE) { @@ -813,7 +813,7 @@ void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, { // if the warning is part of the ignored warning list than // add to the ignored chain if the ignored chain is not null. - if( warning && ignore_warning( reinterpret_cast(error->sqlstate), error->native_code TSRMLS_CC ) && + if( warning && ignore_warning( reinterpret_cast(error->sqlstate), error->native_code ) && ignored_chain != NULL ) { if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { @@ -841,7 +841,7 @@ void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, } bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, _In_ logging_severity log_severity, - _In_ unsigned int sqlsrv_error_code, _In_ bool warning, _In_opt_ va_list* print_args TSRMLS_DC ) + _In_ unsigned int sqlsrv_error_code, _In_ bool warning, _In_opt_ va_list* print_args ) { bool result = true; bool errors_ignored = false; @@ -886,16 +886,16 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, log_severity TSRMLS_CC, print_args ); - copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); + core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, log_severity, print_args ); + copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning ); } SQLSMALLINT record_number = 0; do { - result = core_sqlsrv_get_odbc_error( ctx, ++record_number, error, log_severity TSRMLS_CC ); + result = core_sqlsrv_get_odbc_error( ctx, ++record_number, error, log_severity ); if( result ) { - copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning TSRMLS_CC ); + copy_error_to_zval( &error_z, error, reported_chain, ignored_chain, warning ); } } while( result ); @@ -933,7 +933,7 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo // return whether or not a warning should be ignored or returned as an error if WarningsReturnAsErrors is true // see RINIT in init.cpp for information about which errors are ignored. -bool ignore_warning( _In_ char* sql_state, _In_ int native_code TSRMLS_DC ) +bool ignore_warning( _In_ char* sql_state, _In_ int native_code ) { zend_ulong index = -1; zend_string* key = NULL; @@ -954,7 +954,7 @@ bool ignore_warning( _In_ char* sql_state, _In_ int native_code TSRMLS_DC ) return false; } -int sqlsrv_merge_zend_hash_dtor( _Inout_ zval* dest TSRMLS_DC ) +int sqlsrv_merge_zend_hash_dtor( _Inout_ zval* dest ) { zval_ptr_dtor( dest ); return ZEND_HASH_APPLY_REMOVE; @@ -962,7 +962,7 @@ int sqlsrv_merge_zend_hash_dtor( _Inout_ zval* dest TSRMLS_DC ) // sqlsrv_merge_zend_hash // merge a source hash into a dest hash table and return any errors. -bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ) +bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z ) { if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" ); if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" ); @@ -978,14 +978,14 @@ bool sqlsrv_merge_zend_hash( _Inout_ zval* dest_z, zval const* src_z TSRMLS_DC ) ZEND_HASH_FOREACH_KEY_VAL( src_ht, index, key, value_z ) { if ( !value_z ) { - zend_hash_apply( Z_ARRVAL_P(dest_z), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); + zend_hash_apply( Z_ARRVAL_P(dest_z), sqlsrv_merge_zend_hash_dtor ); return false; } int result = add_next_index_zval( dest_z, value_z ); if( result == FAILURE ) { - zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); + zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor ); return false; } Z_TRY_ADDREF_P( value_z ); From 7e58d1aa92e6d97bf806763de960ae9ef8fd78aa Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 4 May 2020 11:52:58 -0700 Subject: [PATCH 210/249] Removed source indexing code from build scripts (#1132) --- buildscripts/README.md | 12 ++++---- buildscripts/builddrivers.py | 56 ++++-------------------------------- buildscripts/buildtools.py | 2 +- 3 files changed, 12 insertions(+), 58 deletions(-) diff --git a/buildscripts/README.md b/buildscripts/README.md index 46c190e64..8ff3473da 100644 --- a/buildscripts/README.md +++ b/buildscripts/README.md @@ -5,7 +5,7 @@ To build extensions for 1. PHP 7.0* or PHP 7.1* * install Visual Studio 2015 and make sure C++ tools are enabled. -2. PHP 7.2* +2. PHP 7.2* or above * install Visual Studio 2017, including Visual C++ toolset and the Windows SDK components. To use the sample build scripts `builddrivers.py` and `buildtools.py`, install Python 3.x and Git for Windows (which comes with Visual Studio 2017). If `git` is unrecognized in a regular command prompt, make sure the environment path is set up correctly. @@ -14,7 +14,7 @@ To use the sample build scripts `builddrivers.py` and `buildtools.py`, install P You must first be able to build PHP 7.* without including our PHP extensions. For help with building PHP 7.0* or PHP 7.1* in Windows, see the [official PHP website](https://wiki.php.net/internals/windows/stepbystepbuild). For PHP 7.2 or above, visit [PHP SDK page](https://github.com/OSTC/php-sdk-binary-tools) for new instructions. -The Microsoft Drivers for PHP for SQL Server have been compiled and tested with PHP 7.0.* and 7.1.* using Visual C++ 2015 as well as PHP 7.2.1 using Visual C++ 2017 v15.5. +The Microsoft Drivers for PHP for SQL Server have been compiled and tested with PHP 7.0.* and 7.1.* using Visual C++ 2015 as well as PHP 7.2+ using Visual C++ 2017 v15.*. ### Manually building from source @@ -45,7 +45,7 @@ The sample build scripts, `builddrivers.py` and `buildtools.py`, can be used to #### Overview -When asked to provide the PHP version, you should enter values like `7.1.7`. If it's alpha, beta, or RC version, make sure the name you provide matches the PHP tag name without the prefix `php-`. For example, for PHP 7.2 beta 2, the tag name is `php-7.2.0beta2`, so you will enter `7.2.0beta2`. Visit [PHP SRC]( https://github.com/php/php-src) to find the appropriate tag names. +When asked to provide the PHP version, you should enter values like `7.3.17`. If it's alpha, beta, or RC version, make sure the name you provide matches the PHP tag name without the prefix `php-`. For example, for PHP 7.4 beta 2, the tag name is `php-7.4.0beta2`, so you will enter `7.4.0beta2`. Visit [PHP SRC]( https://github.com/php/php-src) to find the appropriate tag names. PHP recommends to unzip the PHP SDK into the shortest possible path, preferrably somewhere near the root drive. Therefore, this script will, by default, create a `php-sdk` folder in the C:\ drive, and this `php-sdk` directory tree will remain unless you remove it yourself. For ongoing development, we suggest you keep it around. The build scripts will handle updating the PHP SDK if a new version is available. @@ -57,7 +57,7 @@ PHP recommends to unzip the PHP SDK into the shortest possible path, preferrably 3. Interactive mode: * Type `py builddrivers.py` to start the interactive mode. Use lower cases to answer the following questions: - * PHP Version (e.g. `7.1.7` or `7.2.1`) + * PHP Version * 64-bit? * Thread safe? * Driver? @@ -68,8 +68,8 @@ PHP recommends to unzip the PHP SDK into the shortest possible path, preferrably 4. Use Command-line arguments * Type `py builddrivers.py -h` to get a list of options and their descriptions * For example, - * `py builddrivers.py --PHPVER=7.2.1 --ARCH=x64 --THREAD=nts --DRIVER=sqlsrv --SOURCE=C:\local\source` - * `py builddrivers.py --PHPVER=7.1.13 --ARCH=x86 --THREAD=ts --DEBUG` + * `py builddrivers.py --PHPVER=7.4.5 --ARCH=x64 --THREAD=nts --DRIVER=sqlsrv --SOURCE=C:\local\source` + * `py builddrivers.py --PHPVER=7.2.30 --ARCH=x86 --THREAD=ts --DEBUG` 5. Based on the given configuration, if the script detects the presence of the PHP source directory, you can choose whether to rebuild, clean or superclean: * `rebuild` to build again using the same configuration (32 bit, thread safe, etc.) diff --git a/buildscripts/builddrivers.py b/buildscripts/builddrivers.py index ff301cc53..26c91c1cd 100644 --- a/buildscripts/builddrivers.py +++ b/buildscripts/builddrivers.py @@ -26,7 +26,6 @@ import argparse import subprocess from buildtools import BuildUtil -from indexsymbols import * class BuildDriver(object): """Build sqlsrv and/or pdo_sqlsrv drivers with PHP source with the following properties: @@ -40,8 +39,6 @@ class BuildDriver(object): make_clean # a boolean flag - whether make clean is necessary source_path # path to a local source folder testing # whether the user has turned on testing mode - srctool_path # path to source indexing tools (empty string by default) - tag_version # tag version for source indexing (empty string by default) """ def __init__(self, phpver, driver, arch, thread, debug, repo, branch, source, path, testing, no_rename): @@ -53,8 +50,6 @@ def __init__(self, phpver, driver, arch, thread, debug, repo, branch, source, pa self.testing = testing self.rebuild = False self.make_clean = False - self.srctool_path = '' - self.tag_version = '' def show_config(self): print() @@ -118,34 +113,6 @@ def get_local_source(self, source_path): print("The path provided is invalid. Please re-enter.") return source - def index_all_symbols(self, ext_dir, srctool_path, tag_version): - """This takes care of indexing all the symbols - - :param ext_dir: the directory where we can find the built extension(s) - :param srctool_path: the path to the tools for source indexing - :param tag_version: tag version for source indexing - :outcome: all symbols will be source indexed - """ - work_dir = os.path.dirname(os.path.realpath(__file__)) - os.chdir(srctool_path) - - if self.util.driver == 'all': - driver = 'sqlsrv' - pdbfile = os.path.join(ext_dir, self.util.driver_name(driver, '.pdb')) - print('Indexing this symbol: ', pdbfile) - run_indexing_tools(pdbfile, driver, tag_version) - driver = 'pdo_sqlsrv' - pdbfile = os.path.join(ext_dir, self.util.driver_name(driver, '.pdb')) - print('Indexing this symbol: ', pdbfile) - run_indexing_tools(pdbfile, driver, tag_version) - else: - driver = self.util.driver - pdbfile = os.path.join(ext_dir, self.util.driver_name(driver, '.pdb')) - print('Indexing this symbol: ', pdbfile) - run_indexing_tools(pdbfile, driver, tag_version) - - os.chdir(work_dir) - def build_extensions(self, root_dir, logfile): """This takes care of getting the drivers' source files, building the drivers. If dest_path is defined, the binaries will be copied to the designated destinations. @@ -185,12 +152,6 @@ def build_extensions(self, root_dir, logfile): # ext_dir is the directory where we can find the built extension(s) ext_dir = self.util.build_drivers(self.make_clean, dest, logfile) - # Do source indexing only if the tag and tools path are both specified - if self.tag_version is not '' and self.srctool_path is not '': - print('Source indexing begins...') - self.index_all_symbols(ext_dir, self.srctool_path, self.tag_version) - print('Source indexing done') - # Copy the binaries if a destination path is defined if self.dest_path is not None: dest_drivers = os.path.join(self.dest_path, self.util.major_version(), self.util.arch) @@ -212,12 +173,10 @@ def build_extensions(self, root_dir, logfile): return ext_dir - def build(self, srctool_path, tag_version): + def build(self): """This is the main entry point of building drivers for PHP. For development, this will loop till the user decides to quit. - - :param srctool_path: the path to the tools for source indexing - :param tag_version: tag version for source indexing + """ self.show_config() @@ -234,10 +193,6 @@ def build(self, srctool_path, tag_version): logfile = self.util.get_logfile_name() - # Save source indexing details - self.srctool_path = srctool_path - self.tag_version = tag_version - try: ext_dir = self.build_extensions(root_dir, logfile) print('Build Completed') @@ -280,7 +235,7 @@ def validate_input(question, values): ################################### Main Function ################################### if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--PHPVER', help="PHP version, e.g. 7.1.*, 7.2.* etc.") + parser.add_argument('--PHPVER', help="PHP version, e.g. 7.4.* etc.") parser.add_argument('--ARCH', choices=['x64', 'x86']) parser.add_argument('--THREAD', choices=['nts', 'ts']) parser.add_argument('--DRIVER', default='all', choices=['all', 'sqlsrv', 'pdo_sqlsrv'], help="driver to build (default: all)") @@ -291,8 +246,6 @@ def validate_input(question, values): parser.add_argument('--TESTING', action='store_true', help="turns on testing mode (default: False)") parser.add_argument('--DESTPATH', default=None, help="an alternative destination for the drivers (default: None)") parser.add_argument('--NO_RENAME', action='store_true', help="drivers will not be renamed(default: False)") - parser.add_argument('--SRCIDX_PATH', default='', help="the path to the tools for source indexing (default: '')") - parser.add_argument('--TAG_VERSION', default='', help="the tag version for source indexing (default: '')") args = parser.parse_args() @@ -354,4 +307,5 @@ def validate_input(question, values): path, testing, no_rename) - builder.build(args.SRCIDX_PATH, args.TAG_VERSION) + + builder.build() diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index 17713f242..884ceb56e 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -100,7 +100,7 @@ def determine_compiler(self, sdk_dir, vs_ver): def compiler_version(self, sdk_dir): """Return the appropriate compiler version based on PHP version.""" - if self.vc is '': + if self.vc == '': VC = 'vc14' version = self.version_label() if version >= '72': # Compiler version for PHP 7.2 or above From d308aa4d0fe474c6d815bdbb8cdbcd4455003de2 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 14 May 2020 20:47:40 -0700 Subject: [PATCH 211/249] Modified some tests to work cross platforms (locales and floats) (#1135) --- .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 11 ++ .../pdo_1018_real_prepare_natl_char.phpt | 13 +- .../pdo_sqlsrv/pdo_ae_insert_numeric.phpt | 44 +++++- .../pdo_sqlsrv/pdo_ae_output_param_all.phpt | 36 +++-- .../pdo_ae_output_param_decimals.phpt | 12 +- .../pdo_ae_output_param_floats.phpt | 12 +- .../pdo_sqlsrv/pdo_ansi_locale_fr.phpt | 10 +- .../pdo_sqlsrv/pdo_buffered_fetch_types.phpt | 20 ++- .../pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt | 13 +- test/functional/pdo_sqlsrv/pdo_prepare.phpt | 4 +- .../pdo_prepare_emulatePrepare_decimal.phpt | 42 ++++-- .../pdo_prepare_emulatePrepare_float.phpt | 57 ++++++-- .../pdo_prepare_emulatePrepare_money.phpt | 42 ++++-- test/functional/pdo_sqlsrv/pdo_query.phpt | 10 +- .../pdo_sqlsrv/pdostatement_GetDataType.phpt | 4 +- .../pdo_sqlsrv/pdostatement_execute.phpt | 4 +- .../pdo_sqlsrv/pdostatement_fetchAll.phpt | 18 +-- .../pdo_sqlsrv/pdostatement_fetchObject.phpt | 4 +- .../pdo_sqlsrv/pdostatement_fetch_style.phpt | 18 +-- .../pdo_sqlsrv/pdostatement_nextRowset.phpt | 12 +- .../pdo_sqlsrv/pdostatement_setFetchMode.phpt | 14 +- .../pdo_sqlsrv/skipif_unix_ansitests.inc | 3 +- test/functional/sqlsrv/0065.phpt | 11 +- .../sqlsrv/skipif_unix_ansitests.inc | 3 +- .../sqlsrv/sqlsrv_ae_output_param_all.phpt | 45 ++++-- .../sqlsrv/sqlsrv_ansi_locale_fr.phpt | 10 +- .../sqlsrv/sqlsrv_buffered_fetch_types.phpt | 34 ++++- ...sqlsrv_fetch_object_unicode_col_name2.phpt | 130 +++++++++++------- 28 files changed, 429 insertions(+), 207 deletions(-) diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index 1f5d997bc..a6edb8d80 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -484,6 +484,17 @@ function getSeqPlaceholders($num) return $placeholderStr; } +/** + * Compare two floating point numbers and return true if the difference is minimal + * @param float $expected : expected value + * @param float $actual : actual value + */ +function compareFloats($expected, $actual) +{ + $epsilon = 0.00001; + $diff = abs(($actual - $expected) / $expected); + return ($diff < $epsilon); +} /** * Fetch all rows and all columns given a table name, and print them diff --git a/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt index 9ee0dcceb..fac93f7bf 100644 --- a/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1018_real_prepare_natl_char.phpt @@ -18,9 +18,15 @@ $p = '銀河galaxy'; $p1 = '??galaxy'; $tableName = 'test1018'; +// in Alpine Linux, instead of '?', it replaces inexact conversions with asterisks +// reference: read the ICONV section in +// https://wiki.musl-libc.org/functional-differences-from-glibc.html +$p2 = '**galaxy'; + function insertRead($conn, $pdoStrParam, $value, $testCase, $id, $encoding = false) { global $p, $tableName; + global $p1, $p2; $sql = "INSERT INTO $tableName (Col1) VALUES (:value)"; $options = array(PDO::ATTR_EMULATE_PREPARES => false); // it's false by default anyway @@ -45,8 +51,11 @@ function insertRead($conn, $pdoStrParam, $value, $testCase, $id, $encoding = fal $result = $stmt->fetch(PDO::FETCH_NUM); trace("$testCase: expected $value and returned $result[0]\n"); if ($result[0] !== $value) { - echo("$testCase: expected $value but returned:\n"); - var_dump($result); + // Also check the other exception + if ($value === $p1 && $result[0] !== $p2) { + echo("$testCase: expected $value or $p2 but returned:\n"); + var_dump($result); + } } } diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt index 60273f342..03e90214b 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_numeric.phpt @@ -10,6 +10,37 @@ require_once("MsCommon_mid-refactor.inc"); require_once("AEData.inc"); $dataTypes = array("bit", "tinyint", "smallint", "int", "bigint", "decimal(18,5)", "numeric(10,5)", "float", "real"); +function fetchFields($conn, $tbname, $inputValues = null) +{ + try { + $sql = "SELECT * FROM $tbname"; + $stmt = $conn->query($sql); + + if (is_null($inputValues)) { + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + foreach ($row as $key => $value) { + print("$key: $value\n"); + } + } + } else { + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + for ($i = 0; $i < 2; $i++) { + if (!compareFloats($inputValues[$i], $row[$i])) { + echo "Expected similar to $inputValues[$i] but got $row[$i]\n"; + } else { + echo "Values matched\n"; + } + } + } + } + } catch (PDOException $e) { + var_dump($e->errorInfo); + } catch (Exception $e) { + var_dump($e->errorInfo); + exit; + } +} + // Note the size of a float is platform dependent, with a precision of roughly 14 digits // http://php.net/manual/en/language.types.float.php try { @@ -28,9 +59,12 @@ try { $stmt = insertRow($conn, $tbname, array( "c_det" => $inputValues[0], "c_rand" => $inputValues[1] ), null, $r); if ($r === false) { isIncompatibleTypesError($stmt, $dataType, "default type"); + } elseif ($dataType == 'float' || $dataType == 'real') { + echo "-----Encrypted default type is compatible with encrypted $dataType-----\n"; + fetchFields($conn, $tbname, $inputValues); } else { echo "-----Encrypted default type is compatible with encrypted $dataType-----\n"; - fetchAll($conn, $tbname); + fetchFields($conn, $tbname); } dropTable($conn, $tbname); } @@ -79,10 +113,10 @@ c_rand: 21474\.83647 Testing float: -----Encrypted default type is compatible with encrypted float----- -c_det: (-9223372036\.8547993|-9223372036\.8547992) -c_rand: (9223372036\.8547993|9223372036\.8547992) +Values matched +Values matched Testing real: -----Encrypted default type is compatible with encrypted real----- -c_det: (-2147\.4829|-2147\.483) -c_rand: (2147\.4829|2147\.483) +Values matched +Values matched \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt index ea20d7ec8..5441ff26d 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_all.phpt @@ -51,16 +51,19 @@ $spSql = "CREATE PROCEDURE $spname ( @c17_nchar = c17_nchar, @c18_nvarchar = c18_nvarchar FROM $tbname"; $conn->query($spSql); -// Insert data + +// Insert data, for bigint, decimal and numeric, should insert as strings +$floatInput = 9223372036.8548; +$realInput = 2147.483; $inputs = array( "c1_int" => 2147483647, "c2_smallint" => 32767, "c3_tinyint" => 255, "c4_bit" => 1, - "c5_bigint" => 922337203685479936, - "c6_decimal" => 9223372036854.80000, - "c7_numeric" => 21474.83647, - "c8_float" => 9223372036.8548, - "c9_real" => 2147.483, + "c5_bigint" => '922337203685479936', + "c6_decimal" => '9223372036854.80000', + "c7_numeric" => '21474.83647', + "c8_float" => $floatInput, + "c9_real" => $realInput, "c10_date" => '9999-12-31', "c11_datetime" => '9999-12-31 23:59:59.997', "c12_datetime2" => '9999-12-31 23:59:59.9999999', @@ -72,15 +75,18 @@ $inputs = array( "c1_int" => 2147483647, "c18_nvarchar" => 'When prefixing a string constant with the letter N, the implicit conversion will result in a Unicode string if the constant to convert does not exceed the max length for a Unicode string data type (4,000).' ); $r; $stmt = insertRow($conn, $tbname, $inputs, null, $r); + // Call store procedure $outSql = getCallProcSqlPlaceholders($spname, count($inputs)); + +// Initialize all inputs, set bigint, decimal and numeric as empty strings $intOut = 0; $smallintOut = 0; $tinyintOut = 0; $bitOut = 0; -$bigintOut = 0.0; -$decimalOut = 0.0; -$numericOut = 0.0; +$bigintOut = ''; +$decimalOut = ''; +$numericOut = ''; $floatOut = 0.0; $realOut = 0.0; $dateOut = '0001-01-01'; @@ -119,8 +125,14 @@ print("bitOut: " . $bitOut . "\n"); print("bigintOut: " . $bigintOut . "\n"); print("decimalOut: " . $decimalOut . "\n"); print("numericOut: " . $numericOut . "\n"); -print("floatOut: " . $floatOut . "\n"); -print("realOut: " . $realOut . "\n"); +if (!compareFloats($floatInput, $floatOut)) { + // Should not expect float values to match exactly + print("Expected $floatInput but got $floatOut\n"); +} +if (!compareFloats($realInput, $realOut)) { + // Should not expect real values to match exactly + print("Expected $realInput but got $realOut\n"); +} print("dateOut: " . $dateOut . "\n"); print("datetimeOut: " . $datetimeOut . "\n"); print("datetime2Out: " . $datetime2Out . "\n"); @@ -143,8 +155,6 @@ bitOut: 1 bigintOut: 922337203685479936 decimalOut: 9223372036854\.80000 numericOut: 21474\.83647 -floatOut: (9223372036\.8547993|9\.22337e\+009) -realOut: (2147\.4829|2147\.48) dateOut: 9999-12-31 datetimeOut: (9999-12-31 23:59:59\.997|Dec 31 9999 11:59PM) datetime2Out: 9999-12-31 23:59:59\.9999999 diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_decimals.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_decimals.phpt index e1fd26cbd..884f3ee54 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_output_param_decimals.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_decimals.phpt @@ -37,14 +37,6 @@ function printValues($msg, $det, $rand, $inputValues) echo "fetched: "; var_dump($rand); } -// this function returns true if the floats are more different than expected -function compareFloats($actual, $expected) -{ - $epsilon = 0.00001; - $diff = abs(($actual - $expected) / $expected); - return ($diff > $epsilon); -} - // function compareIntegers() returns false when the fetched values // are different from the expected inputs function compareIntegers($det, $rand, $inputValues, $pdoParamType) @@ -52,8 +44,8 @@ function compareIntegers($det, $rand, $inputValues, $pdoParamType) /////////////////////////////////////////////////////////////////////// // Assume $pdoParamType is PDO::PARAM_BOOL or PDO::PARAM_INT if (is_string($det)) { - return (!compareFloats(floatval($det), $inputValues[0]) - && !compareFloats(floatval($rand), $inputValues[1])); + return (compareFloats($inputValues[0], floatval($det)) + && compareFloats($inputValues[1], floatval($rand))); } else { // if $pdoParamType is PDO::PARAM_BOOL, expect bool(true) or bool(false) // depending on the rounded input values diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_floats.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_floats.phpt index 61fca7c49..3fccd1032 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_output_param_floats.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_floats.phpt @@ -22,14 +22,6 @@ $pdoParamTypes = array( ////////////////////////////////////////////////////////////////////////////////// -// this function returns true if the floats are more different than expected -function compareFloats($actual, $expected) -{ - $epsilon = 0.0001; - $diff = abs(($actual - $expected) / $expected); - return ($diff > $epsilon); -} - function printValues($msg, $det, $rand, $inputValues) { echo $msg; @@ -118,8 +110,8 @@ function testOutputFloats($fetchNumeric, $inout) } else { // Compare the retrieved values against the input values // if either of them is very different, print them all - if (compareFloats(floatval($det), $inputValues[0]) || - compareFloats(floatval($rand), $inputValues[1])) { + if (!compareFloats($inputValues[0], floatval($det)) || + !compareFloats($inputValues[1], floatval($rand))) { printValues($errMsg, $det, $rand, $inputValues); } } diff --git a/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt b/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt index d54790538..b26d4be10 100644 --- a/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt @@ -33,8 +33,14 @@ function dropTable($conn, $tableName) require_once('MsSetup.inc'); try { - $locale = 'fr_FR@euro'; - setlocale(LC_ALL, $locale); + $r = setlocale(LC_ALL, 'fr_FR@euro'); + if (empty($r)) { + // Some platforms use a different locale name + $r = setlocale(LC_ALL, 'fr_FR.ISO8859-15'); + if (empty($r)) { + die("The required French locale is not available"); + } + } $conn = new PDO("sqlsrv:server = $server; database=$databaseName; driver=$driver", $uid, $pwd); $conn->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_SYSTEM); diff --git a/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt index a10bf6c38..f2d45d473 100644 --- a/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt +++ b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt @@ -25,7 +25,13 @@ function fetchAsUTF8($conn, $tableName, $inputs) $stmt->execute(); $f = $stmt->fetchColumn($i); - if ($f !== $inputs[$i]) { + if ($i == 2) { + if (!compareFloats(floatval($inputs[$i]), floatval($f))) { + echo "In fetchAsUTF8 ($i): expected $inputs[$i]\n"; + var_dump($f); + } + } elseif ($f !== $inputs[$i]) { + echo "In fetchAsUTF8 ($i): expected $inputs[$i]\n"; var_dump($f); } } @@ -45,8 +51,16 @@ function fetchArray($conn, $tableName, $inputs) // By default, even numeric or datetime fields are fetched as strings $result = $stmt->fetch(PDO::FETCH_NUM); for ($i = 0; $i < count($inputs); $i++) { - if ($result[$i] !== $inputs[$i]) { - var_dump($f); + if ($i == 2) { + $expected = floatval($inputs[$i]); + if (!compareFloats($expected, floatval($result[$i]))) { + echo "in fetchArray: for column $i expected $expected but got: "; + var_dump($result[$i]); + } + } + elseif ($result[$i] !== $inputs[$i]) { + echo "in fetchArray: for column $i expected $inputs[$i] but got: "; + var_dump($result[$i]); } } } catch (PdoException $e) { diff --git a/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt index acbeaf233..37b2ddee3 100644 --- a/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt +++ b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt @@ -27,10 +27,19 @@ function verifyColumnData($columns, $results, $utf8) var_dump($results[$i]); } else { $arr = explode('?', $results[$i]); + // in Alpine Linux, data returned is diffferent with always encrypted: + // something like '**** *ä**** *****-**×*' + // instead of '?', it replaces inexact conversions with asterisks + // reference: read the ICONV section in + // https://wiki.musl-libc.org/functional-differences-from-glibc.html if (count($arr) == 1) { // this means there is no question mark in $t - echo $columns[$i]->colName . " value is unexpected"; - var_dump($results[$i]); + // then try to find a substring of some asterisks + $asterisks = '****'; + if(strpos($results[$i], '****') === false) { + echo $columns[$i]->colName . " value is unexpected"; + var_dump($results[$i]); + } } } } diff --git a/test/functional/pdo_sqlsrv/pdo_prepare.phpt b/test/functional/pdo_sqlsrv/pdo_prepare.phpt index 856ac9987..0d4896647 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare.phpt @@ -85,7 +85,7 @@ try { exit(); } ?> ---EXPECT-- +--EXPECTF-- Test_1 : Test with no parameters : array(8) { ["IntCol"]=> @@ -101,7 +101,7 @@ array(8) { ["NVarCharCol"]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_decimal.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_decimal.phpt index d433b911c..5c2787fe2 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_decimal.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_decimal.phpt @@ -6,6 +6,22 @@ prepare with emulate prepare and binding integer "decimal", "c2_money" => "decimal(19,4)", "c3_float" => "float")); } - insertRow($conn, $tableName, array("c1_decimal" => 411.1, "c2_money" => 131.11, "c3_float" => 611.111)); - insertRow($conn, $tableName, array("c1_decimal" => 422.2222, "c2_money" => 132.222, "c3_float" => 622.22)); - insertRow($conn, $tableName, array("c1_decimal" => 433.333, "c2_money" => 133.3333, "c3_float" => 633.33333)); + $inputValues = array( array('c1_decimal' => '411.1', 'c2_money' => '131.11', 'c3_float' => 611.111), + array('c1_decimal' => '422.2222', 'c2_money' => '132.222', 'c3_float' => 622.22), + array('c1_decimal' => '433.333', 'c2_money' => '133.3333', 'c3_float' => 633.33333)); + + for ($i = 0; $i < count($inputValues); $i++) { + insertRow($conn, $tableName, $inputValues[$i]); + } $query = "SELECT * FROM [$tableName] WHERE c1_decimal = :c1"; @@ -27,11 +47,11 @@ try { print_r("Prepare without emulate prepare:\n"); $options = array(PDO::ATTR_EMULATE_PREPARES => false); $stmt = $conn->prepare($query, $options); - $c1 = 422.2222; + $c1 = '422.2222'; $stmt->bindParam(':c1', $c1); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[1]); //with emulate prepare and no bind param options print_r("Prepare with emulate prepare and no bind param options:\n"); @@ -43,7 +63,7 @@ try { $stmt->bindParam(':c1', $c1); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[1]); //with emulate prepare and encoding SQLSRV_ENCODING_SYSTEM print_r("Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM:\n"); @@ -51,7 +71,7 @@ try { $stmt->bindParam(':c1', $c1, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_SYSTEM); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[1]); //prepare with emulate prepare and encoding SQLSRV_ENCODING_UTF8 print_r("Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:\n"); @@ -59,7 +79,7 @@ try { $stmt->bindParam(':c1', $c1, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[1]); //prepare with emulate prepare and encoding SQLSRV_ENCODING_BINARY print_r("Prepare with emulate prepare and SQLSRV_ENCODING_BINARY:\n"); @@ -67,7 +87,7 @@ try { $stmt->bindParam(':c1', $c1, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_BINARY); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[1]); if ($stmt->rowCount() == 0) { print_r("No results for this query\n"); } @@ -86,28 +106,24 @@ Array ( [c1_decimal] => 422 [c2_money] => 132.2220 - [c3_float] => 622.22%S ) Prepare with emulate prepare and no bind param options: Array ( [c1_decimal] => 422 [c2_money] => 132.2220 - [c3_float] => 622.22%S ) Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM: Array ( [c1_decimal] => 422 [c2_money] => 132.2220 - [c3_float] => 622.22%S ) Prepare with emulate prepare and SQLSRV_ENCODING_UTF8: Array ( [c1_decimal] => 422 [c2_money] => 132.2220 - [c3_float] => 622.22%S ) Prepare with emulate prepare and SQLSRV_ENCODING_BINARY: No results for this query diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_float.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_float.phpt index dee4ff599..2633e5ebe 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_float.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_float.phpt @@ -1,11 +1,35 @@ --TEST-- prepare with emulate prepare and binding integer +--DESCRIPTION-- +This test is similar to pdo_prepare_emulatePrepare_decimal.phpt and +pdo_prepare_emulatePrepare_money.phpt but binding parameters with +floating point numbers. However, checking equality of floating point +numbers may not guarantee same results across platforms. Incorrect +results often occurred with implicit rounding when converting string +to floats. +See https://news-web.php.net/php.internals/11502 for in-depth explanation. --SKIPIF-- --FILE-- "decimal", "c2_money" => "decimal(19,4)", "c3_float" => "float")); } - insertRow($conn, $tableName, array("c1_decimal" => 411.1, "c2_money" => 131.11, "c3_float" => 611.111)); - insertRow($conn, $tableName, array("c1_decimal" => 422.2222, "c2_money" => 132.22, "c3_float" => 622.22)); - insertRow($conn, $tableName, array("c1_decimal" => 433.333, "c2_money" => 133.3333, "c3_float" => 633.33333)); + $inputValues = array( array('c1_decimal' => '411.1', 'c2_money' => '131.11', 'c3_float' => 611.111), + array('c1_decimal' => '422.2222', 'c2_money' => '132.222', 'c3_float' => 622.22), + array('c1_decimal' => '433.333', 'c2_money' => '133.3333', 'c3_float' => 633.33333)); - $query = "SELECT * FROM [$tableName] WHERE c3_float = :c3"; + for ($i = 0; $i < count($inputValues); $i++) { + insertRow($conn, $tableName, $inputValues[$i]); + } + + // With data encrypted, there will be no conversion + if (isColEncrypted()) { + $query = "SELECT * FROM [$tableName] WHERE c3_float = :c3"; + } else { + $query = "SELECT * FROM [$tableName] WHERE c3_float < :c3"; + } // prepare without emulate prepare print_r("Prepare without emulate prepare:\n"); $options = array(PDO::ATTR_EMULATE_PREPARES => false); $stmt = $conn->prepare($query, $options); - $c3 = 611.111; + $c3 = (isColEncrypted())? 611.111 : 620.00; $stmt->bindParam(':c3', $c3); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[0]); //with emulate prepare and no bind param options print_r("Prepare with emulate prepare and no bind param options:\n"); @@ -43,7 +76,7 @@ try { $stmt->bindParam(':c3', $c3); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[0]); //with emulate prepare and encoding SQLSRV_ENCODING_SYSTEM print_r("Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM:\n"); @@ -51,7 +84,7 @@ try { $stmt->bindParam(':c3', $c3, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_SYSTEM); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[0]); //prepare with emulate prepare and encoding SQLSRV_ENCODING_UTF8 print_r("Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:\n"); @@ -59,7 +92,7 @@ try { $stmt->bindParam(':c3', $c3, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[0]); //prepare with emulate prepare and encoding SQLSRV_ENCODING_BINARY print_r("Prepare with emulate prepare and SQLSRV_ENCODING_BINARY:\n"); @@ -67,7 +100,7 @@ try { $stmt->bindParam(':c3', $c3, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_BINARY); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[0]); if ($stmt->rowCount() == 0) { print_r("No results for this query\n"); } @@ -85,28 +118,24 @@ Array ( [c1_decimal] => 411 [c2_money] => 131.1100 - [c3_float] => 611.1109999999999%d ) Prepare with emulate prepare and no bind param options: Array ( [c1_decimal] => 411 [c2_money] => 131.1100 - [c3_float] => 611.1109999999999%d ) Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM: Array ( [c1_decimal] => 411 [c2_money] => 131.1100 - [c3_float] => 611.1109999999999%d ) Prepare with emulate prepare and SQLSRV_ENCODING_UTF8: Array ( [c1_decimal] => 411 [c2_money] => 131.1100 - [c3_float] => 611.1109999999999%d ) Prepare with emulate prepare and SQLSRV_ENCODING_BINARY: No results for this query diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_money.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_money.phpt index ae581204a..4943248d7 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_money.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_money.phpt @@ -6,6 +6,22 @@ prepare with emulate prepare and binding integer "decimal", "c2_money" => "decimal(19,4)", "c3_float" => "float")); } - insertRow($conn, $tableName, array("c1_decimal" => 411.1, "c2_money" => 131.11, "c3_float" => 611.111)); - insertRow($conn, $tableName, array("c1_decimal" => 422.2222, "c2_money" => 132.22, "c3_float" => 622.22)); - insertRow($conn, $tableName, array("c1_decimal" => 433.333, "c2_money" => 133.3333, "c3_float" => 633.33333)); + $inputValues = array( array('c1_decimal' => '411.1', 'c2_money' => '131.11', 'c3_float' => 611.111), + array('c1_decimal' => '422.2222', 'c2_money' => '132.222', 'c3_float' => 622.22), + array('c1_decimal' => '433.333', 'c2_money' => '133.3333', 'c3_float' => 633.33333)); + + for ($i = 0; $i < count($inputValues); $i++) { + insertRow($conn, $tableName, $inputValues[$i]); + } $query = "SELECT * FROM [$tableName] WHERE c2_money = :c2"; @@ -27,11 +47,11 @@ try { print_r("Prepare without emulate prepare:\n"); $options = array(PDO::ATTR_EMULATE_PREPARES => false); $stmt = $conn->prepare($query, $options); - $c2 = 133.3333; + $c2 = '133.3333'; $stmt->bindParam(':c2', $c2); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[2]); //with emulate prepare and no bind param options print_r("Prepare with emulate prepare and no bind param options:\n"); @@ -43,7 +63,7 @@ try { $stmt->bindParam(':c2', $c2); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[2]); //with emulate prepare and encoding SQLSRV_ENCODING_SYSTEM print_r("Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM:\n"); @@ -51,7 +71,7 @@ try { $stmt->bindParam(':c2', $c2, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_SYSTEM); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[2]); //prepare with emulate prepare and encoding SQLSRV_ENCODING_UTF8 print_r("Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:\n"); @@ -59,7 +79,7 @@ try { $stmt->bindParam(':c2', $c2, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[2]); //prepare with emulate prepare and encoding SQLSRV_ENCODING_BINARY print_r("Prepare with emulate prepare and SQLSRV_ENCODING_BINARY:\n"); @@ -67,7 +87,7 @@ try { $stmt->bindParam(':c2', $c2, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_BINARY); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); + printRow($row, $inputValues[2]); if ($stmt->rowCount() == 0) { print_r("No results for this query\n"); } @@ -86,28 +106,24 @@ Array ( [c1_decimal] => 433 [c2_money] => 133.3333 - [c3_float] => 633.3333300000000%d ) Prepare with emulate prepare and no bind param options: Array ( [c1_decimal] => 433 [c2_money] => 133.3333 - [c3_float] => 633.3333300000000%d ) Prepare with emulate prepare and SQLSRV_ENCODING_SYSTEM: Array ( [c1_decimal] => 433 [c2_money] => 133.3333 - [c3_float] => 633.3333300000000%d ) Prepare with emulate prepare and SQLSRV_ENCODING_UTF8: Array ( [c1_decimal] => 433 [c2_money] => 133.3333 - [c3_float] => 633.3333300000000%d ) Prepare with emulate prepare and SQLSRV_ENCODING_BINARY: No results for this query diff --git a/test/functional/pdo_sqlsrv/pdo_query.phpt b/test/functional/pdo_sqlsrv/pdo_query.phpt index 99babdfcf..4491c25f5 100644 --- a/test/functional/pdo_sqlsrv/pdo_query.phpt +++ b/test/functional/pdo_sqlsrv/pdo_query.phpt @@ -77,7 +77,7 @@ try { ?> ---EXPECT-- +--EXPECTF-- TEST_1 : query with default fetch style : array(16) { ["IntCol"]=> @@ -105,9 +105,9 @@ array(16) { [5]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [6]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." [7]=> @@ -122,7 +122,7 @@ string(10) "STRINGCOL1" string(23) "2000-11-11 11:11:11.110" string(10) "STRINGCOL1" string(10) "STRINGCOL1" -string(7) "111.111" +string(%d) "111.111%S" string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." TEST_4 : query with FETCH_INTO : string(1) "1" @@ -131,7 +131,7 @@ string(10) "STRINGCOL1" string(23) "2000-11-11 11:11:11.110" string(10) "STRINGCOL1" string(10) "STRINGCOL1" -string(7) "111.111" +string(%d) "111.111%S" string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." TEST_5 : query an empty table : bool(false) diff --git a/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt b/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt index bdd87ffe0..683fbbc43 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_GetDataType.phpt @@ -33,7 +33,7 @@ try { } ?> ---EXPECT-- +--EXPECTF-- Test_1 : BigIntCol : array(1) { ["BigIntCol"]=> @@ -82,7 +82,7 @@ array(1) { Test_10 : FloatCol : array(1) { ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" } Test_11 : RealCol : array(1) { diff --git a/test/functional/pdo_sqlsrv/pdostatement_execute.phpt b/test/functional/pdo_sqlsrv/pdostatement_execute.phpt index 764594299..68164d369 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_execute.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_execute.phpt @@ -48,7 +48,7 @@ try { var_dump($e); } ?> ---EXPECT-- +--EXPECTF-- array(8) { ["IntCol"]=> string(1) "1" @@ -63,7 +63,7 @@ array(8) { ["NVarCharCol"]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt index 6a30232c8..9187df89d 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt @@ -145,9 +145,9 @@ array(2) { [5]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [6]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." [7]=> @@ -237,9 +237,9 @@ array(1) { [8]=> string(3) "111" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [9]=> - string(7) "111.111" + string(%d) "111.111%S" ["ImageCol"]=> string(5) "abcde" [10]=> @@ -273,9 +273,9 @@ array(1) { [17]=> string(420) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." ["RealCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [18]=> - string(7) "111.111" + string(%d) "111.111%S" ["SmallDTCol"]=> string(19) "2000-11-11 11:11:00" [19]=> @@ -353,9 +353,9 @@ array(16) { [5]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [6]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." [7]=> @@ -376,7 +376,7 @@ array(8) { ["NVarCharCol"]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchObject.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchObject.phpt index 01475a6a0..edbe4e5ee 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetchObject.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetchObject.phpt @@ -54,7 +54,7 @@ try { var_dump($e); } ?> ---EXPECT-- +--EXPECTF-- 1 abcde 0 @@ -64,7 +64,7 @@ STRINGCOL1 2000-11-11 11:11:11.1110000 2000-11-11 11:11:11.1110000 +00:00 111 -111.111 +111.111%S abcde 1 111.1110 diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt index 5170bd972..e969325ef 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt @@ -140,9 +140,9 @@ array(16) { [5]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [6]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." [7]=> @@ -163,7 +163,7 @@ array(8) { ["NVarCharCol"]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } @@ -184,7 +184,7 @@ object(PDORow)#%x (%x) { ["NVarCharCol"]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } @@ -203,7 +203,7 @@ object(stdClass)#%x (%x) { ["NVarCharCol"]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } @@ -222,7 +222,7 @@ array(8) { [5]=> string(10) "STRINGCOL1" [6]=> - string(7) "111.111" + string(%d) "111.111%S" [7]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } @@ -233,7 +233,7 @@ string(10) "STRINGCOL1" string(23) "2000-11-11 11:11:11.110" string(10) "STRINGCOL1" string(10) "STRINGCOL1" -string(7) "111.111" +string(%d) "111.111%S" string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." Test_7 : FETCH_CLASS : string(1) "1" @@ -242,7 +242,7 @@ string(10) "STRINGCOL1" string(23) "2000-11-11 11:11:11.110" string(10) "STRINGCOL1" string(10) "STRINGCOL1" -string(7) "111.111" +string(%d) "111.111%S" string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." Test_8 : FETCH_INTO : string(1) "1" @@ -251,7 +251,7 @@ string(10) "STRINGCOL1" string(23) "2000-11-11 11:11:11.110" string(10) "STRINGCOL1" string(10) "STRINGCOL1" -string(7) "111.111" +string(%d) "111.111%S" string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." Test_9 : FETCH_INVALID : diff --git a/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt b/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt index fa63c225c..355dd4de2 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_nextRowset.phpt @@ -72,9 +72,9 @@ array(1) { [8]=> string(3) "111" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [9]=> - string(7) "111.111" + string(%d) "111.111%S" ["ImageCol"]=> string(5) "abcde" [10]=> @@ -108,9 +108,9 @@ array(1) { [17]=> string(420) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." ["RealCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [18]=> - string(7) "111.111" + string(%d) "111.111%S" ["SmallDTCol"]=> string(19) "2000-11-11 11:11:00" [19]=> @@ -189,9 +189,9 @@ array(2) { [5]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [6]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." [7]=> diff --git a/test/functional/pdo_sqlsrv/pdostatement_setFetchMode.phpt b/test/functional/pdo_sqlsrv/pdostatement_setFetchMode.phpt index b74732119..742dc9474 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_setFetchMode.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_setFetchMode.phpt @@ -25,7 +25,7 @@ try { } ?> ---EXPECT-- +--EXPECTF-- Set Fetch Mode for PDO::FETCH_ASSOC array(8) { ["IntCol"]=> @@ -41,7 +41,7 @@ array(8) { ["NVarCharCol"]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } @@ -60,7 +60,7 @@ array(8) { [5]=> string(10) "STRINGCOL1" [6]=> - string(7) "111.111" + string(%d) "111.111%S" [7]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } @@ -91,9 +91,9 @@ array(16) { [5]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" [6]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." [7]=> @@ -116,7 +116,7 @@ object(PDORow)#3 (9) { ["NVarCharCol"]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } @@ -135,7 +135,7 @@ object(stdClass)#5 (8) { ["NVarCharCol"]=> string(10) "STRINGCOL1" ["FloatCol"]=> - string(7) "111.111" + string(%d) "111.111%S" ["XmlCol"]=> string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." } diff --git a/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc b/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc index 3e8b436fd..606824c43 100644 --- a/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc +++ b/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc @@ -14,7 +14,8 @@ if ($localeDisabled) { } $loc = setlocale(LC_ALL, 'fr_FR@euro'); -if (empty($loc)) { +$loc1 = setlocale(LC_ALL, 'fr_FR.ISO8859-15'); +if (empty($loc) && empty($loc1)) { die("skip required French locale not available"); } diff --git a/test/functional/sqlsrv/0065.phpt b/test/functional/sqlsrv/0065.phpt index 985b8e2bb..87745b34a 100644 --- a/test/functional/sqlsrv/0065.phpt +++ b/test/functional/sqlsrv/0065.phpt @@ -84,9 +84,18 @@ if (!AE\isColEncrypted() && $t !== "So?e sä???? ?SCII-te×t") { die("varchar(100) \'$t\' doesn't match So?e sä???? ?SCII-te×t"); } else { $arr = explode('?', $t); + // in Alpine Linux, data returned is diffferent with always encrypted: + // something like '**** *ä**** *****-**×*' + // instead of '?', it replaces inexact conversions with asterisks + // reference: read the ICONV section in + // https://wiki.musl-libc.org/functional-differences-from-glibc.html if (count($arr) == 1) { // this means there is no question mark in $t - die("varchar(100) value \'$t\' is unexpected"); + // then try to find a substring of some asterisks + $asterisks = '****'; + if(strpos($t, '****') === false) { + die("varchar(100) value \'$t\' is unexpected"); + } } } diff --git a/test/functional/sqlsrv/skipif_unix_ansitests.inc b/test/functional/sqlsrv/skipif_unix_ansitests.inc index 42b0d7491..8a2838721 100644 --- a/test/functional/sqlsrv/skipif_unix_ansitests.inc +++ b/test/functional/sqlsrv/skipif_unix_ansitests.inc @@ -14,7 +14,8 @@ if ($localeDisabled) { } $loc = setlocale(LC_ALL, 'fr_FR@euro'); -if (empty($loc)) { +$loc1 = setlocale(LC_ALL, 'fr_FR.ISO8859-15'); +if (empty($loc) && empty($loc1)) { die("skip required French locale not available"); } diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param_all.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_all.phpt index d7ff5db1a..0bdfe8483 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_output_param_all.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_all.phpt @@ -6,6 +6,13 @@ Test for binding output parameter of encrypted values for all types 2147483647, "c2_smallint" => 32767, "c3_tinyint" => 255, "c4_bit" => 1, - "c5_bigint" => 922337203685479936, - "c6_decimal" => 9223372036854.80000, - "c7_numeric" => 21474.83647, - "c8_float" => 9223372036.8548, - "c9_real" => 2147.483, + "c5_bigint" => '922337203685479936', + "c6_decimal" => '9223372036854.80000', + "c7_numeric" => '21474.83647', + "c8_float" => $floatInput, + "c9_real" => $realInput, "c10_date" => '9999-12-31', "c11_datetime" => '9999-12-31 23:59:59.997', "c12_datetime2" => '9999-12-31 23:59:59.9999999', @@ -74,13 +84,14 @@ $stmt = AE\insertRow($conn, $tbname, $inputs); // Call store procedure $outSql = AE\getCallProcSqlPlaceholders($spname, count($inputs)); +// Initialize all inputs, set bigint, decimal and numeric as empty strings $intOut = 0; $smallintOut = 0; $tinyintOut = 0; $bitOut = 0; -$bigintOut = 0.0; -$decimalOut = 0.0; -$numericOut = 0.0; +$bigintOut = ''; +$decimalOut = ''; +$numericOut = ''; $floatOut = 0.0; $realOut = 0.0; $dateOut = ''; @@ -119,8 +130,14 @@ print("bitOut: " . $bitOut . "\n"); print("bigintOut: " . $bigintOut . "\n"); print("decimalOut: " . $decimalOut . "\n"); print("numericOut: " . $numericOut . "\n"); -print("floatOut: " . $floatOut . "\n"); -print("realOut: " . $realOut . "\n"); +if (!compareFloats($floatInput, $floatOut)) { + // Should not expect float values to match exactly + print("Expected $floatInput but got $floatOut\n"); +} +if (!compareFloats($realInput, $realOut)) { + // Should not expect real values to match exactly + print("Expected $realInput but got $realOut\n"); +} print("dateOut: " . $dateOut . "\n"); print("datetimeOut: " . $datetimeOut . "\n"); print("datetime2Out: " . $datetime2Out . "\n"); @@ -142,11 +159,9 @@ intOut: 2147483647 smallintOut: 32767 tinyintOut: 255 bitOut: 1 -bigintOut: 9.2233720368548E\+17 -decimalOut: 9223372036854\.8 +bigintOut: 922337203685479936 +decimalOut: (9223372036854\.8|9223372036854\.80000) numericOut: 21474\.83647 -floatOut: 9223372036\.8548 -realOut: 2147\.4829101562 dateOut: 9999-12-31 datetimeOut: (9999-12-31 23:59:59\.997|Dec 31 9999 11:59PM) datetime2Out: 9999-12-31 23:59:59\.9999999 diff --git a/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt b/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt index c8bd131f6..d0839f2d0 100644 --- a/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt +++ b/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt @@ -42,8 +42,14 @@ function dropTable($conn, $tableName) require_once('MsSetup.inc'); $tableName = "srv_ansitest_FR"; -$locale = 'fr_FR@euro'; -setlocale(LC_ALL, $locale); +$r = setlocale(LC_ALL, 'fr_FR@euro'); +if (empty($r)) { + // Some platforms use a different locale name + $r = setlocale(LC_ALL, 'fr_FR.ISO8859-15'); + if (empty($r)) { + die("The required French locale is not available"); + } +} $conn = sqlsrv_connect($server, $connectionOptions); if( $conn === false ) { diff --git a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt index 425d7f58f..3351f201e 100644 --- a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt +++ b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt @@ -10,7 +10,15 @@ Test various conversion functionalites for buffered queries with SQLSRV. $violation = 'Restricted data type attribute violation'; $outOfRange = 'Numeric value out of range'; $truncation = 'Fractional truncation'; -$epsilon = 0.00001; + +function compareFloats($expected, $actual) +{ + $epsilon = 0.00001; + + $diff = abs(($actual - $expected) / $expected); + + return ($diff < $epsilon); +} function fetchAsUTF8($conn, $tableName, $inputs) { @@ -29,10 +37,17 @@ function fetchAsUTF8($conn, $tableName, $inputs) $f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING('utf-8')); if ($i == 0) { if ($inputs[$i] !== hex2bin($f)) { + echo "In fetchAsUTF8 ($i): expected $inputs[$i]\n"; + var_dump(hex2bin($f)); + } + } elseif ($i == 2) { + if (!compareFloats(floatval($inputs[$i]), floatval($f))) { + echo "In fetchAsUTF8 ($i): expected $inputs[$i]\n"; var_dump($f); } } else { if ($f !== $inputs[$i]) { + echo "In fetchAsUTF8 ($i): expected $inputs[$i]\n"; var_dump($f); } } @@ -59,15 +74,26 @@ function fetchArray($conn, $tableName, $inputs) } for ($i = 0; $i < count($inputs); $i++) { + $matched = true; if ($i == 1) { $expected = intval($inputs[$i]); + if ($results[$i] !== $expected) { + $matched = false; + } } elseif ($i == 2) { $expected = floatval($inputs[$i]); + if (!compareFloats($expected, $results[$i])) { + $matched = false; + } } else { $expected = $inputs[$i]; + if ($results[$i] !== $expected) { + $matched = false; + } } - if ($results[$i] !== $expected) { + // if ($results[$i] !== $expected) { + if (!$matched) { echo "in fetchArray: for column $i expected $expected but got: "; var_dump($results[$i]); } @@ -100,9 +126,7 @@ function fetchAsFloats($conn, $tableName, $inputs) } } elseif ($i < 5) { $expected = floatval($inputs[$i]); - $diff = abs(($f - $expected) / $expected); - - if ($diff > $epsilon) { + if (!compareFloats($expected, $f)) { echo "in fetchAsFloats: for column $i expected $expected but got: "; var_dump($f); } diff --git a/test/functional/sqlsrv/sqlsrv_fetch_object_unicode_col_name2.phpt b/test/functional/sqlsrv/sqlsrv_fetch_object_unicode_col_name2.phpt index 47251aa1c..7f5e6dbbd 100644 --- a/test/functional/sqlsrv/sqlsrv_fetch_object_unicode_col_name2.phpt +++ b/test/functional/sqlsrv/sqlsrv_fetch_object_unicode_col_name2.phpt @@ -24,7 +24,7 @@ class Product return $this->UnitPrice." [CAD]"; } - public function report_output() + public function report_output($dummy) { echo "Object ID: ".$this->objID."\n"; echo "Internal Name: ".$this->name."\n"; @@ -49,11 +49,20 @@ class Sample extends Product return $this->UnitPrice ." [EUR]"; } - public function report_output() + public function report_output($unitPrice) { echo "ID: ".$this->objID."\n"; echo "Name: ".$this->личное_имÑ."\n"; - echo "Unit Price: ".$this->getPrice()."\n"; + + // Since UnitPrice column is of type FLOAT, + // should not expect the values to match exactly + $epsilon = 0.00001; + $diff = abs(($this->UnitPrice - $unitPrice) / $unitPrice); + + if ($diff > $epsilon) { + echo "Expected $unitPrice [EUR] but got "; + echo "Unit Price: ".$this->getPrice()."\n"; + } } } @@ -74,6 +83,50 @@ function getInputData2($inputs) 'Code'=> $inputs[1]); } +function insertInputsSimple($conn, $tableName, $data) +{ + $sql = "INSERT INTO $tableName VALUES \n"; + for ($i = 0; $i < count($data); $i++) { + $sql .= '('; + for ($j = 0; $j < count($data[$i]); $j++) { + if (is_null($data[$i][$j])) { + $sql .= 'NULL'; + } else { + $sql .= "'" . $data[$i][$j] . "'"; + } + + if ($j < count($data[$i]) - 1) { + $sql .= ","; + } + } + if ($i < count($data) - 1) { + $sql .= "),\n"; + } else { + $sql .= ") \n"; + } + } + + return sqlsrv_query($conn, $sql); +} + +function insertInputsWithSQLTypes($conn, $tableName, $data, $sqlTypes) +{ + $sql = "INSERT INTO $tableName VALUES + (?, ?, ?, ?, ?, ?, ?), + (?, ?, ?, ?, ?, ?, ?), + (?, ?, ?, ?, ?, ?, ?), + (?, ?, ?, ?, ?, ?, ?)"; + + $params = array(); + for ($i = 0; $i < count($data); $i++) { + for ($j = 0; $j < count($data[$i]); $j++) { + $params2 = array($data[$i][$j], null, null, $sqlTypes[$j]); + array_push($params, $params2); + } + } + return sqlsrv_query($conn, $sql, $params); +} + require_once('MsCommon.inc'); $conn = AE\connect(array('CharacterSet'=>'UTF-8')); @@ -90,48 +143,24 @@ $columns = array(new AE\ColumnMeta('CHAR(4)', 'ID'), new AE\ColumnMeta('VARCHAR(20)', 'Color')); AE\createTable($conn, $tableName1, $columns); -// Insert data +// Input data for $tableName1 +$data = array(array('P001', 'Pencil 2B', '102', '24', '0.24', '2016-02-01', 'Red'), + array('P002', 'Notepad', '102', '12', '3.87', '2016-02-21', Null), + array('P001', 'Mirror 2\"', '652', '3', '15.99', '2016-02-01', NULL), + array('P003', 'USB connector', '1652', '31', '9.99', '2016-02-01', NULL)); + +$sqlTypes = array(SQLSRV_SQLTYPE_CHAR(4), + SQLSRV_SQLTYPE_VARCHAR(128), + SQLSRV_SQLTYPE_SMALLINT, + SQLSRV_SQLTYPE_INT, + SQLSRV_SQLTYPE_FLOAT, + SQLSRV_SQLTYPE_DATETIME, + SQLSRV_SQLTYPE_VARCHAR(20)); + if (AE\isColEncrypted()) { - $sql = "INSERT INTO $tableName1 VALUES - (?, ?, ?, ?, ?, ?, ?), - (?, ?, ?, ?, ?, ?, ?), - (?, ?, ?, ?, ?, ?, ?), - (?, ?, ?, ?, ?, ?, ?)"; - $stmt = sqlsrv_query($conn, $sql, array(array('P001', null, null, SQLSRV_SQLTYPE_CHAR(4)), - array('Pencil 2B', null, null, SQLSRV_SQLTYPE_VARCHAR(128)), - array('102', null, null, SQLSRV_SQLTYPE_SMALLINT), - array('24', null, null, SQLSRV_SQLTYPE_INT), - array('0.24', null, null, SQLSRV_SQLTYPE_FLOAT), - array('2016-02-01', null, null, SQLSRV_SQLTYPE_DATETIME), - array('Red', null, null, SQLSRV_SQLTYPE_VARCHAR(20)), - array('P002', null, null, SQLSRV_SQLTYPE_CHAR(4)), - array('Notepad', null, null, SQLSRV_SQLTYPE_VARCHAR(128)), - array('102', null, null, SQLSRV_SQLTYPE_SMALLINT), - array('12', null, null, SQLSRV_SQLTYPE_INT), - array('3.87', null, null, SQLSRV_SQLTYPE_FLOAT), - array('2016-02-21', null, null, SQLSRV_SQLTYPE_DATETIME), - array(null, null, null, SQLSRV_SQLTYPE_VARCHAR(20)), - array('P001', null, null, SQLSRV_SQLTYPE_CHAR(4)), - array('Mirror 2\"', null, null, SQLSRV_SQLTYPE_VARCHAR(128)), - array('652', null, null, SQLSRV_SQLTYPE_SMALLINT), - array('3', null, null, SQLSRV_SQLTYPE_INT), - array('15.99', null, null, SQLSRV_SQLTYPE_FLOAT), - array('2016-02-01', null, null, SQLSRV_SQLTYPE_DATETIME), - array(null, null, null, SQLSRV_SQLTYPE_VARCHAR(20)), - array('P003', null, null, SQLSRV_SQLTYPE_CHAR(4)), - array('USB connector', null, null, SQLSRV_SQLTYPE_VARCHAR(128)), - array('1652', null, null, SQLSRV_SQLTYPE_SMALLINT), - array('31', null, null, SQLSRV_SQLTYPE_INT), - array('9.99', null, null, SQLSRV_SQLTYPE_FLOAT), - array('2016-02-01', null, null, SQLSRV_SQLTYPE_DATETIME), - array(null, null, null, SQLSRV_SQLTYPE_VARCHAR(20)))); + $stmt = insertInputsWithSQLTypes($conn, $tableName1, $data, $sqlTypes); } else { - $sql = "INSERT INTO $tableName1 VALUES - ('P001', 'Pencil 2B', '102', '24', '0.24', '2016-02-01', 'Red'), - ('P002', 'Notepad', '102', '12', '3.87', '2016-02-21', Null), - ('P001', 'Mirror 2\"', '652', '3', '15.99', '2016-02-01', NULL), - ('P003', 'USB connector', '1652', '31', '9.99', '2016-02-01', NULL)"; - $stmt = sqlsrv_query($conn, $sql); + $stmt = insertInputsSimple($conn, $tableName1, $data); } if (!$stmt) { fatalError("Failed to insert test data into $tableName1\n"); @@ -142,7 +171,7 @@ $columns = array(new AE\ColumnMeta('CHAR(4)', 'SerialNumber'), new AE\ColumnMeta('VARCHAR(2)', 'Code')); AE\createTable($conn, $tableName2, $columns); -// Insert data +// Insert data for for $tableName2 if (AE\isColEncrypted()) { $sql = "INSERT INTO $tableName2 VALUES (?, ?), (?, ?), (?, ?)"; $stmt = sqlsrv_query($conn, $sql, array(array('P001', null, null, SQLSRV_SQLTYPE_CHAR(4)), @@ -159,7 +188,7 @@ if (!$stmt) { fatalError("Failed to insert test data into $tableName2\n"); } -// With AE enabled, we cannot do comparisons with encrypted columns +// With AE enabled (without secure enclave), do not do comparisons with encrypted columns // Also, only forward cursor or client buffer is supported if (AE\isColEncrypted()) { $sql = "SELECT личное_имÑ, SafetyStockLevel, StockedQty, UnitPrice, Color, Code @@ -196,18 +225,19 @@ if (AE\isColEncrypted()) { } } -// Iterate through the result set +// Iterate through the result set - expect only the first and last sets of input $data // $product is an instance of the Product class -$i=0; -$hasNext = true; +$expected = array(getInputData1($data[0]), getInputData1($data[count($data)-1])); +$i = 0; +$hasNext = true; while ($hasNext) { $sample = sqlsrv_fetch_object($stmt, "Sample", array($i+1000), SQLSRV_SCROLL_ABSOLUTE, $i); if (!$sample) { $hasNext = false; } else { - $sample->report_output(); + $sample->report_output($expected[$i]['UnitPrice']); $i++; } } @@ -225,8 +255,6 @@ print "Done"; --EXPECT-- ID: 1000 Name: Pencil 2B -Unit Price: 0.24 [EUR] ID: 1001 Name: USB connector -Unit Price: 9.99 [EUR] Done From 06d7a496ae98fa16939c6639ff0da0db4fc4a8d8 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 22 May 2020 15:09:28 -0700 Subject: [PATCH 212/249] PDO errorinfo includes additional odbc messages if available (#1133) --- appveyor.yml | 3 +- source/pdo_sqlsrv/pdo_util.cpp | 215 ++++++++-------- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 1 + source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 2 + source/shared/core_sqlsrv.h | 36 ++- source/shared/core_util.cpp | 26 +- .../msdn_pdoStatement_errorInfo.phpt | 5 +- test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt | 3 + .../pdo_sqlsrv/PDO_ConnPool_Unix.phpt | 6 +- test/functional/pdo_sqlsrv/isPooled.php | 62 ++++- .../pdo_924_display_more_errors.phpt | 156 ++++++++++++ .../pdo_sqlsrv/pdo_924_log_all_warnings.phpt | 239 ++++++++++++++++++ .../pdo_sqlsrv/pdo_connection_logs.phpt | 6 + test/functional/pdo_sqlsrv/pdo_error.phpt | 36 ++- .../pdo_sqlsrv/pdo_errorMode_logs.phpt | 6 + .../pdo_mars_disabled_error_checks.phpt | 43 ++++ 16 files changed, 694 insertions(+), 151 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_mars_disabled_error_checks.phpt diff --git a/appveyor.yml b/appveyor.yml index cf87f520f..8f81e3d47 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -99,6 +99,7 @@ install: - echo install opencppcoverage - choco install opencppcoverage - set path=C:\Program Files\OpenCppCoverage;%PYTHON%;%PYTHON%\Scripts;%path% + - copy %APPVEYOR_BUILD_FOLDER%\codecov.yml c:\projects build_script: - copy %APPVEYOR_BUILD_FOLDER%\buildscripts\*.py c:\projects @@ -122,7 +123,7 @@ test_script: - ps: >- If ($env:BUILD_PLATFORM -Match "x86") { Write-Host "Running phpt tests via OpenCppCoverage..." - OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8; + OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --excluded_sources ${env:PHP_SRC_DIR}\pdo_sqlsrv\shared\core_stream.cpp --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8; Write-Host "Showing the last 25 lines of the log file..." Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -Tail 25; ls *.xml diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 58f49efa1..8cda4670d 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -43,8 +43,11 @@ const int WARNING_MIN_LENGTH = static_cast( strlen( WARNING_TEMPLATE sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code); // build the object and throw the PDO exception -void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ); +void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error); +void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args); + +void add_remaining_errors_to_array (_In_ sqlsrv_error const* error, _Inout_ zval* array_z); } // pdo driver error messages @@ -462,47 +465,26 @@ pdo_error PDO_ERRORS[] = { { UINT_MAX, {} } }; -// PDO error handler for the environment context. bool pdo_sqlsrv_handle_env_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, _In_opt_ va_list* print_args ) { - SQLSRV_ASSERT(( ctx != NULL ), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null" ); - pdo_dbh_t* dbh = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT(( dbh != NULL ), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null" ); - - sqlsrv_error_auto_ptr error; - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { + SQLSRV_ASSERT((ctx != NULL), "pdo_sqlsrv_handle_env_error: sqlsrv_context was null"); + pdo_dbh_t* dbh = reinterpret_cast(ctx.driver()); + SQLSRV_ASSERT((dbh != NULL), "pdo_sqlsrv_handle_env_error: pdo_dbh_t was null"); - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args ); - } - else { + sqlsrv_error_auto_ptr error; + format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args); - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); + // error_mode is valid because PDO API has already taken care of invalid ones + if (!warning && dbh->error_mode == PDO_ERRMODE_EXCEPTION) { + pdo_sqlsrv_throw_exception(error); } - strcpy_s( dbh->error_code, sizeof( pdo_error_type ), reinterpret_cast( error->sqlstate )); + ctx.set_last_error(error); - switch( dbh->error_mode ) { - - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error ); - } - ctx.set_last_error( error ); - break; - - default: - DIE( "pdo_sqlsrv_handle_env_error: Unexpected error mode. %1!d!", dbh->error_mode ); - break; - } - // we don't transfer the zval_auto_ptr since set_last_error increments the zval ref count // return error ignored = true for warnings. - return ( warning ? true : false ); - + return (warning ? true : false); } // pdo error handler for the dbh context. @@ -513,95 +495,50 @@ bool pdo_sqlsrv_handle_dbh_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned SQLSRV_ASSERT( dbh != NULL, "pdo_sqlsrv_handle_dbh_error: Null dbh passed" ); sqlsrv_error_auto_ptr error; - - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args ); + format_or_get_all_errors(ctx, sqlsrv_error_code, error, dbh->error_code, print_args); + + // error_mode is valid because PDO API has already taken care of invalid ones + if (!warning) { + if (dbh->error_mode == PDO_ERRMODE_EXCEPTION) { + pdo_sqlsrv_throw_exception(error); + } + else if (dbh->error_mode == PDO_ERRMODE_WARNING) { + size_t msg_len = strnlen_s(reinterpret_cast(error->native_message)) + SQL_SQLSTATE_BUFSIZE + + MAX_DIGITS + WARNING_MIN_LENGTH + 1; + sqlsrv_malloc_auto_ptr msg; + msg = static_cast(sqlsrv_malloc(msg_len)); + core_sqlsrv_format_message(msg, static_cast(msg_len), WARNING_TEMPLATE, error->sqlstate, error->native_code, + error->native_message); + php_error(E_WARNING, "%s", msg.get()); + } } - else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } - - SQLSRV_ASSERT(strnlen_s(reinterpret_cast(error->sqlstate)) <= sizeof(dbh->error_code), "Error code overflow"); - strcpy_s(dbh->error_code, sizeof(dbh->error_code), reinterpret_cast(error->sqlstate)); - switch( dbh->error_mode ) { - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_WARNING: - if( !warning ) { - size_t msg_len = strnlen_s( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE - + MAX_DIGITS + WARNING_MIN_LENGTH + 1; - sqlsrv_malloc_auto_ptr msg; - msg = static_cast( sqlsrv_malloc( msg_len ) ); - core_sqlsrv_format_message( msg, static_cast( msg_len ), WARNING_TEMPLATE, error->sqlstate, error->native_code, - error->native_message ); - php_error(E_WARNING, "%s", msg.get()); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_SILENT: - ctx.set_last_error( error ); - break; - default: - DIE( "Unknown error mode. %1!d!", dbh->error_mode ); - break; - } + ctx.set_last_error(error); // return error ignored = true for warnings. - return ( warning ? true : false ); + return (warning ? true : false); } // PDO error handler for the statement context. -bool pdo_sqlsrv_handle_stmt_error( _Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, - _In_opt_ va_list* print_args ) +bool pdo_sqlsrv_handle_stmt_error(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _In_opt_ bool warning, + _In_opt_ va_list* print_args) { - pdo_stmt_t* pdo_stmt = reinterpret_cast( ctx.driver()); - SQLSRV_ASSERT( pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed" ); + pdo_stmt_t* pdo_stmt = reinterpret_cast(ctx.driver()); + SQLSRV_ASSERT(pdo_stmt != NULL && pdo_stmt->dbh != NULL, "pdo_sqlsrv_handle_stmt_error: Null statement or dbh passed"); sqlsrv_error_auto_ptr error; + format_or_get_all_errors(ctx, sqlsrv_error_code, error, pdo_stmt->error_code, print_args); - if( sqlsrv_error_code != SQLSRV_ERROR_ODBC ) { - core_sqlsrv_format_driver_error( ctx, get_error_message( sqlsrv_error_code ), error, SEV_ERROR, print_args ); - } - else { - bool err = core_sqlsrv_get_odbc_error( ctx, 1, error, SEV_ERROR ); - SQLSRV_ASSERT( err == true, "No ODBC error was found" ); - } - - SQLSRV_ASSERT( strnlen_s( reinterpret_cast( error->sqlstate ) ) <= sizeof( pdo_stmt->error_code ), "Error code overflow"); - strcpy_s( pdo_stmt->error_code, sizeof( pdo_stmt->error_code ), reinterpret_cast( error->sqlstate )); - - switch( pdo_stmt->dbh->error_mode ) { - case PDO_ERRMODE_EXCEPTION: - if( !warning ) { - - pdo_sqlsrv_throw_exception( error ); - } - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_WARNING: - ctx.set_last_error( error ); - break; - case PDO_ERRMODE_SILENT: - ctx.set_last_error( error ); - break; - default: - DIE( "Unknown error mode. %1!d!", pdo_stmt->dbh->error_mode ); - break; + // error_mode is valid because PDO API has already taken care of invalid ones + if (!warning && pdo_stmt->dbh->error_mode == PDO_ERRMODE_EXCEPTION) { + pdo_sqlsrv_throw_exception(error); } + ctx.set_last_error(error); // return error ignored = true for warnings. - return ( warning ? true : false ); + return (warning ? true : false); } - // Transfer a sqlsrv_context's error to a PDO zval. The standard format for a zval error is 3 elements: // 0, native code // 1, native message @@ -613,6 +550,8 @@ void pdo_sqlsrv_retrieve_context_error( _In_ sqlsrv_error const* last_error, _Ou // SQLSTATE is already present in the zval. add_next_index_long( pdo_zval, last_error->native_code ); add_next_index_string( pdo_zval, reinterpret_cast( last_error->native_message )); + + add_remaining_errors_to_array (last_error, pdo_zval); } } @@ -639,7 +578,7 @@ sqlsrv_error_const* get_error_message( _In_opt_ unsigned int sqlsrv_error_code) return error_message; } -void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) +void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error) { zval ex_obj; ZVAL_UNDEF( &ex_obj ); @@ -650,10 +589,10 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); sqlsrv_malloc_auto_ptr ex_msg; - size_t ex_msg_len = strnlen_s( reinterpret_cast( error->native_message )) + SQL_SQLSTATE_BUFSIZE + + size_t ex_msg_len = strnlen_s(reinterpret_cast(error->native_message)) + SQL_SQLSTATE_BUFSIZE + 12 + 1; // 12 = "SQLSTATE[]: " - ex_msg = reinterpret_cast( sqlsrv_malloc( ex_msg_len )); - snprintf( ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message ); + ex_msg = reinterpret_cast(sqlsrv_malloc(ex_msg_len)); + snprintf(ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message); zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, ex_msg ); zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, @@ -665,6 +604,9 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) add_next_index_string( &ex_error_info, reinterpret_cast( error->sqlstate )); add_next_index_long( &ex_error_info, error->native_code ); add_next_index_string( &ex_error_info, reinterpret_cast( error->native_message )); + + add_remaining_errors_to_array (error, &ex_error_info); + //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) //and the refcount of the zend_array is incremented by 1 zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, @@ -677,4 +619,59 @@ void pdo_sqlsrv_throw_exception( _In_ sqlsrv_error_const* error ) zend_throw_exception_object( &ex_obj ); } +void add_remaining_errors_to_array (_In_ sqlsrv_error const* error, _Inout_ zval* array_z) +{ + if (error->next != NULL && PDO_SQLSRV_G(report_additional_errors)) { + sqlsrv_error *p = error->next; + while (p != NULL) { + // check if sql state or native message is NULL and handle them accordingly + char *state = ""; + char *msg = ""; + + if (p->sqlstate != NULL) { + state = reinterpret_cast(p->sqlstate); + } + if (p->native_message != NULL) { + msg = reinterpret_cast(p->native_message); + } + + add_next_index_string(array_z, state); + add_next_index_long(array_z, p->native_code); + add_next_index_string(array_z, msg); + + p = p-> next; + } + } +} + +void format_or_get_all_errors(_Inout_ sqlsrv_context& ctx, _In_opt_ unsigned int sqlsrv_error_code, _Inout_ sqlsrv_error_auto_ptr& error, _Inout_ char* error_code, _In_opt_ va_list* print_args) +{ + if (sqlsrv_error_code != SQLSRV_ERROR_ODBC) { + core_sqlsrv_format_driver_error(ctx, get_error_message(sqlsrv_error_code), error, SEV_ERROR, print_args); + strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast(error->sqlstate)); + } + else { + bool result = core_sqlsrv_get_odbc_error(ctx, 1, error, SEV_ERROR, true); + if (result) { + // Check if there exist more errors + int rec_number = 2; + sqlsrv_error_auto_ptr err; + sqlsrv_error *p = error; + + do { + result = core_sqlsrv_get_odbc_error(ctx, rec_number++, err, SEV_ERROR, true); + if (result) { + p->next = err.get(); + err.transferred(); + p = p->next; + } + } while (result); + } + + // core_sqlsrv_get_odbc_error() returns the error_code of size SQL_SQLSTATE_BUFSIZE, + // which is the same size as pdo_error_type + strcpy_s(error_code, sizeof(pdo_error_type), reinterpret_cast(error->sqlstate)); + } +} + } diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index 79a294f95..d7f5ac0cb 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -31,6 +31,7 @@ ZEND_BEGIN_MODULE_GLOBALS(pdo_sqlsrv) unsigned int pdo_log_severity; zend_long client_buffer_max_size; +short report_additional_errors; #ifndef _WIN32 zend_long set_locale_info; diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index abf711ceb..e81cf09de 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -53,6 +53,7 @@ extern HMODULE g_sqlsrv_hmodule; // (these are defined as macros to allow concatenation as we do below) #define INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE "client_buffer_max_kb_size" #define INI_PDO_SQLSRV_LOG "log_severity" +#define INI_PDO_SQLSRV_MORE_ERRORS "report_additional_errors" #define INI_PREFIX "pdo_sqlsrv." #ifndef _WIN32 @@ -64,6 +65,7 @@ PHP_INI_BEGIN() zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) STD_PHP_INI_ENTRY( INI_PREFIX INI_PDO_SQLSRV_CLIENT_BUFFER_MAX_SIZE , INI_BUFFERED_QUERY_LIMIT_DEFAULT, PHP_INI_ALL, OnUpdateLong, client_buffer_max_size, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals ) + STD_PHP_INI_ENTRY(INI_PREFIX INI_PDO_SQLSRV_MORE_ERRORS, "1", PHP_INI_ALL, OnUpdateLong, report_additional_errors, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals) #ifndef _WIN32 STD_PHP_INI_ENTRY(INI_PREFIX INI_PDO_SET_LOCALE_INFO, "2", PHP_INI_ALL, OnUpdateLong, set_locale_info, zend_pdo_sqlsrv_globals, pdo_sqlsrv_globals) diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index adb4a5500..0dae9a0ea 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -776,6 +776,7 @@ struct sqlsrv_error_const { // subclass which is used by the core layer to instantiate ODBC errors struct sqlsrv_error : public sqlsrv_error_const { + struct sqlsrv_error *next; // Only used in pdo_sqlsrv for additional errors (as a linked list) sqlsrv_error( void ) { @@ -783,16 +784,18 @@ struct sqlsrv_error : public sqlsrv_error_const { native_message = NULL; native_code = -1; format = false; + next = NULL; } - sqlsrv_error( _In_ SQLCHAR* sql_state, _In_ SQLCHAR* message, _In_ SQLINTEGER code, _In_ bool printf_format = false ) + sqlsrv_error( _In_ SQLCHAR* sql_state, _In_ SQLCHAR* message, _In_ SQLINTEGER code, _In_ bool printf_format = false) { - sqlstate = reinterpret_cast( sqlsrv_malloc( SQL_SQLSTATE_BUFSIZE )); - native_message = reinterpret_cast( sqlsrv_malloc( SQL_MAX_ERROR_MESSAGE_LENGTH + 1 )); - strcpy_s( reinterpret_cast( sqlstate ), SQL_SQLSTATE_BUFSIZE, reinterpret_cast( sql_state )); - strcpy_s( reinterpret_cast( native_message ), SQL_MAX_ERROR_MESSAGE_LENGTH + 1, reinterpret_cast( message )); + sqlstate = reinterpret_cast(sqlsrv_malloc(SQL_SQLSTATE_BUFSIZE)); + native_message = reinterpret_cast(sqlsrv_malloc(SQL_MAX_ERROR_MESSAGE_LENGTH + 1)); + strcpy_s(reinterpret_cast(sqlstate), SQL_SQLSTATE_BUFSIZE, reinterpret_cast(sql_state)); + strcpy_s(reinterpret_cast(native_message), SQL_MAX_ERROR_MESSAGE_LENGTH + 1, reinterpret_cast(message)); native_code = code; format = printf_format; + next = NULL; } sqlsrv_error( _In_ sqlsrv_error_const const& prototype ) @@ -802,16 +805,26 @@ struct sqlsrv_error : public sqlsrv_error_const { ~sqlsrv_error( void ) { - if( sqlstate != NULL ) { - sqlsrv_free( sqlstate ); + reset(); + } + + void reset() { + if (sqlstate != NULL) { + sqlsrv_free(sqlstate); + sqlstate = NULL; + } + if (native_message != NULL) { + sqlsrv_free(native_message); + native_message = NULL; } - if( native_message != NULL ) { - sqlsrv_free( native_message ); + if (next != NULL) { + next->reset(); // free the next sqlsrv_error, and so on + sqlsrv_free(next); + next = NULL; } } }; - // an auto_ptr for sqlsrv_errors. These call the destructor explicitly rather than call delete class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptr { @@ -852,7 +865,6 @@ class sqlsrv_error_auto_ptr : public sqlsrv_auto_ptrnative_code, wnative_message, SQL_MAX_ERROR_MESSAGE_LENGTH + 1, &wmessage_len ); // don't use the CHECK* macros here since it will trigger reentry into the error handling system - // Workaround for a bug in unixODBC 2.3.4 when connection pooling is enabled (PDO SQLSRV). - // Instead of returning false, we return an empty error message to prevent the driver from throwing an exception. - // To reproduce: - // Create a connection and close it (return it to the pool) - // Create a new connection from the pool. - // Prepare and execute a statement that generates an info message (such as 'USE tempdb;') -#ifdef __APPLE__ - if( r == SQL_NO_DATA && ctx.driver() != NULL /*PDO SQLSRV*/ ) { - r = SQL_SUCCESS; - } -#endif // __APPLE__ + // removed the workaround for Mac users with unixODBC 2.3.4 when connection pooling is enabled (PDO SQLSRV), for two reasons: + // (1) not recommended to use connection pooling with unixODBC < 2.3.7 + // (2) the problem was not reproducible with unixODBC 2.3.7 if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) { return false; } @@ -348,6 +339,15 @@ bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_nu break; } + // Only overrides 'severity' if 'check_warning' is true (false by default) + if (check_warning) { + // The character string value returned for an SQLSTATE consists of a two-character class value + // followed by a three-character subclass value. A class value of "01" indicates a warning. + // https://docs.microsoft.com/sql/odbc/reference/appendixes/appendix-a-odbc-error-codes?view=sql-server-ver15 + if (error->sqlstate[0] == '0' && error->sqlstate[1] == '1') { + severity = SEV_WARNING; + } + } // log the error first LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate ); diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt index 6f8e4bc66..5c958b6ec 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt @@ -20,5 +20,8 @@ Array \( \[0\] => 42S02 \[1\] => 208 - \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid object name 'Person.Addressx'. + \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid object name 'Person.Addressx'\. + \[3\] => 42000 + \[4\] => 8180 + \[5\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. \) \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt index 9252ad32c..62c112f99 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt @@ -23,4 +23,7 @@ Array \[0\] => 42S22 \[1\] => 207 \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid column name 'Cityx'. + \[3\] => 42000 + \[4\] => 8180 + \[5\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. \) \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt b/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt index c68a46b14..9d242ad58 100644 --- a/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt +++ b/test/functional/pdo_sqlsrv/PDO_ConnPool_Unix.phpt @@ -3,7 +3,11 @@ PDO_SQLSRV Connection Pooling Test on Unix --DESCRIPTION-- This test assumes the default odbcinst.ini has not been modified. --SKIPIF-- - + --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $stmt = $conn2->prepare("SET NOCOUNT ON; USE tempdb; SELECT 1/0 AS col1"); + $stmt->execute(); + } catch (PDOException $e) { + checkErrorInfo($stmt, $e); + } +} + +unset($conn2); + +function connectionID($conn) { $tsql = "SELECT [connection_id] FROM [sys].[dm_exec_connections] where session_id = @@SPID"; $stmt = $conn->query($tsql); @@ -23,4 +37,42 @@ function ConnectionID($conn) $stmt = null; return ($connID); } + +function isAzure($conn) +{ + try { + $tsql = "SELECT SERVERPROPERTY ('edition')"; + $stmt = $conn->query($tsql); + + $result = $stmt->fetch(PDO::FETCH_NUM); + $edition = $result[0]; + + if ($edition === "SQL Azure") { + return true; + } else { + return false; + } + } catch (PDOException $e) { + echo $e->getMessage(); + die("Could not fetch server property."); + } +} + +function checkErrorInfo($stmt, $err) +{ + $expected = "*Divide by zero error encountered*"; + $idx = count($err->errorInfo) - 1; + $failed = false; + if ($idx != 5 || !fnmatch($expected, $err->errorInfo[$idx])) { + echo "Error message unexpected!\n"; + $failed = true; + } + if ($err->errorInfo !== $stmt->errorInfo()) { + echo "Error info arrays should match!\n"; + $failed = true; + } + if ($failed) { + var_dump($err); + } +} ?> diff --git a/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt b/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt new file mode 100644 index 000000000..82d30b8b4 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_924_display_more_errors.phpt @@ -0,0 +1,156 @@ +--TEST-- +GitHub issue 924 - Wrong error message after switching database context +--DESCRIPTION-- +Verifies that the user has the option to see the following error message after the first one. +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $stmt = $conn->prepare($tsql); + $stmt->execute(); + + var_dump($stmt->fetchColumn()); + + echo "Exception should have been thrown!\n"; + } catch (PDOException $e) { + // compare errorInfo arrays from both the exception object and the stmt object + if ($on) { + compare2ErrorInfo($e->errorInfo); + compare2ErrorInfo($stmt->errorInfo()); + } + else { + compareErrorInfo($e->errorInfo, $errorInfo); + compareErrorInfo($stmt->errorInfo(), $errorInfo); + } + } + + unset($stmt); + unset($conn); +} + +function checkWarning($conn, $on) +{ + global $tsql, $errorInfo, $errorInfo2; + + ini_set('pdo_sqlsrv.report_additional_errors', $on); + + try { + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + $stmt = $conn->prepare($tsql); + $stmt->execute(); + + compareErrorInfo($stmt->errorInfo(), $errorInfo); + if ($on) { + compareErrorInfo($stmt->errorInfo(), $errorInfo2, 3); + } else { + echo count($stmt->errorInfo()) . PHP_EOL; + } + } catch (PDOException $e) { + echo " Warnings are logged but do not expect exceptions.\n"; + var_dump($e); + } + + unset($stmt); + unset($conn); +} + +try { + // This forces PHP to log errors rather than displaying errors on screen + ini_set('display_errors', '0'); + ini_set('log_errors', '1'); + + $logFilename = 'php_924_errors.log'; + $logFilepath = dirname(__FILE__).'/'.$logFilename; + + if (file_exists($logFilepath)) { + unlink($logFilepath); + } + + ini_set('error_log', $logFilepath); + ini_set('pdo_sqlsrv.log_severity', '2'); // warnings only + + $conn = new PDO("sqlsrv:server=$server;", $uid, $pwd); + checkWarning($conn, 1); + checkException($conn, 1); + checkWarning($conn, 0); + checkException($conn, 0); + + if (file_exists($logFilepath)) { + echo file_get_contents($logFilepath); + unlink($logFilepath); + } else { + echo "Expected to find the log file\n"; + } +} catch (PDOException $e) { + var_dump($e); +} + +echo "\nDone\n"; +?> +--EXPECTF-- +3 +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5701 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to 'master'. +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5703 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed language setting to us_english. +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 5701 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Changed database context to '%s'. +[%s UTC] PHP Warning: PDOStatement::execute(): SQLSTATE[01000]: Warning: 5701 %s[SQL Server]Changed database context to '%s'. in %spdo_924_display_more_errors.php on line %d +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 5701 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Changed database context to '%s'. +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 5701 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Changed database context to '%s'. +[%s UTC] PHP Warning: PDOStatement::execute(): SQLSTATE[01000]: Warning: 5701 %s[SQL Server]Changed database context to '%s'. in %spdo_924_display_more_errors.php on line %d +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 5701 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Changed database context to '%s'. + +Done diff --git a/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt new file mode 100644 index 000000000..d7bfb3313 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_924_log_all_warnings.phpt @@ -0,0 +1,239 @@ +--TEST-- +GitHub issue 924 - verifies the warnings or error messages are logged to a log file +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + return $conn; +} + +function printCursor($cursorArray) +{ + if ($cursorArray[PDO::ATTR_CURSOR] == PDO::CURSOR_FWDONLY) { + $cursor = 'FORWARD ONLY cursor'; + } else { + switch ($cursorArray[PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE]) { + case PDO::SQLSRV_CURSOR_DYNAMIC: + $cursor = 'server side DYNAMIC cursor'; + break; + case PDO::SQLSRV_CURSOR_STATIC: + $cursor = 'server side STATIC cursor'; + break; + case PDO::SQLSRV_CURSOR_KEYSET: + $cursor = 'server side KEYSET cursor'; + break; + case PDO::SQLSRV_CURSOR_BUFFERED: + $cursor = 'client side BUFFERED cursor'; + break; + default: + $cursor = 'error'; + break; + } + } + + echo "#####Testing $cursor#####\n"; + return $cursor; +} + +function checkResults($data, $results, $resultSet, $expectedRows) +{ + $failed = false; + for ($j = 0; $j < $expectedRows; $j++) { + if ($results[$j][0] != $data[$resultSet][$j]) { + $failed = true; + echo "Fetched results unexpected at row $j:\n"; + print_r($results[$j]); + break; + } + } + + return $failed; +} + +try { + ini_set('log_errors', '1'); + + $logFilename = 'php_924_cursors.log'; + $logFilepath = dirname(__FILE__).'/'.$logFilename; + + if (file_exists($logFilepath)) { + unlink($logFilepath); + } + + ini_set('error_log', $logFilepath); + ini_set('pdo_sqlsrv.log_severity', '3'); // warnings and errors only + + // All supported cursor types + $cursors = array(array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_DYNAMIC), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_STATIC), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_KEYSET), + array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED), + ); + + + // Data for testing, all integer types + $data = array(array(86, -217483648, 0, -432987563, 7, 217483647), + array(0, 31, 127, 255, 1, 10), + array(4534, -212, 32767, 0, 7, -32768), + array(-1, 546098342985600, 9223372000000000000, 5115115115115, 7, -7), + array(0, 1, 0, 0, 1, 1), + ); + + $tableName = 'pdo_924_batchquery_test'; + + // Column names + $colName = array('c1_int', 'c2_tinyint', 'c3_smallint', 'c4_bigint', 'c5_bit'); + $columns = array(new ColumnMeta('int', $colName[0]), + new ColumnMeta('tinyint', $colName[1]), + new ColumnMeta('smallint',$colName[2]), + new ColumnMeta('bigint', $colName[3]), + new ColumnMeta('bit', $colName[4])); + + $conn = toConnect(); + createTable($conn, $tableName, $columns); + + $expectedRows = sizeof($data[0]); + + // Expected result sets = number of columns, since the batch fetches each column sequentially + $expectedResultSets = count($colName); + + // Insert each row. Need an associative array to use insertRow() + for ($i = 0; $i < $expectedRows; ++$i) { + $inputs = array(); + for ($j = 0; $j < $expectedResultSets; ++$j) { + $inputs[$colName[$j]] = $data[$j][$i]; + } + + $stmt = insertRow($conn, $tableName, $inputs); + unset($stmt); + } + + $query = "SELECT c1_int FROM $tableName; + SELECT c2_tinyint FROM $tableName; + SELECT c3_smallint FROM $tableName; + SELECT c4_bigint FROM $tableName; + SELECT c5_bit FROM $tableName;"; + + for ($i = 0; $i < sizeof($cursors); ++$i) { + $cursorType = $cursors[$i]; + // $cursor = printCursor($i); + $cursor = printCursor($cursorType); + + $stmt = $conn->prepare($query, $cursorType); + $stmt->execute(); + + $numResultSets = 0; + do { + $res = $stmt->fetchAll(PDO::FETCH_NUM); + $failed = checkResults($data, $res, $numResultSets, $expectedRows); + ++$numResultSets; + } while (!$failed && $stmt->nextRowset()); + + if ($numResultSets != $expectedResultSets) { + echo ("Unexpected number of result sets, expected $expectedResultedSets, got $numResultSets\n"); + break; + } + + if (file_exists($logFilepath)) { + echo file_get_contents($logFilepath); + unlink($logFilepath); + } + + unset($stmt); + echo "#####Finished testing with $cursor#####\n"; + } + + // Now reset logging by disabling it + ini_set('pdo_sqlsrv.log_severity', '0'); + + dropTable($conn, $tableName); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} + +echo "Done.\n"; + +?> +--EXPECTF-- +#####Testing FORWARD ONLY cursor##### +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5701 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to '%s'. +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5703 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed language setting to us_english. +#####Finished testing with FORWARD ONLY cursor##### +#####Testing server side DYNAMIC cursor##### +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 16954 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Executing SQL directly; no cursor. +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +#####Finished testing with server side DYNAMIC cursor##### +#####Testing server side STATIC cursor##### +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 16954 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Executing SQL directly; no cursor. +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +#####Finished testing with server side STATIC cursor##### +#####Testing server side KEYSET cursor##### +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 16954 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Executing SQL directly; no cursor. +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +[%s UTC] pdo_sqlsrv_stmt_next_rowset: SQLSTATE = 01S02 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: error code = 0 +[%s UTC] pdo_sqlsrv_stmt_next_rowset: message = %sCursor type changed +#####Finished testing with server side KEYSET cursor##### +#####Testing client side BUFFERED cursor##### +#####Finished testing with client side BUFFERED cursor##### +Done. \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt b/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt index 46ea1cdfc..762537a60 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_logs.phpt @@ -44,6 +44,9 @@ try { } else { echo "$logFilepath is missing!\n"; } + + // Now reset logging by disabling it + ini_set('pdo_sqlsrv.log_severity', '0'); echo "Done\n"; } catch (Exception $e) { @@ -56,6 +59,9 @@ try { [%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 [%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5701 [%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed database context to '%s'. +[%s UTC] pdo_sqlsrv_db_handle_factory: SQLSTATE = 01000 +[%s UTC] pdo_sqlsrv_db_handle_factory: error code = 5703 +[%s UTC] pdo_sqlsrv_db_handle_factory: message = %s[SQL Server]Changed language setting to %s. [%s UTC] pdo_sqlsrv_dbh_prepare: entering [%s UTC] pdo_sqlsrv_stmt_execute: entering [%s UTC] pdo_sqlsrv_stmt_describe_col: entering diff --git a/test/functional/pdo_sqlsrv/pdo_error.phpt b/test/functional/pdo_sqlsrv/pdo_error.phpt index 65ddce7e4..3b3b4ac68 100644 --- a/test/functional/pdo_sqlsrv/pdo_error.phpt +++ b/test/functional/pdo_sqlsrv/pdo_error.phpt @@ -5,21 +5,36 @@ Test the PDO::errorCode() and PDO::errorInfo() methods. --FILE-- query("SELECT * FROM $tbname WHERE IntColX = 1"); + $tbname = "PDO_test_error"; + + // create a dummy table + createTable($db, $tbname, array(new ColumnMeta("int", "id"))); + + try { + // query with a wrong column name -- catch the exception and show errors + $stmt = $db->query("SELECT * FROM $tbname WHERE IntColX = 1"); + echo "Should have thrown an exception!\n"; + } catch (PDOException $e) { + echo $db->errorCode() . PHP_EOL; + if ($e->getCode() != $db->errorCode()) { + echo "Error codes do not match!\n"; + echo $e->getCode() . PHP_EOL; + } + $info = $db->errorInfo(); + print_r($info); + if ($e->errorInfo != $info) { + echo "Error info arrays do not match!\n"; + print_r($e->errorInfo); + } + } dropTable($db, $tbname); - unset($conn); + unset($db); } catch (PDOException $e) { - print($db->errorCode()); - echo "\n"; - print_r($db->errorInfo()); + var_dump($e); } ?> --EXPECTREGEX-- @@ -29,4 +44,7 @@ Array \[0\] => 42S22 \[1\] => 207 \[2\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Invalid column name 'IntColX'\. + \[3\] => 42000 + \[4\] => 8180 + \[5\] => \[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]\[SQL Server\]Statement\(s\) could not be prepared\. \) \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt b/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt index a0169696f..239a8c509 100644 --- a/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt +++ b/test/functional/pdo_sqlsrv/pdo_errorMode_logs.phpt @@ -96,6 +96,9 @@ Done with 0 [%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42S02 [%s UTC] pdo_sqlsrv_stmt_execute: error code = 208 [%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Invalid object name 'temp_table'. +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 8180 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Statement(s) could not be prepared. Done with 1 [%s UTC] PHP Warning: PDO::query(): SQLSTATE[42S02]: Base table or view not found: 208 %s[SQL Server]Invalid object name 'temp_table'. in %spdo_errorMode_logs.php on line %d @@ -112,5 +115,8 @@ Done with 4 [%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42S02 [%s UTC] pdo_sqlsrv_stmt_execute: error code = 208 [%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Invalid object name 'temp_table'. +[%s UTC] pdo_sqlsrv_stmt_execute: SQLSTATE = 42000 +[%s UTC] pdo_sqlsrv_stmt_execute: error code = 8180 +[%s UTC] pdo_sqlsrv_stmt_execute: message = %s[SQL Server]Statement(s) could not be prepared. Done with -1 diff --git a/test/functional/pdo_sqlsrv/pdo_mars_disabled_error_checks.phpt b/test/functional/pdo_sqlsrv/pdo_mars_disabled_error_checks.phpt new file mode 100644 index 000000000..e7b14c421 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_mars_disabled_error_checks.phpt @@ -0,0 +1,43 @@ +--TEST-- +Error checking for multiple active row sets (MARS) disabled +--DESCRIPTION-- +This is similar to sqlsrv srv_053_mars_disabled_error_checks.phpt to check the errors +when multiple active row sets (MARS) is disabled. +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $sql1 = "SELECT 'ONE'"; + $sql2 = "SELECT 'TWO'"; + + $stmt1 = $conn->query($sql1); + $stmt2 = $conn->query($sql2); + $res = [$stmt1->fetch(), $stmt2->fetch()]; + var_dump($res); + + unset($stmt1); + unset($stmt2); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} + +echo "\nDone\n"; +?> +--EXPECT-- +array(3) { + [0]=> + string(5) "IMSSP" + [1]=> + int(-61) + [2]=> + string(313) "The connection cannot process this operation because there is a statement with pending results. To make the connection available for other queries, either fetch all results or cancel or free the statement. For more information, see the product documentation about the MultipleActiveResultSets connection option." +} + +Done From c31ee0e48f6d5bdeb8804c8c54cdc3d8ccb2e37a Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 10 Jun 2020 12:23:06 -0700 Subject: [PATCH 213/249] Fixed the login timeout test (#1141) --- test/functional/sqlsrv/srv_007_login_timeout.phpt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/functional/sqlsrv/srv_007_login_timeout.phpt b/test/functional/sqlsrv/srv_007_login_timeout.phpt index 8145c370f..8112394b9 100644 --- a/test/functional/sqlsrv/srv_007_login_timeout.phpt +++ b/test/functional/sqlsrv/srv_007_login_timeout.phpt @@ -1,14 +1,18 @@ --TEST-- False connection with LoginTimeout option --DESCRIPTION-- -Intentionally provide an invalid server name and set LoginTimeout. Verify the time elapsed. +Intentionally provide an invalid server and set LoginTimeout. Verify the time elapsed. +The difference in time elapsed is platform dependent. In some Linux distros, extra delay +may be caused by the attempts to resolve non-existent hostnames. Already set leeway to 2 +seconds to allow some room of such errors, but this test remains fragile, especially +outside Windows. Thus, use an invalid IP address instead when running in any non-Windows platform. --SKIPIF-- --FILE-- Date: Fri, 19 Jun 2020 14:45:13 -0700 Subject: [PATCH 214/249] Conversion is unnecessary for numeric parameters (#1136) --- source/shared/core_stmt.cpp | 542 +++++++++--------- .../pdo_sqlsrv/pdo_ae_insert_decimal.phpt | 35 +- .../pdo_ae_insert_scientificNot.phpt | 117 +++- .../pdo_fetch_fetchinto_query_args.phpt | 18 +- .../pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt | 2 +- test/functional/sqlsrv/0065.phpt | 3 +- .../sqlsrv/sqlsrv_ae_insert_decimal.phpt | 93 +-- .../sqlsrv_ae_insert_scientificNot.phpt | 172 ++++-- 8 files changed, 612 insertions(+), 370 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 18f51e91c..54a49f147 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -66,6 +66,9 @@ struct col_cache { const int INITIAL_FIELD_STRING_LEN = 2048; // base allocation size when retrieving a string field +const char DECIMAL_POINT = '.'; +const int SQL_SERVER_DECIMAL_MAXIMUM_PRECISION = 38; // 38 is the maximum length of a stringified decimal number + // UTF-8 tags for byte length of characters, used by streams to make sure we don't clip a character in between reads const unsigned int UTF8_MIDBYTE_MASK = 0xc0; const unsigned int UTF8_MIDBYTE_TAG = 0x80; @@ -99,7 +102,7 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve void core_get_field_common(_Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len); // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding ); +SQLSMALLINT default_c_type(_Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSMALLINT sql_type, _In_ SQLSRV_ENCODING encoding); void default_sql_size_and_scale( _Inout_ sqlsrv_stmt* stmt, _In_opt_ unsigned int paramno, _In_ zval* param_z, _In_ SQLSRV_ENCODING encoding, _Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits ); // given a zval and encoding, determine the appropriate sql type, column size, and decimal scale (if appropriate) @@ -107,6 +110,7 @@ void default_sql_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ _Out_ SQLSMALLINT& sql_type ); void col_cache_dtor( _Inout_ zval* data_z ); void field_cache_dtor( _Inout_ zval* data_z ); +int round_up_decimal_numbers(_Inout_ char* buffer, _In_ short decimal_pos, _In_ short decimals_places, _In_ short offset, _In_ short lastpos); void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len); void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt ); void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, @@ -117,7 +121,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ); void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z, _In_ SQLULEN paramno, SQLSRV_ENCODING encoding, _In_ SQLSMALLINT c_type, _In_ SQLSMALLINT sql_type, _In_ SQLULEN column_size, _In_ SQLSMALLINT decimal_digits, _Out_writes_(buffer_len) SQLPOINTER& buffer, _Out_ SQLLEN& buffer_len ); -void adjustInputPrecision( _Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits ); +void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits); void save_output_param_for_later( _Inout_ sqlsrv_stmt* stmt, _Inout_ sqlsrv_output_param& param ); // send all the stream data void send_param_streams( _Inout_ sqlsrv_stmt* stmt ); @@ -125,7 +129,7 @@ void send_param_streams( _Inout_ sqlsrv_stmt* stmt ); void sqlsrv_output_param_dtor( _Inout_ zval* data ); // called when a bound stream parameter is to be destroyed. void sqlsrv_stream_dtor( _Inout_ zval* data ); - +bool is_a_numeric_type(_In_ SQLSMALLINT sql_type); } // constructor for sqlsrv_stmt. Here so that we can use functions declared earlier. @@ -501,7 +505,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ } } // determine the ODBC C type - c_type = default_c_type( stmt, param_num, param_z, encoding ); + c_type = default_c_type(stmt, param_num, param_z, sql_type, encoding); // set the buffer based on the PHP parameter type switch( Z_TYPE_P( param_z )){ @@ -544,15 +548,21 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ break; case IS_STRING: { - if ( sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC ) { - adjustInputPrecision( param_z, decimal_digits ); + // With AE, the precision of the decimal or numeric inputs have to match exactly as defined in the columns. + // Without AE, the derived default sql types will not be this specific. Thus, if sql_type is SQL_DECIMAL + // or SQL_NUMERIC, the user must have clearly specified it (using the SQLSRV driver) as SQL_DECIMAL or SQL_NUMERIC. + // In either case, the input passed into SQLBindParam requires matching scale (i.e., number of decimal digits). + if (sql_type == SQL_DECIMAL || sql_type == SQL_NUMERIC) { + adjustDecimalPrecision(param_z, decimal_digits); } buffer = Z_STRVAL_P( param_z ); buffer_len = Z_STRLEN_P( param_z ); + bool is_numeric = is_a_numeric_type(sql_type); + // if the encoding is UTF-8, translate from UTF-8 to UTF-16 (the type variables should have already been adjusted) - if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 ){ + if( direction == SQL_PARAM_INPUT && encoding == CP_UTF8 && !is_numeric){ zval wbuffer_z; ZVAL_NULL( &wbuffer_z ); @@ -602,7 +612,9 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ buffer, buffer_len ); // save the parameter to be adjusted and/or converted after the results are processed - sqlsrv_output_param output_param( param_ref, encoding, param_num, static_cast( buffer_len ) ); + // no need to use wide chars for numeric types + SQLSRV_ENCODING enc = (is_numeric) ? SQLSRV_ENCODING_CHAR : encoding; + sqlsrv_output_param output_param(param_ref, enc, param_num, static_cast(buffer_len)); output_param.saveMetaData(sql_type, column_size, decimal_digits); @@ -2041,7 +2053,7 @@ bool convert_input_param_to_utf16( _In_ zval* input_param_z, _Inout_ zval* conve // returns the ODBC C type constant that matches the PHP type and encoding given -SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSRV_ENCODING encoding ) +SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, _In_ zval const* param_z, _In_ SQLSMALLINT sql_type, _In_ SQLSRV_ENCODING encoding ) { SQLSMALLINT sql_c_type = SQL_UNKNOWN_TYPE; int php_type = Z_TYPE_P( param_z ); @@ -2079,6 +2091,22 @@ SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, sql_c_type = SQL_C_DOUBLE; break; case IS_STRING: + switch (encoding) { + case SQLSRV_ENCODING_CHAR: + sql_c_type = SQL_C_CHAR; + break; + case SQLSRV_ENCODING_BINARY: + sql_c_type = SQL_C_BINARY; + break; + case CP_UTF8: + sql_c_type = (is_a_numeric_type(sql_type)) ? SQL_C_CHAR : SQL_C_WCHAR; + //sql_c_type = SQL_C_WCHAR; + break; + default: + THROW_CORE_ERROR(stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno); + break; + } + break; case IS_RESOURCE: switch( encoding ) { case SQLSRV_ENCODING_CHAR: @@ -2269,129 +2297,63 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f // Likewise, if decimals_places is larger than the field scale, decimals_places wil be ignored. This is to ensure the // number of decimals adheres to the column field scale. If smaller, the output value may be rounded up. // - // Note: it's possible that the decimal data does not contain a decimal dot because the field scale is 0. - // Thus, first check if the decimal dot exists. If not, no formatting necessary, regardless of + // Note: it's possible that the decimal data does not contain a decimal point because the field scale is 0. + // Thus, first check if the decimal point exists. If not, no formatting necessary, regardless of // format_decimals and decimals_places // - std::string str = field_value; - size_t pos = str.find_first_of('.'); - // The decimal dot is not found, simply return - if (pos == std::string::npos) { + // Check if it's a negative number and if necessary to add the leading zero + bool is_negative = (*field_value == '-'); + char *src = field_value + is_negative; + bool add_leading_zero = false; + + // If the decimal point is not found, simply return + char *pt = strchr(src, DECIMAL_POINT); + if (pt == NULL) { return; } - - SQLSMALLINT num_decimals = decimals_places; - if (num_decimals > field_scale) { - num_decimals = field_scale; + else if (pt == src) { + add_leading_zero = true; } - // We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php - // as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is - // followed by 5 or above. + SQLSMALLINT scale = decimals_places; + if (scale > field_scale) { + scale = field_scale; + } - bool isNegative = false; + char buffer[50] = " "; // A buffer with two blank spaces, as leeway + short offset = 1 + is_negative; + short src_length = strlen(src); - // If negative, remove the minus sign for now so as not to complicate the rounding process - if (str[0] == '-') { - isNegative = true; - std::ostringstream oss; - oss << str.substr(1); - str = oss.str(); - pos = str.find_first_of('.'); + if (add_leading_zero) { + buffer[offset++] = '0'; } + // Copy the original numerical value to the buffer + memcpy_s(buffer + offset, src_length, src, src_length); - // Adds the leading zero if not exists - if (pos == 0) { - std::ostringstream oss; - oss << '0' << str; - str = oss.str(); - pos++; - } + int last_pos = src_length + offset; - if (num_decimals == NO_CHANGE_DECIMAL_PLACES) { - // Add the minus sign back if negative - if (isNegative) { - std::ostringstream oss; - oss << '-' << str.substr(0); - str = oss.str(); - } - } else { - // Start formatting - size_t last = 0; - if (num_decimals == 0) { - // Chop all decimal digits, including the decimal dot - size_t pos2 = pos + 1; - short n = str[pos2] - '0'; - if (n >= 5) { - // Start rounding up - starting from the digit left of the dot all the way to the first digit - bool carry_over = true; - for (short p = pos - 1; p >= 0 && carry_over; p--) { - n = str[p] - '0'; - if (n == 9) { - str[p] = '0' ; - carry_over = true; - } - else { - n++; - carry_over = false; - str[p] = '0' + n; - } - } - if (carry_over) { - std::ostringstream oss; - oss << '1' << str.substr(0, pos); - str = oss.str(); - pos++; - } - } - last = pos; - } - else { - size_t pos2 = pos + num_decimals + 1; - // No need to check if rounding is necessary when pos2 has passed the last digit in the input string - if (pos2 < str.length()) { - short n = str[pos2] - '0'; - if (n >= 5) { - // Start rounding up - starting from the digit left of pos2 all the way to the first digit - bool carry_over = true; - for (short p = pos2 - 1; p >= 0 && carry_over; p--) { - if (str[p] == '.') { // Skip the dot - continue; - } - n = str[p] - '0'; - if (n == 9) { - str[p] = '0' ; - carry_over = true; - } - else { - n++; - carry_over = false; - str[p] = '0' + n; - } - } - if (carry_over) { - std::ostringstream oss; - oss << '1' << str.substr(0, pos2); - str = oss.str(); - pos2++; - } - } - } - last = pos2; - } - // Add the minus sign back if negative - if (isNegative) { - std::ostringstream oss; - oss << '-' << str.substr(0, last); - str = oss.str(); - } else { - str = str.substr(0, last); + // If no need to adjust decimal places, skip formatting + if (decimals_places != NO_CHANGE_DECIMAL_PLACES) { + short num_decimals = src_length - (pt - src) - 1; + + if (num_decimals > scale) { + last_pos = round_up_decimal_numbers(buffer, (pt - src) + offset, scale, offset, last_pos); } - } + } - size_t len = str.length(); - str.copy(field_value, len); + // Remove the extra white space if not used + char *p = buffer; + offset = 0; + while (isspace(*p++)) { + offset++; + } + if (is_negative) { + buffer[--offset] = '-'; + } + + short len = last_pos - offset; + memcpy_s(field_value, len, buffer + offset, len); field_value[len] = '\0'; *field_len = len; } @@ -2967,154 +2929,6 @@ void resize_output_buffer_if_necessary( _Inout_ sqlsrv_stmt* stmt, _Inout_ zval* } } -void adjustInputPrecision( _Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits ) { - // 38 is the maximum length of a stringified decimal number - size_t maxDecimalPrecision = 38; - // 6 is derived from: 1 for '.'; 1 for sign of the number; 1 for 'e' or 'E' (scientific notation); - // 1 for sign of scientific exponent; 2 for length of scientific exponent - // if the length is greater than maxDecimalStrLen, do not change the string - size_t maxDecimalStrLen = maxDecimalPrecision + 6; - if (Z_STRLEN_P(param_z) > maxDecimalStrLen) { - return; - } - std::vector digits; - unsigned char* ptr = reinterpret_cast(ZSTR_VAL( Z_STR_P( param_z ))); - bool isNeg = false; - bool isScientificNot = false; - char scientificChar = ' '; - short scientificExp = 0; - if( strchr( reinterpret_cast( ptr ), 'e' ) || strchr( reinterpret_cast( ptr ), 'E' )){ - isScientificNot = true; - } - // parse digits in param_z into the vector digits - if( *ptr == '+' || *ptr == '-' ){ - if( *ptr == '-' ){ - isNeg = true; - } - ptr++; - } - short numInt = 0; - short numDec = 0; - while( isdigit( *ptr )){ - digits.push_back( *ptr - '0' ); - ptr++; - numInt++; - } - if( *ptr == '.' ){ - ptr++; - if( !isScientificNot ){ - while( isdigit( *ptr ) && numDec < decimal_digits + 1 ){ - digits.push_back( *ptr - '0' ); - ptr++; - numDec++; - } - // make sure the rest of the number are digits - while( isdigit( *ptr )){ - ptr++; - } - } - else { - while( isdigit( *ptr )){ - digits.push_back( *ptr - '0' ); - ptr++; - numDec++; - } - } - } - if( isScientificNot ){ - if ( *ptr == 'e' || *ptr == 'E' ) { - scientificChar = *ptr; - } - ptr++; - bool isNegExp = false; - if( *ptr == '+' || *ptr == '-' ){ - if( *ptr == '-' ){ - isNegExp = true; - } - ptr++; - } - while( isdigit( *ptr )){ - scientificExp = scientificExp * 10 + ( *ptr - '0' ); - ptr++; - } - SQLSRV_ASSERT( scientificExp <= maxDecimalPrecision, "Input decimal overflow: sql decimal type only supports up to a precision of 38." ); - if( isNegExp ){ - scientificExp = scientificExp * -1; - } - } - // if ptr is not pointing to a null terminator at this point, that means the decimal string input is invalid - // do not change the string and let SQL Server handle the invalid decimal string - if ( *ptr != '\0' ) { - return; - } - // if number of decimal is less than the exponent, that means the number is a whole number, so no need to adjust the precision - if( numDec > scientificExp ){ - int decToRemove = numDec - scientificExp - decimal_digits; - if( decToRemove > 0 ){ - bool carryOver = false; - short backInd = 0; - // pop digits from the vector until there is only 1 more decimal place than required decimal_digits - while( decToRemove != 1 && !digits.empty() ){ - digits.pop_back(); - decToRemove--; - } - if( !digits.empty() ){ - // check if the last digit to be popped is greater than 5, if so, the digit before it needs to round up - carryOver = digits.back() >= 5; - digits.pop_back(); - backInd = static_cast(digits.size() - 1); - // round up from the end until no more carry over - while( carryOver && backInd >= 0 ){ - if( digits.at( backInd ) != 9 ){ - digits.at( backInd )++; - carryOver = false; - } - else{ - digits.at( backInd ) = 0; - } - backInd--; - } - } - std::ostringstream oss; - if( isNeg ){ - oss << '-'; - } - // insert 1 if carry over persist all the way to the beginning of the number - if( carryOver && backInd == -1 ){ - oss << 1; - } - if( digits.empty() && !carryOver ){ - oss << 0; - } - else{ - short i = 0; - for( i; i < numInt && i < digits.size(); i++ ){ - oss << digits[i]; - } - // fill string with 0 if the number of digits in digits is less then numInt - if( i < numInt ){ - for( i; i < numInt; i++ ){ - oss << 0; - } - } - if( numInt < digits.size() ){ - oss << '.'; - for( i; i < digits.size(); i++ ){ - oss << digits[i]; - } - } - if( scientificExp != 0 ){ - oss << scientificChar << std::to_string( scientificExp ); - } - } - std::string str = oss.str(); - zend_string* zstr = zend_string_init( str.c_str(), str.length(), 0 ); - zend_string_release( Z_STR_P( param_z )); - ZVAL_NEW_STR( param_z, zstr ); - } - } -} - // output parameters have their reference count incremented so that they do not disappear // while the query is executed and processed. They are saved in the statement so that // their reference count may be decremented later (after results are processed) @@ -3152,4 +2966,200 @@ void sqlsrv_stream_dtor( _Inout_ zval* data ) sqlsrv_free( stream_encoding ); } +void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits) +{ + char* value = Z_STRVAL_P(param_z); + short value_len = Z_STRLEN_P(param_z); + + // If the length is greater than maxDecimalStrLen, do not convert the string + // 6 is derived from: 1 for the decimal point; 1 for sign of the number; 1 for 'e' or 'E' (scientific notation); + // 1 for sign of scientific exponent; 2 for length of scientific exponent + const int MAX_DECIMAL_STRLEN = SQL_SERVER_DECIMAL_MAXIMUM_PRECISION + 6; + if (value_len > MAX_DECIMAL_STRLEN) { + return; + } + + // If std::stold() succeeds, 'idx' is the position of the first character after the numerical value + long double d = 0; + size_t idx; + try { + d = std::stold(std::string(value), &idx); + } + catch (const std::logic_error& err) { + return; // invalid input caused the conversion to throw an exception + } + if (idx < value_len) { + return; // the input contains something else apart from the numerical value + } + + // Navigate to the first digit or the decimal point + bool is_negative = (d < 0); + char *src = value + is_negative; + while (*src != DECIMAL_POINT && !isdigit(*src)) { + src++; + } + + // Check if the value is in scientific notation + char *exp = strchr(src, 'E'); + if (exp == NULL) { + exp = strchr(src, 'e'); + } + + // Find the decimal point + char *pt = strchr(src, DECIMAL_POINT); + + char buffer[50] = " "; // A buffer with 2 blank spaces, as leeway + short offset = 1 + is_negative; // The position to start copying the original numerical value + + if (exp == NULL) { + if (pt == NULL) { + return; // decimal point not found + } + + short src_length = strlen(src); + short num_decimals = src_length - (pt - src) - 1; + if (num_decimals <= decimal_digits) { + return; // no need to adjust number of decimals + } + + memcpy_s(buffer + offset, src_length, src, src_length); + round_up_decimal_numbers(buffer, (pt - src) + offset, decimal_digits, offset, src_length + offset); + } + else { + int power = atoi(exp+1); + if (abs(power) > SQL_SERVER_DECIMAL_MAXIMUM_PRECISION) { + return; // Out of range, so let the server handle this + } + + short num_decimals = 0; + if (power == 0) { + // Simply chop off the exp part + short length = (exp - src); + memcpy_s(buffer + offset, length, src, length); + + if (pt != NULL) { + // Adjust decimal places only if decimal point is found and number of decimals more than decimal_digits + num_decimals = exp - pt - 1; + if (num_decimals > decimal_digits) { + round_up_decimal_numbers(buffer, (pt - src) + offset, decimal_digits, offset, length + offset); + } + } + } else { + short oldpos = 0; + if (pt == NULL) { + pt = exp; // Decimal point not found, use the exp sign + oldpos = exp - src; + } + else { + oldpos = pt - src; + num_decimals = exp - pt - 1; + if (power > 0 && num_decimals <= power) { + return; // The result will be a whole number, do nothing and return + } + } + + // Derive the new position for the decimal point in the buffer + short newpos = oldpos + power; + if (power > 0) { + newpos = newpos + offset; + if (num_decimals == 0) { + memset(buffer + offset + oldpos, '0', power); // Fill parts of the buffer with zeroes first + } + else { + buffer[newpos] = DECIMAL_POINT; + } + } + else { + // The negative "power" part shows exactly how many places to move the decimal point. + // Whether to pad zeroes depending on the original position of the decimal point pos. + if (newpos <= 0) { + // If newpos is negative or zero, pad zeroes (size of '0.' + places to move) in the buffer + short numzeroes = 2 + abs(newpos); + memset(buffer + offset, '0', numzeroes); + newpos = offset + 1; // The new decimal position should be offset + '0' + buffer[newpos] = DECIMAL_POINT; // Replace that '0' with the decimal point + offset = numzeroes + offset; // Short offset now in the buffer + } + else { + newpos = newpos + offset; + buffer[newpos] = DECIMAL_POINT; + } + } + + // Start copying the content to the buffer until the exp sign or one more digit after decimal_digits + char *p = src; + short idx = offset; + short lastpos = newpos + decimal_digits + 1; + while (p != exp && idx <= lastpos) { + if (*p == DECIMAL_POINT) { + p++; + continue; + } + if (buffer[idx] == DECIMAL_POINT) { + idx++; + } + buffer[idx++] = *p; + p++; + } + // Round up is required only when number of decimals is more than decimal_digits + num_decimals = idx - newpos - 1; + if (num_decimals > decimal_digits) { + round_up_decimal_numbers(buffer, newpos, decimal_digits, offset, idx); + } + } + } + + // Set the minus sign if negative + if (is_negative) { + buffer[0] = '-'; + } + + zend_string* zstr = zend_string_init(buffer, strlen(buffer), 0); + zend_string_release(Z_STR_P(param_z)); + ZVAL_NEW_STR(param_z, zstr); +} + +int round_up_decimal_numbers(_Inout_ char* buffer, _In_ short decimal_pos, _In_ short num_decimals, _In_ short offset, _In_ short lastpos) +{ + // This helper method assumes the 'buffer' has some extra blank spaces at the beginning without the minus '-' sign. + // We want the rounding to be consistent with php number_format(), http://php.net/manual/en/function.number-format.php + // as well as SQL Server Management studio, such that the least significant digit will be rounded up if it is + // followed by 5 or above. + + short pos = decimal_pos + num_decimals + 1; + if (pos < lastpos) { + short n = buffer[pos] - '0'; + if (n >= 5) { + // Start rounding up - starting from the digit left of pos all the way to the first digit + bool carry_over = true; + for (short p = pos - 1; p >= offset && carry_over; p--) { + if (buffer[p] == DECIMAL_POINT) { + continue; + } + n = buffer[p] - '0'; + carry_over = (++n == 10); + if (n == 10) { + n = 0; + } + buffer[p] = '0' + n; + } + if (carry_over) { + buffer[offset - 1] = '1'; + } + } + if (num_decimals == 0) { + buffer[decimal_pos] = '\0'; + return decimal_pos; + } + else { + buffer[pos] = '\0'; + return pos; + } + } + + // Do nothing and just return + return lastpos; +} + + } diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_decimal.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_decimal.phpt index 4a51b434c..25521e634 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_decimal.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_decimal.phpt @@ -1,20 +1,24 @@ --TEST-- Test for inserting into and retrieving from decimal columns of different scale +--DESCRIPTION-- +This test is similar to sqlsrv_ae_insert_decimal.phpt but it requires enabling column encryption in order +to test the rounding of decimal numbers --SKIPIF-- --FILE-- $num, "Testing numbers between 1 and -1:" => $frac); $scalesToTest = array(0, 1, 2, 3, 4, 5, 7, 9, 19, 28, 38); try { - $conn = connect(); + $conn = connect("ColumnEncryption=Enabled;"); $tbname = "decimalTable"; + foreach ($numSets as $testName => $numSet) { echo "\n$testName\n"; foreach ($numSet as $input) { @@ -35,7 +39,9 @@ try { foreach ($decimalTypes as $key => $value) { $insertValues = array_merge($insertValues, array($key => $input)); } - insertRow($conn, $tbname, $insertValues); + + // Use prepare / execute to bind parameters + insertRow($conn, $tbname, $insertValues, "prepareBindParam"); $stmt = $conn->query("SELECT * FROM $tbname"); $row = $stmt->fetch(PDO::FETCH_ASSOC); @@ -47,6 +53,7 @@ try { $conn->exec("TRUNCATE TABLE $tbname"); } } + dropTable($conn, $tbname); unset($conn); } catch (PDOException $e) { @@ -211,6 +218,16 @@ c4: 123456789012346261234567890123.4629 c5: 123456789012346261234567890123.46290 c7: 123456789012346261234567890123.4629000 c0: -13775323913775323913775323913775323913 +c0: 7654 +c1: 7654.0 +c2: 7654.00 +c3: 7654.000 +c4: 7654.0000 +c5: 7654.00000 +c7: 7654.0000000 +c9: 7654.000000000 +c19: 7654.0000000000000000000 +c28: 7654.0000000000000000000000000000 Testing numbers between 1 and -1: c1: .1 @@ -306,3 +323,13 @@ c38: .00000000000000000000123456789000000000 c28: -.0000000000000000000000012346 c38: -.00000000000000000000000123456789012346 c38: .00000000000000000000000000000001377532 +c0: 1 +c1: .9 +c2: .88 +c3: .880 +c4: .8800 +c5: .88000 +c7: .8800000 +c9: .880000000 +c19: .8800000000000000000 +c28: .8800000000000000000000000000 diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_scientificNot.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_scientificNot.phpt index 14205f954..b02959d9f 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_scientificNot.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_scientificNot.phpt @@ -1,19 +1,80 @@ --TEST-- Test for inserting into and retrieving from decimal columns of different scale +--DESCRIPTION-- +This test is similar to sqlsrv_ae_insert_scientificNot.phpt but it requires enabling column encryption in order +to test the parsing of decimal numbers in scientific notation --SKIPIF-- --FILE-- $posExp, "Testing numbers between 1 and -1:" => $negExp); $scalesToTest = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 19); +function testErrorCases($conn) +{ + // Create a dummy table + $tableName = "pdo_sci_not"; + createTable($conn, $tableName, array("Column1" => "decimal(38, 1)")); + + $expected = '*Invalid character value for cast specification'; + + $tsql = "INSERT INTO $tableName (Column1) VALUES (?)"; + $input = ".1e-0+1."; + + $stmt = $conn->prepare($tsql); + $stmt->bindParam(1, $input); + try { + $stmt->execute(); + echo "Expect $input to fail"; + } catch (PDOException $e) { + if (!fnmatch($expected, $e->GetMessage())) { + echo $e->getMessage(); + } + } + + $input = "10E+0.1."; + try { + $stmt->execute(); + echo "Expect $input to fail"; + } catch (PDOException $e) { + if (!fnmatch($expected, $e->GetMessage())) { + echo $e->getMessage(); + } + } + + $input = "-9E0+2"; + try { + $stmt->execute(); + echo "Expect $input to fail"; + } catch (PDOException $e) { + if (!fnmatch($expected, $e->GetMessage())) { + echo $e->getMessage(); + } + } + $input = "1234.1234.1234"; + $expected = "*String data, right truncation"; + try { + $stmt->execute(); + echo "Expect $input to fail"; + } catch (PDOException $e) { + if (!fnmatch($expected, $e->GetMessage())) { + echo $e->getMessage(); + } + } + dropTable($conn, $tableName); +} + try { - $conn = connect(); + $conn = connect("ColumnEncryption=Enabled;"); + + testErrorCases($conn); + $tbname = "decimalTable"; foreach ($numSets as $testName => $numSet) { @@ -33,12 +94,9 @@ try { $insertValues = array(); foreach ($decimalTypes as $key => $value) { - if (isColEncrypted()) { - $insertValues = array_merge($insertValues, array($key => strval($input))); - } else { - $insertValues = array_merge($insertValues, array($key => $input)); - } + $insertValues = array_merge($insertValues, array($key => $input)); } + insertRow($conn, $tbname, $insertValues, "prepareBindParam"); $stmt = $conn->query("SELECT * FROM $tbname"); @@ -93,7 +151,7 @@ c6: 191.784647 c7: 191.7846470 c8: 191.78464696 c9: 191.784646962 -c19: 191.7846469620200000000 +c19: 191.7846469620226500000 c0: -833 c1: -833.3 c2: -833.33 @@ -115,7 +173,7 @@ c6: 850.000000 c7: 850.0000000 c8: 850.00000000 c9: 850.000000000 -c19: 850.0000000000000000000 +c19: 850.0000000000000600000 c0: -852 c1: -851.6 c2: -851.65 @@ -126,7 +184,7 @@ c6: -851.648352 c7: -851.6483516 c8: -851.64835165 c9: -851.648351648 -c19: -851.6483516483500000000 +c19: -851.6483516483516800000 c0: 316000 c1: 316000.0 c2: 316000.00 @@ -231,12 +289,12 @@ c1: -12345678.9 c2: -12345678.90 c3: -12345678.901 c4: -12345678.9012 -c5: -12345678.90124 +c5: -12345678.90123 c6: -12345678.901235 -c7: -12345678.9012350 -c8: -12345678.90123500 -c9: -12345678.901235000 -c19: -12345678.9012350000000000000 +c7: -12345678.9012346 +c8: -12345678.90123460 +c9: -12345678.901234600 +c19: -12345678.9012346000000000000 c0: 13775320000 c1: 13775320000.0 c2: 13775320000.00 @@ -248,6 +306,17 @@ c7: 13775320000.0000000 c8: 13775320000.00000000 c9: 13775320000.000000000 c19: 13775320000.0000000000000000000 +c0: 1000 +c1: 999.9 +c2: 999.90 +c3: 999.900 +c4: 999.9000 +c5: 999.90000 +c6: 999.900000 +c7: 999.9000000 +c8: 999.90000000 +c9: 999.900000000 +c19: 999.9000000000000000000 Testing numbers between 1 and -1: c0: -10 @@ -279,7 +348,7 @@ c6: -.019178 c7: -.0191785 c8: -.01917846 c9: -.019178465 -c19: -.0191784646962020000 +c19: -.0191784646962022650 c1: .1 c2: .08 c3: .083 @@ -299,7 +368,7 @@ c6: -.085000 c7: -.0850000 c8: -.08500000 c9: -.085000000 -c19: -.0850000000000000000 +c19: -.0850000000000000060 c1: .1 c2: .09 c3: .085 @@ -309,7 +378,7 @@ c6: .085165 c7: .0851648 c8: .08516484 c9: .085164835 -c19: .0851648351648350000 +c19: .0851648351648351680 c1: -.3 c2: -.32 c3: -.316 @@ -392,7 +461,7 @@ c6: .000123 c7: .0001235 c8: .00012346 c9: .000123457 -c19: .0001234567890123500 +c19: .0001234567890123460 c1: -.1 c2: -.14 c3: -.138 @@ -403,3 +472,11 @@ c7: -.1377532 c8: -.13775320 c9: -.137753200 c19: -.1377532000000000000 +c3: .001 +c4: .0010 +c5: .00100 +c6: .001000 +c7: .0009999 +c8: .00099990 +c9: .000999900 +c19: .0009999000000000000 diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt index 72991158a..50a5cf1a3 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_fetchinto_query_args.phpt @@ -1,5 +1,7 @@ --TEST-- fetch columns using fetch mode and different ways of binding columns +--DESCRIPTION-- +This test should not use temporary table as it might occasionally cause deadlocked transactions. --SKIPIF-- --FILE-- @@ -27,19 +29,22 @@ function FetchInto_Query_Args() include("MsSetup.inc"); set_time_limit(0); - $tableName = GetTempTableName(); + $tableName = 'fetchinto_query_args'; $conn = new PDO( "sqlsrv:server=$server;database=$databaseName", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); - $stmt = $conn->exec("CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); + dropTable($conn, $tableName); + $conn->exec("CREATE TABLE $tableName ([c1_int] int, [c2_tinyint] tinyint, [c3_smallint] smallint, [c4_bigint] bigint, [c5_bit] bit, [c6_float] float, [c7_real] real, [c8_decimal] decimal(28,4), [c9_numeric] numeric(32,4), [c10_money] money, [c11_smallmoney] smallmoney, [c12_char] char(512), [c13_varchar] varchar(512), [c14_varchar_max] varchar(max), [c15_nchar] nchar(512), [c16_nvarchar] nvarchar(512), [c17_nvarchar_max] nvarchar(max), [c18_text] text, [c19_ntext] ntext, [c20_binary] binary(512), [c21_varbinary] varbinary(512), [c22_varbinary_max] varbinary(max), [c23_image] image, [c24_uniqueidentifier] uniqueidentifier, [c25_datetime] datetime, [c26_smalldatetime] smalldatetime, [c27_timestamp] timestamp, [c28_xml] xml, [c29_time] time, [c30_date] date, [c31_datetime2] datetime2, [c32_datetimeoffset] datetimeoffset)"); $numRows = 0; $query = GetQuery($tableName, ++$numRows); $stmt = $conn->query($query); + unset($stmt); $query = GetQuery($tableName, ++$numRows); $stmt = $conn->query($query); - $stmt = null; + unset($stmt); $sql = "SELECT * FROM $tableName ORDER BY c27_timestamp"; $obj1 = new PdoTestClass(); @@ -51,10 +56,11 @@ function FetchInto_Query_Args() $stmt2->setFetchMode(PDO::FETCH_INTO, $obj2); VerifyResults($stmt1, $stmt2, $tableName); + dropTable($conn, $tableName); - $stmt1 = null; - $stmt2 = null; - $conn = null; + unset($stmt1); + unset($stmt2); + unset($conn); } function VerifyResults($stmt1, $stmt2, $tableName) diff --git a/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt index 37b2ddee3..0c06cd9d6 100644 --- a/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt +++ b/test/functional/pdo_sqlsrv/pdo_insert_fetch_utf8text.phpt @@ -153,7 +153,7 @@ function runInOutProcWithErrors($conn, $utf8_2) function runIntDoubleProcWithErrors($conn) { - $sql = "{call pdoIntDoubleProc(?)}"; + $sql = "{call pdoUTF8InOutProc(?)}"; $val = pack('H*', 'ffffffff'); try { diff --git a/test/functional/sqlsrv/0065.phpt b/test/functional/sqlsrv/0065.phpt index 87745b34a..097d2c331 100644 --- a/test/functional/sqlsrv/0065.phpt +++ b/test/functional/sqlsrv/0065.phpt @@ -236,11 +236,12 @@ if ($t !== $u) { die("Round trip failed."); } +// $t is an invalid utf-8 string, expect the procedure to fail $t = pack('H*', 'ffffffff'); $sqlType = $params = array(array(&$t, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING('utf-8'))); -$query = "{call IntDoubleProc(?)}"; +$query = "{call Utf8InOutProc(?)}"; $s = AE\executeQueryParams($c, $query, $params, true, "no error from an invalid utf-8 string"); dropTable($c, $tableName); diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_decimal.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_decimal.phpt index 656c60259..afda09582 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_decimal.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_decimal.phpt @@ -6,51 +6,54 @@ Test for inserting into and retrieving from decimal columns of different scale $num, "Testing numbers between 1 and -1:" => $frac); $scalesToTest = array(0, 1, 2, 3, 4, 5, 7, 9, 19, 28, 38); -try { - $conn = AE\connect(); - $tbname = "decimalTable"; - foreach ($numSets as $testName => $numSet) { - echo "\n$testName\n"; - foreach ($numSet as $input) { - $numInt = ceil(log10(abs($input) + 1)); - $decimalTypes = array(); - foreach ($scalesToTest as $scale) { - if ($scale < 39 - $numInt) { - array_push($decimalTypes, new AE\ColumnMeta("decimal(38, $scale)", "c$scale")); - } +$conn = AE\connect(); +$tbname = "decimalTable"; +foreach ($numSets as $testName => $numSet) { + echo "\n$testName\n"; + foreach ($numSet as $input) { + $numInt = ceil(log10(abs($input) + 1)); + $decimalTypes = array(); + foreach ($scalesToTest as $scale) { + if ($scale < 39 - $numInt) { + array_push($decimalTypes, new AE\ColumnMeta("decimal(38, $scale)", "c$scale")); } - if (empty($decimalTypes)) { - $decimalTypes = array(new AE\ColumnMeta("decimal(38, 0)", "c0")); - } - AE\createTable($conn, $tbname, $decimalTypes); + } + if (empty($decimalTypes)) { + $decimalTypes = array(new AE\ColumnMeta("decimal(38, 0)", "c0")); + } + AE\createTable($conn, $tbname, $decimalTypes); - $insertValues = array(); - foreach ($decimalTypes as $decimalType) { - $insertValues = array_merge($insertValues, array($decimalType->colName => $input)); - } - AE\insertRow($conn, $tbname, $insertValues); + $insertValues = array(); + foreach ($decimalTypes as $decimalType) { + $insertValues = array_merge($insertValues, array($decimalType->colName => $input)); + } + $stmt = AE\insertRow($conn, $tbname, $insertValues); + if (!$stmt) { + var_dump($insertValues); + fatalError("Failed to insert the above values\n"); + } - $stmt = sqlsrv_query($conn, "SELECT * FROM $tbname"); - $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); - foreach ($row as $key => $value) { - if ($value != 0) { - echo "$key: $value\n"; - } + $stmt = sqlsrv_query($conn, "SELECT * FROM $tbname"); + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + if (empty($row)) { + fatalError("Empty array is returned\n"); + } + foreach ($row as $key => $value) { + if ($value != 0) { + echo "$key: $value\n"; } - sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); } + sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); } - dropTable($conn, $tbname); - sqlsrv_close($conn); -} catch (PDOException $e) { - echo $e->getMessage(); } +dropTable($conn, $tbname); +sqlsrv_close($conn); ?> --EXPECT-- @@ -210,6 +213,16 @@ c4: 123456789012346261234567890123.4629 c5: 123456789012346261234567890123.46290 c7: 123456789012346261234567890123.4629000 c0: -13775323913775323913775323913775323913 +c0: 9876 +c1: 9876.0 +c2: 9876.00 +c3: 9876.000 +c4: 9876.0000 +c5: 9876.00000 +c7: 9876.0000000 +c9: 9876.000000000 +c19: 9876.0000000000000000000 +c28: 9876.0000000000000000000000000000 Testing numbers between 1 and -1: c1: .1 @@ -304,4 +317,14 @@ c28: .0000000000000000000012345679 c38: .00000000000000000000123456789000000000 c28: -.0000000000000000000000012346 c38: -.00000000000000000000000123456789012346 -c38: .00000000000000000000000000000001377532 \ No newline at end of file +c38: .00000000000000000000000000000001377532 +c0: -1 +c1: -1.0 +c2: -.99 +c3: -.990 +c4: -.9900 +c5: -.99000 +c7: -.9900000 +c9: -.990000000 +c19: -.9900000000000000000 +c28: -.9900000000000000000000000000 diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_scientificNot.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_scientificNot.phpt index 16c78696d..2944832dd 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_scientificNot.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_scientificNot.phpt @@ -6,54 +6,112 @@ Test for inserting into and retrieving from decimal columns of different scale $posExp, "Testing numbers between 1 and -1:" => $negExp); $scalesToTest = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 19); -try { - $conn = AE\connect(); - $tbname = "decimalTable"; - foreach ($numSets as $testName => $numSet) { - echo "\n$testName\n"; - foreach ($numSet as $input) { - $numInt = ceil(log10(abs($input) + 1)); - $decimalTypes = array(); - foreach ($scalesToTest as $scale) { - if ($scale < 39 - $numInt) { - array_push($decimalTypes, new AE\ColumnMeta("decimal(38, $scale)", "c$scale")); - } - } - if (empty($decimalTypes)) { - $decimalTypes = array(new AE\ColumnMeta("decimal(38, 0)", "c0")); - } - AE\createTable($conn, $tbname, $decimalTypes); +function testErrorCases($conn) +{ + // Create a dummy table + $tableName = "srv_sci_not"; + $colMeta = array(new AE\ColumnMeta("decimal(38, 1)", "Column1")); + + AE\createTable($conn, $tableName, $colMeta); + + $expected = '*Invalid character value for cast specification'; + $tsql = "INSERT INTO $tableName (Column1) VALUES (?)"; + $input = "- 0E1.3"; + $param = array( + array(&$input, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_DECIMAL(38, 1)) + ); + + $stmt = sqlsrv_prepare($conn, $tsql, $param); + if (!sqlsrv_execute($stmt)) { + if (!fnmatch($expected, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + } else { + echo "Expect $input to fail"; + } + + $input = "8e0-2"; + if (!sqlsrv_execute($stmt)) { + if (!fnmatch($expected, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + } else { + echo "Expect $input to fail"; + } + + $input = "-19e032+"; + if (!sqlsrv_execute($stmt)) { + if (!fnmatch($expected, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + } else { + echo "Expect $input to fail"; + } + $input = "5678.5678.5678"; + $expected = "*String data, right truncation"; + if (!sqlsrv_execute($stmt)) { + if (!fnmatch($expected, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + } else { + echo "Expect $input to fail"; + } + dropTable($conn, $tableName); +} + +$conn = AE\connect(); - $insertValues = array(); - foreach ($decimalTypes as $decimalType) { - $scale = intval(ltrim($decimalType->colName, "c")); - array_push($insertValues, array($input, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_DECIMAL(38, $scale))); +testErrorCases($conn); + +$tbname = "decimalTable"; +foreach ($numSets as $testName => $numSet) { + echo "\n$testName\n"; + foreach ($numSet as $input) { + $numInt = ceil(log10(abs($input) + 1)); + $decimalTypes = array(); + foreach ($scalesToTest as $scale) { + if ($scale < 39 - $numInt) { + array_push($decimalTypes, new AE\ColumnMeta("decimal(38, $scale)", "c$scale")); } - $insertSql = "INSERT INTO $tbname VALUES(" . AE\getSeqPlaceholders(count($insertValues)) . ")"; - $stmt = sqlsrv_prepare($conn, $insertSql, $insertValues); - sqlsrv_execute($stmt); + } + if (empty($decimalTypes)) { + $decimalTypes = array(new AE\ColumnMeta("decimal(38, 0)", "c0")); + } + AE\createTable($conn, $tbname, $decimalTypes); + + $insertValues = array(); + foreach ($decimalTypes as $decimalType) { + $scale = intval(ltrim($decimalType->colName, "c")); + array_push($insertValues, array($input, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_DECIMAL(38, $scale))); + } - $stmt = sqlsrv_query($conn, "SELECT * FROM $tbname"); - $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); - foreach ($row as $key => $value) { - if ($value != 0) { - echo "$key: $value\n"; - } + $insertSql = "INSERT INTO $tbname VALUES(" . AE\getSeqPlaceholders(count($insertValues)) . ")"; + $stmt = sqlsrv_prepare($conn, $insertSql, $insertValues); + if (!sqlsrv_execute($stmt)) { + fatalError("Failed to execute $insertSql\n"); + } + + $stmt = sqlsrv_query($conn, "SELECT * FROM $tbname"); + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + if (empty($row)) { + fatalError("Empty array is returned\n"); + } + foreach ($row as $key => $value) { + if ($value != 0) { + echo "$key: $value\n"; } - sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); } + sqlsrv_query($conn, "TRUNCATE TABLE $tbname"); } - dropTable($conn, $tbname); - sqlsrv_close($conn); -} catch (PDOException $e) { - echo $e->getMessage(); } +dropTable($conn, $tbname); +sqlsrv_close($conn); ?> --EXPECT-- @@ -246,6 +304,28 @@ c7: 13775320000.0000000 c8: 13775320000.00000000 c9: 13775320000.000000000 c19: 13775320000.0000000000000000000 +c0: -53687 +c1: -53687.1 +c2: -53687.09 +c3: -53687.092 +c4: -53687.0919 +c5: -53687.09185 +c6: -53687.091854 +c7: -53687.0918543 +c8: -53687.09185426 +c9: -53687.091854260 +c19: -53687.0918542600000000000 +c0: 1000 +c1: 999.9 +c2: 999.90 +c3: 999.900 +c4: 999.9000 +c5: 999.90000 +c6: 999.900000 +c7: 999.9000000 +c8: 999.90000000 +c9: 999.900000000 +c19: 999.9000000000000000000 Testing numbers between 1 and -1: c0: -10 @@ -401,3 +481,21 @@ c7: -.1377532 c8: -.13775320 c9: -.137753200 c19: -.1377532000000000000 +c1: .1 +c2: .05 +c3: .054 +c4: .0537 +c5: .05370 +c6: .053697 +c7: .0536971 +c8: .05369709 +c9: .053697092 +c19: .0536970918542600000 +c3: .001 +c4: .0010 +c5: .00100 +c6: .001000 +c7: .0009999 +c8: .00099990 +c9: .000999900 +c19: .0009999000000000000 From d4a29fe332789013243cf4ba2bbbd5ccbee4a99a Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 22 Jun 2020 15:42:32 -0700 Subject: [PATCH 215/249] Check exception when finalizing output parameters (#1143) --- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 14 +- source/shared/core_util.cpp | 2 +- .../pdo_ae_output_param_errors.phpt | 138 +++++++++++++++++ .../sqlsrv/sqlsrv_ae_output_param_errors.phpt | 143 ++++++++++++++++++ 5 files changed, 294 insertions(+), 5 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_output_param_errors.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_ae_output_param_errors.phpt diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 0dae9a0ea..72925e1cb 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1913,7 +1913,7 @@ enum error_handling_flags { // 3/message) driver specific error message // The fetch type determines if the indices are numeric, associative, or both. bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, - _In_ logging_severity severity, _In_ bool check_warning = false ); + _In_ logging_severity severity, _In_opt_ bool check_warning = false ); // format and return a driver specfic error void core_sqlsrv_format_driver_error( _In_ sqlsrv_context& ctx, _In_ sqlsrv_error_const const* custom_error, diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 54a49f147..5ddf73876 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -112,7 +112,7 @@ void col_cache_dtor( _Inout_ zval* data_z ); void field_cache_dtor( _Inout_ zval* data_z ); int round_up_decimal_numbers(_Inout_ char* buffer, _In_ short decimal_pos, _In_ short decimals_places, _In_ short offset, _In_ short lastpos); void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT field_scale, _Inout_updates_bytes_(*field_len) char*& field_value, _Inout_ SQLLEN* field_len); -void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt ); +void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt, _In_opt_ bool exception_thrown = false ); void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len ); stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key, _In_ const stmt_option stmt_opts[] ); @@ -820,7 +820,7 @@ SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_l // if the statement executed but failed in a subsequent operation before returning, // we need to cancel the statement and deref the output and stream parameters if ( stmt->send_streams_at_exec ) { - finalize_output_parameters( stmt ); + finalize_output_parameters( stmt, true ); zend_hash_clean( Z_ARRVAL( stmt->param_streams )); } if( stmt->executed ) { @@ -2364,10 +2364,18 @@ void format_decimal_numbers(_In_ SQLSMALLINT decimals_places, _In_ SQLSMALLINT f // parameters passed to SQLBindParameter. It also converts output strings from UTF-16 to UTF-8 if necessary. // For integer or float parameters, it sets those to NULL if a NULL was returned by SQL Server -void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt ) +void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt, _In_opt_ bool exception_thrown /*= false*/ ) { if (Z_ISUNDEF(stmt->output_params)) return; + + // If an error occurs or an exception is thrown during an execution, the values of any output + // parameters or columns are undefined. Therefore, do not depend on them having any specific + // values, because the ODBC driver may or may not have modified them. + if (exception_thrown) { + zend_hash_clean(Z_ARRVAL(stmt->output_params)); + return; + } HashTable* params_ht = Z_ARRVAL(stmt->output_params); zend_ulong index = -1; diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 2d0c9c589..99b1113a2 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -257,7 +257,7 @@ void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* i // 3/message) driver specific error message // The fetch type determines if the indices are numeric, associative, or both. -bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, _In_ logging_severity severity, _In_ bool check_warning /* = false */) +bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, _In_ logging_severity severity, _In_opt_ bool check_warning /* = false */) { SQLHANDLE h = ctx.handle(); SQLSMALLINT h_type = ctx.handle_type(); diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_errors.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_errors.phpt new file mode 100644 index 000000000..f6654665a --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_errors.phpt @@ -0,0 +1,138 @@ +--TEST-- +Test to incorrectly bind input parameters as output parameters of various types +--DESCRIPTION-- +Test to incorrectly bind input parameters as output parameters of various types. +The key is to enable ColumnEncryption and check for memory leaks. +--SKIPIF-- + +--FILE-- +getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; + $vers = explode(".", $msodbcsql_ver); + + unset($conn); + if ($vers[0] >= 17 && $vers[1] > 0){ + return true; + } else { + return false; + } +} + +require_once("MsCommon_mid-refactor.inc"); + +try { + // Check if the ODBC driver supports connecting with ColumnEncryption + // If not simply return + if (!checkODBCVersion()) { + echo "Done\n"; + return; + } + + $conn = connect("ColumnEncryption=Enabled;"); + + // Create a dummy table with various data types + $tbname = 'pdo_output_param_errors'; + $colMetaArr = array("c1_int" => "int", + "c2_smallint" => "smallint", + "c3_tinyint" => "tinyint", + "c4_bit" => "bit", + "c5_bigint" => "bigint", + "c6_decimal" => "decimal(18,5)", + "c7_numeric" => "numeric(10,5)", + "c8_float" => "float", + "c9_real" => "real", + "c10_date" => "date", + "c11_datetime" => "datetime", + "c12_datetime2" => "datetime2", + "c13_datetimeoffset" => "datetimeoffset", + "c14_time" => "time", + "c15_char" => "char(5)", + "c16_varchar" => "varchar(max)", + "c17_nchar" => "nchar(5)", + "c18_nvarchar" => "nvarchar(max)"); + createTable($conn, $tbname, $colMetaArr); + + // Create a dummy select statement + $tsql = "SELECT * FROM $tbname WHERE c1_int = ? OR c2_smallint = ? OR c3_tinyint = ? "; + $tsql .= "OR c4_bit = ? OR c5_bigint = ? OR c6_decimal = ? OR c7_numeric = ? OR c8_float = ? "; + $tsql .= "OR c9_real = ? OR c10_date = ? OR c11_datetime = ? OR c12_datetime2 = ? "; + $tsql .= "OR c13_datetimeoffset = ? OR c14_time = ? OR c15_char = ? "; + $tsql .= "OR c16_varchar = ? OR c17_nchar = ? OR c18_nvarchar = ?"; + + // Initialize all inputs, set bigint, decimal and numeric as empty strings + $intOut = 0; + $smallintOut = 0; + $tinyintOut = 0; + $bitOut = 0; + $bigintOut = ''; + $decimalOut = ''; + $numericOut = ''; + $floatOut = 0.0; + $realOut = 0.0; + $dateOut = '0001-01-01'; + $datetimeOut = '1753-01-01 00:00:00'; + $datetime2Out = '0001-01-01 00:00:00'; + $datetimeoffsetOut = '1900-01-01 00:00:00 +01:00'; + $timeOut = '00:00:00'; + $charOut = ''; + $varcharOut = ''; + $ncharOut = ''; + $nvarcharOut = ''; + + $usage1 = 0; + $rounds = 30; + for ($i = 0; $i < $rounds; $i++) { + $stmt = $conn->prepare($tsql); + + $stmt->bindParam(1, $intOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); + $stmt->bindParam(2, $smallintOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); + $stmt->bindParam(3, $tinyintOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); + $stmt->bindParam(4, $bitOut, PDO::PARAM_INT, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); + $stmt->bindParam(5, $bigintOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(6, $decimalOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(7, $numericOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(8, $floatOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(9, $realOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(10, $dateOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(11, $datetimeOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(12, $datetime2Out, PDO::PARAM_STR, 2048); + $stmt->bindParam(13, $datetimeoffsetOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(14, $timeOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(15, $charOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(16, $varcharOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(17, $ncharOut, PDO::PARAM_STR, 2048); + $stmt->bindParam(18, $nvarcharOut, PDO::PARAM_STR, 2048, PDO::SQLSRV_ENCODING_UTF8); + + // Expect the following to fail so just ignore the exception caught + try { + $stmt->execute(); + } catch (PDOException $e) { + ; + } + unset($stmt); + + // Compare the current memory usage to the previous usage + if ($i == 0) { + $usage1 = memory_get_usage(); + } else { + $usage2 = memory_get_usage(); + if ($usage2 > $usage1) { + echo "Memory leaks ($i)! Expected $usage1 but now $usage2\n"; + } + } + } + + dropTable($conn, $tbname); + unset($conn); +} catch (PDOException $e) { + var_dump($e); +} + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ae_output_param_errors.phpt b/test/functional/sqlsrv/sqlsrv_ae_output_param_errors.phpt new file mode 100644 index 000000000..4933aacaa --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_output_param_errors.phpt @@ -0,0 +1,143 @@ +--TEST-- +Test to incorrectly bind input parameters as output parameters of various types +--DESCRIPTION-- +Similar to pdo_ae_output_param_errors.phpt - test to incorrectly bind input parameters +as output parameters of various types. The key is to enable ColumnEncryption and +check for memory leaks. +--SKIPIF-- + +--FILE-- += 17 && $vers[1] > 0){ + return true; + } else { + return false; + } +} + +// Check if the ODBC driver supports connecting with ColumnEncryption +// If not simply return +require_once('MsHelper.inc'); +require_once('MsSetup.inc'); + +$conn = sqlsrv_connect($server, $connectionOptions); +if ($conn === false) { + fatalError("Failed to connect to $server."); +} +if (!checkODBCVersion($conn)) { + echo "Done\n"; + sqlsrv_close($conn); + return; +} + +// Create a dummy table with various data types +$tbname = 'srv_output_param_errors'; +$colMetaArr = array( new AE\ColumnMeta("int", "c1_int"), + new AE\ColumnMeta("smallint", "c2_smallint"), + new AE\ColumnMeta("tinyint", "c3_tinyint"), + new AE\ColumnMeta("bit", "c4_bit"), + new AE\ColumnMeta("bigint", "c5_bigint"), + new AE\ColumnMeta("decimal(18,5)", "c6_decimal"), + new AE\ColumnMeta("numeric(10,5)", "c7_numeric"), + new AE\ColumnMeta("float", "c8_float"), + new AE\ColumnMeta("real", "c9_real"), + new AE\ColumnMeta("date", "c10_date"), + new AE\ColumnMeta("datetime", "c11_datetime"), + new AE\ColumnMeta("datetime2", "c12_datetime2"), + new AE\ColumnMeta("datetimeoffset", "c13_datetimeoffset"), + new AE\ColumnMeta("time", "c14_time"), + new AE\ColumnMeta("char(5)", "c15_char"), + new AE\ColumnMeta("varchar(max)", "c16_varchar"), + new AE\ColumnMeta("nchar(5)", "c17_nchar"), + new AE\ColumnMeta("nvarchar(max)", "c18_nvarchar")); +AE\createTable($conn, $tbname, $colMetaArr); + +// Create a dummy select statement +$sql = "SELECT * FROM $tbname WHERE c1_int = ? OR c2_smallint = ? OR c3_tinyint = ? "; +$sql .= "OR c4_bit = ? OR c5_bigint = ? OR c6_decimal = ? OR c7_numeric = ? OR c8_float = ? "; +$sql .= "OR c9_real = ? OR c10_date = ? OR c11_datetime = ? OR c12_datetime2 = ? "; +$sql .= "OR c13_datetimeoffset = ? OR c14_time = ? OR c15_char = ? "; +$sql .= "OR c16_varchar = ? OR c17_nchar = ? OR c18_nvarchar = ?"; + +$options = array_merge($connectionOptions, + array('ColumnEncryption' => 'Enabled', + 'CharacterSet' => 'UTF-8')); + +// Initialize all inputs, set bigint, decimal and numeric as empty strings +$intOut = 0; +$smallintOut = 0; +$tinyintOut = 0; +$bitOut = 0; +$bigintOut = ''; +$decimalOut = ''; +$numericOut = ''; +$floatOut = 0.0; +$realOut = 0.0; +$dateOut = ''; +$datetimeOut = ''; +$datetime2Out = ''; +$datetimeoffsetOut = ''; +$timeOut = ''; +$charOut = ''; +$varcharOut = ''; +$ncharOut = ''; +$nvarcharOut = ''; + +$usage1 = 0; +$rounds = 30; +for ($i = 0; $i < $rounds; $i++) { + // Connect with ColumnEncryption enabled + $conn2 = sqlsrv_connect($server, $options); + if ($conn2 === false) { + fatalError("Failed to connect to $server."); + } + + $stmt = sqlsrv_prepare($conn2, $sql, array( array( &$intOut, SQLSRV_PARAM_OUT ), + array( &$smallintOut, SQLSRV_PARAM_OUT ), + array( &$tinyintOut, SQLSRV_PARAM_OUT ), + array( &$bitOut, SQLSRV_PARAM_OUT ), + array( &$bigintOut, SQLSRV_PARAM_OUT ), + array( &$decimalOut, SQLSRV_PARAM_OUT ), + array( &$numericOut, SQLSRV_PARAM_OUT ), + array( &$floatOut, SQLSRV_PARAM_OUT ), + array( &$realOut, SQLSRV_PARAM_OUT ), + array( &$dateOut, SQLSRV_PARAM_OUT ), + array( &$datetimeOut, SQLSRV_PARAM_OUT ), + array( &$datetime2Out, SQLSRV_PARAM_OUT ), + array( &$datetimeoffsetOut, SQLSRV_PARAM_OUT ), + array( &$timeOut, SQLSRV_PARAM_OUT ), + array( &$charOut, SQLSRV_PARAM_OUT ), + array( &$varcharOut, SQLSRV_PARAM_OUT ), + array( &$ncharOut, SQLSRV_PARAM_OUT ), + array( &$nvarcharOut, SQLSRV_PARAM_OUT ))); + + // Expect the following to fail so just ignore the errors + sqlsrv_execute($stmt); + + // Compare the current memory usage to the previous usage + if ($i == 0) { + $usage1 = memory_get_usage(); + } else { + $usage2 = memory_get_usage(); + if ($usage2 > $usage1) { + echo "Memory leaks ($i)! Expected $usage1 but now $usage2\n"; + } + } + + // Free the resources to trigger the destruction of any zvals with refcount of 0 + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn2); +} + +sqlsrv_query($conn, "DROP TABLE $tbname"); +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done \ No newline at end of file From d5e1d8cfbc315ae4264251c983efc999331e6ec0 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 23 Jun 2020 13:52:50 -0700 Subject: [PATCH 216/249] Binding integers as output parameters with correct C types (#1144) --- source/shared/core_stmt.cpp | 15 +++++++------- .../sqlsrv/srv_699_out_param_integer.phpt | 20 ++++++++++++++++++- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 5ddf73876..b7d99b09e 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2079,13 +2079,14 @@ SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, sql_c_type = SQL_C_SLONG; break; case IS_LONG: - //ODBC 64-bit long and integer type are 4 byte values. - if ((Z_LVAL_P(param_z) < INT_MIN) || (Z_LVAL_P(param_z) > INT_MAX)) { - sql_c_type = SQL_C_SBIGINT; - } - else { - sql_c_type = SQL_C_SLONG; - } + // When binding any integer, the zend_long value and its length are used as the buffer + // and buffer length. When the buffer is 8 bytes use the corresponding C type for + // 8-byte integers +#ifdef ZEND_ENABLE_ZVAL_LONG64 + sql_c_type = SQL_C_SBIGINT; +#else + sql_c_type = SQL_C_SLONG; +#endif break; case IS_DOUBLE: sql_c_type = SQL_C_DOUBLE; diff --git a/test/functional/sqlsrv/srv_699_out_param_integer.phpt b/test/functional/sqlsrv/srv_699_out_param_integer.phpt index 39f195697..5f58dc3da 100644 --- a/test/functional/sqlsrv/srv_699_out_param_integer.phpt +++ b/test/functional/sqlsrv/srv_699_out_param_integer.phpt @@ -65,7 +65,7 @@ if (strtoupper(substr(PHP_OS, 0, 3)) === 'LIN') { $sql_callSP = $set_no_count . "{call $procName(?)}"; -// Initialize the output parameter to any number +// Initialize the output parameter to any positive number $outParam = 1; $params = array(array(&$outParam, SQLSRV_PARAM_OUT)); $stmt = sqlsrv_query($conn, $sql_callSP, $params); @@ -79,6 +79,24 @@ if ($outParam != 123) { echo "The output param value $outParam is unexpected!\n"; } +// Initialize the output parameter to any negative number +$outParam = -1; +$params = array(array(&$outParam, SQLSRV_PARAM_OUT)); +$stmt = sqlsrv_prepare($conn, $sql_callSP, $params); +if (!$stmt) { + fatalError("Error in preparing $procName\n"); +} +$res = sqlsrv_execute($stmt); +if (!$res) { + fatalError("Error in executing $procName\n"); +} + +while ($res = sqlsrv_next_result($stmt)); + +if ($outParam != 123) { + echo "The output param value $outParam is unexpected!\n"; +} + dropTable($conn, $tableName1); dropTable($conn, $tableName2); dropProc($conn, $procName); From ecbd53f712249f68cfa0e1eb3fa5eb9348576f5b Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 26 Jun 2020 15:56:51 -0700 Subject: [PATCH 217/249] Simplied conversions from strings to numbers (#1146) --- source/shared/core_results.cpp | 146 ++++++------------ .../pdo_sqlsrv/pdo_buffered_fetch_types.phpt | 70 +++++++-- .../pdostatement_fetch_orientation.phpt | 51 +++--- .../sqlsrv/sqlsrv_buffered_fetch_types.phpt | 62 +++++--- 4 files changed, 183 insertions(+), 146 deletions(-) diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index fd55c2947..64c3e6e58 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -262,64 +262,6 @@ std::string getUTF8StringFromString( _In_z_ const char* source ) #endif // !_WIN32 -template -SQLRETURN string_to_number( _In_z_ Char* string_data, SQLLEN str_len, _Out_writes_bytes_(*out_buffer_length) void* buffer, SQLLEN buffer_length, - _Inout_ SQLLEN* out_buffer_length, _Inout_ sqlsrv_error_auto_ptr& last_error ) -{ - Number* number_data = reinterpret_cast( buffer ); -#ifdef _WIN32 - std::locale loc; // default locale should match system - std::basic_istringstream is; - is.str( string_data ); - is.imbue( loc ); - std::ios_base::iostate st = 0; - - std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf()), std::basic_istream::_Iter( 0 ), is, st, *number_data ); - - if ( st & std::ios_base::failbit ) { - last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "22003", ( SQLCHAR* ) "Numeric value out of range", 103 ); - return SQL_ERROR; - } - - *out_buffer_length = sizeof( Number ); -#else - std::string str = getUTF8StringFromString( string_data ); - - std::istringstream is( str ); - std::locale loc; // default locale should match system - is.imbue( loc ); - - auto& facet = std::use_facet>( is.getloc() ); - std::istreambuf_iterator beg( is ), end; - std::ios_base::iostate err = std::ios_base::goodbit; - - if ( std::is_integral::value ) - { - long number; - facet.get( beg, end, is, err, number ); - - *number_data = number; - } - else - { - double number; - facet.get( beg, end, is, err, number ); - - *number_data = number; - } - - *out_buffer_length = sizeof( Number ); - - if ( is.fail() ) - { - last_error = new ( sqlsrv_malloc(sizeof( sqlsrv_error ))) sqlsrv_error(( SQLCHAR* ) "22003", ( SQLCHAR* ) "Numeric value out of range", 103 ); - return SQL_ERROR; - } -#endif // _WIN32 - return SQL_SUCCESS; - -} - // "closure" for the hash table destructor struct row_dtor_closure { @@ -851,7 +793,6 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ case SQL_C_LONG: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length); case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_long(field_index, buffer, buffer_length, out_buffer_length); case SQL_C_CHAR: return sqlsrv_buffered_result_set::long_to_system_string(field_index, buffer, buffer_length, out_buffer_length); - case SQL_C_WCHAR: return sqlsrv_buffered_result_set::long_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); default: break; } @@ -862,7 +803,6 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_double(field_index, buffer, buffer_length, out_buffer_length); case SQL_C_CHAR: return sqlsrv_buffered_result_set::double_to_system_string(field_index, buffer, buffer_length, out_buffer_length); case SQL_C_LONG: return sqlsrv_buffered_result_set::double_to_long(field_index, buffer, buffer_length, out_buffer_length); - case SQL_C_WCHAR: return sqlsrv_buffered_result_set::double_to_wide_string(field_index, buffer, buffer_length, out_buffer_length); default: break; } @@ -1082,23 +1022,6 @@ SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( _In_ SQLSMALLINT return r; } -SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, - _Inout_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" ); - - unsigned char* row = get_row(); - double* double_data = reinterpret_cast( &row[meta[field_index].offset] ); - SQLRETURN r = SQL_SUCCESS; -#ifdef _WIN32 - r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -#else - r = number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error ); -#endif // _WIN32 - return r; -} - SQLRETURN sqlsrv_buffered_result_set::long_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Out_ SQLLEN* out_buffer_length ) { @@ -1131,23 +1054,6 @@ SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( _In_ SQLSMALLINT fi return r; } -SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_bytes_to_opt_(buffer_length, *out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, - _Inout_ SQLLEN* out_buffer_length ) -{ - SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_LONG, "Invalid conversion to wide string" ); - SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" ); - - unsigned char* row = get_row(); - LONG* long_data = reinterpret_cast( &row[meta[field_index].offset] ); - SQLRETURN r = SQL_SUCCESS; -#ifdef _WIN32 - r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -#else - r = number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error ); -#endif // _WIN32 - return r; -} - SQLRETURN sqlsrv_buffered_result_set::string_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { @@ -1157,7 +1063,16 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_double( _In_ SQLSMALLINT field_i unsigned char* row = get_row(); char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); - return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); + double* number_data = reinterpret_cast(buffer); + try { + *number_data = std::stod(std::string(string_data)); + } catch (const std::logic_error& err) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); + return SQL_ERROR; + } + + *out_buffer_length = sizeof(double); + return SQL_SUCCESS; } SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, @@ -1169,7 +1084,20 @@ SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( _In_ SQLSMALLINT field_ unsigned char* row = get_row(); SQLWCHAR* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); + double* number_data = reinterpret_cast(buffer); + try { +#ifdef _WIN32 + *number_data = std::stod(std::wstring(string_data)); +#else + *number_data = std::stod(getUTF8StringFromString(string_data)); +#endif // _WIN32 + } catch (const std::logic_error& err) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); + return SQL_ERROR; + } + + *out_buffer_length = sizeof(double); + return SQL_SUCCESS; } SQLRETURN sqlsrv_buffered_result_set::string_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, @@ -1181,7 +1109,16 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_long( _In_ SQLSMALLINT field_ind unsigned char* row = get_row(); char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); - return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); + LONG* number_data = reinterpret_cast(buffer); + try { + *number_data = std::stol(std::string(string_data)); + } catch (const std::logic_error& err) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); + return SQL_ERROR; + } + + *out_buffer_length = sizeof(LONG); + return SQL_SUCCESS; } SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, @@ -1193,7 +1130,20 @@ SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( _In_ SQLSMALLINT field_in unsigned char* row = get_row(); SQLWCHAR* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR ); - return string_to_number( string_data, meta[field_index].length, buffer, buffer_length, out_buffer_length, last_error ); + LONG* number_data = reinterpret_cast(buffer); + try { +#ifdef _WIN32 + *number_data = std::stol(std::wstring(string_data)); +#else + *number_data = std::stol(getUTF8StringFromString(string_data)); +#endif // _WIN32 + } catch (const std::logic_error& err) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); + return SQL_ERROR; + } + + *out_buffer_length = sizeof(LONG); + return SQL_SUCCESS; } SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, diff --git a/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt index f2d45d473..ebf197655 100644 --- a/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt +++ b/test/functional/pdo_sqlsrv/pdo_buffered_fetch_types.phpt @@ -14,12 +14,40 @@ $outOfRange = 'Numeric value out of range'; $truncation = 'Fractional truncation'; $epsilon = 0.00001; +function fetchAsChar($conn, $tableName, $inputs) +{ + $query = "SELECT c1, c2, c3, c4, c5, c6 FROM $tableName"; + try { + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_SYSTEM); + + // Fetch all fields as strings - no conversion + for ($i = 0; $i < count($inputs) - 1; $i++) { + $stmt->execute(); + $f = $stmt->fetchColumn($i); + + if ($i == 2) { + if (!compareFloats(floatval($inputs[$i]), floatval($f))) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump($f); + } + } elseif ($f !== $inputs[$i]) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump($f); + } + } + } catch (PdoException $e) { + echo "Caught exception in fetchAsChar:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + function fetchAsUTF8($conn, $tableName, $inputs) { $query = "SELECT * FROM $tableName"; try { $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); - + // Fetch all fields as UTF-8 strings for ($i = 0; $i < count($inputs); $i++) { $stmt->execute(); @@ -145,14 +173,7 @@ function fetchCharAsInt($conn, $tableName, $column) $stmt->bindColumn($column, $value, PDO::PARAM_INT); $row = $stmt->fetch(PDO::FETCH_BOUND); - // TODO 11297: fix this part outside Windows later - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - echo "in fetchCharAsInt: exception should have been thrown!\n"; - } else { - if ($value != 0) { - var_dump($value); - } - } + echo "in fetchCharAsInt: exception should have been thrown!\n"; } catch (PdoException $e) { // The (n)varchar field - expect the outOfRange error if (strpos($e->getMessage(), $outOfRange) === false) { @@ -194,6 +215,35 @@ function fetchAsNumerics($conn, $tableName, $inputs) } } +function fetchNumbers($conn, $tableName, $inputs) +{ + // Fetch integers and floats as numbers, not strings + try { + $query = "SELECT c2, c3, c4 FROM $tableName"; + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + $stmt = $conn->prepare($query, array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED)); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, true); + $stmt->execute(); + + $row = $stmt->fetch(PDO::FETCH_NUM); + if ($row[0] !== intval($inputs[1])) { + var_dump($row[0]); + } + $expected = floatval($inputs[2]); + if (!compareFloats($expected, $row[1])) { + echo "in fetchNumbers: expected $expected but got: "; + var_dump($row[1]); + } + if ($row[2] !== $inputs[3]) { + var_dump($row[2]); + } + } catch (PdoException $e) { + echo "Caught exception in fetchAsNumerics:\n"; + echo $e->getMessage() . PHP_EOL; + } +} + try { $conn = connect(); $conn->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); @@ -226,11 +276,13 @@ try { unset($stmt); // Starting fetching using client buffers + fetchAsChar($conn, $tableName, $inputs); fetchAsUTF8($conn, $tableName, $inputs); fetchArray($conn, $tableName, $inputs); fetchBinaryAsNumber($conn, $tableName, $inputs); fetchBinaryAsBinary($conn, $tableName, $inputs); fetchAsNumerics($conn, $tableName, $inputs); + fetchNumbers($conn, $tableName, $inputs); // dropTable($conn, $tableName); echo "Done\n"; diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt index a14d6ddec..6115965ca 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetch_orientation.phpt @@ -8,20 +8,17 @@ PHPT_EXEC=true PDO::CURSOR_SCROLL); + if ($buffered) { + $options = array(PDO::ATTR_CURSOR=>PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE=>PDO::SQLSRV_CURSOR_BUFFERED); + } - // Prepare test table - $tableName = "pdo_test_table"; - createTable($conn1, $tableName, array(new ColumnMeta("int", "id", "NOT NULL PRIMARY KEY", "none"), "val" => "varchar(10)")); - insertRow($conn1, $tableName, array("id" => 1, "val" => "A")); - insertRow($conn1, $tableName, array("id" => 2, "val" => "B")); - insertRow($conn1, $tableName, array("id" => 3, "val" => "C")); + $stmt1 = $conn->prepare($tsql, $options); + $stmt1->execute(); - // Query table and retrieve data - $stmt1 = $conn1->prepare( "SELECT val FROM $tableName ORDER BY id", array( PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL )); - - $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_LAST ); if( $row[ 'val' ] != "C" ) { throw new Exception( "Not C" ); @@ -38,7 +35,7 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } - + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_LAST ); if( $row[ 'val' ] != "C" ) { @@ -57,7 +54,7 @@ try { throw new Exception( "Not false" ); } - $stmt1->execute(); + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_LAST ); if( $row[ 'val' ] != "C" ) { throw new Exception( "Not C" ); @@ -66,8 +63,8 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } - - $stmt1->execute(); + + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_LAST ); if( $row[ 'val' ] != "C" ) { throw new Exception( "Not C" ); @@ -76,7 +73,7 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } - + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_ABS, 2 ); if( $row[ 'val' ] != "C" ) { @@ -138,7 +135,7 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } - + $stmt1->execute(); $row = $stmt1->fetch( PDO::FETCH_ASSOC, PDO::FETCH_ORI_FIRST ); if( $row[ 'val' ] != "A" ) { @@ -148,10 +145,26 @@ try { if ($row !== false) { throw new Exception( "Not false" ); } + + unset($stmt1); +} + +try { + $conn1 = connect(); + + // Prepare test table + $tableName = "pdo_test_table"; + createTable($conn1, $tableName, array(new ColumnMeta("int", "id", "NOT NULL PRIMARY KEY", "none"), "val" => "varchar(10)")); + insertRow($conn1, $tableName, array("id" => 1, "val" => "A")); + insertRow($conn1, $tableName, array("id" => 2, "val" => "B")); + insertRow($conn1, $tableName, array("id" => 3, "val" => "C")); + + // Query table and retrieve data + runTests($conn1, $tableName, false); + runTests($conn1, $tableName, true); // Cleanup dropTable($conn1, $tableName); - unset($stmt1); unset($conn1); echo "Test 'PDO Statement - Fetch Scrollable' completed successfully.\n"; diff --git a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt index 3351f201e..b2181be76 100644 --- a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt +++ b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt @@ -20,6 +20,41 @@ function compareFloats($expected, $actual) return ($diff < $epsilon); } +function fetchAsChar($conn, $tableName, $inputs) +{ + $query = "SELECT c_varbinary, c_int, c_float, c_decimal, c_datetime2, c_varchar FROM $tableName"; + + $stmt = sqlsrv_query($conn, $query, array(), array("Scrollable"=>SQLSRV_CURSOR_CLIENT_BUFFERED)); + if (!$stmt) { + fatalError("In fetchAsChar: failed to run query!"); + } + + if (sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC) === false) { + fatalError("In fetchAsChar: failed to fetch the row from $tableName!"); + } + + // Fetch all fields as strings - no conversion + for ($i = 0; $i < count($inputs) - 1; $i++) { + $f = sqlsrv_get_field($stmt, $i, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); + if ($i == 0) { + if ($inputs[$i] !== hex2bin($f)) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump(hex2bin($f)); + } + } elseif ($i == 2) { + if (!compareFloats(floatval($inputs[$i]), floatval($f))) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump($f); + } + } else { + if ($f !== $inputs[$i]) { + echo "In fetchAsChar ($i): expected $inputs[$i]\n"; + var_dump($f); + } + } + } +} + function fetchAsUTF8($conn, $tableName, $inputs) { $query = "SELECT * FROM $tableName"; @@ -132,16 +167,9 @@ function fetchAsFloats($conn, $tableName, $inputs) } } else { // The char fields will get errors too - // TODO 11297: fix this part outside Windows later - if (isWindows()) { - if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { - var_dump($f); - fatalError("in fetchAsFloats: expected $outOfRange for column $i\n"); - } - } else { - if ($f != 0.0) { - var_dump($f); - } + if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { + var_dump($f); + fatalError("in fetchAsFloats: expected $outOfRange for column $i\n"); } } } @@ -179,16 +207,9 @@ function fetchAsInts($conn, $tableName, $inputs) } } elseif ($i >= 5) { // The char fields will get errors too - // TODO 11297: fix this part outside Windows later - if (isWindows()) { - if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { - var_dump($f); - fatalError("in fetchAsInts: expected $outOfRange for column $i\n"); - } - } else { - if ($f != 0) { - var_dump($f); - } + if (strpos(sqlsrv_errors()[0]['message'], $outOfRange) === false) { + var_dump($f); + fatalError("in fetchAsInts: expected $outOfRange for column $i\n"); } } else { $expected = floor($inputs[$i]); @@ -284,6 +305,7 @@ if ($stmt) { } // Starting fetching using client buffers +fetchAsChar($conn, $tableName, $inputs); fetchAsUTF8($conn, $tableName, $inputs); fetchArray($conn, $tableName, $inputs); fetchAsFloats($conn, $tableName, $inputs); From 64e8ddc855ae267e86fa7b4ae162666a1e13a01b Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 7 Jul 2020 11:31:16 -0700 Subject: [PATCH 218/249] Use VS2019 for php 8 in Windows in the build scripts (#1149) --- buildscripts/buildtools.py | 11 ++--- .../msdn_pdoStatement_bindColumn.phpt | 2 +- .../msdn_pdoStatement_bindParam.phpt | 2 +- .../msdn_pdoStatement_bindParam_2.phpt | 2 +- .../msdn_pdoStatement_bindParam_3.phpt | 2 +- .../msdn_pdoStatement_bindValue.phpt | 2 +- .../msdn_pdoStatement_closeCursor.phpt | 2 +- .../msdn_pdoStatement_columnCount.phpt | 2 +- .../msdn_pdoStatement_debugDumpParams.phpt | 2 +- .../msdn_pdoStatement_errorCode.phpt | 7 +-- .../msdn_pdoStatement_errorInfo.phpt | 7 +-- .../pdo_sqlsrv/msdn_pdoStatement_execute.phpt | 2 +- .../pdo_sqlsrv/msdn_pdoStatement_fetch.phpt | 2 +- .../msdn_pdoStatement_fetchAll.phpt | 2 +- .../msdn_pdoStatement_fetchColumn.phpt | 2 +- .../msdn_pdoStatement_fetchObject.phpt | 2 +- ...n_pdoStatement_fetchObject_class_name.phpt | 2 +- .../msdn_pdoStatement_getColumnMeta.phpt | 2 +- .../msdn_pdoStatement_nextRowset.phpt | 2 +- .../msdn_pdoStatement_rowCount.phpt | 2 +- .../msdn_pdoStatement_setAttribute.phpt | 2 +- .../msdn_pdoStatement_setFetchMode.phpt | 2 +- .../pdo_sqlsrv/msdn_pdo_beginTransaction.phpt | 2 +- test/bvt/pdo_sqlsrv/msdn_pdo_construct.phpt | 2 +- test/bvt/pdo_sqlsrv/msdn_pdo_construct_2.phpt | 2 +- .../pdo_sqlsrv/msdn_pdo_construct_MARS.phpt | 2 +- test/bvt/pdo_sqlsrv/msdn_pdo_errorCode.phpt | 5 +- test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt | 3 +- test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt | 2 +- .../bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt | 7 +-- .../msdn_pdo_getAvailableDrivers.phpt | 2 +- test/bvt/pdo_sqlsrv/msdn_pdo_prepare.phpt | 2 +- test/bvt/pdo_sqlsrv/msdn_pdo_prepare_2.phpt | 2 +- .../pdo_sqlsrv/msdn_pdo_prepare_cursor.phpt | 2 +- test/bvt/pdo_sqlsrv/msdn_pdo_query.phpt | 2 +- test/bvt/pdo_sqlsrv/msdn_pdo_quote.phpt | 2 +- .../bvt/pdo_sqlsrv/msdn_pdo_setAttribute.phpt | 5 +- .../msdn_pdo_setAttribute_direct_query.phpt | 47 ++++++++++--------- .../pdo_bindParam_inout_double.phpt | 2 +- .../pdo_bindParam_inout_integer.phpt | 2 +- .../pdo_bindParam_inout_string.phpt | 2 +- test/bvt/pdo_sqlsrv/skipif.inc | 4 ++ .../sqlsrv/msdn_sqlsrv_begin_transaction.phpt | 2 +- .../msdn_sqlsrv_begin_transaction_2.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_cancel.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_cancel_1.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_close.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_commit.phpt | 3 +- test/bvt/sqlsrv/msdn_sqlsrv_configure.phpt | 3 +- test/bvt/sqlsrv/msdn_sqlsrv_configure_2.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_connect.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_connect_MARS.phpt | 2 +- ...dn_sqlsrv_connect_returnDateAsStrings.phpt | 2 +- ...srv_connect_returnDatesAsStrings_utf8.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_connect_utf8.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_date_format.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_errors.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_execute.phpt | 2 +- .../sqlsrv/msdn_sqlsrv_execute_datetime.phpt | 3 +- .../sqlsrv/msdn_sqlsrv_execute_string.phpt | 3 +- test/bvt/sqlsrv/msdn_sqlsrv_fetch.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_fetch_array.phpt | 2 +- .../bvt/sqlsrv/msdn_sqlsrv_fetch_array_2.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_fetch_object.phpt | 3 +- .../sqlsrv/msdn_sqlsrv_fetch_object_2.phpt | 2 +- .../sqlsrv/msdn_sqlsrv_field_metadata.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_free_stmt.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_get_field.phpt | 2 +- .../sqlsrv/msdn_sqlsrv_get_field_stream.phpt | 2 +- .../msdn_sqlsrv_get_field_stream_char.phpt | 2 +- .../msdn_sqlsrv_get_field_string_utf8.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_has_rows.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_next_result.phpt | 2 +- .../bvt/sqlsrv/msdn_sqlsrv_next_result_2.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_num_fields.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_num_rows.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_num_rows_2.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_prepare.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_prepare_2.phpt | 2 +- .../msdn_sqlsrv_prepare_bind_param.phpt | 2 +- ...dn_sqlsrv_prepare_scrollable_buffered.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_query.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_query_2.phpt | 2 +- .../sqlsrv/msdn_sqlsrv_query_bind_param.phpt | 2 +- .../sqlsrv/msdn_sqlsrv_query_param_inout.phpt | 2 +- .../sqlsrv/msdn_sqlsrv_query_param_out.phpt | 2 +- .../sqlsrv/msdn_sqlsrv_query_scrollable.phpt | 2 +- ...msdn_sqlsrv_query_scrollable_buffered.phpt | 2 +- .../bvt/sqlsrv/msdn_sqlsrv_query_sqltype.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_query_stream.phpt | 1 + test/bvt/sqlsrv/msdn_sqlsrv_query_utf8.phpt | 2 +- test/bvt/sqlsrv/msdn_sqlsrv_rollback.phpt | 2 +- .../bvt/sqlsrv/msdn_sqlsrv_rows_affected.phpt | 2 +- .../sqlsrv/msdn_sqlsrv_send_stream_data.phpt | 2 +- ...stream_data_no_sendStreamParamsAtExec.phpt | 1 + test/bvt/sqlsrv/msdn_sqlsrv_server_info.phpt | 2 +- test/bvt/sqlsrv/skipif.inc | 7 +++ test/bvt/sqlsrv/sqlsrv_param_inout.phpt | 2 +- 99 files changed, 148 insertions(+), 136 deletions(-) create mode 100644 test/bvt/pdo_sqlsrv/skipif.inc create mode 100644 test/bvt/sqlsrv/skipif.inc diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index 884ceb56e..b0ed54628 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -101,15 +101,10 @@ def determine_compiler(self, sdk_dir, vs_ver): def compiler_version(self, sdk_dir): """Return the appropriate compiler version based on PHP version.""" if self.vc == '': - VC = 'vc14' + VC = 'vc15' version = self.version_label() - if version >= '72': # Compiler version for PHP 7.2 or above - VC = 'vc15' - if version == '74': - # Compiler version for PHP 7.4 or above - # Can be compiled using VS 2017 or VS 2019 - print('Checking compiler versions...') - VC = self.determine_compiler(sdk_dir, 15) + if version == '8': # Compiler version for PHP 8.0 or above + VC = 'vs16' self.vc = VC print('Compiler: ' + self.vc) return self.vc diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindColumn.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindColumn.phpt index 3904d76ee..c0e1c86a9 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindColumn.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_bindColumn.phpt @@ -1,7 +1,7 @@ --TEST-- a variable bound to a column in a result set --SKIPIF-- - + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $stmt = $conn->prepare('SELECT * FROM Person.Addressx'); $stmt->execute(); @@ -13,8 +14,8 @@ echo "Error Code: "; print $stmt->errorCode(); // free the statement and connection -$stmt=null; -$conn=null; +unset($stmt); +unset($conn); ?> --EXPECT-- Error Code: 42S02 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt index 5c958b6ec..b827ff1ea 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_errorInfo.phpt @@ -1,19 +1,20 @@ --TEST-- reports the error info of a SQL statement with a mispelled table name --SKIPIF-- - + --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $stmt = $conn->prepare('SELECT * FROM Person.Addressx'); $stmt->execute(); print_r ($stmt->errorInfo()); // free the statement and connection -$stmt=null; -$conn=null; +unset($stmt); +unset($conn); ?> --EXPECTREGEX-- Array diff --git a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_execute.phpt b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_execute.phpt index b43bd1c79..12169d893 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdoStatement_execute.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdoStatement_execute.phpt @@ -1,7 +1,7 @@ --TEST-- Executes a statement --SKIPIF-- - + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $query = "SELECT * FROM Person.Address where Cityx = 'Essen'"; $conn->query($query); print $conn->errorCode(); //free the connection -$conn=null; +unset($conn); ?> --EXPECT-- 42S22 \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt index 62c112f99..3b7878870 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_errorInfo.phpt @@ -1,11 +1,12 @@ --TEST-- reports the error info of querying a misspelled column --SKIPIF-- - + --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $query = "SELECT * FROM Person.Address where Cityx = 'Essen'"; $conn->query($query); diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt index 76ede8e85..2f0f9b124 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_exec.phpt @@ -1,7 +1,7 @@ --TEST-- execute a delete and reports how many rows were deleted --SKIPIF-- - + --FILE-- --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $attributes1 = array( "ERRMODE" ); foreach ( $attributes1 as $val ) { @@ -13,7 +14,7 @@ foreach ( $attributes1 as $val ) { var_dump ($conn->getAttribute( constant( "PDO::ATTR_$val" ) )); } -$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); +$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $attributes1 = array( "ERRMODE" ); foreach ( $attributes1 as $val ) { @@ -25,7 +26,7 @@ foreach ( $attributes1 as $val ) { print_r($conn->getAttribute( PDO::ATTR_CLIENT_VERSION )); //free the connection -$conn=null; +unset($conn); ?> --EXPECTREGEX-- PDO::ATTR_ERRMODE: int\(0\) diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_getAvailableDrivers.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_getAvailableDrivers.phpt index 7db00eea3..672625b39 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdo_getAvailableDrivers.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_getAvailableDrivers.phpt @@ -1,7 +1,7 @@ --TEST-- check if sqlsrv is in the array of available PDO drivers --SKIPIF-- - + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $attributes1 = array( "ERRMODE" ); foreach ( $attributes1 as $val ) { @@ -22,7 +23,7 @@ sets to PDO::ATTR_ERRMODE } //free the connection - $conn=null; + unset($conn); ?> --EXPECT-- PDO::ATTR_ERRMODE: int(0) diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute_direct_query.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute_direct_query.phpt index ae2ca1b12..f78c59ba2 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute_direct_query.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_setAttribute_direct_query.phpt @@ -1,42 +1,45 @@ --TEST-- sets to PDO::SQLSRV_ATTR_DIRECT_QUERY --SKIPIF-- - + --FILE-- setAttribute(constant('PDO::SQLSRV_ATTR_DIRECT_QUERY'), true); + require('connect.inc'); + $conn = new PDO("sqlsrv:Server=$server", "$uid", "$pwd"); + $conn->setAttribute(constant('PDO::SQLSRV_ATTR_DIRECT_QUERY'), true); - $stmt1 = $conn->query("DROP TABLE #php_test_table"); + $tableName = 'pdo_direct_query'; + $tsql = "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'" . $tableName . "') AND type in (N'U')) DROP TABLE $tableName"; - $stmt2 = $conn->query("CREATE TABLE #php_test_table ([c1_int] int, [c2_int] int)"); + $stmt1 = $conn->query($tsql); + $stmt2 = $conn->query("CREATE TABLE $tableName ([c1_int] int, [c2_int] int)"); - $v1 = 1; - $v2 = 2; + $v1 = 1; + $v2 = 2; - $stmt3 = $conn->prepare("INSERT INTO #php_test_table (c1_int, c2_int) VALUES (:var1, :var2)"); + $stmt3 = $conn->prepare("INSERT INTO $tableName (c1_int, c2_int) VALUES (:var1, :var2)"); - if ($stmt3) { + if ($stmt3) { $stmt3->bindValue(1, $v1); $stmt3->bindValue(2, $v2); - if ($stmt3->execute()) + if ($stmt3->execute()) { echo "Execution succeeded\n"; - else + } else { echo "Execution failed\n"; - } - else + } + } else { var_dump($conn->errorInfo()); + } - $stmt4 = $conn->query("DROP TABLE #php_test_table"); + $stmt4 = $conn->query("DROP TABLE $tableName"); - // free the statements and connection - $stmt1=null; - $stmt2=null; - $stmt3=null; - $stmt4=null; - $conn=null; - ?> + // free the statements and connection + unset($stmt1); + unset($stmt2); + unset($stmt3); + unset($stmt4); + unset($conn); +?> --EXPECT-- Execution succeeded \ No newline at end of file diff --git a/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_double.phpt b/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_double.phpt index abcbdac2d..f82871f3b 100644 --- a/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_double.phpt +++ b/test/bvt/pdo_sqlsrv/pdo_bindParam_inout_double.phpt @@ -1,7 +1,7 @@ --TEST-- call a stored procedure and retrieve the errorNumber that is returned --SKIPIF-- - + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- + --FILE-- + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- + --FILE-- + --FILE-- --FILE-- --FILE-- --FILE-- + --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- \ No newline at end of file diff --git a/test/bvt/sqlsrv/sqlsrv_param_inout.phpt b/test/bvt/sqlsrv/sqlsrv_param_inout.phpt index 603f0ab57..5bc3e2a57 100644 --- a/test/bvt/sqlsrv/sqlsrv_param_inout.phpt +++ b/test/bvt/sqlsrv/sqlsrv_param_inout.phpt @@ -1,7 +1,7 @@ --TEST-- call a stored procedure (SQLSRV Driver) and retrieve the errorNumber that is returned --SKIPIF-- - + --FILE-- Date: Wed, 8 Jul 2020 19:45:00 -0700 Subject: [PATCH 219/249] Updated functional tests for php8 (#1150) --- buildscripts/buildtools.py | 8 +- test/functional/pdo_sqlsrv/PDO95_Extend2.phpt | 5 +- .../pdo_065_construct_prefetch.phpt | 13 +- test/functional/pdo_sqlsrv/pdo_construct.phpt | 15 +- .../pdo_sqlsrv/pdo_construct_attr.phpt | 2 + .../pdo_sqlsrv/pdostatement_fetchAll.phpt | 29 ++-- .../pdo_sqlsrv/pdostatement_fetch_style.phpt | 22 ++- test/functional/sqlsrv/0022.phpt | 37 +++-- test/functional/sqlsrv/TC24_Close.phpt | 61 +++++--- test/functional/sqlsrv/TC36_Close.phpt | 40 +++-- .../sqlsrv/sqlsrv_buffered_fetch_types.phpt | 2 +- test/functional/sqlsrv/sqlsrv_close.phpt | 29 +++- .../functional/sqlsrv/sqlsrv_close_twice.phpt | 17 +- test/functional/sqlsrv/sqlsrv_configure.phpt | 74 +++++---- test/functional/sqlsrv/sqlsrv_errors.phpt | 145 ++++++++++++------ .../sqlsrv_fetch_field_twice_data_types.phpt | 4 +- .../sqlsrv_input_param_unknown_encoding.phpt | 76 ++++++--- test/functional/sqlsrv/test_conn_execute.phpt | 41 +++-- test/functional/sqlsrv/test_stream.phpt | 22 ++- 19 files changed, 445 insertions(+), 197 deletions(-) diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index b0ed54628..4d9bde697 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -50,12 +50,8 @@ def major_version(self): def version_label(self): """Return the version label based on the PHP version.""" - major_ver = self.major_version() - - if major_ver[2] == '0': - version = major_ver[0] - else: - version = major_ver[0] + major_ver[2] + major_ver = self.major_version() + version = major_ver[0] + major_ver[2] return version def driver_name(self, driver, suffix): diff --git a/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt b/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt index 5175f6a2d..34b573077 100644 --- a/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt +++ b/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt @@ -20,6 +20,9 @@ function Extend() // simply use $databaseName from MsSetup.inc to facilitate testing in Azure, // which does not support switching databases $conn2 = new ExPDO("sqlsrv:Server=$server;Database=$databaseName", $uid, $pwd); + // With PHP 8.0 the default is PDO::ERRMODE_EXCEPTION rather than PDO::ERRMODE_SILENT + $conn2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + DropTable($conn2, "tmp_table"); $conn2->exec("CREATE TABLE tmp_table (id INT)"); $conn2->exec("INSERT INTO tmp_table (id) VALUES (1), (2)"); @@ -52,7 +55,7 @@ class ExPDO extends PDO return (call_user_func_array(array($this, 'parent::exec'), $args)); } - public function query() + public function query(string $statement) { $this->protocol(); $args = func_get_args(); diff --git a/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt b/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt index 88f47495f..2adbf9419 100644 --- a/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt +++ b/test/functional/pdo_sqlsrv/pdo_065_construct_prefetch.phpt @@ -12,13 +12,22 @@ try { echo "Testing a connection with ATTR_PREFETCH before ERRMODE_EXCEPTION...\n"; $dsn = getDSN($server, $databaseName, $driver); - $attr = array(PDO::ATTR_PREFETCH => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + // With PHP 8.0 the default is PDO::ERRMODE_EXCEPTION rather than PDO::ERRMODE_SILENT + if (PHP_MAJOR_VERSION == 8) { + $attr = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT, PDO::ATTR_PREFETCH => true); + } else { + $attr = array(PDO::ATTR_PREFETCH => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + } $conn = new PDO($dsn, $uid, $pwd, $attr); echo "Error from unsupported attribute (ATTR_PREFETCH) is silenced\n\n"; unset($conn); echo "Testing a connection with ATTR_PREFETCH after ERRMODE_EXCEPTION...\n"; - $attr = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_PREFETCH => true); + if (PHP_MAJOR_VERSION == 8) { + $attr = array(PDO::ATTR_PREFETCH => true); + } else { + $attr = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_PREFETCH => true); + } $conn = new PDO($dsn, $uid, $pwd, $attr); //free the connection unset($conn); diff --git a/test/functional/pdo_sqlsrv/pdo_construct.phpt b/test/functional/pdo_sqlsrv/pdo_construct.phpt index 6521f23de..eba2c640e 100644 --- a/test/functional/pdo_sqlsrv/pdo_construct.phpt +++ b/test/functional/pdo_sqlsrv/pdo_construct.phpt @@ -7,11 +7,13 @@ Test PDO::__Construct by passing connection options and attributes. require_once("MsCommon_mid-refactor.inc"); try { + // With PHP 8.0 the default is PDO::ERRMODE_EXCEPTION rather than PDO::ERRMODE_SILENT + // With PHP 7.X the ATTR_ERRMODE must be set before the unsupported attribute(s) to have any effect $attr = array( PDO::SQLSRV_ATTR_ENCODING => 3, PDO::ATTR_CASE => 2, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_PREFETCH => false, PDO::ATTR_TIMEOUT => 35, - PDO::ATTR_ERRMODE => 2, PDO::ATTR_STRINGIFY_FETCHES => true, PDO::SQLSRV_ATTR_DIRECT_QUERY => true, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, @@ -33,6 +35,7 @@ try { "TransactionIsolation = " . PDO::SQLSRV_TXN_READ_UNCOMMITTED . ";" . "TrustServerCertificate = false;" . "WSID = whatever;"; + $conn = connect($dsn, $attr); echo "Test Successful\n"; } catch (PDOException $e) { @@ -41,4 +44,12 @@ try { } ?> --EXPECT-- -Test Successful +array(3) { + [0]=> + string(5) "IMSSP" + [1]=> + int(-38) + [2]=> + string(58) "An unsupported attribute was designated on the PDO object." +} +Test Successful \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_construct_attr.phpt b/test/functional/pdo_sqlsrv/pdo_construct_attr.phpt index 92dfc4757..cba24eab0 100644 --- a/test/functional/pdo_sqlsrv/pdo_construct_attr.phpt +++ b/test/functional/pdo_sqlsrv/pdo_construct_attr.phpt @@ -7,8 +7,10 @@ Test PDO::__Construct by passing different connection attributes require_once("MsCommon_mid-refactor.inc"); try { + // With PHP 8.0 the default is PDO::ERRMODE_EXCEPTION rather than PDO::ERRMODE_SILENT $attr = array( PDO::SQLSRV_ATTR_ENCODING => 3, PDO::ATTR_CASE => 2, + PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT, PDO::ATTR_PREFETCH => false, PDO::ATTR_TIMEOUT => 35, PDO::ATTR_STRINGIFY_FETCHES => true, diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt index 9187df89d..2bbd79eb6 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt @@ -77,11 +77,25 @@ function fetchAllInvalid($conn, $tbname) $stmt = $conn->query("Select * from $tbname"); try { $result = $stmt->fetchAll(PDO::FETCH_UNKNOWN); - } catch (PDOException $err) { - print_r($err); + } catch (PDOException $ex) { + print_r($ex); + } catch (Error $err) { + $expected = (PHP_MAJOR_VERSION == 8) ? 'PDO::FETCH_UNKNOWN' : 'FETCH_UNKNOWN'; + $message = "Undefined class constant '$expected'"; + if ($err->getMessage() !== $message) { + echo $err->getMessage() . PHP_EOL; + } } } +// When testing with PHP 8.0 it throws a TypeError instead of a warning. Thus implement a custom +// warning handler such that with PHP 7.x the warning would be handled to throw a TypeError. +// Sometimes the error messages from PHP 8.0 may be different and have to be handled differently. +function warningHandler($errno, $errstr) +{ + throw new Error($errstr); +} + try { $db = connect(); $tbname1 = "PDO_MainTypes"; @@ -105,7 +119,10 @@ try { echo "Test_8 : FETCH_CLASS :\n"; fetchAllClass($db, $tbname1); echo "Test_9 : FETCH_INVALID :\n"; + + set_error_handler("warningHandler", E_WARNING); fetchAllInvalid($db, $tbname1); + restore_error_handler(); dropTable($db, $tbname1); dropTable($db, $tbname2); @@ -427,10 +444,4 @@ string(10) "STRINGCOL2" string(10) "STRINGCOL2" string(%d) "222.222%S" string(431) " 2 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." -Test_9 : FETCH_INVALID : - -Fatal error: Uncaught Error: Undefined class constant 'FETCH_UNKNOWN' in %s:%x -Stack trace: -#0 %s: fetchAllInvalid(%S) -#1 {main} - thrown in %s on line %x +Test_9 : FETCH_INVALID : \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt index e969325ef..4221ad0a0 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt @@ -7,6 +7,14 @@ Test the PDOStatement::fetch() method with different fetch styles. require_once("MsCommon_mid-refactor.inc"); require_once("MsData_PDO_AllTypes.inc"); +// When testing with PHP 8.0 it throws a TypeError instead of a warning. Thus implement a custom +// warning handler such that with PHP 7.x the warning would be handled to throw a TypeError. +// Sometimes the error messages from PHP 8.0 may be different and have to be handled differently. +function warningHandler($errno, $errstr) +{ + throw new Error($errstr); +} + function fetchWithStyle($conn, $tbname, $style) { $stmt = $conn->query("SELECT * FROM $tbname"); @@ -68,11 +76,20 @@ function fetchWithStyle($conn, $tbname, $style) } case "PDO::FETCH_INVALID": { + set_error_handler("warningHandler", E_WARNING); try { $result = $stmt->fetch(PDO::FETCH_UNKNOWN); } catch (PDOException $err) { print_r($err); + } catch (Error $err) { + $expected = (PHP_MAJOR_VERSION == 8) ? 'PDO::FETCH_UNKNOWN' : 'FETCH_UNKNOWN'; + $message = "Undefined class constant '$expected'"; + if ($err->getMessage() !== $message) { + echo $err->getMessage() . PHP_EOL; + } } + restore_error_handler(); + break; } @@ -255,8 +272,3 @@ string(%d) "111.111%S" string(431) " 1 This is a really large string used to test certain large data types like xml data type. The length of this string is greater than 256 to correctly test a large data type. This is currently used by atleast varchar type and by xml type. The fetch tests are the primary consumer of this string to validate that fetch on large types work fine. The length of this string as counted in terms of number of characters is 417." Test_9 : FETCH_INVALID : -Fatal error: Uncaught Error: Undefined class constant 'FETCH_UNKNOWN' in %s:%x -Stack trace: -#0 %s: fetchWithStyle(%S) -#1 {main} - thrown in %s on line %x diff --git a/test/functional/sqlsrv/0022.phpt b/test/functional/sqlsrv/0022.phpt index 916907490..cecc6f4a0 100644 --- a/test/functional/sqlsrv/0022.phpt +++ b/test/functional/sqlsrv/0022.phpt @@ -4,11 +4,21 @@ zombied streams after sqlsrv_stmt_cancel. --FILE-- getMessage() . PHP_EOL; } sqlsrv_free_stmt( $stmt ); @@ -44,9 +59,13 @@ zombied streams after sqlsrv_stmt_cancel. sqlsrv_fetch( $stmt ); $stream = sqlsrv_get_field( $stmt, 0, SQLSRV_PHPTYPE_STREAM("binary")); sqlsrv_cancel( $stmt ); - while( !feof( $stream ) && is_resource($stream) ) { - $str = fread( $stream, 80 ); - echo "$str\n"; + try { + while( !feof( $stream ) && is_resource($stream) ) { + $str = fread( $stream, 80 ); + echo "$str\n"; + } + } catch (TypeError $e) { + echo $e->getMessage() . PHP_EOL; } sqlsrv_free_stmt( $stmt ); @@ -64,7 +83,5 @@ Baby and Howlin Wolfs How Many More Times into near-cartoon parodies, the band a lso hinted at things to come with the manic Communication Breakdown and the lumb ering set stopper Dazed and Confused. \--Billy Altman\<\/I\> Source: Amazon.com essential recording - Most critics complain \Back in Black\< - -Warning: feof\(\): supplied resource is not a valid stream resource in .+(\/|\\)0022\.php on line [0-9]+ - -Warning: feof\(\): supplied resource is not a valid stream resource in .+(\/|\\)0022\.php on line [0-9]+ +feof\(\): supplied resource is not a valid stream resource +feof\(\): supplied resource is not a valid stream resource diff --git a/test/functional/sqlsrv/TC24_Close.phpt b/test/functional/sqlsrv/TC24_Close.phpt index 6ff584563..fad83c794 100644 --- a/test/functional/sqlsrv/TC24_Close.phpt +++ b/test/functional/sqlsrv/TC24_Close.phpt @@ -11,6 +11,13 @@ PHPT_EXEC=true getMessage() . PHP_EOL; } - // Invalid Query - $stmt1 = sqlsrv_query($conn1, "SELECT * FROM [$tableName]"); - if ($stmt1) { - die("Select query should fail when connection is closed"); + try { + // Invalid Query + $stmt1 = sqlsrv_query($conn1, "SELECT * FROM [$tableName]"); + if ($stmt1) { + die("Select query should fail when connection is closed"); + } + } catch (TypeError $e) { + echo $e->getMessage() . PHP_EOL; } - // Invalid Statement - $conn2 = AE\connect(); - $stmt2 = AE\selectFromTable($conn2, $tableName); - sqlsrv_close($conn2); - if (sqlsrv_fetch($stmt2)) { - die("Fetch should fail when connection is closed"); + try { + // Invalid Statement + $conn2 = AE\connect(); + $stmt2 = AE\selectFromTable($conn2, $tableName); + sqlsrv_close($conn2); + if (sqlsrv_fetch($stmt2)) { + die("Fetch should fail when connection is closed"); + } + } catch (TypeError $e) { + echo $e->getMessage() . PHP_EOL; } $conn3 = AE\connect(); @@ -53,17 +72,15 @@ function connectionClose() } try { + set_error_handler("warningHandler", E_WARNING); connectionClose(); } catch (Exception $e) { echo $e->getMessage(); } ?> ---EXPECTREGEX-- - -Warning: sqlsrv_close\(\): supplied resource is not a valid ss_sqlsrv_conn resource in .*TC24_Close.php on line 18 - -Warning: sqlsrv_query\(\): supplied resource is not a valid ss_sqlsrv_conn resource in .*TC24_Close.php on line 25 - -Warning: sqlsrv_fetch\(\): supplied resource is not a valid ss_sqlsrv_stmt resource in .*TC24_Close.php on line 34 +--EXPECT-- +sqlsrv_close(): supplied resource is not a valid ss_sqlsrv_conn resource +sqlsrv_query(): supplied resource is not a valid ss_sqlsrv_conn resource +sqlsrv_fetch(): supplied resource is not a valid ss_sqlsrv_stmt resource Test "Connection - Close" completed successfully. diff --git a/test/functional/sqlsrv/TC36_Close.phpt b/test/functional/sqlsrv/TC36_Close.phpt index 7df34c79a..e0da16885 100644 --- a/test/functional/sqlsrv/TC36_Close.phpt +++ b/test/functional/sqlsrv/TC36_Close.phpt @@ -12,6 +12,13 @@ PHPT_EXEC=true getMessage() . PHP_EOL; } trace("\nClosing the statement again (no error expected) ...\n"); - - if (sqlsrv_free_stmt($stmt1) === false) { - fatalError("A statement can be closed multiple times."); + try { + if (sqlsrv_free_stmt($stmt1) === false) { + fatalError("A statement can be closed multiple times."); + } + } catch (TypeError $e) { + echo $e->getMessage() . PHP_EOL; } - dropTable($conn1, $tableName); sqlsrv_close($conn1); @@ -49,15 +62,14 @@ function close() } try { + set_error_handler("warningHandler", E_WARNING); close(); } catch (Exception $e) { - echo $e->getMessage(); + echo $e->getMessage() . PHP_EOL; } ?> ---EXPECTREGEX-- - -Warning: sqlsrv_num_fields\(\): supplied resource is not a valid ss_sqlsrv_stmt resource in .*TC36_Close.php on line 21 - -Warning: sqlsrv_free_stmt\(\): supplied resource is not a valid ss_sqlsrv_stmt resource in .*TC36_Close.php on line 29 +--EXPECT-- +sqlsrv_num_fields(): supplied resource is not a valid ss_sqlsrv_stmt resource +sqlsrv_free_stmt(): supplied resource is not a valid ss_sqlsrv_stmt resource Test "Statement - Close" completed successfully. diff --git a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt index b2181be76..905976705 100644 --- a/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt +++ b/test/functional/sqlsrv/sqlsrv_buffered_fetch_types.phpt @@ -212,7 +212,7 @@ function fetchAsInts($conn, $tableName, $inputs) fatalError("in fetchAsInts: expected $outOfRange for column $i\n"); } } else { - $expected = floor($inputs[$i]); + $expected = floor(floatval($inputs[$i])); if ($f != $expected) { echo "in fetchAsInts: for column $i expected $expected but got: "; var_dump($f); diff --git a/test/functional/sqlsrv/sqlsrv_close.phpt b/test/functional/sqlsrv/sqlsrv_close.phpt index 2c8e2d10e..0bcdbd7b9 100644 --- a/test/functional/sqlsrv/sqlsrv_close.phpt +++ b/test/functional/sqlsrv/sqlsrv_close.phpt @@ -4,6 +4,15 @@ using an already closed connection. --FILE-- /Bhå,Ö~ßßbOUZªîÄ:*öuä_AühåaðCuUÄ +aöoÄУÄÐöüoA.ð*ÄZBªß£åboÖ@Ö©Ã~ß:ðð~ö|Ъz>C~bßB:b|.öoB>ÐCåýüuåbb+hÃB>*.h_/ß ©ãßâ~î*ÐÖÜ:£BUaÖ<:Ü:ZUo£Zä© O~©,z,Ch ¢.£ävä,:ßhü>@aüAå,~©A ÐUA|OCb Ãhå/ãbh~a+Ör<ÐÃߣ|Ã+/a@:zzÃ,ãÄÃZ.zýý/ý.ÐÖ<Ö+h~AÐðaCob/~ZU>zÃöCÖîÖå+ß ðü:¢h.ãß©üar/ö CuÐbÄÄÖöÃÐÜ£Ðã¢UuU,ýb£@ðaÐ<|@Z<@o.ß@ozb¢OaÜî*ÜCöbÄßüAöZ:ÄßbýuO/,Uv>åzA£OÃîOÄîzÖCCÐî_bC/Ub|rvý@ªZ*£@@ýbOåo:äZ:üZÖh¢¢,>O£r@UßÐß,Oozî+UßUCb|a,ªUhßBv:C+îrÜOazýÐU/bÖZÄý_ªbüî>|ß<ßbü,:Ä:+vßUBCrUu*ab@_UAî+a,BÜåª.AöbAã|Z|zöu ߢãöðüa+/ö<:Züß,zÜ£ý:vü*£bbZ¢hvOÖOa/<öãBUvååA O_ÐåÐå>z:Üý£ýOÜ<Ã/oî¢î*üî~ÖÖ¢vܪv@Öoa,ð.rß@ªðÃö+ÐÖ|/ÄCÄvrÜüoCÖ©Oa,+<.ªu©ö£UåÖ/vÃbh@îO:îª/.ðÖðª<>©U~Ä äðABßa:A,O_hrß uã*vãrbv~:Ð oÐr>ã|*rr<ß+//|Au+ªÐßhZßî ähß©:¢bÃZZr©Ä~äªhÐAýß,¢aAräa@A:+ª@Ãr.@aOZÖhvã_©Aära|Üî><@ߣvÜbovãßbrå£O¢ÄäÃbU*O<@ßhzaå~ÃauâåÖ.uªå/a<ÐäUÐa.åü+vÃB,a>~ bö@,åÖýÜu¢ðoZ£ÄÐ+ðÖ>üÃ:ÐU/ÄB|_©,CzÜöý*ý<Öurvbuýb,££|ãã++<Üh:båu©ö_AüäÜÖaövÖÐýuz>ohB>bÃÄÜ>@:üÐz*äOÐråö/îZªîBv>zý@Ðüh,ÃðÜOAo+£+/ÖÜ©, Ua._ÐUßîoª/vO_ðuÖoüovüaäßbUðohzªCýÖ> ..oåaª~~îOC:uýªÄUbva|Ö£ýåÐaZãorÜ.î*ÐßZU*.våîh:öoÄååð<>Ü£ÖoývªzÖÖü Az£äîößî~ZäÖä.ßä~öZZzðhÃü£ZüÖC@hðß/¢@ãüðB|î<ßaz|<ÖÐß:ÃABOöåv©äo<ÖÄãbýO Öý+OÐA£îü Ä>a,ÐÃuÖ*:|öÃUu..Ðbou~Z.üßB>Zb@bððü+oBAAªz¢ÐÜßZCö ªðßßCCrª.î~Ð:vð@AÜZävzÃA|ðü|£O_ü_ü:ªuBßão_ÃC:åã*ßUÜ@ãBbZUßvOo£üU+ªO>ßzzðö/ÄĪ|~ÃbªÖ,,Ah:îo+/ã£Ã@~:ÐãÃ*_>öä|.++©©u~B_î|ã*<Ъ£öåbzÐrCoßÐaÐbU:@:Äb_<Ãrv_hªAª,+öÐî_bßvÃOU@ã<~*>£C îÜ£boU©ßÜ~~B_/*býhÄ,ÄraîZüäÐÃO£üðZ<ªZ©ÖZ,ý>B~oý,C@**öĪÄÖÖÜO,öC~ö¢OvüÖÖ ,.aÐÃîzhü<ªoßZar©Üã@å|î/åîoüýÐ/+ÖAü@Ãåaã:£uî*öÖ£ªü~Oðu/ß@|äßÖ oUAu.Ü >>ÜÖabU*ýBöh@*ahU:_ªoahAuBC>üåC_ý C:ß.îor<@:ð£uýhCÜÜåOo<+Ö_îöÐ+Cý,oOu_ ã_+.©>©C~äh+äB ©ßo£Bbåar:B,<@Ü>ýÄ£übß+@A£Ð*A~:vö_oö~BOªBß,ÜbhBoA©©,.îö.B|+ãÜCaoh@aÄ ßîå@Aåu.ª:î|~ *ðbãA~ÖäaÐ*ãüßu¢_h+ßuÖÜä+AÐaãÐ@>zä_ðäÄ|au>båãrý.ÄåТª.A¢BCBhðã |+îBÐîÃv b*UrzÐ/,öBhãßåÃ:@Zܪ<åZ£Ö ÐBv@aª.|voã +©ÄOßÄ<Ü:bîbü|,u~+*üü.a+ý|ÜCÃarÜ+¢äa~.b/zu/@ЩZ©a¢>UßÄOaürvbåC*b@öåÐ.oÃ>/ ßOb>¢ vBCb@üü£/äA,oA_rä*ýÜß|vãöaß>îaö<|ðÜßýz||ýuZ_Cz*.Ü¢zÄ@ä_ÄßvB@o<Ö.î|ãuUÐr_ß.ß|vÄÄ££.ÄZÄUöÄ,büÐub,><ä£|:Cob.ÖîBbý:C|:äÖÖ:Ä©ªhãOÜ< ÖAZb,boO_U©äö ä£.¢äoÖO£Öaoüö Zývö/obÖîbA.v >B~Ö Ö|ßÖ>äh£/.£rää/*¢ä~Ãrãßör.bUéUhü|ðüC|bÃuå¢ß:aý,ßbZ£ªbéÖå_bßåüÖo>uZö:ubßå< raÜ<äÃ*Üî|@bý/ZÖCãobÐ>öh,äððð*|OähbðîÖh+/uuãßZ_ü::üöa©BÜ_£BýÖ/ã_a uã++ã+Äߪ>ZC Czö<:+ªoObZ+v©hªa+©ã:bĪBªªUub~>/züÃßü+O.oAÜbý|Ã|rªhªväb~ ,äÖ+öC<ßðüÃ/Übý<.zAö@BAî©A:/åo/îC<|@.AuîZZbªaC._hCä|A£ßzããÃð©AîÖ_ß Z<îý|Z.ðozÜÐh/©îäªrß>bÜå,äÃ+ÃÖ<ã£Ää+ýää|.Äa~ĪAU,ßöB/~zð,>u_ääß/åªßý,ãüüßh/£üå.+u,ßo@/ßzb+@a£zÄ|ð~ÄBßbã_ãüzZh£ö>Bã :b£î:ß<ða ü ~ZA@>r,:ÐðauA', null, N'îzÃî>åÄh@ã*©özðAA¢UðãCz£ä:ß,ÜbCoßð/a~.aÄ~åý©äÐ@.ýür._b_ªßöÖª©+UåC¢î~ªA>o©uðår£åb/~ÃCv.å*vUåÐöÐü¢z>ãA,öðîOÃßzC£,hbÄOB©hªÃÖ_z*hÖuövz~Äßä£<©ßð~ª|uªäÖ©U_åüOîhvã¢|Ö.<äAßb', N'ubÃ.årABr Ä>,Bý,, ,ßÃ~ßäBA>BÐ@,_O//Oä:Býäbb@ðb+ >:£>©å¢¢UãzäÃð*©ðozü£ß_ðU.ö¢@ZðA©OªvåîäzßßðahUZðÃ,ªÖÖ,©ÃðÐUvðýCüîZO@ BßAzaOãå@ÜaCaöC©oZv>ð*o£~+ßÄüCäbܪªª|:Ðrãß,öUo', null, N'ZÐýða,У,Ä£,hð.ðü .*>CÄ_b©Cür,//ÐÄ©+äCÖråüOÐBßÄåüaZß@Ä©auz©ßå_+Uðo/ýÃ:Öö<_ÄãîÜv|å Äßöbv*ÃZb<ðÖu åbüö~îvbÖ>BrßãB:ACð_ã|zãåBZ.+ZAÐz+üãß üýb£A:ýb_äO¢/ßß©>zÖCb>ªã©<:+î.äU¢ÃhöÃß<Ð:zßAüCð,UÃC*ÄrUÃBå|ão_ZU@|z./:¢ÜA£CCý~ößýðÃbß|o>ßh*ä©äuüa|>.ß>üßb zbBCÄ>öÜãUäu/ö,ߪZCÖCO.oC.b@ý<öåý:UC,Ã/ãðîaýCÜh|Aaî©Ãzuh¢öaZ//Ü+ZüuýÃZ.îävãuO/A Z@<ü|ZrÃUCC£Äh_oäÖ~aß.<ýüOî©ýîößßýÖ_Ð|ßüuÄã*B@ÐåÜh£Ð,|<ÜuUoZ_ah>_zãÄ,a.Zö¢ÜB|Cî/:aîäUu.u@ü <.>/r| ßßÃA>ÜÄ+ÜÜÜ,uB*Ðã@CZv å¢r.Ãðå:O~@u BÖ/>hhå/£/CÖuh©©îBZb:îbãBBß< ~råýÖUý,aýo,åUOb©:ßrÜZ>,+B:Z@ßãý.*C|Ö@ªð|ß.öö_£bÜAð _+ª@>öª_:är..rü.CBABrÜÖ£boÜh@©|Uh,:ãO<,|îîäözoå>+©@abzößÜv<_äÐ ö@ZªOÜB>@.|äÄv_ åZZÜäh_rã:£bðaäÄO~ð.a|ÃÄvoýªAU~CöÄ:auÃ,@ªý BÃîB£BîÄ+höoß <,_/:_üý*BðZCö.uÐbaB©ühZÄ,C_uýa_£©ÖzäBh:+C*ßo+ýöZh,ähÄ£>UUÄb..Ü*B+¢/rC+¢ð_Ð|ª*C*.OUbªh,öAUbª,bbö*aÃßÖ|ÖOÜÖðªýbîîC+©UOÃÐüü©.öbÖUä>ßuª£ ~ã.bZärßüö*h>A.ßÜ£vÐßboßhA>bBräZvÜÜßßC,åÖoÄ<~îvu:Ãa~*ð>@êðv*CÃÜBbåÄU~ý©U,ü©+ÃuuÖZ¢.Ovð ZvCuOC@¢:ö+@a_üäÄ* üöãha+Ä@b>Z~ßh|/ý:ÖZÐÐ~ÜåOOýä*îa@.rABÐ*ÖB*+ÐöÄåÖß..rüzÄ.~|©OZhCbbäÃÃ/ªªüzÖr,*äãoz.O/öÄbrÃÜ/@bo<üu ©BAO Oß/åuC bÄÐ>åäü©B*ß>a£ObîovUoaOÐhCBý¢>ßãB*ÄÐß|v¢ý|U Z_OÃaÐ_ÄöåOb_ý+ÜÖüAª@ü.£ð/Z î£U~Üä:ªð.üîüCårO©Oßr<äA@¢ß C_Ö|/rßz¢~Z©_Öã*ah¢.br:vv>h.ßubhbC<,£,<ÜZ~|ª+>U/¢>a,a,Uðb.>o¢/v:ê©ãC>¢äîÖUÃÜZrÃC©å| ob_ÐåboBuåßOOß,ÄovÖZr~|Ã*¢uußAö_ÄC@_hCU.<Ü©ä/@~<ßüü.hvh~@ã+UäðZOðv:bZä~ba ß/ðÃUßbÐ.ãb_./ýöaZÖa|_<<ðob_rð,ð,ß:ãߪ_OC@/C,äAb äzZýÖvö/ÖbÜ>_<:oh~ýzZrÃrß:ab+_zA<äbb¢.bOÖub@ÜÃ|büüvvAÖA©¢BoZUåbuÜÐAÃ,+_Ä@ß|bho.hîüB.vAýÄß.:@UßðäoãÐb£å£~/_ÐUrü@:©A+ZߣöÖÐzäüða<ÐåÄîZ/+ýÐZÖ@ãåaãu.£b>îýz*a©©.ba*>bUAz_*öz©r:üÜAbîov u£,,:bhA£ã|©Z,AÖ£ý_©bÃÐãÃ@ |ýªÖUßÃhãÃã|U.zz >o£ã|~üÖ åuÃÃOÃvö©ÜÜ/Ö:U__*Zäð/Zz~b_bö,oBü,¢zuöÄ|îaAÖîbß@@ß å,ßUUü£BÐu+_ÐÄß_+ãðzª¢Ðüð,@,@uA_obZÄho+ ~äü @:zbb@:Ã~<Äv©ã ä:U|@rß,©/C,r:~ÐÖ£./:b:zaÖ*ö©*Abzã@å Z:CÄb¢¢üCUuýÖüªU.äÜýö_ÜääÐ .bB+öÄ,:rZÐ./ã*î>Cöð+.ßîüÐî,Щvã zuåAäZZ*>,ö©: h~£vÐîßöývr.rßaÐ@Aåbh¢:ä¢ã©|åaãu@.ÖßB.@ð@*u¢ã:b<*Üåðß+r@CÜ¢åUUbü/üåßbr£C>ö|Ü,ãAOÖOuChð.huUÃzÜ¢ÃvZvOä~Z/hhÖ/ãß.hvÐ:hbAüã>_Ð|ß@ ävZ>C£O~<Ðbý+>~ ßB.| >¢Uz/ÃzãÄ¢¢ß/|~@îaª|ä£züý:aü.ÖÄBÜa*¢bOÃüåÖÜC/OöðaßîBßa:äzv ü© /öð_A.uö<Äz£ÃCår@ß@Büªä¢ZÖ_uA vÐo<Ðä£å£ö.£ rßo|îbhå:o:|.¢*¢zz¢~hå:ß|Äð ,@býÄýýßîrov~îOChÄööbãß>b£vÜb>bv,aÖ/Ã*Ð@£|ÐBh+auÄ*©ãÜüv|Ä|ðbÄrÖ.oîä|oÐ/Üý/z:büðîã*©Ü/ýrv:©hb~ð.:ßöaz**¢å©ßÜhOoüBÜð+hÖüÖÐîoOCZÜÄaßb£hüª_<ÄßC£ubbzÄbv©_vÃãOZ,ro@~îbÖ h>ãho+å.üäC@äbå.U>b<_*ßöÃu* rrr|:ÐößåðCz£äÐýbÖ_CZB .AZÜÜÄå/öA|rÖ£|bChB_ ,Ößâßåð©UABåß_OüObýÃ/ýr|aAZ/¢ZA_Ä|AbÜOvöã~åzýO>|üî.üu£@Ãü/üã@Ð*©äöãüb,âab*.Z>ß|_ð<ÐäbzbA C u/.BUu:b*@@|rÜöoZB.<¢,z~vªßߣÜðÐZ+.~£vObbÖua.î@bChÜðð£ÄßCßã¢.£.|ªÜZã++>Að*/åOZO+BЩ>aýߢ£ª:ß.|£, £.¢ª©ÄУÐa|uߣߣ<@©ýð|ªü©OÜð©ª+ÃA_@å_Ö.AöãvýU.ãö¢OöAý..Bߣö>ðßüh£åOZCöBÄC£ABz¢ÐU*ßz>©*âh/BîßðBCäC *u_ßÃbUî*+ ÜOðÃhC@<:zO_oÃãrår_ðuOZ,aßýª.//*vߣîýCÜýª+©aOÃ,Cãå*Äß/Bªå@© CîbAåßZ*böuÜ.|zã@vu .åÜ@BÐ|ßÄba::/äÄz¢h*.bbå©~Zuhvb¢z*îUC_öov:¢aðÖzýê_åCåð+ßåä@/ Ã:ü,hC@¢~ÖÃýbö.böîvz@_Är>/._/uÃ/ÄöÜOA_¢r:baÜrhßýüÐööUhßoÐÜåÃÃýO*bvZhOuA.vbä:@öý+£/~ÄZÐO~h¢:v£ð>:å£Äðã©ßÐÃ_©rv*¢ßÜ@bv,£ÐßvaÄ@ªå©b/ßãöä,Ö Zð~îßu*ßC©U¢Ö :Aüb壪ý@ÄäCß~Ãuaª/üÜBhªß©zUzhª BbZz /o£U>,äb©,ÜBOß_+î*uu *©,B_aÐZ,î~/ßÄvÃv|hv@o,>ühz_ð,ürözåßaÜ>££<Äabr,bvãßBößrZîh.Är+ýð,>>ã/©¢b/,/ÜÖUvUÖOCý>Ã_C£Bð:CÄb©ÃåUßaoÐ~ ÖÖå:+£<ðCUÖv@ahî|ãü|oðÐzÖbäý:uª/@£Ð', 0x6D7709E9BE3A37F1CD76004CF5F519684B067430D57F7103531A503D68C3BF18353167CEC834A3D9943C294F03E1C88A59747AF881B3F3ED050595A4EAF98809B6E816C48D4AA016629A9B985621FF7151C6E4F349C796813E79C8C29EFFEF9AB31F991BCB03FD0ED46395E7185A97E4052452231595C0EE5295861382DD7862AB01E43687E4A64EC8D51FE531AF69E3E2E4BD79ADCB841E5FAAC9357DE98CF7EA333B1675615C217D045B3F76DABDA22CA2319E20FFB5DF7CC5D7DD1B70D0DF4928FAFE73526D81879BFBA97045A884ED2609DAB8EED6EB7D5C9AAF897F060C337BF64EDFBF2FD935AF1684030B6E371D52F0F467F6265B510F8016B8BDF70795EE76F44E5AA42DF62FA508D751E871EE6292368F11A53D559BFCD03E182177B80EEF149579E8C2AEC2FE38501E83FF270F28552BC0788349399FB35DD4EFDE0E2E50C862D3DB747ACF88CC136DEC37, 0xA92FF0CB4CF7966BCB5CDA3565D9D2202922CDBA9D5333B3DB5B37AB70DC4D689B602AC0ED42E15832F6087F9A456AAEF9F343C9695D6056FC3C, null, 0xEAFAEE0F719904AF1232E6407587A64AB31996F188B27D9883FF3E00D4C3B052F19848FC196F29AB66FFE6C05D88F3D5B90B482359FF36387D568B725B03DB4F82934FDC3FAA3043F0EA8A89CBBC3BFB0B6946C87B8CF8F162107EC3E4B6506224973EC93817950FDA04336E74AD6CC01A9066F2BC12D4F59A5D792E2B6C5F399A2765337E72C2F3D336EE76588A7DB5A3BDA953505EA511C3492EDA9D67E46DA5FAD44C473942A8CD721E85245D9C10E0C48B6DC30BE83C8153703444E7556C5280D15EB6306D6A7B0186DD3F20ED65AC1DFAD8DC76AA991197CAF4C90D63E51CB233A48C3E5AB05720F2C2850BFD61650A8727ED7C90DCD5374C1E6578A16D9DA0C08CED49D86EC24A81BB83E7140699688EBE853CF89F2256CE55F13B4ADE7F8525E44A70741C311964A2FBD458EE234584FCEAE2E760A8206A967B837C8600A23BFEC22FBB29F4728D6CD855A44DFEE031ACB64DF49D9DC43C2A06DEA1AE9CDEA259985A2BF3895B0877547A88527C1C989A51D0CFB2AD9E60FEF3912215D1DED0FAB24BB4A3B43E225B447D48233BE9ADEA46065CCB0EEFABFAFA2C00F739F3A675AA8E9F3167F4E513AD746E30553DC209E338CFCA8204EBC29FBAA9883E2B2151D6D279AC497C825120491E99817AA5BC6AF6130024DBF3A5A3DC91AD4B5DD582521C5A82C315BF67B67DAA38E651412414EC1D4717212DFADFD3096DEAD406C2749B47652022F95436AC4AA631BBBE519B6D985E3F8D6B31EF5FA3F00C527CE177E7C34A0ADB03F2F3C9116AD786C82414B8D17D3CA883E7301676C17F0947E330692F4AF77447BF69A66A767B005A68967FDB5381FEA5CF64DC19F4654273EA822D3A6E577E2DA0711114963D0D6B30BC0D2AFF2DF36231C360E61A5A535A263280F44EC6EC1018523AE270E90E915771E937581773BC8ECB08AAF79812451EB7ED63BD191D6AD0FD12ADF311F148B5CE8FA9711B020FBFC36582BD42197F3FF5C4B8FCABD43E537F675A04C5C97DD9C0F4290969D402B5DEA3F9B39BAC8EE4926EFE, '99999999-9999-9999-9999-999999999999', '5305-06-29 02:10:46.642', '2042-01-28 21:45')"); sqlsrv_free_stmt($stmt12); - $stmt13 = sqlsrv_query($conn1, "SELECT * FROM [PHPTable] ORDER BY [c1_int]"); - sqlsrv_close($conn1); + try { + $stmt13 = sqlsrv_query($conn1, "SELECT * FROM [PHPTable] ORDER BY [c1_int]"); + } catch (TypeError $e) { + echo $e->getMessage() . PHP_EOL; + } + + try { + sqlsrv_close($conn1); + } catch (TypeError $e) { + echo $e->getMessage() . PHP_EOL; + } $stmt14 = sqlsrv_query($conn2, "DROP TABLE [PHPTable]"); sqlsrv_free_stmt($stmt14); sqlsrv_close($conn2); ?> ---EXPECTREGEX-- -Warning: sqlsrv_query\(\): supplied resource is not a valid ss_sqlsrv_conn resource in .*sqlsrv_close\.php on line [0-9]+ - -Warning: sqlsrv_close\(\): supplied resource is not a valid ss_sqlsrv_conn resource in .*sqlsrv_close\.php on line [0-9]+ +--EXPECT-- +sqlsrv_query(): supplied resource is not a valid ss_sqlsrv_conn resource +sqlsrv_close(): supplied resource is not a valid ss_sqlsrv_conn resource diff --git a/test/functional/sqlsrv/sqlsrv_close_twice.phpt b/test/functional/sqlsrv/sqlsrv_close_twice.phpt index c0201704a..4eee2a77d 100644 --- a/test/functional/sqlsrv/sqlsrv_close_twice.phpt +++ b/test/functional/sqlsrv/sqlsrv_close_twice.phpt @@ -3,6 +3,14 @@ Free statement twice --FILE-- getMessage() . PHP_EOL; } catch (Exception $e) { - echo $e->getMessage(); + echo $e->getMessage() . PHP_EOL; } echo "\nDone\n"; endTest("sqlsrv_close_twice"); @@ -45,9 +55,8 @@ function Repro() Repro(); ?> ---EXPECTREGEX-- - -Warning: sqlsrv_free_stmt\(\): supplied resource is not a valid ss_sqlsrv_stmt resource in .+sqlsrv_close_twice.php on line [0-9]+ +--EXPECT-- +sqlsrv_free_stmt(): supplied resource is not a valid ss_sqlsrv_stmt resource Done Test "sqlsrv_close_twice" completed successfully. diff --git a/test/functional/sqlsrv/sqlsrv_configure.phpt b/test/functional/sqlsrv/sqlsrv_configure.phpt index f6849c380..c582c97ed 100644 --- a/test/functional/sqlsrv/sqlsrv_configure.phpt +++ b/test/functional/sqlsrv/sqlsrv_configure.phpt @@ -4,17 +4,29 @@ sqlsrv_configure. --FILE-- getMessage() . PHP_EOL; + } + // warnings_return_as_errors the only supported option $result = sqlsrv_configure("blahblahblah", 1); if ($result) { @@ -134,34 +146,34 @@ sqlsrv_configure. } ?> ---EXPECTREGEX-- -Warning: sqlsrv_configure\(\) expects exactly 2 parameters, 1 given in .+(\/|\\)sqlsrv_configure\.php on line [0-9]+ +--EXPECT-- +sqlsrv_configure() expects exactly 2 parameters, 1 given Array -\( - \[0\] => Array - \( - \[0\] => IMSSP - \[SQLSTATE\] => IMSSP - \[1\] => -14 - \[code\] => -14 - \[2\] => An invalid parameter was passed to sqlsrv_configure. - \[message\] => An invalid parameter was passed to sqlsrv_configure. - \) - -\) +( + [0] => Array + ( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -14 + [code] => -14 + [2] => An invalid parameter was passed to sqlsrv_configure. + [message] => An invalid parameter was passed to sqlsrv_configure. + ) + +) Array -\( - \[0\] => Array - \( - \[0\] => IMSSP - \[SQLSTATE\] => IMSSP - \[1\] => -14 - \[code\] => -14 - \[2\] => An invalid parameter was passed to sqlsrv_get_config. - \[message\] => An invalid parameter was passed to sqlsrv_get_config. - \) - -\) +( + [0] => Array + ( + [0] => IMSSP + [SQLSTATE] => IMSSP + [1] => -14 + [code] => -14 + [2] => An invalid parameter was passed to sqlsrv_get_config. + [message] => An invalid parameter was passed to sqlsrv_get_config. + ) + +) sqlsrv.LogSubsystems = -1 sqlsrv_configure: entering sqlsrv.LogSubsystems = 8 diff --git a/test/functional/sqlsrv/sqlsrv_errors.phpt b/test/functional/sqlsrv/sqlsrv_errors.phpt index fe70816a8..49efd9ddc 100644 --- a/test/functional/sqlsrv/sqlsrv_errors.phpt +++ b/test/functional/sqlsrv/sqlsrv_errors.phpt @@ -8,19 +8,44 @@ sqlsrv_close returns true even if an error happens. --FILE-- getMessage() !== $expected) { + echo $err->getMessage() . PHP_EOL; + } + } + + set_error_handler("warningHandler", E_WARNING); + sqlsrv_configure('WarningsReturnAsErrors', 0); sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL); require('MsCommon.inc'); $conn = sqlsrv_connect("InvalidServerName", array( "Database" => "test" )); - $result = sqlsrv_close($conn); - $errors = sqlsrv_errors(); - if ($result !== false) { - die("sqlsrv_close succeeded despite an invalid server name."); + try { + $result = sqlsrv_close($conn); + if ($result !== false) { + die("sqlsrv_close succeeded despite an invalid server name."); + } + } catch (TypeError $e) { + compareMessages($e, + "sqlsrv_close(): Argument #1 (\$conn) must be of type resource, bool given", + "sqlsrv_close() expects parameter 1 to be resource, bool given"); } - print_r($errors); + $errors = sqlsrv_errors(); + print_r($errors); + $conn = AE\connect(); $tableName = 'test_params'; $columns = array(new AE\ColumnMeta('tinyint', 'id'), @@ -72,28 +97,52 @@ sqlsrv_close returns true even if an error happens. sqlsrv_free_stmt($stmt); die("sqlsrv_send_stream_data failed."); } - $result = sqlsrv_free_stmt($stmt); if ($result === false) { die(print_r(sqlsrv_errors(), true)); } - $result = sqlsrv_free_stmt($stmt); - if ($result === false) { - die(print_r(sqlsrv_errors(), true)); + try { + $result = sqlsrv_free_stmt($stmt); + if ($result === false) { + die(print_r(sqlsrv_errors(), true)); + } + } catch (TypeError $e) { + echo $e->getMessage() . PHP_EOL; + } + + try { + $result = sqlsrv_free_stmt(null); + if ($result === false) { + die(print_r(sqlsrv_errors(), true)); + } + } catch (TypeError $e) { + compareMessages($e, + "sqlsrv_free_stmt(): Argument #1 (\$stmt) must be of type resource, null given", + "sqlsrv_free_stmt() expects parameter 1 to be resource, null given"); } - $result = sqlsrv_free_stmt(null); - if ($result === false) { - die(print_r(sqlsrv_errors(), true)); - } - $result = sqlsrv_free_stmt($conn); - if ($result !== false) { - die("sqlsrv_free_stmt shouldn't have freed the connection resource"); + + try { + $result = sqlsrv_free_stmt($conn); + if ($result !== false) { + die("sqlsrv_free_stmt shouldn't have freed the connection resource"); + } + } catch (TypeError $e) { + echo $e->getMessage() . PHP_EOL; } + print_r(sqlsrv_errors()); - $result = sqlsrv_free_stmt(1); - if ($result !== false) { - die("sqlsrv_free_stmt shouldn't have freed a 1"); + + try { + $result = sqlsrv_free_stmt(1); + if ($result !== false) { + die("sqlsrv_free_stmt shouldn't have freed a 1"); + } + } catch (TypeError $e) { + compareMessages($e, + "sqlsrv_free_stmt(): Argument #1 (\$stmt) must be of type resource, int given", + "sqlsrv_free_stmt() expects parameter 1 to be resource, int given"); } + print_r(sqlsrv_errors()); dropTable($conn, $tableName); @@ -102,24 +151,43 @@ sqlsrv_close returns true even if an error happens. if ($result === false) { die(print_r(sqlsrv_errors(), true)); } - $result = sqlsrv_close($conn); - if ($result === false) { - die(print_r(sqlsrv_errors(), true)); + + try { + $result = sqlsrv_close($conn); + if ($result === false) { + die(print_r(sqlsrv_errors(), true)); + } + } catch (TypeError $e) { + echo $e->getMessage() . PHP_EOL; } - $result = sqlsrv_close(null); - if ($result === false) { - die(print_r(sqlsrv_errors(), true)); + + try { + $result = sqlsrv_close(null); + if ($result === false) { + die(print_r(sqlsrv_errors(), true)); + } + } catch (TypeError $e) { + compareMessages($e, + "sqlsrv_close(): Argument #1 (\$conn) must be of type resource, null given", + "sqlsrv_close() expects parameter 1 to be resource, null given"); } - $result = sqlsrv_close(1); - if ($result !== false) { - die("sqlsrv_close shouldn't have freed a 1"); + + try { + $result = sqlsrv_close(1); + if ($result !== false) { + die("sqlsrv_close shouldn't have freed a 1"); + } + } catch (TypeError $e) { + compareMessages($e, + "sqlsrv_close(): Argument #1 (\$conn) must be of type resource, int given", + "sqlsrv_close() expects parameter 1 to be resource, int given"); } + print_r(sqlsrv_errors()); echo "Test successfully done.\n"; ?> ---EXPECTF-- -Warning: sqlsrv_close() expects parameter 1 to be resource, bool%S given in %Ssqlsrv_errors.php on line %x +--EXPECT-- Array ( [0] => Array @@ -133,12 +201,8 @@ Array ) ) - -Warning: sqlsrv_free_stmt(): supplied resource is not a valid ss_sqlsrv_stmt resource in %Ssqlsrv_errors.php on line %x - -Warning: sqlsrv_free_stmt() expects parameter 1 to be resource, null given in %Ssqlsrv_errors.php on line %x - -Warning: sqlsrv_free_stmt(): supplied resource is not a valid ss_sqlsrv_stmt resource in %Ssqlsrv_errors.php on line %x +sqlsrv_free_stmt(): supplied resource is not a valid ss_sqlsrv_stmt resource +sqlsrv_free_stmt(): supplied resource is not a valid ss_sqlsrv_stmt resource Array ( [0] => Array @@ -152,8 +216,6 @@ Array ) ) - -Warning: sqlsrv_free_stmt() expects parameter 1 to be resource, int%S given in %Ssqlsrv_errors.php on line %x Array ( [0] => Array @@ -167,12 +229,7 @@ Array ) ) - -Warning: sqlsrv_close(): supplied resource is not a valid ss_sqlsrv_conn resource in %Ssqlsrv_errors.php on line %x - -Warning: sqlsrv_close() expects parameter 1 to be resource, null given in %Ssqlsrv_errors.php on line %x - -Warning: sqlsrv_close() expects parameter 1 to be resource, int%S given in %Ssqlsrv_errors.php on line %x +sqlsrv_close(): supplied resource is not a valid ss_sqlsrv_conn resource Array ( [0] => Array diff --git a/test/functional/sqlsrv/sqlsrv_fetch_field_twice_data_types.phpt b/test/functional/sqlsrv/sqlsrv_fetch_field_twice_data_types.phpt index 4719b73dc..32d357bab 100644 --- a/test/functional/sqlsrv/sqlsrv_fetch_field_twice_data_types.phpt +++ b/test/functional/sqlsrv/sqlsrv_fetch_field_twice_data_types.phpt @@ -98,7 +98,7 @@ function Repro() Repro(); ?> ---EXPECT-- +--EXPECTF--  Test begins... string(79) "A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute." @@ -111,7 +111,7 @@ string(25) "Field 0 returned no data." float(1.09) bool(false) string(25) "Field 1 returned no data." -float(3.4379999637604) +float(3.437999963760%s) bool(false) string(25) "Field 2 returned no data." string(23) "1756-04-16 23:27:09.130" diff --git a/test/functional/sqlsrv/sqlsrv_input_param_unknown_encoding.phpt b/test/functional/sqlsrv/sqlsrv_input_param_unknown_encoding.phpt index aa796ae99..970962514 100644 --- a/test/functional/sqlsrv/sqlsrv_input_param_unknown_encoding.phpt +++ b/test/functional/sqlsrv/sqlsrv_input_param_unknown_encoding.phpt @@ -1,10 +1,35 @@ --TEST-- test input param with unknown encoding +--DESCRIPTION-- +When running this test with PHP 7.x, PHP warnings like the one below are expected + "Warning: Use of undefined constant SQLSRV_ENC_UNKNOWN - assumed 'SQLSRV_ENC_UNKNOWN' + (this will throw an Error in a future version of PHP)" +When running this test with PHP 8.0, the previous warnings are now errors directly from PHP +Because PHP warnings are intercepted, no need to check sqlsrv errors for those. +Add a new test case to check the error message 'An invalid PHP type for parameter 2 was specified.' --SKIPIF-- --FILE-- getMessage() !== $expected) { + echo $err->getMessage() . PHP_EOL; + } +} + set_time_limit(0); sqlsrv_configure('WarningsReturnAsErrors', 0); sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL); @@ -13,7 +38,7 @@ sqlsrv_configure('LogSubsystems', SQLSRV_LOG_SYSTEM_OFF); require_once('MsCommon.inc'); $conn = AE\connect(); -$tableName = 'php_table_SERIL1_1'; +$tableName = 'table_unknown_encoding'; $columns = array(new AE\ColumnMeta('int', 'c1_int'), new AE\ColumnMeta('varchar(max)', 'c2_varchar_max')); $stmt = AE\createTable($conn, $tableName, $columns); @@ -21,37 +46,38 @@ if (!$stmt) { fatalError("Failed to create table $tableName\n"); } -$errState = 'IMSSP'; -$errMessage = 'An invalid PHP type for parameter 2 was specified.'; - -$intType = AE\isColEncrypted() ? SQLSRV_SQLTYPE_INT : null; -$stmt = sqlsrv_query($conn, "INSERT INTO [php_table_SERIL1_1] (c1_int, c2_varchar_max) VALUES (?, ?)", array(array(1, null, null, $intType), - array("Test Data", SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_UNKNOWN), null))); -if ($stmt !== false) { - sqlsrv_free_stmt($stmt); - die("sqlsrv_query shouldn't have succeeded."); +try { + $intType = AE\isColEncrypted() ? SQLSRV_SQLTYPE_INT : null; + $stmt = sqlsrv_query($conn, "INSERT INTO $tableName (c1_int, c2_varchar_max) VALUES (?, ?)", array(array(1, null, null, $intType), + array("Test Data", SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_UNKNOWN), null))); + if ($stmt !== false) { + sqlsrv_free_stmt($stmt); + die("sqlsrv_query shouldn't have succeeded."); + } +} catch (Error $err) { + compareMessages($err); } -verifyError(sqlsrv_errors()[0], $errState, $errMessage); - -$stmt = sqlsrv_prepare($conn, "INSERT INTO [php_table_SERIL1_1] (c1_int, c2_varchar_max) VALUES (?, ?)", array(1, array("Test Data", SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_UNKNOWN), null))); -if ($stmt === false) { - die(print_r(sqlsrv_errors(), true)); -} -$result = sqlsrv_execute($stmt); -if ($result !== false) { - sqlsrv_free_stmt($stmt); - die("sqlsrv_execute shouldn't have succeeded."); +try { + $stmt = sqlsrv_prepare($conn, "INSERT INTO $tableName (c1_int, c2_varchar_max) VALUES (?, ?)", array(1, array("Test Data", SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_UNKNOWN), null))); +} catch (Error $err) { + compareMessages($err); } +restore_error_handler(); +$stmt = sqlsrv_query($conn, "INSERT INTO $tableName (c1_int, c2_varchar_max) VALUES (?, ?)", array(array(1, null, null, $intType), + array("Test Data", SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STRING(SQLSRV_PHPTYPE_INT), null))); + +$errState = 'IMSSP'; +$errMessage = 'An invalid PHP type for parameter 2 was specified.'; verifyError(sqlsrv_errors()[0], $errState, $errMessage); + +echo "Done\n"; -sqlsrv_query($conn, "DROP TABLE [php_table_SERIL1_1]"); +sqlsrv_query($conn, "DROP TABLE $tableName"); sqlsrv_close($conn); ?> ---EXPECTREGEX-- -(Warning|Notice)\: Use of undefined constant SQLSRV_ENC_UNKNOWN - assumed \'SQLSRV_ENC_UNKNOWN\' (\(this will throw an Error in a future version of PHP\) )?in .+(\/|\\)sqlsrv_input_param_unknown_encoding\.php on line 24 - -(Warning|Notice)\: Use of undefined constant SQLSRV_ENC_UNKNOWN - assumed \'SQLSRV_ENC_UNKNOWN\' (\(this will throw an Error in a future version of PHP\) )?in .+(\/|\\)sqlsrv_input_param_unknown_encoding\.php on line 32 +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/test_conn_execute.phpt b/test/functional/sqlsrv/test_conn_execute.phpt index 55731ef1d..b84650b01 100644 --- a/test/functional/sqlsrv/test_conn_execute.phpt +++ b/test/functional/sqlsrv/test_conn_execute.phpt @@ -4,21 +4,42 @@ crash caused by a statement being orphaned when an error occurred during sqlsrv_ --FILE-- getMessage() !== $expected) { + echo $err->getMessage() . PHP_EOL; + } + } + + set_error_handler("warningHandler", E_WARNING); sqlsrv_configure( 'WarningsReturnAsErrors', 0 ); sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL ); require( 'MsCommon.inc' ); + try { + $conn1 = Connect(); + $stmt1 = sqlsrv_query($conn1, "SELECT * FROM Servers"); + sqlsrv_close($conn1); + $row1 = sqlsrv_fetch_array($stmt1); + $conn3 = Connect(); + } catch (TypeError $e) { + compareMessages($e, + "sqlsrv_fetch_array(): Argument #1 (\$stmt) must be of type resource, bool given", + "sqlsrv_fetch_array() expects parameter 1 to be resource, bool given"); + } - $conn1 = Connect(); - $stmt1 = sqlsrv_query($conn1, "SELECT * FROM Servers"); - sqlsrv_close($conn1); - $row1 = sqlsrv_fetch_array($stmt1); - $conn3 = Connect(); - - echo "Test successful\n"; + echo "Done\n"; ?> ---EXPECTREGEX-- -Warning: sqlsrv_fetch_array\(\) expects parameter 1 to be resource, bool(ean){0,1} given in .+(\/|\\)test_conn_execute\.php on line 11 -Test successful +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/test_stream.phpt b/test/functional/sqlsrv/test_stream.phpt index dee4e72e9..1b7312fc9 100644 --- a/test/functional/sqlsrv/test_stream.phpt +++ b/test/functional/sqlsrv/test_stream.phpt @@ -4,6 +4,16 @@ Test for stream zombifying. --FILE-- getMessage() . PHP_EOL; + } + sqlsrv_free_stmt($stmt); sqlsrv_close($conn); ?> ---EXPECTREGEX-- -Warning: fread\(\): supplied resource is not a valid stream resource in .+(\/|\\)test_stream\.php on line [0-9]+ +--EXPECT-- +fread(): supplied resource is not a valid stream resource \ No newline at end of file From 0caf160cadb09841fb70fbbfbdd5c0ef3213ea23 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 8 Jul 2020 22:22:48 -0700 Subject: [PATCH 220/249] Use 80 instead of 8 (#1152) sqlsrv_80_* or pdo_sqlsrv_80_* etc. --- buildscripts/buildtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildscripts/buildtools.py b/buildscripts/buildtools.py index 4d9bde697..ad555e2cb 100644 --- a/buildscripts/buildtools.py +++ b/buildscripts/buildtools.py @@ -99,7 +99,7 @@ def compiler_version(self, sdk_dir): if self.vc == '': VC = 'vc15' version = self.version_label() - if version == '8': # Compiler version for PHP 8.0 or above + if version == '80': # Compiler version for PHP 8.0 or above VC = 'vs16' self.vc = VC print('Compiler: ' + self.vc) From 48e3dd01becbba709b457694f648caa05eaf747d Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 9 Jul 2020 19:12:00 -0700 Subject: [PATCH 221/249] Updated functional tests with iterator and locale issues (#1153) --- .../pdo_sqlsrv/PDO32_StmtInterface.phpt | 3 +++ .../functional/pdo_sqlsrv/PDO92_Iterator.phpt | 2 +- .../pdo_sqlsrv/PDO93_Recursive.phpt | 2 +- .../pdo_sqlsrv/pdo_1063_locale_configs.phpt | 6 ------ .../pdo_sqlsrv/pdo_1063_test_locale.php | 21 ++++++++++++++++--- test/functional/sqlsrv/sqlsrv_errors.phpt | 8 +++---- .../sqlsrv/srv_1063_locale_configs.phpt | 6 ------ .../sqlsrv/srv_1063_test_locale.php | 21 +++++++++++++++---- test/functional/sqlsrv/test_conn_execute.phpt | 4 ++-- 9 files changed, 46 insertions(+), 27 deletions(-) diff --git a/test/functional/pdo_sqlsrv/PDO32_StmtInterface.phpt b/test/functional/pdo_sqlsrv/PDO32_StmtInterface.phpt index 1bc73c2f0..4b6cac1df 100644 --- a/test/functional/pdo_sqlsrv/PDO32_StmtInterface.phpt +++ b/test/functional/pdo_sqlsrv/PDO32_StmtInterface.phpt @@ -56,6 +56,9 @@ function checkInterface($stmt) unset($expected['__wakeup']); unset($expected['__sleep']); } + if ($phpver >= '8.0') { + $expected = array_merge($expected, ['getIterator' => true]); + } $classname = get_class($stmt); $methods = get_class_methods($classname); diff --git a/test/functional/pdo_sqlsrv/PDO92_Iterator.phpt b/test/functional/pdo_sqlsrv/PDO92_Iterator.phpt index efd3fb27a..a1765b4bd 100644 --- a/test/functional/pdo_sqlsrv/PDO92_Iterator.phpt +++ b/test/functional/pdo_sqlsrv/PDO92_Iterator.phpt @@ -71,7 +71,7 @@ class PDOStatementAggregate extends PDOStatement implements IteratorAggregate $this->setFetchMode(PDO::FETCH_NUM); } - function getIterator() + function getIterator() : Iterator { echo __METHOD__ . "\n"; $this->execute(); diff --git a/test/functional/pdo_sqlsrv/PDO93_Recursive.phpt b/test/functional/pdo_sqlsrv/PDO93_Recursive.phpt index 79e5cb107..87be5fb28 100644 --- a/test/functional/pdo_sqlsrv/PDO93_Recursive.phpt +++ b/test/functional/pdo_sqlsrv/PDO93_Recursive.phpt @@ -69,7 +69,7 @@ class PDOStatementAggregate extends PDOStatement implements IteratorAggregate $this->setFetchMode(PDO::FETCH_NUM); } - function getIterator() + function getIterator() : Iterator { echo __METHOD__ . "\n"; $this->execute(); diff --git a/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt b/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt index 32c35f7a4..660b894f8 100644 --- a/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1063_locale_configs.phpt @@ -34,13 +34,11 @@ pdo_sqlsrv.set_locale_info = 0*** Amount formatted: 10000.99 Friday December -3.14159 **End** **Begin** Amount formatted: $10,000.99 Friday December -3.14159 **End** ***sqlsrv.SetLocaleInfo = 1 @@ -50,13 +48,11 @@ pdo_sqlsrv.set_locale_info = 1*** Amount formatted: 10000.99 Friday December -3.14159 **End** **Begin** Amount formatted: 10.000,99 € Freitag Dezember -3,14159 **End** ***sqlsrv.SetLocaleInfo = 2 @@ -66,11 +62,9 @@ pdo_sqlsrv.set_locale_info = 2*** Amount formatted: $10,000.99 Friday December -3.14159 **End** **Begin** Amount formatted: 10.000,99 € Freitag Dezember -3,14159 **End** diff --git a/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php b/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php index b37174494..f77cabffd 100644 --- a/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php +++ b/test/functional/pdo_sqlsrv/pdo_1063_test_locale.php @@ -32,9 +32,9 @@ function printMoney($amt, $info) echo "**Begin**" . PHP_EOL; -// Assuming LC_ALL is 'en_US.UTF-8', so is LC_CTYPE +// Assuming LC_ALL is 'en_US.UTF-8', so is LC_CTYPE, except in PHP 8 (TODO) // But default LC_MONETARY varies -$ctype = 'en_US.UTF-8'; +$ctype = (PHP_MAJOR_VERSION == 8 && $setLocaleInfo == 0) ? 'C' : 'en_US.UTF-8'; switch ($setLocaleInfo) { case 0: case 1: @@ -55,9 +55,11 @@ function printMoney($amt, $info) $c1 = setlocale(LC_CTYPE, 0); if ($ctype !== $c1) { echo "Unexpected LC_CTYPE: $c1" . PHP_EOL; + echo "LC_NUMERIC for $setLocaleInfo: " . setlocale(LC_NUMERIC, 0) . PHP_EOL; } // Set a different locale, if the input is not empty +$english = true; if (!empty($locale)) { $loc = setlocale(LC_ALL, $locale); if ($loc !== $locale) { @@ -68,6 +70,7 @@ function printMoney($amt, $info) if ($loc === 'de_DE.UTF-8') { $symbol = strtoupper(PHP_OS) === 'LINUX' ? '€' : 'Eu'; $sep = strtoupper(PHP_OS) === 'LINUX' ? '.' : ''; + $english = false; } else { $symbol = '$'; $sep = ','; @@ -106,8 +109,20 @@ function printMoney($amt, $info) $stmt = $conn->prepare($sql, array(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true)); $stmt->execute(); + // The following change is required for the breaking change introduced in PHP 8 + // https://wiki.php.net/rfc/locale_independent_float_to_string $row = $stmt->fetch(PDO::FETCH_NUM); - echo ($row[0]) . PHP_EOL; + $value = $row[0]; + $expected = 3.14159; + if (PHP_MAJOR_VERSION < 8) { + if ($setLocaleInfo > 0 && $english === false) { + $expected = floatval($pi); + } + } + if ($value != $expected) { + echo "Expected: '$expected' but got '$value'\n"; + } + unset($stmt); dropTable($conn, $tableName); diff --git a/test/functional/sqlsrv/sqlsrv_errors.phpt b/test/functional/sqlsrv/sqlsrv_errors.phpt index 49efd9ddc..7122a2842 100644 --- a/test/functional/sqlsrv/sqlsrv_errors.phpt +++ b/test/functional/sqlsrv/sqlsrv_errors.phpt @@ -19,7 +19,7 @@ sqlsrv_close returns true even if an error happens. function compareMessages($err, $exp8x, $exp7x) { $expected = (PHP_MAJOR_VERSION == 8) ? $exp8x : $exp7x; - if ($err->getMessage() !== $expected) { + if (!fnmatch($expected, $err->getMessage())) { echo $err->getMessage() . PHP_EOL; } } @@ -40,7 +40,7 @@ sqlsrv_close returns true even if an error happens. } catch (TypeError $e) { compareMessages($e, "sqlsrv_close(): Argument #1 (\$conn) must be of type resource, bool given", - "sqlsrv_close() expects parameter 1 to be resource, bool given"); + "sqlsrv_close() expects parameter 1 to be resource, bool* given"); } $errors = sqlsrv_errors(); @@ -140,7 +140,7 @@ sqlsrv_close returns true even if an error happens. } catch (TypeError $e) { compareMessages($e, "sqlsrv_free_stmt(): Argument #1 (\$stmt) must be of type resource, int given", - "sqlsrv_free_stmt() expects parameter 1 to be resource, int given"); + "sqlsrv_free_stmt() expects parameter 1 to be resource, int* given"); } print_r(sqlsrv_errors()); @@ -180,7 +180,7 @@ sqlsrv_close returns true even if an error happens. } catch (TypeError $e) { compareMessages($e, "sqlsrv_close(): Argument #1 (\$conn) must be of type resource, int given", - "sqlsrv_close() expects parameter 1 to be resource, int given"); + "sqlsrv_close() expects parameter 1 to be resource, int* given"); } print_r(sqlsrv_errors()); diff --git a/test/functional/sqlsrv/srv_1063_locale_configs.phpt b/test/functional/sqlsrv/srv_1063_locale_configs.phpt index 0f5ea5e95..298057c0c 100644 --- a/test/functional/sqlsrv/srv_1063_locale_configs.phpt +++ b/test/functional/sqlsrv/srv_1063_locale_configs.phpt @@ -34,13 +34,11 @@ pdo_sqlsrv.set_locale_info = 0*** Amount formatted: 10000.99 Friday December -3.14159 **End** **Begin** Amount formatted: $10,000.99 Friday December -3.14159 **End** ***sqlsrv.SetLocaleInfo = 1 @@ -50,13 +48,11 @@ pdo_sqlsrv.set_locale_info = 1*** Amount formatted: 10000.99 Friday December -3.14159 **End** **Begin** Amount formatted: 10.000,99 € Freitag Dezember -3,14159 **End** ***sqlsrv.SetLocaleInfo = 2 @@ -66,11 +62,9 @@ pdo_sqlsrv.set_locale_info = 2*** Amount formatted: $10,000.99 Friday December -3.14159 **End** **Begin** Amount formatted: 10.000,99 € Freitag Dezember -3,14159 **End** diff --git a/test/functional/sqlsrv/srv_1063_test_locale.php b/test/functional/sqlsrv/srv_1063_test_locale.php index 06ab387da..2c8eb56db 100644 --- a/test/functional/sqlsrv/srv_1063_test_locale.php +++ b/test/functional/sqlsrv/srv_1063_test_locale.php @@ -37,9 +37,9 @@ function printMoney($amt, $info) echo "**Begin**" . PHP_EOL; -// Assuming LC_ALL is 'en_US.UTF-8', so is LC_CTYPE +// Assuming LC_ALL is 'en_US.UTF-8', so is LC_CTYPE, except in PHP 8 (TODO) // But default LC_MONETARY varies -$ctype = 'en_US.UTF-8'; +$ctype = (PHP_MAJOR_VERSION == 8 && $setLocaleInfo == 0) ? 'C' : 'en_US.UTF-8'; switch ($setLocaleInfo) { case 0: case 1: @@ -60,9 +60,11 @@ function printMoney($amt, $info) $c1 = setlocale(LC_CTYPE, 0); if ($ctype !== $c1) { echo "Unexpected LC_CTYPE: $c1" . PHP_EOL; + echo "LC_NUMERIC for $setLocaleInfo: " . setlocale(LC_NUMERIC, 0) . PHP_EOL; } // Set a different locale, if the input is not empty +$english = true; if (!empty($locale)) { $loc = setlocale(LC_ALL, $locale); if ($loc !== $locale) { @@ -73,6 +75,7 @@ function printMoney($amt, $info) if ($loc === 'de_DE.UTF-8') { $symbol = strtoupper(PHP_OS) === 'LINUX' ? '€' : 'Eu'; $sep = strtoupper(PHP_OS) === 'LINUX' ? '.' : ''; + $english = false; } else { $symbol = '$'; $sep = ','; @@ -121,9 +124,19 @@ function printMoney($amt, $info) fatalError("Failed in running query $sql"); } +// The following change is required for the breaking change introduced in PHP 8 +// https://wiki.php.net/rfc/locale_independent_float_to_string while (sqlsrv_fetch($stmt)) { - $value = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_FLOAT); - echo $value . PHP_EOL; + $value = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_FLOAT); + $expected = 3.14159; + if (PHP_MAJOR_VERSION < 8) { + if ($setLocaleInfo > 0 && $english === false) { + $expected = floatval($pi); + } + } + if ($value != $expected) { + echo "Expected: '$expected' but got '$value'\n"; + } } sqlsrv_free_stmt($stmt); diff --git a/test/functional/sqlsrv/test_conn_execute.phpt b/test/functional/sqlsrv/test_conn_execute.phpt index b84650b01..32cb495d3 100644 --- a/test/functional/sqlsrv/test_conn_execute.phpt +++ b/test/functional/sqlsrv/test_conn_execute.phpt @@ -15,7 +15,7 @@ crash caused by a statement being orphaned when an error occurred during sqlsrv_ function compareMessages($err, $exp8x, $exp7x) { $expected = (PHP_MAJOR_VERSION == 8) ? $exp8x : $exp7x; - if ($err->getMessage() !== $expected) { + if (!fnmatch($expected, $err->getMessage())) { echo $err->getMessage() . PHP_EOL; } } @@ -35,7 +35,7 @@ crash caused by a statement being orphaned when an error occurred during sqlsrv_ } catch (TypeError $e) { compareMessages($e, "sqlsrv_fetch_array(): Argument #1 (\$stmt) must be of type resource, bool given", - "sqlsrv_fetch_array() expects parameter 1 to be resource, bool given"); + "sqlsrv_fetch_array() expects parameter 1 to be resource, bool* given"); } echo "Done\n"; From 92f796c07aa76947d4eac4b141e3e3896d905913 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 14 Jul 2020 17:22:46 -0700 Subject: [PATCH 222/249] Modified source files based on some static analysis tools (#1155) --- source/shared/FormattedPrint.cpp | 8 +++----- source/shared/core_conn.cpp | 5 ++++- source/shared/core_stmt.cpp | 6 ++---- source/shared/core_util.cpp | 10 +++++----- source/sqlsrv/init.cpp | 2 +- source/sqlsrv/php_sqlsrv_int.h | 2 +- source/sqlsrv/util.cpp | 2 -- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index dd076078c..94c7b28f2 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -389,7 +389,6 @@ int FormattedPrintA( IFormattedPrintOutput * output, const char *format, v char sz[BUFFERSIZE]; } buffer = {'\0'}; WCHAR wchar; /* temp wchar_t */ - int buffersize; /* size of text.sz (used only for the call to _cfltcvt) */ int bufferiswide=0; /* non-zero = buffer contains wide chars already */ #ifndef _SAFECRT_IMPL @@ -406,7 +405,6 @@ int FormattedPrintA( IFormattedPrintOutput * output, const char *format, v textlen = 0; /* no text yet */ state = ST_NORMAL; /* starting state */ heapbuf = NULL; /* not using heap-allocated buffer */ - buffersize = 0; /* main loop -- loop while format character exist and no I/O errors */ while ((ch = *format++) != '\0' && charsout >= 0) { @@ -631,9 +629,9 @@ int FormattedPrintA( IFormattedPrintOutput * output, const char *format, v case ('a'): { /* floating point conversion -- we call cfltcvt routines */ /* to do the work for us. */ - flags |= FL_SIGNED; /* floating point is signed conversion */ - text.sz = buffer.sz; /* put result in buffer */ - buffersize = BUFFERSIZE; + flags |= FL_SIGNED; /* floating point is signed conversion */ + text.sz = buffer.sz; /* put result in buffer */ + int buffersize = BUFFERSIZE; /* size of text.sz (used only for the call to _cfltcvt) */ /* compute the precision value */ if (precision < 0) diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index d8d2f5066..16b453246 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -808,7 +808,10 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou zval* auth_option = NULL; auth_option = zend_hash_index_find(options, SQLSRV_CONN_OPTION_AUTHENTICATION); - char* option = Z_STRVAL_P(auth_option); + char* option = NULL; + if (auth_option != NULL) { + option = Z_STRVAL_P(auth_option); + } if (!stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) { activeDirectoryMSI = true; diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index b7d99b09e..27fd1cae3 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2101,7 +2101,6 @@ SQLSMALLINT default_c_type( _Inout_ sqlsrv_stmt* stmt, _In_opt_ SQLULEN paramno, break; case CP_UTF8: sql_c_type = (is_a_numeric_type(sql_type)) ? SQL_C_CHAR : SQL_C_WCHAR; - //sql_c_type = SQL_C_WCHAR; break; default: THROW_CORE_ERROR(stmt, SQLSRV_ERROR_INVALID_PARAMETER_ENCODING, paramno); @@ -3056,14 +3055,13 @@ void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digi } else { short oldpos = 0; if (pt == NULL) { - pt = exp; // Decimal point not found, use the exp sign - oldpos = exp - src; + oldpos = exp - src; // Decimal point not found, use the exp sign } else { oldpos = pt - src; num_decimals = exp - pt - 1; if (power > 0 && num_decimals <= power) { - return; // The result will be a whole number, do nothing and return + return; // The result will be a whole number, do nothing and return } } diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 99b1113a2..930455ae0 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -146,6 +146,11 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_( return true; } +#ifndef _WIN32 + // Allocate enough space to hold the largest possible number of bytes for UTF-8 conversion + // instead of calling FromUtf16, for performance reasons + cchOutLen = 4 * cchInLen; +#else // flags set to 0 by default, which means that any invalid characters are dropped rather than causing // an error. This happens only on XP. DWORD flags = 0; @@ -154,11 +159,6 @@ bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_( flags = WC_ERR_INVALID_CHARS; } -#ifndef _WIN32 - // Allocate enough space to hold the largest possible number of bytes for UTF-8 conversion - // instead of calling FromUtf16, for performance reasons - cchOutLen = 4*cchInLen; -#else // Calculate the number of output bytes required - no performance hit here because // WideCharToMultiByte is highly optimised cchOutLen = WideCharToMultiByte( encoding, flags, diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 7c6fcc196..7ddbaffa0 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -694,7 +694,7 @@ PHP_RSHUTDOWN_FUNCTION(sqlsrv) LOG_FUNCTION( "PHP_RSHUTDOWN for php_sqlsrv" ); reset_errors(); - // TODO - destruction + // destruction zval_ptr_dtor( &SQLSRV_G( errors )); zval_ptr_dtor( &SQLSRV_G( warnings )); diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index e0cea7bb7..43f91f2e2 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -366,7 +366,7 @@ inline H* process_params( INTERNAL_FUNCTION_PARAMETERS, _In_ char const* param_s SQLSRV_UNUSED( return_value ); zval* rsrc; - H* h; + H* h = NULL; // reset the errors from the previous API call reset_errors(); diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index b593b4604..c240dde17 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -775,7 +775,6 @@ void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, zval temp; ZVAL_UNDEF(&temp); core::sqlsrv_zval_stringl( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); - //TODO: reference? Z_TRY_ADDREF_P( &temp ); if( add_next_index_zval( error_z, &temp ) == FAILURE ) { DIE( "Fatal error during error processing" ); @@ -797,7 +796,6 @@ void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, // native_message ZVAL_UNDEF(&temp); ZVAL_STRING( &temp, reinterpret_cast( error->native_message ) ); - //TODO: reference? Z_TRY_ADDREF_P(&temp); if( add_next_index_zval( error_z, &temp ) == FAILURE ) { DIE( "Fatal error during error processing" ); From 61f87aacf65afe3115758cda2b2310fce60917dc Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 15 Jul 2020 17:04:42 -0700 Subject: [PATCH 223/249] Modify a stream test to run parts of it outside Windows (#1158) --- .../functional/sqlsrv/sqlsrv_str_streams.phpt | 104 +++++++++--------- 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/test/functional/sqlsrv/sqlsrv_str_streams.phpt b/test/functional/sqlsrv/sqlsrv_str_streams.phpt index 336e00357..4638712c7 100644 --- a/test/functional/sqlsrv/sqlsrv_str_streams.phpt +++ b/test/functional/sqlsrv/sqlsrv_str_streams.phpt @@ -1,7 +1,7 @@ --TEST-- reading different encodings in strings and streams. --SKIPIF-- - + --FILE-- Date: Mon, 20 Jul 2020 12:58:23 -0700 Subject: [PATCH 224/249] More safe guards against anomalous results (#1160) --- source/shared/core_conn.cpp | 2 +- source/shared/core_sqlsrv.h | 3 ++- .../sqlsrv/sqlsrv_fetch_object.phpt | 5 ++--- .../sqlsrv/sqlsrv_fetch_object_2.phpt | 5 ++--- ...sqlsrv_fetch_object_unicode_col_name1.phpt | 4 ++-- .../srv_053_mars_disabled_error_checks.phpt | 19 +++++++++++-------- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 16b453246..4b861c424 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -813,7 +813,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou option = Z_STRVAL_P(auth_option); } - if (!stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) { + if (option != NULL && !stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) { activeDirectoryMSI = true; // There are two types of managed identities: diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 72925e1cb..41a35d7c0 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -397,7 +397,8 @@ inline void* sqlsrv_malloc( _In_ size_t element_count, _In_ size_t element_size, DIE( "Integer overflow in sqlsrv_malloc" ); } - if( element_size * element_count + extra == 0 ) { + // safeguard against anomalous calculation or any arithmetic overflow + if( element_size * element_count + extra <= 0 ) { DIE( "Allocation size must be more than 0" ); } diff --git a/test/functional/sqlsrv/sqlsrv_fetch_object.phpt b/test/functional/sqlsrv/sqlsrv_fetch_object.phpt index c1231745e..857b645b9 100644 --- a/test/functional/sqlsrv/sqlsrv_fetch_object.phpt +++ b/test/functional/sqlsrv/sqlsrv_fetch_object.phpt @@ -223,12 +223,11 @@ else { echo "Past the end of the result set (7)\n"; $obj = sqlsrv_fetch_object( $stmt, "foo" ); if( $obj === false ) { - die( print_r( sqlsrv_errors(), true )); + print_r( sqlsrv_errors()); } if( is_null( $obj )) { echo "Done fetching objects.\n"; -} -else { +} elseif ($obj) { $obj->do_foo(); print_r( $obj ); } diff --git a/test/functional/sqlsrv/sqlsrv_fetch_object_2.phpt b/test/functional/sqlsrv/sqlsrv_fetch_object_2.phpt index 8961fe8cb..11fc5a91e 100644 --- a/test/functional/sqlsrv/sqlsrv_fetch_object_2.phpt +++ b/test/functional/sqlsrv/sqlsrv_fetch_object_2.phpt @@ -227,12 +227,11 @@ else { echo "Past the end of the result set (7)\n"; $obj = sqlsrv_fetch_object( $stmt, "foo" ); if( $obj === false ) { - die( print_r( sqlsrv_errors(), true )); + print_r( sqlsrv_errors()); } if( is_null( $obj )) { echo "Done fetching objects.\n"; -} -else { +} elseif ($obj) { $obj->do_foo(); print_r( $obj ); } diff --git a/test/functional/sqlsrv/sqlsrv_fetch_object_unicode_col_name1.phpt b/test/functional/sqlsrv/sqlsrv_fetch_object_unicode_col_name1.phpt index a6c2cb00c..407e448f6 100644 --- a/test/functional/sqlsrv/sqlsrv_fetch_object_unicode_col_name1.phpt +++ b/test/functional/sqlsrv/sqlsrv_fetch_object_unicode_col_name1.phpt @@ -220,11 +220,11 @@ if (is_null($obj)) { echo "Past the end of the result set (7)\n"; $obj = sqlsrv_fetch_object($stmt, "foo"); if ($obj === false) { - die(print_r(sqlsrv_errors(), true)); + print_r( sqlsrv_errors()); } if (is_null($obj)) { echo "Done fetching objects.\n"; -} else { +} elseif ($obj) { $obj->do_foo(); print_r($obj); } diff --git a/test/functional/sqlsrv/srv_053_mars_disabled_error_checks.phpt b/test/functional/sqlsrv/srv_053_mars_disabled_error_checks.phpt index d80a1ad97..1d060d834 100644 --- a/test/functional/sqlsrv/srv_053_mars_disabled_error_checks.phpt +++ b/test/functional/sqlsrv/srv_053_mars_disabled_error_checks.phpt @@ -13,20 +13,22 @@ if (!$conn) { } // Query -$stmt1 = sqlsrv_query($conn, "SELECT 'ONE'") ?: die(print_r(sqlsrv_errors(), true)); +$stmt1 = sqlsrv_query($conn, "SELECT 'ONE'"); +if (!$stmt1) { + print_r(sqlsrv_errors()); +} sqlsrv_fetch($stmt1); // Query. Returns if multiple result sets are disabled -$stmt2 = sqlsrv_query($conn, "SELECT 'TWO'") ?: die(print_r(sqlsrv_errors(), true)); -sqlsrv_fetch($stmt2); - -// Print the data -$res = [ sqlsrv_get_field($stmt1, 0), sqlsrv_get_field($stmt2, 0) ]; -var_dump($res); +$stmt2 = sqlsrv_query($conn, "SELECT 'TWO'"); +if ($stmt2) { + echo "Expect case 2 to fail\n"; +} else { + print_r(sqlsrv_errors()); +} // Free statement and connection resources sqlsrv_free_stmt($stmt1); -sqlsrv_free_stmt($stmt2); sqlsrv_close($conn); print "Done" @@ -56,3 +58,4 @@ Array \) \) +Done From bba1f18f0f16711fb9f626813f8f1101b4ed0ec8 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 22 Jul 2020 09:52:16 -0700 Subject: [PATCH 225/249] Updated function signatures and error messages (#1163) --- test/functional/pdo_sqlsrv/PDO94_Extend1.phpt | 2 +- test/functional/pdo_sqlsrv/PDO95_Extend2.phpt | 2 +- test/functional/pdo_sqlsrv/PDO96_Extend3.phpt | 2 +- test/functional/pdo_sqlsrv/PDO97_Extend4.phpt | 2 +- test/functional/pdo_sqlsrv/PDO98_Extend5.phpt | 2 +- test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt | 7 +++++-- test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt | 7 +++++-- .../sqlsrv/sqlsrv_input_param_unknown_encoding.phpt | 2 +- 8 files changed, 16 insertions(+), 10 deletions(-) diff --git a/test/functional/pdo_sqlsrv/PDO94_Extend1.phpt b/test/functional/pdo_sqlsrv/PDO94_Extend1.phpt index 9aa66e362..3920d93c6 100644 --- a/test/functional/pdo_sqlsrv/PDO94_Extend1.phpt +++ b/test/functional/pdo_sqlsrv/PDO94_Extend1.phpt @@ -58,7 +58,7 @@ class ExPDO extends PDO $this->test2 = 22; } - function query($sql) + function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args) { echo __METHOD__ . "()\n"; $stmt = parent::prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('ExPDOStatement'))); diff --git a/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt b/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt index 34b573077..f387be22e 100644 --- a/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt +++ b/test/functional/pdo_sqlsrv/PDO95_Extend2.phpt @@ -55,7 +55,7 @@ class ExPDO extends PDO return (call_user_func_array(array($this, 'parent::exec'), $args)); } - public function query(string $statement) + public function query($statement, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args) { $this->protocol(); $args = func_get_args(); diff --git a/test/functional/pdo_sqlsrv/PDO96_Extend3.phpt b/test/functional/pdo_sqlsrv/PDO96_Extend3.phpt index bc634d167..1a80cc6f1 100644 --- a/test/functional/pdo_sqlsrv/PDO96_Extend3.phpt +++ b/test/functional/pdo_sqlsrv/PDO96_Extend3.phpt @@ -61,7 +61,7 @@ class ExPDO extends PDO echo __METHOD__ . "()\n"; } - function query($sql) + function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args) { echo __METHOD__ . "()\n"; $stmt = $this->prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('ExPDOStatement', array($this)))); diff --git a/test/functional/pdo_sqlsrv/PDO97_Extend4.phpt b/test/functional/pdo_sqlsrv/PDO97_Extend4.phpt index e4fe031a6..03f312886 100644 --- a/test/functional/pdo_sqlsrv/PDO97_Extend4.phpt +++ b/test/functional/pdo_sqlsrv/PDO97_Extend4.phpt @@ -66,7 +66,7 @@ class ExPDO extends PDO echo __METHOD__ . "()\n"; } - function query($sql) + function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args) { echo __METHOD__ . "()\n"; $stmt = $this->prepare($sql, array(PDO::ATTR_STATEMENT_CLASS=>array('ExPDOStatement', array($this)))); diff --git a/test/functional/pdo_sqlsrv/PDO98_Extend5.phpt b/test/functional/pdo_sqlsrv/PDO98_Extend5.phpt index 6cccf924a..a3e9c94b7 100644 --- a/test/functional/pdo_sqlsrv/PDO98_Extend5.phpt +++ b/test/functional/pdo_sqlsrv/PDO98_Extend5.phpt @@ -69,7 +69,7 @@ class ExPDO extends PDO echo __METHOD__ . "()\n"; } - function query($sql) + function query($sql, $fetch_style = PDO::FETCH_BOTH,...$fetch_mode_args) { echo __METHOD__ . "()\n"; $stmt = parent::query($sql); diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt index 2bbd79eb6..79b31cd2f 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetchAll.phpt @@ -80,8 +80,11 @@ function fetchAllInvalid($conn, $tbname) } catch (PDOException $ex) { print_r($ex); } catch (Error $err) { - $expected = (PHP_MAJOR_VERSION == 8) ? 'PDO::FETCH_UNKNOWN' : 'FETCH_UNKNOWN'; - $message = "Undefined class constant '$expected'"; + if (PHP_MAJOR_VERSION == 8) { + $message = "Undefined constant PDO::FETCH_UNKNOWN"; + } else { + $message = "Undefined class constant 'FETCH_UNKNOWN'"; + } if ($err->getMessage() !== $message) { echo $err->getMessage() . PHP_EOL; } diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt index 4221ad0a0..b03ee4156 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetch_style.phpt @@ -82,8 +82,11 @@ function fetchWithStyle($conn, $tbname, $style) } catch (PDOException $err) { print_r($err); } catch (Error $err) { - $expected = (PHP_MAJOR_VERSION == 8) ? 'PDO::FETCH_UNKNOWN' : 'FETCH_UNKNOWN'; - $message = "Undefined class constant '$expected'"; + if (PHP_MAJOR_VERSION == 8) { + $message = "Undefined constant PDO::FETCH_UNKNOWN"; + } else { + $message = "Undefined class constant 'FETCH_UNKNOWN'"; + } if ($err->getMessage() !== $message) { echo $err->getMessage() . PHP_EOL; } diff --git a/test/functional/sqlsrv/sqlsrv_input_param_unknown_encoding.phpt b/test/functional/sqlsrv/sqlsrv_input_param_unknown_encoding.phpt index 970962514..7a61e7e77 100644 --- a/test/functional/sqlsrv/sqlsrv_input_param_unknown_encoding.phpt +++ b/test/functional/sqlsrv/sqlsrv_input_param_unknown_encoding.phpt @@ -21,7 +21,7 @@ function warningHandler($errno, $errstr) function compareMessages($err) { - $exp8x = "Undefined constant 'SQLSRV_ENC_UNKNOWN'"; + $exp8x = 'Undefined constant "SQLSRV_ENC_UNKNOWN"'; $exp7x = "Use of undefined constant SQLSRV_ENC_UNKNOWN - assumed 'SQLSRV_ENC_UNKNOWN' (this will throw an Error in a future version of PHP)"; $expected = (PHP_MAJOR_VERSION == 8) ? $exp8x : $exp7x; From 460d9aa2dfb24ddbd8b9faebfc5a5caa99e9bcd6 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 23 Jul 2020 13:46:25 -0700 Subject: [PATCH 226/249] Removed wrappers for Zend APIs (#1161) --- source/pdo_sqlsrv/pdo_dbh.cpp | 3 +- source/pdo_sqlsrv/pdo_init.cpp | 11 +--- source/pdo_sqlsrv/pdo_stmt.cpp | 4 +- source/shared/core_conn.cpp | 91 +++++++---------------------- source/shared/core_sqlsrv.h | 71 ----------------------- source/shared/core_stmt.cpp | 4 +- source/shared/core_util.cpp | 26 ++++----- source/sqlsrv/conn.cpp | 33 ++++------- source/sqlsrv/stmt.cpp | 103 +++++++++++++++------------------ source/sqlsrv/util.cpp | 48 +++------------ 10 files changed, 107 insertions(+), 287 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 21d81e706..fa9ef60fd 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -1230,8 +1230,7 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout //Declarations below eliminate compiler warnings about string constant to char* conversions const char* extver = "ExtensionVer"; std::string filever = VER_FILEVERSION_STR; - core::sqlsrv_add_assoc_string( *driver_dbh, return_value, extver, &filever[0], 1 /*duplicate*/ - ); + add_assoc_string(return_value, extver, &filever[0]); break; } diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index b0ceaf4cf..fdccf5346 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -279,10 +279,7 @@ namespace { zend_class_entry* zend_class = php_pdo_get_dbh_ce(); SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_LONG: php_pdo_get_dbh_ce failed"); - int zr = zend_declare_class_constant_long( zend_class, const_cast( name ), strlen( name ), value ); - if( zr == FAILURE ) { - throw core::CoreException(); - } + zend_declare_class_constant_long(zend_class, const_cast(name), strlen(name), value); } void REGISTER_PDO_SQLSRV_CLASS_CONST_STRING( _In_z_ char const* name, _In_z_ char const* value ) @@ -290,11 +287,7 @@ namespace { zend_class_entry* zend_class = php_pdo_get_dbh_ce(); SQLSRV_ASSERT( zend_class != NULL, "REGISTER_PDO_SQLSRV_CLASS_CONST_STRING: php_pdo_get_dbh_ce failed"); - int zr = zend_declare_class_constant_string( zend_class, const_cast( name ), strlen( name ), const_cast( value ) ); - if( zr == FAILURE ) { - - throw core::CoreException(); - } + zend_declare_class_constant_string(zend_class, const_cast(name), strlen(name), const_cast(value)); } // array of pdo constants. diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index faf78fcc1..062c43e54 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1065,7 +1065,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno } // initialize the array to nothing, as PDO requires us to create it - core::sqlsrv_array_init( *driver_stmt, return_value ); + array_init(return_value); field_meta_data* core_meta_data; @@ -1080,7 +1080,7 @@ int pdo_sqlsrv_stmt_get_col_meta( _Inout_ pdo_stmt_t *stmt, _In_ zend_long colno // initialize the column data classification array zval data_classification; ZVAL_UNDEF(&data_classification); - core::sqlsrv_array_init(*driver_stmt, &data_classification ); + array_init(&data_classification); data_classification::fill_column_sensitivity_array(driver_stmt, (SQLSMALLINT)colno, &data_classification); diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index 4b861c424..f3b93bf1a 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -71,7 +71,6 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou void* driver,_Inout_ std::string& connection_string ); void determine_server_version( _Inout_ sqlsrv_conn* conn ); const char* get_processor_arch( void ); -void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len ); connection_option const* get_connection_option( sqlsrv_conn* conn, _In_ const char* key, _In_ SQLULEN key_len ); void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_len) const char* val, _Inout_ size_t val_len, _Inout_ std::string& conn_str ); void load_azure_key_vault( _Inout_ sqlsrv_conn* conn ); @@ -590,19 +589,11 @@ void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) c void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval* server_version ) { try { - - sqlsrv_malloc_auto_ptr buffer; + char buffer[INFO_BUFFER_LEN] = ""; SQLSMALLINT buffer_len = 0; - - get_server_version( conn, &buffer, buffer_len ); - core::sqlsrv_zval_stringl( server_version, buffer, buffer_len ); - if ( buffer != 0 ) { - sqlsrv_free( buffer ); - } - buffer.transferred(); - } - - catch( core::CoreException& ) { + core::SQLGetInfo(conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len); + core::sqlsrv_zval_stringl(server_version, buffer, buffer_len); + } catch( core::CoreException& ) { throw; } } @@ -617,33 +608,25 @@ void core_sqlsrv_get_server_version( _Inout_ sqlsrv_conn* conn, _Inout_ zval* se void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *server_info ) { try { - - sqlsrv_malloc_auto_ptr buffer; + char buffer[INFO_BUFFER_LEN] = ""; SQLSMALLINT buffer_len = 0; // Get the database name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len ); + core::SQLGetInfo(conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len); // initialize the array - core::sqlsrv_array_init( *conn, server_info ); + array_init(server_info); - core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ ); - buffer.transferred(); + add_assoc_string(server_info, "CurrentDatabase", buffer); // Get the server version - get_server_version( conn, &buffer, buffer_len ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ ); - buffer.transferred(); + core::SQLGetInfo(conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len); + add_assoc_string(server_info, "SQLServerVersion", buffer); // Get the server name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len ); - core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ ); - buffer.transferred(); - } - - catch( core::CoreException& ) { + core::SQLGetInfo(conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len); + add_assoc_string(server_info, "SQLServerName", buffer); + } catch (core::CoreException&) { throw; } } @@ -657,39 +640,29 @@ void core_sqlsrv_get_server_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *server_ void core_sqlsrv_get_client_info( _Inout_ sqlsrv_conn* conn, _Out_ zval *client_info ) { try { - - sqlsrv_malloc_auto_ptr buffer; + char buffer[INFO_BUFFER_LEN] = ""; SQLSMALLINT buffer_len = 0; // Get the ODBC driver's dll name - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len ); // initialize the array - core::sqlsrv_array_init( *conn, client_info ); + array_init(client_info); #ifndef _WIN32 - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverName", buffer, 0 /*duplicate*/ ); + add_assoc_string(client_info, "DriverName", buffer); #else - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ ); + add_assoc_string(client_info, "DriverDllName", buffer); #endif // !_WIN32 - buffer.transferred(); // Get the ODBC driver's ODBC version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ ); - buffer.transferred(); + add_assoc_string(client_info, "DriverODBCVer", buffer); // Get the OBDC driver's version - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len ); - core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ ); - buffer.transferred(); - - } - - catch( core::CoreException& ) { + add_assoc_string(client_info, "DriverVer", buffer); + } catch( core::CoreException& ) { throw; } } @@ -918,30 +891,6 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou } } - -// get_server_version -// Helper function which returns the version of the SQL Server we are connected to. - -void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len ) -{ - try { - - sqlsrv_malloc_auto_ptr buffer; - SQLSMALLINT buffer_len = 0; - - buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN )); - core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len ); - *server_version = buffer; - len = buffer_len; - buffer.transferred(); - } - - catch( core::CoreException& ) { - throw; - } -} - - // get_processor_arch // Calls GetSystemInfo to verify the what architecture of the processor is supported // and return the string of the processor name. diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 41a35d7c0..0b55a5238 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -2498,77 +2498,6 @@ namespace core { } } - - // exception thrown when a zend function wrapped here fails. - - // wrappers for the zend functions called by our driver. These functions hook into the error reporting of our driver and throw - // exceptions when an error occurs. They are prefaced with sqlsrv_ because many of the zend functions are - // actually macros that call other functions, so the sqlsrv_ is necessary to differentiate them from the macro system. - // If there is a zend function in the source that isn't found here, it is because it returns void and there is no error - // that can be thrown from it. - - inline void sqlsrv_add_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zend_ulong index, _In_ zval* value) - { - int zr = add_index_zval( array, index, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_next_index_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array, _In_ zval* value) - { - int zr = add_next_index_zval( array, value ); - CHECK_ZEND_ERROR( zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_null( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key ) - { - int zr = ::add_assoc_null( array_z, key ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_long( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _In_ zend_long val ) - { - int zr = ::add_assoc_long( array_z, key, val ); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_add_assoc_string( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _Inout_z_ char* val, _In_ bool duplicate ) - { - int zr = ::add_assoc_string(array_z, key, val); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - if (duplicate == 0) { - sqlsrv_free(val); - } - } - - inline void sqlsrv_add_assoc_zval( _Inout_ sqlsrv_context& ctx, _Inout_ zval* array_z, _In_ const char* key, _In_ zval* val ) - { - int zr = ::add_assoc_zval(array_z, key, val); - CHECK_ZEND_ERROR (zr, ctx, SQLSRV_ERROR_ZEND_HASH ) { - throw CoreException(); - } - } - - inline void sqlsrv_array_init( _Inout_ sqlsrv_context& ctx, _Out_ zval* new_array) - { -#if PHP_VERSION_ID < 70300 - CHECK_ZEND_ERROR(::array_init(new_array), ctx, SQLSRV_ERROR_ZEND_HASH) { - throw CoreException(); - } -#else - array_init(new_array); -#endif - } - inline void sqlsrv_php_stream_from_zval_no_verify( _Inout_ sqlsrv_context& ctx, _Outref_result_maybenull_ php_stream*& stream, _In_opt_ zval* stream_z ) { // this duplicates the macro php_stream_from_zval_no_verify, which we can't use because it has an assignment diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 27fd1cae3..f7629a917 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -159,7 +159,7 @@ sqlsrv_stmt::sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error { ZVAL_UNDEF( &active_stream ); // initialize the input string parameters array (which holds zvals) - core::sqlsrv_array_init( *conn, ¶m_input_strings ); + array_init(¶m_input_strings); // initialize the (input only) stream parameters (which holds sqlsrv_stream structures) ZVAL_NEW_ARR( ¶m_streams ); @@ -574,7 +574,7 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ } buffer = Z_STRVAL_P( &wbuffer_z ); buffer_len = Z_STRLEN_P( &wbuffer_z ); - core::sqlsrv_add_index_zval( *stmt, &( stmt->param_input_strings ), param_num, &wbuffer_z ); + add_index_zval(&(stmt->param_input_strings), param_num, &wbuffer_z); } ind_ptr = buffer_len; if( direction != SQL_PARAM_INPUT ){ diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 930455ae0..4bcdff31e 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -617,27 +617,27 @@ namespace data_classification { zval data_classification; ZVAL_UNDEF(&data_classification); - core::sqlsrv_array_init(*stmt, &data_classification ); + array_init(&data_classification); USHORT num_pairs = meta->columns_sensitivity[colno].num_pairs; if (num_pairs == 0) { - core::sqlsrv_add_assoc_zval(*stmt, return_array, DATA_CLASS, &data_classification); + add_assoc_zval(return_array, DATA_CLASS, &data_classification); return 0; } zval sensitivity_properties; ZVAL_UNDEF(&sensitivity_properties); - core::sqlsrv_array_init(*stmt, &sensitivity_properties); + array_init(&sensitivity_properties); for (USHORT j = 0; j < num_pairs; j++) { zval label_array, infotype_array; ZVAL_UNDEF(&label_array); ZVAL_UNDEF(&infotype_array); - core::sqlsrv_array_init(*stmt, &label_array); - core::sqlsrv_array_init(*stmt, &infotype_array); + array_init(&label_array); + array_init(&infotype_array); USHORT labelidx = meta->columns_sensitivity[colno].label_info_pairs[j].label_idx; USHORT typeidx = meta->columns_sensitivity[colno].label_info_pairs[j].infotype_idx; @@ -647,22 +647,22 @@ namespace data_classification { char *infotype = meta->infotypes[typeidx]->name; char *infotype_id = meta->infotypes[typeidx]->id; - core::sqlsrv_add_assoc_string(*stmt, &label_array, NAME, label, 1); - core::sqlsrv_add_assoc_string(*stmt, &label_array, ID, label_id, 1); + add_assoc_string(&label_array, NAME, label); + add_assoc_string(&label_array, ID, label_id); - core::sqlsrv_add_assoc_zval(*stmt, &sensitivity_properties, LABEL, &label_array); + add_assoc_zval(&sensitivity_properties, LABEL, &label_array); - core::sqlsrv_add_assoc_string(*stmt, &infotype_array, NAME, infotype, 1); - core::sqlsrv_add_assoc_string(*stmt, &infotype_array, ID, infotype_id, 1); + add_assoc_string(&infotype_array, NAME, infotype); + add_assoc_string(&infotype_array, ID, infotype_id); - core::sqlsrv_add_assoc_zval(*stmt, &sensitivity_properties, INFOTYPE, &infotype_array); + add_assoc_zval(&sensitivity_properties, INFOTYPE, &infotype_array); // add the pair of sensitivity properties to data_classification - core::sqlsrv_add_next_index_zval(*stmt, &data_classification, &sensitivity_properties ); + add_next_index_zval(&data_classification, &sensitivity_properties); } // add data classfication as associative array - core::sqlsrv_add_assoc_zval(*stmt, return_array, DATA_CLASS, &data_classification); + add_assoc_zval(return_array, DATA_CLASS, &data_classification); return num_pairs; } diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index d8336e7c7..b083aa1e7 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -967,28 +967,22 @@ PHP_FUNCTION( sqlsrv_rollback ) PHP_FUNCTION( sqlsrv_client_info ) { - LOG_FUNCTION( "sqlsrv_client_info" ); ss_sqlsrv_conn* conn = NULL; PROCESS_PARAMS( conn, "r", _FN_, 0 ); try { + core_sqlsrv_get_client_info(conn, return_value); - core_sqlsrv_get_client_info( conn, return_value ); - // Add the sqlsrv driver's file version //Declarations below eliminate compiler warnings about string constant to char* conversions const char* extver = "ExtensionVer"; std::string filever = VER_FILEVERSION_STR; - core::sqlsrv_add_assoc_string( *conn, return_value, extver, &filever[0], 1 /*duplicate*/ ); - } - - catch( core::CoreException& ) { + add_assoc_string(return_value, extver, &filever[0]); + } catch (core::CoreException&) { RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_client_info: Unknown exception caught." ); + } catch (...) { + DIE("sqlsrv_client_info: Unknown exception caught."); } } @@ -1011,20 +1005,15 @@ PHP_FUNCTION( sqlsrv_client_info ) PHP_FUNCTION( sqlsrv_server_info ) { try { - - LOG_FUNCTION( "sqlsrv_server_info" ); + LOG_FUNCTION("sqlsrv_server_info"); ss_sqlsrv_conn* conn = NULL; - PROCESS_PARAMS( conn, "r", _FN_, 0 ); - - core_sqlsrv_get_server_info( conn, return_value ); - } + PROCESS_PARAMS(conn, "r", _FN_, 0); - catch( core::CoreException& ) { + core_sqlsrv_get_server_info(conn, return_value); + } catch (core::CoreException&) { RETURN_FALSE; - } - catch( ... ) { - - DIE( "sqlsrv_server_info: Unknown exception caught." ); + } catch (...) { + DIE("sqlsrv_server_info: Unknown exception caught."); } } diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 0e19771dc..36355f7ba 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -499,8 +499,8 @@ PHP_FUNCTION( sqlsrv_field_metadata ) } zval result_meta_data; - ZVAL_UNDEF( &result_meta_data ); - core::sqlsrv_array_init( *stmt, &result_meta_data ); + ZVAL_UNDEF(&result_meta_data); + array_init(&result_meta_data); for( SQLSMALLINT f = 0; f < num_cols; ++f ) { field_meta_data* core_meta_data = stmt->current_meta_data[f]; @@ -508,13 +508,13 @@ PHP_FUNCTION( sqlsrv_field_metadata ) // initialize the array zval field_array; ZVAL_UNDEF( &field_array ); - core::sqlsrv_array_init( *stmt, &field_array ); + array_init(&field_array ); // add the field name to the associative array but keep a copy - core::sqlsrv_add_assoc_string(*stmt, &field_array, FieldMetaData::NAME, - reinterpret_cast(core_meta_data->field_name.get()), 1); + add_assoc_string(&field_array, FieldMetaData::NAME, reinterpret_cast(core_meta_data->field_name.get())); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type ); + //core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type ); + add_assoc_long(&field_array, FieldMetaData::TYPE, core_meta_data->field_type); switch( core_meta_data->field_type ) { case SQL_DECIMAL: @@ -523,9 +523,9 @@ PHP_FUNCTION( sqlsrv_field_metadata ) case SQL_TYPE_DATE: case SQL_SS_TIME2: case SQL_SS_TIMESTAMPOFFSET: - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SCALE, core_meta_data->field_scale ); + add_assoc_null(&field_array, FieldMetaData::SIZE); + add_assoc_long(&field_array, FieldMetaData::PREC, core_meta_data->field_precision); + add_assoc_long(&field_array, FieldMetaData::SCALE, core_meta_data->field_scale); break; case SQL_BIT: case SQL_TINYINT: @@ -535,26 +535,26 @@ PHP_FUNCTION( sqlsrv_field_metadata ) case SQL_REAL: case SQL_FLOAT: case SQL_DOUBLE: - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SIZE ); - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::PREC, core_meta_data->field_precision ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE ); + add_assoc_null(&field_array, FieldMetaData::SIZE); + add_assoc_long(&field_array, FieldMetaData::PREC, core_meta_data->field_precision); + add_assoc_null(&field_array, FieldMetaData::SCALE); break; default: - core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::SIZE, core_meta_data->field_size ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::PREC ); - core::sqlsrv_add_assoc_null( *stmt, &field_array, FieldMetaData::SCALE ); + add_assoc_long(&field_array, FieldMetaData::SIZE, core_meta_data->field_size); + add_assoc_null(&field_array, FieldMetaData::PREC); + add_assoc_null(&field_array, FieldMetaData::SCALE); break; } // add the nullability to the array - core::sqlsrv_add_assoc_long(*stmt, &field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable); + add_assoc_long(&field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable); if (stmt->data_classification) { data_classification::fill_column_sensitivity_array(stmt, f, &field_array); } // add this field's meta data to the result set meta data - core::sqlsrv_add_next_index_zval( *stmt, &result_meta_data, &field_array ); + add_next_index_zval(&result_meta_data, &field_array); } // return our built collection and transfer ownership @@ -1873,54 +1873,45 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ } int zr = SUCCESS; -#if PHP_VERSION_ID < 70300 - CHECK_ZEND_ERROR(array_init(&fields), stmt, SQLSRV_ERROR_ZEND_HASH) { - throw ss::SSException(); - } -#else array_init(&fields); -#endif - for( int i = 0; i < num_cols; ++i ) { - SQLLEN field_len = -1; + for( int i = 0; i < num_cols; ++i ) { + SQLLEN field_len = -1; - core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/, - field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out ); + core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/, + field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out ); - zval field; - ZVAL_UNDEF( &field ); - convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field ); - sqlsrv_free( field_value ); - if( fetch_type & SQLSRV_FETCH_NUMERIC ) { + zval field; + ZVAL_UNDEF( &field ); + convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field ); + sqlsrv_free( field_value ); + if( fetch_type & SQLSRV_FETCH_NUMERIC ) { - zr = add_next_index_zval( &fields, &field ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } - } + zr = add_next_index_zval( &fields, &field ); + CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { + throw ss::SSException(); + } + } - if( fetch_type & SQLSRV_FETCH_ASSOC ) { + if( fetch_type & SQLSRV_FETCH_ASSOC ) { - CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 0 && !allow_empty_field_names ), stmt, - SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) { - throw ss::SSException(); - } + CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 0 && !allow_empty_field_names ), stmt, + SS_SQLSRV_WARNING_FIELD_NAME_EMPTY) { + throw ss::SSException(); + } - if( stmt->fetch_field_names[i].len > 0 || allow_empty_field_names ) { + if( stmt->fetch_field_names[i].len > 0 || allow_empty_field_names ) { - zr = add_assoc_zval( &fields, stmt->fetch_field_names[i].name, &field ); - CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) { - throw ss::SSException(); - } - } - } - //only addref when the fetch_type is BOTH because this is the only case when fields(hashtable) - //has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because - //fields now only has 1 element pointing to field and we want the ref count to be only 1 - if (fetch_type == SQLSRV_FETCH_BOTH) { - Z_TRY_ADDREF(field); - } - } //for loop + add_assoc_zval(&fields, stmt->fetch_field_names[i].name, &field); + } + } + //only addref when the fetch_type is BOTH because this is the only case when fields(hashtable) + //has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because + //fields now only has 1 element pointing to field and we want the ref count to be only 1 + if (fetch_type == SQLSRV_FETCH_BOTH) { + Z_TRY_ADDREF(field); + } + } //for loop } diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index c240dde17..d1de883e5 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -519,13 +519,7 @@ PHP_FUNCTION( sqlsrv_errors ) } zval err_z; ZVAL_UNDEF(&err_z); -#if PHP_VERSION_ID < 70300 - if (array_init(&err_z) == FAILURE) { - RETURN_FALSE; - } -#else array_init(&err_z); -#endif if( flags == SQLSRV_ERR_ALL || flags == SQLSRV_ERR_ERRORS ) { if( Z_TYPE( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( &err_z, &SQLSRV_G( errors ) )) { @@ -763,47 +757,35 @@ sqlsrv_error_const* get_error_message( _In_ unsigned int sqlsrv_error_code ) { void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, _Inout_ zval* reported_chain, _Inout_ zval* ignored_chain, _In_ bool warning ) { -#if PHP_VERSION_ID < 70300 - if (array_init(error_z) == FAILURE) { - DIE( "Fatal error during error processing" ); - } -#else array_init(error_z); -#endif // sqlstate zval temp; - ZVAL_UNDEF(&temp); - core::sqlsrv_zval_stringl( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); - Z_TRY_ADDREF_P( &temp ); + ZVAL_UNDEF(&temp); + core::sqlsrv_zval_stringl( &temp, reinterpret_cast( error->sqlstate ), SQL_SQLSTATE_SIZE ); + Z_TRY_ADDREF_P( &temp ); if( add_next_index_zval( error_z, &temp ) == FAILURE ) { DIE( "Fatal error during error processing" ); } - if( add_assoc_zval( error_z, "SQLSTATE", &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } + add_assoc_zval(error_z, "SQLSTATE", &temp); // native_code if( add_next_index_long( error_z, error->native_code ) == FAILURE ) { DIE( "Fatal error during error processing" ); } - if( add_assoc_long( error_z, "code", error->native_code ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } + add_assoc_long(error_z, "code", error->native_code); // native_message - ZVAL_UNDEF(&temp); + ZVAL_UNDEF(&temp); ZVAL_STRING( &temp, reinterpret_cast( error->native_message ) ); - Z_TRY_ADDREF_P(&temp); + Z_TRY_ADDREF_P(&temp); if( add_next_index_zval( error_z, &temp ) == FAILURE ) { DIE( "Fatal error during error processing" ); } - if( add_assoc_zval( error_z, "message", &temp ) == FAILURE ) { - DIE( "Fatal error during error processing" ); - } + add_assoc_zval(error_z, "message", &temp); // If it is an error or if warning_return_as_errors is true than // add the error or warning to the reported_chain. @@ -811,7 +793,7 @@ void copy_error_to_zval( _Inout_ zval* error_z, _In_ sqlsrv_error_const* error, { // if the warning is part of the ignored warning list than // add to the ignored chain if the ignored chain is not null. - if( warning && ignore_warning( reinterpret_cast(error->sqlstate), error->native_code ) && + if( warning && ignore_warning( reinterpret_cast(error->sqlstate), error->native_code ) && ignored_chain != NULL ) { if( add_next_index_zval( ignored_chain, error_z ) == FAILURE ) { @@ -854,13 +836,7 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo if( Z_TYPE_P( reported_chain ) == IS_NULL ) { reported_chain_was_null = true; -#if PHP_VERSION_ID < 70300 - if (array_init(reported_chain) == FAILURE) { - DIE( "Fatal error during error processing" ); - } -#else array_init(reported_chain); -#endif } else { prev_reported_cnt = zend_hash_num_elements( Z_ARRVAL_P( reported_chain )); @@ -872,13 +848,7 @@ bool handle_errors_and_warnings( _Inout_ sqlsrv_context& ctx, _Inout_ zval* repo if( Z_TYPE_P( ignored_chain ) == IS_NULL ) { ignored_chain_was_null = true; -#if PHP_VERSION_ID < 70300 - if (array_init(ignored_chain) == FAILURE) { - DIE( "Fatal error in handle_errors_and_warnings" ); - } -#else array_init( ignored_chain ); -#endif } } From e1e0108b1f5c43d88f787e96516c5e3198f43406 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 23 Jul 2020 16:07:41 -0700 Subject: [PATCH 227/249] Dropped the use of LOCK TIMEOUT (#1165) --- source/pdo_sqlsrv/pdo_stmt.cpp | 9 --- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 3 - source/shared/core_sqlsrv.h | 4 +- source/shared/core_stmt.cpp | 9 +++ source/sqlsrv/php_sqlsrv_int.h | 3 - source/sqlsrv/stmt.cpp | 23 ------ .../pdo_1100_query_timeout_disconnect.phpt | 72 +++++++++++++++++++ test/functional/sqlsrv/test_timeout.phpt | 11 +-- 8 files changed, 90 insertions(+), 44 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1100_query_timeout_disconnect.phpt diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 062c43e54..2c4fc16db 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1523,12 +1523,3 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, return sqlsrv_phptype; } - -void pdo_sqlsrv_stmt::set_query_timeout() -{ - if (query_timeout == QUERY_TIMEOUT_INVALID || query_timeout < 0) { - return; - } - - core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast((SQLLEN)query_timeout), SQL_IS_UINTEGER); -} \ No newline at end of file diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index e81cf09de..7d377496f 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -260,9 +260,6 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { // for PDO, everything is a string, so we return SQLSRV_PHPTYPE_STRING for all SQL types virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); - // driver specific way to set query timeout - virtual void set_query_timeout(); - bool direct_query; // flag set if the query should be executed directly or prepared const char* direct_query_subst_string; // if the query is direct, hold the substitution string if using named parameters size_t direct_query_subst_string_len; // length of query string used for direct queries diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 0b55a5238..f1cd5e3db 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1520,6 +1520,8 @@ struct sqlsrv_stmt : public sqlsrv_context { // free sensitivity classification metadata void clean_up_sensitivity_metadata(); + // set query timeout + void set_query_timeout(); sqlsrv_conn* conn; // Connection that created this statement @@ -1571,8 +1573,6 @@ struct sqlsrv_stmt : public sqlsrv_context { // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants virtual sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ) = 0; - // driver specific way to set query timeout - virtual void set_query_timeout() = 0; }; // *** field metadata struct *** diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index f7629a917..c4eb5cba0 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -275,6 +275,15 @@ void sqlsrv_stmt::clean_up_sensitivity_metadata() } } +void sqlsrv_stmt::set_query_timeout() +{ + if (query_timeout == QUERY_TIMEOUT_INVALID || query_timeout < 0) { + return; + } + + core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast((SQLLEN)query_timeout), SQL_IS_UINTEGER); +} + // core_sqlsrv_create_stmt // Common code to allocate a statement from either driver. Returns a valid driver statement object or // throws an exception if an error occurs. diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index 43f91f2e2..e04bceeb4 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -130,9 +130,6 @@ struct ss_sqlsrv_stmt : public sqlsrv_stmt { // driver specific conversion rules from a SQL Server/ODBC type to one of the SQLSRV_PHPTYPE_* constants sqlsrv_phptype sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream ); - // driver specific way to set query timeout - virtual void set_query_timeout(); - bool prepared; // whether the statement has been prepared yet (used for error messages) zend_ulong conn_index; // index into the connection hash that contains this statement structure zval* params_z; // hold parameters passed to sqlsrv_prepare but not used until sqlsrv_execute diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 36355f7ba..ea1bfe546 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -261,29 +261,6 @@ sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _ return ss_phptype; } -void ss_sqlsrv_stmt::set_query_timeout() -{ - if (query_timeout == QUERY_TIMEOUT_INVALID || query_timeout < 0) { - return; - } - - // set the statement attribute - core::SQLSetStmtAttr(this, SQL_ATTR_QUERY_TIMEOUT, reinterpret_cast( (SQLLEN)query_timeout ), SQL_IS_UINTEGER ); - - // a query timeout of 0 indicates "no timeout", which means that lock_timeout should also be set to "no timeout" which - // is represented by -1. - int lock_timeout = (( query_timeout == 0 ) ? -1 : query_timeout * 1000 /*convert to milliseconds*/ ); - - // set the LOCK_TIMEOUT on the server. - char lock_timeout_sql[32] = {'\0'}; - - int written = snprintf( lock_timeout_sql, sizeof( lock_timeout_sql ), "SET LOCK_TIMEOUT %d", lock_timeout ); - SQLSRV_ASSERT( (written != -1 && written != sizeof( lock_timeout_sql )), - "stmt_option_query_timeout: snprintf failed. Shouldn't ever fail." ); - - core::SQLExecDirect(this, lock_timeout_sql ); -} - // statement specific parameter proccessing. Uses the generic function specialised to return a statement // resource. #define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \ diff --git a/test/functional/pdo_sqlsrv/pdo_1100_query_timeout_disconnect.phpt b/test/functional/pdo_sqlsrv/pdo_1100_query_timeout_disconnect.phpt new file mode 100644 index 000000000..36cf8fc88 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1100_query_timeout_disconnect.phpt @@ -0,0 +1,72 @@ +--TEST-- +GitHub issue 1100 - PDO::SQLSRV_ATTR_QUERY_TIMEOUT had no effect when reconnecting +--DESCRIPTION-- +This test verifies that setting PDO::SQLSRV_ATTR_QUERY_TIMEOUT should work when reconnecting after disconnecting +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $leeway); + trace("$elapsed secs elapsed\n"); + + if ($missed) { + echo "Expected $expectedDelay but $elapsed secs elapsed\n"; + } +} + +function testTimeout($conn, $timeout) +{ + $delay = 5; + $query = "WAITFOR DELAY '00:00:$delay'; SELECT 1"; + $error = '*Query timeout expired'; + + $t0 = microtime(true); + try { + $conn->exec($query); + $elapsed = microtime(true) - $t0; + echo "Should have failed after $timeout secs but $elapsed secs have elapsed" . PHP_EOL; + } catch (PDOException $e) { + $t1 = microtime(true); + + $message = '*Query timeout expired'; + if (!fnmatch($message, $e->getMessage())) { + var_dump($e->getMessage()); + } + checkTimeElapsed($t0, $t1, $timeout); + } +} + +try { + $keywords = 'MultipleActiveResultSets=false;'; + $timeout = 1; + + $options = array(PDO::SQLSRV_ATTR_QUERY_TIMEOUT => $timeout); + $conn = connect($keywords, $options); + + testTimeout($conn, $timeout); + unset($conn); + + $conn = connect($keywords); + $conn->setAttribute(PDO::SQLSRV_ATTR_QUERY_TIMEOUT, $timeout); + + testTimeout($conn, $timeout); + unset($conn); + + echo "Done\n"; +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} + +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/test_timeout.phpt b/test/functional/sqlsrv/test_timeout.phpt index 195faf63f..28c83ce06 100644 --- a/test/functional/sqlsrv/test_timeout.phpt +++ b/test/functional/sqlsrv/test_timeout.phpt @@ -10,19 +10,23 @@ sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL ); require( 'MsCommon.inc' ); -$throwaway = Connect(array( 'ConnectionPooling' => 1 )); +// MARS allows applications to have more than one pending request per connection and to have more +// than one active default result set per connection, which is not required for this test. +$options = array('ConnectionPooling' => 1, 'MultipleActiveResultSets' => 0); + +$throwaway = Connect(); if( !$throwaway ) { die( print_r( sqlsrv_errors(), true )); } for( $i = 1; $i <= 3; ++$i ) { - $conn = Connect(array( 'ConnectionPooling' => 1 )); + $conn = Connect($options); if( !$conn ) { die( print_r( sqlsrv_errors(), true )); } - $conn2 = Connect(array( 'ConnectionPooling' => 1 )); + $conn2 = Connect($options); if( !$conn2 ) { die( print_r( sqlsrv_errors(), true )); } @@ -47,7 +51,6 @@ for( $i = 1; $i <= 3; ++$i ) { die( print_r( sqlsrv_errors(), true )); } - $stmt2 = sqlsrv_query( $conn2, "WAITFOR DELAY '00:00:05'; SELECT * FROM [test_query_timeout]", array(null), array( 'QueryTimeout' => 1 )); if( $stmt2 === false ) { print_r( sqlsrv_errors() ); From e9090a4c2b228022316897a29d64568fb2c88494 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 24 Jul 2020 18:31:39 -0700 Subject: [PATCH 228/249] Added new tests for testing gb18030 (#1166) --- .../pdo_sqlsrv/pdo_ansi_locale_fr.phpt | 9 ++- .../pdo_sqlsrv/pdo_ansi_locale_zh.phpt | 53 +++++++++++++++ .../pdo_sqlsrv/pdo_test_gb18030.php | 63 +++++++++++++++++ .../pdo_sqlsrv/skipif_unix_ansitests.inc | 9 +-- .../sqlsrv/skipif_unix_ansitests.inc | 9 +-- .../sqlsrv/sqlsrv_ansi_locale_fr.phpt | 11 ++- .../sqlsrv/sqlsrv_ansi_locale_zh.phpt | 55 +++++++++++++++ .../functional/sqlsrv/sqlsrv_test_gb18030.php | 67 +++++++++++++++++++ 8 files changed, 258 insertions(+), 18 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_ansi_locale_zh.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_test_gb18030.php create mode 100644 test/functional/sqlsrv/sqlsrv_ansi_locale_zh.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_test_gb18030.php diff --git a/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt b/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt index b26d4be10..bcc348e42 100644 --- a/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ansi_locale_fr.phpt @@ -5,7 +5,14 @@ This file must be saved in ANSI encoding and the required locale must be present --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- test_gb18030.php +Drop the temporary database when the test is finished. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- +exec($query); + + shell_exec("export LC_ALL=zh_CN.gb18030"); + shell_exec("iconv -c -f GB2312 -t GB18030 pdo_test_gb18030.php > test_gb18030.php"); + + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/test_gb18030.php $tempDB")); +} + +try { + $tempDB = 'GB18030test' . rand(1, 100); + require_once('MsSetup.inc'); + + $conn = new PDO("sqlsrv:server = $server;database=master;driver=$driver", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + runTest($conn, $tempDB); +} catch (PDOException $e) { + echo $e->getMessage() . PHP_EOL; +} finally { + $query = "DROP DATABASE $tempDB"; + $conn->exec($query); + unset($conn); +} + +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdo_test_gb18030.php b/test/functional/pdo_sqlsrv/pdo_test_gb18030.php new file mode 100644 index 000000000..4eefc1f2a --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_test_gb18030.php @@ -0,0 +1,63 @@ +prepare($tsql); + + $stmt->execute(array($text)); +} + +require_once('MsSetup.inc'); + +$tempDB = ($_SERVER['argv'][1]); + +setlocale(LC_ALL, 'zh_CN.gb18030'); + +try { + $conn = new PDO("sqlsrv:server = $server;database=$tempDB;driver=$driver", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $conn->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_SYSTEM); + + $tsql = "CREATE TABLE test1([id] int identity, [name] [varchar](50) NULL)"; + $stmt = $conn->query($tsql); + + // Next, insert the strings + $inputs = array('ÖÐÎÄ', 'ÄãºÃ', 'δÕÒµ½ÐÅÏ¢', '»ñÈ¡¸ü¶à'); + $hexValues = array('d6d0cec4', 'c4e3bac3', 'ceb4d5d2b5bdd0c5cfa2', 'bbf1c8a1b8fcb6e0'); + for ($i = 0; $i < 4; $i++) { + insertText($conn, $inputs[$i], $hexValues[$i]); + } + + // Next, fetch the strings + $tsql = "SELECT * FROM test1"; + $stmt = $conn->query($tsql); + + $i = 0; + while ($result = $stmt->fetch(PDO::FETCH_NUM)) { + $name = $result[1]; + if ($name !== $inputs[$i]) { + echo "Expected $inputs[$i] but got $name" . PHP_EOL; + } + $i++; + } +} catch (PDOException $e) { + echo $e->getMessage() . PHP_EOL; +} finally { + $tsql = "DROP TABLE test1"; + $conn->exec($tsql); + + unset($stmt); + unset($conn); +} + +echo "Done"; + +?> + diff --git a/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc b/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc index 606824c43..d0da3b4e9 100644 --- a/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc +++ b/test/functional/pdo_sqlsrv/skipif_unix_ansitests.inc @@ -12,11 +12,4 @@ require_once('MsSetup.inc'); if ($localeDisabled) { die("skip not set up to test ansi locale"); } - -$loc = setlocale(LC_ALL, 'fr_FR@euro'); -$loc1 = setlocale(LC_ALL, 'fr_FR.ISO8859-15'); -if (empty($loc) && empty($loc1)) { - die("skip required French locale not available"); -} - -?> \ No newline at end of file +?> diff --git a/test/functional/sqlsrv/skipif_unix_ansitests.inc b/test/functional/sqlsrv/skipif_unix_ansitests.inc index 8a2838721..373eb95e1 100644 --- a/test/functional/sqlsrv/skipif_unix_ansitests.inc +++ b/test/functional/sqlsrv/skipif_unix_ansitests.inc @@ -12,11 +12,4 @@ require_once('MsSetup.inc'); if ($localeDisabled) { die("skip not set up to test ansi locale"); } - -$loc = setlocale(LC_ALL, 'fr_FR@euro'); -$loc1 = setlocale(LC_ALL, 'fr_FR.ISO8859-15'); -if (empty($loc) && empty($loc1)) { - die("skip required French locale not available"); -} - -?> \ No newline at end of file +?> diff --git a/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt b/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt index d0839f2d0..bc9009e47 100644 --- a/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt +++ b/test/functional/sqlsrv/sqlsrv_ansi_locale_fr.phpt @@ -5,7 +5,16 @@ This file must be saved in ANSI encoding and the required locale must be present --ENV-- PHPT_EXEC=true --SKIPIF-- - + --FILE-- test_gb18030.php +Drop the temporary database when the test is finished. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- +"master", "UID"=>$userName, "PWD"=>$userPassword); + $conn = sqlsrv_connect($server, $options); + if( $conn === false ) { + die(print_r(sqlsrv_errors(), true)); + } + + $tempDB = 'GB18030test' . rand(1, 100); + $query = "CREATE DATABASE $tempDB COLLATE Chinese_PRC_CI_AS"; + $stmt = sqlsrv_query($conn, $query); + if ($stmt === false) { + echo "Failed to create the database $tempDB\n"; + } + + shell_exec("export LC_ALL=zh_CN.gb18030"); + shell_exec("iconv -c -f GB2312 -t GB18030 sqlsrv_test_gb18030.php > test_gb18030.php"); + + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/test_gb18030.php $tempDB")); + + $query = "DROP DATABASE $tempDB"; + sqlsrv_query($conn, $query); + sqlsrv_close($conn); +} + +runTest(); + +?> +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_test_gb18030.php b/test/functional/sqlsrv/sqlsrv_test_gb18030.php new file mode 100644 index 000000000..10c125f6b --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_test_gb18030.php @@ -0,0 +1,67 @@ +$tempDB, "UID"=>$userName, "PWD"=>$userPassword); +$conn = sqlsrv_connect($server, $options); +if( $conn === false ) { + die( print_r( sqlsrv_errors(), true )); +} + +$tsql = "CREATE TABLE test1([id] int identity, [name] [varchar](50) NULL)"; +$stmt = sqlsrv_query($conn, $tsql); + +// Next, insert the strings +$inputs = array('ÖÐÎÄ', 'ÄãºÃ', 'δÕÒµ½ÐÅÏ¢', '»ñÈ¡¸ü¶à'); +$hexValues = array('d6d0cec4', 'c4e3bac3', 'ceb4d5d2b5bdd0c5cfa2', 'bbf1c8a1b8fcb6e0'); +for ($i = 0; $i < 4; $i++) { + insertText($conn, $inputs[$i], $hexValues[$i]); +} + +// Next, fetch the strings +$tsql = "SELECT * FROM test1"; +$stmt = sqlsrv_query($conn, $tsql); +if ($stmt === false) { + var_dump(sqlsrv_errors()); +} + +$i = 0; +while (sqlsrv_fetch($stmt)) { + $name = sqlsrv_get_field($stmt, 1); + if ($name !== $inputs[$i]) { + echo "Expected $inputs[$i] but got $name" . PHP_EOL; + } + $i++; +} + +$tsql = "DROP TABLE test1"; +sqlsrv_query($conn, $tsql); + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +echo "Done"; + +?> + From cb489c3e6918c66a0301a61c4a6c95e697fe1211 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 29 Jul 2020 10:43:00 -0700 Subject: [PATCH 229/249] Added right locales and updated locale tests to handle errors (#1168) --- Dockerfile-msphpsql | 2 + azure-pipelines.yml | 2 + .../pdo_sqlsrv/pdo_ansi_locale_zh.phpt | 34 +++++++++-- .../sqlsrv/sqlsrv_ansi_locale_zh.phpt | 58 ++++++++++++++----- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/Dockerfile-msphpsql b/Dockerfile-msphpsql index 477740a00..7d5f65850 100644 --- a/Dockerfile-msphpsql +++ b/Dockerfile-msphpsql @@ -45,6 +45,8 @@ RUN sed -i 's/# en_US ISO-8859-1/en_US ISO-8859-1/g' /etc/locale.gen RUN sed -i 's/# fr_FR@euro ISO-8859-15/fr_FR@euro ISO-8859-15/g' /etc/locale.gen RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen RUN sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen +RUN sed -i 's/# zh_CN GB2312/zh_CN GB2312/g' /etc/locale.gen +RUN sed -i 's/# zh_CN.GB18030 GB18030/zh_CN.GB18030 GB18030/g' /etc/locale.gen RUN locale-gen # set locale to utf-8 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 23579eab0..6f7f2185d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -114,6 +114,8 @@ jobs: sudo sed -i 's/# fr_FR@euro ISO-8859-15/fr_FR@euro ISO-8859-15/g' /etc/locale.gen sudo sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/g' /etc/locale.gen sudo sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen + sudo sed -i 's/# zh_CN GB2312/zh_CN GB2312/g' /etc/locale.gen + sudo sed -i 's/# zh_CN.GB18030 GB18030/zh_CN.GB18030 GB18030/g' /etc/locale.gen sudo locale-gen export LANG='en_US.UTF-8' export LANGUAGE='en_US:en' diff --git a/test/functional/pdo_sqlsrv/pdo_ansi_locale_zh.phpt b/test/functional/pdo_sqlsrv/pdo_ansi_locale_zh.phpt index cbdcb7395..6500a0cdc 100644 --- a/test/functional/pdo_sqlsrv/pdo_ansi_locale_zh.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ansi_locale_zh.phpt @@ -1,8 +1,8 @@ --TEST-- Test Chinese locale in Linux --DESCRIPTION-- -This test will invoke another php scirpt that is saved as GB2312(Simplified Chinese) ANSI format, -namely pdo_test_gb18030.php. +This test requires ODBC Driver 17.6 or above and will invoke another php script that +is saved as GB2312(Simplified Chinese) ANSI format, namely pdo_test_gb18030.php. To run this test, create a temporary database first with the correct collation CREATE DATABASE [GB18030test] COLLATE Chinese_PRC_CI_AS @@ -19,6 +19,28 @@ $loc = setlocale(LC_ALL, 'zh_CN.gb18030'); if (empty($loc)) { die("skip required gb18030 locale not available"); } + +require_once('MsSetup.inc'); +try { + $conn = new PDO("sqlsrv:server=$server", $uid, $pwd); + $msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)['DriverVer']; + $version = explode(".", $msodbcsqlVer); + + $msodbcsqlMaj = $version[0]; + $msodbcsqlMin = $version[1]; + + if ($msodbcsqlMaj < 17) { + die("skip Unsupported ODBC driver version"); + } + + if ($msodbcsqlMaj == 17 && $msodbcsqlMin < 6) { + die("skip Unsupported ODBC driver version"); + } +} catch (PDOException $e) { + die("skip Something went wrong during SKIPIF."); +} +?> + --FILE-- getMessage() . PHP_EOL; } finally { - $query = "DROP DATABASE $tempDB"; - $conn->exec($query); - unset($conn); + if ($conn) { + $query = "DROP DATABASE $tempDB"; + $conn->exec($query); + unset($conn); + } } ?> diff --git a/test/functional/sqlsrv/sqlsrv_ansi_locale_zh.phpt b/test/functional/sqlsrv/sqlsrv_ansi_locale_zh.phpt index 5b8b911cf..bfbee8b27 100644 --- a/test/functional/sqlsrv/sqlsrv_ansi_locale_zh.phpt +++ b/test/functional/sqlsrv/sqlsrv_ansi_locale_zh.phpt @@ -1,8 +1,8 @@ --TEST-- Test Chinese locale in Linux --DESCRIPTION-- -This test will invoke another php scirpt that is saved as GB2312(Simplified Chinese) ANSI format, -namely sqlsrv_test_gb18030.php. +This test requires ODBC Driver 17.6 or above and will invoke another php script that +is saved as GB2312(Simplified Chinese) ANSI format, namely sqlsrv_test_gb18030.php. To run this test, create a temporary database first with the correct collation CREATE DATABASE [GB18030test] COLLATE Chinese_PRC_CI_AS @@ -14,42 +14,68 @@ Drop the temporary database when the test is finished. PHPT_EXEC=true --SKIPIF-- $userName, "PWD"=>$userPassword); +$conn = sqlsrv_connect($server, $connectionInfo); +if ($conn === false) { + die("skip Could not connect during SKIPIF."); +} + +$msodbcsqlVer = sqlsrv_client_info($conn)["DriverVer"]; +$msodbcsqlMaj = explode(".", $msodbcsqlVer)[0]; +$msodbcsqlMin = explode(".", $msodbcsqlVer)[1]; + +if ($msodbcsqlMaj < 17) { + die("skip Unsupported ODBC driver version"); +} + +if ($msodbcsqlMaj == 17 && $msodbcsqlMin < 6) { + die("skip Unsupported ODBC driver version"); +} +?> + --FILE-- "master", "UID"=>$userName, "PWD"=>$userPassword); $conn = sqlsrv_connect($server, $options); if( $conn === false ) { - die(print_r(sqlsrv_errors(), true)); + throw new Error("Failed to connect"); } $tempDB = 'GB18030test' . rand(1, 100); $query = "CREATE DATABASE $tempDB COLLATE Chinese_PRC_CI_AS"; $stmt = sqlsrv_query($conn, $query); if ($stmt === false) { - echo "Failed to create the database $tempDB\n"; + throw new Error("Failed to create the database $tempDB"); } shell_exec("export LC_ALL=zh_CN.gb18030"); shell_exec("iconv -c -f GB2312 -t GB18030 sqlsrv_test_gb18030.php > test_gb18030.php"); - + print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/test_gb18030.php $tempDB")); - - $query = "DROP DATABASE $tempDB"; - sqlsrv_query($conn, $query); - sqlsrv_close($conn); +} catch (Error $err) { + echo $err->getMessage() . PHP_EOL; + print_r(sqlsrv_errors()); +} finally { + if ($conn) { + if (!empty($tempDB)) { + $query = "DROP DATABASE $tempDB"; + sqlsrv_query($conn, $query); + } + sqlsrv_close($conn); + } } -runTest(); - ?> --EXPECT-- Done From 6aa6c076817a3e2a7c3def46d681762e1bdfaafb Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 30 Jul 2020 13:58:50 -0700 Subject: [PATCH 230/249] Added tests to fetch empty strings with wrong php types (#1169) --- source/shared/core_stmt.cpp | 1 + test/functional/pdo_sqlsrv/values.php | 788 +----------------- .../sqlsrv/srv_013_sqlsrv_get_field.phpt | 3 +- .../sqlsrv/srv_014_sqlsrv_get_field.phpt | 3 +- .../srv_015_sqlsrv_get_field_nulls.phpt | 87 ++ 5 files changed, 96 insertions(+), 786 deletions(-) create mode 100644 test/functional/sqlsrv/srv_015_sqlsrv_get_field_nulls.phpt diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index c4eb5cba0..cccee0a97 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1844,6 +1844,7 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i { sqlsrv_malloc_auto_ptr field_value_temp; field_value_temp = static_cast( sqlsrv_malloc( sizeof( double ))); + *field_value_temp = 0.0; SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ), field_len, true /*handle_warning*/ ); diff --git a/test/functional/pdo_sqlsrv/values.php b/test/functional/pdo_sqlsrv/values.php index 3373224ad..b08504bea 100644 --- a/test/functional/pdo_sqlsrv/values.php +++ b/test/functional/pdo_sqlsrv/values.php @@ -2,14 +2,11 @@ // This file holds different data of many different types for testing // Always Encrypted. Currently, the tests that use this data are: -// pdo__ae_azure_key_vault_keywords.phpt ($small_values) + +// pdo_ae_azure_key_vault_keywords.phpt ($small_values) // pdo_ae_azure_key_vault_username_password.phpt ($small_values) // pdo_ae_azure_key_vault_client_secret.phpt ($small_values) -// sqlsrv_ae_fetch_phptypes.phpt ($values) -// sqlsrv_ae_type_conversion_select.phpt ($values) -// sqlsrv_ae_azure_key_vault_keywords.phpt ($small_values) -// sqlsrv_ae_azure_key_vault_username_password.phpt ($small_values) -// sqlsrv_ae_azure_key_vault_client_secret.phpt ($small_values) + // The orders of the array elements below correspond to the column // data types defined in the tests above. @@ -35,781 +32,4 @@ const STRSIZE = 256; const LONG_STRSIZE = 384; -$values = array(); -$values[] = array(array(("BA3EA123EA8FFF46A01"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("7BDD1C6794E0BA9556F63B93A"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("73536B21A1C760E5C47ECF6E7F12117"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "à ¢î ãbýýüzbo>ªo~äbhîýÃ~åÜB¢ß©+ü~ßÄAZî>öî~C_<ý@:Oåãã/v", - "Öý.C+.~a<äã+büa_ b|¢:|.ÃÄ~@ð ª*ZÃz @£+_>~âao|©å.ä/ªßAZ/ý/©ãbboU**©Ã£>îü_bbÜð,@oð¢@:OÃ<£Aî@rÄÄ.>_+©ßåðUîüªð>î/|ÄbªåAÄÖüÃUߣ_u|ÃÄZý,ª<ãã>,ðZußýöãZböãu<ðZuvb:zöäurîAöu,öh,u*b,AA©ª,v©Ö:Üð©O:,©rr:bbaåüýªîCß/ýhååOU.äî", - "¢ý¢oüZ¢+Cß>,|:uö© ¢Är¢.@C@î_,abßåü>Ö,~bü£å¢ãCÃ¥:,Üãß+|Z>rÜâbäA*ã ÃB ßZ<.aBÃBÃ¥~.ub>Ö_uÃUª>ã@zß:AüB|Bbb:ððvräzÃœbBýß |üÖ*ÖOååZZbO@î <:ãuCvöB/a~Ö Ä:Ö_><îbuÖßðOOO¢äb©öüZ~UÖÃbªåz*o/|oã_@ÃAývvÄAzðBÃœ:~ððß*/ä>Üß.zrð.©<¢a B ,¢üÄAÃ¥u:Öüã.Ã/u¢rÃýAÃäªÃb*Z*ßzB/+U@Zã¢,üv b:<ÃœOý/ã.Z*b~@av£Ã~ä¢/ä,A<îöß~ aÖ.ßhuZozðýåßBz©ýv|ýÄ+.Ä¢ªbÃÄb>Aãü zOÃÃ¥b+@vö£|bzÃßî©Uªª£*ÃO_h_/U/öAãÖaÄ_U|Ä £ZÃü._å¢CA+@_bªhÜ£ öuCOZ<ðvªÃouý_bb>@îaovÃœb>~BbßÖ@Ä©U~zöB£©h~ã|,b_.ß>UUª¢oÃ:b©Ä+ä<ãr/|ÃüÜh.äUÃ¥~.r©/AÃUz>ZCAbîäå.öZvBa*ö@ß:b+ã++Ö~ZU,U©Ã¢o ð @£bîUýðö¢<,äÃðuurö©îÃZ>¢¢rãßz.¢,ßO.îvOhvAU/£ä/_v,CÃ¥*Ußð©Ã:£:ã¢î,Bu|î£ß @ðOUãU:îb©|/O@voüUÖuß@Ã..*b_¢ä£¢~ß+/b~b/u_~ßvrÄäßb~£î~@aUÖß>OðZuaöã,oãB,O ývöîhu¢rÄ_výÄaZ*OzÖ|Uzv*Ãœ *zÃýür<~~Ub*ª~äýrÃüAuãCߣܢªbÃý~hßäz>Ã|b@h.AZãÄoÄUC_,ÃC@rî*r|Ã¥>îv>ußÄ_>:oî@b.B>ßb:åã|/O,Ã*Cª++>£¢>Öߢî:oÖ£,>uABC,_CBý© ßzßC+/îbÃœ,ä/î ÄOOaÃBðb©o£Uarãv,£üOð<ßßbÃã|ªC*~rßÃ+ðBU~.Ü£©:C/ÃU>au<Ã,u+,ðaU*/|ßbbu@:*Z.+Öoãh özhzCBhC:,:a_oU b*Bä.buOßoÄßBüu+ubýÄBAz:©ð~ßb:ßhobÄßÖZðåBü@ö, A>obÜ©|ã|+Öh©AªaU|£@ã©Ãðv ÖU£öäî@ãüåÃÃOC_vîAöÄ@|u©Ã©Bðý©,ýßOî r¢>ãªßbÄz_+_ü~Ü£ý|ßOAürrß~äZ>Ãäarubv@BCÃ>>*OZ|ü,Ã¥Ãav*Äzu.zAaüÜuª©+ Ö_@CÃ¥/hÃ@ªAîU¢:,ÖÃ~£<ðßäubCÄÄZÃ>ðurä+¢CãuAÜ¢+,Ãœ+@+:ðbÃäãÄ£îß /üUä@ýA.~ah|Ö.:hr<ßvÃO ßß/r++oÃÄZ>ßb_Öä.Ööð+ + z.Ã>Ob_ªU¢.bBߣA/Z.vU¢,ª:¢,@Ãœvü>/Båüã|¢Ãî|/CÃÖ¢Cîhîz|~㣣bUä<ÃbÃ¥ÃÃ¥,ÜÖBbo.îza <£äOAÃœ<üÃÖÃr/<*Öa CüÖb~v@ý,bÃå©ZãzO.+©:î~ðAh£~ ¢Ä,,~rb~,bª©A,.ußb:+hzð,*äu+ãb:¢ö©Ü* >Bb᎚+Ãœa.>OÃubOrrzÃœabÄý©Ã*ªüÄ+¢bîzü,~._|ª /Äroß,:Ã<:| :+Bhý.ÜßZbÃ¥CßîBߢßÜu,Ävv:~@u@ýåo>|ä~Z/.hßßb,ßB*ðZ£BÃÃ*ðÃCß©,Ã¥|vzoâub>>O+£/~©å:*uªðåårü£Ä h~ý:B@>£:BßuZãÜöü>.ªãª,@ä>Ãœ*¢<îß@öð£BýÃ:B@äObÃœZCýo@åýAbüOäz_ÄZ~oßýa@+bß_AÖ:vä_Ãu~ªu~ããu.aU<>O.Ävßz~:ªåÜ©Cöäu~ßC£oböCba üÖª@C|z+¢bO:_öU¢Ãß|:åýaãrhã B:~rßý¢Ãý+©Bß|uo,*|Äð/UZ@_/üÖÖzüÃývo:ý¢_ |¢ h:© Öã/ã~h,£rz©v|£ðubÃ*ßZbÃ,Ãa,+ßðhbÃ¥Crî <,ªªªöo *ý©¢,v:BäAÄhAC|ÃaäZo¢,::aî~zÖr©B_b+AÃÖ,£ÃöaB>Öz*ãä~Ö©aÄö_rª>©h*©*ªªöÃ_av:¢:©,+övªU<åªð/@+.>@oîý|ª*Ãîo*ªZã zA|b|ov*î/©:b.oB| b>C|£îbb¢bBU¢OAZª,ü ã vßý_Ã¥hZ ÃbßZU*uÖ,©ý@_rU©ß>.ÜßÖhu<ÄUz|BaîÖzzA: ðÃbäaZ* Ãœb| Ö,Ä>@üð|ß@CîOZ£bÖUîru:|ÃÄöÃãåoöOÃU*¢rUß BÃ¥@,~oÃâv|_o*CZßýß~uB©©~ðOööAÃ¥@:|B öoo|Uar¢~,A+Cbßr:rCauäß©üÃߪ+bBªvÃœ*©ãU uîbbOåßßäU©bU:BCb*<~@uªU_Z/C Ãh+rååCoߢßOîð+Äýäoo©vz*©ßaZß_Ã¥/Ã/ßv.h@~oÄaª£vuÄbZý:oÄßÃZvzZÃO¢aý*îO>üC,ðÃßö/r+ZäoÖbZZOܪCühãuîýBa¢z*buBo~_<ðåUbîa/_.~ÃÄOh|+aý|Ãßßã/~bðßð£åÃÃÄãÄååußß+|@Zîhu<äBOAã©CAß>ãb¢¢ÜÃAüCÃœÃî/u o©ðÄ b|éîAÃÃb,+ÖOÖßßUö©ð¢*ý,ZöUîÄ£ßð*Z|rý+b/|o.Ä,ýÃÃ.>îÃvÜÄÄ~z+öÜßü/~:,Ã¥Oªßååu+<ðvazu|ß©aãA.UÃœ ZbÃr*Ö©>ßßZ ýCåÜ|£ZðhbÄ+b£_ÃU¢o*bz,ö+:+/z¢oÃœ|oã_hb<ÖöãrU +hÄrövo£ðUÃÄð,Bvã¢>îößîßý~Oh.,UBo¢a ©u++ýh¢îoßr©,>Buà ,_uîu*BAU@a~_z,ܪ¢UhðßOa£o+>ÖîzA.ãU:BÖaavöîBî~vîZ¢î<ü|.~+uvbý~Z uüoÄbð©ã¢UäüîbBbî:uhaßbä/£BÃ¥>üAé|åð.|ä*öbäÃÃ/©zCuo@+o .îzZ,ãß*£uOZ rO¢ ªäOöü*oýüÜÜZß>z@U@h*Cö~ßî>ü+:|oZ.bAbãÄäÜBÄh@bo AuÖã¢ä¢©ãÃýªß|äa:öaA:C£*Ü©+v,ßbÃœÃOðoÃÃ@.ß,r+äCbuuÄÃ|bhäð|,bö<+ZA¢u䢢à Ã/~UbðZ>©¢hAÃB>êzaªZ©|ö/Coãv.~@+üh:îßývî+ * ~öhA ,zUªC@uªoabü.*îZßBßrÄbüCÜßZßÖu_ß~Ü£ÖªbÃ:|öÃ.h~BüUb:¢Ãߪ CßzU.ãö:U+£Ão©Ü,+£Ö+|ýrÃz|ÃÜðhÃœoz.£¢ªCüUbC¢ ©ývßC¢ uaabÃAz.|aüßa<@>Ãß/Aßu/äî|uß*ãboã::+r>zB*o¢Ö_zªäo<|ýÃ~@ý|ooCß>bÃðßý|AhzO@ âªÃzªü*£åvb:Uð:ßb rZäCåßÄzbãao+<+îî+åã|h:ãO<ä*â|*väzAã@Ãäªzî@£ÄåªB*AbîrÃœbãaö.ü+|/v©î@o b,£AUvuðr:ªUv,~Büb|b|:zAB@oh_£<Ööå|£bßBÃüýbU|ß|ð~BåéÜî/ßrÃßýäýBO<Ä*UäbhöãC|,b©ý*Ahãö<öäÃßßߢßßöZÄÖÄÃU|Bu+|Ö/rOãAr@Ã:ßÜboÄ@ÄýB>.aÖüäÖözBðÃ|üÄãCCäoÖ©U Bbî.u/oroüZAbAzbhîZ:ðb,*,¢î>ÄÖ/ÃœUzäÄ@äåb Ä_~©ÃÜBßßaä+Ãœ,A©|A~@©>,@@ÜÖO~_vaUhr@ß©ÃäßÃîÖ~,<ðßbüãz@©uobhîaðuvÖäh£r>äå ÃÄü~¢öhzã*_|.ÃÃ:ý+Ö@ßãî:£aÃvãÄjkl@~ßâ@BaªO|Ãà o.OrhC*_åß@ö¢uär© Öü©|ä£@ObÃýÃüOÃîåoCb_*r/A<.äZ:ã u>Öýö£aߪãvÃî.ü~©öÜ*b¢ß,ÖU@+U©î¢å£<<.Äöuhz:ÃB<_¢@ãO ÃÃ>Ö:¢ZÃAÃU hv@.ªböauÃœO<*Ä/äbÃC:Ö:Äa© ä©.ußÄuUßhÃ¥:oý_UÃ¥Ã|A,ÃO/¢ |Ãœ|Ã_>ÖvrZ~ýö|ý:¢öC<ý~|.._Ä£ZððZýª@BßbC__äCZBÃœ,zð¢AhÄîߪ+@aÖrÃ¥o@,b,ý vªßb<>ªCUab£ßªðßüÜCª~b_:CUîuüåa+.ý©ArBU.r>h@öB.vUÃ/zZ+Ãrb+öuª,h*aÃUßöåªå@Ä.BöA>aã¢üZ:ÃbAU:üãhaßß*Ãœ>vä~ *b_ß~ö£vAäö,ü/äîzªr~<îÜÖ>î¢ÃO@UhUZ¢rîî>B~Oöür¢ã*>är£©aä:zßZà ðB@¢ßhÃß©ý.CCýzüß|Ã¥bvuzz~U,¢bÃßrAzÃ¥U£ÜåÜz* u_:o,îÄ,/@bðUå©,äuOÃýZuzBªîîbýaur© öABýÃ*Z*Ãü+¢*ü+a|h£*ð hbßýß :>ãÄ||Ã+ÃœU/*Ãö@*Äü_öÃü:B| ,¢åArÃ@öårh©bßUýUzÃZ£ýßß*ß/ä¢>AÃouoB<ÃAÄãÃb rbîÖÃÄÄh,vÃ.ãbÃ+z£ +o,>ã£h@>Zý:îð:ªzðÃOðßî î_Öv.b©,O.©Oß|bC|rrä.ßÜ<+<,:ª<,~Ub~ZååC:ª~Z>UvßÃã~¢Ã~AðBÃœvåð©rÜÃaBðÃüßu©BÖUã@ZuC©£ªAä©ýuh>@ªaOÃœ.ߪbÃœUAªÖß*BÄ~:vª©îüãAðßbÖªä_©ðao.bða|UöÖßbÃœCb¢Aüå>¢vhvbÃß@Uö/r©¢vaUåîßOÖUrÃœ<~_ðÜz¢*ßö<ã¢Ä/@/_~v£Cb£bUhbb Ã¥Aö<~Bv~:¢ßãä©ßo¢ObhaÃ¥CÄ ßZ@£oä:*Äß._+CrîB ªZv,ÃœB£ö~,Uüäýã/UüÖ<+ ÃœAa ðª,ãA+ýðÖub,>.ýrvbBÖö/Ãðåuh¢öZ>U,:OA|Ö*äüðuª,ã: åý_ß|ÄhªOb/rüu*Ä¢uÃÖ£ÃÄ.zhî@r,Ö@übö.|Ãüãîbvzã¢ý©ÃÃ|ý|räOvh@/ߢ.ÃœzðUÃ¥Zðoöväã|.,|Ãhuã<ãÖüÜuÖo.O_av©>að+B+Ö~¢ÃÃ*ZÜå~ßZ~ßðö_ö<©_ÃœÃÃœÃã~îî,¢zbãbîrAv.öývã*U.<Ã_:üî.ãüãäaäÖ+ä<ö@UÃÄAÃ>ÃbÃ|>Ã¥.<.BO+ð£,¢aCb*A,©~Cßîãý©ÜÃßüZäÃZ:ü/@/©O>_zC¢Ã~ý,:Z,h/Äü|>Öz>_ü+_ðãbî@üý,¢UªÃ+öOÄ.>ý<_ãÖýäAÃ*Ãö~ð~Ãaöî<ö ,Öß:+ãCßhÃœh<+b+åîbb>Z£Ä©©©oÃöUßö|©BßaZªAîåuîßB|@zCAß//bÃAoÜÄÃ/äåüOðÄü~äª:a~,/ßä~C¢+ßuý>C¢*Üä>î*Ãßu><üva@ß~*ß©,©ß__+båß+ßßäðÄC/@vb.ÖaözZC©äª+ðrßÃA_ðZö/ßýäÃÃB*z.+bBÖv¢©äuvv,ArýåªbU©~@Uz¢ªÜoU¢OÃœ+:U ß/_b+©ÄAÃAÜßäßbAB¢ãäßÜh©ä>¢~bðzðb~O_äîrrAðßUü|oaã©ü/åÃv,|ªCC£¢£Ã/Ã*ðåÜaz©ãBA,ÃœUZAÖBßüåA+ ÜýUÃœb_,vbßvrUhb©©Öb~UaÃœ+ßBî@ßÜ_©ü~BÜÖ@ªZÃÖzüýbߢAoZhýÜBÖuåüü£å:zvªU£AZîhhÖå>Ãß+Ãzv>£<.©r>*|Ã¥|££ßzýÃ+ãhªÖvhýUîªb,>u©+,ßßãðAh©Äa~>CbOÄCaÃ¥o©Ö, //ZîZo,*üv/@oä:ßaðAU| >Ä£,>,>Öu,|¢U@㣩_v|boÖÄhÖüZ:>ðååa<|ÃœC©:öbÃœ@<ª öZrÖßAöv*üðUÃ/uZÃ:©Ãubvä_äߪa+uU@/aývÖCäöÃhªääöAÃÃœbbßåBîA*rüu UüÃÃÃz.uaý*ßß>ð©ý©îÜåU o/£ Cüð|o¢ªUo¢@,Baa_u@Ã¥:~ßBߢî£ÃhîbAbzäBöOCh~.O£hU.ßåbaA£@r<üÃÃUhßCU|<*ßhðªb,Öåo£:Ã,<äßbÃ¥a,@B+CU/åýÖZðb©î¢£ö/ü£î¢Aã@a ÖZaA*_BßÄÖß*ª>ÖCåÄ,A>ärä£ð+<îb_ªuCCÄÖU~@|bîåob+ho>oChýãåbo,/Oªð£urh<,b+¢hä:.ü Ã|_U ,Ã¥.>UÄ©Öª©@ýî>ã~..éBß:Öbü| b/ÄðzÃOßýZÄO|üUrÄu|~:ßüB:ÃÖÃha£CvZO,ߪ©o bªhÄ@Ch_vbä.ýåU:¢:¢ý/£ý£ªîvßä,B_.U,rã,ö|¢OåüßÃZ,ððooboÃ¥,¢>Cªã ÃäözaÃ>AªU@b+vÖAãî¢åäðuö>U*ý~*v ÄZ:vür+BÜÄa+rbýüªzz@:Ã*ýÄb ÃßÄ.A£ðo./bü/~Ããð~vaîý>o ©Zz¢h:_îä¢ZbzýÄvð~@| åðÜî/,/b/Öz|B**¢@vý>b~ü,Öªª|Ãý.ü~ýß*_ÃÄbCä/åý>r¢*£,z_bZzü+oßßäz/* ýC|aBß|ð+Ohb.v.ýrB~B/@Ä<,o~Ã¥vU|ý_©@Bb*_ªÖ¢ÃBoîöî.ߣuäUÃ¥:.bªðu©£h¢o@ÖÃzðä|ü*ß㪠>ü©Äz¢U Ã/AÃaãªÖªßßZuÃÃ@bÃ.AZð üßAäªCüðÜ/Brß~rãaOUªrzðvÃœvh£~rubÖCbåýýý¢¢äbäC@îöäå,ãAzC£åÖB*üz |rb/Ã@zäAr¢ü vbhU*rßÄß+A:ý,au墣z_<£bBu._", - "", - "Ãœ,ߪäýÜ@¢ÖãAýÃhz¢h|ü©ßÜ.bhhßUÃ/ĪBraOªA£åßîÄßAB_ZU+UB/©ýª,@Ääüvð C.å©v:vªh@U:và ðÜho*ð©ð.ß_*åöb.ߢhãa~£@:v+vo,Üß/ß.*bO/oîäbäÄB+£b", - 'F*^vEjJvsK6ESpv&$tt.c7H?8p50/KICWE$!A+*IQ@7D=s`t2! qp;icVYk_[hrg!)WOK#7chF+&T>HZCr,gk`b9Ow]M.K1)EAFAj7tYL]FT\BE\`I36XqlSr!0tK&4!i@jZa4,]`&Gk-t\mk#@TSVL6aQciYwV@\.CoM P;;3rrkK%kdj$_t#lu@mS"S)^f3&\<:>gLP!vjaLlYZs[sE&CQ,RZC"`bGU kwAkTkUjA`/ANWKVHI(,@Hb/ktiCI?swvaS/%qptXAv6oG/\.s[OV9OlQR6]kjCseu)`#:wG07XN?Mu"fnnM)Eae+v^(BQ_eAm^6L+^X,7r2,53FVs)-o?"u<$f(,1=t[n$DQD_kfWRcgr#()?RwklO!E^^:+?jcB/NmLFt!4In#!u\3CQ$kM7mVD8.9 =+VL+?5wc@NlK(7.FmFP3rO-aAPaE_AKN!i7O@MN?I@[0&$RUpYC-_HAkF?J2Es:JlGH1q0ax6ivL%:7rGTso)__ 6iQjWPoll)-=gC&(4p56ujk)c27,>Q^dm8R. 1@/b[E[E=Ui:=J(BX1+C(qn-75dC ?/&Wpp4D7MKUU]*iR:O_e(e.ehub23ZF2iZ>Lf!;#h@-HK0(KrIMU>w38R(e@e(gOmNWRPjYBw$S1,[EvJ7+3pNM[j?SKp3_9c81`ES+Dk1^3A]N`+#Mk;Oq^bG4Z+JXX=t;.9d5dedmL!GZv4o3kkuYkpl-MuBB:aE@!((6!`x0^AW9+,Z0.XIGbokgA4+6Nil]7dQ$.]HVTl6vT2I\_r:^s9e7qTMr)XFpo>A(nGDX1;a!J]s?\"oH(w!@=iE/o2(7"G3WN`o-D0oa00pV^IK]K`eS:N/mjF0wxb]UV07n0QVTF`Mk*Vu$X);p f#n&!@tN38b+06-#&P?^ps$aVhv;09uKO($!)>1ET\"[.DDGeZ:PisM;pD@mMv51;m%h65S4b^dXbhI3MWBoq,-6:>)gp/v-.pAk?Gjk5no@mlYP \-k!_41Ehb(7RFv$^,\2hNOR;\riEscquc"xOI4hc)op3X&iL>[1&k9>".r:!2Z^%B&WL4N"%K`urbo@efj5(&$lb=?\d?bqt(=dt%3N+mc_-66Ak?A*jrYg0lk%Ii/p=eZB!"QU#E#v]q3%6>:M3`C2bH1ZM@KkKN"F*+I\"0,fh5Z00d+O6d,hkDBtFg*D3JdnrCT0gaxL99g\;NbM3Fk!^A/MTl-LIo!w`3g7!X$`HF!Uh"kS!pxu&2=1. &k/*"w21qd/L6hC^x7d;.61a9d?[bj+T*Rk*9h0]-khH$?Mrx.3UkKJ>/N*w7kc@DCEL!>kVeJJxZvS,kPEQ)Y7:hY*&I!X:JYq3E3e$lTS-6IZ^[4CVnS-t$`]vQ+QsL(@vek/JvOBn#f_i aJ?&%wH"sWqt\4ETkVW`IV-JKOhD,W2HN2n> FaJ\3ZBk=_49K"9ti4Aj]8L_%=>&sfk^_3E@X !tKQQ)>*S`6mYb[G/w^MMas`i=,<8sTfgX+%IT7gnsj#U4VrlPd.D[C[>l1cakp0F;E)9sH xR(s0Os+mABEt.pL[ito_3T0ZTU46jb =K"Z+ZxunArRkph?^6*Lixcv;)Hdwc#QkIDa:/Xxs 2_,3!fcs&+P]/rwVelA%ot0x[7V6v>xvs$a7kS>4nL l]7!nl^6A)gVt5+h<(M4F8x0ZF@(&ud@*J2bwAl+N5qU&_8&WeN(c]/]ZhH0r*WYrKU D]&Y%1N]9*:(R>B uU<\-Rsv?w9U%l+&3u)0lSPpY&V+t%s&%:tR9%dh>gd9>J3].?JZkHgl2MYZkv9RQPk7d/iLh2+T\%KPM3!5AVZiwEdADG8xUd3d.&Ph4wZb8)EH:Sq;7,=VKtN.pLafe*(jNb.9HP C@v7Y^R2-k`w_\P6lYTRlkK`o":xL&L!(Y=bU*p<"Vfq4i7tj5(bgKW`6MoaP471F9LaJCN,Nsh=[0SO8^J*LojG5m_>t?[e/)8TtJ;!u2gB]c>Le/(w_":l*?Z#2YPvgm$6aLY6xdV-gbuK&+Ug-Zpu^6MPap+&0*C nn(,SJaY[FK]P&F_7vZMQ/h7J%.[q_3,()vTlRNF=YVBxFY<_<c@G,T,x!r)PNm*q#_YG,x#Gn4`XE-R1>vSW:[i)W3*NI=VV5h(viIpW@PhxEtGr"LuIsMJ8DM6/"FU8 4E1Lvcm>]?OSG[Dtrv4`)U(4S/rQ)ELFPZl%fbmn`l[H/gWc,B7P*lX8&,%O*SF>@;;H@I\i.2HZCr,gk`b9Ow]M.K1)EAFAj7tYL]FT\BE\`I36XqlSr!0tK&4!i@jZa4,]`&Gk-t\mk#@TSVL6aQciYwV@\.CoM P;;3rrkK%kdj$_t#lu@mS"S)^f3&\<:>gLP!vjaLlYZs[sE&CQ,RZC"`bGU kwAkTkUjA`/ANWKVHI(,@Hb/ktiCI?swvaS/%qptXAv6oG/\.s[OV9OlQR6]kjCseu)`#:wG07XN?Mu"fnnM)Eae+v^(BQ_eAm^6L+^X,7r2,53FVs)-o?"u<$f(,1=t[n$DQD_kfWRcgr#()?RwklO!E^^:+?jcB/NmLFt!4In#!u\3CQ$kM7mVD8.9 =+VL+?5wc@NlK(7.FmFP3rO-aAPaE_AKN!i7O@MN?I@[0&$RUpYC-_HAkF?J2Es:JlGH1q0ax6ivL%:7rGTso)__ 6iQjWPoll)-=gC&(4p56ujk)c27,>Q^dm8R. 1@/b[E[E=Ui:=J(BX1+C(qn-75dC ?/&Wpp4D7MKUU]*iR:O_e(e.ehub23ZF2iZ>Lf!;#h@-HK0(KrIMU>w38R(e@e(gOmNWRPjYBw$S1,[EvJ7+3pNM[j?SKp3_9c81`ES+Dk1^3A]N`+#Mk;Oq^bG4Z+JXX=t;.9d5dedmL!GZv4o3kkuYkpl-MuBB:aE@!((6!`x0^AW9+,Z0.XIGbokgA4+6Nil]7dQ$.]HVTl6vT2I\_r:^s9e7qTMr)XFpo>A(nGDX1;a!J]s?\"oH(w!@=iE/o2(7"G3WN`o-D0oa00pV^IK]K`eS:N/mjF0wxb]UV07n0QVTF`Mk*Vu$X);p f#n&!@tN38b+06-#&P?^ps$aVhv;09uKO($!)>1ET\"[.DDGeZ:PisM;pD@mMv51;m%h65S4b^dXbhI3MWBoq,-6:>)gp/v-.pAk?Gjk5no@mlYP \-k!_41Ehb(7RFv$^,\2hNOR;\riEscquc"xOI4hc)op3X&iL>[1&k9>".r:!2Z^%B&WL4N"%K`urbo@efj5(&$lb=?\d?bqt(=dt%3N+mc_-66Ak?A*jrYg0lk%Ii/p=eZB!"QU#E#v]q3%6>:M3`C2bH1ZM@KkKN"F*+I\"0,fh5Z00d+O6d,hkDBtFg*D3JdnrCT0gaxL99g\;NbM3Fk!^A/MTl-LIo!w`3g7!X$`HF!Uh"kS!pxu&2=1. &k/*"w21qd/L6hC^x7d;.61a9d?[bj+T*Rk*9h0]-khH$?Mrx.3UkKJ>/N*w7kc@DCEL!>kVeJJxZvS,kPEQ)Y7:hY*&I!X:JYq3E3e$lTS-6IZ^[4CVnS-t$`]vQ+QsL(@vek/JvOBn#f_i aJ?&%wH"sWqt\4ETkVW`IV-JKOhD,W2HN2n> FaJ\3ZBk=_49K"9ti4Aj]8L_%=>&sfk^_3E@X !tKQQ)>*S`6mYb[G/w^MMas`i=,<8sTfgX+%IT7gnsj#U4VrlPd.D[C[>l1cakp0F;E)9sH xR(s0Os+mABEt.pL[ito_3T0ZTU46jb =K"Z+ZxunArRkph?^6*Lixcv;)Hdwc#QkIDa:/Xxs 2_,3!fcs&+P]/rwVelA%ot0x[7V6v>xvs$a7kS>4nL l]7!nl^6A)gVt5+h<(M4F8x0ZF@(&ud@*J2bwAl+N5qU&_8&WeN(c]/]ZhH0r*WYrKU D]&Y%1N]9*:(R>B uU<\-Rsv?w9U%l+&3u)0lSPpY&V+t%s&%:tR9%dh>gd9>J3].?JZkHgl2MYZkv9RQPk7d/iLh2+T\%KPM3!5AVZiwEdADG8xUd3d.&Ph4wZb8)EH:Sq;7,=VKtN.pLafe*(jNb.9HP C@v7Y^R2-k`w_\P6lYTRlkK`o":xL&L!(Y=bU*p<"Vfq4i7tj5(bgKW`6MoaP471F9LaJCN,Nsh=[0SO8^J*LojG5m_>t?[e/)8TtJ;!u2gB]c>Le/(w_":l*?Z#2YPvgm$6aLY6xdV-gbuK&+Ug-Zpu^6MPap+&0*C nn(,SJaY[FK]P&F_7vZMQ/h7J%.[q_3,()vTlRNF=YVBxFY<_<c@G,T,x!r)PNm*q#_YG,x#Gn4`XE-R1>vSW:[i)W3*NI=VV5h(viIpW@PhxEtGr"LuIsMJ8DM6/"FU8 4E1Lvcm>]?OSG[Dtrv4`)U(4S/rQ)ELFPZl%fbmn`l[H/gWc,B7P*lX8&,%O*SF>@;;H@I\i.2<', - "9999-12-31 23:59:59.997", - "2005-01-03 17:25:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("52.7878", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array(null, null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - array("0", null, null, SQLSRV_SQLTYPE_FLOAT), - -3.4E+38, - -293433712, - 0, - -1, - 113, - 0, - array(("BA3EA123EA8FFF46A01"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("7BDD1C6794E0BA9556F63B93A"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "à ¢î ãbýýüzbo>ªo~äbhîýÃ~åÜB¢ß©+ü~ßÄAZî>öî~C_<ý@:Oåãã/vÄÃ~bA|Ö/o ª :+Ãœ~äÃ*CuzÃ¥rBîU¢/öß@_zbü/UåörbßoZßo¢ß.ðÖåCvö,bðäðÃAuZ©hbZÖ¢bb£ZaZ>UÃ¥z+ĪOüÄb¢ßîOauö_Ah>BªoCv¢v./:äOÜîoÃýZ*_.îÖðBüî>ãÄÖ", - "Öý.C+.~a<äã+büa_ b|¢:|.ÃÄ~@ð ª*ZÃz @£+_>~âao|©å.ä/ªßAZ/ý/©ãbboU**©Ã£>îü_bbÜð,@oð¢@:OÃ<£Aî@rÄÄ.>_+©ßåðUîüªð>î/|ÄbªåAÄÖüÃUߣ_u|ÃÄZý,ª<ãã>,ðZußýöãZböãu<ðZuvb:zöäurîAöu,öh,u*b,AA©ª,v©Ö:Üð©O:,©rr:bbaåüýªîCß/ýhååOU.äî©r:ÃhCvAð|:raîa AhêÜ+_öaÜÜßzvoU ZÜãªZÖäÃ|ü| UýuÃ¥_oBÄUhZ©>Ä* :é,uäÃCoÃÃ+ãår.ý¢Üß|ðÖBb@Bh>OB|._vßZoC+UBÄß/Äå+ðäÃåð_Ö_ý|*Ã", - "", - "Ãœ,ߪäýÜ@¢ÖãAýÃhz¢h|ü©ßÜ.bhhßUÃ/ĪBraOªA£åßîÄßAB_ZU+UB/©ýª,@Ääüvð C.å©v:vªh@U:và ðÜho*ð©ð.ß_*åöb.ߢhãa~£@:v+vo,Üß/ß.*bO/oîäbäÄB+£b", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("52.7878", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array(null, null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 1 -$values[] = array(array(("7BDD1C6794E0BA9556F63B93A30498294A3D8F3B3701F62D5CFF0F48FC0417F7CB356CCCF8573BF328364C96078121F0"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("BA3EA123EA8FFF9D1DC26EDAFA97CBA70704BA4DC43C3E9A55C44A1E5290D225679EB2449"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("7692AB34DF43359086CDEE4334EAF6677BDD1C6794E0BA9556F63B93A30498294A3D8F3B3701F62D5CFF0F48FC0417F7CB356CCCF8573BF328364C96078121F0"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "à ¢î ãbýýüzbo>ªo~äbhî~ÄßðÄã/.Ö", - "Öý.C+.~a<äã+büa_ b|¢UîÖäÃ|ü| UýuÃ¥_oBÄUhZ©>Ä* :é,uäÃCoÃÃ+ãår.ý¢Üß|ðÖBb@Bh>OB|._vßZoC+UBÄß/Äå+ðäÃåð_Ö_ý|*Ã", - "¢ý¢oüZ¢+Cß>,|:uö© ¢ÄrßZ:~ð¢ªbÃý~hßäz>Ã|b@h.AZãÄoÄUC_,ÃC@rî*r|Ã¥>îv>ußÄ_>:oî@b.B>ßb:åã|/O,Ã*Cª++>£¢>Öߢî:oÖ£,>uABC,_CBý© ßzßC+/îbÃœ,ä/î ÄOOaÃBðb©o£Uarãv,£üOð<ßßbÃã|ªC*~rßÃ+ðBU~.Ü£©:C/ÃU>au<Ã,u+,ðaU*/|ßbbu@:*Z.+Öoãh özhzCBhC:,:a_oU b*Bä.buOßoÄßBüu+ubýÄBAz:©ð~ßb:ßhobÄß", - "", - "Ãœ,ߪäýÜ@¢ÖãAýÃhz¢h|ü©ßÜ.bhhßUÃ/ĪBraOªA£åßîÄßAB_ZU+UB/©ýª,@Ääüvð C.å©v:vªh@U:và ðÜho*ð©ð.ß_*åöb.ߢhãa~£@:v+vo,Üß/ß.*bO/oîäbäÄB+£b", - "CÃ¥ h¢ÃB|,ÃœAbÖðB_Ä+£rüöh@ßß+åÄObýö©_
BbßöoZC+ÄÖ¢oöOß*ß rärÃbBB+ª:>öoä*/b<üärª~ß*u >ªzvä¢ohÖZ,ýÃäÜ©©äܪÖß>C +öCªv,*ßÖ~ÃœzO¢ ýÃ~o£©b_<Ã¥AOüvý+>U:ªAu*:äCðã+:ÃœrOåÃhUh£öOBv¢zvÃ.~.ÄCa_<ÖÄb+Ã*Ã¥C/Uüß+zuUBß©äCbã_ãrvböÖ©r£.ð/OÜî~zýã+ <*C*OªZ Ã*Ãä_@_BßOÃ~Ãœoü,ßbzîB_hU~ ÃuߣCÃ¥*éCv>åÖÃrZ,b*ABýîb~ð>BýööääüªOüÄü/>£ü+A~£ä,*Ã/uz~uðª@ö䪩o £AÃœCA ©Bð@:A_aav£Ã£u>£_++U+Oa|îüßA,_äÃ+Ab*ZÃœhr,~@©uðAöãa<.ÃÜü>B*¢¢:.îzªOh*ÃœUOäÜ£ *üðär+b:aªb|Uðh£ýåübvz¢.ÜéÃO|îOãÖa£|*ZU+öîÃ|ªå.¢~Oýß~bA*>B>Ä+Z/ÄäÄ>ðh>|+Cåâ/£ðÄ:zÃbß><ÜöOA+ßoãööOv,ÖðBß_v+ü*ä. bbÃý,Or.¢_ö:O|örB©<:©zÄ+ªUvuBߪ ¢AÖbý©_ÜýãÜrü¢¢üzoå©,ö©Ö+ ,oîüzßð¢ßhßår.CÃ<ß_>,üªzîðîöðÃ+ýÄ©Z,äa_öZ*ßßãÖäävöhAz£©Ä v/ ßåý~ª¢äU,:ª.:|<îvA ÃœZã|@h|ð.>.ßr@C~*uðruåª:ß*Ã|ÃoÖ/zö©büz::bÄ:Öý£,î*ýCÃ*ßCå©+ÃœO.ýOvÃü>vãbªbÖ/ã<.BýUßßÃð_BÄzA<ýåßåa||~bvÖZzß|ªäý|>Ã¥a,£Z.Ã¥uaOª ¢aBãAªåöÃåüÃZr>ð+ðUÃÃ@~ü@ÄÖuÃöU<*äu@b<ª|ðÜCvba ¢UªÖßÜ+äðrzaüöUÃœa_ Oa*ä", - "9999-12-31 23:59:59.997", - "2005-01-03 17:25:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("10000000000000000", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array(null, null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 0, - -3.4E+38, - -293433712, - 0, - -1, - 113, - 0, - array((""), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array((""), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "à ¢î ãbýýüzÄÖ|ÃÄåAÜÖå~Äã*¢ã¢oo:ᛜ*Ö£z+*Ã+bÃ*b ðÜÖübªO>ÄßðÄã/.Ö", - "Öý.C+.~a<äÃÖ_ý|*Ã", - "", - "Ãœ,ߪäýÜ@¢ÖãAýÃhz¢h|ü©ßÜ.bhhßUÃ/ĪBraOªA£åßîÄßAB_ZU+UB/©ýª,@Ääüvð C.å©v:vªh@U:và ðÜho*ð©ð.ß_*åöb.ߢhãa~£@:v+vo,Üß/ß.*bO/oîäbäÄB+£b", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("10000000000000000", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array(null, null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 2 -$values[] = array(array(("AE48C31DD726D469CC5228A4980E9BFFC9D"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("0F"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("D1CFF9612BB7648C5D10D7D268CCC910FA6F1FB1B809DA1EC9DE58C80D41C0CB8437482FE4F95FCBF3E204EDFE16478A8C793FEC18051224CA0FD023D42DEEBE40D711C22FD4544EF89522126ED0261A6E0943D720D4B37F152365D5193BE7D5F3469D16F4756AA824E0F382D30FB5F9E9B5C5DADA179D7C2392ACFC7E7F25BCBD5C1F28330F15E8AEABB23FE1309C157D7BFB53760CFBDBB920F351D86E39A5CB36C586E09DB6EAAFDD2C3E655D8AF2F6978EF283C246A050336F121BB41040620C74AA6B1534DFB510B398E0CD766B95F06B40DF3711F8F01526AD3EA4CEEBCCDBCC58D8E087E152AF4D731EADCF92BD037C1B58CF612AFA8BBA55EE87A11041D7DC04F12D82B57879EE2FF9AEEA425593E02B38B382FD7AA16E4D263202CB0E2D3136D1BA919F70C6A2EEE131314BA178004AA775E79E54DE9A5FCF458F46771321CC7DA360AB0250BA6200941C397012ABBE31B823884592D1F31041C03B376C67CE1F64DDFE07654846CB758DB0C250EFACA9C114BEEF9986DAD16D8AB0808F3AA43C4E2A0B44ADA6CC7FECCCD0152130BF6223916657A8DE75DDF32CA9DA87C63C4AD9848436D467FC1A484F02DCB6C5B03CCB5E6CFDC45C836FB43D41710CF1E82680853B9CBC889DBB954C0F59DBA9273D7C6D1F39594E768CF11472A7168B0D4666EC4C218A138042C0B158784D29940B160404AFE2AECD420EBE9B9805F6951A4EB8F2EF6CF5B368ECDC78A7E60E3FD780779102866D63C82EFA12DB77F83CDA1C4E398436A4AE10509BB30626155C008EC4079E558283D1552AC062E2D7E0F49FAE9B97D4ABA0EBC970B1EABA5D9209AE124CB3E0AE7489BA683F0BE44B3D78AABE768789B42BD3231BC4E4D0A916F788C6B4C09B3D03F13A01DD7C6C95078E43393FFBE71A7FE9E67990D4D98FB9114147731D866CED7DBA755D87D6BF768E144691A7692B007E46DADA11AC3F27AA58CC3B1511E9F7D64BC23161CCF803370302B082FE01A8164704D6AB82CBC17B5DA33F1A84516C2A4EBB5BA6E99EA15EC7A31EA97E94AC472414DE0D134C437FF0B61419B8F8B2A196863432B6012CA86377D3EFAD280CB8F6F7F66F736F16F267EBA6238A8795DEE07D02E850799122F835A99B6863C2D8B95B12C102533E377F41CACA5AF91DDBD204FF302514688D66D3773DB57B8A0418DD7A2F64D4DC9D3ECB791EAEC6C7FF17CDC9636482A3A4F2956A2EDBF071F3B7B6A17D8EEE770D2851F1599B49DE900B7825F7B2269F9629C53CDF6AADC14E598556F8154A602956CC3B12815A09A8C6CA2363DA8A4191171B53B9B1A7928A90B2F015E2E68AB107FCA0F5EB8D04CFCB8408A08A5D73DF43C5D0F9F6B677BF39B071DE59F3C5D04F625BDDD95BA59A1683FE3D9EF1F0333F20D22D1580CA5174E1BC2B1B2675C7CE2851180195F5F81046D372B84A1047B03E9F392198CACEA7FA58F79F8A7EF01E266F6690E831A93AEA0CDEAD47993AC1746ADC873CA6256D9260073D0E5895C7E35491DAB9A6D80CF5FE479BFC1150612E261B1F9268D1704A08FE783A7C3C816706B9E3076017DD82DF914E6E8961810F961EE84D76258A831190B0A86FD279C1B624F5576D69BE0E4D8CC2C79311B0977D68945D4433F0D13F6FE5DF5725230D177CA8647C29923C50DA0FE962B2E00FD0292AF73E2C7D723414180D83EEB71869B5B026695C2B53C6580C7A34077E8C7FD5FF3DFA2BCEFAFF3620680194B86212E78FBCF2C9A6840DD730E610034E2C959B8DADFF891E3DE6B537232BB34231AF9A575C77C189A63F6B3022FAE8DAEBF7F3C276070A7299C941C64D9273684A84FF3675ABE1B4D401D77E9448FB42FDDA4B3B27F0CC2A9527BD8335FB4C13BBF2E59DFBA26540064EEE742F9BDE40DE0ED86357384059B8D4A15DEE6E7EE8C5C3C291635D474B74FB34D90257FB09497CA52712D8CC73B29BE51701075197C005BE6BD94CED80B78CEE5C03774760E9A80471B957C5ED074EEEC778204A6DA99D090CA98F200C8F247F95EA60604B44743D6902EFDE783FC1648A6E1608873280A2666E4AB75A7807E294D83FF296DE9372F3E2E16AC9BE80CCD82AAC9590D5F795572C3B59CB91167648C73719DB27EA486CE2A67B8F5CBCAE983E3C9379D72E97406CD2D619CC11DE7696055F54E51FB694C428AE2F8C160A6F8CC19D54730C61A89FD97E55042E90D0D7F4E9575D49B32E860EA5AE8067C456D8669D0A8479F39993B6B50BA15180A587CD57873CA302330E7EDE7456C3183805178C9D91965C6E014FC3A694A77D247E0026B3004D1A25545A0C159B8F42C09E7D0689D4F313F170E985ECEF6BAE66F1BA0646AC6527C80D5739378A46C271FCF30817A0F34F8AFC254226B73D35F8ABA513E188FC2439553E8C7DB9DC673A1703EFFC6396B5FE6A7B5A77130100C011408BE522E0F900CECEB27276C1D12FBE180FB34D498514EF25770DA1AD26442DBC6C69407207361AEF2DA4D4437798AA2AB90B015D82D7CB77060D79FB5F11A87E44CA6567DF4C00D9A86E1BE0309BADDE5376E41B32D039849DC97C09BC1F900BFC55FEBD33325B7D0A37B514BBEB466A5F9B97D82D9D3B7480E0DA15CFF71D88CE0FE3DAACF0AB112B77110B1B14C8B28311BA1077C7EE6F2A8A2C426835AD031A2C56BBF76BCD6EF086ACB67C20597C181792BE4CFD01062174B012C458A8F5CA0DF0FECE1CCB17444B2FF12B27E2FA20B03A50A2FEB61D1B408A72BBAA9AA7EDB6FC6E3F6ABE0B5C7B79F1DD1FEB0981B5E2142E5842DB57D3E37D82485B99B08813643B84BAC5F22F043784B44FFB6580F0F5B87679857411166B873079141FD041450C76CE0D0B2C11FF440B569383E808014765E13A0E8DF01A6E5F4EB1B5F017B462FAEEFEB2E3FA9C548A3168CA72D79F3867CCB9EF17468E76A3AEABDD603953F3A22098F9195E636B44D5844C975FFB87BF895E336DC55BB460780F455EBEF450ED7528424E961F19594E9A31C241EF53B3242DD6EB2085DCD4D498A14F1E1C95D1236F2227A949196A34DA1F2B29F21E88BB922A812D65BCEF4B7D69FD7DDA3B3204509EE2CD2C09EB147CA65666B1AAE28A18938BE204B3CDA232EC2BDF2BFC6A9EED26C9633BCB8F884F7CB0BF567899C287736536BF4FFFACC1308A4FDE0BE8704BC7798AE17F4E4D03F39298030E4BCA426957A89723DFAD272C920B413FBA438430739EFF0EE108351832BBAD6799C2E4C00D74B44FE927E7DC63B4ED330BD4495F03D29ABCCB33784F737EF113FFCF82DF267132359F44474BB722AB3C1AF20C6E725E63C7282804DA0C64CC6A36C3EC94321E1C8A9203CFBF8B4EF5A8884C24817282A1915BBBA6FE7C68392A87DD14E33606FA0CE571CC6E9EA44BE9225C41C0304AF7A4224B4876671E0E39DA9537AE2A12627142279BF0C63302B77FBDCC101668FA3CE570C0FACE8395C7739283F97C9DCF1FF9C07E18AF06DDEF4880F6A941FE1361AAFAC8A94167A2CE81C48D33BF093AE1FEAC2645FAD18DEEA2D1A23D8CEC85857F125E71D515CE822DBF86DA79AA303F5614AA95C7CD76C96CEA067FC56531954F07B49CBAC91CE8ED94283B5AB364F8A162E77831EFBDB1AB8504FB1F5ECCB87DE18B71E593848CF7C1B833953B7EB9750DCE527DC9536F5A02F125B421D98EC77FCD6927E3130DEE5A7070277A1C60065B7601807A3AB49E2C27E2A25ED8D5E8396C257E159181FAC0641348E347DFF6812F58ABC7FE5D3508CAFBCB305B8F81AA87FE1DF6F1F0C9E416C006177BBAE457EC213F702A103B1EE0754A95B6BA64E4919E5B49B20F9F2E9EB7B13E472D8D101773443A50018067B30F068CB6243D5C5B2175ED5F9A95EC8200D1626F0CC7C2E82A7E43763A94900DE396B2F912787A8A54D315A7AFB08CDD57B84F202E08A63743948BA5E9D6209CE5DF7BF3E2F36F8866D80A39A21B3869F1762E72456FEFDAE07BB57DFCC4A07EA288C1C04488EA5448B6B955FE7C85285C11B463B7AE4775FF9113774DF19BA2278B8729597825688BCA399EA699DC63E68D11DB003CC704314BD0A3757316572B172E72F56BDEE3FC4BE7FD74B8CF55B4AF80C0DFB091EAC482E80D9781F318A3E2739F6B697E8AAE3DEEC454D98F887C7AEEC5E255876A8533AE6273C14C6A58CF4992353FFA5B21061291D1DD85FAF36F912C932D365E5B090A37BB0C16ECE1746B958FFD434C8093A4DC32E598B7ADB37F04302FD72B98EB372C25B29014FF35A05D61F06AF62C449C0F885A1B7570528A079D02ACC1AD400C94C61716CDF38FED0D353B55BD4878E7EB83C7933EDFC1BBD3388887EC88EEB011F3C630053DDE0C086D1E30450CDCBF142AC14DD5ACCDC4F46B43956AF80074568B33B97238D2ADB5BB9AD1C19166D66BD963B1B790DA6536014BA9FA4891106C99D9C9C9133CEB4813982A11FC0363046D2991844EBCC5B0CE8370FD38B36D1A7A7527C7AD0D489C8879A408A0534A0DD8958D1F0F8C1DDD14190A9FCBEE0F602B0F9776DBAB676FE0F50BC518BD007B396314A3265E1AFE575AB38402A8D09A7A33EDA0BBC3223D3E2D2E0A8048B457188F5658C96C39CCE4A92F80E7D36D3824615AE4DF5FCF7630E7650930007E1E965C7B735277D11DFA10BC02A3A1456ABFF5D61EFDDF87B8CFE017E207309CD43B10EA9B57F9E5BC904CEABE93BCD29F91FA594A3A5F10C6D4B2A3725FEF06C983D7D792C6BA02C600298EA0E4C915131D88500CE752A9DBF013B6A546735CA136113ABBE53FC8D7F7833A00C06FE21B9D8064D79F09E2213DFBA2B2F36D7F9CDCED7AC38E693DDDCF78155428A91B8031AB07B60E75022C3F098DE99D05D48A039D23E2F51E8D3452870826F48E0884E012815865B98E57EB4E9474DB4430B9938B408B75288B131DC363876CEB37A100F9097A848C245A0EDC07A68A08690E565E83694D7D1FB7976E3A925606EAF006E373D24AB6351756F68CA920D499056A55311E57DE6383E941E3164191F3F7E107AF969486C390197C0246F07696B2D52537F0D5DCA8388FFFF8F8C31EC5C50284B81934F270E39190F4EBCC1C9372BB30411C69F312146F7716A48DCACD0AA63D446BBE7D7DD2B4546F34D7BC6C635C864D92CDA96B0F62A3B9CDEBDC55D6D3FA459EC837B44DC27E681593ED3CA31AB348077D3A3CFCD244F90AB3BFF6E7FE9860ED76CD9B24C20FC05F41557B856706B338845E59A9BD48CC833EA98A12877016FB7C5F3C4D1813356D69EDBB7CC963A3BCFCCE53A03EC7302E0107E62885BDC7C14E701917CA76106118EC4FCF9D4FB9DB05DD15C92C66CCB52F0E3C002D0CE7AC15EA3E595A8BE286ADE28742776090C15E1B12B8A80EA4D7A353F8BC4A10340C0493B9B311608089EA8F1B39DF8A3DBE7D6AB211FA3F04073556A5F683264C9DBE8B4DE3A6DDBFED9A98CE18F37B0829163A4EDF5E2211D59FA27DBABA89F6C4428D7B543C5A079FBAC4875170EB13BA5E7CD058E77D3F8C697844046FF52300A1F9D675FFA3ECCC22AF24999AB054B49EC36C4DA9ADECD54DFEFAC416033FC59186BF7"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "üo+/>ýz,ovöCZ.©bZö|~öãý¢ß~©Ü|OðÃ*.rUÃ¥@Brî_,vAa.z©ürCã+£,:<ªª:êba/öý:ß,/:+bovÃ~Ãœo+C~ýßãüÖßÜ,î>ÖoözUªb.ãã|Ö©: >CÃîã/bªaBr~üU£ba_ü¢åuö>üÄÃ_vAuh,¢vÃA>bb©|ðuAaBOOß|uÄã:voUðCÄ_*ýäbr üüa><îö ZÃ:üª|*<
䢩oü:Uö@|ßC*,>hã oÃ/v¢£bAbýª/rü*/> oªvÃ_CuUBa¢bZßÃßaOb~aABð,U B+ãÜîåOýub.hzb,_b,AÄÜÖ+öO", - "zC@ *zÃœCäUUÃoBß:êAÖ_ª|büOãÜ¢uUb_ÖÖ.bÜåßÜz+¢@©b:zöv_Cäß~vä¢bCö|zh,:̟̚̚OhOhß |Äräü ¢ª>OåÜBãZ: z ðbÜðrbª£:©©~Uö©:ßðb~ü>ßö@bîãä©ÄßäßåvÜÜýß+ÃOðã~¢ã/+<_vÄ*.Zß.hoÖÜßöäÄ.b.ÃAðvuZ äBð:b.>OO_o:<üö+䣢î+üýårÃœa©:ªaÖîazÃrÄ/ÄýhãåîAhߣUaª:Ãã+räýå/©ü£:b£+|ýðßOÄ:¢B|ýO£BðüîåZoOö@*Ä|Z oO/ã,Ãœu©zO/ÃZrv¢äßu*ÃaÃ|Ö@z:Zbäzvbö©¢UÄuz.Öåý:hß:Ãä *©¢ªå_ߣ/îr©OAr,, >üäý¢h*zZA,>a_O@öª/ ßB.bß.ubÄövÃ.CUÃ¥:*AAaa |ðü Ã+©_Ä>öãUozz C+r£Cð|üüb* /|££ÜÃ:>Ã.uuC~U üCÖßoAbã>.ßa£uÃ>OZöZr::äߣÃhü@@©u¢að~>v_~ßZv© ðåüîCßAuÜî©a,Ä.orr ¢>¢U~u*O>>v©>Ör©ðz+_©aüÖßvZð Ä*:Ä©åßz¢+©Öª@Ä>UoUOb>äߣ©ª_ä/ ð/äÜoßöãå,*ZðÃÜb,<ÖÃßCbO.v+ªa_:OÖa_B£Ã/:CbÃAbãO~ßbîZðUÜî@*ßhÖ,ßß,Ãäh¢ßäÄbz~Ã,aC|Bßßö£ov>u¢~ZÃ¥ZÄÄUð£*üAöauOUª©b@>Uubö~Ã¥hU.*ðar|ßÜ~ä<:+Ö~bäßÃ<î©ãoCBb©uÖh+Ãœ.ö¢Bðߣ+OãhöC O+î|å¢åªîZüå@:ðh£ßî+O@_o_rbªOO|OCªaÄ_ãvAÃœ/U@BÃ*£|ArzAÃh©ãÖaÃ|v/ÄÃßÜð:u~ß|åªÖvAbäã,AhýUh+: åÜß|:>ý_ßzî/r*:.Öhh+,ßÄoäoUzÄ<£,:C ßüý/BÜÄuÖö,r©Ã~übA£/ÖÄAª©~ör:ü~ªhý£h+OvÃzzoãbîðC:/C£bhÃZuÄ_ýýO|*©bboýßuBÜßý©vüßß_ÃoÃÃãåÃrÄî/bÖbbÃ¥*¢a©ZÄh¢/@äz©| @u>Ö:ÜÄb|ßövåßbaÃðßî ßoUýaCZza,@o~ðßvzªB¢~:ä*/Uß,>h,å£aÄbUÃœ/©.Ã_..CzäuÖ/zÃZÃ*Zî_ÃœZåßåa£>~rÖa@o.räî:ð ööOÃÃbO+v+Ãœ U©,_ð£,ܢĩ:ÃîhvbÃ¥/ßýrß Äbrö:C ãüÃZ>A©ðvbhÄuZÃýßðîz+ðü bãbßã:: >,ã~býb,a_ßöîa*U£ýäbߢßuã:UöOZ©ð.Öu<_UaãÜb/ßÃ/ö<Ã+ýv_ä+CbZÖªÃCrÃäå@hßåÃ.üazÃra@,>ÖUßZ.,_,B.v<.B£ãÃðßîß©ßÃ|Z@|¢ðbÖ+Büä.:/>ýÃÄ:.ð|î£å>bÃ<Ãb/CÄhäýÜo+@CzCÃœ/UUðu*o>O©öB.Z+ã<ßAZo+~ ÖÜ*.©î+îöß©,|z|@ZÃœ.Öª_Ãß>uð/bÖ~Aîîo/aÃãvZAhª¢üZoU>a+üßÃrB|ð/üöäOu£<©oC@_öh<~.Bo@öÜO_ÖrbBoü*/ß,z*oö+ý<ªahý:/ã*oßa,,>rÖ /A~,>OÃœbÄ|ãh.:ÄOb Öb_ZÃœuÃýo*b.Ã¥B~ã@*,BCß+îÖåýuÖ*Zr*O:ÄÜov+£>A.AZ©Ã_|ð+ßörðâ_Ã¥u@Ußbãbãåb*BA/ªÜöå:rv©r.î.,ü@Ah¢+hhvu*ruýb©<~ZÖ@ªhÃÖb©üäb+o.©ÃhãöÃÄîz*|ª©UuOaÄ㢣å++Z+_ÃovuärÃœ_vöß_ã|ärv~hBã/ßä _@ÜÜ*Ãœ||OAÖACÃ,îÃüuOOýbÜÃBÃœoZÄU*.îz©aÜîovü©_CohÃ¥Ãro<~©h_<ýBva.Z<Ãœ,ªu,Ãîðbbðu @©@üå.B_ÜÜÖÖÜra©£oäÃOÖÄ_î>azU,@Öðrö ~Zhýrða¢ä<ª_Ã¥U/îb@hÖöüb+.ßz+Uaß~U©b*Aäü£b+ðöv~*ðvbîC|,ö**îÜÄããÖ©äÄCO+h¢<£vîäðuOUUãî/¢ÜvÃ,@/rÄvßrªvB|:O_h:/Ãœ+ü~ª¢:ü/C>Ü©ª*ö>rB::z */+CBð*u¢ßîîbbB*bbîåöÜÄZÃ¥,Ããhªü<Ã:¢Av@ý¢uBÃ¥.a:öUÃößUöÃAý+_@üä_~.>~åê:î/|v¢väZo©BoBbUB@+/,¢ßbÃ¥Uüß+@B>ü+/+ääOäã @|ãåZ_ª<î<.UBãC+©a£:>ÖbOhߣÃ+U*>~bCUuaü.r媪BÄvªv>Oor+ãÖ>£Ö<îîB©hî©AÃÃ¥h©buvªh/b/@b.Ã¥bOzru*Ã|v<@ZÜöCüCUªb|züãÃß| >~~/>ußB_vo.,b©ðã/ÄbðZ+ߢ<|:bAovÃ¥bö~*~zaßA+ãU~zu¢åÖu¢>Uýh@ª+hãü¢ý¢.u.uvÃ¥U:uýÜ.¢åý|ußb<ßaßö/ßüöåýbÃã¢OBß,ªäüOvUuÃÃ¥Ã,|ªo|>ß.,<ß/îz|UðA~aÄîÖãrO@¢Ö|ÃÄöU|z+ðo ßÖà ~oü©ªa OhÃ¥A<ᚔ,ýO£ãÃB>©ÖväaäUrör.UbuãAÃu_|bub>~üÜ*¢Cä~<£Zî h zî©bv,_îohhîªr_/:ÃÜvABî|ã*ßÖ,ðöêîU£,hßBbb¢r>rU,öuãu|äzuaCUð|Ãœ>ur:zÃöîbzåÃuö:Ãb¢B.ßoBÄhêa¢våå@Z~,ýAî_:bO@ å£*Özb¢ßoü h> ~BðvÃœ/aoð+bÄAÃ_üÖý|+vbUU rvb+zUv>Äz:@ß,B ÄÄýêAÃ¥vOr*~Cb@ä©ö|oöߢCßrz.B/üª:b_*v|ÄO+ßOCî©,£ßªãäßö,îBäî,Ä:oÃuÃhb>oªCu+Ãœhª@uh᜛>ªCÃœ>©¢U,©z<ð£:Ã¥o@ h¢aâ/oUz~£Ö~bã©hCü¢,äîr:ðîä>o¢bª>@ý>Ã¥> . ©ÖîÃ.büzÃœråðvÄßvÄ_zÃoü~hb_ãovª¢zý_U:|O<Ãœ@ãääÄ~UvêßßA©oÄoÖ£åußbo|~ðÃ: Ä@b@r BÄ/>z|<£ßãvz+_Zß_ª|ÄÜÄzC£>ß*îvÃ>:ãª/ h~£ . Äz¢*ðAîý:ü<_,.zäßÖCÃ|BÖã@öä<,Aî,++~ßaoÃ¥zavZ_ýZraAZÜîu©Ü¢|ßßüO/ª.bBOAã>ã¢äßÃZßuäuÃã:BUaoêöUu îh~©ä<Ã¥COO@:hA/ö<ß ð~U*ý>Obð>.Ã¥Uªzö©oªöÃüC_£ð£BüÃÃhhðZéÜ~oÃ/öªÜZZããbÜßääªö<ÄÃbäBBrÃœZà z|ZÃ¥|:vzA@Ö+¢ßUîüðvZz vöb@z~ Ãã>_ü@vÜåU.~zðüö /bãrUãa:aýz*ßAh©üZ©vß©výCbÃäÃý.zýÃBuÖh>ý*ßÃb~auUßCö...vzb£rOÃ,_röbo£ãüÜ,Ãœ,:U+Öãh: Ovã~¢/*rüCU£Öüv@C_ã|.ã _£AüAð ßbÃ,rv++.Äz¢a©oC¢<ýä¢zZåî ðZöª< v£zrî*ÖýhÖChvÜã©,@,Öã>ä b/ßUý+**üuvÄýÄäOo¢C>Uî.uýZOöß/bäî£b zîZ.< .zCÃœb*ßzªu++bOaªUÃO._@o.ß £_Ã¥v*ÄAðãã>@|Ã¥zÄ£~UbÜåhÄöÄÄZ/ å© oÃå>A:*zbà îZÖÜuOªÖ:ýo~ý,v_Cßb>üãr.O/r¢hîÖÃrßß©öðbvÃCr*aBîBÖðu<ýO*bB*~ßüü¢|u.,oªª~rÜßÜä¢ã.B@CÃ¥u©vÃa:Ã~ª*UZÖ<£C~ÄÖ>o AÄr.>.Uð>ZãB,åüÃ,Ãäaýã@>a+ýÃã,ܪÄî£|_~üz ©äü:©bÃœoÃ¥ b£h OOÃ.h/@BC>u,>u,hßÃ_à ý|b*Zr|öbÄü||:+äOßåß~Ö:uZ~|ÖÖ@zC:ar¢¢bäUöhbßvßöÖaZªî£äÃUBãbÄ£ßO_.äC_vÖuhü£ä/éö:¢ßÃ<î,:~vö_ßüh~hö>,.Ä,>vb<öaaz_.åªbäbÄ¢a.@vzu.oßbÃßÖz£Uzî_|BC/ÖC*< hbüª>uO<ðÃãAvãZðo@+£ªªAUÃœZÖ/o_br>ªOãhÃ¥Z@hOz¢b@zvß>ª~üßÄ@AÃßu<à +ª¢_©£:ßvUã>/Z,OUý<ª£ö>u©î£ß.bbb,ãðA/ý£ª+Ä**hzvU£||UýCÃ/B.äÖa£,rOßCª**Öv Ã¥Uö~oB*£,oä>ßbvh>öaCåªà oO r£åZßzîCÃ¥Ããöz@Cª<_Ä*Ãã:ä.Ão:Z>¢/ýÖhý>Ã¥AOðö,,£ýĪÖýUß/ߪ+OAäbh/ã+C,ö|+hBª>@.ß:br.A|ÄZvãaooÜß@ZßÖu/_üö|Öß@bãå/ýåaÃ~,ß/>u+:Ch @ÃOÃbîã>hßüUa*aö*~Ãœ,bUAÖoý.åÄA©äO.îÜðÖã@o,äî¢oßb>vAÃ¥ ÖZZÄ.~b<öÜCoBb~:uA~ÖUu©+Chýr*,:aß_.Cªb>OÃœArªÖÖ@Br¢vU:hb_¢ð~v~äh| C*|£<ão@,üuAbª+¢åýÃCu_©:Ã¥>Ã|zr£Ãå,/C|©BÖýhÃZÃœbz+Üüý©/Zða>>@ª+ð,v~ðA>uOBÄäÄã..ZB|hß uývª<.ªbo>,bÖ.ÄäAßoªåvßÄ_Z~bu.AUCbA~£/Aß|A_* ©îaÄ.bÜýr:üO_bOß@äÖAhaäªb|_îð:C£ð:~h*_b:_Ao/<_¢ßÃA,ðßãývv©C*ßUa~<¢/AýßîÖbb>Cã.C_öÃoîbrãB:zåÃ.ãZa~öäBªßðC¢äÖ Ã¥/ã_~.bBC@Zß*|©ðu**,rBU@C|a_B:ß:ª/öoaüobrOßÜ:©¢rCß@u hb+zO@UÃCßU:öÃha_*rð î©öÃb/öß/vð~C<+~ãß:î+B,| @ö:¢ãܪZBvýOvÄ+ª<,äv/ÃÄÄÖîö~Zb.CAO_~ãväÖĪÃýÜv.ß+.>/.CüåÜÖåöãAýbãåhBOßÖr@B_ãöÃö>äOuÃ¥@ý©ßªOäUZªA+o>£uvb+ BÄÖva:rÃ¥Ã.bUßÜU£ubZ>aoaÄ|ð<îý£Z*|©ohÄB_h:>|ßB£/a|U|ÃöîßüývrOã.>bCÄ©C¢Bðh@oA>ªrªî<<*ÖB_,©åOo/Ã¥+Zba<Ã+ý ,: .rÜäZ+Ü©ZÃ+bÃý| aU/ð>UoåýCbübö@ßObîåßUU.+@ZOz uÄu£¢_é<üar¢.OüBh+ª>ZÄîß|h*uo¢ßüOÃ¥*Ö.zýB@|ð媢ãU,/hßÄßöýzäb,+/ OU:@ãr©ß+>ýÄÜArözCßÃ>av*ö*:|U~ÃbUªÃ+o ,rÖz/rãurZ©CBv¢~ßOüB|>ÖC|ᚌ,bÖî/£Ã¢.ðÖAöã*C_ý¢£ªî~ß*~r/zz:ß,ß/ÄußO /ö£~ýBubÃœ+ð:Ahî<<ð_ä/zzC<ö£C,£îÃßZr£vaãv*h£ö,ýÜUðöuZv Ã¥h£@Cb:C©hZ£+ÄvᛆbðuÃäÃO Ã¥u<~zhhB@Ä©.>£ß|ÃðvîÖzUܪ@oÃ.:UOÃ¥>|Ãœ++ <Öö.@b/r@@ <|z,Äö~*Züü/Ãb:<@aBbO*~ÃœoßîÃÄoz£ObüÃä>ܪraB*@zv*b~îr.ãOvuvä_|©CUCUÃðãÄzUä|*+ýÄOA©UZöäüð¢üäÖv,+:O@ä@B~Ãvbzß*ð>ßZ@bãä +O,o böüöAä>CoäCOB/ýýu¢bªýhO~ur_@hß <+Ãœ@ÃZCvðäß öo*ý,Ã¥*o_z_ ðÜ,Ããzýü*@£ß ðvÃC*oÖOßU/Ã¥uo¢Ü£ßOã/~öî+/ªåãü.>_£:¢©bOoÃœoh_öã¢h*rÄÃ<öå*zÃœÃð_ä ßßð |ÄaC~ªabZ/,ö_ß:_£vÃA~ÃœBzÃ¥ vªðß ÃA>b/*bCC:îªðîßOCb Ã|hîÖߪÃUv*.hr@ÄZß@öA+ooüß>/ý*A᎛,+©Ä_OuÃœ:oBåãã¢.hÃßC~ACAo.+B*ýbý|åßhb~v ÄUðabBü£ß|rZýªA+U¢_hßC/:BBzöA>+|<<, CCýb/ÄZÜßÜaüä/Ã¥.AC@ªß:îöÜa¢zr£ß>rÃUh£:ÃBߣ© B*B~ªö@*üÜ@©uAz/Bü~oü>ÖOrÖÜ~..ÃœOß|ä.ðAÃUC:_.O~îªovýäb>+~~ð,äOöð_a*ä©OÃ¥>bv A>ü*ýªäo/o.ðCZOZãa¢ãbO@ý.ãÄu/vÖð~Ã<å¢UðÜaa@ÃœCBu~>îb>//väýüðoî¢Äo,< +ßz£~bäãb£ÖåzªhÃ_rv>o<ªî£zÖå+z*+¢ÖãÜrU:.~ªrruB¢|ÜîoöhýOîr,aZ ü:Öz+U|Ö*@zÖãð_v,©|ÃOC:h,U£~ö|ßuOBªh:îäãbß.,ärzb¢£ßÃ/~häýªðhªýßßÃb|Ö ,ß>ßâ+ÃbZOðªözÖbÃœ~öÖoãýÄuba¢£Ü£|Ãuß ðÄüÜoî ÃraA>b£zýß|Uü>B>|*b/@_+äÃ<.ob¢o~v@U@ub>~a<£Öoýäßßu./B<ßOßhzA¢>u*£ýý:v|v>ã<.bå¢Äê~ äuÄ<ÜÄbÃBÃ<ü>UZBoý,|UöbÃœZO.|.AO+hö@rÖräOÃœ*@ðß~__ÃrAbîzCäÃöÖ@©Obð äoh£ß£++ÃœhUChüÄ¢äßraB<ßߣ<ß/*>~Z~ªð/,îö|£ÃCOåêübåß*äîu, ýzãöAv_ö@@AvUüå£*,*£öðåã+<ÃU/©+_Cbî£ÄÄ©ã£ðu", - "ߣßão>hobuö£Ã>ýAÄßî|ãåÜuC_>/ÄãuÄCCv*.+ð/©<ÖUZîB_Ã*+ªBaÄÃ@ZêýÄ/Uau_¢u£ª*î:î*C@öu:üÖ+:ªªB,~ãh¢z:öA@ub*oAöbÃœ.¢/u£ß<Ä~aözªzîäü£Ä¢/åýýaýäÜhh/aüÖß/ðU©Uv C©Z", - "abzUÃUðîBoAüo_©ü/:ÃAÄåb¢Öaß+/|ab*Ãßß/ÃuãZã+@ßÖ.,_O+öAzãhaÃ>Ã/|<@oö¢.Öã@ÃuäÖå>@ßýª/ãUrbbAå£ßÖr>.Ã*ä+Ã¥hý£Ã©¢Äo|ü£<ä>haÃUzbÃ¥~_BAÖ_Ä/a_¢oðäªÜh|~*Ã¥:ða åÃ+.aB|Zý_:_©îBýÃ_.a ð£@u~ÃÃÃýhªbÃåä*Aa/båö oU<©>*aöbãr..ß_ý,ã:üAu£Ä_ýð/ý ", - "<åßîã:b>åÖ/:ö¢Cð+~ãîüuBÃzv>:ÃœaB ª<>ý|Oᜆ<.ßðB<Ã¥z+bCßUýUÃœh~|öªÄä£Bä:CC*OßOA<ßZÃœuߪo.üý_*>Uu/ªuÃvÖ|oßuaÃœOaäzªr>/>**Ãœ*:zå©|>üåîð~|>ÖÄUCüaÜãü.B©ðoýßÃbU:*ß:©>UÄbÃ¥vbZÖC:Ãœr+, a><åÜý¢*übOã Bhöv|¢ÄZÃüà ßðßÃrª*Ãî:ä |î>ª*bßBuö£ü>@£ÃÃAv_ß|öb᜙<ßo.£Z¢Ã©CBö©hªBÃ¥vãrÃü¢aÃ¥Oz_aö/ÜÄz vb@ýÃåãü¢~Bª,O:bßý:b@ro_<üB©Ã©zu+ß A+vo ãýý.Ãœ|ð*|å£ßUußbðü@ÄÜoUöZäU|ã/Ã@ãhu_rÃÜîbÃCäA@ýhavC,Ö/_ÃÃbhî +>hOCüCýväOrª©AA,ðÖåC_~zzÖ,~_ßoªå~©Uý©î|ähÖ,ðroÃœ>£*h+außðßä*|@:<Ä~ aýßAÄaraüßr,BªªbÃ¥:Auä+äzOß|ý*ÃZ©*Ã¥oßĪ¢hOaUaÃ/ÜÖö/|aÃ+<¢C<äî/r©Z@@bu/ß+vßîAãCä~ÖAßbîCvýü.@©uÖÃÃü*ü>u+| Cbzrß ¢>vÃ¥Cä£oÖOU@bßîoh@brb>o*++<<ðãßbr£ßa. £äbß~ßUÃzÖ.üðÜh~UC*î@Ã+oUð+r .aåÖßårovî+CÃbÃböîAðª,©©>ßî>Ã¥|£.b*Ã¥Ã@uhîð.ýrüßã©£BZA*vÃA*uvüüÄZÄvöb Oý_ÃÃœCar.:ÃÃýUãÜv¢bÜ¢ä,ZüoðrCvöaÃ>>£__Uuv_ªî@|ýÃÃ¥>:¢åZÄzªUÜ¢rßbh>©ö<ÃrUÄ,ä>BU<Ãœ,or ¢ß h/ßOAZÜ¢A<<<,äª>ýÃvÃuAߢbC<,:ýCÃœoÄvî+u:ðýÖ*uü+öU<£ãîÜö>U.:uÃ¥Cå©AÃ¥|:¢£ää ,ÃÃð+U_/å£~B©raura>îa~ßBB *.îzªªß>@bu~Ã¥oðÜýUÜ©u.B/rîC>¢Ü/ßý>,à |ãuAäãðvöÃBb£zbv~b/ª©__ä:C¢.:BÄ~.|ð,|¢*~ooUOa/ý:*@./üh©Az+~Ãu@ßäa@£b+ÃÄüÄä:>Z*ãahoböÃZªvîîBrAª*£*,o>ßîb©ß/ÄCÃoÖö@|ªb/äOÃî+O<~>ðC>ßoîr uUb,CU|>~Ãho©r.ª/Ä~Bz@ªªåaäÃr**übÃöo,Ãœ:*.ß:Brª©ÃÜÃÃœ@b>,v*oö:|ã>Ööur>bÄbý* /Ã¥<ð.@_hUU¢z£:>/+Cö¢ABýv ,AÄßö©åßé~zAaß,:ÜüO.,,hÄ._bÃœ@+v,bbãrBu:ß©CCÖ£ð©oÃ*rabbä>rub~ÜãÃ| ãO.h*:ö*>_Ãœ>ÃzÖ£a,Ã¥.a£vhCê+orÃ/<åîzvA*îv©__Bý<îzöÖ:äh rðZaªã|rzã Aã,/+ýbýo CÃACUÃbr/U~ |Ä,ýaÃbÄä~Ãœ.åýýbrUÃ¥+h>ýåªÖÄZÃb+üzð/äîbZoªrr¢£ãà |£rÖC+ðr< býU+ßßrßBü_hOoöÃÖöz+UÃœb,Äbä¢äðýU+|üzðU+,/äÃÃÃœ/:bß,¢üîCBzðä£Üö|öoüzÖªO£ß@|ðð+b ªZ~ßãC@ZÖöü:b:<|hªÄüÃîrª+bÃœ,Üßr/ h/Äß>oà äßo:@CUÃœbBÃœ:oa|£ ~/*C@öÃ_CðAÜã/ü¢ãbboãö@¢ÃÄ/üOhÃ¥b>îÃ,r*Ã¥/î,ruýz: <@O<ªoÖ>ß>ÃœuZ|ößoÄUÃÄ~/~|UC<.äaZ.v@A,©>Ī£Ü>ÄýöîÃã@£ãÜö*O.+ÃO<+ü,~bßzo vÃœ/ü£u©/höýUÖÄ/Aãab*ýbbßÃßßbü>©*ÄB.uu:~|îB+.zAbýé@_ãîu.ZÃœ+ý+Ãœ*b/ Ã¥.Ãåßð|Ã+©|bääC/îBü>ßåýö/bªOCÃzüüÄr|ýßĪZuäÃÃÄ>hU*:ý:ß@ý.ß/Ö +~¢+~Bo£|/B,+r~avrÃ|<ðÖZz@hrrhr*övî¢u*hª*<Ãî*ªB¢ðýo~ÄOã:ß*bböü:.uÖ/BZöÜîbCuauäðÄ/@vrý¢£Ü.:ßåZb£,aZ©ªoý ürU@~üãuü.*£.ÃU.ªC©©ãhî¢ý:ãýöO |/rZaÖCbß~Uä:ö/UÃÖåb<>z>ãzª@CCäZÃœBßru,U£>bäB~Z/Ã|uhUbCãbCÄ:Ãh~ðÃÄüb+huaZ>u|.¢uî|äðüåýªu©UÃB@ö|>ðhO+¢ub_ uãÃã_//îßîßuªAÃCÃ,C.h>*öÄ£Z:ãv ß+£©hÃ¥+hßZrÄbÖZzBÃœCîoýb@|aãü+|u+vzhöÜzr. Oü|,>ãv:ö¢CUu£zhO<Ã:*Ãîb@|¢ýUðr~Ã|ýa>hbo~>.ðCCA©ßߪß<üý/b@äöuvra/@_£©z¢b.|äã ýö,_üO/ +CÃ~_öývCözz,>~|+ý@O_,ðChBb|<,UBÜÃb.ßbC+£Üzª__ö>*ßoü©UÜür¢:/ä.ßÄÃu@Ä/oÖzäh@ÃœBz|ªo~u£Uv.@îBurªÃ>Uüªî aubObäB@Ãaååbävvro<.ßaß ö|ÄO¢Zß<,U~ö@A|,Oîßår*hÖ.uAßC<@a.ÃU@hðå/u,<ßv ÖO~BUîuðhª.öÜ,Uü*ðÃ~@ßÜbUUa+©ßäzÄåýåÖÃv~ßå*©ä~:å£/,îAC:,äãüuö ©ü+îA,uð@ÃœuC,*+v*£/Cbaoî<ª/~aîÖA£ ßãU©ß:ßýb¢hu,,î~vvî|©B@u~ö©Ã~.oäüz._၎bb >Övrã,züz_¢_.ðvba_ßC,¢ ªÖü+Cv,ªC¢u.Ã¥>vÄöovöðäZýüÃ/öOaüßABz>C£/_Äß>ãbßr_Z£ªaöýÜ+>.ßÖªÃrb¢ãrÄÃÄ/Ã*h/ð~ÃîÜ_ ©A>ð<~,.öo¢*bAZððuÄvý£î/ohßb£Z.>vÃzãåru>Ah.ÄüB/.£©ß©bZ*ACbÖ*ß Ö©ÖCB Ã/bÃýuuãuO:bÃ+.ý<Ã:ü,ZÃß/åÄÃ,ÃüãýBÃrªbÃßb>ßöªÃ£.ðÖOa©Cö/ÄUZB@_:+Ãœ~z@bzÃýªbÃÃ+Üübrbüðb|äußßðv:u//Ãa¢< ,ãobrhr|AZÜýC©BZ~ uÃBÄu,<@+ªZA*o<Ãßrßäã.££Ü@hv*|*oCÃ,ÃU©ªÜ~ÃÖ~Zýð¢>+äå<Ä>ßb/öÄUrOßvhî©ÖÖ>h~Avo£b+ܪ@ÖüÄ:©.r~©ßÄî_+>öjklväÖåÄz:/ oabOCÃ:rCß:>å£ÄÖr© £ýbýouüä.©Ä_ßßðzß>Īߩ©rz¢ü_Ã:Uä>Ãœbb_ý åÃ@v.Oö.äAAÃ/rÃߪߣZüU©>ü", - "5875-05-16 22:34:25.445", - "1972-11-29 05:50:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array(-1, null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("0.0782", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 0, - 1, - 192263244, - 11.23, - -32768, - null, - null, - array(("AE48C31DD7264B5F9649E3E8376C659701F1287A4980E9BFFC9D"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("0F"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "üo+/>ýz,h/+@ª ä/ðb/©vÃœ,vaä<>A@ ÃÖ*¢å>¢Öaüª|uüOîÖvåäî*.zOÃœB.¢>ß*ªC©ÖUÃüðz .Ä£rozãüÃUo©. *b/B.rC>ovöCZ.©bZö|~öãý¢ß~©Ü|OðÃ*.rUÃ¥@Brî_,vAa.z©ürCã+£,:<ªª:êba/öý:ß,/:+bovÃ~Ãœo+C~ýßãüÖßÜ,î>ÖoözUªb.ãã|Ö©: >CÃîã/bªaBr~üU£ba_ü¢åuö>üÄÃ_vAuh,¢vÃA>bb©|ðuAaBOOß|uÄã:voUðCÄ_*ýäbr üüa><îö ZÃ:üª|*<䢩oü:Uö@|ßC*,>hã oÃ/v¢£bAbýª/rü*/> oªvÃ_CuUBa¢bZßÃßaOb~aABð,U B+ãÜîåOýub.hzb,_b,AÄÜÖ+öO", - "ߣßão>hobuö£Ã>ýAÄßî|ãåÜuC_>/", - "abzUÃUðîBoAüo_©ü/:ÃAÄåb¢Öaß+/|ab*Ãßß/ÃuãZã+@ßÖ.,_O+öAzãhaÃ>Ã/|<@oö¢.Öã@ÃuäÖå>@ßýª/ãUrbbAå£ßÖr>.Ã*ä+Ã¥hý£Ã©¢Äo|ü£<ä>haÃUzbÃ¥~_BAÖ_Ä/a_¢oðäªÜh|~*Ã¥:ða åÃ+.aB|Zý_:_©îBýÃ_.a ð£@u~ÃÃÃýhªbÃåä*Aa/båö oU<©>*oî>aaöbãr..ß_ý,ã:üAu£Ä_ýð/ý +üBaU,ãuBbÃðßÖhz©zÃ@Ãzý.UãO~Ã¥_ªCÃÖßC.~äö|ýåa_äÖ_<ö|z|,r+ªýz:Uðäö@©AäÃaÃ¥b/ßüÃ.ýä+b£BöüZ|*_/@/Z.â", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array(-1, null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("0.0782", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 3 -$values[] = array(array(("FBB2B8D1429F4CF743301F5AEC311F7C7F1F62D59F958AF667506C36D2FDC59BE9CC37B38E16F684242A2250E559EC648E18F3450A7E33DC6F11C2F252BE1BB1830146"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("745B77A9709CA60E6F872AFCC2E5E48CAE0E16B40FC079B6F5197785FF253398A65E11CA5028533794EEF8836FD1BC68DB68D2F311658169B02D55FE65538B3A62AC5563D4193C85BADF2021C8F646235C0EAA9652408640DDA81922D354D14162BE21C37B5893C22C4D3832455F"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("E632A257A9F4E7222515862CFF19B2660DDFBF09D583A7037AE76A050854FB7D065F3CA1B27A976DD6FE9D66D267C2129AD12566A097E5628A9EDCC18AE11AD11A0825A0DD10B492475133D6E540311324586A362F95DD6F79D916139ED5E3F4E4CC4CC9412234891D921E4E630F7D496DC89C3177E3A2852BB62603FAA0F4716E3FF119F467D20223E0189C768E0B695CCCA6D538B17D0029C79D8AFB308F7D9DACE0241E635505F69A0D96CB050E9DAABCA72DFA1F38BCC1F0EF2DCA4706F3519BFFC9ADE64BF977A0DF9EA3E63524B800DF3F8961827E7424622E73273BAE941A45300544EFC371AEB6593F9A18DABACB74C967C669EF18815589831D854F65300989A1041E73101BCDDBAB293ED90B8773A7B27146CDB1551884EF6640542866F40E918F39B6AD00B84BF98E43EF427C7EFBA9DD9CF3EA3BD74CEC9984AADB79B245342F487B4EBDFAB1B9FBD42212284172E844AA4BC9012455B8EDC9137C0E6F7FDBFEBA133402ADECE2EB3AEE66B439C35711C297CF677B330AF2C361F3E635AA8B5447136E2E3EE597BD68DEC635EE03B305BB6A4BD8B074828708413F4BF8FBB58F63126D265529D2DD0011FF9D6CD403C0AEEE2BCB5FB3A4D391E625FCA64BC46C2937D2DC958CAF92976ED79E74E51A343E7E9C0AD0EA30B06D99946F04D636B44C92743502262A213E9833F0DB055EACCBF1BE27AFBA9B3E06216FFBF8408EC1372251E270E53118547ACC37B63FD5673BE8A312180F0592F7352EE1B72E29FD0A72B9B8B47C3BE6BFA5D334EE6388C74C07C00F9D7BA9271DBFEA3BB750612551CE2D43861CB3BEACBC53723CD0A493AC6795AF6D195F8FC9919BF591002F6F44ADE31F3645412F530162E02736EB4C1A578E50BBB1B8376E9D7AF481BE2DAC8DE32E8FED845C6725655B6CB67F6792A64EDA3971DFB492F4ACB199CA4CAE95D4B65343085B5F96FA272A55564C80EF35E74E33E4F33BAE2F9C9A40D3C39CD9B7C697002B248A4AEE443F4AB55E3CA82BC264E3FD7AB0E07BA5146CC11EC3378A509E8FB480C3115D0445A0B4F1F9D160F115115FB9DC3C16B685860F1D88CF8007BF64BFACF4ABFE5285F835102C89AB430CCF09B2D8D0736279FDFA3C013F7B751FAC319411B3FD6A120C34E306058AFBDACC11B9C5CE2FEFF15D33F6B0C151457CC0F07F58173158D29E9FCB9C8F5B7373FF61352EC2AF507D41A3B38007BA50BBEE6746140BA2C39AB8E7272F63536101BF82B05F1C17B910A00117747DD8985B6869A5B660E24AF91672AFB8BB005C321B053EF05D576526D23652318D4BA95AAB6E1EEC52D58CBE43BBB9C38D6EDD76A870F681A5A7FD10037EE32990186BCBC9550D4DE270E045797FACA29EA2996458DB891C5427B404710DE9D3F162108605B453ABE3C3B87DCA80907A9DEBB9DEB5B76CB15CDCE55D9E2A296F131FDD6835F2C619FE188D14FB43272CFA6D66FFC3299558F06F3928CEF61DB9AA596AAAB328449B25AFD0AD3A47B619B5B85320B3299BE97A7467C6C99D4B080AC37A91E87C79C228DBDA2358F82EA8B92F630692ECF670BD71204EC08DD18864D8A276DD1431494E8107E75F78908255BF1DAA3A34651DCCF9E4674C2B01AB0A41BE7055A7DB94EE29C811D51F2134E0ADF90170274E207384B9D33FC9FD138F6CED0DBA103CADE378BC107C4348BDD8AF86AE32003AD2F39E0CD0FE0865243CA4927832138AC0714B2DF77B3DB4203BFBFA90434EE18F220A1A2C135BBFEB1397E3C7B0C08BC524C63CE6E23357C5E9EC94F20DC4D0EDC3134628095EBD159B32E3E359EC6F7B70357B8D01FA9CFEA2444226C6BDDD4EB6F5F4AD7A53BBC609275BAF5F746BFD8D1971C85953EBD8765775B51C864EED65953C53E7B2011C79B573EAAEFD9F3DD854A9AD3C04F5531A2C3712EE06A982D87E39CA481805CB18554EA3EFF13978C837DCC7EC91A1509FD939CB7AEE3E730BF10220C3D4F5C05EDE94D6B6517318F2CE5AA629E19636CCE92A6EDE3A503A26DBA7EEFF08A4F3420C790E0CCA9A4E5620C8BC8388491A5ACB63EAAA86E2148E6ABFEAB98429FA1967641D78983A024EDC08BD61A3A6AE828D9423A4CA48CB2A548FA21ABF94D206DE932C242BA1C291519BDE7E34A2EAB401582BFFF6555F2EA8F3140887F79F3736B683008A8A56EBC182432E6A4CE5B194EE095778D2008943FD7B288DBA196A0CA7378E1FDA398701403E640220B45C1D626399EE37CE64A609CC8D0F2E6E0778D911C3056791CAB4725628A3AC8723392F1B1B567F8BD4D8D2D8160DBC87D2E226EB290EF156B5D0A8E56BE195C10982A943E01342B1095D4CEA328F8BBCF7FA83467CB15E1447C8653AA"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "ý~ý/ý.ÃœZßü///bOrðbðÜ*AvhÃ*öªb:/OAaß©B£ÜbuÄ.ª|¢zÖ*Ö: :ÄrüzäâBvZã_£ª©|CbrvzýBÃzßä+UühªbÃåärÖ.äÄÃåÄrÄÃZCv.CÃ¥ äýz/,Üüb£ä/Bß,Äo£|>hÃbr+r¢zÃÄýÄbO*aååzaBã", - null, - "ß*ª~Ã. ~uoÄaÄrböbu¢@z|Ã.Ä:__ã/ýöÜ~ý/ £bbåÖ/ý£îÄOÖ v ÜåÖv©OÃ¥våÜÖßOÖuCr£ª/o~¢_hÃÜßÖ+ß@O£oîãÃÃœhÜßOBa¢ßå©~üCÄßorvrCü.b@>ýuª@~ v<ä:©+.@@>ååho,©ß*>ü¢£ªßÃÄZ©CÃœaZz*z+zUbß,ýî:>ªäðÄ@:îÃzÜÖö|uÃßß+@*bÄ|îbrZ:h:AÜýÄvC,î,üCbO~Ö.ÃA+A|ª+C/Ã¥za©zo/|äÃüüäö|.ðý|ua_üo£ ãÜ:äBߢ£ªßî.* ä", - "î*/ýz_B~|ÖA©ðÜ@@uC+OªoZ>uߪüäA<>uuÃ¥*b*_U*Z@A~ü©A+Ã¥hr>ãuÃböÖ<ðCÄ_Z_:ÃåO<ä~Cãz£vîUh~ªüîvý,ýhzB~bzu~ýÖb@ýor.v¢+aîBÃœb> *@Ö£a_bÃÃÃ¥+v@ߪé>äÃo/._¢£ª,bb@bBÄüÄß*Ã>:Z.zbýB|,O/ZOÖOðCra+ðâÃ* OßãåîzÃœ+ZÃ¥aB>£_býîªUä _bª¢ +|Ö©ÃüÄßZÄ>vC@ßßA~+£Zhü,vß,BßOýz|äöª+|äÃðZhÄAu B,./C©ßÃaÃ¥@ߣ/äob|*_.üå.<ÃÃÃaýv*A:ZÄ.Öä£ÜoöÃãÜrUbhboéîh>ýü+ýbÃaªoÖrhoÃzÃÄÜvao|Ã_/©åðC©*_or/ß<£Ã<öCÃAýCî,Ö:üî£C*.vã_.ßb><,£ýüZ@rbbßbAå©_ Öö*å¢*öÖ.Ä~OããUÄýbÃåîã_BAãöýðåü£v_ro©/ va~|Ä_ª,î©vÃrCaãª+AB:¢ö/o+,.ªãÃvz<:.åªhoÄb/:ðA¢ãößåÜ£ å£bo. hOÃœbða/ãªöauÃ¥+,O<>CÖ_:ßöö¢ýªß*ÃOßa_v+_îªîr*äå|Öö<<~£Z~ªÄüCý,:CãUuã.åãhßö~@ä*Cöb,äAh ÃœbîU.ßZv|@oÃœ+B£bhÃ¥C*âbÃœÃUªAA~_.ý@Cü.b>oUý.£vªÃý |>rä*hߣzß|v.Bªzh,ßðv|ãZ<Ã¥aÄäîª@v¢.U|üv*zzBvðåãîß Crðbba£ð,bÃœ*OavAU:bäö©<,ÖuA©Ö|CbUÃœbÃœA©:Ã¥,ruuÄðzäÃ/©/~z~z,ä, hZ¢ª,ä¢h@ö_COZvCb~AÃ¥_ê©BUîðrÖ,OvCÃB|ªßãZo ßå|UZbÃßör_ª>:oa<ü*öUUZÃ¥h,:ß.¢< ZÃœbr/uªÖuo|+ÃœuZ/ßßO_*äÃöv~ýbBöª¢Ão Ã+ü|v+ªÄBÃðBîCaý¢:öý.bhðäu@£v_ö¢¢å|bäuðoÃoªv*>büböýoßOð£UAÄAãö:_ÃzÃ¥zÜÃb_Aab/hß~£að©r+:*îz+CüöÃð©üé:býZÃý+b .AvoZbrÃ¥,:ooß<üBÜð~>£~AOübåß:b@zßu©B b_@,öö,*C|bÃßBA,ß/ßB**äÖü,îaßäaüÃÃÜ/|<*ðZ.zzBîb ÄÃO:_< ðîÃ>|@ß*î.Ã/vZ©hoZî|B@Ö~b.~zb©_/U*ã@ၚ,:b,Ã¥*:ßAAªAO¢îU,oo:hö@ZuÃ¥ZOî:o O>>CACÃ¥u~~_Z:¢+ß C:+£@¢:öÜOabZð~Ã¥Zä©hßuvvöä|.uã~><ãßýÜ©£åAoUöåOz©ö,oÜÄ©_Ã¥OBÄ©bAÄö~>|/C~BªußB£üz,Ö_Ã¥<ã©ßÜßzîß©üüßa/Z++ªå_*Z||ð~åîäÄöCr|Ã¥vÃœÃÖCuÃœ/£UýBrÃöAo© C_CUäU.ýöüaãª/_a~£äÖ |äîzð<ÃuîÃß:ä/*o@ä@ÃUß<¢,,££©+,Zîa|©rððÖÃÖbvu,ÃýräbÃœ bbÃ,Zð_ßA|_Z>|äå©ö~öÖü<Ãœr.a£©:ßzªüãðßZða+ðÄO>ýÄßäC_î<ã|B~öOäîåo~ÖUÃ_Äöb+A~|ãvzZuÃOß|ðîßÜ,Ö äCbB>ÃœC.vjklðboåîvÃãZ©üoÃBª©ä.îoüªrvüÖað>£ðArhî+,o:_ã,Ahzäîð.rÃÃ.£b££uBîä>,>b|ýCbC©å© o@>ö_£äB>züu¢_BvÃob|Ã*a@bÖäÜäz_ßab<@hÃÃ:AÖýB_ßð:Aß+>ßC¢ß>üåßÜBurÖOr|b|Cb<ª@ð+ðýUß.Öbðbüî/©AOßå©ö_Ãœ *¢+,:© auªÄ/~ý¢ý+*hvÄrzC,_ðuªð|,ª~¢¢/AZ,~AÖzÃU/rOÃ¥zª,>¢ozUýðÃC.ZAzßý*ý| bÃé:a,hBÃAÄ¢öýBü< OA+£<¢:v..:ãr ü_©ßüÃZãäßabÄ£b ÄAå©+zÃBrbýÖýÄ£îã+£, ZaßA*O_£.ðßObÃ.ü@ü©ä|ªðÃ_ähoBu ý©a/AB£Ahh/ßhO|îauö ,ý_Ö©bB<ýöCðoîðahvÖöýo_BÖîª_+bãÖãb:/:ߢãzª>@î:+v£ß,ß>:åÄ..üuuUhöC+BB:<_z¢ð£+£arZ@*î|£AU+OÃAAã,¢b<|._*Zßovbo~ß>b,u©Äå*~ZbhÄîÃ:>:UZ~îBª£ðU_ÃZ~ä£|é£ßo©ÖÄåZ*.£+bÄÖ¢ðzu~üÜ|Ö<*ªZÄroAîb *Ãœ+|o@ :¢å~ÜÖUCßh ð:ä+CÖ.uCzßÃ.B:¢~~ü@|*ðü~@@ããîßÃAÄý¢<ý£¢oð.a©ð<< h£_ᦚ~@îߪu~,u>B©ÃCßhªö*+¢ððZ©AA@ÃUZä/ö,B|rb,|_r>~Uß*>ýbªbýß_ßböaU¢*.Ö U@C*/rbUz|üåÜÃßZb+oÄ/ä/Ã_öB¢@ÃœZzÃœBß.b_vC:ß©u|ðvrrü|üUAÃ¥,B+ß>A>ßzau_aUZÃœCª|Ãœo:äöðu:>>aßaîãav.bÜßvU|O|ÃO£î/©ª/,h>Ö:~Z~azzOr¢OuA*bßÜ@ðî©+rAß~b u|@Ã@@ü<>rvðUý*Ba.Ã¥bªÜ©äu~bb+Uß.vüü£C<Ä@,| |vý£©aCa+ÄüÃ:î£<ð/Ã¥CU.ã©ZAÄü_Ä+ÃA:CªUð.bvU.ÄZU@Ã<ýÜB>:bh/z+£zh:©*vÃaßAuýaßãaah+b@ü<_ðZr|+üOð|ÖÖCßÃ+a_@ö/ÖÜb©¢bhzö C £îÖbbC¢ßäz/UbÄrvÖ*>aabÃœhr@özßåýu£.<åýZö.Ã¥h|:*ߪîäå_~ßC ªCªä|Ozaýzrª£U Z<.bCÃ¥< ß,ðb:ö*..@BäÖ+/+U~C~ohrü£@~boÖa/Cßß:£Cäb£îßB_+~îAAã_Öîo©rü,ð*ZU©@©Öö~üÃCÄböv C.ZöÃAhî<©u¢hvÃÃöÄ+£h~墢 bZÄßÃüî*B_Cü©UCÖbý@z/Äb,Ö*|ªO_@£ +üãr£üvÃßOh¢übC_,@Ã¥BzÃ¥*uzb ,zo_a:£ZZv_,av~£_ðÜÜz:bÃœa£_A>¢ªî~¢ä/¢¢Co ÖãvähåãÄußz_ZAöu+_+ðªß£©¢ß+,oAîߢ/|ªßðåÜÄzvßZ*ZªðrUbÃuÃörªuz>/_Oî>ðã:*.:©ªuZ@ã+Z_Ãœ<üÜßb,Ī.uÃ~vvÃœbîß/:h~Üß.ha/.*uöÖÄü~,B/@CäðrÖB ö@/ð~ãB¢îOý.ð+jklýÃß|ZOÃß*î Z£|A ßbîý/£C©Bb,.©>oã©åuAZÜ© ÖBuðÖ U¢übhz+r¢ ¢h:ußvhãÖ<¢Ü*aba,|u /rðÜßÜ/ª+ã::vÃ¥,h :_<ü+oZÃ:ªîÃöaÃ+bA¢b*>:|Uð", - "9999-12-31 23:59:59.997", - "2079-06-06 23:59:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("0.7414", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("0.5117", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - null, - 0, - 1826608718, - -1761264475, - 20544, - 255, - 0, - array(("FBB2B8D1429F4CF743301F5AEC311F7C7F1F62D59F958AF667506C36D2FDC59F34F4973B8E17045382F4769C1ED8B2126F4FF2A8F564B86528C26AF2321625466A7F25F89AE4EE681B0E52AA1FEDD88F9E59C7830A1DA9CF3B3112CBAC12216BCF30319EB2BA778A608A8CE7110E453AEA"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("745B77A9709CA60E6F872ACC493745AFA4B06A995B437D530432660E71CCE41255F81ABC7C9C9AE28CCD008997250986D601DF50DCFE1030DE23A43E35979DA2A31877511C88320716870C8E43F3BBDE28E0386FED933656E8EADD58F75BACF42F5DE67FAEB7694DCFD73A54F182ECEA67C615555F2EB410E2206932ABDA0FBBDB175EE50D3C307176EAA01558E16566FDF2F03145569401A9D69D8"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "ý~ý/ý.ÃœZßü///bOrðbðÜ*AvhÃ*öªb:/OAaß©B£ÜbuÄ.ª|¢zÖ*Ö: :ÄrüzäâBvZã_£ª©|CbrvzýBÃzßä+UühªbÃåärÖ.äÄÃåÄrÄÃZCv.CÃ¥ äýz/,Üüb£ä/Bß,Äo£|>hÃbr+r¢zÃÄýÄbO*aååzaBãuãÜ¢îî,CÃœ_+O£ßßöuÃœ:u*£o*î/+ãv ", - null, - " ", - "Äðãvãß ßß oba.£hß/,ßB£B/Zö+ßrð,/@_@öãaÃ/uåüz¢î.AÃ<+>+zUbß,ýî:>ªäðÄ@:îÃzÜÖö|uÃßß+@*bÄ|îbrZ:h:AÜýÄvC,î,üCbO~Ö.ÃA+A|ª+C/Ã¥za©zo/|äÃüüäö|.ðý|ua_üo£ ãÜ:äBߢ£ªßî.* ä", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("0.7414", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("0.5117", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 4 -$values[] = array(array(("70BA23A819E1BEADC92E0840F382D7A5DD89EDA9FA12486DCD843FA57FAEC5439F0DE3836E6804ABD214C35672F9552A2CE003709ABB9C5E72B6DA94A2192E1B68C5A0E69F5336758441D7E54EF1FAD32E14799EAA55A9E3708411B88D6F09B8E6A7EDAD586F8BD8A98EEB58E4B61080"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(null, null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("D4E78A93FE9861D99DF7DD00C910BDEA7CEDC7F286334EDD6C038C8627D3A06EABE03A2B710CDC80D5D1AE1453C78B746B41D521EDC63CA098AC27FA0CDAB2C9576F55E9D691787011461CB4853A8AA477181BE5FE7E81E5A49992C5E4E273FB1FE4BCE472A29ED9C8650FA6AA881B5E218382F4BFCDCBD8ED6F453CFFB28B2FD32195A07EA631D57A421C996EDB3B1B7E34A3615F7E3B5119E3F6025D61D916D9E0D388F37AE07B51AD7A7CB0326A123429BDCB0F29CF54E543879AAF2E77186806BF8D08B2280354270FB204BFA1E679E4CE34A40E06AF117FA3AFF345CC22356214A973DC098200BA77E71A1C5026A1A020CA60437359000F7C890E780F0E5BEC7DA1ABF8739DDA4C33E9971F95F42F7D0E522401C2F2EEBAA9645F807387AA688C2D9FAE7AA92C83EF140F8AC2E179875A916ABDE9592A8AB2F14F0F3D5AD8AC3F2E92685580026525A111ADE364F7C7EAD826E21DED7F320314FC89591D45953B624B934E1131D98E059747487CA74EF987256533F68F51AF60654184790B085C7C7180186379D9A64D54175637EA866DAD90D2F8DF6245D3F9E4EDABF05515F98EEBCB0A2635950D4BBFE6A277FB7776DED3D6E839107C192CAC1F7F8C2DB5F73E1DC74B1E7B4678FAC4AC1F7CA35CFE6D76CD1C7F2CC07E6FB2DE7A1516811D0AEE6359342B58638D5DEE74635C9D8950201215609878125C49DC6B9CCC62E4428E158D4002CA411E698F213E4C1A12145529F4046CBD717650FCEA4EECC6C1FA0A31D44610494D56D0F7F60956D4102EC95747A26DDFD6D1A416EF06DFF231A3CE26331A45CC040CDC9E3375C6681F7CECBC84957F1F8C2A5B03A2F9594777DDC5533B2FA0F5E8FD533AC5CFAC610A3BF77319226D9C23AF9A09BF42290AB2952655AF722DEFDBF6D387424459BD7DFAF86602E62D557029308C959736A6D4488830A956B1970E0797234FE659DF55DC8BC549C207CC67D1E58642A15944BF2CB2F9BC291327496058FA4F0A0589CF5036B90B715D1141BCC8D8712A556DAE5363223360232CC9647BE86A0D7C5DCB5CB6F26AA0384ECEA1B800603BB51E0BEA4CFCA6E2556F2D09EFA6F575A6333F6A0C157463020D1A155392A822CB8DAE5DDC7FC080B7E80647C460B0925859FA84C1F62FA0A9A8E73FE1918EC2C60A3E62F797982773BEB3230E2C9EA25C88BCFA982361D55E0EB43498819E25594380B9224E9AC1C1D31102420B917D0945E259D5942AA2A96804E9E3638EF11F8DCACBABE47F0EEDFA38C5125E93537C372A529F4450C418DD10BA4AB0DD5D441FE3B441C6D124C5D36CDA5C65904AD5B06CAC393C52DA3D05B2CDD4C5C43D1DDA80F37BDE12B665058CCCD87668B34DC5E59944312408CD08773B130350000E2EBD81E49DCFF69EB54633AF8601C69CFF800EE02BF9EBB678257266D6A362B58BBDAAB9E7CEB0F6EEFA8EEBF0584707B09F14834120E235D1CA6B775C90C812E37E23C8570DB430CF18F1316A00C620DE6AB5368EDF736BDB74E71D6239995E327B0771347FAFA2EAE3B3292EB1CAD0044B98F1DA9183030BB8ECE32697CD957E93FEEFF954EB5FBA124CAB2B4A3C2AA9161917A1A783A0F803667C92CC7C53161EAA9BB2DBD7AF5F3837C745D8D8C17BC2390F7EB6F4D70F5EF6F5EE6FCDEE96F9950D9FA177CD3F69B76870BC5B1F2BA400CF936CD45F29D38B54E4E1C0EF6B48314C43CA21DA4B6BD014FE8C665302D7CA496FFD5A292435E3CB1523E85DA8C7FC278B0FDC60822684279CC0609D6D38510D1777806EEE07270DE656C42782736B368F3EC8F2FA69C64978338D64D4465EDDD1DB4531478E3983E7CE7A2C615DA428542A0D2E6F5E12ADB037F6BA7AC339A0F7E756A72D3AEF0105440B2CEF99E71B8E640FAA38306D72C898800F987E092395DA357906FDFB1A3A0C292A53E6BD164FDA2B15B38E67C779E4DE95B9DF69863BE79615646E149169F56F2C07431EE2DA69C0BD52F46F06726D00221E66605AA2A2EF6C4FA8B2E1C59AACBE1E3BC036C5FBBA4F1FAC3347778FA3F66DF272039414896453B29332B7DEF7B848B1E737ADC95C23AB113BF66EA7F4BF05BB73D327A8290AB25BE21268889740B47BFFC5A86C49A4954F5783D992F5ACBED32DFC6A24909F88DD8452E649BA2379FA42F3A82E179871CF3DD751A5301F653654955B9AD590EA603DCAE7F7BD4B953409DE5002038FBB948A9BE6B94DE2AD64ECB0F525C8DE24B10948F04DB17A12F1421D9C51CF6332C4D9C6103AA931125B1D820A0C56706B2AB99EF8BB9E6EE21088F47630465B95887D304B01CEEA08D83595302F6F059BD2B17197316B76256AE3FFB1B5BAE214C1ED818E26246A5CCF962086093EB3A08359A47ED4C3631D070AE11DCE38521D52806E41ED7674AE4715C89B4BF01C2B305F2E5B16643FFD8964871B424C8D9643023388CF71B20CE4F636BFFBC8D216EDB5ED6F0AF707EE4171B889EF586BFB88315848BC945BBEAC2276202D8002C191702E8D5E1D8C4BFA45475EB9281D8B8A3B37C970895A58592FC2E880E6C1C06EF7F77E2ADD9D6AA50C4D774DFD048305EB95F24838500816D5332E5AA4CB148486F0C1309E61AD9DCD23643BE30B5C2A3EE29551CE7835A6EF6755A96E0F014139F3C70C43526689E8547B15ECCC33CAD7041BC135C2B50DBA1B41360C032FBD212301F0444FE7EB7C0B6879FD20BA23348AD26AF3400B773DB95227112F7DE525BDF3C9CA4285871C3044295A3B0F9A611D018130C4C9E1178AB28AFDBFDA4C8E8354402CF059F2054A4EC3FBE21D02A9B8C6F42DFCB460F312B23F90FB895C56F89BD6E7CF6D70C40F7CE67606E5356DB0E045F9E51E91CFF34C1A3E196109C0F16D0346461BD94A55301D80CEEAED2C7CD1E01A446370D90906F3835232AAAE72258498E2EE269506FAA3B5011101085EEEC14781EE7C8EFD293D3F2756F823856F0DA866554A8EAA4A1449BFBAD8F805EB256370622D83A8F59BB8B1E76DB0027F2F910358A208A4329C36DA33CF4251453A3C3094B32242890070BA91DDC3DD6347408B46F4AC481CBF3DC6F6A9752659676BAC317F6DCC0D4F7958324E1255D85976EB8EB70A518EF314D4755C4BE08A49612A7117ABE4D9737DF77A5316CDADE8ED9D328997A15C1A1E3D6A9857B10398CEE8EDE8E3C1AD92121B3912D63ADD47278C6EADAE00B2BFCE56F96D79401CCCF4E1852EDFC4042CEFA0777C684944E4602370CA4255EACD362E198CC74018D1FE6D1EF5B4AF67628742BE0E8835568412150C56CF528A9EF6B7F8B1AB7864AE87A67EFB2A4F2FB1BB0E420097DC4500D79579B234C101ACE380FEFCB6B6B70FDD0F86E1B65B59C3E55B9AC10F89061A72F7D915FEB17AC6A5D218AA5033FA05563222885563497B3BB6CDD9273F9C5979E389BFDCA2112C94C03CE443AF72B260956BDC677EC40FAD5DDA48E4A19E6535BF306DA93B28B321F6F3952765388C431B7891D3BFF1FA1E7A127FC8C947F221D6AE847E0508256CB7BB718B2FE848254D04C8290D9176CD364E2F28A9EB58AE017450B6B30D3D1253241C037F6FEF92C9597AC47EC9144438B648B6D9FCC3CB63C389BB9791723FC6B6D5E875724DC65F9C5E4F71932F37600B7EFCC58738576150A0BCE533FA37E73AFDC8F32BCEC56881573C9D735ACF93525B8B13757A3F7D7D8725D291E02BA2276836956643343DB893432FFCBAEDF5D02893D86E2471C0BD4B76A4A9F3D7A02B9A760A23392C0E264E5BE2361081C3992F01684D523402F776D71BCD9093C5BBA3036144E2D196FB84B11FC88F89ADC9B20A79F16F53D9667312AC62114096BA9526930C1F7FB65D6A6CC8A0143A68ED29521B851AD34C4F2AFBD0CF2534EA431B643368F6A1AA32531B86038A385139AABF38BF2511A0C1D943E123AEE63835818585AF06FFF688A8E831FF4D48C5F2AA972565FC2B1E0AC4718EB3BF86846CE2518105B2A132AAC9AF173DDFA174917EEF7917C1D33856667BEB957043C82164C41CFBC6BF3F08657A9C4D14E12A22FCFD35D00C6CAB0E182B9E37B69C3A85E6F5D81A8478CABEB071B14637EB418550C898720EF815F7190DE39B210751C84016661F08109E843376209BBB64D72CEA18E6ED04C777EEDFE00176952F9CA81FE9062185227C402A143F97D77A3CB436B7B0082064C3789E773F270F27EF04DA777BD15EB6A7C89BA6C3F088DF1665A44C40DD7E9487BB258E820DE2553102253ABD60FE6B568010E86C7AA1D3F0BE60D983C2E7118645F0C0ABCF3399E54674C0CA4D22B66638F5C0CD95270C19529EC3AD08D16E1B14B4776F3EBF864C77032505E108A29CA9734BCD2643F01DE3A6593170EBDC7505A4B699AA4628796E74569202073849FB27039585A1F5D0C4598C976655DB8B27AF003C5208F1624BD25EB2ED5E1BDC6D103549E746EF64AEE6D550BA90121998CA6A5206FBF7F235A04AB34343E2653EB029551F92A3E0BF9B8E6B33A1A8BABF441922F3C9D57219421447FC3C206B036F120966BCFA06268A926B6D8BBF94F88FF4FE29BF7275DBB73CDC7E5CE3837B2267C38E9E54024A13FC68366A048D837F4F6C47224244FD9D2EAE801E87CAA60EEF4A23FB05593BF2B416C3093E70B2AE58FE0D773B58E023C6F170A5812B8B31C2277AB969887AF530556E2B80F45EBDFBD1C689BD2FD3976A003513E99670888A914716F6F945D0831746E6EAB4A2D192B2A9B06502749254E24F6952660C9AE4E5A1EEAD48C7028CF505A6E8ECADB65D638137EDBF23881DB4C213A9DB7D1C0AAC010413B3244AEAA6246FFCCEAA6835990BEFE5D5AD24C219D1F886B358B078E89B3BEAA8A2AA3E364C469BB38B4F0959DD10D08E3634FFDD42F9E22CC4374D73F714326E7D8DF661B0AF8966890F8E81EE2CD548E0E9B1663C0D66A9C7127215F828BB771C9BDB501A6EDD87AB9853326B613D976105920D62CB3270264C9A9E3D746E615317645350852D720C2ABAA3B9333D66D4DAB3F295AD1451686B29CDD43E4A372AF2DF54FCEA47DCD7200138118FB6F30F34C962C07BCBA50213EBE45B609DCDDF8378C753E969EA93D71B74ACDC3FD9B536BEA075E877AF263A51E9FF6281778334588D16EF9F25CADFCFE96057F926D3122F24A1FD58F70CC9A019977FEB3056A3F17783B2C2C59799D595188D4ECF412B15EC325CD686DC61814C4F858D4AF39E92BD6681A2214F6A8D0827C2DE7E7BD0D86C6AFEFEF9CD6B9B22307676110B8AA27AFB9169BF88F49DC8CFCD5E1A8203D17EAD21E0F5A098EFFAE580F0D9F47CFD9C1F6383E96718E37660352AFF0C819F2DA4F763E9AAADDA62A16ED90703C4D452D0C1FA1E80E716943D6A946A6EF0ED64E1C334FC1813F272892047F93EDC729C61D72F4B8780ADBCE1CEAC3F1870F3B1807526408FD73ABCCE87955FA01D5661F8F70896C0A2DD6015781B4A67B35A192C9578C9B5310D899D737CFFEB4E1CC3784B4721675DF1DE98C62B17BCF0551B2D7C320AA58CABC6E97EF84D25DD75C65B57D11C6B38B1CFA49931EF06F6CDAD1D61D26E1C121DEB9F8137E865A4B9BA5064BB641E39AFF4CA62650B5BBCBFF3B14B2FC30A06AA4939E67CEA00C96A00E0769EA23B688EE4B1DDA3B9AC1290E2C144361975F5AF4AB9712B38BCC4F401F8652BE41E1CB749BC4A112D62F09D27559929BB92508AEBC753E9C93D5628EDE9A2C146E7ABC4A48CED5D2AE382E7C8D403E2229B386FA8DB3F1DCA33EE0C66212444C02F5740AAD5842EA4F25F08456C20F539730651B032196BA78EFA709EE0F709BE3E26987CC3384C160ACAB57B37D75D13F9D3F21BD3938E16EC991CDB1490C4FB564907458778763BB93AA66863EF6FB69C5CA50D04EDEAAA988290291B71027A06B924EDD0188C815CB66299ED26B2FC29653FB7EBB6F6695F5CAC05E74C1F47372B61AACB82E584D023B04E8A51BC72CF9300B5324B8F50C2BA8E746B869910961A61C1C40830A780BBE6C705A8DE0D9F3A35534C6A15EC9C11E711F37118498AA0B770F0FBAEC66358357764C74B1ADB80D4315145C42C057424B3211FB30FA29C3C64F16E1DA48C6EF0F3349E87D5C6CD35739CE7F845DC8A6A7BD6FAA543A525D4C88BE6286C632FA5E254D0F867EC4FED54EA3F72BAA4F7886704A1CE91C9AD4D0EC9BF49DFA4C8106D4216F00B92038F860D515865B31A24945EC168C2601318A682E99D2D060DB0212F04778D067CD5001148837754E85BAF9BB581E2604E0C2EFD4988A8656280FFA13735AEA2968DE1EC67B0188FA0529A8DEE91508BCDC1E1F84890BCF32C9AAFC783D61DB2AC0C05289FF6F89DF49E5CF7EE1A77D02F73D37553B65905D0EEBFCD00500BCA34E8E1E18C28EBD7C94303A933C5051123DE980F02A85EF5B3FF57B1362B9DA39391C3063995440788FCAC10B2AFD20BE8ADA921B8FCC78C89EB2E8CD03AA09EA66016A101FFD88E9A908705FC4E43032D4CC888817CEA96CA377C4D66B8374D5B0D6952983009E9D51B872874C1903AA82A5F8371181C2A767FD7CA828A27E2593AAAEEE4F16BFC43DEB366F4C22208A641A53B58B3DC43F03FDB18583E674AF3A5CE3917A156B7BF62409265B635B35DCF2340E5EC89A65E66FDA51552A4047768734FF3E9F004EBED7BF67249DE3A0A27BB891A2764061B28DBB1A4B0A26CBE3C1BFC43E5C073DF1E54F77092341488C29AED971BE94F036C33CB0D3E603B4B0E38352EE1F7256747A9FCD26C9689F3C729E116B5D5A253CBF9D6B1F466A0BE19AC615AFEE6A6CB75854F71ED7D96D6AAD746157E24963995E262E8207373E1093DB9C0342EF5E08316A02F8531654E6200061C208B40F41628C41C83BDAA02126E67AFC3D6A17CE26592678466B3647C1486206D56C07EFDC4AA0C69D95CC56B9E1D17C9B68A8B5E8A3ABAF9D86FAF74609F6F368A94BBADE9B2FE2C95120909CCFCE6499B9AC689FAC2E31C06852554FA970939EC8EE1096169DF048B5FC87730B12CDB8966D9C5D8E63CC8B19FA7648DA931FE09CF1C3D1BDCE732BBC2FD9BF96BEB555849CB94BE436106521857C2C27B77E12DCA6E6195EFC0AC7EA60A16E8A0CE46B4510B43DE9D40C56E15EB0285256F3F4B1B6E2FFD89C069881E1517394A1DB149110D707033038F8AA8F37F03BE275937CACBCD329F482A91A1BBBF88288C23F739B87418FECF1718DEA454964065C9F7BBE06E7E507FA9C8E2DECE774306611CCF87CA06B34138FBB3B453FBF394503CF1C8CD481DA4A6BA0EAB352001A0D4D74338C7B99BC3447AA773CB9C2BAED44D8D92867B3918FF2AE010F7070B18FA13E07CB7BBCD97A02D8353A0AAD90A8E5FD7E7926E19599E2A399CD88ACC3D6E8F78B7BBAFF61880F6F158F7E6775450D581973024868BF987DD8D8230BE166146E51131214EDF68BE679B7DC161B7B2E44B0209340C4908EDBE34D169964F37DBF6B00166222E87A12F71BCEA0D372525DB9DE4BC0C2D399B084463190EFF271864A2CBB0B64CC11D7B67059A8D1EDF860FAB8EF86A7BCEC876665DDECA6D922F4AA4866B6C746F8EDBC740A5289F0EEA65DD0A63712EADE9D4033A50EEE3520ED4D550E89773199B3CFF1CA23A299FD7577378ED622FBD1BF69D96693A1EA0F87532D0534E2552F1C46F2672110BAEAB6362F87B6B64ABA41835F1FADDB0611D5F242D08584C8932208629F3084B09EBEEEB05D725248D10862B3B19335714140E0912EDDEA65217D9C65AE43BB89BCC2B060CF1AA89F73A971C822D1170FEC97114E5FF381C0D4898EEFE5FC9A8D4CD8F6EED2D4B23698511D755C124250668C8917226D39C930EF8C359BB8A9871EF47B49DA282D07D4EAB60EE60C74B2472E895963735ADFAF3DADF3488C9ADFA786666796F8863E6F714247B72AD3347AC9FE6D3F40139BA9AB9A9C7A39BF585FD6575FA61D2B4EA84AF9F2311328745A79788E8E2D906762CACDFB6DE9C435C1E9F7D0D3838E6DAEAFC4F456215A86831641F0B130CA2235FFECDB4F2FE0BE86B420472B47A0D6EB0ADFEE68AC014170377847A7C374812FC5AAD396B31E8ECF166DA1F1136B4B656B05B660EBC999EEAB6B17A79916C33FAF27559CCD4037EC412F423E6F405CCC3CD23CBDCE20440C10F4EB57F6D735FFAA6251EC15AEA82ADDCA1FBC775B8F6B0E5B7AB6DA15617EE3498BD854EFF579F95293FCEE70A69E3A11D004108794B5658049AB6C7F259500E308CC12CFC5FDD4A5C324B7827C9DA4EC7CC8355D6E6BB4C662D4964A0D7A2D0EFC0B77EA7B6A4BD23F6AB4700DD303BBF680025D5FA3749D174C5CF1678A9858F7B8967115CA61028C532DF3B744C89FD0DA538D77DFBF4886DAC33F1D4A7AA4D25B78454C92A62C1EFFC0FC7580E5D273CD209D370AE118095DEA4B0731F531E879115BCC83434E24277E780EB2C305E4F0BB4029ED7DD76A604BFEA85B24ED0A9F140C6F9A05F079E2691F9C7919468AD4660676B5080B0B49E16C475495D8F2817D7FAEC6A65AFABAC647746E3AF146F563D3F52E66458708F115A8A9B9254C901D0AFBA9A7C42E0B1BBBEEB086AD0EE01541E0B1D3C41B27E895C580A25101A0FAE4413FF5AD5DAFA5EA12BA56C6825406C88467349ABC48EFD3B101FEAD68CABA4DC5925BDEEA77F4411BF9B160C112137179E9CC9A6C6037F84021EC5BB1088157F01900EFF837DE1C72E37EC2978F02AEEE8D238EFE1FE1556A639EC88BA88003686960B8013379420BB02778E17560C12F3D6D23E4F259E027BB7594A91580CA637B5A2E1E4AADAB03D1734DE86863F4F2DDA94E3B5C2E761F808045BDE4A8F62F5D15761B42A2D49FC220038F1C07CF478091DB7C329F074CE41E9DCA57ED2F387D1E8B42FDD9B02AC4BE793C1AE6F388B0FE3D42CD89ECCE1E293724150A37D4F0DB020D7C8C59A6D1A0A78425CB31FD1B050959AFDC6AE321CD24C4928C05BA76A44B4FE4F60C349496611CE268A852F74A190000DE2514601EDBAF57950455AAED0D717FE2081508C4B5197B4E00B924D1CD5872F5B9987F26EFC61A6CED0F821B72055B26D31531F4074C190BA9121E128A85D7BBD2A533750258F2420A9BE7B5875FDBFB6E0DFEA8167EFE6D7F738DDDA807D25737D638DCD1E6248A12FFF96B546374494622B48C972F9553B6C5F59ECB249A02C9808DB126FF2977D96495168583191BFF3487854355C00830D5260BF095CEEE55F6A1C3D5C32246F93CA3228A7EDE23BCD99F86AD14BF63C0757041D91D8743A5A6C09ED145462C12408C16B29D7DB206F25F26F6384131373E035042E07DDBA55F8CE3BE3F52779373646C88A8B13307CE3AE6CE605D6D658F57A341E5E931D2B06163109A03C7AFE78372C3E4B89E8CC05884A5870D44F21F7E2ED7BF25AD8A3289D8375E226DCA6346E6DCDA9970D6C916718D649C4BA31C60F11BD4FFF0B1E5F8915AE0573EADFACA324026781775FD580E98B42025A64E252AED2141E5734624F6E20825F671E7C392AA69FB7CA4497B1E8CE157FDC5C2FB1CBB3C417CA8D4BEF9F171357DD7519CB0F08D8B3C42A87693B6336891FE37312F1D193B459646F9C5AB018B23140D472B44A70D3837BECA12B5ADC6757C2D0BCA400650CE26571AA983C581412D538FB8EC3D4D4729F8E75E4F288FF87084498DF5B0D30DAC8904ED9C63A4722D78F5522E1D894EDFC4A0572EF27F0DC722290F7C035B6C70720C63385FAAC6446530C6333DCA630B0AAE31108BEEAA09F7167A3AA5061A2D88E1BC1116068019DA02B9C1A8DA9957C60461652A5BF48362693139308BAF6F16A8BA4C1E51E5042E17367934D8512C7D12749094B5032CED28AEBCC546D94E634ABB00204D49642F6D3703257B5E8A0814BC2E170EDA51958D6EE5A3283EE74DE805EA2D510C2FD90071377DFFF761CA24B4B08ABB376470596A92EE53C0F72E5FC54D75646CB7089C45168D92D39595FD1B7E738E5C82A0A179C3E67941753F8A191E3AB9FC65C7FDA7FE6620E6ED85C4FCF1384273A003329CC3FA98C09F4ADFD6F389B56ADBF0B5B675D66E2C8FCA9C4E117C404CFD16691B082D286B2F3D18A39659E353C06A2100829BFDD995060D096E5AAF230A92D9ED10AA52BEC85A9748BDCA8635D7387DB75B4FCA61189F85F2101EFCC48DF599037FF6432607623827F4A51BDA7FD73235ECF7C7CAC878C47124609EA95DC2CAC82182B5D0E557AFE907926C7383BB7238ECC6E7962DDE3B0731D201A6C38EDA31EA43A8FD2151508557EEB3360C50ED68EE65CC86DB24AA5357AEAF1355F91588B83A89400285179B76623E4F1AF4FAD0E498C86DC24356B5A945A572B23D2F5EC93A8DD1D7AD56694E311078F66B4852B3CE2ECD30B7B674D3C3A6CCDFA4E93932EF75D077190947326F2F8122DDCF4E0D42BEE40E522E1174190E04069989A957E73606E567DBAAC4E79AD1883541F5F468CF9CE5F52AD0B1545C6E1AF53098D576394B97B8589567142F26C75C894EC7081F9DD9FCC352BC5545810B7D9FCFA6F1FC11FBF66E3C002A25C1D6041964DB1D9702E5A2BEE8B2E68F4262C789F9845F7FC5507752D0A9D39DAFD6F18CB84BE0B5FDD68CEA0443136F15671BC948A02C4D7684541463BAC66902050F11B159D6F682018C1307756B824D084D698E0A450FAAB40623F7CC52331013D7FF3401BDBB802537371409360128C1D75B7D5CEAF7C00F66FD53B30EB5BF4902E39D6C5E4B5A81D968C31DFBA18DF50B25F506C5B338D03F24AAB057DD167187CB065263BC739BF0FB737224A203B57F5EB7E8C0B8834FAABD2D2990AA2A0C52419630AB2B914EA26905CA4FB6AB9D20E53109252DB0DF37B6A7A68F49FB56C1772FD31BD31A43CD693657A2B90FE4119008BB6920D72490C6F8E483EABF687DBF08C896D8814DCAFD3BCBF7692C7EA6B394FF413FED90900B4244EA25CFD2A9DDE9E3B0375D391EBAE07BE51B2D379A7A3DC045C0173DC168288B863C63A52D5912298F38CE6F565BD0965F71BB84DE1ABA6FE7FF9CD9C6AC22481ED1B2D2574B4E8C0903B851BBDE79F9EF80EE5FBCA3AD6B81F3BD3DCC3862D316765084A90E3F04332F7BDA6258B41985A2EA35CDC29B6B244750538E00CFC0A0D54B357DE540E141DA2C9D27D309394F93B8294E2D1543EAF56051ADEB86226FBB399D1D7EAB905208F3E3A5FE33514E2703EA58967283CFF74A7884FF435653FA3D7CA97C2BC0F817D74529219DECC20F0386D79BC9C938A5CD1634231B52B5AD9FBBC0B6B"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - null, - "oªU_äa@uªÄÃ|>Aªu¢z<Ã¥vA©ãßbaUߪ:ªððzOZ*aßöuvCBÃ¥@ýoßߪ£ÜbCö.Äßav.ýUb¢|Ã/ðvZÃöbÖ_Ãð_aBýßäöoÃ¥OUä£rÜÃßZäavövÃî_zöãa*.©Üvýo.o,h<Ã_ObªbðUU<©Ä©~Ö+BZ_ýzäããOåöß@,C<åÜUÄ_ßðUA~ÜäÃüB", - "ah£b©@O:vz©/~havÃðZhÃ,Ã¥+>ßr.ªÄ>,/¢|CA<Ã/aÃ>hÃ¥B@BÖ/zöÃ|UªvZ~äzÜä|ßZýö~Ã¥UvUäCüîÃ>.ZC©O,|Äå+öãÃåßCUåð ~B~@ärAÄß:ZÃ_Ãœo~rC©ov~C|¢A>+.ÖäBý.îzÃ~ßü+ãå.BUzöÄUîhrÖbaüaåßÄ©Zb~OÄhåäåªzÃß.ÄÃ:+Ã_£OãÃîu î ÃÄývÄ|ßãð_Obä+Zã>ü©ZZ_>BoaCUäZ<ßü@rîz/Bãîä_ã_:Üß_Ã¥,v©ªbhrîBÃ/|ý¢b+C*O,@bäß,.a£_ÄuzÃüåuh*:bÃ. ,. äîßO*/¢h+ ,äu¢h|*,ÖÖßUbr_>.,ܪ@~ß|ðzÃåÜ*obÄvãbbCbhîb@©:üAA_£uö@_: >Ã@aUaÄ.ªÜ@ß*<:Z ¢ÃbaZðOª£*ßUoÃA +uÃã©/CräCbv oAã rb£@+©BÃœ~ýO~Ãœ/£ö*|.B,ý,äoOãîv> ýðã>ü©ãäbÖåäCAv*¢¢+ß:.Uöu©ß+Oßr@B©r©ðð©Z@Ä+*>hZãåãrvåÄä|©îZZä:©u_+Ö/ߪ.ÃöBvÖbî/b,ü<*ªð¢,ߢýa£,.bAhßC+ãaZ.> ß/C@Ãœ:råîüzª¢Ö+¢rª,b|©rÄîC Ä|öröBªßöÃCr|:>>BÄ_o+ýrß©a:ÖCu,ª©h> öÜ+>Að.öb.ãªbOCý*ööÄ+h@ö£ãÄÄz@_.üO_z*Ah/|vÃvoäÄöC,UÄOÜößU.äOuâ|ä.£å¢ß£ ß@hC,vÜÜhÖªîvßUOuÃbðOöuå¢O,Ãuß zÖðð©¢ã.ý£bb:BÃZbO:©åÃÄhãZv>uäA|z/Zh î|_+ bZCîÖÜåå*|Öo/|bßb,+*Ohª>ÃAß~öh¢îZ|©.aö_<ª ><ßoýÄB_ãOOAhðêOÄüZðrðãååî¢ÜhãåÃä¢CÃ~Ab<.z/ÃU/ÖÃ,Öß>z.Z_üvCß<ÜÄ ZzZZ~ð~©îß@î>B£/hî++A+./*|Äzðva£ªC/ðh:oÃ¥,b䢩©|, ZðåãöarZÖ£ßåZ+©ÄzÃÖîrîÖð|ö>äÃU~/Uvß@bz@hvÃ¥+üÃaB©rî+ýß*~>b*¢A|h©ýOª¢z@ßu|äã£Üå~¢Öh/+BA@¢ðzäî*,bCbU+ßräßä*ÃÄh©Ãߢ:ÃäåCÖ|ü£ýÄa©ååoÃœÃ~_>ß.îz~a*z,üU>u/ª@Oýz©,ouhßO:ä|+öBvh©zö~_Öu©va<>/:v£~ÃväB¢rÖ<,_<ÖaAhUBÖZ~ª/|/ÃÃ¥r©zÖa~îÜrß,AACOBorÖîUý*v ÃübýzÄ.*ÖýBO¢@åãUßzãbÃO,>Z+Ãߢu A/ ªãßîCü©ZCBð@©äbaoA/vüß©>A*Ä_hÜÖorrvý/ðOä,/bz~ÖÃãÖªbZªo+ßOZäÜvho* Ãœz<~b|uä|ä@Ã*ðv£åÜ@Ã:_Aß:ßob.ªvZÃüß/uU©Aã/Azãbß+ªh+*¢£äuZ£hv.o/ü~uA,Cß.¢C<äA>Ãý+ýÜ:Zuî©bC~o~C,ª©ýbÄ¢b/vÜÄÖUhßãÄ_©ÜZ£ÄoÃÃ|ýZ@£ß@ÄärA.a*z|>©|<©@vvä¢ð*Cüöã*AbboÃhîu@v~ßZ@z:ÄhbªUabÃa©AîO~îUrbýzv.Ö_©UhU©<|B¢å,ãåUBaa hbBýbö+ ªu_ýrz+@ðýäßb_BöZjkl*üýüZðvÃ/©rrÃ¥ ä*_¢ÃB:+zBhuvªýoÄÖÖzý£U|ÄvAÃBUß©îéÖ,£ªBªý£åßb£©ÄZü/:|*uBýð.bÃœZbzv,B,:ArîO/+*buã£bßüvîO|ZOabãüã/ bZu+ãå>Cv©oå¢vßb_C*A>*ªbãßu/ü+ð ýbZÖý<ßozäaua©©îuýzä<.ßÄÃh_A©boîÖãî|ýAbb Oã*+bohzuübbä~U|bäöÖ>vÃBvßr<Ö>uÃbü¢rrC.uaäüÜhýh/,vrr:~Ä_A/ýC£åZîã*Ö~ðÄäB __ao£+v£ÜB>Zau>UboÃœhz|h|bößu_>Ã,CåðAÃ+,bÄ:åßߪãCv ã_Büaü./BÄ~bBv,BoCz<_ÄÜÃ/äÃ*Ãœ:C©üu©hzhäö¢vZÃ>©*ª,Ã/ÄßÜÜhÃœ,_oZb©¢ªîbåªUÃœAU*ãZ.BrÖ>böba,äO/_+îªÄ©|~©Bª~ÄÃrÜ£uCªªrbÃ¥:bäå._ÃvÄÖýªOabßoªö,UößÃÄßb/:ýbÃ¥_ÄAövAýðÜ©Üüꪣoz/aU©£@UOoª:OO<|Uözozª|bo/Öß©ð/uZÃBßäîªbA+ã@ߪA<@Üýå.*ý©ruß~z@Ä,ÃZO:b£Ã* UhöÖbv_,vzîö|ßðÃ/¢£bAÖ>©:ã@/~,äb¢_oubðoüvrAuß/h¢vzrÃBßå:ß a£ÄÃ@<©ßßÄZãaÄAräÃãa*u£~ܪöuo,oÖýCCã@ah_+:<ß /Z>Ãîöo¢ßýÜaCðî_:£/|_ãî~r©ä~z¢åÃãU /@v/ýhrC*ß.BUýðßbUß~+ð>UB| OCAoä~|Cð,ý|,£ÜU+vÖ*zÄbê:Öü>Aa*/ßAaz©_>©åa.b©.ö~ÃCý ra:A _.㪪î,£h|¢ÜðÄÄU©/Ä©£ðAß+ßðAAuß|ýüCC©bvBA._ã ßÖ,vb_~Ö*ê+AuÜý*ªðÖbÜü~ª~ªOª£U,r<_b@ÖüÃUߢZ>âãð.oÄ:uO_ÃÄ:~£u@voÃbäöÜÃ<ý/ÃœBu@zÄB:ÖߣðãZ|BoÜ£bh,~ýåBÃß_oÄ©Ãa:BzO.Ö>O<âUäßZäªðãîZ+@U¢uüZîªCüOßã<Üý+î~üðå+£uö@Ã_ßãßÄb|b_OÃœuvÃ¥|£bAüßb¢|ðz :¢äüýAÖub h+CÖîÃ/OC+Ãbzêrýâîuî>+¢ðu|rÃ:bßU>©îrð,r,ßö£, h+ßbbªhBuäÖCü<äü,ð¢ubuðZÜÜuÄãB|Ãuªbr,©,:©hZßoBöð ..Aaovð/@ZZ+/vCzAÃ¥+,£~Ä@o|ððBCÃßz+/Ã@Äb*oÜå,BBuÃ¥o@hr~@©ßÄ>äª ý ,ªî<~übrÄüÃbÃ,ÜßãUa*A_U*ðäUÖB hîva@Ãœ:ÃåÜ,ozbÖoã.v*CßãßboU_|ÄÄ B+ãÜßÃåa+C+uüäuoªÖAã>Örzb<ð+@îOobbbðUü+h_výa:ªßrðBöß©åã O@ÄzÖ><:öroîßZð£,Ã¥ZUÃœOä:£Ä/.ü+>OUÃÖÄUb,AÃœ@bv:ð,£oZ,ܪ~|ý©U£bbb<|Ãý£B_r,*>v:UC~aÃzßvÃýÄ|£oÃœba, üÄb@|:vzBüîbv*CzäbuðÃvßß~ý.aðÜo,öOZÖrªA_:aÃZÜð/u@äîüÜ<~ÄîUð~AU, ß>CBb+BvbÃ:Ã|@a£_O>bäOª:~~äü©/>rª*>Zß*OoÖ¢ðBaäh,uvAäÖ :¢å£><>hªªOUCuaÃäOa*zUU@/Ä:<Ã,.£:Zîä,hãã¢a ðÖÃð+>.ãã.Au++ußAA.ur@ßã o*Ä*zéuðª©ü_CUü|ßAU©>aðb©¢rßh,üîªuoÃ>_Ã*ý:Ãœ_ß>ðOU£ÄÜ~££ ßZ¢bÃO ö|ßîrî£å_£|ßÃ|ABü~:<Ãöªb.b:+/Ã¥Z@@Ãý v+ä*£ao,©|>¢©ö|îuÄ£aüÖ>h:Z:£+bZa|ÃãªÃCß©ßr,ã_ßÃ< ,ZßBü+@äÄå+|>A¢v>býCöOCîürÖ*£~£~ü üÜ<îªzzÃAU/CzOÃœ|bÜÄ++uOîCܪOrCUߪobäÜ~:©ßroä:äî~|b_äå~öÖ~|ÜßÃh@îa£>hCðß: A~obz:väAªh|+ðarZvrߢü£ÃUäß*Ãœ:¢Zb_ãaßÃvC@ü©_,*¢Ã>ðß©~£Ãbö<ß/£îB~ýã<ßba.ªýBZ£zCZÃ¥ubää¢üÄ/ãÜb åß:¢åb_|äߣ<>u©<+AÖßrðr~buüACbüZöBÃo¢£ÜU|ÄA..+*,ZubßAUCö¢å~£C@:ßã*_.>>äÄv>v>îßo©å|ÃåýovÃüÜ_ÖbCCbvu~bãðåÜ+ _@ý¢bßÄîÖ_ß|ß©Bã:ÄüC£h,uU©uBãü©ö@©¢ÃÄ ÃÄAO+~UÃ.îüý:OßUÃ,hÃœ,+ª¢bßåãAhb*CCýUB<Üßü_Uzhä>>CZüuªöð+C.C|Ä,©Zä¢ä O~ou¢ ßäCZýoz@CÃ~ZhbUßðîv ©ðCaå©h|:<ä.uUýoãýuhbr¢/U:bh+zÖ:öÖåöä/ªzaÃb£åobÃ¥bbýöÃCZö,ýßrß|ÃÜäv¢u/+v~äAbä.üãZC/bAÃœBÖuÜãbð.bhz<öO:A>zrÖßh£|¢_/h@b+>ÃýÜ@.:ªz OÜîÖ,:Ãœ,vªhävýöC£oZªßåaÖßßü.öªbÃî/ª+ÃßUB£ÖU:u£.ýã.ÖZzÃ_ZÃœhö~>*:£_/üã+|+ßîrüA£_@O+ü~@äÜ,rî ~~å£~Zzªîß©~¢vAð+CÃ>£u¢Ür_ÜðßÜbÖðbýßöv¢©ÃA¢Ã>ß z>ð,v:.Ã*üC/A@+ßhÃð£~ZUýC>bÖªªåv,r:/ä+ÃœZ@*¢ßãß~h*U>a~oÖAUöZÃœ,£bÖBãAßböðÖü¢Cu£/ozîð.+BäÃU:.OðBýîåväÃýÜÖB,zÖböA@,ßßüå:UÄ©uoß+¢Öa£üÄUöÜÄo@hãab*ßÃrðA+CCC:£|r>ãý,:£ZBbUß~ªC¢:êu¢z..~/ª,Ã¥@äv~+|r _Ö,+ßUÃ>", - "|ãå_ߣ,Z*ÖüuÃ,zÃœ./Ä/A¢h@oÜÜ .ªUü <*ªä©Ö>öÄßå|oã rU|,ý| ä|Ä¢+ܪ>Ã¥@aîrbÄîér> ßUöýö:CObãýÖo>AoA,à rð+|v£Z> ßÃÃœbab Ö£/ > ã©ahb_©ÃÄZÃœr©C|:ßOUu£Ã@ßUª.¢A@ß *büb@~OvbÃœ~:.+:rvöî|ýã¢ÄÜ¢ÜåðÖÄÃÃ/ä~o>öUýýoäCb,Ã¥hr*bªv*@:UZåßz bãÜý@äbÃœa©|a©ÖªÄuîÃÃ,£h/b hîrb/ããßvZ.+Zh|", - "Ã*ãâ|£î~~ð.Ä*îOÄh|öuÃ¥*ãö*/ß_/~ß|rÃÃZ*äªß/<ðÖ<Ãý@|Oübß|A +>zCb|AbÃbu>¢_av,Z.AÃ,u~Ã¥.a>ÃœuhüÖOðã|<ÃÃ¥:äb,:<Ã¥aoÄ£>ha¢üZÄu©|_Z:övÖ:¢oÄð__|ã*ßC,@ý b~£ßÃ_ü|ãUbzð~~:rÃ/,ýýÃöª/ߢ:ªßo|urÃ¥z/A婢U_:öåbroü_åðýÜuß>r:a|väßßv£__ü+,î<ßððã©ßî@O_böhv,,UüåA>üZî£_¢CbB ÖäC,ohÄ.@/,ãUvÄA/UÃ¥O+|ð¢zB>ª.U+AÃ¥+Ã¥U>a<>ZÖüªBhb¢ð>räoðörö>Bb¢CýZb+öä+:_/Ö,å¢~z@obä©ðb>äü.Ãzaî.äoÖÃ_ÃrOU:üÖBÖA ª¢üðAÃœ/bu|>ü:Ão@@*_ÄäåÜßüäªuüZ,CðBb©bý,BÄÃ:üuuÃ|z/UÖu :ÄäoöoouBöO,b*ª<<_ö/ýO.ß~ÖCbð_b¢åª¢*ßåAUö>~@ßßO+üßüÃßß Zzb~rABz|î_@/ÃU©ª>Ä>>~îaÖ~ÖBC|*ßBZ*bÃœZãrãOA¢vzãÄh+OBzßÃ,bã¢Zarý_Ã.o+Oîz©@£ _vÖ/ãB+üroÄß:££Ä:uîãüAB_Ã¥~v@Ub@~£>ohz,Ãœr¢UÃœ+a¢ÄÃ@Oã:ªß*@Ou_vßãÃo£hhö>@.b/ÄrÃvÄ@vÃ¥_abrbýão¢öBã@+ß>¢Ãä<ÖUýÖßAU<ÃZ+ZãÃöªozßä~ rî ,U¢ý©ÜCZaZ.ABr_,<,|hBößÃhÄOö+./~C|u+¢Uýã,@~bBvö+ ÜüâbÃ¥bbU_©©_CCÄB_|äC ýÖz _Ã¥bö~ruö+>r/üð Ä*ªbýOÖh*ðð¢Ãr+bB:CU/v~åßA._AýÖuob¢ãrüåÃÖÃÄA£|>Ãa:Cýã©î:ÖÄUüÜuC+oÃ<©~@äC~.>Ã_|UU@©@~äÜ+>hOB<Äðð_¢*åöBßo<ªý./orîO, ü_..+.©<übrÃZoÃ>býä+ö*övîaÄ<ããBã<¢CZÖbu~ÖörhÄ>rãÃ~£_¢a+¢~h>+ÃO£Cü*r£b.ÖýÖZ¢ahß_.ßåÖUr|u:v>ê|äî|ZbO+z_ß ã£Ü*,+<ߪB|ß/.î.hÃ,_Äo/î+bÄb¢OCUB.,_ÜäbÃ¥aîa:zBhäoÖU|oUªÃuåü*£.o.ýzÃaa*¢v©>@ßoü~Zîäîäö_BãßÄ îåÄUªÃ~<ý*hãßCüå©ÄÜ@vZÃ/ß>zªðÜaBÃü£/CAObªrv:B/BAÃœ<ö_Ãœ,~ð.ßÃöÖÃÖ©Ü.|@ÜßÖoCühOÃßߣ<:ã:ã: *+Äa Uzð/ª©Ãå|vüÄß/ßU.U¢üA:zãzß brhbÃœ/ᄜrÖbCCÜðräÖba@Cü<,*ZÃœabCÃœ*ªÃ.rrZü|hA£O@ãv_Cü|Ãœz|BO~,b@B£h¢ZÃœ@B>ý©îAîà obhzBÄäOAÃ¥z@|>äÖAbZÃ<>U*äv¢üåov,hOäOÜß.ãÖ+bßoOîýCãa*ý|öýäÖ*_î_Ão¢zab@ß|ß_v|åü¢bz@ä¢_bhzBzäÖA:äü~@ OãOã< vußÄýbAU©AabîoB>aîÖ£A@ ©zý+¢:UhÃ+zvß ~Cuã@+.ãî©+u©ãvbßZÃîh,aÄ,Ãœ/î ©ªåZO.ß_ß>oðß.za,Ãœ,äª|a_ðOrB@:Ãð@@ub<ö>.:@aß|üzüÃÄýaAAÃC~ *ýuÄA+ÖU,zrU@:vü,b|uüöåu,ß:ÃÃ¥>ßC£b ~aÃœ:O_Ãb¢£|+*,CýzuîA*î@bÖ+äÖÜ£~îo£+ä/ö¢ðýßäh|Cßr.u:ZÃAý©<ÃÃßÃüZýb£rÄAU_*ªýA@b+@¢|£*|OzOh©Ã+ÃvßUa_Ã¥BððBOo©B©Öý:¢CObUA¢ÃßÖ:bA äßBßÖaýoZªÃ<.aO_Ãb£o£ÃBöZ:¢Z£äÄ.£ªßýrrhýîö@oAÃöýð.h ~+ö_+Öo£B_o/+|äßuO*<ö£UÄuöî|>£*uÃ+ßv£U¢:b<î+ÃœbÃœ<Ã¥hÖÃb£,öhU /ã~h:OöÄaÃ~oý|Ã¥.:Öåðho.Ã*î¢hbüåh.îrª<*:rã+ÜÖZýÖOOuörUzrv/îÜ_|ߢAã/ÖB+vöBz©BÖ,£,+h/öãAÃåBÄðO|~î£AÖÖîãU©ßðZãå|u/åÜ@..CãÜýo|£+/B+@,Aba¢Ö_C<¢A|rßzÃ¥Zb.bO<@OüÖÃZªZ,ßîz>_o.UaüZö>ßuß+UÃœB,© *ð@Ã¥bäaU@Our£åzÜÄäß,bî_A+Ãü+bãOao~ü|aZ*+aÃÖÖvª|îb:bu~ZU:+<>zî¢OÃýubbîÃÖ.@/,*hßÖÖObzªÜü.Ã¥*@r@Ãœb<+_bÖA|>ý âÄoååz|ÃÃhßz~ðAßOÄ,@u¢:öaßbAÄr©býîoUvý:r£ßbî£bühÃu+ܪß:ýUrÃ¥Bu@a_<Ö ©ÜÜ_rvrÃ¥/~|Züãöz<ãå/Ã¥ÃZ+öÃO@,ðA,ÃððUZu@Ä~ZouªÜßO||ÖåCA:Ä¢>:ýA@Ö£,ÃUîÄîOã£výazÃAOåÃoB@BðÃð*ßîA/_>Z,*bß,>:~+h:åýÃ~.+hBäOÃüýĪª.Ã¥>©ä£OzüaB~ÃA@hü:ãð£r>©z/¢CÃ,bBÜåbÃå:ªUß+¢,Z_+ã©_ý_ä©ür,ã*.Ab¢|üaßãüßv>äªBv~B+ÖbÃœ/o,@uCðãZbBOuÖÖb/ZÃ@äãZ<©v|âZ:ª*U¢ãühZåý>b¢ÃzbbýåuoB¢*~ß aªUªßh£ð@¢îã.uU£©ªüðOZßããb bC+uðA£C©¢,©îüßß/oh:ýð/üÖªähÃß,ÖÃß|ßo.ýßÜC|AZöbü+>*Ãâ o©.,äU¢ðb,a¢AO*Ãå+.©ß@îÜu:ÄbC<.hÃö©¢vÄÄߣaý|+ä @*ð/ßUãÜvÄo,@~ß~üäîB>Ä.o©¢Aß_ ,*hrÃOA¢åaZî+||zvbr,ª>üua£@vh¢ÃãUðzßUzåª_*ýåߪ äª*+ý~hÃî,ðOb©Ö+z>ÃœÃaBÃaªr_Zß*Ãß £b+ß>b:/Aã>ãîUß©bBã*BöÃOÃýo.ªß<|ª<*uh|ß/ züvroÃöêÖUîZ_Z<ÃîvðvAhbZö>:<Äðu>u©*ãrhvCrUêýaãÃzv_Ä£AO_ðÖvo©ÖööÄýbåð¢@Üýßbß.:zö£Äo:,@är<ßð/äB/:zÜîýüüß©ÃÖÄãü,h<: >ü~>_OCã,hÄBðî+ª<¢_züÃî>Ä¢ßAuB©:ý@ª<äßUÃäC~©ü£ãß<+î@ðh+ßbBÜð*B¢Z:vB,Zîé,ß/ãBu_bÃœa_ãOo@ãoÃœ.ÖAz+Ãœ>ZrÃ_:@|¢UCBÄ@Ãß|Äo*_ÃœCzÃUU@<ý¢b/©*ßAã@ >ýuöA.h¢A~r oAuÄOîözrýb£ªb©AoîßÃrßã||oZ Cª<åßzî.:£¢CÃ¥uC@î~*ýÖBOäzhU~@v©ãýÃz©ýßz|ah*B o|uß<ª@UA@AÄÄAã@@öäC¢o£åüOrzbo:hB_h|AÃœb£o~öOZüvÃœ:b>O>ýÃað*ßZ.rOäbåªOÃ¥v<ÃîrüAr<~að/ãA©Z¢CãC£ÃöÖUö.BB.*bÃ¥O|Ãœvza©ß:Öß+ý*orßU>*Ö¢ßÜoöaªC+O£ã_©:Ã¥a BozOðh+vªvÃ¥zoãÃC_ÜýC*abÄb~o_:ÃÃzßî.O::å£ßÃ,*ubaßC.CÃîîýbCªB üãßî||@U/_©Bî* ©ªÄÜ©|Ö+|ÖÃ*ãâ|£î~~ð.Ä*îOÄh|öuÃ¥*ãö*/ß_/~ß|rÃÃZ*äªß/<ðÖ<Ãý@|Oübß|A +>zCb|AbÃbu>¢_av,Z.AÃ,u~Ã¥.a>ÃœuhüÖOðã|<ÃÃ¥:äb,:<Ã¥aoÄ£>ha¢üZÄu©|_Z:övÖ:¢oÄð__|ã*ßC,@ý b~£ßÃ_ü|ãUbzð~~:rÃ/,ýýÃöª/ߢ:ªßo|urÃ¥z/A婢U_:öåbroü_åðýÜuß>r:a|väßßv£__ü+,î<ßððã©ßî@O_böhv,,UüåA>üZî£_¢CbB ÖäC,ohÄ.@/,ãUvÄA/UÃ¥O+|ð¢zB>ª.U+AÃ¥+Ã¥U>a<>ZÖüªBhb¢ð>räoðörö>Bb¢CýZb+öä+:_/Ö,å¢~z@obä©ðb>äü.Ãzaî.äoÖÃ_ÃrOU:üÖBÖA ª¢üðAÃœ/bu|>ü:Ão@@*_ÄäåÜßüäªuüZ,CðBb©bý,BÄÃ:üuuÃ|z/UÖu :ÄäoöoouBöO,b*ª<<_ö/ýO.ß~ÖCbð_b¢åª¢*ßåAUö>~@ßßO+üßüÃßß Zzb~rABz|î_@/ÃU©ª>Ä>>~îaÖ~ÖBC|*ßBZ*bÃœZãrãOA¢vzãÄh+OBzßÃ,bã¢Zarý_Ã.o+Oîz©@£ _vÖ/ãB+üroÄß:££Ä:uîãüAB_Ã¥~v@Ub@~£>ohz,Ãœr¢UÃœ+a¢ÄÃ@Oã:ªß*@Ou_vßãÃo£hhö>@.b/ÄrÃvÄ@vÃ¥_abrbýão¢öBã@+ß>¢Ãä<ÖUýÖßAU<ÃZ+ZãÃöªozßä~ rî ,U¢ý©ÜCZaZ.ABr_,<,|hBößÃhÄOö+./~C|u+¢Uýã,@~bBvö+ ÜüâbÃ¥bbU_©©_CCÄB_|äC ýÖz _Ã¥bö~ruö+>r/üð Ä*ªbýOÖh*ðð¢Ãr+bB:CU/v~åßA._AýÖuob¢ãrüåÃÖÃÄA£|>Ãa:Cýã©î:ÖÄUüÜuC+oÃ<©~@äC~.>Ã_|UU@©@~äÜ+>hOB<Äðð_¢*åöBßo<ªý./orîO, ü_..+.©<übrÃZoÃ>býä+ö*övîaÄ<ããBã<¢CZÖbu~ÖörhÄ>rãÃ~£_¢a+¢~h>+ÃO£Cü*r£b.ÖýÖZ¢ahß_.ßåÖUr|u:v>ê|äî|ZbO+z_ß ã£Ü*,+<ߪB|ß/.î.hÃ,_Äo/î+bÄb¢OCUB.,_ÜäbÃ¥aîa:zBhäoÖU|oUªÃuåü*£.o.ýzÃaa*¢v©>@ßoü~Zîäîäö_BãßÄ îåÄUªÃ~<ý*hãßCüå©ÄÜ@vZÃ/ß>zªðÜaBÃü£/CAObªrv:B/BAÃœ<ö_Ãœ,~ð.ßÃöÖÃÖ©Ü.|@ÜßÖoCühOÃßߣ<:ã:ã: *+Äa Uzð/ª©Ãå|vüÄß/ßU.U¢üA:zãzß brhbÃœ/ᄜrÖbCCÜðräÖba@Cü<,*ZÃœabCÃœ*ªÃ.rrZü|hA£O@ãv_Cü|Ãœz|BO~,b@B£h¢ZÃœ@B>ý©îAîà obhzBÄäOAÃ¥z@|>äÖAbZÃ<>U*äv¢üåov,hOäOÜß.ãÖ+bßoOîýCãa*ý|öýäÖ*_î_Ão¢zab@ß|ß_v|åü¢bz@ä¢_bhzBzäÖA:äü~@ OãOã< vußÄýbAU©AabîoB>aîÖ£A@ ©zý+¢:UhÃ+zvß ~Cuã@+.ãî©+u©ãvbßZÃîh,aÄ,Ãœ/î ©ªåZO.ß_ß>oðß.za,Ãœ,äª|a_ðOrB@:Ãð@@ub<ö>.:@aß|üzüÃÄýaAAÃC~ *ýuÄA+ÖU,zrU@:vü,b|uüöåu,ß:ÃÃ¥>ßC£b ~aÃœ:O_Ãb¢£|+*,CýzuîA*î@bÖ+äÖÜ£~îo£+ä/ö¢ðýßäh|Cßr.u:ZÃAý©<ÃÃßÃüZýb£rÄAU_*ªýA@b+@¢|£*|OzOh©Ã+ÃvßUa_Ã¥BððBOo©B©Öý:¢CObUA¢ÃßÖ:bA äßBßÖaýoZªÃ<.aO_Ãb£o£ÃBöZ:¢Z£äÄ.£ªßýrrhýîö@oAÃöýð.h ~+ö_+Öo£B_o/+|äßuO*<ö£UÄuöî|>£*uÃ+ßv£U¢:b<î+ÃœbÃœ<Ã¥hÖÃb£,öhU /ã~h:OöÄaÃ~oý|Ã¥.:Öåðho.Ã*î¢hbüåh.îrª<*:rã+ÜÖZýÖOOuörUzrv/îÜ_|ߢAã/ÖB+vöBz©BÖ,£,+h/öãAÃåBÄðO|~î£AÖÖîãU©ßðZãå|u/åÜ@..CãÜýo|£+/B+@,Aba¢Ö_C<¢A|rßzÃ¥Zb.bO<@OüÖÃZªZ,ßîz>_o.UaüZö>ßuß+UÃœB,© *ð@Ã¥bäaU@Our£åzÜÄäß,bî_A+Ãü+bãOao~ü|aZ*+aÃÖÖvª|îb:bu~ZU:+<>zî¢OÃýubbîÃÖ.@/,*hßÖÖObzªÜü.Ã¥*@r@Ãœb<+_bÖA|>ý âÄoååz|ÃÃhßz~ðAßOÄ,@u¢:öaßbAÄr©býîoUvý:r£ßbî£bühÃu+ܪß:ýUrÃ¥Bu@a_<Ö ©ÜÜ_rvrÃ¥/~|Züãöz<ãå/Ã¥ÃZ+öÃO@,ðA,ÃððUZu@Ä~ZouªÜßO||ÖåCA:Ä¢>:ýA@Ö£,ÃUîÄîOã£výazÃAOåÃoB@BðÃð*ßîA/_>Z,*bß,>:~+h:åýÃ~.+hBäOÃüýĪª.Ã¥>©ä£OzüaB~ÃA@hü:ãð£r>©z/¢CÃ,bBÜåbÃå:ªUß+¢,Z_+ã©_ý_ä©ür,ã*.Ab¢|üaßãüßv>äªBv~B+ÖbÃœ/o,@uCðãZbBOuÖÖb/ZÃ@äãZ<©v|âZ:ª*U¢ãühZåý>b¢ÃzbbýåuoB¢*~ß aªUªßh£ð@¢îã.uU£©ªüðOZßããb bC+uðA£C©¢,©îüßß/oh:ýð/üÖªähÃß,ÖÃß|ßo.ýßÜC|AZöbü+>*Ãâ o©.,äU¢ðb,a¢AO*Ãå+.©ß@îÜu:ÄbC<.hÃö©¢vÄÄߣaý|+ä @*ð/ßUãÜvÄo,@~ß~üäîB>Ä.o©¢Aß_ ,*hrÃOA¢åaZî+||zvbr,ª>üua£@vh¢ÃãUðzßUzåª_*ýåߪ äª*+ý~hÃî,ðOb©Ö+z>ÃœÃaBÃaªr_Zß*Ãß £b+ß>b:/Aã>ãîUß©bBã*BöÃOÃýo.ªß<|ª<*uh|ß/ züvroÃöêÖUîZ_Z<ÃîvðvAhbZö>:<Äðu>u©*ãrhvCrUêýaãÃzv_Ä£AO_ðÖvo©ÖööÄýbåð¢@Üýßbß.:zö£Äo:,@är<ßð/äB/:zÜîýüüß©ÃÖÄãü,h<: >ü~>_OCã,hÄBðî+ª<¢_züÃî>Ä¢ßAuB©:ý@ª<äßUÃäC~©ü£ãß<+î@ðh+ßbBÜð*B¢Z:vB,Zîé,ß/ãBu_bÃœa_ãOo@ãoÃœ.ÖAz+Ãœ>ZrÃ_:@|¢UCBÄ@Ãß|Äo*_ÃœCzÃUU@<ý¢b/©*ßAã@ >ýuöA.h¢A~r oAuÄOîözrýb£ªb©AoîßÃrßã||oZ Cª<åßzî.:£¢CÃ¥uC@î~*ýÖBOäzhU~@v©ãýÃz©ýßz|ah*B o|uß<ª@UA@AÄÄAã@@öäC¢o£åüOrzbo:hB_h|AÃœb£o~öOZüvÃœ:b>O>ýÃað*ßZ.rOäbåªOÃ¥v<ÃîrüAr<~að/ãA©Z¢CãC£ÃöÖUö.BB.*bÃ¥O|Ãœvza©ß:Öß+ý*orßU>*Ö¢ßÜoöaªC+O£ã_©:Ã¥a BozOðh+vªvÃ¥zoãÃC_ÜýC*abÄb~o_:ÃÃzßî.O::å£ßÃ,*ubaßC.CÃîîýbCªB üãßî||@U/_©Bî* ©ªÄÜ©|Ö+|ÖÃ*ãâ|£î~~ð.Ä*îOÄh|öuÃ¥*ãö*/ß_/~ß|rÃÃZ*äªß/<ðÖ<Ãý@|Oübß|A +>zCb|AbÃbu>¢_av,Z.AÃ,u~Ã¥.a>ÃœuhüÖOðã|<ÃÃ¥:äb,:<Ã¥aoÄ£>ha¢üZÄu©|_Z:övÖ:¢oÄð__|ã*ßC,@ý b~£ßÃ_ü|ãUbzð~~:rÃ/,ýýÃöª/ߢ:ªßo|urÃ¥z/A婢U_:öåbroü_åðýÜuß>r:a|väßßv£__ü+,î<ßððã©ßî@O_böhv,,UüåA>üZî£_¢CbB ÖäC,ohÄ.@/,ãUvÄA/UÃ¥O+|ð¢zB>ª.U+AÃ¥+Ã¥U>a<>ZÖüªBhb¢ð>räoðörö>Bb¢CýZb+öä+:_/Ö,å¢~z@obä©ðb>äü.Ãzaî.äoÖÃ_ÃrOU:üÖBÖA ª¢üðAÃœ/bu|>ü:Ão@@*_ÄäåÜßüäªuüZ,CðBb©bý,BÄÃ:üuuÃ|z/UÖu :ÄäoöoouBöO,b*ª<<_ö/ýO.ß~ÖCbð_b¢åª¢*ßåAUö>~@ßßO+üßüÃßß Zzb~rABz|î_@/ÃU©ª>Ä>>~îaÖ~ÖBC|*ßBZ*bÃœZãrãOA¢vzãÄh+OBzßÃ,bã¢Zarý_Ã.o+Oîz©@£ _vÖ/ãB+üroÄß:££Ä:uîãüAB_Ã¥~v@Ub@~£>ohz,Ãœr¢UÃœ+a¢ÄÃ@Oã:ªß*@Ou_vßãÃo£hhö>@.b/ÄrÃvÄ@vÃ¥_abrbýão¢öBã@+ß>¢Ãä<ÖUýÖßAU<ÃZ+ZãÃöªozßä~ rî ,U¢ý©ÜCZaZ.ABr_,<,|hBößÃhÄOö+./~C|u+¢Uýã,@~bBvö+ ÜüâbÃ¥bbU_©©_CCÄB_|äC ýÖz _Ã¥bö~ruö+>r/üð Ä*ªbýOÖh*ðð¢Ãr+bB:CU/v~åßA._AýÖuob¢ãrüåÃÖÃÄA£|>Ãa:Cýã©î:ÖÄUüÜuC+oÃ<©~@äC~.>Ã_|UU@©@~äÜ+>hOB<Äðð_¢*åöBßo<ªý./orîO, ü_..+.©<übrÃZoÃ>býä+ö*övîaÄ<ããBã<¢CZÖbu~ÖörhÄ>rãÃ~£_¢a+¢~h>+ÃO£Cü*r£b.ÖýÖZ¢ahß_.ßåÖUr|u:v>ê|äî|ZbO+z_ß ã£Ü*,+<ߪB|ß/.î.hÃ,_Äo/î+bÄb¢OCUB.,_ÜäbÃ¥aîa:zBhäoÖU|oUªÃuåü*£.o.ýzÃaa*¢v©>@ßoü~Zîäîäö_BãßÄ îåÄUªÃ~<ý*hãßCüå©ÄÜ@vZÃ/ß>zªðÜaBÃü£/CAObªrv:B/BAÃœ<ö_Ãœ,~ð.ßÃöÖÃÖ©Ü.|@ÜßÖoCühOÃßߣ<:ã:ã: *+Äa Uzð/ª©Ãå|vüÄß/ßU.U¢üA:zãzß brhbÃœ/ᄜrÖbCCÜðräÖba@Cü<,*ZÃœabCÃœ*ªÃ.rrZü|hA£O@ãv_Cü|Ãœz|BO~,b@B£h¢ZÃœ@B>ý©îAîà obhzBÄäOAÃ¥z@|>äÖAbZÃ<>U*äv¢üåov,hOäOÜß.ãÖ+bßoOîýCãa*ý|öýäÖ*_î_Ão¢zab@ß|ß_v|åü¢bz@ä¢_bhzBzäÖA:äü~@ OãOã< vußÄýbAU©AabîoB>aîÖ£A@ ©zý+¢:UhÃ+zvß ~Cuã@+.ãî©+u©ãvbßZÃîh,aÄ,Ãœ/î ©ªåZO.ß_ß>oðß.za,Ãœ,äª|a_ðOrB@:Ãð@@ub<ö>.:@aß|üzüÃÄýaAAÃC~ *ýuÄA+ÖU,zrU@:vü,b|uüöåu,ß:ÃÃ¥>ßC£b ~aÃœ:O_Ãb¢£|+*,CýzuîA*î@bÖ+äÖÜ£~îo£+ä/ö¢ðýßäh|Cßr.u:ZÃAý©<ÃÃßÃüZýb£rÄAU_*ªýA@b+@¢|£*|OzOh©Ã+ÃvßUa_Ã¥BððBOo©B©Öý:¢CObUA¢ÃßÖ:bA äßBßÖaýoZªÃ<.aO_Ãb£o£ÃBöZ:¢Z£äÄ.£ªßýrrhýîö@oAÃöýð.h ~+ö_+Öo£B_o/+|äßuO*<ö£UÄuöî|>£*uÃ+ßv£U¢:b<î+ÃœbÃœ<Ã¥hÖÃb£,öhU /ã~h:OöÄaÃ~oý|Ã¥.:Öåðho.Ã*î¢hbüåh.îrª<*:rã+ÜÖZýÖOOuörUzrv/îÜ_|ߢAã/ÖB+vöBz©BÖ,£,+h/öãAÃåBÄðO|~î£AÖÖîãU©ßðZãå|u/åÜ@..CãÜýo|£+/B+@,Aba¢Ö_C<¢A|rßzÃ¥Zb.bO<@OüÖÃZªZ,ßîz>_o.UaüZö>ßuß+UÃœB,© *ð@Ã¥bäaU@Our£åzÜÄäß,bî_A+Ãü+bãOao~ü|aZ*+aÃÖÖvª|îb:bu~ZU:+<>zî¢OÃýubbîÃÖ.@/,*hßÖÖObzªÜü.Ã¥*@r@Ãœb<+_bÖA|>ý âÄoååz|ÃÃhßz~ðAßOÄ,@u¢:öaßbAÄr©býîoUvý:r£ßbî£bühÃu+ܪß:ýUrÃ¥Bu@a_<Ö ©ÜÜ_rvrÃ¥/~|Züãöz<ãå/Ã¥ÃZ+öÃO@,ðA,ÃððUZu@Ä~ZouªÜßO||ÖåCA:Ä¢>:ýA@Ö£,ÃUîÄîOã£výazÃAOåÃoB@BðÃð*ßîA/_>Z,*bß,>:~+h:åýÃ~.+hBäOÃüýĪª.Ã¥>©ä£OzüaB~ÃA@hü:ãð£r>©z/¢CÃ,bBÜåbÃå:ªUß+¢,Z_+ã©_ý_ä©ür,ã*.Ab¢|üaßãüßv>äªBv~B+ÖbÃœ/o,@uCðãZbBOuÖÖb/ZÃ@äãZ<©v|âZ:ª*U¢ãühZåý>b¢ÃzbbýåuoB¢*~ß aªUªßh£ð@¢îã.uU£©ªüðOZßããb bC+uðA£C©¢,©îüßß/oh:ýð/üÖªähÃß,ÖÃß|ßo.ýßÜC|AZöbü+>*Ãâ o©.,äU¢ðb,a¢AO*Ãå+.©ß@îÜu:ÄbC<.hÃö©¢vÄÄߣaý|+ä @*ð/ßUãÜvÄo,@~ß~üäîB>Ä.o©¢Aß_ ,*hrÃOA¢åaZî+||zvbr,ª>üua£@vh¢ÃãUðzßUzåª_*ýåߪ äª*+ý~hÃî,ðOb©Ö+z>ÃœÃaBÃaªr_Zß*Ãß £b+ß>b:/Aã>ãîUß©bBã*BöÃOÃýo.ªß<|ª<*uh|ß/ züvroÃöêÖUîZ_Z<ÃîvðvAhbZö>:<Äðu>u©*ãrhvCrUêýaãÃzv_Ä£AO_ðÖvo©ÖööÄýbåð¢@Üýßbß.:zö£Äo:,@är<ßð/äB/:zÜîýüüß©ÃÖÄãü,h<: >ü~>_OCã,hÄBðî+ª<¢_züÃî>Ä¢ßAuB©:ý@ª<äßUÃäC~©ü£ãß<+î@ðh+ßbBÜð*B¢Z:vB,Zîé,ß/ãBu_bÃœa_ãOo@ãoÃœ.ÖAz+Ãœ>ZrÃ_:@|¢UCBÄ@Ãß|Äo*_ÃœCzÃUU@<ý¢b/©*ßAã@ >ýuöA.h¢A~r oAuÄOîözrýb£ªb©AoîßÃrßã||oZ Cª<åßzî.:£¢CÃ¥uC@î~*ýÖBOäzhU~@v©ãýÃz©ýßz|ah*B o|uß<ª@UA@AÄÄAã@@öäC¢o£åüOrzbo:hB_h|AÃœb£o~öOZüvÃœ:b>O>ýÃað*ßZ.rOäbåªOÃ¥v<ÃîrüAr<~að/ãA©Z¢CãC£ÃöÖUö.BB.*bÃ¥O|Ãœvza©ß:Öß+ý*orßU>*Ö¢ßÜoöaªC+O£ã_©:Ã¥a BozOðh+vªvÃ¥zoãÃC_ÜýC*abÄb~o_:ÃÃzßî.O::å£ßÃ,*ubaßC.CÃîîýbCªB üãßî||@U/_©Bî* ©ªÄÜ©|Ö+|Ö*ãâ|£î~~ð.Ä*îOÄh|öuÃ¥*ãö*/ß_/~ß|rÃÃZ*äªß/<ðÖ<Ãý@|Oübß|A +>zCb|AbÃbu>¢_av,Z.AÃ,u~Ã¥.a>ÃœuhüÖOðã|<ÃÃ¥:äb,:<Ã¥aoÄ£>ha¢üZÄu©|_Z:övÖ:¢oÄð__|ã*ßC,@ý b~£ßÃ_ü|ãUbzð~~:rÃ/,ýýÃöª/ߢ:ªßo|urÃ¥z/A婢U_:öåbroü_åðýÜuß>r:a|väßßv£__ü+,î<ßððã©ßî@O_böhv,,UüåA>üZî£_¢CbB ÖäC,ohÄ.@/,ãUvÄA/UÃ¥O+|ð¢zB>ª.U+AÃ¥+Ã¥U>a<>ZÖüªBhb¢ð>räoðörö>Bb¢CýZb+öä+:_/Ö,å¢~z@obä©ðb>äü.Ãzaî.äoÖÃ_ÃrOU:üÖBÖA ª¢üðAÃœ/bu|>ü:Ão@@*_ÄäåÜßüäªuüZ,CðBb©bý,BÄÃ:üuuÃ|z/UÖu :ÄäoöoouBöO,b*ª<<_ö/ýO.ß~ÖCbð_b¢åª¢*ßåAUö>~@ßßO+üßüÃßß Zzb~rABz|î_@/ÃU©ª>Ä>>~îaÖ~ÖBC|*ßBZ*bÃœZãrãOA¢vzãÄh+OBzßÃ,bã¢Zarý_Ã.o+Oîz©@£ _vÖ/ãB+üroÄß:££Ä:uîãüAB_Ã¥~v@Ub@~£>ohz,Ãœr¢UÃœ+a¢ÄÃ@Oã:ªß*@Ou_vßãÃo£hhö>@.b/ÄrÃvÄ@vÃ¥_abrbýão¢öBã@+ß>¢Ãä<ÖUýÖßAU<ÃZ+ZãÃöªozßä~ rî ,U¢ý©ÜCZaZ.ABr_,<,|hBößÃhÄOö+./~C|u+¢Uýã,@~bBvö+ ÜüâbÃ¥bbU_©©_CCÄB_|äC ýÖz _Ã¥bö~ruö+>r/üð Ä*ªbýOÖh*ðð¢Ãr+bB:CU/v~åßA._AýÖuob¢ãrüåÃÖÃÄA£|>Ãa:Cýã©î:ÖÄUüÜuC+oÃ<©~@äC~.>Ã_|UU@©@~äÜ+>hOB<Äðð_¢*åöBßo<ªý./orîO, ü_..+.©<übrÃZoÃ>býä+ö*övîaÄ<ããBã<¢CZÖbu~ÖörhÄ>rãÃ~£_¢a+¢~h>+ÃO£Cü*r£b.ÖýÖZ¢ahß_.ßåÖUr|u:v>ê|äî|ZbO+z_ß ã£Ü*,+<ߪB|ß/.î.hÃ,_Äo/î+bÄb¢OCUB.,_ÜäbÃ¥aîa:zBhäoÖU|oUªÃuåü*£.o.ýzÃaa*¢v©>@ßoü~Zîäîäö_BãßÄ îåÄUªÃ~<ý*hãßCüå©ÄÜ@vZÃ/ß>zªðÜaBÃü£/CAObªrv:B/BAÃœ<ö_Ãœ,~ð.ßÃöÖÃÖ©Ü.|@ÜßÖoCühOÃßߣ<:ã:ã: *+Äa Uzð/ª©Ãå|vüÄß/ßU.U¢üA:zãzß brhbÃœ/ᄜrÖbCCÜðräÖba@Cü<,*ZÃœabCÃœ*ªÃ.rrZü|hA£O@ãv_Cü|Ãœz|BO~,b@B£h¢ZÃœ@B>ý©îAîà obhzBÄäOAÃ¥z@|>äÖAbZÃ<>U*äv¢üåov,hOäOÜß.ãÖ+bßoOîýCãa*ý|öýäÖ*_î_Ão¢zab@ß|ß_v|åü¢bz@ä¢_bhzBzäÖA:äü~@ OãOã< vußÄýbAU©AabîoB>aîÖ£A@ ©zý+¢:UhÃ+zvß ~Cuã@+.ãî©+u©ãvbßZÃîh,aÄ,Ãœ/î ©ªåZO.ß_ß>oðß.za,Ãœ,äª|a_ðOrB@:Ãð@@ub<ö>.:@aß|üzüÃÄýaAAÃC~ *ýuÄA+ÖU,zrU@:vü,b|uüöåu,ß:ÃÃ¥>ßC£b ~aÃœ:O_Ãb¢£|+*,CýzuîA*î@bÖ+äÖÜ£~îo£+ä/ö¢ðýßäh|Cßr.u:ZÃAý©<ÃÃßÃüZýb£rÄAU_*ªýA@b+@¢|£*|OzOh©Ã+ÃvßUa_Ã¥BððBOo©B©Öý:¢CObUA¢ÃßÖ:bA äßBßÖaýoZªÃ<.aO_Ãb£o£ÃBöZ:¢Z£äÄ.£ªßýrrhýîö@oAÃöýð.h ~+ö_+Öo£B_o/+|äßuO*<ö£UÄuöî|>£*uÃ+ßv£U¢:b<î+ÃœbÃœ<Ã¥hÖÃb£,öhU /ã~h:OöÄaÃ~oý|Ã¥.:Öåðho.Ã*î¢hbüåh.îrª<*:rã+ÜÖZýÖOOuörUzrv/îÜ_|ߢAã/ÖB+vöBz©BÖ,£,+h/öãAÃåBÄðO|~î£AÖÖîãU©ßðZãå|u/åÜ@..CãÜýo|£+/B+@,Aba¢Ö_C<¢A|rßzÃ¥Zb.bO<@OüÖÃZªZ,ßîz>_o.UaüZö>ßuß+UÃœB,© *ð@Ã¥bäaU@Our£åzÜÄäß,bî_A+Ãü+bãOao~ü|aZ*+aÃÖÖvª|îb:bu~ZU:+<>zî¢OÃýubbîÃÖ.@/,*hßÖÖObzªÜü.Ã¥*@r@Ãœb<+_bÖA|>ý âÄoååz|ÃÃhßz~ðAßOÄ,@u¢:öaßbAÄr©býîoUvý:r£ßbî£bühÃu+ܪß:ýUrÃ¥Bu@a_<Ö ©ÜÜ_rvrÃ¥/~|Züãöz<ãå/Ã¥ÃZ+öÃO@,ðA,ÃððUZu@Ä~ZouªÜßO||ÖåCA:Ä¢>:ýA@Ö£,ÃUîÄîOã£výazÃAOåÃoB@BðÃð*ßîA/_>Z,*bß,>:~+h:åýÃ~.+hBäOÃüýĪª.Ã¥>©ä£OzüaB~ÃA@hü:ãð£r>©z/¢CÃ,bBÜåbÃå:ªUß+¢,Z_+ã©_ý_ä©ür,ã*.Ab¢|üaßãüßv>äªBv~B+ÖbÃœ/o,@uCðãZbBOuÖÖb/ZÃ@äãZ<©v|âZ:ª*U¢ãühZåý>b¢ÃzbbýåuoB¢*~ß aªUªßh£ð@¢îã.uU£©ªüðOZßããb bC+uðA£C©¢,©îüßß/oh:ýð/üÖªähÃß,ÖÃß|ßo.ýßÜC|AZöbü+>*Ãâ o©.,äU¢ðb,a¢AO*Ãå+.©ß@îÜu:ÄbC<.hÃö©¢vÄÄߣaý|+ä @*ð/ßUãÜvÄo,@~ß~üäîB>Ä.o©¢Aß_ ,*hrÃOA¢åaZî+||zvbr,ª>üua£@vh¢ÃãUðzßUzåª_*ýåߪ äª*+ý~hÃî,ðOb©Ö+z>ÃœÃaBÃaªr_Zß*Ãß £b+ß>b:/Aã>ãîUß©bBã*BöÃOÃýo.ªß<|ª<*uh|ß/ züvroÃöêÖUîZ_Z<ÃîvðvAhbZö>:<Äðu>u©*ãrhvCrUêýaãÃzv_Ä£AO_ðÖvo©ÖööÄýbåð¢@Üýßbß.:zö£Äo:,@är<ßð/äB/:zÜîýüüß©ÃÖÄãü,h<: >ü~>_OCã,hÄBðî+ª<¢_züÃî>Ä¢ßAuB©:ý@ª<äßUÃäC~©ü£ãß<+î@ðh+ßbBÜð*B¢Z:vB,Zîé,ß/ãBu_bÃœa_ãOo@ãoÃœ.ÖAz+Ãœ>ZrÃ_:@|¢UCBÄ@Ãß|Äo*_ÃœCzÃUU@<ý¢b/©*ßAã@ >ýuöA.h¢A~r oAuÄOîözrýb£ªb©AoîßÃrßã||oZ Cª<åßzî.:£¢CÃ¥uC@î~*ýÖBOäzhU~@v©ãýÃz©ýßz|ah*B o|uß<ª@UA@AÄÄAã@@öäC¢o£åüOrzbo:hB_h|AÃœb£o~öOZüvÃœ:b>O>ýÃað*ßZ.rOäbåªOÃ¥v<ÃîrüAr<~að/ãA©Z¢CãC£ÃöÖUö.BB.*bÃ¥O|Ãœvza©ß:Öß+ý*orßU>*Ö¢ßÜoöaªC+O£ã_©:Ã¥a BozOðh+vªvÃ¥zoãÃC_ÜýC*abÄb~o_:ÃÃzßî.O::å£ßÃ,*ubaßC.CÃîîýbCªB üãßî||@U/_©Bî* ©ªÄÜ©|Ö+|ÖÃ*ãâ|£î~~ð.Ä*îOÄh|öuÃ¥*ãö*/ß_/~ß|rÃÃZ*äªß/<ðÖ<Ãý@|Oübß|A +>zCb|AbÃbu>¢_av,Z.AÃ,u~Ã¥.a>ÃœuhüÖOðã|<ÃÃ¥:äb,:<Ã¥aoÄ£>ha¢üZÄu©|_Z:övÖ:¢oÄð__|ã*ßC,@ý b~£ßÃ_ü|ãUbzð~~:rÃ/,ýýÃöª/ߢ:ªßo|urÃ¥z/A婢U_:öåbroü_åðýÜuß>r:a|väßßv£__ü+,î<ßððã©ßî@O_böhv,,UüåA>üZî£_¢CbB ÖäC,ohÄ.@/,ãUvÄA/UÃ¥O+|ð¢zB>ª.U+AÃ¥+Ã¥U>a<>ZÖüªBhb¢ð>räoðörö>Bb¢CýZb+öä+:_/Ö,å¢~z@obä©ðb>äü.Ãzaî.äoÖÃ_ÃrOU:üÖBÖA ª¢üðAÃœ/bu|>ü:Ão@@*_ÄäåÜßüäªuüZ,CðBb©bý,BÄÃ:üuuÃ|z/UÖu :ÄäoöoouBöO,b*ª<<_ö/ýO.ß~ÖCbð_b¢åª¢*ßåAUö>~@ßßO+üßüÃßß Zzb~rABz|î_@/ÃU©ª>Ä>>~îaÖ~ÖBC|*ßBZ*bÃœZãrãOA¢vzãÄh+OBzßÃ,bã¢Zarý_Ã.o+Oîz©@£ _vÖ/ãB+üroÄß:££Ä:uîãüAB_Ã¥~v@Ub@~£>ohz,Ãœr¢UÃœ+a¢ÄÃ@Oã:ªß*@Ou_vßãÃo£hhö>@.b/ÄrÃvÄ@vÃ¥_abrbýão¢öBã@+ß>¢Ãä<ÖUýÖßAU<ÃZ+ZãÃöªozßä~ rî ,U¢ý©ÜCZaZ.ABr_,<,|hBößÃhÄOö+./~C|u+¢Uýã,@~bBvö+ ÜüâbÃ¥bbU_©©_CCÄB_|äC ýÖz _Ã¥bö~ruö+>r/üð Ä*ªbýOÖh*ðð¢Ãr+bB:CU/v~åßA._AýÖuob¢ãrüåÃÖÃÄA£|>Ãa:Cýã©î:ÖÄUüÜuC+oÃ<©~@äC~.>Ã_|UU@©@~äÜ+>hOB<Äðð_¢*åöBßo<ªý./orîO, ü_..+.©<übrÃZoÃ>býä+ö*övîaÄ<ããBã<¢CZÖbu~ÖörhÄ>rãÃ~£_¢a+¢~h>+ÃO£Cü*r£b.ÖýÖZ¢ahß_.ßåÖUr|u:v>ê|äî|ZbO+z_ß ã£Ü*,+<ߪB|ß/.î.hÃ,_Äo/î+bÄb¢OCUB.,_ÜäbÃ¥aîa:zBhäoÖU|oUªÃuåü*£.o.ýzÃaa*¢v©>@ßoü~Zîäîäö_BãßÄ îåÄUªÃ~<ý*hãßCüå©ÄÜ@vZÃ/ß>zªðÜaBÃü£/CAObªrv:B/BAÃœ<ö_Ãœ,~ð.ßÃöÖÃÖ©Ü.|@ÜßÖoCühOÃßߣ<:ã:ã: *+Äa Uzð/ª©Ãå|vüÄß/ßU.U¢üA:zãzß brhbÃœ/ᄜrÖbCCÜðräÖba@Cü<,*ZÃœabCÃœ*ªÃ.rrZü|hA£O@ãv_Cü|Ãœz|BO~,b@B£h¢ZÃœ@B>ý©îAîà obhzBÄäOAÃ¥z@|>äÖAbZÃ<>U*äv¢üåov,hOäOÜß.ãÖ+bßoOîýCãa*ý|öýäÖ*_î_Ão¢zab@ß|ß_v|åü¢bz@ä¢_bhzBzäÖA:äü~@ OãOã< vußÄýbAU©AabîoB>aîÖ£A@ ©zý+¢:UhÃ+zvß ~Cuã@+.ãî©+u©ãvbßZÃîh,aÄ,Ãœ/î ©ªåZO.ß_ß>oðß.za,Ãœ,äª|a_ðOrB@:Ãð@@ub<ö>.:@aß|üzüÃÄýaAAÃC~ *ýuÄA+ÖU,zrU@:vü,b|uüöåu,ß:ÃÃ¥>ßC£b ~aÃœ:O_Ãb¢£|+*,CýzuîA*î@bÖ+äÖÜ£~îo£+ä/ö¢ðýßäh|Cßr.u:ZÃAý©<ÃÃßÃüZýb£rÄAU_*ªýA@b+@¢|£*|OzOh©Ã+ÃvßUa_Ã¥BððBOo©B©Öý:¢CObUA¢ÃßÖ:bA äßBßÖaýoZªÃ<.aO_Ãb£o£ÃBöZ:¢Z£äÄ.£ªßýrrhýîö@oAÃöýð.h ~+ö_+Öo£B_o/+|äßuO*<ö£UÄuöî|>£*uÃ+ßv£U¢:b<î+ÃœbÃœ<Ã¥hÖÃb£,öhU /ã~h:OöÄaÃ~oý|Ã¥.:Öåðho.Ã*î¢hbüåh.îrª<*:rã+ÜÖZýÖOOuörUzrv/îÜ_|ߢAã/ÖB+vöBz©BÖ,£,+h/öãAÃåBÄðO|~î£AÖÖîãU©ßðZãå|u/åÜ@..CãÜýo|£+/B+@,Aba¢Ö_C<¢A|rßzÃ¥Zb.bO<@OüÖÃZªZ,ßîz>_o.UaüZö>ßuß+UÃœB,© *ð@Ã¥bäaU@Our£åzÜÄäß,bî_A+Ãü+bãOao~ü|aZ*+aÃÖÖvª|îb:bu~ZU:+<>zî¢OÃýubbîÃÖ.@/,*hßÖÖObzªÜü.Ã¥*@r@Ãœb<+_bÖA|>ý âÄoååz|ÃÃhßz~ðAßOÄ,@u¢:öaßbAÄr©býîoUvý:r£ßbî£bühÃu+ܪß:ýUrÃ¥Bu@a_<Ö ©ÜÜ_rvrÃ¥/~|Züãöz<ãå/Ã¥ÃZ+öÃO@,ðA,ÃððUZu@Ä~ZouªÜßO||ÖåCA:Ä¢>:ýA@Ö£,ÃUîÄîOã£výazÃAOåÃoB@BðÃð*ßîA/_>Z,*bß,>:~+h:åýÃ~.+hBäOÃüýĪª.Ã¥>©ä£OzüaB~ÃA@hü:ãð£r>©z/¢CÃ,bBÜåbÃå:ªUß+¢,Z_+ã©_ý_ä©ür,ã*.Ab¢|üaßãüßv>äªBv~B+ÖbÃœ/o,@uCðãZbBOuÖÖb/ZÃ@äãZ<©v|âZ:ª*U¢ãühZåý>b¢ÃzbbýåuoB¢*~ß aªUªßh£ð@¢îã.uU£©ªüðOZßããb bC+uðA£C©¢,©îüßß/oh:ýð/üÖªähÃß,ÖÃß|ßo.ýßÜC|AZöbü+>*Ãâ o©.,äU¢ðb,a¢AO*Ãå+.©ß@îÜu:ÄbC<.hÃö©¢vÄÄߣaý|+ä @*ð/ßUãÜvÄo,@~ß~üäîB>Ä.o©¢Aß_ ,*hrÃOA¢åaZî+||zvbr,ª>üua£@vh¢ÃãUðzßUzåª_*ýåߪ äª*+ý~hÃî,ðOb©Ö+z>ÃœÃaBÃaªr_Zß*Ãß £b+ß>b:/Aã>ãîUß©bBã*BöÃOÃýo.ªß<|ª<*uh|ß/ züvroÃöêÖUîZ_Z<ÃîvðvAhbZö>:<Äðu>u©*ãrhvCrUêýaãÃzv_Ä£AO_ðÖvo©ÖööÄýbåð¢@Üýßbß.:zö£Äo:,@är<ßð/äB/:zÜîýüüß©ÃÖÄãü,h<: >ü~>_OCã,hÄBðî+ª<¢_züÃî>Ä¢ßAuB©:ý@ª<äßUÃäC~©ü£ãß<+î@ðh+ßbBÜð*B¢Z:vB,Zîé,ß/ãBu_bÃœa_ãOo@ãoÃœ.ÖAz+Ãœ>ZrÃ_:@|¢UCBÄ@Ãß|Äo*_ÃœCzÃUU@<ý¢b/©*ßAã@ >ýuöA.h¢A~r oAuÄOîözrýb£ªb©AoîßÃrßã||oZ Cª<åßzî.:£¢CÃ¥uC@î~*ýÖBOäzhU~@v©ãýÃz©ýßz|ah*B o|uß<ª@UA@AÄÄAã@@öäC¢o£åüOrzbo:hB_h|AÃœb£o~öOZüvÃœ:b>O>ýÃað*ßZ.rOäbåªOÃ¥v<ÃîrüAr<~að/ãA©Z¢CãC£ÃöÖUö.BB.*bÃ¥O|Ãœvza©ß:Öß+ý*orßU>*Ö¢ßÜoöaªC+O£ã_©:Ã¥a BozOðh+vªvÃ¥zoãÃC_ÜýC*abÄb~o_:ÃÃzßî.O::å£ßÃ,*ubaßC.CÃîîýbCªB üãßî||@U/_©Bî* ©ªÄÜ©|Ö+|Ö", - "9999-12-31 23:59:59.997", - null, - "7073-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("-100000000000000000", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("100000000000000000", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 0, - 1, - null, - 0, - -1, - 238, - 0, - array(("70BA23A819E1BEADC92E0840F382D7A5DD89EDA9FA12486DCD843FA57FAEC5439F0DE3836E6804ABD214C35672F9552A2CE003709ABB9C5E72B6DA94A2192E1B68C5A0E69F5336758441D7E54EF1FAD32E14799EAA55A9E3708411B88D6F09B8E6A7EDAD586F8BD8A98EEB58E4B61080BC06658DA09070131BBC751A427EE715381EFFA6044C7BBF89A0F41C6A50FFBA7490FB86097B185536DA929BFA70952BA7D520C6B6697C6F8BA604FF9B54A9CABDBBA6B282600CBC06A96F29BADFC419"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(null, null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - null, - "|Ã/ðvZÃöbÖ_Ãð_aBýßäöoÃ¥OUä£rÜÃßZäavövÃî_zöãa*.©Üvýo.o,h<Ã_ObªbðUU<©Ä©~Ö+BZ_ýzäããOåöß@,C<åÜUÄ_ßðUA~ÜäÃüB©uß åähoßÄ*åÄhCÄã+rãî*Aßv*zöÄBüBuÃœv|ÃbuªÖª.aOörBîäð<@Cr rüb@|O/ÃÃU£ðä|üÜoªBZðA£ hrbäo<~C~ð*ÃœaÃb|OÃvöZvu*îo ðAð.OzÃ++h~|v©_*örrÖ:|@CuÖÄAåü:ªððzOZ*aßöuvCBÃ¥@ýoßߪ£ÜbCö.Äßav.ýUb¢|Ã/ðvZÃöbÖ_Ãð_aBýßäöoÃ¥OUä£rÜÃßZäavövÃî_zöãa*.", - "|ãå_ߣ,Z*ÖüuÃ,zÃœ./Ä/A¢h@oÜÜ .ªUü <*ªä©Ö>öÄßå|oã rU|,ý| ä|Ä¢+ܪ>Ã¥@aîrbÄîér> ßUöýö:CObãýÖo>AoA,à rð+|v£Z> ßÃÃœbab Ö£/ > ã©ahb_©ÃÄZO+|ßOýußýbßuýð~.ußb", - ".Z/A< +Öv>Ãœr©C|:ßOUu£Ã@ßUª.¢A@ß *büb@~OvbÃœ~:.+:rvöî|ýã¢ÄÜ¢ÜåðÖÄÃÃ/ä~o>öUýýoäCb,Ã¥hr*bªv*@:UZåßz bãÜý@äbÃœa©|a©ÖªÄuîÃÃ,£h/b hîrb/ããßvZ.+Zh|", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("-100000000000000000", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("100000000000000000", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 5 -$values[] = array(array(("F77B01558B14390B5D153D199E510EFAB893BA155798F1E832315A12B2999B22B7DF0A26306B294E97CFECDB5E94FB5EF78585B07D2D5EB61AA04B601C87F977382AB851E7FBA867FA1467B89C16999D460B1B2D13DAB59A80541B902FB9221FC665A333EC99770BDD2DC658C59619F406AE2A117CDC636E1A4E83"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("1F490F38B008EF657365696D8818EE15357EDAAEB125EB57039C7903DE118446D026129CE3E093F2811D83D76D0304A604F7A0783B2385D3C7BC6AAD2C4CD779FB44239306512144DAD82D93A203E9F097D30ED6710C0BAC903EC53775F6C6344609DC28EF61AFB3C65B9D5305E231B3C27A15594DF0F8EF387DEE40B0063A2F"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("42D6C508809FDBD47B9A6BC196C023120B017BAEFCD5ABB38663B66F51120103E2FC469DE7010BD6D65EBCF1E9B60BD614442E2DA9C0386C2B12811DE96C83699968DE997A862A0AF6A5D79E18B9CAD058E05A0DE3D35922C4D972D580EDED6ADB535881D7715B8E1A243704E89A547147B1C508117585C60EBCD5A113548A08B76DC3C56148F8E684CBF3BF9B55392A84018DCE28E45865CD92721B9BBA7BC5992F4D7BD68E0FFFB8DE844582C62AB982AA1C11C6BBDFD2145EF8CED5DD4BCA502D256C07BE53F6F18E164A6012E20668B998F19D69FFDE4F7A6678964129DA93BAF455A0BEB1C457261F37A49572027BF1661C455A2CE72E471D8D57DA5FB0653CB6A61D8C66AA08D3997749C739B90864FDA5DA0D95398E0D2759A0246D7CC76FD268DECBE4EF8ABC1371C0B49D660D6384F2DDB9D658A67D1810650C09E4661DB0DAF5D1DA3C800D1A1EF92379D96FC04C3DA07A9E0D9879C161B3D4897EB32F0AA203C3713B884772CF9EB75F82043EA0A4C0CB43EB7EA3417B107989AB849C6BC919D6D5AC4F11B45808CE6EE11A3E5D7434E5BEE7EB1F246081F52B0FBDC64B024C95692D69F354AF17EBE3AF346060D11B14779EDE84DDFAB2742B59E5A49B42836B9584D42F0B40910FD0003C742B21955AF4A94EAD24C8B86F7BF4C8FAC97250A02CC9CBDBFCEC09370DCFB3DCBE3D4809C48BB0F1F3D547CCCC65B3167796455DB09B476256B95D08A017A05514DBB13EABBE353EC15EAD5781FE0F9738F2BF7689562C22F6E6B30B2DA851DC35F2B8F1B3756BB960079B52EEF0E157A9C6CFAD5729CBBDAF8E48C50B2910190CD0218E461EE3C287061682BADA3405045C6267D8A3840C27FA41242DA5AD36F682451DCE52118BE30D2651134903D55942C1AFC7A940684C15674BDCBE94E158DF8B38941736B14AD683CB7D32F8DDF76CCAEBE4FE6F4D41DCD1B8EBC5AE247844F933BECA40420B0F64F02575EB191C8A546FF3A9FD59703DF7B7457DC684285F523839C51F2C2C88DFD49A7E27CBF1B89C61713F30CE47A0C381D388FC8D48DFC8C623B2E0576B27436BC928BFF1152CC58DE16513E82E83F199019D59A38B528CBAB661A83ED844F64AFCC5051EE0D26BA7C26FABE3CCDF979FC14656CDCE9FDF779C043BB38387B59D6EC8EC687ED2B82F32B2E01CA6A8EED6C9087D91EE297B5CE58D82DC96B7233BB90E7403F32F20EF8E269A1CBFC3D1F41665EA7B0924AE454D8F83F14FDB987B8EA4629733EAE29AE78D02C78E5F9D21BB8EC631CDA831945F2EB664AB2F9FB38E1F8D1A6039C02B824F937B22E7B7EDF16135C11566ADE672CD3704347EEEB8EE4F386FD283C081CE9AAE7D14B6727877C8724AF9DE9D2771A7A79301BBF70460DB8DD3AE481B6EA0B3F37AB730D1BC5BBF8D6B0BA4BE10B111FD2ADB2D7F12A2AA1C39289FCA2FAD04A946EA226F41D5EB38422E8CF8BB1C7D219C4AF0937333E0D80D139793BBF45DBD07FB77258EA1BC55EFF53DE416B2601CAB7E2DE4BC703612094A622D3E577C4ED0CEF0C0A55793FB8E9BA37DF6FCC25BB37AE83DC6790196C5651B433F8E169878EE1347C1A07753EA325620CDB81C0D88077AC36F3F8757CD59FB3F3C4FBCFF840A0407E97A99120394529423E0C90C480EBA371453293967BB7A68A437179A76569BC2B314230E6E67C0DBECF234EC90EFF62D9484EB31E1CBA02D00316ECB7CB71ECC45065834EAACC9AD934D4C74878ED7B540E906F26B6AE1E874F60D3DF9F55E35D467A78997C243E8BA3D9D937F2BA57159B88A92B12C61DA256FC0F39E8CB39EEF16A020E74D1E7EA61E372DCC4D6848509CE3406ACB83A806CC6427024F492E8AFFA4688B858A0B9A0BEC93F4340DD70777A302A872332392F7191AE8F67C56D409A2CCA9FFBDCA11D8A65FDFF0B3D1BF8D1F8BAC468517CC80D5936B7022A38F96BEEB91402E08B9D8F1CFCCD344A243485B5BD7F0AA8D848B002829295FA1B602CF4931C8407E5F17D1D355B54BF83C33502314C6CC9EEB3067064006DE90794F19C904FB717B07DCFCBF1FD638CE4007EBF6D93B8CEF36305D00613A3D5838405A069C81F441C2D51B37CA0C7E15F898C6391AE8B73DC22993D39D7019CD9FA386DF7B3232BE12AA330320542529B24808C874043E50891D4372EE21A351BB4D9827859F99C666072670B4B31D6E046921556850599A6536555EEFC0CFF106E248D2BBFEA312AE41626CDECE9BD5FFD56D85004EE96AF4FEC24F4A3BD7A538356AD26B4BB206C50EA726EEEDBB969B7F5F1A5249F1B7F8DDDEBD3C54C8736ABD33A963C96AB42266BFF1A7C37C06422BB3CADCBD52EE44B2D98D6DF4CA704634BB30B91232D215AAF7EC8AFF103561D77A11A0FD97AD6A4565B1EFDD472D9C4EAA72074B4C2FF8E1B531C5666444A1A32C957AB9D0489D0EC674AD81E23763C94F3B549D2E158A4A18BF4865E703E020104BB1B1B3E27E62D5F985D88276FE7DCDFC1729EF0375160A2FA010FF3726C2938C709786B2156CBCF2C83490B2C8A2C0CB98EBECB7310DD59BA684426D629ED2C244F50667F71BE6AA6DFC3B41855866FE50349E3BD5DC5B63C1EB0B912A4E902961CE6ED39B59D47D04424A35D294154E1D431D2D0097EC679AA2A50B0F54801E6B11DFB7EA9362F7602037939226912DD5BC73E6316229CAE10639F9B3C86DAFC22BC9C0B55D648AE126CDDF6059D57311783652E099ED09E714D5E359694397B387D0665DD0777758E9050A944A6A8831C5E73B921ACE283EBFD8A188813077E2352CD8886E164001BEFAF794E2749DA94DB29481080598B8A8289DF7D6EFD7B88D275E1E91FFC2AEA73AD320D7945B4A3BF73ACF9CF5DA30931FE04679482ACE233E06BB157F174DEFE5DACE3177E113CC52BCDFB4081C984C2E8A721F7B355E0F05FC988DA035BBDFEDDFB61B0CFE1B3DCC7A268B4B191B226E6EE7F5A52D73B07BBE2C7B49862B1D01FFC728667C40306E3453010ED2C4ECE1761F324DC639BF6C059F4DD1A0F4464E098A4B51F3BBF12DB68670DA71A2A8FA4099D366F9CF3AC608BC61DDF25ECD400CB30448789AEEBA2D04973FC8E4FA63F653910620DF54394F8549375A143A58B930CDBE9D6081A5AB1939F438FBFD23C1FDE69FCCC6202D5ECAF8F351B75F19CA3E8698163CC596D1EC5B07A66001548825755C2D9B8BF210A05FD2678E54F4B91F2AF975640C853850CEE06B1987FEC9A46A2B4102EC2EB3938876DCF32E4397664D863507DE2419F024DCCDAB87F6186108C22329B2B633FB46E1DB95A945218A4804CB01C8C1C5461251DE1061070B915E7AEA6C1F6EB496533AC1FD488346DEBA4262832A179947FCDC1761D723993ED549372F5BB56BDA5035AE8D00C8FE8E0AFF551FA4886647E0A26F435752BE2FA08227FE4C178305D78833F5680B4633DE8856D8CD7A9A4DE3EF4EB8452882FD767EED7EA1FF42F37C50481C5570DED45B3C65CFFCE66225E8D91B5CC8CBBFAE05367FEA1EC891DE45635B7D327DF872117A5C09C2B3008F1CC795C8E4E62AB19099161FF1F4B693AA8789998AE4F36AE1A94B4E6EB5EB5135EFCE5632C9F95F38AAEC9867D9801DC3C95F3B2A670437D42BE73B9ADB42DBADC4E901265F60E08D2A60E0F75A0307282E8AA456B027CDEB968207E2BEC45D6811EBA2EF7FF664697EAE0007243F7928DF126C509D84FD9C1C168FDC58FECF650278E66F6018A937263BD70127C5C75609DE02AB570CCA8CED05D3696787107FDF9A04AC5909C8C769352A62ACFB75564F39D379FB230DF6AD59815A4440D09E2A57228CC01AFCFF163B4222E168DFF1907C1A2DB49784BB8C0EFE00876D8229658C3755D9E895343DD363BFE1EB1544798FC9C825A2EA93D367FFB21BFC77A8B918BC55308A18A8E6E5F0E034C4D9A1F9BA86F7A4B903C2A1A48171E8E318415BBA06C82EBF14EE7721E140B2095E0F8D5EB961758004FF15FAB38C09A7BF03CDAE8479BE117239A6988083D7C30F974572E48929B7CBDBB606B3340B87E5CF61DE730385E5517CEC80BBDCFF812852EA1B4697C236DA15E3A8C3B75D3E2D1EB3B021F9500B126020679DA9FBEEDC34DE6149F0404D0C2A035D20DC21991EAD529C294E5F2E01DF09E6765B983119F4CC8ED9FCF43DDD65A3F6F6F3EB363F8748EF565EC58ED5B05F6AE65AD7B192D2432C1092C022B915BFC4AA68380EF4D2A7EF13A8DABB2F3DB9299C8A56838EF26423AFBE26ACC72536409F00E46D77517613B3E79EE9F788FDD2D996852EBF3C36B703145E35ACFA28ED719A1B2E594BB14B1342D0A6547A2B27C153D330A0078163CA2C6963F24167B8BFB1775D6CCE788DE53A1B7C6B8F6388E936735827F74C6A18B7632CE24E59D2DB17E6A303CEBAF2DB454B66EC04F73AC9E079BBCD0E90C7640D44B46A4CAB96E048A054A8C0851A7B817DE1ACE67D14C0296301D49D05CD871F3FF58A05888E9AB485A463A391095B5F3F4C90AE1B7D9030F441E8A9C24342D3CC89818F9076F8EE47F861D8B01C975AA5755DB93CD804C165591146A6F0F2416290D60888CBCEC953A167221BB780D7AA8E034CE58896F485BF2A3664B24856051F761F458ED2DAA693669B70E1A6AED9399B18EDA55BD9F9E282C8A19864A058111DFB4060911BBB29A65122FB83A4197C5C2B1630685FC49D26630C09380E2EC5425EC3AA6D79A7805CC3FF09B7FE5FDDAF253B398A28A8BD37A33A2E0FB2F097235CB824E06E0FE38396D4A560BAE627032E5A13D3F53B1AB50672C93E7871AAD268E65A453AFD401657B5F353035D7CBD122AE058DDC42D209E6EDAFC45A40B252AD2F7D38BE2C93119FC7B3F48C6B2A8F50B2C4D9051F5A3B93AA7CC2244D0B3AACE293165A8323061AF3EE049EDD8D7BC32063978BD94D8DC541A503AFC0ED52CC795799D80424DD9FBECE113145ACB62D00FF03E963BED395054118DE1F564A069B481585E393CEFB9607CD1B3C5C7FA4FEE6AED1E96958CA83F4D2B31BFF7058740A960D8720397D3008536BE1B868B8D163EF38B7781D21FF5B01FEC10BEF968764A731B6722DDEAD2191E115ECD8CB9C19E1A8103790FBFD640E2C1E4ABC2428BCD48D1137025F46008B95D63C1C02824652A72A37864FC3AB444263F1C062756DC869185BAA651782620FBC0B15B1580BA9C7F80509F12684B965F2CFFB543029107873B2FA269BED9FA7D57693B89582EA6847F87A6063C7328392447257870DCF82FAAD4DA501732C0A75621BAFE0F0D07FD5930C47BEA004AC707684577B4810C7A710CDE8354556C4CC7FFB01AB9292A1640271F3F187D44A98A1248AEED48F3D5153F8D022E83A803C477B4EA502F0EDD3E2F368C61CD62F3C735F8420CBD5F7E0CDB05799B27E7BC7F1C7BA1C85A585B036B70BD11667348DD993BF98B66F6061836DE7529954116477A0D4551C12B6DDC9C3EA14DA16CA567BD9C02ECED3118267489927BE3326E29597597592EF61C6ECD1DEE2754B976C9380E6BF93F2A5949F41FDD3898F2F86AD078F32BD44C38C780931ED042F1D1BB12C842D5C674E92F1C4E640F18E3D6DB248456DBC64AF5ECD5A479AB0B8907034D2225CFAF23A45B8EC40F85E729AE9CBD43ABBD9AF5F4C8D356FF22FB3E23924761C2F6BE062C00E0FB4C013A6F04ABF5DDEEC8DD4968572D5CFC5823825F50A07C3E8667B8DDB616AADD9FA8F28D6352532C1E505AB5FEF96AC9610AD3DD5E9BFC61BE317D443DFF37B1326BC8AE3ACF9806D055410046C73F8DFCEE7BAC43CD7F04AC91CF32D40FEE1E18A8829230BC8E9272CC0C0859FB1AF6F1F0484E848B6D64ECC301A8FF6532C16722E79D44AD5C4060E83977C077ABB077D840E3C6A09A8D3A1DA66CBAE5930C994BA136147DB2449407EE9B51F6A47418503127244EFAC6885A622606EF857A552DDD75D5749CFA26FC4FBEB1FE3B4479315369B46BE74B51DAAEBA1A8B3DA481734624182E9FA4B4370128FF3B158A1E1EADD49C1CC5E9459223FBC6E2DF9ABFAB3C3DB8172B4A92AFB57D6ABF4C15C5249CFCDE680765AD5CDF860BA3DED157666953FED7D106F26242EC67487F86460D36A146E3BB56A3D7905B38A43FEFD0E3250356F817ED5295097C261BFD65DD5840FD6D3C8A73AB12AF274776CE4D22531B83227B2F4DF6484FB1DA3E3088D92A7EDF6442829458B1584349F0A6DBC6BB59D3FF79426228F30624ECD9996CE2274D1CF7EFE9E0D1872B7022EC0406A173EA1EE55C895FE71CB6713B5DA77E18EB6021A28AE4D7D27902774ED23F3CEF008278EF7A87859EED34DB3878A573E0AC58368BE263178F124C6C5A111015DC79823ADA2E8F9D64DA83B455089E5AD38E97A27908B00C9C6BE9DD2D1A2B8CDD1EFEBFF56A99B11DBAD2B05EAFC18773F298CB796E9CD4C8759E6E45B26F49AFF6D7370ADF6468F5E684BF3600AEEDA961693B825F136E8229F680CC2A6B6B4A880167D78A7C938E2FD693EADC9EC8C6B0C4E34834C74FE369B4E0E9E39BCB7A1974ADFE47151FE4D5AC52897F83A58B2DBA1C65872216C1B1836ED69CAAAA940B18D068F1C97F9BA5FE728775C663E50E54BC297F65F3F65EEC4DC3A749482B02BE7E67B33D9A633A80BF88AF8E4CEA1F88C748EE8114A524D90A047CBCF45CB64785B648FC32D1BA58EFFEA56A46CF9F62A31E81FF3D80AAD942D93395E77E8C63D1680995A22356F68D959215FFBA933A651918D6DAB609040C6FB9589D247070724298DEC60AC07FC78B71A7EE94D98846F830CE65B6891A5E0488326AFD58682421A3BD21B4745A58980E4E44621678953112A4F4A20BBBB87D7FC1818D15C9320982CCF11C669184D575CCA83AD5FCED109D39EBB3748216E804A435B2D9CB83451B3BDF498678CA81201E2D3E137EA782D470F0DCDE7F9400B60918C25BD2FCAA48621E9A1CA489246E69935555D7D486FCB2DE68DBA7679CA7DF927D6008254D2283F5B05F0D6530E7CB6D74EB3FABDF3492C5A8F494CFA1FB81F33D110BC365444532D1E12426EF4DAD800F39F2B16734B349CE437C598DB454B4DD1A281FDB070830A50CD9FD0F6BD65B4613BF6808D08B095DC2F522C7EBA51D8D6F49D4248EA50731C20B5B5AB539636C21137AF4EBAB0C8F1EC11812E568B967BB709F210236B76EB83746C530AEA78891B6A6D82B29A1C33C4505CC8CF44B5951F39E4599416AEA5C0C3CB465475E701A90802FC2E13C92CEDBE5BC31EAE1C3EBEC743C0F61EE504BBB7C1EECEC5EF5E695B2FC741B966FB70B76BD5CADB9712A78C979D32160B8A130E407651DA6E17DEFD69B37E54FCF55048503EA30F49D020AF47323E521C434FA5A43378F0A996595406CD71D394529F923CF9C5CCD713B46055A2832BE98CE11663D8A0B97C5F9EB9D55F088C6E4D0AF4A29C15228D82DE908F18AAF7033D6A89EC6B7FCBA1D615DD5B0BE7780B380208979E794D67BE88E0FA103A63B4A7A6A7E48EAFF2DD4F42CC7647D7719B56854716A26F74B42D122DD28EC191ABB228B3554F838C980F3B584BBCC72FD71745938AE34D123831E3C76C72204E7DB110C41F241C5BD308BF076707290755F256CB392E06B875167B235BCC1025EEECDA8A9C0300B2FAF9C0723435AB2108EC616A2B55054FBA584F08A8F8F016E889288A3C9E6B52F193D04F6D84843F7B4A1A7F5AA60A89D79AD033B3F809F209B989AB1D10F47A710E788784C5E225D41F9D6EDE70BC428EAEB2A1BE379EF999DE45B3FAD9225A55A00E4C2D72F501B8AA5FA46A9B23890D472C7E6E52BC497F8EEDB6803E3B0AFD33193E7260151BA9A0F913D7FA8EFEAABCA6A3ACB3FBF2E3B4DCAE854E718A7F54B73781E5069D3210C4BD69A87174FFFF0BC387875E3937164ABD8366865B2F7E948343576CC5B609719BEF3E994B4367AE9632BDF2E7F67DF06B9A4919CB9A699DFB03EE31A882461D7E15C25A088D0F6A2BB7E028717BAC9CC557012B6CFFA60175A09E3765260613FC9084D22423F7EE8ADEFE13AA411C8F57516C51811AB08E6FB0D457EEC804F5B1DC3BE0E6DA68A16F12908DF1926734841EE003C9A482D1E00662734F9FFFC2C9F851CE053B988CF8BAF26E7A228CDD59AFD3CFE8013C0D4959F0E067F561CF12DDC3F940992F5886530B0EB22467CD8995199FCC3FEAC8CC4474BB3AB8250359234BAC5E839CB0808B4984F62D5B24F23356C2DBC202E2F30B3CFC24741DCE3E07A59ED8CB6F0608691F30E655C29EF95FB907515227971058A95356909C1C7D7B56FE87BC4C36D450CA72A2C1FC9D489E02C4BB067CDBD01E7E80120BBF67CD02C37B0EC4173D137232F097B4E45E6C0C1AB89DC751670E7B1E694DCC24C8A152A59FFE819E7476C5B138D37C342B2A201405F0F6D9F5B16C6C83B5BDEF475D7A4F451F545FDA7DA641C"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "©ýÜßOåÖ|_/Aãª+©ÜÃý©>|a_@_äßb©ÃO@ îýUÃ¥~ÜäÃÜ¢zßöÜ|ß<,£|ª_©/UZzbÄîã", - "", - "öh,bZv~Ã*ubzrUäO© z|B£b.öÖö.äOh¢äÜA/:~:ÄCo*©+r|ãÜýÄß/©@zBüB_zOÃ¥Ob/:.r/Äbä~:öZ,AÃOB¢ä< rOUÃ*<<ð,övO~öÖh£*Z¢öãu~aÃð**u_aÃÃuvoÃCßBýb:Z~ä,*ü©Ä£_h|o.U<äÄ<üovAAý£Äîîåã+ZÃœU/ã äz>Ö/Ã:+AB/¢ö+@bZA_ß.ÄUÜßÖÄÖv,oߢ_ãO<.ß*|ª¢ªuÃ¥AðobÄö©ýÃB¢ðªý,_ ~Ã¥rrUßB¢z_ßUîbåö𪪩CÖ:o+ä>ýÃäZ_ð@¢ªaÃÜCb...äÃßbÖý©,ßärã.bß+ZBö/vv +.vðUß<ªÄ*bZ£b:UÄOåÖbzßrüä,ãü<üoª,.¢>+ÖOzãäUv+>vb¢bü+.£¢U+îÃU£ßÜ£Ãz/ßßzßrÃBaCuÄÜBC*a,bbo©¢vC:bð£aB~@bb+>/~ö+¢åüîåzoA<Öª£@u,/äîðßãOÖrÃßhÜ¢ü_Ãœ ,:/ÖCã©ðÖãäÄBUhAB,ß|o@/CÃœubO+.>uß CÜü©Ãbväb*Ã¥<~|ýÃ@~ÄuOÃ¥azýîÜ<|* £åÖh*b.£ÖZZ/BuZ¢ªouÖ£©ãZÃœzÃAUªß/£©Bðüðå:ðbÄZzzð/,ovªîª.Öß<>ýÃbý©ßA_üßa@ÃZÜã~ü|ý<,z¢b*u@AU*B>.åÖäbb>Ã:îAv,aü>ðbÃÄ/©u:Öå,v,~ãa,Z+.oÄîÖvuO<ª ðCß*ãOÖzoÄr:|ÃCu Ã¥baäC¢| roý:u<Öã/üvUa£aÜãöu:¢CÃãÖvh/ÖÜaü*¢Aö*/ O+UühÃœUbb¢>Uo/Öbb>ÃœvªoöäÃß@Ã¥,ªb..¢bb,h~b@ubßv@ã_ª_ý:|oÄA.+Z U*©<_bß@,©ÃîîÄÖ,BhªOüüZ㢢ävða©aävßo|~vOà oðäb_ä, övÃœB+Zbßäðr|ª+ý©b£åªß>ä.<£ß*ý_Baä~b,uÖî_bÃÃ*ýhÄßöOAåüß>/,+¢Ã<ÃãzãrÃaZª:ZÖ*@bÖ@.*ða.ß>rAbB¢ýãhOb*î/@Ãœ/Ä> ÄÖh¢v.©åöZß: AߢO¢ð£ªªo~vOO a£îu>bBÖb,ß©üCåã+¢Aä¢å~ᚄÃC~_+BU äz+~öUöA_ÜßUÃ<Äzr£ozÄCbB/£ üo@AÄ/¢U~:ß©Üð~*ßý_oU,Z*Äöð¢ÖUZhZðüåaÖCßbUhã.ü,,hÃœUBî>B~Ã¥OzÖ@ý> O£,ãöÄ båÖbaU/ðÖ|@>üarUÜß+ýã/*vBÃß©Ö|_/.:Orh.b /v|rý>_ ü.ªa~Ä>ª_<ýZ:ÃœOOABb@ßýB*Öb|~Ã¥hZ~r<Ã*ö@äÄü~©©/ßå©B+Oî.a>üzvv_oîÄ,O,Bübßu@U+|U .ðüoÃ¥>@ü+oOüÄAÃU _bävÃ:U©,öb|ä>ý£urÃœ:bouA>_î~rv~Äu _/Ä/ßÃ>hb> ÃvÃ+>+,ÃBu|ßäzªãî:C*+*_ö|öÜ+büðBÄÃÜvOuÃîOAhCý¢ähuäªÃðoü+ÃÜß|~AªhZ:Co@ £ÃÖuo u.üöB£hr*Uo|Ã._,.hzo/ýã_>êîuîðÄý¢o ßä:öå,ÃœAZ|.Cßvvªîaîu~//,AUý:z:|BUu~_©*ZAußUöv>Cü¢zßýÄöÃ+/bî¢, Ããý ä~©Äö_ÄäaÃœO*>Ãr,ö|B:ª BbuCÄÜ>OðªÃî¢+ @+@ao©BoZ, u~ab/ßzAor£ßU.:Ö/媪Cßb.O~/:©aåðÄ+|ðoÃ¥Oðª~uhÃ¥_vªÃî©Z©ßu.@~äã+ä+hzÄCää_~_ÃÃa_~ð_¢zãÖÖ*ªvðho,~a _Örbão/.ÃhzC~üÜÃß~Au@_B¢ßÃ:OÃÄha_*ov @B+rîBª/UaZh: ß_Üßý£|ZhÄv©Aaz|uß@*z>©A¢:CÄß.ðZÃ¥CãÃ>üßä£Üoz*ã~ãßuz©vÖza>u|AÃ¥+~üöÖaÖäÃÃ+uÄ,~:Ca©ðZðªBvACßßrv.ÖývOO+ªãßüb@ö:_+Ä_,rÃ¥Bª:ýå©Äåðb_/ü:Ãh,ZÃ.ãBzäboß<ÖCvßb@£+ßrzÃåöåð@BüýßUzv£ßAazªðÄåuCßÜÃ+/ß* rüA@Ã¥r, üOß©@ßaov>£bOZ~ß©ãbhßÃ_r:îCz¢vhßîZö@b_~aÃœ~Cöovhî+uãbhU@U£ªr¢ÃÄ_ý*äoî*bÃCUb+u¢~bOÖrð,/B©©ÖÄå<*üzbbCªZCu| <ÃßbÃ¥z¢b|aä>Ãœh|UO>o£OvÃœvÖzO>ýÖb_¢~ðÃZªß/bßð CßZî|*C¢bðhuö_z|hhbßð äOuäü|ÃÄ|+*/oöavð£¢::Ä:ÄÃÖzuzöÃC,ßöBÃÄ+_ð*+Üä@,*|är/o<äoÃ>Cuv:.ª¢uA*að|uvB_C*©ãu*ü©/ÖzöððÜ*O~oZ:öÖh¢ß/b@@CÃ¥Cöh+©OhbÃœo£Uuý<ÃöhC:o:Buà zvöuðöU£©££:>ª>ª+©*+z> ÃC,,£Ä©äî¢*åäB|UÄa| ßb<¢>ü|B.ä*AßöUC@_~öýßÃßoÄ¢>ß/£+Ão>¢¢Üå©o|/bZzbðã/ÄO.¢ A ©<ß*bZüuüZ£:¢>Ö£ðÜ/*Ã@ã¢Ä£+Ä<öCßãî©£h¢o£.ðvÃaOßü*_:_~ÃoîärßrU//Öö:vðߢߩöé+rBzã¢>ä,@üîhÖÖßößü,ü<>Ãœ|îîoÃÃᦌAÜÃÖåü|Ã~AUý|CobãääüZÄåà ÖöÃ*C©Ã*ýCübbbã,£,:vßýÜÃBåäö£¢ îa/CAã,:*>¢|arýßßa~¢z./@ÖoaUUßßÄ+ßbbªöß_vðCÖo/Ãœ@BBbðbýa<|î*åãhu@ÄðOuhöOãOA+,z©+ ++ýBÜäZß/_::vUzvÃÄ¢CBßbýr_B~ÃœO,~O|: Cªßoö/CäÄ:ü@Ã*ð/|zãCr,*>@+ßð/~ðr/ßã>Z£rÃßr>và aCªv|* Ä.¢äbaÃOA~Ãœ+ä<:aýoðBB ýîbÖr*à oa|Ã¥Ã,u¢U£@|Ö¢Ãîåä|CªC£Uu+.Äßzß*<Ã.ÖßýÃbÃœ*îZ~_|+OOä ß~Äb+ £+ü>ãöãoBbuüBîC<*Ur*£ÃßA_¢/<_åü*,< +BZ@ü:./>orböuzU<äa©ö|ðöu>+¢uBä~ª|Ö,bÃuîUvZÃ¥*_>+ß,ßo Zö./,Cýßzãa.b© ,îvªA.ýü+Ch©äý_© |ü//UÖh~:>|:ýr*Ãœ_UabýAzv/hãÜO©äbUÃ¥hhCa|ð.@,ðÜîzÃ@ªä>_ð:u/äoz£ßvz*v@£Båö:>@b:£,<_ßBU|ß*_z.ðÃUävvªUB~>+©¢¢©,ýaãa|b,¢<||Ä>ýÃuuOrêUvý~,¢¢åo,ZOöobbr/~bÖå:a,£r>O*ªAözî:uª¢C,zÖuzªh:Z U*ä_*äbüOÃ.hÃßßvU£v~ߪß<©:CuÃß@ÃrãCUo,üA,U.îÃvªö/b*>rÄ>£|+äîÃßZ,u©öäÃ_BãÃUO~rîÜ.ãOߣ:a_> :ÃÜ@bZ~üA©a ÖUåö>ßö@ð/£ª:b|A¢ðrh/@uãz ªöª¢OuÃœBBððÄöO~ü.AqwtyÄ| B<ååuaA¢Orýîzv/ß+ö¢Cðüa_îªÖäarÃöobÃuãOAö.a:rÃœ|@oªåöZ_ßßB£~ä+ãrB_ð._a*u~ð..ýB*AÃ*BªvhU,, v¢~U ©BuîðZ*a>ß,zaÃ¥hßÃÖªä>ZUöüZ@auÖâ ªÃzÖO/Ö+*~ßäßU@v>|äý+ß,äZö¢Ö ÜÖbÖü,hßb@ ã>oÃ/bhbaÃÄOä£üß*/Bh©aZ:Uv:>v*BZß.î~ð||bðA+Är|ýßZÃCîÃ>äî<ü_/ho¢/bBð+:ĪÄUÃ¥C¢ö..Ãb~aÃ¥<ªBz©rð@AhöÖ.ð£B¢¢*|C.©ß+Uöb|ß+_hÃü:a¢ÄvßBÃCð+rAÃœzB/U:ÃœbðZð.UäörÖ.ªÃüOBaîBÃœaÄ|äabðZvÄöZÃüå@ÃÃZAðh<*@Ö@b~uB@oÃÖ*+.î+z|Ã>ßCðÄb©+z¢bãåUr*v_åÄAª/hau_ozÜðÃ+/ü_,ãßüðäÜåBääCob£ã.ä©î+ýî<_>b@Ä Ãœbo.ðz..ßvOÖ,.ãb.>,+|*vÜð~>håýß/Zý ¢r@+uOb Ur>*Äß@*Oü@_hzßÜr<äßOüÖå+*ð_î,,ß_./ÃœOÖß©h£A|*Ãu£h Oßü<~ Ä©ãB+aB@zä©b>.uÄ£obÃ¥ÃBv¢a|ªbbb*C ðýÜå~£©CäO,ÖäîÜ¢å.åöªî@â *+Ã>ãUb/ðü:,~Z©©b aðÃâ~r.@Bã*aäCUbbC|©oÃ/üßo>ߪZðãr©Ã~¢ÃCz/Äý~|ü~v u~_@£äUZÖã.Ä~", - "ð@ßO.£/*aUU/Ää.ã|u>+Ã+z_ß:zäîêãÜO©Ao,ã©ÜU_v,/_/*ýã>,©~/hªî£_~Oa>Z..£ßAããoUßB*+_o/_@ªÖ|ýhÃü,hã@öv>ohb©îüh©©u.Ãœ*Ãœu*>Bb©.ÃÃ/vîã¢Ãa£Z_~ru><@|+öB ~*,Ooß*.î~B..¢,ýh>ü©~CUÃœ|aãa~", - "uªaA~aã*büßUÃœOzÃÃbßoã+äîbzb¢äßzãÃoª.ÃÜ:üî|uîÜ:öß Ä|ÄaßãÖCüvöu>U©ö|¢~Ã¥aü~ãvZhöC¢*vz@BÃh©*Ö©~ßOA©büUßå u/ ÖüäÃ_Äßý ð©ðüoðßåvßu/h,ߪroÄzÃAbär*Ãœvößbã~ßߪ¢üýß>C*:u¢b/>/öÖO¢,:ð@./Zo/a©<:,bOäZ¢ß/î|,uãC|ß,aoüå.*©B|_@*© ÃœÃO©+z~©h+r>î_Z| Ä*Aý@@|üÄ@ÖÄBZBuuª<©:O>ß*A~©ð/hßýrßý£*vuªÃ©ßßöAbCZÃðßîä_ãu¢<ðåbZ+üÄåZÃ¥.uãUÃ¥~£,b|©ý|@üåßCA£>Ö>¢_@,Uöðý£ZBðîzbUªO@>*U¢åÃz+rb<¢+£ÄrÃ*O,:h|aÄ:~b|Üå.<ýUo+ð£©ö|ÃðO£ÃvOB*î.ßb+Ãœuð/hUß.Obo+.|ᚎ@:î.¢ãbÄzÃ¥<.üa©öä+hýu@ÃÖzÖh>*äªv~rî*hüh,äÄã+ arbà .z|Baîz.ªßÄîªýävÄz*,Cho.öo£¢üð£ýz/Ãß ª_:hãªbbOÃßböbßoZrZhhoäv©/©hĪoUböBÜåvv_C*Ö/ä~aåãßAu:©r:Ãr:,ðî*~|_ýaöAvaöÃaÃœ,.,B/ßzî~öÜ+Ö£BÃßåªuýoäz~aöß_aZÄ©.bvß*ühª:a¢Ö¢vzö~ObCßßU_B>.ä>*|£bÄ ~ßå:î<ª_oU_üâãüßu~ä¢ßÄÃä_ZBB/üÃîðÃCÄÃh£OUbz|_Ãu£,Ã/<ãÄÖCOª+r>/ÄvãbßîÃvBbö©+Ã,h äb/ÄÄ_ubrüa@CÃœ/Ãß+AÃ:Ã¥zðh¢*@o*AÄbZ@hbÄaêaªöÃÃýz>ý_++,äüÖ>~brÃåãðZ:ßvz¢B*zC:v+r,£baOߣ*£/_,|hð_îCÖa/ZoAr+:AbZ,Ä+zO/h©ÄbZöß,Ãîbö.Ör*@*ÖB,U.ÄÃ>>ÃœzZz@ªzBbOA_bühäîÄ©©/*îîßýð+üðߢvªÄÄ_>ßaüZý£ýÄå_v+>abãb..,<_ÃöCC,,vý ©ÄBv_*©oZªUC©£äAO¢r©Ö~/ ÃäÄ_B:ð|ohÃÄr|ý îÃ| Äa>ÃÄz<+Zz~bvÖ~©£hbßÃÃü_öoߢ¢äÜbAC¢Ã+üãÜý_vä_¢::ý ÃܢãAbüß|<äÖ/_@boCîz©zZãh.Bý Ã*£ZOa/z:ª¢,ovzCßãzA~z bAa< Ã¥>ªöý<ähu:U_bÃÃ@vî@AýÄ*|ßöÖ,A|vo_C.üýÃ.Ã,oî ~ÜöãªßßC>/*OhAoAÃÜý>Ã¥vu£©ß_*BbhAßÃäîb/îuBÃ¥~bBåÃÃ|î>ua.îü_>ýZrÃ¥ÃÃ¥,*hUr*bbåÜ.ýÃo_ÃœCßßO|îA,Üî+|/¢auoéÖÖ:_rã/*UaîZ+üuo,b_ < Ã:zý>ªvÖrãý<ßA£vª*C|ß,å¢ýªu:¢h¢*r.Aaur*ãB<ßbZîOr@Cä>ßoß|vBß,| ðªÃÖO/ä<ÃœZü,å©~Cª+öÜ:ÃœCªªª_>ð|>C@aUÃ|_/vhýoÜîäbhB_ü :¢ÃUuîA,CßßÄÖrý,> ã*Bßßb,üuUaãÖBu*aßo|¢£ÃÜ¢b:ÃaÄäâÜÄ¢U£ãÃäUÃß+ZbÃ<,OruöÃO¢zoÃ~Ãö@, rü,ªC*ö<Ã,üö,*aÃr¢@£:ÃB@ýð>BCoZO,bbb*ävßvÖUzu©îßÄã> ýÃÃ¥>åä¢rää+_BÃœ_*Zö, *Ã,Ã:Ãœz aÃ+v©üO£O~Oýåzý_böüîßîZãbãu+ßa_BÃ|*äv@©BÄýåz*hÄ~OoÃüC,ðbað>AüB*~v.zzöCv|vÃ¥OßAý<ðh£O£ ~üZ£vã*A£Cª£Zböã,¢bîhÃUrhbA/~Ã¥ îÄv©/a©ÃZî_ªÖ|oÃö_BÃahÃrÃ¥Ãb@ußî/ÖÜÄöA+>ßÃöÜý~B<¢üzBî>ozÜübÜÃðo::aBãýäuÄ|Avab*êßv:_ðÃýöª:ðüý~ßå~> ý>U<Ãœ", - "2462-01-17 02:21:36.465", - "1965-09-18 17:12:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("0.7719", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("0.9405", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 0, - 1, - -1, - -1768876553, - -17727, - 7.21, - 0, - array(("F77B01558B14390B5D153D199E510EFAB893BA15579A840B599AEDFD103348DC09698ABE87D1D1AC31C3F3CA2063D3205254CBA4DD2F075CF6F5FCF0263BFA2ACD610F34C64F73E6C20DE393A6B9EAF6C6CCABBC67A3FFDA622EC791D5E085F5C9C9FDBF539E5902E1E4E1CF4B73B53D0B062A59903EC2296235D29888E211E288EDCADDBC2BA32872A8DB82C1308EEF788A86CFB62B658B06D45C1DCCC179FFD3FF475DA8D880DF6302E7E762406732A6E320FE5B422C5C8D6E8CE855BB12ED"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("1F490F38B008EF657365696D8842173457D84ECB665D09943362EE374C8CBAF3319FB78FC5A016CDA11F1A0F331B632706E6F7AAF50E09C1407B054E19B719E7AB763BE98B886EB7C181294743FA6017FFC73166940FFE2396F64E4A9AC8B3E9053263BDDF7A9C7D7740E973E4C17E461AED4F5186B2539B7818529439241CF9CD40411096A338842BB4EE54268A3ED578082FAD"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "©ýÜßOåÖ|_/Aãª+©ÜÃý©>|a_@_äßb©ÃO@ îýUÃ¥~ÜäÃÜ¢zßöÜ|ß<,£|ª_©/UZzbÄîã", - "Ä,.îBußßß,uêBUurZ_öÄ*zߢz:ßüåÜbZ/bO,~Z/ÜßÖZr@Ãuß<Ã|Ã_ß*bªvßÖüãîªo/oAbã¢aCOð~AªCªUA.ªbv£Bo|U ©¢+îð,ZbrÃuA@¢zbU>ßv¢Üî>:.¢r/azbZvý*ðä:@abA|Z,B.ü~ã£ÖüÄîÄå/>Ãœb£O+h", - "ð@ßO.£/*aUU/Ää.ã|u>+Ã+z_ß:zäîêãÜO©Ao,ã©ÜU_v,/_/*ýã>,©~/hªî£_~Oa>Z..£ßAããoUßB*+_o/_@ªÖ|ýhÃü,hã@öv>ohb©îüh©©u.Ãœ*Ãœu*>Bb©.ÃÃ/vîã¢Ãa£Z_~ru><@|+öB ~*,Ooß*.î~B..¢,ýh>ü©~CUÃœ|aãa~", - "uªaA~aã*büßUÃœOzÃÃbßoã+äîbzb¢äßzãÃoª.ÃÜ:üî|uîÜ:öß Ä|ÄaßãÖCüvöu>U©ö|¢~Ã¥aü~ãvZhöC¢*vz@BÃh©*Ö©~ßOA©büUßå u/ ÖüäÃ_Äßý ð©ðüoðßåvßu/h,ߪroÄzÃAbär*Ãœvößbã~>Öîå,ÖÃ*aO/b~¢ßbð/ªvurÄ,ååü,:bö*Büv¢ößÃÄoÃœ uä£.AãUᚦ_Zzb@ý@ßzåöuBýª*rChÃîC/Ã¥<@U<ð|rvoÃ_Ãü~.ßuðãå,ý֣ߩ£h©bÄ/ã.|ýÖbäÖ©Ãb+/î,ªÃ*<üZ|>uªaA~aã*büßUÃœOzÃÃbßoã+äîbzb¢äßzãÃoª.ÃÜ:üî", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("0.7719", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("0.9405", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 6 -$values[] = array(array(("E67CD00C1929F775AE525AC74900F156D39556047D95E9AC7166724B0561AD5AEE54B6B522F319DB45648F168E748CDFD2FBB7E72C9BB600F35ED443AB902B8DB30A2ADD64564153A9868F2BBEE72DFCD9926809A9B63E52AEA5C1C41EC63FA538F1538459BF7B7CBA544DA60C8A520C30D77A092821D40CDBC836D5CF322F33"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("A02E3CF0F890D26C16E7B9C5C802FA491A9504DB381790D59EBEB08F2345A9D55F182F03F2422DEB571BD4AB3209AB6E8A3EA27D842B83899D67513AE04249D13DE366AFD2F656D86718841D42E4A294134105E5A22B503395A5658AF0F30B14D2036D4EA3FB9C93E7CA166CA66647B9C4BC279E3D642F1293FDAE9C3"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("FE9DB7F93783481A395912FF3E37326A11784F26559A3CE2BD487FD82E37F979B51232174D20FA96996A60F95BC41F167B312B724D8F066461D43CEF0470EB025F98492B041B1DF07DB88DEA394287F828685CFD0C872B250CCEB578E40A91287F5E9AC27BF31160143AF494021C2A1F4C43C69B14EEF5D86C231F3665A40C7227646BDBA8C57D3457FBE8D1929723F0A3962CA043F5149168C15557D4DBFA702E816D9DB04F7687C6914418052445D5002E93CE786B2F314FD972E67026607B0178A31029853F5320A4DAB2B0FC8B447C7F56A5482BF8C2523B4ED7DD9017A031C8168D8134AB7AEF3FAED3907D80534D2E6AF0CFBA33D580E0BDEE3768768F8D3A80E7E5FEC347D4DCB6087D2532D5BAFA89AE2627D07795410DA077BD4287C91E4556FC986D4F6B221CF2032B575AAEE8333DDEC76966690952A3F122D0169A2F3F5EDFDBD9228C3A43030ADE736AE9B2F28E8A05A802B5DFECF346CA5D49520558D4ECF97BD20F11B0A483655B8B95E8BA3F2F418C95863CA900EFE5DCC2DCFAB105859E7EF036207F7854047E4F83615D5D101E191078C34EAF507BE32934EDC789B45605F76AC9080FC1134082BD597C8E887EB2BEE1A08DC250CE9FBB3C5644673CF59B2D5B65F34615094F6483CBF1F7FE55A0B63A09A814C843A79B2A4CD36BCFD845C8BE4F32228A6CA8D751283F84DDA8BDE7B58D2C334D866418BE581CC836E394AA852938AE227E2BCE60D0C2A00322BD818FEBB542AC127B37EB1A633250DFA16D9395FEB46F0EC436625ED27CC3C4149C9A9A394A2D52FE73AD01C3081BF87D802F550C6E23091180267FE04E40FE732605C698B5A6563F41968B066E4BBC1B69E0514D6EB7A5789089B823AE72AF32AF8E9C025460C8DAD815CBD79703A2356A63766EEFB0046F5F4D082CEE3291CEE75956E0AEB9C70917767559ECCEE18DE682B94AFB4143FE892DA206589CEBA584FCF03EACC287B19C6D56783BAB82916C7A456B7946D2A51558578116C0B17B8ACFD4F0DDA2E024FB07AE8EEBCBC3DDAA1199DD57802B3D294A29546A059ADCBBDAED6BD692B4CA992070719844BA4A9479D60526D4240AA397C692788CB9F56DD1D210DD5706185DCF433610BAA5E590DEBFF12F95B6E7B6E2C83FCB21510D667A04C671AA6C3D73BEA85790EDFAE9F1E1029ACDC8C6D81BEA59FB8FC74C3B7D36CF96B5DE73E671827D71ED5E57C8E8DE4DEA1EB7409EB32CAA865071ADAC716CD95A1E6433136D1C284A4A54EABC939012B079611CC5F531E28934ED9500D4D1FF8185FCAA83AA3A7D028FF5535E41EDF2BF5EFD320F4F1464EC82AADF808628767855693797E68E053EE41A3500DE834746FAD6A1D79FEF317197BD01005BFD7C677FDA09D8B65BD9D0F7F32B35DE640EFE629985B0869ED96537C6FF288C04E3E79DCE8003796F8F9FFCD1DFABC4FC9233AF039EC277CBE0E001A3A0EF5CB043F20F43970FE30B84F209E6B3B5BE2DA13A33CB798966216FD2EE9F02D6CF9F434C8EEC9B80658350A47EACB891182A21D30265BC9037B98CEED3C5214308AE2269A26727D19221D4B07BD40BB969DEB9C37E2FD469E3B214D460ADACA0610C9BCF894FEEB0034F41E1910B1562ADD285F50B5DC5D9DAB985F0535271E474334E6E393B3019EF09C02EEDF2D8D79DB1278D28B6950347C2F4D0C9FE5DB0DE5C6C5DBDA62971A26B06019D97EBE7E1AE52A12BE4BBBD9E4A2776476050F0AB0F4F91867A1AF787B5EBBDB09366AFD60DEE1BABBC84FAA0AEBF727EB32A421A15568D475A6166935B1976F9FE0D0AE477A039713544EF8B55F0D7BBA878D48AA8A29A3DD8687CAB888750A20E01A2A1B58D0712F0D0E4D16602F16A89ABEAE245EB505B73CC0B0F248DE8ADF44D7C544D36051B71C199D7BDC9D7D6761122DD9D6D5D22B0462DE981F7EE63E836DD19859D1C52A5CF718352789DAAF43598126B0B1086F059A307091C9979D236EF87EBBE50D9F5CF38CDFCD9D038A883F44B5FDCF7DC2656292CF915498836F7B01B8816BE36E5BE1D9CD3438916896B5E86284D1AF63F1360949523E0DF85AF970529B4AF91C4BD66A0BF5B08E2CD12A38EB6C35FE8DDBADC75B529A1C1DE3D1623AB84ECD4BBCCC16BE77937E40A7EC782805128C150AF276FD73ADB9B1BA4C28144E5364925CED2EA02F3016383F86A37621F408C1554D2FC5C7DFC81F0265042B7B59F25477E5CB17F3EBF5DADC4EBDFCFA438FB64356CEF1239EDA8D251F9DBA2BFACCD5945F0E4C38F0DD695754F98911DB14CED54DE8149B0120D91B4DF2E45E3BAEE171E8689B45438D528955A9DEA5416B59FACEE4DE12A84A833E2530EAFDE91B1BA608073972798981C0959B45575233C9E2A8E98F56C0DEC9ADD56D10129254903A8E289D4A21D77135531D8D1CF53BEBF17708550F9BC4D5393D8B9D0C697F3E310B0AC929967FB0C17849F3B351BCA899812EAB8A0F90AEC943640C964A64DBE3DF4C01FA62B26AF757838741C121E780E2F38B4AD3A4317A6C0FD622631A87037E1FD405AABE36DEC3A4327C095CF8218C8BB346E981002B1A978E52EC38843D0C83D3B14DD1D634CCBC872F71B5611F17A495E7673019ED0FA8FB032FD3E28B2FF57F757946E3BAA58E8A3D09E0B965367F4B3792ACD5CC69E6A194D6D304E8C9FBFF8C35FB95C0C0EB31E8B5ECD31EFF95DCB098BAF8832FB30B92955ED3D279A1631DE13D2F4AD97A76A76AED068D03829540F644401EF37C5C72E0032B72176F6F1479C52DC26691E72E34412832AC8E8F12B982DC2551541A74D0BC5AC64F049FFB8DF2034E54D2EA646D21DAB25419D6518418C8848339B315AAB8E1AA3710DF3FA12CB4AA7B30BDD69877B82F895810B6D5F942364C0B78443EED939BF1302D5C429B1137652BD6435AE1FC0B81839C52A82AD43DCEA8F395A4707B8DAD21E5585A6BECFE46D951AA230EDB5A1F6CC2AB5140F3C66BEF9DA9977B92E4B058A6390C93B115A69E0BCED92A1ACFDDA97637BDC02C6C2E1EFC6ACE4356CB466CF9CA68A24D8BBBAD04C33534ED31C4DDCBFDF55B36F02C3A0AEDA74657634730786C3040672B5BFA8C10CB6926F921BA95F07758D5FB35F9AE2E976FE46555917C04BC80F18476A0A20ACF1634B31301BBECB056FBEE6A7D3646923893EC1626CB29FD124CA0B91189D6A6CB3C1FC4FB0B85A3F7A4FE958D9A3F5ABAE23E0CB5F4A85B0AB5F0C51B239EDD25379F411671A73D0E481E71DC846F880D560E3F4BC9F0D683B4CA350AB098D4161170460AEE34394B010E7DD6368E765678C7B5256F816B46B8CD51B762224DBF4A6BBB14198D1F11C4E44ABB72F05D3643EE91F8D3AEA690150F4220D281E8DEC0CE9BCAD3523CFBB893607B36AFC3B8602B73739054DB9BB51AFF1A3AEE3F430B7BFAB4648252E1A19640304E2B59753B89C0B70E653E7BE30005B86322B0CA642D6FCAA51F53DDC881C20A04AB5D99C189DEB12D8834A63D8EC1547CAA8E8ED585DC10BB8EC01C25E2FE8CA3346747B27BF6D9440F9CDBF8C98D165E0F512DF343248ECF3CDE32B9C29B4A89A7F2F92789A200B3A53AD5E8F027E1F1B9CE2681D089601FD878759DB42F8174524240D693FEC45D506A5E085EA5DF14EC310FF464C8B91E92E94F012C62FF85B96196ADD71B6DCAF9C921663B67C132A960B1732F8D0F89993233AAAFC330FD6AC84EED0F213DD0D3450A9B9752E33B281C67BBB18C662C520D14A4E7C31B369833FB7318D4237832D0AFEA8263F2364EF882836B753BEE62229B020C98F90029D4035357602253E8EF88D0DC8BA9EE1AEE55EF4087CB12F80D10F639217BA9E79FB0BE609EDEEED62EAA7BFC6B01D678ACF72E3309CF9208E05B5A5F3C1AC773DEE94CCAEB8D7C0F770911ED5F169CFB3A7D9C2A4D66DDD01EB57033848FAA13D3082D71C50B9D2F7B2ECC00BDB4A9C9BE5F24CE7EAD26781B7CAC360D388AF76EFE1A46F92B362EE07CAF2EAADD126A0BA675356AEBA535E1A566586F57124293D4A1BF9A62C09328F5EE01FDF7DD3E5A323A56B3558C8358E46553C4DDCF895A6C8AD63B62DF98D5E20C64FD9337E9D8280E2D2CBB8F4E482E56B60D7CDE4B3654A7816CF702E7629170D1491785C5AC7B7B9CDFA26C9EED3C8E0D0C299B6903C13EA8EBB67CD11516BA8B3ABE05209891098201316B83F808A42078F5EB48F617CD77E8ECD9E5FE9A77C8E7D1542F376D746B101957D255AAA0D3124C682F3B619522E09D47BBC7138366C9A29C26A474F91B817038ECCFFB123E3BF6D3A232588D864C314EAA15B8A3FC85E108B30E8A2EAD534C9DBB38A852F57B097FC04FE134711515EA73365EF5C8CC4C66DEE26F2FB1C8365EE5BAD25D93EE492F0C38D3AC94AC979DF8AFBB670456D4EEEC3174130BE9CE0819825EB642C84684FD2FE99FF89FC2224AD137120CCF5A9EE5E361526A2623227ADB93025DD14B20593679D46D44F65C7F41C82AB2BBFB4A37CFD06F66F42AC70CC7251D0CBFD4699606C8D6457621A0ED14E3186DF87FBEE048DA9E00DBC9650BB2D7A61FD94FBF7D6E94ABF31504B19E3F72520102691447663CB3E53668CA4D53DA82AE7B8113C5339B0F53C3CBCB4418FFC1596753F959B35EB99C8BCA46F5DA6E9F4A721322BE6E8CDD7769B0839A717FDC369760D60FD138EB1CC7773969540989553C70C1F5DD12EADA7681F002DB1F2B1BEE006E69988F79492805E6F753641350272E6DF0BDD6697475FB894F38E841A84C2D8B43AB1FCCAD0788DDC72BF387126E9FDBACB13A6E0796E9E39D062A003FC44EAB35E8CFB50761B0E924F8A258827476E0990C94F711CF73BCB12C2FA6ED78ADF2ED4841399EEAC44AF041390EE49161288F3977232F4799767512F3DB321A0BBC5AAF6B4A233D41BE4859F6D08664C82FCB0717A3F17AC395AEDAC48BE6E918E6357CB44536270551F7C8EAD550C8E0005AD4102AF257CAEFDBC59B5878C5C75F31009B19B082F9A401C5BC388FFE44E5EAB42112D8EDF3EDB1C469608BBCD922019B94502DD65FC7DA7C292CAB826FED80A56DC494A476530CBD67EC29891844FC9A26E469E4B8145469486CC4C7322F647BEFE64F048D5D254143706E63AE4153D430116B7B839BED49603B6C199E457B3887ADA92205E80CD9CF189137A5E0BAF708E08710C3E8F843F8F59451E11BB30E0535EBF0A821DE580481AC6B0AA5A38F706500BDBE5A749EB555390043BAE8B36593327004CA5ED7023F0C324C9D49435214D18C0B054FAAB14D4B42F8DE0459A8F8F56763237AB61629AB8F4E3252180BE5AE2EE96CA8605397BEB77F0BEE242AB2994397E1D03E9A2B65D159B05C8EFB2337133FE5703C409513DB64C3370A4B77649B991E802A056F94BDF6C706934B3FCC4BC4CAF445425D02F2DE04C9D6E45B95D25BB0E5DCA31D0DC040B245798E708034F6FB4570831F594C0B8D5BF0A5BDD9AB3134378A49EFA8B7CD33F4801789745D8494DFE916155F3C67A9D115C11D2A82E8AA385A38F5FB17528DB34F2AC9348218FE7C10EDE0852BD50B22A01E82F6B01961A77D411A148E99C896886200119E754EFCC29109A4F8151C753DD34AF8CF970335B921B7310EE9FD772549FCB9D4E9843ACF86341F0FBCAF582494E6AB5ECFD6F15ECA586CBE0DFDE8A3928FAE1862A0444F10293962E435FC726F0C69F3421855730B5B0A81178CB1985B6B6D3CF14FC1E4925C4E96AD8E6A946BFA4EF193E5617A2A3B0D07A5C3278377A11788C8CF711F4A7560E5D9CEC57CF066252D3DBFF47609E302C632CA4EDF24BB2EFC76E1DB9C792FB71E747B906B7580174F9E0A55B7ACA17ED5BCBAEC703691B12E9FC9060E9A98B40E08E5F18E429740A2B2FF312A65775FD572F5DFBE4346E7D2BA71DBAC8A08A5054647BEC6AFE083256A49CD375086C92879208BA4B905CAC20E2ED3D0CBA418AA4A3BE41DC899358262BA43F979A08233B8A323698D005C408CECB8811CB259C62112ED14A2BB6FEFEB0931FEB1C1485D9C90281880AF2D49EF9380DBD0B475B0508DD866B36D80FD43437E73DE5D9ECBA45AE47C8A413571F5A3350F2A940F3683819E23F8E1AA3F1F2AC53D30A96B9204C52775CFF9261F485661003871D2BF3437F05CCF6AD2C2B0B56D6F7C08414C37930A656C89669A5B7EEB927099F69D5815F8F8B91EC2A9E264C51A9BB6A3400524C895776D42AAD155F1407F304216BEC017560F488328D0507307C45C0EA4DBAB9D5BD16125EAC34E27532D822AB212AE6BCBAF4D30BC91AFC516FA4AE685575C64D3A2F6454DAA3C92B1213FFB4E35B0F525E032F59E6AD0BA21AC0EF08867A20BD11D471F1C691E1172AA7F9B61A476E13945C0951456C678C24C13A1AB4C0A597F342A20EEA1E8BCC22182E3EB35588D252DB742A70CE77A46DE4CD0557C6636A0261C3D8A45F2876D03C2F2C3E28D8CE3868C8D84C7B2624CAFDA1055185755BB5C43C5292AF2EF2B2FD59339F4B66440BFECCEEFE8A1D863A9E0EA6BE22254BC7D37779EC863C8939D5254E21F2857F975CFA52B8865C2AA6B517F4A187348FD80E6CCCD3ED5884C243B42965CE6447188F0F6128D29240764CD3366F47E7B2A095761386B9ABA904759075D133C5D750CFDB1277D267057B2B55861921157A5B0288CF5851C15FE3655CB3421C6E4287FBE59F6CD53ACD2E71975A63248FE35F151ACFAAF67FFF0E7C2E1275B4C35FBD12FF1E3B760E85C7BBA0E7B966FBBED579532C029F98917CE690EFA628577AAD389D502571A7E9069407EF559846A7806F3CFC60965C1D7A6EA155E61FDE9F63263980C9AA9964D26A256E98A93BE4A0E86E37EB5B1917DC80D6B6CF5203E7B5678B2E5EC5DED6D5D829F670ED85D132BDB5FDB0A7B3070C22A449D4E29480673EB9301D0C85DA0A9861133223A6EEFA0584129D05370DB484223C219401F343A98F323455B32538DBF217469FD5E97BB54ECE5E82E36106AB7B98EA49B875976ED0889374B9A5515FD9C11320C714AE38C06C463394C1C62D2B88D91A3AEE237C80D7CF4C5507FBA97CFFA8214DE5E466EAB2884B2F63D90FC2664831CB3624EB9E96A0EF4562E2B9709CB69518A5DF46A472308766D74D1CFEC08AEFBECADD45DABD82B745F52188D0CEDB92AC43F61D86028C59069C84C74984F085D7F3FAA5BF71BB5B222800AE51A8B797FBBA6FBAC96D69BC3D843F3D38BA6DC1049CB0DCD335D23B889BFA5D01DE075DE4ECCB8F1435BB6A893E673B317D2268BA3FA9B5F8DBC3C5E734322E720BBA5F7CE2594CEFDEACCFE0455295CE97090B7992A2C1345264092805A7DF038CF77E8307BF4F23D679DC92DAD4B2882EF7502A27B5E068F92A2A7D4F23E936CDCB544DD411C6D8B9E7A219C62E5A7DB281FA0E57B4DCD717168ADD95D2D4B28FA5C50E8064B31D3177714012C6A1CAD0ACCACE29DBDF18EF7C35BD86E943E712F08920684BFD4DCDDCDDDF439CC55174C00CE9070689B4C6AD1F032EF6964734892D223C806185AE7D004CFDFFD453B7E448C6DC5AE00C66D45ABA2CFA1FD74695AABA6EA314F45E873A8A053CB6C6D9CB4C49ADCF6ADAA820BB6726A659B45D2805685419E414CF5467551F5607291D93E160AED52A772ACF114EE7ACCDB82D862CDC374E71BFE63779586715BB5218914A92C4F80688FE8E3E8508962C2386AF2798292AF2CF21217A765BD20F1514BF3556623BF33580A4DEA910ECB7500B56D1B8ED2228EE9E7A70A37D8F2C2B7E28704860D5C04BBCF218844B49BF356A296A05005728F19647B7E8CA50B8F078B32AE77B6EF0359585FCFB09A4A255BA3B385324272A27007FB2181DD8806B39280C981ED8B9FB39356F7B5B2CE7F2A4622D8F1764CB508803E6FCBCD717BD0590C1E1970A37E2813C19EFB8AFCBD397C074BBABF054D55CBC101AF1BD3F621DB276CB6B055B992A4A0EDA4ADB98D8371264FB625C11143BEB85DAFA1CF1C4FDB7D2063900689165DA9060E2798B802437E2DE5843FFC1A0618F8080809F0E594A"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "Av++ü*+_ãßÃvBZÖhuvv<*o>>>o o,ÄåÃÜý¢+*¢bCýÖv@aßz:z¢C¢<£ß:ä+êo+ý/@aAröü¢UbÖßv ©Zhß@ öoAhoO@ýð:üªÜãýýÜC>*ýå|Ãã|COb|Ü¢b£>*ýîAÖ/£zªßu<>ÃZ*Äaý bªZZ~b*î*© |", - "", - "¢+b<äz_£r/ð.ãÖ¢Zbý~ZÄ_*Ur/üuv*ACüß:ÄaßvAZvuAUÜÜîC CðÃܪÃßßÃüðÜÄa|ß/AZîvvÃ|a: ý.©bBöUC,OrÄðrCZB>|u:/ êubãOoÃœo.<ßvzãÃÜÜ:/Uß|ýbo::>Ã¥BÃ¥|bð<åå,ðu.bz/+ãüvßß:ýabîAuüÃÃÄvv ~vr,+ßå+@rãÜv_Äz~O©ÄÄr+îoö© ÃÃüZê©åz©o|.ÖÖäÃü©zða|ªzh,/uýäü,hB|O:ý£Ã+rîaBZö:>rOZ+,ÃC*ý|uu©zßrOZ©OÃUüa~|ß©ªrä:ýÄÜ©©AÖ./ßÃåBßðavvßüuZßßå:ÖÜÃÜuü_h@h_vÃåÖ_~båî|ÃœZãOÃðß:,Uð@ýbÃãZÃ*üzÜäª>béCîªÖÖßbßößo|ª:>+orý~ÜÄ@ü_CbrÃ¥bÜêZräävÃœBhåÖîß.Z@av_ß>öÖ* ÃÜö C~<ª*>üaÃœu©£~~@C+bª:öðäbOö Ã~o:@ðü/ª.>**©ã@ªÜ/aðUUü¢o<,rÄ//.oU/ªÖoâhîÃåÄvÃœB,vZÃð.<*rã*:|UîýböO/.îäªÄ,Ãœ*_BîîÄzß:hÃ>ÃÖB.îßvbÃ¥aÄßÜb ©<.©a_£hßBð:Ãv//ã+//à >öß/ Ã¥ ãZhã+¢ b<©öB¢Zzý _ß:|ä/ ¢£*ý:ã~uÖßüßÜ¢+¢C,+Ã< bAOü<î@/ÖüãßrÄZ ~¢BÃ*ßz>rÄoo o<ÖãZªzß>*©ÃzAýüü.£*ÜßÖ~ÖýuÄÃÜÜÃ+Bª>BüîCü_Bv/bAßz@uz@ß|*ÄoBz+~CÄ~ð u/>åý@@,UÄÃß*ߣußa/z£UA,+öOöOCÄÜÃrÄöCðÖaß*ᜁCb~@z©rCuª_îz/+.Uäª.ä/>£:ªOb¢Äöß oUãaýð<ãrßüÄÄZC@büü~b CZz©_ð¢>oZ~OhO_.vðC~©¢_öÃzb@|ÖýUvßh|Äz+|rÄ£©C_oðý£ÃA:<©<Ã*öUäBð.z*ð AüÃü||,üýäãroh_îbüÜÜo£ß++CÄBÃœu+ܪßîAüÄ:îbÃ<Ã¥@ÃBÃ|A+ |ho+hzª/ãzOÖåÄ:Oöã£Aü@ÖäAr:©@å£CAbÃœ@Ã,:CÖ>ABAß©/Ãrr:ü,åÜ/î:ãh©Ä£vr<ßðö:_,@Z<<£ãr@aüz©Oo¢OabßöªýBÃœ,ßß*ä@ÃÖ£b/C,ZÃœbUzCÃ.üBbä£ð墪Ü_OÄBãaä©>u_aCÄ>ÜÄzÖÄZä,*/ÃÜÖ/h*,ZZhßuh _ßßýOÃC<üo@ÜÜÃro|ã/äåo*ßvC_bCßý~/Öü_Ã¥*<_ßý@~î*~Ãbh|ýßv:,:aÃbob+oOö aÄ©vu*Ã¥bî£Öîî_ãýýUaÃœOz~båª/,ܪ+.öa|ý.ä*¢îr|ÖÃCß> b,îOooÄh~,ªð>ZZZ*öîüä|Cî+åüÖß+ÖßðbÄr:ußãäãã:ÃÄ.ðv<*Oð_<>výßZrözr£ßa/~bßChbBaão©*A|Ã¥+~uð:.>¢ð@¢ßðÃü*O£uAä/:Ö¢ãoßo:üObö¢ã*h*ã.O>BãUü>ÃzÃß_aðh:z¢vÃ<:|:ÖýbCýAz@,/vö©ß~O :hv+@ðÄÄåãÃ/üöÃö£bvî+ÄZ,Zo|©Oß@AB+azZbub*.Br|B/>@aaÄ vöÖäåB+*A@BzÃ*ªð.£|ãr|üªBßOÄbhOÜßÄß.v:.ýãÄzîaååÖß~B*:äCÃühÖbî£ZÃ¥ z>ý@O£üU~a>Ubªrö/î>bß ö~C/hÃ+bO./|Ã,îU£ðCCåä¢:î@_B<Ößã@ý_,,Ã>ZÃ¥>ßbß>ðßbrU|ðÄ/bü©_¢hvª/¢~AÄä>ãð.ãu,b_¢,ªÃbv@uðß_:/ÄrÃ¥hªå+:u:uö_* ob©B,ð+Ãœ>îObãz ,ÃÖoã+<Ãä*,Ãœ<ß_ã©¢Ã+bvbüzaC/o>£ªÖ_ßvðåOObärO,BuîÖZªîå~b/uzb~Ãœ:> rbä|rÃœrüa|©ha@+ A,.î:üzª ©aª._o>ü.ß+b ZüB£åzîð.ÄðÃ|,A~Ã¥uÖvu_Ãœ~_:.oo~ÃaUÃC:ßßBýÃ.UZÃÃýÜ<+_~ªßCÃr>B.îÃÖßaî>äv~ÃœbhÖ|ßz<åä<£ÃÜbC,/~,UzÄÃBö£.vßrh>åª<:äUZ/Ä¢UövAüÜî@bvÃ¥ <*:©ªOîuB© hßO|©_Ozö_©<Ããî©¢+Zhå¢_hhýr¢C_>_|ªCußrUüåöðßßåÄßÃ>vãß_:@Zb/© _O.bhîhÄÃå.CzUðÃÖ*vv+ð¢v£ÃAoªöÖÜ|£ Ö>Ö£ü~CaªãÄ>Z@ã_ÃO.ü¢|o,.äýCÖh£|©ðoOýÃZÃvða/ðuÖ¢Ü,bªo©Är**UB~ýU:roã++£_aåÄÄÃ_Äuäzý<ýã_ý£houUh..zßßäüBªBo@ÖßUß,à @Uð>| CðÃ<Ã+Ãœ/_:ý|A£åðh<©Z:UªUäAÃœ/ß OvUßÃ:hÃœ@v<*h/*ðbbåðªüu./U >¢£ª ªuZÄzuö/ß*ö.ßCBoåðýÄ|åÄrbã*/h¢oå©hÃîo£¢ßC/CUaߪ ooªÃ£îÖÃ@b£:ä>zOrbé,rýUzO~CÃœh:~+@îhÃœ:zªåvaC©UähZÃœ:O~ßü,Aß Ãœ@vü+ÃÄÖhOãBCBÃ¥@/Ov|@oß>ÄÃvý~|@CüC_bªZ£+ü<Äü:à BÄv@uÖuߪÄÜv©:ÄO|£ÄCý_ßðo@î~A/ãAA*Ovzbz*ÖZð+ßAüîUzh Ã|*|ð**Ãã¢ßoåÖu Z:ä@ *+ê/CC|ý*ÃZ>ZÖuUhäZbÃ¥+:Öz©BåßÃãUrãöbZî+_Z©>ðv*©äÃ:>©ß/Ã¥Cªb öÃ_ßz@ö:B|Oöu<ÃÃœrÃ¥_<öaUh*rO/Ööã/u~/ãÃär,zî|z|>jkl©ÃUb~+ð:,:B*@ÄoÄÖãAÃ¥A*©oob:ö~ãða bvª*¢r@, .CAßr.~᚛hr¢*|ð/ <@ߪo*O¢åC,B:@<Äö|C+äb+ÃœbZäh:ðA/rÃ¥AÃ¥@ßöâ|@:.ÃO¢Äð,aßb:.hA@Ã¥BzÃœv¢ußuÃ>ßhZüöÖCzoA:vUb.ãrhZðÄÜ_ZozäO/Böðh+Ã¥ÃvUuhäuÃ~AßbÃãßA©ðßÜA~aߣåðåbb@häoÃ~üðob@Ãr|oubÃÜÖZý¢Ü:,Ö~Uýß@ßvz| @*îhC+:Ä£A|O.a@u~zä>:ýO> C©©ýîöUªå_,åªo¢obß+O.ßýZzuü:bÃ¥_O,ßaÃßÄÖBz~_:h ühU ,oß îzÃ+éüß_aar>ßoð+*bvåãv_oüUãuÃ<++båÄa.Abh>A@:öãoüzöbb_/bAhuÄ/h~ß@¢ß/î:ðü ah+ßÖbrhbäîÜ/zã¢ß©äOz¢ýðraãC.U>,ýÄb.bvZbA~*v©bB ýßBÃœbßA+h/ªÃUbÄã*>ßÄu.__,ßoÄZÃUoî@hªoà OÃÃZ¢©_ãöå@îOU+B|U_,v£ B,O/ãÄöBâoªA@hðåãöußÖßbð@/~*ª b_+ªå/UÃCaÜãva¢*|. ©_z/> ~C>zZ*ÃbzBb©ðv<£oãubð_bZOäoAöÜÖCÃ¥vZ Ã,ã£AÖüî|©£oäa,uaoð,vî/ävZ>v./z¢/|ªåýOÄB¢<£.u:Ã¥>¢ur|rhbb*ᎄ.>~hÄ|hªB¢uß:ßÖÜva¢ß.ý¢rUÖ.z<|@O:ãÃhî>Ä© v.aý+B+,ZÃœ*oð:.ä,îöhßh_BA+ªh¢åÖzü:h~¢rä£AîOßOA_+vv>ªÖ@>Cåîöa>uÃœ.abr:zßuObzCîüÃðaßuÃ¥*+o_/üAÄÃvãoßhÜÜhãbb/Az|:.~ohýðOv*.Aä>Ö>ZC>ðrhzÃœOüßOÖo/zbªBA+ä~Ö £å/ÄÃð£B|Ãœ _,*zCßüoOÖ/BðCüU,Ãh.@Ã~|väb:fghj+BÖ©vßð_î/|~uß@ªÜ©U*/+ßßßßbUÃœ@CA©UÄß*Zý~ß~@Boa : /©+îÃÖaZöä:Ö©Ü+ @îübÄU>é_ãäãß:~î äÃhö>Ä_..@ßhßhÖ£B+,¢rOÃUOîo|ZbaOÖ:@ª,ü<+Oßboã,AÃüaÄBOZC©AîÖ~_ýh+@£++ªoUhÃ¥A/+B_.Üå/~v*îa< ã|>råßrÃÄ¢OA©CßÜ~rhãÃ~<<üÄC,äa¢£îAÖªÖðauUÃ,îuÖrzß>ÜÃä¢~vbauoÄßßÃa_C¢+ra£rðªAO~vu>ß~+Uðßv:<@ÃÃã>/åÃåâ~öªÖa£ðu/:BÖäOå©vü*/Oo<Ã~~ðãýåh/äUî,Aý©r~r/UßðÜ.bðuhC>ã,ªßO.Ãbu|z£åªv>£ZZoãüÄuî£ß@Ãœb©ýz+,@Ãœußv:@üåðoAOh|Zöäü£åÃ,î@ªrZOî @rÖÄ.:AÄ ýßZê£+rÃœÃh*Ä@ã, Z©Uußüªß@ü+ää<ãU åäBoðöãb,Orüß©ð Bo*:BräÃðB©a*ðUZB,£üZo+Ä.o.ß,äÄZ:@Ãœ <ßUÃa.UãvßÄ<ðîä.AöbðÜ£AðßbZb+,|ãoðßzhüüåð_/~/ðå>ýzv><ã_ãÖzîöCO:Ã¥vÃ¥ ýb|vbüoaUr/>äoßbbCb+z,ý_ãbaÖÃb|z|ªå..Ar@zßZ¢ ßAß B,Öu@aªBA/aü,vo£CðvZ+©Að_bö/*ãÃ:_>Ãß.ýÃü¢<äAåÃabZ/Öühðü+ahvÄîhðbÄzª_+ßZÃa<.oåªZ¢u:ÃU_hOý uãCor|b.åÃv/uuÖð<ß<>üUüÃßüÄhÃ¥*v_äU+ßa:/üã@Zu|ðh_ýb ühýUð<*b£ä_.Z+C£:üÃCî>/<_+oÃœ+Cãßöh:ßãZ+Öuäß,Bî©Ã¢ã©|Uö/<Ä>Ã_z|Ã_aÃß b>üvb,ß©hAßå.OCÖO::ðO:~Ãœ_uÃ¥,£arr¢ß<ðZZözO/r|a|uüO¢rüAÃ¥o~ðrÄß@.ACã¢U¢ÄO:Ã¥@oÖÃUaÜýýîuaßUoÖ£åaB~hßÖ.BÃ+zªvýOz:Ã¥C.*î/Ãߪraz~:>üßO+z++a oZ,Ã,¢uã>ÃœZ¢U_B£ãzO|.Ãœhðߣ:ßßab>CBOªbA/ýüOßßoäÄZbÄ:br:ð+o:Ã¥ÃO.a<ÄÜ@o*>ªßäO~uü ÜððoÃC|az©Oß:~oÄß|o*B+*|ð~B£raäÃ.ãÃüßa©îðÄ<äý_bA£o+ab¢u,~+uoaîßZß<,ªîrðßåv/,Ã¥>~,_býb£ã¢ÜªZ,/Ãœ@Ã¥aîrb>h/C:U~hßrß>bÜãªöðãöäªý¢ß>a++.>Ã/BUCö@ßbÄß~ßÜz£ðîöbo|B¢@/>£Ö~Ãœ/Ãœ ߣê>ý,ð~UbArã>ãb:/* C :ÄÃ:Ã¥b<ö+ß:ö©ö .öÃuýä|>u£oî> a/ýÃzuåß_@äBZÃrÃîãîÃA<©Bh<Ã¥u:ßÄoü¢*,:v+Ä/ª.o*Ã¥|Ã¥Z<Ö aÖZ@Üî_ã*©*ÄßÄC©*ßZ¢:OZ/b>Ãvîaðüb,ou ߢ|Ä_+ÃœUÃ+£Üî@ Ãœ> +oý¢.OÃ: b,+C|bUb~£/ubO~z/,ÄUªåbãZÜöZê.å¢CÃœaöbAðöZ/A ã*boÖbßürÄo @ßßCö,üu|~ö,|+**ßz :A@:/îhð_¢ð..¢©.Uußýî<ßÜU,©~,Ä/oî+Äza++uß:.ª¢O_ÖåöCCý /üuÃubU__ãOÖ>Að+,åä~o/brhbãîÃ/~zO~öÃBvh üZî~.åÃ>ã@|uv,oýîaªOr ãBUÃ¥|ðBOvCÃ_ªå@<ßv+*bOÃ¥*bÃœ.üßb©üC£/ß @zÖ.£U/>ÄÜZ:.u*ßãZ©uåãß@C*r:Ãœ.ü:Äovöªö,ð:ä aC@.ã oZ*Ö~:Ã@UZZbüÄzßãÃvü,rååÖÜa>Ãœ,ªö*hªh¢ÃhÄ|/*©BäýÖO¢bäahO~>öCoÃh@|ÄoÖä_u¢:/b©Üð©/äÖC~Ãœu v+öäªð>~*êhßÖ£uOª Öî<~ãÖCîö_öüååâ£|+Ä+ÃhöOßå©u_ðý:.:îß:*@îÜb o__ö¢OüZÖäCv/o£+ö,îUOßhãåßo .Ä<:ýäabh@o.©¢ba~+ßã¢<_ZÃ_zzhrÃväo~~:*@| h>ÖAbýOî|ð>azÄðOOÃœ:_Ãðöãå|@b<|uäU@<üÜU£_C,|vrv|:£.v~ba¢u|ðh<:h.ýÄ£Ößuö,ðhî.Z*©¢¢ab,bZ£ÜUÃ¥v.äCoBýý~|_@ÖuÃA :O~ äåZ@<,Ö*ÃbÃ¥@îü_@ßüß/zÃœCBCUãÃbör~£|,_|~©ÄU£å>©ýãvo>><+oaBÃœr.u_bZÃäZÃ¥*|aöuZBO©îª/C,~@_:Äu<_zÖ+O/OO*h¢+C>Ão_ZOvý*roÃœZãö_ÖZhb,ßvU~bãðýzÖýbýîî+ýo_öÖvr<:aöO¢>o£/Ã¥~©O<ãÄåOßbUZabC¢©z@ÃübÃ|ÄÃO.>.<@rB~|@ãärª~/hUoaäbýßãhÖð@ö¢ÜC@:©,~ÃBî>_Bß/zb,ª~z_Ä *>~£+/Ob¢åbÖÃoîb|@bC+_îa,ã©@î* u,rBoßö*£Ã/ðrÃâî<|Aß<ªöߪ~U_Ü£hv|ã+>bÃUªo/+ö¢ÃZÄuÃœb* @ÖÜUz|uÃßåbZ£¢åzCãà .¢AUaOðZ_ ýªßöbBªUÖOaî*_Z£ýb,,BözaîîÄ@UC£ðo>Ã¥<>Ã~ßÃåb|z>Z<ö+räîhubZÃœAo+bý£_bÖä©ßoZ<ªÄräÖÃ,ªu~åîÜAavãOÄ£/.¢å.AUÖ/ßCrð+z/AO_b¢_oÃœZ*B*ÄÖߪý*ÖA:Ö* o+OýÜ¢höü_Ö/v¢*.üåÄb¢U@îßAA_>Czððää<ß@a£ro,oý+ *a*b|AZÄîãZoêª:", - "@.Öä~ð*:Aª îCÃ¥:,åÜ*~a|~Cz>ÃðüöC/Ob|@üZß<+üo, åßßb©b Chbb_o@ru/îÃA ð/~ýåuA,OÃœ.ÄåäZäÜzAa>üßÖªvö*|ruÄ~ÃAßö¢ß+ªv| Z vö,Ãb+/bBýã¢.£Cî,ZãOã:Ä©Oª", - "ã+ßZC©oa.a~+Z_ªo+Ã¥C£ /,zäBüÄðßr:ö.@v|*Z_êCêßu©>v:/ÃÃÃ@UoÄaÄ", - null, - "2705-07-09 05:25:19.800", - "2062-11-09 17:05:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("0.2643", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - null, - -1.79E+308, - 1, - "9223372036854775807", - -54290834, - 7088, - 128, - 1, - array(("E67CD00C1929F775AE525AC74900F156D39556047D95E9AF5A62E48C3F910956E76F38DBEFAFE500EAA8E60464EF10B45BA1DDB20EF506519221B1B6456B351C4903E0DAC7166724B0561AD5AEE54B6B522F319DB45648F168E748CDFD2FBB7E72C9BB600F35ED443AB902B8DB30A2ADD64564153A9868F2BBEE72DFCD9926809A9B63E52AEA5C1C41EC63FA538F1538459BF7B7CBA544DA60C8A520C30D77A092821D40CDBC83EBE948D44E346E9F2BD83A9A32A26FB5F5578884613C583474"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("A02E3CF0F890D26C16E7B9C5C802FA491A9504DB381790D59EBEB08F2345A9D55F182F03F2422DEB571BD4AB3209AB6E8A3EA27D842B83899D67513AE04249D13DE366AFD2F656D86718841D42E4A294134105E5A22B503395A5658AF0F30B14D2036D4EA3FB9C93E7CA166CA66647B9C4BC279E3D642F1293331A2C90CBE8740C704A3E7754F117A572FDD815E38D2E5EACC533CB6DD76ED45BBB6FE06F021BE1A160361B084170CA0FF9CB6656FCAFA6ECCEE406B4A89F99DC6A1B99813736"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "Av++|Ü¢b£>*ýîAÖ/£zªßu<>ÃZ*Äaý bªZZ~b*î*© |C*¢aäbÖî*~öö¢bª<ßub¢b .Öâ:ÃBÖöö:©+obv>¢vBý<©*©>:äBh~Ã>Üðå U¢bß~¢a<öÄ//@Ã/*ü öB*ööuu£ß*Öo~UOöUarZ,ä©äÄßa/~.h*@:ýÜýz©ä~öߣÄßCrv~b@£ßä//zoýðäª_Bßýv:/ÃÃÃ@UoÄaÄ", - "@.Öäü uovÄåZßUÃ_/Ä:./@>|+ß|ü|hýîzã©Ãä,>ÄöÜOäö|bzrZÄhßCO@:Öã*Ãà h>rOö~A©buÃ<£ £ªßÜvÖv.ãbO©hUC_hAr/A+Ö+BÄO¢£ðB£@rßhA~.übãCaZ/ß|>BÄvrZZä:©_~£Öðý_oð/ü@_ª", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("0.2643", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - null, - ); -// 7 -$values[] = array(array(("E37D2DBAFDC4A6EA81F3B310E32028A4897E78DF620D1F1400E1A6F3B62AAAAEEBEA3BDF65A4DDFE99962161DE684EFC42BDFA2231375ED55D19BA1C1DB23357490525F9E4483FE5A07A45CCECBE359892AAB1F82F887CB38B66814CEAC1E1E762C918D9435517F97E1581B7F192E89FE3228972003B27C088716D7EC6F2FB0F"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("351C9216E59D647073E1F1E1ECDFB235CBC13596DD47E0D9DF9AB4AD56E040C535BAFC99CF24E46C91A19B18A106B56D82DCE0B3D93581ED3CB9374BE8B61992A12CAC32E21866A6DDC8043BACC737D9E4472B7AFF8CEBFF380F5D15ED9884DA8708777559838A69763AD51030454175815E6648146E02"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("C1F2457978DA9D517574256E00773BAEC5F014225CB4EB7BDF438C1F0705B67C28B3E7E10440001E373E9BF688B4A95751994E7DEDC8C60D07CA99354E7D496FBEA8454C8F5AAA84F0F5EB1D18F5F22B9934CE6EB7388E60ECDADA3AA7CAD0BAF52C38DF6E5CC7F6EFCA8DF8DDA8F411D76E9B19AEDBB8851AE55FB5806FDD26243028ECBC13BC3D6DD77FEDAB968244E6C862382EB70B1A73B3C589FD12767FEC67D3AC800755C4B15EABD75CCBD984621BCEB70DA5F76D250E79094D33405B60FF0044C3D6C3DE729E86D00C34BEA7DA4126E143BF71476F6586728E4C767E1E50765CCA94C08B57F0446F1E2761F14FD12E0C0C5E18A18DB9D55D505150262708B1BCBD61DD45809A89527A022948049422BC1D6972BF37D4CE11BF633B069F1E471575A533EEF4E88C71D4C5278B908C58BB2E9309BD96F184E993CB861C5F82B35CBB13D25F6134C834DD00EA72AC4381320A80D70C15897E83204A5A28616F125212CBCF7CBAF959FACBD4E09506113C4F4A5412DEAAA64731944E3E16D99FD26DEE9860E17304781126DD4040A9E87D0E0D0ADD0D5D34A76B849EE76BBA43202B0172A76DB15BDC45DC01B7CD660688ABD12F08CEC2C510B9F1F45CD51D0C56E3BD153E36410F5CB22E97305C7CD9057D153B998A3A51693C61ED7CBCFE0728CFAAD31050C8516AC2D356A02A94B43D17EFDEBFE273CBA86BAC01D553939FB3276F61B1A8C9B4B5C4D0D7B7E4B03EA8ADB064CD2047465CDE2AA4A4C67C172E33A279BA3202944E3A28FA119B14C4BA6D5927A22FD293DF2905D27E2510EF48B8A98442378475AA1C1592F74D34C1CA66E9366C5475FF600E9A912D2F16282A39B258F28F8183E9E47D8619409C92F44408173BDA82F443A924349CF3477A06ED67BD9BDCCE3AE4664D88D6EEE62071089CE927FEED50B6BD14486B4DE6F69C02802CD8DEF71875CD32D58CF91D54C304C394565D5207FF1707DDC785CF977F57ECD36EBA47921184745ACC18C3BDA0BF85678A519F3EB0782CC0D71951EFFD9A55350D49E608BC3083D2E9F83BE754493C0700680D2EB7AD23F68D87BAF732855C31C38DB057D97CAA59CDCC55B73D456C8EDD49EF958D277D5F25EF7B34758C9F649C0924C699E52D83BDB360EA586CB8481A2C1AE895C7A49C0F5031E90175E4412DF11B3320A6F4F71C6BD7D9C69307B837321A4872B6CED46588B649B4438247CF1EBD66518427743822CC2AEFCBD57C4978B2CF5B9BCF762346CE98EFAB43D8E98F09A8D5A067B20AA50F4FEAA4E599B239FF3B91B3EB334F47430BB58F858DDFF34060C12DA7A811FFC4B68B808B7F6034E6B5AB074736A3D5588D5D5BFC7B20826FF66385D88592572991EE35A7CFBBC4AA81A9CFE1D8AC09BCEBD4194BB60348960910941BDB5C602353DDB5476E2E89D298B07BD250A0964A59C8ECB1790C738EA59E9EBE26A8CE2448AC8EFAF6C24DBD4A47B205877646687A4AC2136FAFAA476A27A1CF8FCBABEFE2568AA4129C53977A2F6632596E062F583644DCE6BBB59902468FCF207E2E7F9BC22331ECA155DDF62A983F4877EDFC2072765887CF0926A1F0A1CC0C8477FC01E796974BD543B7E166E5BC2375508A5993AEAE08D5F7CCB9FC8FFB0088D072B39C3E61E156B14A2EBF7720E94D032BA0177379D5C9F42BEFC9DE4E4548B7589572088636EEA803E1647F485ED7E26CF8645AE815579DEAA7137217776204E5081E8B76447CF4B26E70977149ABCC18E67256E1EECE423ED51EDE237C918B7F5F9A40404C6C277B6FA77AA5C02CA0FC465A30050C23F639DB4CA1259ED036DDE41711AF6A8CCC7A4ADA7146549D7EA87EE2DD226085733BA30664FFCA01DD9F719CC0A81AF192B98DB6BA31B1B0E53ECEF8A7BBF3147D5FA4D50B6328CCB049FDD2D4B7DCD58E8D502494AACA428AF3212530D15921AB5D1F610345E52E90C0057133DFDB2D62F86A1A6BCF7FD294624B1841039CDEEFDC2676987AD248A72193D3A4590EAA17F4C237673FB5DCDE4CCDA0F9CCEC8D24278257B86494E82621724A3BAC2E3B54A0014D406477AF264A984BAC314FDB3406AF7DD6B6F2B35FDA1145CC6BA1321E924C58CB9F4DC0AFD2306ECB493BF4D2971535FCB1649DAE4EED655232E2BBA9662D1AE5F60F95B9EECD038583E7169F1D7235556B356C2DAEAA7DB1E76D7DEF75C5651B200EDC87807C0DE360172B66D7C634BFAD653D31C0ED364C92E4D181501310BE82FA7E0F96ADD5EE2788CC977F16D92038BD420F87335A33D790F49BAA8C934C076CE4DD47252900904E0A38209FCD7C0AB14893D100364E9F4B3706B839449AF57F8F1231FCD81293427F3929221337C37890F943007B0636953DFCEE2347560A6F42A00FBA4A0ED835FF8F80FD257C37FF433FA84B56F50488DB9BBEDC2470FA282FB9E07327082E82200D00EB853970C0C4EEE007ADBF184D9B87DFAE50FC118F9457CD6B9CC4D73A11262A49F41902A22BFD17358FF37B70A092D7A029803124FC71562E6B0423FF17A5F68794CF5882843C8352E4DBFFA1DC5DCB09ED361191F4054C83C2D43626D9171EE9FDF4DBD03A551CECFDDF402071B5510D391A12E09365928F82D5BFAF055C78CCBD15A4E882FACF9719259315C52C110D8329EDBF053E5697614B4D64956DE6A06673625B10688A3E4A7AC66ED17CC956C4B22BDC6EB9C77FBF0C8637AC6B02AC3EF2AE52C9B5FC1A9EAE758A77E7C6E481F7A0E2410A32ADABE39C04E25392DF78192FDECE778C0EB6113DC0443D07D746A1B5B5CA1F6A34C001A7E619F7384B1B95115DB8E7160A6A912C30CC71696DDCC4BD409EDD1459AFFA1DCDD0A6F4BED9F2A4AE2CA91F5E8BAA4A38F339684F9739095539F7AC3E80B8AEBDCA493AC49D6D57D9CDBB4132A55FB5F393DC657592CE35969C9F84E145662D73FA61944170869B85E28E82DCF7D7B0DA41B9ABA452382BFB13A327FDE87593668DCD3844CB74830C40DF93034C599EC5EFAAA08CBA8920C135982DCC5F9D9D6CFA1B4F299E6B0FCD359E6373A527AFBA04610E3ED506AC03AD760E3246B6ADA342260930097426839AD2571C25AD1C0DE873C8FD8E5E97F36F4B7C285DDC877FF0C0307E1E7EB08F3A8BA302AF8FDDFF59004AD2EDB014E48F15C1BDF8FD26BE4BD72C657C9DEE69C238C203BA9444DF6D7DCE5C92B49140AB4CEE09BCF2973C64E5B7D8637882BC11E00FB5ED1029FBD1E2507B2FC92B9645EBB930AEE5ACCFA1869ED5DD6B7BF5FA124AB2A4DC529B23F338DAA21C0D9BB9209277BE520D2C333F136771541E65665A801C7E79F269991E0CBBACD22D8478031BBDD90436A7041E7F64C8EB29CFD5D374489BF01C402D29DEA045527F60128FEE6E38608C6223D81860FA8DA0AEF43DE5D95EE5E887E7DB8FAF259662B19E1E5239FCBB9674461124C2D8BB45A08930AB076DA6022DA667865CD1DC3C8D7666C2DB3B66B139A670924CC026AF2B95EF26F5EA5A44F81C2493953439A36AD5964C71F86964237E7260FC13B56C80B75BC7E1B547A31C22CABF16D21C056DCD28772AD93D332B8A037678F948E566B713DDD972C1B48DF32F4F2EAA4D51626DF32FEA3E42A679279B57B2D02A22D9E5595EBE3E2BFA0B74E936AAEE05813DB2140B0518FA28756D63B1D66A01BDC80C754850EE922341F92476540BFB8B221A1900BB1F1DC0EA30E950DA7A23A0722C6E9D78BC304E1FF1CA0CF484D36A71D88EBC500101A9EFB45E1F054248C6D58303279D5E1366E9CBE1CA5510CC665C7EDE02EC664F712AC5ADC35E1048DDA803377180ACCD13C528D385203653DFE8F7C2866345DA6E3492FDD7B04F07D882032151D95AE5B0B1CB3DC393C951F371CEDB664DC2796329C83CEDAEF5627520FB021C1501058D1111D4980716DB6A410966E273A08AC08ABE28101EF702BC68C7A298C735DE70C84D543BEDE05B12EDD1ACA58F80027F70419157D8F46F1A0F182F3F7443A30C2AF619264C4ED3A96216234D4B5C2DE8C5232785A38797D3DE3A2CD4B01C9EAA2EA07B9966DC33216EE8F4C1D121CFB3EB4779501B1D69A25E45471122FEF84678781EAAE09481E9EF0517BA92D502BC6B78E969A24D8F1037D83A219422FF70F0AE49580224D257C690611506AE1089438AA6AA148FA32BE90E7308595F55CD06A6CAE48CA9B947A592A312EB790989C8B1DE0A7B555B610A1D6EF4B1CAFD663A930E5FE32CFC99D05F0482EC913F7004EA3FA8814A51B14B69D96E3AC595DA953CD7586BAAA59CB445391C50D87479DEFEEFC78A54E5F0425D147A528E5E0BC929EDAFBAE7055A44A3F43C11346172F460903CD5CE4F4057E7160D962B4926E62E5326EBC5BEF6F99582E760A9DE0DFCA185F2FD2235FAD9AC58D3D1F7C4195ACE55E3E13A872FA19943404B68E0AE080811F5A55628C4F8584C3E6DEB2502D0698C9153FB2BC119565064DEF5AF71CCAACAC8E5CF7303A690EA88ED49C1B673416FE2DB4E5A0DDD50DB4F05C4E503A9DB71C2205D1C01F688EBC210A3D4D0949AF09D85CC957E4FBDEB95F323FEE2AAE7DE12D7FF9616246EEB526297FC397C0BBB6BD3487DDC46F3726272870DD38A091829C4C1F89503DA7917E1421BB8C1B118E868920E626A02CCE38C34A39C6018C8587FD375BA122667F9CF65FB03D7BAA0C06E256BF7FBB5BEC2F51409CF36DCB7C87AC339EE3C5ED5412B15930506C80CD193E22D3AADD05DC914B2C4FE1C97C2F2AE32891480B2840F5029CCE621B9BB2D08373B53AE7C1BA9656FD9899AD7259352A056D43FF751E15E1DEA840477A85BF796F5E55B6F443117DFAE11D8FB75790338938062E7EE10E65AF5909197A5EEC77E9103E13F37E25C24BD5F483F0862A87180CBE327F80FA8E4FF0179B39F7F39C0495A697197C400B0233D2A8EC8A65193E72016D90BA085825AFC403669A9853D2588F6AEF937D9BBCA3203BAE51A1C59AFE6CB7F695FB4A9D4E7169A84CA477AB8ED683FAFA399B0475A0FD89CCB9C40E0E68782DE05D3C5D34DD911203E3700EA8902F8B2DDA90B4C1A370E50DC0C094AD6BA9D43C59CB219A26B62C9889008230EEE13C8A48D033D7B35C3CEC575F740D0C7498499BD25ED4FDE0F714642C4892FEB41EDF840A549B760C78FD8E4542D7421F9C047848951C82679C67E91F63971399FD9330947902100CC39F596146A8AE2FD25490342AA61F025D2C6DFCA26698E1D9F457AFA6AD9699F47B74F5B31120E3C9B93D1F06DF7F47842E475C94235A6D0677975250FE8934BE064FBF21294F3C0B7A32F92979DAFA85002B786649F5D11117FF4B89CE92CB300861009FB0266ADF0B5119E061438C9C9CDD40D300CE64006E9E192A03A23CE767B91118DA5AC4E9C0995AF8EBE0BFC462AEFCF141F83F39FD0021120D30DB59142278A83263C23208671277737B248DEE44B764B53B957F89D71BDA39A2DC8FCDF1D4B9680791FEECBAE8EAD5C7C681DBA333C87966A3D88D092E5B47E808658A82A971966B25D6B2B887FA52BA99C20099FE866F1406575E0C2C6ED936A6F4B28350531E44EB707D6D05B0EEB985D26F6C4ABDF971F6767C42239A4EC6FD7C018347CF6A46A3CBAE8E09020E4578A47659FF7CEBF90B308CD9D6CC215121B66F89AF5B61B66828B88F0F6B68F47826D97F13F4C7709018880742906FE79A5D5C529274961D32CFF1D6C45CF99FD79861AFE2A24D3CEEEEF996D36A73C7B8D0F4A21F1651F380CC4C53F2F0BC331EE83FBA3ECFF4E8001F63A38B0F39C84C2A2DE3684780B1366E1F2655F939FE22B687FB3AC998E8DC0C91FFB2AE7881828407D8D2D8DAC7577E73D0A0DE9216D56580C4774E9D729135175BA5810E87E58D93D1E1611B59DBB093BC45B8480D7A8B1A4D2DA40A43E68547ADB17F21C67FFBEB133AB035744A26F8C6AAD13CD8FD09E12A76B15A41834FEB5C297D986C9ECD4C59DD24D2CA55A5F306BB43A915586EB0B78DF75FBEA1A99FEAC4D94ADC8E8C37DD84D2FBC83726D4C0CE43864FA6D5F3BACCE0DE976D8DA83384E7F57C6B26882F0BCB808572E88067229EF05E135043FED5800F9DBDA070072AB8434F31585597BB6733DB457E47D4606774CE9D5A20249406A6DB16FA616B5B895ABAA53135DA4958A2BC2C68A957383B3D463BA169D0981EBB2AA17A80C0F5D88FBC239D3F3F319A8534A774082B5D5FA2EBBD2B3FBA1F7A1296860B28CEC7D1B643E8FD239AFC0E14911C352924068FDD51D7A503DAA8C5ACB98AA8A92671C66CD5BDC432602C07EFBF069EADEA40FE8FF63DD6E722CDEBAB23407A104354EE1F45C85366424D6D25CFB544C40A27B2689C28B2F8AADF4A07C2191F1FB1E79C6A188F5811ED884DB8C0A1176D791790FB8E01842A91F574768E5B94292E5D73E828DABB8ADF907B03DAFA2809B7DC8EAA782381A06361D8231AD19391E4148A633D3F44AE6C161BED86705FA7CD6ACEA1CCF9A10ACBC374B70288700E0CE91FC67FB4CC7897E03E62079419E3122055A012F1D4FA637FA769CA8D9BE3A61DE0AD608938089E4619ECBB4F0337B98194E232CFEB0B042562F01D62BE0A545ABBE9A36C8298F84D01DCE7E84F147C6FB038F8B9A0D632E5994B2637EB3B980F88508329844D0F0BE9E5CE1E232BD64E83ADB27511AAA9E5B6CDC34762C061C634D85FFE8142EB6339ECCFCB0814C27D64E7B44D7912FDEDA6AD72B679C7A92983F7E94ACBE7BD30C24F4B34DADEDD920B45D28B0C245D11EA7247E3D024E96233A647003B77B18D2A837B29D7C912C7DFD0BB23E52E1D6FAAC750499FD9509D65D8F12497CD668394555088A9233A216F70040F79C57D1598E765641777ED5122EDC40EC7EE30026A379F9FCD94B6878D3EEA2FC55123905BEB9BB067F26C2C680F55BB5F3B59814001953225ED47F420DDE6E88DCD9BD1CA23A2EC428FAA77CB7337CE3F0B2851E568EFB3BBE17A7E93978F8A1B839EAA7383F13B887D3EC0672D76B2A589C525B5950324EDD825D8B93458AAF0105C47E48D5ABB09FCC9682A2F5C8E7AC7C909D269C4867069CC9B5E1872441483C046C12ECE182F0A1BC8CB9AFACE7B976DF16B4ED8964BD5C77AC0BF5CE18A7543A65F05CD4BF9A0EAC84E0BBAE57F0E2FC6AA70E8D5BB2A0AEB5753577FBD3A4E4647546F75CA2207CA640058302AE20BB29F9637099C7DFB250AD1F496496B1B13CD74AE1AAEA9FC88791E71AAD1CAC07557A4D016F334A9D9693A6AF82D6170F8F54472B643E113E599AE807DFC505330018F4EA49AF41088CAC4B1644F046B07AD88A59D581A216680EA8D44B43BDD08D73AE6AE22CC5DF123E9EB7F2FAFA6954388D073AAC5E7F75DD813A076DBD49EF7A89D041DBCE4E12AA4797E2EB236E143E7F13B6B596E1142FF404AA8BD8A48055C878712927786A3EF58F65C79C8AB91326C8EDAA9339E4A7BC12E7C9A387D2A2168D22ABFDEA1707A877C0067C4D926A4B976410B7D6C28E94C96D1491A19A6EBDC2B00F670E163CB0E6011B79DD2B6E807D7D0C22C1EB3FCA9417337D027507DC5148B99C83023C80285FD0D4A614B69EFE9414238D4BFCD77044D3F7E2BEAE70F93F279571D1FD7A418BFA621A6B247F60957E6D61E44E064CE22B3E2F459E69B05FDB67ED212D2C9799E1015199BC052053964C40400F14F021488EA6BE4BFCED90B1BB90BC913F6851D5C7034DB5B225261DDBAE52286F50C677CCD89E86D84EE514C0E8017563C156E4EF6B4711A26B956980A0704D9B67416BC945C566CACAFD791D38F4C924B0BA19586D390408B4D46043350638DD987958A796AB1EFCF01A246DCDF36A77CB44CFE656F52D2D3C621570652395C4C59C85BF9B228E049B1C675315963BC06AE977F0365186A34B2A6B3396AE97CC855FA8316EAADBA9B6CA4B3A8DDA18A52754067BACA48D7315E27370B75A6EED859BCEB6EE0951BF0DCD3A6C064C167112C50C41A81B1FE0D1351C8C376AF15C5C5EB4F10B62B71D02466EDE6A216DC8CB015EF14D2C44697BEEE4B8EDC587A2DCDE0FA8982466B00EEF3F61AD4175B0C753C0350C000E2F194A751D7DFE2F3DBECB028C11C530A79BADE35EEDE3BC3A7833F099045295DBCFE08E4DAF483210BF01A3E13EE99E0223199A5F1CB041368E3AA54A0D96396C3626BD3136E2F8527AD39EEC40DBFC8E94EE76EAB736BF1F9E58BF74A3E80229C836AB182C6DCB402147E4491255F5B23E16B51E1F4EB4FF679D92DD2383AE92D07423092FE462262235C97051750C48620C358D33E3097A81ABB08EDBEE1D9D9952799D32EC825AB38EFCEA8444ADF191497A174E205EEC6AA9878433D44B34182CD6FFFC6F167689DBDAA4E71FF63DFDBEFD9173054F78F38C328864FE26DF42C7D1576DAEF52133048D7246B9054BB34A8675D7BAC112DD5E0B703C8D118AB4FB62204D9ECF4FC8DB5D6F78DC313987E7D51A78B1078805BF7A0EA285E5A5F203BE4B57E4FD157A36ACFB5C529A002CD2E955B8F71203F3DA1A8E23C0AD43F6B87C751EE6E2A2EAC0A041050C2BB3247AE2D09C8FEDFAF0BA577DCA6EF1B9896805A4D524E246EF466BF757D0FDE7AE676AA8DEDCA666CB8284638E64D77BCEE6B89C0687DDFF989BE05524F5A320BD70CE630449DAACC19ED853A07EB78A6EEF29701760E027896A1043FC5141D5DE63A51D967477850B0CDA67AE8A5F19E355E635CC32095C90A7D9993897AF705D03743CD899F4FA77A3650B857878FE0BF65FA66A0C09AC5A498DEE9ADD4D735C6A0AAB1622414961ECBD29CCFED7172FDB8E4138090650FAC7E28F0C065F9956F1AE1B6ECCE8E8B515457D4EDFDF5C7176BFBF4E0B3B5263BAB14E3FB13D5B8D23D2DB4FBBA962F3A68B22C489021AE8861D9547A04B4AB65BB7B41B99A804D7EAEF2A0C040DB5CD5CCCA880188EDAB79766B993A813EDD88824F303F54759FD874AD999D70DE37968C8EAD3A2CA9ECDDDFC8333EF702FD6025284DF19B1D81572ABF9CDB440E08A83402BDF291935BE862626F8C3260D161B03968588035FB6EFD561F7D0D0189D1BF9B0AF454FADE91CC559F38BDF514C9DE3B2B1AA5B017FE075F053B83DEDB50E88DCCA765ED0CB56AD68C2AA6C2A1084145AED1E66F2C0F730CED05F5930DED8A4E5020287D63E12E65AFCF23ED5744AE0CFA3E30096A7961CD24590006BFC7C8AEA0587270DFD3E1BDB370CF6CEB95B59857F8A8DFAD80D3D6CE6B490AD127B5E806853CFED23D2D264C8245E9B9F4E19EA377AEB72EE8F12F849BA2748AE6DC8BCAE7665658B6C61ADB82832B3AFA2CD979BA676978FEC91D940D7A368AB89B65A5CA1ED29E8B35ECA183EB9C65C5F48A6ED8FAC08F6302DC1E48C24B4C8F9012E72D8560DB5A3AFE847AC7F5032CA7CE08EFD7B1CDDAD632B742B445B8EAA021F3C8A5383083BF14ADFC4C04AB1DC8F190F4325CB7DE551C4742B988A5E53599CAA5F14A777F8D8530B313C19EC1979989B2B4B4A8536A256398F0FDCA366EB8C6A24A052C8E7A2176BD2BF4CA937D75BB200EE33070E1773BDBD6DE7E9FBB0A445B0AEC87826D0B38834DFB37655AD02B7B32AA378B8845C0C2C950B7ABA2D70221A06F9890EE38B2F3AAAD22F64FC670F6B3036D00C2C8C2B52BC4B91C439CD076E905B4FB478A9D09E733D0DB12CA72F2DFB0E8AF79F15EE79CD375297F96A01DDCBC1D3A0185BF286653199EF7F7A25141B64C92B430FCE7B6DD5E6415739A4083C30E56BD48F15A01B03916E7BFEFA53113D7B95A1D59ABBF258334374B12F2C8D91B34E94B9408C4E280F3F7E60FF6FDA932F19BD0E6666962A797B5BD22C6AD364E1EA4BACBE58FCEE4778C65A3D9258C368E081166E7E2A4DB86A40C4307CFCA56B197F15242ED4D71CCFC7EE9F3BECE72397694BE8BDF662C0FDF3541738BDCA7C20EF2312429F3A0CA9F99FB63CAD7C7D59A09913B1BCAD231C54263A7FC0E412FD717E4BC9D4E0F82E3F3EC457AC844F76D7BE9724B7CB6441FFF6EE79FF8CE9158CB9CA2E31B384C0A0E4717DE9CC6FD0D6C3A7014DED45A61A09C9DEF27F2FB255AFCAF3B920452439D683D259E65833861DA23BE4E7C79E73241427E9A6CD21B5BE283A027C17B57E83C8766A724C34B19658D5DCF3CE6DE72B80950C1E3A9209611D9ABD1D300906ECBD601F513400D7DF75534EF844C1F0BB53D6FC81A95F49366D0855B27693654ECFF0356D4D95F77FBE2CA8E644101C5BA901CA5A0D84425D16E9E42BAB2469E2ED0029548C836EBDDF7CC37CB763F25CECF90875C80B404AADD85664B0CBD499C3C6F843E7C8CF26930D7A91C1C6A6B3F7883FB11CF319E2BFDEA6B32C090C14A729B9F06AAE75232AE1E1BB91AF89E731A36D288C237DCFAD168408211BFA8A0066E9970CB64948131B873A125D511055BF81889BE38884ACE94254296C020EE757BB2AC5F2DA7B5A2415709C42BD50C573B3619A71EE1CDE65EA2373F41FA6197853BAA1264A63E4052E76CE17E8692BF28FB5B9A07475689F4C6007B60865056F5723EF02AE9E294F7D9443784DC67D80F8816569EC7804DD5202D9E95B2EFDC9715FC0C47A2843F08C6923DF32F05186E387BE86FF9C82DE1F11B0ABC53FF44F967906EE06B806CF3D74DD825424AB62B15306D46F76C9D4DCE1A56878982B4C75D449B8E9627F2B80EF498ABFFF8AFA664EFA713920DF1FB0321225139E57A449900D80DAF3784FD4DB53802D3E15DC57AA02299DBA1354F42A08B74F5917E297A4412FF2092072D5AB9D5ECC5FF2711C36CE699B2D078F7CB98BB0EBC1FF7AC371E888FDE6EA4D8A8F54F21CB5889537969B198BAD466A9DB2B2397D6F8714AB229A5A776A74354B80DF0CD81472C730F6785526E5EE152B7DEA4A34B1D01A074046F91B2D1ECD2165CD0ACE3991883FEB0887F632EDC634842FF97F29019AB0C40598CE78AB03D8AB33C018E447F2EB610B48A84924D45280C596FE244DF764325A0934F9BAC73F04AE4BFF2DAD503D3C0D1D1EB8F167A030464DB21505B35D3C5CE5893DBEF2A74309A49AD8D525D27559C1172FD8AB41F26903B36C142AFFFBEEC2D0C8C6FF7E1A76DBAF4DCDC85B207B3CBB439B2D63B0A497D4BA3F3A2EDEA49FEA12540AAF5A42FCB9FDFCF325A1A892F1D86FBB5FC05E20232E2BC4DB3BACD0A92EDD48CC708D61009E7466B4B0723212AD49566FE83F29B333E6802E7E217D512B4AF7C35FC5804A0DBAC04CF86C73BCF8B12EF25561C0EEB494988A0C33827DF61588667A6FB3E7C80C612D7EDCBBC773"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "Oᄎ*aUAZb bv@<Ã¥~üÄa£uUoÜåÃb:>ZauaÃßîUhhýå*/öÃÃœu Zª@Ã¥ZAö|Äöî+Ãœ<+îýOÃ¥ZA,ÃœAoÄu/ C~h~BAå¢Bã©ÃÃ>Ã,zB ö£Ã/aU,z_u_~bãÖ£îÖÜ©:>Zî*u~ Ã¥b£ã¢î>©.oh ÃÃ*,_U¢ãðäߢ@.Ã¥/+ßbß©b.:~Ãöh*", - ",rîAAîðÄÄ,¢BÄh~ã¢UÖU,¢aý+©~¢OvovC©_u©Ba/|Ã¥B|~¢z+Üü£Äðå.Örð£* z_/U+ýA~+o@uz©@_/.Ö+rãr¢/ UvÃœAÖ/£Cv £ r£vß.ßC<¢rîuÃœ@îýC: ßßAÃÜß*.B|O.OÃ¥BrCB./,>öÄA*|UU_©aßaã_oî<ýb,Að/ßÄ ÃzÜåÃað ã Ä.UBO_îOªOb|Ar¢/+a:AvU£u|ðÖßzßb@ÃãrbÃüu>avr@ZCv~vv£ðäßb:ÃœA@UÃœCäßC@Ã¥ra_Ã,ßöaýbCð/", - "Uðz£öýAu.ßßßÖÃbZuäîÄbU©r+A©~ýZAOÃœr~ö îuã~î>Zßöý¢hbåßu©CavCZð /UCߪa_oCö £bhoÃ+,.AA:ð,uöCb|Ãrªz@h£,>Oð/*äu<~_.ããbß~hZß<Ã.uo*©ð@Aüåª*äߪ_:ðA>,zÖ|<Ã¥@ä z¢vüð ãoÃðÃ:¢©ß<ýurb©+Öý*B:~äÜ.©Ã¢~üBåîvÃZUBßð©bÖýîZ©Z£öäv¢ãäUZî@AÖýBã,B ð*,C©o..+ãäåCa.ü£Cßvb,:ý*|ÃœOÃýîªßöÃövßOCu.>,.a,A*a/Ã|ãv@Ã¥,ÃœB+UÃ+ðvbov<ÄßðÄA£îb~ OÖßÃ|OAüÃrbäbBÃü©C*>ä<|rz@öC,Z/:Öö£@OÃœ öÄÜüÖ@öÃZo©<,,+C>|ÃäBzZ:BZrO/@Äa.oov¢+h*,<ð>ýߪßîuî+ßîU¢/zÃ¥: ÃœzÖ:£bUUCð@ß <©aªOob*Ö*~ßÃÖö>/¢U©ßªÜü@BhC/| ÄZo©Ö ä/uÄh@ ß:<ä,ÄbZvo>äîbbbAöîOAä@~BOo|rAüÃb/obªvÄÃrüörðãa,+Öå/@b£z<|ý:ä¢Ãu,ý+b £ðÖUÃöÄAu:bzö@aü,Ã*bßÜ_Ãb¢~UÃßö:öbÃãhrÃœCövUCîhðBÄUýzaý>Ã¥+aOB+>öÜzã@huÄh+Ã+ß©¢rzO*©C¢BÃÃüÃ~©Aö>.z<*ßî*©ubv<öC|ß|::U.~ßaB© />COª.Oav<ߢýAvv@~ªO£:Uo|ðOBå£bÖCZzÖß:uo v/<<Ãü,vCAÄBOÃœA_uaÜ©åößãܪ*£ö@Zr*ABbo¢>öboÃœ~oåîUoÃ_aÜß_ov Ã¥zOÃœ~£>åäB+ä*ª@ᦎOr©åÄC>výv._<ðßãÃOÖZ~hBZaßäz ð>ürãýb£aªbvz©u:zª:b>ä bzCAC,äåvOðbÃœ:*¢öÃrÃ|ÃAZ:äßÄr.UãboîîÃööUhÄßb,¢/aüü> zhÖ+Zã~U~o©äöBý¢|/îýÜö~äåöåã.ßÃuîð@ð,@¢å+Ä£ýAöhz£ß/@b@_+_î>*öÖßÜ//üa|buAßa¢bZªßðß~Oª+ro+,o, öä.hýîv©äå¢ä@üU:ÄOBà O*_ß>ð|öÄî@O@£h_+C~©/ª>ãåbB.a¢h ßaÖ@Ã¥ >üÃÄoýB©azBî b>", - "~ãÃö/ãoðbÃœr+>墩b¢¢üÄ/@ÃrbÃœzBzbßÜCO©O,bC_Ãœbrîz>.£|>/ð*zãî_*ßäa¢ðÄöA*u££bÃöz>>ÃhÄ@âä,¢UÃœaCãäÃAüÖb<Ã¥/êZað.Ãýbß*r.£ZC,Ö@BÃU*CßÜ+£ü|.AßÃað:¢öýîvÃOÖ£:AÃä+ ößÖ/vorhAbA<,z,Ä¢+£Öß_r.>o*o ðBrbBað~£z.ßößUvÃœ:vÃ,*rª@,ª öBßbßÄoðZzu+ßbÃ*", - "Bªh@Bzz>b|ªB*+ã:,U._oö./AýUuZîCa_ã/*Ã<¢ZÃœA£îªOÃ:ãÖ£O~~:oOÃ*aÃ|:ã*ä©ã¢ävÃÃðO*£üah_üäabãA ß:|ßbA¢a,ãü£o.Bo>Bvß>~:ðAhZbýBÖª Ã/î©î¢rZî¢bZ~oãÄ<üOÄ@z/CO:äî@B:zýa/*Aßß+U,b©ß,Ä Ö©UC|åß.ýßZauaÃßîUhhýå*/öÃÃœu Zª@Ã¥ZAö|Äöî+Ãœ<+îýOÃ¥ZA,ÃœAoÄu/ C~h~BAå¢Bã©ÃÃ>Ã,zB ö£Ã/aU,z_u_~bãÖ£îÖÜ©:>Zî*u~ Ã¥b£ã¢î>©.oh ÃÃ*,_U¢ãðäߢ@.Ã¥/+ßbß©b.:~Ãöh*buâ:üÃoÃ¥*B~Ã.Ava uabz¢b*©¢zåßäz<>ã¢bab/ov<:ýrð£,O|*ÃCZܪ©vÃœ .Zb<ßBu|,raUÜäßßOBhÃýðÃv>~*rbA,ÃZBÃý/ürCÃbýý_Cöv>ö/|Cb*>~ßürÃA>+ßvö©", - ",rîAAîðÄÄ,¢BÄh~ã¢UÖU,¢aý+©~¢OvovC©_u©Ba/|Ã¥B|~¢z+Üü£Äðå.Örð£* z_/U+ýA~+o@uz©@_/.Ö+rãr¢/ UvÃœAÖ/£Cv £ r£vß.ßC<¢rîuÃœ@îýC: ßßAÃÜß*.B|O.OÃ¥BrCB./,>öÄA*|UU_©aßaã_oî<ýb,Að/ßÄ ÃzÜåÃað ã Ä.UBO_îOªOb|Ar¢/+a:AvU£u|ðÖßzßb@ÃãrbÃüu>avr@ZCv~vv£ðäßb:ÃœA@UÃœCäßC@Ã¥ra_Ã,ßöaýbCð/£O@bhh£äÖÃZÄ:üÃî,,.ÃœUb~Ä|vhðßb.©Cbªª~ߣüåa:vvýößuBª£Ãor~ªß<Ã¥aÃbÃz¢~+*h ~Ã/Ã¥Uªz:~zzÃÃ<ÖüüåaýÜ ÜüoðÖü¢,~墩b¢¢üÄ/@ÃrbÃœzBzbßÜCO©O,bC_Ãœbrîz>.£|>/ð*zãî_*ßäa¢ðÄöA*u££bÃöz>>ÃhÄ@âä,¢UÃœaCãäÃAüÖb<Ã¥/êZað.Ãýbß*r.£ZC,Ö@BÃU*CßÜ+£ü|.AßÃað:¢öýîvÃOÖ£:AÃä+ ößÖ/vorhAbA<,z,Ä¢+£Öß_r.>o*o ðBrbBað~£z.ßößUvÃœ:vÃ,*rª@,ª öBßbßÄoðZzu+ßbÃ* aª+OU+åî~ö|îª|Ã.~£_<+üU© ý:uåö*¢äv@ßuoÃÃðoUÖ~a©Ö_ür.OåüA+ÄäUî©a¢~öb AîO/öß_uÃ.@Z/ßvhªO£ü*Ä¢ßÜ:* ÖZî¢|örüCî©ývbCZ/", - "Bªh@Bzz>b|ªB*+ã:,U._oö./AýUuZîCa_ã/*Ã<¢ZÃœA£îªOÃ:ãÖ£O~~:oOÃ*aÃ|:ã*ä©ã¢ävÃÃðO*£üah_üäabãA ß:|ßbA¢a,ãü£o.Bo>Bvß>~:ðAhZbýuö:ýOÄð/zü+Ã¥Oößý*ühåÃ>ý~ußua¢ÖîÖ_aOÃ¥@©ýÃOÄOCîð ¢¢ð©oorZÃüäÖ*>îß|bª*oÜð:Ãý/ÃýzäÃz>ª+ÄBÖª Ã/î©î¢rZî¢bZ~oãÄ<üOÄ@z/CO:äî@B:zýa/*Aßß+U,b©ß,Ä Ö©UC|åß.ýßéOߣÃ.~.ßO~UCåÃvbZr ¢rß@Ö*_ß O©Ã©ö@ãU@ßÜ|ãu_Ößð|Uz,ýU,ßã©ýî,åÜb¢>î|o+ãa|", - null, - "ßývÖü/ü|AÜÃ/vC©Ov¢.åÜÖ~bäuÖO,zã>ߢAä.uðüîrbäß:ZAéÖöb_üA,uîéßOÃöO¢£BßÜbÃ:ü~ÄÃu,öÃ>Ã|Ã¥.v£a:OzrZCAzî¢ß£öäîÃv¢|,z©a@ßðZ/ObÃœ+ýB+~Ã>oä_,ß~~ZÜ¢ßä¢ÜÃvÃÜOß,*ã~hãã:©ä>züÜ:Äãðã.ð>o£¢ /ý©Ä.~,öBßaüvüÃC¢ðh>åäzý.ÜöÜäårOüß.äÜö/îðA©©Ãå£ä©ßåu>C/~©ÃZ££¢br~ßý:|Ã¥*©>U~äO£Z@CbBbýýÃä+ãOB£ßUZö>>.:>Ã¥u>.öü.*ÃÖ©ßÃä@~B¢Z_,ßÄZz._.h¢A/,äåu_h©bz@ZoAüÄÄ£î,Ã¥CîCb>rB_.ýubÄu.åýZbüöuUä_|v*ÃßÜuuoz<ÖßÄ AC£ Zr:ß_ãÃ+ã/ðå©U@ööÖä~uaã|îðCO|Ãðã~ð| C:ßãB,+Ãœu/ß>ZÃœ+vvbCZ+îå¢Ä_ßC@Bzérß>üa>ߢb B¢aÖä>Üð,uªößåðu,ÃœO+bßßã<* ü@£££o©Að|<.¢*o/ä*ð@*¢bv@,A:ZãOZ@oß,Ö~öÜCäý©î+_aÃ¥*ö>_ßý*:+CvCrÃB,rî:|ÖÃZ,o,<¢,*oãa|öåÃÃÃO:ö+:éîCîvÄÄbAü/B£ßrÃ:rZBßzãÃ<<*ÃC£ÃÖ|.vu~*v©Öå*C£©Ub¢Ã/aßAuvbO@ã+£Üuh~o a|UUz|ä@.b>Ã¥O£>Ãa+_bÃ¥Zß/ßã, +vüb@ðAbz ãob,*ððÖüoãA.bO©*¢UÄÃÄa*v< >ãü/ßÄ>aAÄãußð:bîÄZ¢ZÖ_£Cv~ªCoÃCåª.ðaÄA ðboü@./ßZ©B£üãbÖZ z@CîååaÄ*O>_br©üÄuª~hÄßßz:bbhA|©¢©,>uaOvüßbUZêîüahßoÃOªÃzª.ãö| ¢ãð+ßoÃÄA ¢ £|@~ähBhÃ¥Ã@ð>Üßð,Ö/auÖ¢:ßUBßZ/ßßbäühßa|Ãä//Ã¥zýåa*ßzvî¢BåöbÖªbZä:öÄä¢ß©C,OÖhßzÖhß+b::C@î:ÖbOÖvü£~~üöªhUr©ÃðBo+CuC|u*ß@ÃCåÜÜr/Ãý/>ýã Ä©ÖO¢@ßÃ*:O ßäbîbu,,UCbb¢hýb.:U>_Ãœ>/ßAÄZÄöÜ.~:,|Ãb*vª:Bª~Ã|>_Ö îuÃÜa*¢Bb@C©Z*©*rz*Ãœu.zå£îåb:¢ªªbðCübÖvýÃ:BBCÖ/©î.ðb£Ab+,:ßåC>äÖbb/|:@Z:A+bÃœzrÖå£>ßOüOÃ|~aßîU_Ãbª©,ðAýüåÜZ>öör|,ðÃOb*ä<@@ªC.îÄ>ÃUrÄ/ UÖ/*O<_ß>.~Ī©Öå_bUaöÖÜö/AaäÖoö¢r.r/£hrî@hbbZa~ACh¢ããªüCðuh><Ãœ,ruÃ¥@OßÄé//üÖª@Bߪ:b@.ýÄ/ÖÃãß.>._ßîuý>Ch.O|Äb>aÜ©hBîÃ.ßã.oÃ¥ÃbåðvðA/öÄzCZ> £ ãZ¢öÃöoÃz~ÃhAbßãª+b_vzÃœbbbb+aß.,Ö:ýC@öahÄuö* zð£hr£Ãß_£+ý<ªAª+A>ªv:ý.ð|b婪>BÃ¥rðv:výCAz.Oª£zaOªå+Z,ZZ+br¢|ßhðhvCZÃÖoCA+|öbßßvUbÄbÃhß<ð¢î+b*_uOBbrý:¢*bäÜAÃß*Ö|Ö<ÃuÃzªüß, ChübhBZAåðÃv_ÃâB<|©ßÜroÖßðh*/h|OAZAAÃ¥|Ou£öÄäöã,ãCuÃ¥@A+vĪÃUä~~ªO,Ö:~auýé*vßo_*©@ZUrvbh£a /rz £b @:O,ßãîUhä©C:_b ~ Z*.î*¢ß|ÃÜß/ Ãr<¢bovÃ: üCr:_|ß,äÄa+ÄðªC|~uoh:Ä@üîüîüOz~ãa/ßhoUýî,ðÄz <_aüªÃ@zr@.©<¢ªå.Ã,.@Ã>ßü,*uvÃ@ýr£/.Ã¥a|¢b_ßð,:ê,aüu îßÜbrýu Zb:ý.<ãrOý.CÖ*ýå|/>ÃÃßUÃœZ©zã£+ÃA*zÃœ>*öýýä.uA|C|_ßî>Ãœ<@ª:ßÃÃÃoý|ßz+Ãœ:|>_¢:@ã ªuuOCÖaÖåð|ãÃzéUUßäã,ÄO Bý *äzö©Zä~<>ª.üßö+ßðÖUßå+rÃýAãAÜÃ,aªªo.:COã.Ã¥A: *. Ã@<>CUÃߣb/©~¢Z/ðÜrÖ*ÜÃâ|ZB/Öo©/ÃAoî@>*<ªßAÖbbܪª@|o£ýüãÜä|üª+îðÖuåªC@.ZäZÃü©ö|>@>ãuª@ÜÄb h£ð,Ã¥UðBýãOoA|öÄ *aÖC u.u:ßýbCÜöÃÃ_Ä/Ã¥vîUCÃœ+Ã¥AÃ¥*b¢,OBäUZ.î|uý~|©ãÃ~ß*a¢Ã~ZÖ£ß,bOãã¢rÃ¥boaÃ/ÃœÃbÃu*ð,î¢~oý|ªbÄðA.A@¢Ã£bbzÜÜ@~BîUå©ãb.Ä.>:ª©äb~ß*Uüz.UC*BhZ¢ªCCýbb Ã.åä¢ÃªAoBýuüÄü.Z¢ß>ýÜ ÃðCBß+*ªªÜÖä,:+ ªßuöUCîzî£Äß+v~ öbhö>ªß£ß>+:uä~üîZOÃÖßb¢aßOo©@u O.îzz/ãaý|CÖÃ:BbbÃîBBî~Ca@ðÃ.£ýzvbvßöv£<:oÃOÄzî_ÄhBðªCUZãÜzß>h~*~ß<©rzBßB/£ðr,.ê+ýZ_bÃhßüüoüãüz|*ªåãu*.AZßO< B/+ªOhhzß>h Ã¥u ðãv: *ãÜ@ä,A|vvU>©<ä@ß*©ãOv|CÖ>*CbB:|ÃhüU£ãÃßC|A_v£©,+ßC<ªªobZ©Ü+bî>OßÃÖ_>ªäü¢äêuAäCãöÖvüüüCýîüüb+ÃrbäÃ|Ã¥håÖöoäÃÃ¥_ãvߣ@BouuãÄ¢_ª£îîUh@_+ZZUÄbazßCUU/bÄo.ä£bã£îäU~vBbvª~+a<ü_.ßaoî.Ä:,C|öÃ<,<:ÄÃ,.ýhßîv_@£å/~êaA¢îðUO,ä:ÄozÃœrCîzÃ~h.©rU£ärß@Ãüuh:*ã|Ãœ>rah©CÃüÖz_ühãh<~+/£v+ý~ ÃœÃöozãv @U,CZaB>ÃBÃ¥v_|hb©åý.î>Cr/ßå,ßß:/ãä/ahr©rärß©BZC_:>îÖª£Ü~Ã*Cᛚ/.zuu,ßh*Ãœ/Bo/¢_~ÃœbbüZB©bÃÖÃßvª*ÃZ_ý.ÃÃüväÃA+Bbu|Öå î,Ã¥:..ª>.äv/ãÃî ã~bîbbÃœCªbZü©_Ob+Z/z,B~ýBu|,~vO©rÄ+ý©ýZߪÜä C¢ãäÃub|UªöÖaBßã@+@ îªÖüor,,ýªÃ¢uUä:,ÃÄ+r¢.ªÜ¢ü~O:zuU_/>åäÃ>ß©|£îz@Öu*/ã©ohvroO_ußu_z<,Ãß*~@ýu__bÜÃ:Bu+ßa<|B,ozÄÃovA:a /ðãöäÜðã/ªªr.<Ä/a/,_ãîZZ¢bäããö@bb:v_Uv>Ab£C<ßüîUüÄv~b<>@¢|zßîBý>B@oürOOÃö*:Oßß>©h¢åC©ðA|öÖîÃöÃhü£ª,ZßöAÃœ_@£ÄAzÄãb£©Zuåßo<ªa:v<ßUu<_O.uÃ¥r~B~hZ _¢Ab£zaãÖÜßußrð|>bÃ,<_Ãœ_.ãÖ<ÄhZÃåob,/rã>U,ÃÃœ,O.Ãva~@.åãýä>£ß_@h¢><ÃÃ>oðvîýÖðä¢ßvÃbÄO*ªª<ÃbA©h+îCbÃhÃœZoa©,>Boðª@OUzÃ:Ãœz+bîÃruåßaCb£äC:|C_ã/ߣ: Ö+bÃ¥~üßß~>übZÃ.Ãuzo.Ã>@~:Cb©A+r<ªzCÖ/h,ªUUr*_+Z,hhr>zoABã©_Ã¥OÃa|ÄåîüÄðÃC:abbîzÃœ<Ã¥C~îåbö©/ ¢u~Ã¥Aª<îvÃÖz*ýäbAZCýO_îbabBCâöu@,z<_~ý/|ýAbÄ~Ö~Aßb£|/hb >äü@+u¢Ü_ÜüÃ,@:z©.r>ã+/z@bä~z£bUuobÄaðrÃœ.vOßÜå£z~ îAö~îðßo,äÜb>C£:ä__/ößzýÃÜî__ÃœCAv©Oäbî|>,ÃÄÖ|öÖvv .>Ãbå£h/ÃävÃr+©öu@+Oª.ébåãBÄ:zbUu*¢zOÃœ@êOÃU©>*B¢ÃUîBÄýÖbAaßAÃv_r>üab¢AÃã:aUb/,+>aBÃboo@+äUoÃœÃÃäîhbO_ AýÃ>b¢ÄÖ⢢za£_uaC£UO+,B+Z¢©ãÖba:£ãã<ÖZÜ£/@äba©ðbzîãO+ä¢+U.,:Uo> +zãv£ÃUᄦÃã@b+¢Ã¢@ärßbÃœrîð_O:ßý+©ßArüuO>z£/¢bzh_ß©,.o<ÖªUbÃBß~©üäß+üÖ.£.öAð.¢|:ªßý,Ãœ.|urA/üîCð:©ßU<ðvbªß ðÜå.<Ã~OCCr<>uão/~B.Ãzv*_ä,Äb>+ZUZb/@AðßßüßÖUUã©..vÜÜ©U>CÖ*v£b*Ãåo~BO>~ÃzªA/î:Ar,üÖ@CÃœ~Ãœ~CO¢ÜOý*>,| £Ö<îã|:O¢ß~©Äoä© ã~B~ß>b öðCª©ð@îÃ_£>ã©| .hÃœ@>ðª zA©ß:ÃßU,~î¢*|üäbÃœaªrOßCCCz©hbÜöÃ,|îZ@îª._ß B+ãC b*B£îu£ÄÄ£ÃaÃ¥hZß,ª© îßÄ<>+ßÃ+¢_üüüÃðÃBßü£ÃÃðü.>~rUbßývBðãUß~A£rb:.brA*BßäÖ@üÖB+öß:zC|ÃýÄåÖb:Zröý>¢<_+z_äovðUÃCOÖääß|*b>Ä/ Zv£>£ß@ÖZ/u¢bCAZz£îBrîÄ+|ÃðbbU.äbýäÃ_>©ã:__ZÃC@u_hBÃÖ ðä,£.|©:ÃAA+~bUZÖßýb££+.£/OÄ@ðh©BÃba*Ã¥bBoaä>U+_:üv,ZÖév<üaý,üðÖî+väýUöäÖåî¢ßåovîÄÖU@ãî.bß_+Ã¥BÄrbB~Ã¥BÃ<à _@åßb<ªßÃb©uÃUb:äßOÄ|ü©oÃ_ÃZvouUAðüzaªýzð|habý,ä@.UÃ¥ZAÖ|äÃC@ßz_|ä/OÖvÖ/vᄚaã£BÄhßÜäð öýo.:Z<<@ªA~oªß¢ªÜýýB.UÖÃr¢.+ý_ã@<î@uaåöU_Ö,©U£bC_b ö¢hªaÃ<ßuÃœbbÃœ>Ãh>ãÖ@ßÄZö/rrA>ÃrßB¢zÃ¥*z><_bÄ<<©ªO|>~vÃœ|@U*roß_/z@~UýhOßßO*ZýÖýß:ܪAb*vhÃÖ<£@zb>uAÄ©¢<ÃZvrZOðÄ~a£oãÃour+Ä.v~ *î/,ðð.b~/ÖÃ~UäbBrüO<ý+:+*aß*öruCöîzãßaßOðbÃBü|ý z¢uZ.ä+_ýa~ð~ãu@¢¢ |rzrOý_ð£*.Ãœa>AzýðÃB_ÜäÜ/åªr/>|*ð©ÜB aC.z:墪ßîbà Uªð_ßÜ*@Ãœz,*Bvü/,äýÖßUßAüüÖã@ü|*Ö*vÜ£ÜÄåC*h åä+åߣ:<ÖOOÃaUßCh+£©,OÃÖarÜ¢oB¢:B.ð*¢CýA,O:oÃœzÃüAa£Ã|vðÃ:_¢hoªrC.<ã:ßvî*ýö¢u|_ý¢ :ãu£@>u>rÃ¥b<ãäÜý*ÃZ:uhÃœCzâ£Ãoî|aßÃÖý<åý¢üª+ßähbãUußÃOB bB Üä~ßbý@¢üOªC>>Ã¥~aBuîßUÃœ:<©ÃÖh>o~uO@Äß©ßäéßr>Ãœho:r a@ß+~*@@ zÃ>:AAÃ¥züOÖ z+rh.|öÄ_~zöOhb,ßö*zo:|üO+o<åßOv/ýÄhªuoÃœ|ßßÄ._Zåü<>Aã/OãßUüA,<ßÃ,A@r/ðÜ~Ãœ|Ö*©ã_¢BZ>,zo/ýßä +vC+býbz,Ab/bÃîUv©ãoüÃ*öCü..Uª@,~~. £OÃ+ª_o~aã:Ä,îozüÃ+©îOvB|©bÃœ@Ãä:Z~ªBý<|/_ÖöZ@Äa /r.Ã¥uÃ.<ð~ßö<+bzüý.ßzb >Cðhouü|ã©Ã*zZýU_oCo,©¢üoã _zOA*åîÖZªvvüaüZÃAÃœaüÃä£rððã>üý<~£A_<Ãa.,uüöîär+Ä*uhî¢*ß/_ã.übvzªßà ý.ßba*¢ÃÜðÃ*Ã_hhÖä|öZUCöä~/ ü£ CoÃ¥,Z|oC|ýªa@ß>Ã/ðh|ÖUÃAÃzäO/îzÃœh~¢Oãhã~~Ãýü©u~o+Ã¥a,ßB~Ã¥/ã£ýÃü©©ÃãåöozäzB|ýaUÃœ+@ÄÄß_îîöbaÖÃý<ãåªb vã*£ýbAÄzvC|¢£@ObÄÜ@OªÃö/CB@ ~Ã¥:ö|>ü>Ä~rUãCU*£:bUüî,ÃZýîAß~üuB¢£/z@o~ ßUî/äî", - "|hÃœ>hZãªböÃoÃœA@üor<ßbhZ:îo©ÄU/ã+ßð.OÃöoý©BZÜÜÜäãoU/îU Ãý_¢ÜUC,übAbBåã@ bOOߣ>A,@äýîäßU|.|/+", - null, - "vOö__ªU*buåðßÖî_.ªu|.ß rü|:¢Ã_ßüÖö¢z~rÃ¥~uÃBÄvZãåCbßöCzÃ@:Z~ýoªª@.îOÃ¥C~üuÄ£*ÃâöÃÄ|>ÃÄäã/uCäZ¢î.ªh|z*CüÜ@Bªu<übÜã*Ã¥vªCðß_bãaéªßuÃ_o ª~Ößü~C£+rÖÖZ+zÖß/a.Uu>¢@Öz>ßh ÄîÄv>©ÄÃÃ¥O.U*.|zr:AOoBuCU©Ä>r.hý+Ã¥bã|BßBBÄhuãýorö/BbÜüOªÃªAßör_:b.Ã¥:+ÃÖC£ðB:üývv@>|Özbu.Ã/oübýhÃ¥O©O.CÃãuÃý,ääâà Öz/£Ühür:Üð|ÃœA©ßrZZßäoß©*b Ã+_üß<£ ©ÃU,@ru£ðð£ðüÃb©:*ä<ª>ÃœZÃßZãðßBBCÄýBüåÖª£>îÃÃo+üzÃ¥><ªOÄOîöÄÄð+ÄCß|ßvÃüÃ~~vßB~b~_: _bZ¢<öýßOaObÃvᚌh+Cu Oðå**ª~ä+ovªã©ßOBävðßÃ,_||zßh:O*Brä+ÖC~,uz£aÃœb.CüÃb>ãz_>@*ÜßzÃœÃîýð>£ß:/üvåÃhOã,.©>Ü©*ã,*ßb/|Ãaä|ÃœZ@ZbUü¢b /Ãý<ªAª:ßUîz+îð*î B .@oªåÃßbhü>UuÃ/ãßßîv:ÃœO+ooÖÃ+zuªuOîî|+ªðu|zßébÃð@< aðAýzÜßAov@ªZÖ~+B¢~üvCu>+:ob .Av£@/îðO©,o~ãä>zöA*,zð@båÃ@¢ã¢ÜÄÃuUüÜzÃ/ðörß©.~£åoraüaÄ h/|r._ßÜ+äö.ª@@£*î<*ÃÃ.:CåÖÃ/ÖÃÃ@ðã*Ö~abß©uZ £/£Ür:hZo.BhÃ,b<>:bÄ+Zäu<+@uU:öÜ©@Ü£u <ÄAaUî|vßröªzrO|ö*|.zßöîbÄ< :Oª.UOý*Ã+bü©r| Z¢hêC|.bßA©Ühü@/Uߢ~@*hußä~Z~ÖCU¢¢/|ÃÄ*ªr :hz||,__.+öÖÖZa>ÃœUüh>Að/o>_öÃ@vÃœ_£/Oå©.ÄAÖh,£ðÜÃvÃäãýßhZßA>ª_h:Ã¥>_ªýa êC©h£ßÖv*ªîAaZýhÖObu©ÖÃ|@_ ¢b/©~©Üv<*r/©¢Uoý£B/~>,ãßðBAã,CªB:Cuîðz+ua@ß_Ã.~/ªÃî/BÃh©~ä©~Ã.Buª r@avb Ãr~ý~Zb>>ªU£rOaÃý_zb£Ü>~.~ö.ub. ©~ÃåohbZÃœobhO/A.z<@:öý©öÄý|a*©Z/Oîý¢>h|äÄ*.ü__CÖ|ö,©îÃoO|böCä¢üv:,ðoî©ýöÄOu+|ª:ß__Ã¥U@zÃÖý,Z,bo¢B,o@h¢öãîö:üÄ@ܪüªo|zÖÄÃh©OO@+ªå__hý.bzOªÄýZb*aUrÃœzÜîªz ©.+<:Öz@*|/O+ÃœrîzýüªÜ/£ß©öüÃbßüöuß ýÃãÃã,:ö>îßä.~.<ð¢Ä|uÃœ~ß>ß~:_ª>ö_:rr:UCuz>hîãb_h£ bz@vBuCAubßý,î.äzü,uA©@>Uâ©ö/U/ßu_r/Z+ýb.Ab. BU>ýÖ~Ö.Z+ÃœAa*ª/BÜß+OO*r/Aª,hª£:bð£,OüéÃ,ðO..va,¢ UvboîåZ|Ãü*ZÃà öBÄuvhBh©ÜvOßaÖªa>Z,OAÃœÃ>î.öà Cu:b:OZO+rhC++£Ä<_ý+ Ãœ, oð ýÄ*Äo:*£AÃÃ_|zãCBUb£,bß.äüzý*ãCð_ãZ:A<ý©AâýÖý.b>£zbbvöðîöäOh*zrªßaÃa+B¢U,¢£:ᜄ_U>Ã_bãZî+_bªß_Ã/.+üý©bC|©ßãZ:BZý¢å㢣|üÃã£ãaa/äðuA/Ãåår,Cäuo¢ªð:ߢO£ãßÜßavã.Z_£+ßÄb.£ªCÖÃ:>Ãœ ßBUÄ+u,vub.vªÃªAßC~uã ð@>ßO>.ü,zý>rOªÃîzvBª¢©oAð>Ãî î|>:OÄu|AåävÃBZCAv>B.uý+:bBzªb£BÜý£_/O©_@Zu©ýððýZvhAoðãh|uüßä£OäAßzîbßbä~ö~ha ,@oCA ðªöãh@ýrräÃåB*¢o¢Að.,Ö*ÖröZU/uZOv,©*¢Ãö©©<@¢|@|bÃZUhö*hb>î@z,ßr>ðÄåßäÖa/ÜãÃîUÃr©,r>oühUîU¢ãÃr£vävaÃä¢Ürüvß+BªußÜÄãðOzb_Ãœ:ü_uîZuC~å£uA bªa@Ã¥hÃœhzAãba*Ãb|r<î,£UübüªU@Ã~|Ö+ßzOÃœO.a ©@,ãvä,>z/ÖCö,+A:ð__ª~/v £_/îÜAbîbýÄ|uÃÃäA.hߢ/.ðozUä:Ãbbh@¢ üª_,©uÃ¥,©:UußÃ~OZ/ÃrC@Ö<Ãœr/ î£/Ãb.Ã¥.äÖo.|AvÃbAbÃœoîÜÄß/r:äb¢:Ãœ|ä©ýýªU/@ÃœBrᜄ C+.|££|a,îå<©O,¢Z*ãCUÃœov+>¢bB*ãz<£.Cßääh_O.Ãüã:Ob@AzÄbBß hroa>îAb~ýAÃœZß.<Ã|b*.Z@b/ý~z_ª._~ÜéÄCbZ+©,ä/.ßÄ¢ßa>b:îä©B~@ Ä@ü~>r@CÃÃœ~@ÜýÃ婪AOäÃÄ~î¢ b¢öhªzoÃ¥,ý:h~>¢ßÄ,ruouÃß/ü/ÄAAÃœzÄv.ÖÃÄzü£ÜÖöÖ>B*,zZßz.Oßåhãä:ã*uh/ BäbaBu|,:ÃœböäBzÃÖüaZBªä~a©Brð.êbÃœU@UZÃbãß*BBÖÜÖhCªUCoUvÃ¥*bßß<<î_Ãœ@ÖZzö: Zb£äÃî*¢CaOÄöuUýÃ@Ãœ £v|üý_bUÃOðaýüüÄã.r/ÄO©:zrª_ZÃœ|ßohÃA<©äoãBåöBoÄýv_hz *¢:~C©ßö_üü¢CäãZðUO|AªªZÃ+o<ªvÖ¢U<ß@u:+abrZhðzüZåà ZüÖZÃaoãÃ@z vr ,£bzb<äo<ãz:+/,ä~îö:©>Ob@bª¢<Öåað~ðzU|£ã/ßv:/_ß, ©Ä. Ö£å:üßýäUÃÃöZ_Ãœ@BªAvBAÃ¥uãüvuZßA:ÃOöAãvÜ£ýOB@>haaÃ¥UbÃhUÄÖ£à ããöåÄ>ª*ãZî Cã v/buß~*,îB:,ãZ<Ã:vZrÃœz/..£@¢£CÃßðý Ö .z:C¢.aý/@>a~/bã¢|üý©ßObÃÃß,©:ãöÄ+AößbhÃ¥/.üCaªb©ªüz/+Ü£¢C+,墢 ý©|zu,ha_/b|UãbbCÃCÜÄo.|ðÄ.ß/ÃUvßBý:/Zßv<öÖýzu.ÃœbzvO>ãªãUa,@ýz|äOßÖo㪢ãåãzÖ..UÃz¢Öh.bÜü
Ã~Ub>@Ãh,>îuA:O£vý>*ð*býa>Ã<~@.BÃ¥U@:/rö/aübhßh£ÖîZ*üð*|*_vbýßU_Ã¥bß ßöÄubAb*üzîOåãö|zo/O_©OðOhö¢Ö.obaßäßrbvðåa~b¢BOoå©uÄ¢¢ßCÖÜAöB.~_häobüC+Uî@Z_bhzbößÃbr//+aÖb.A ã:.bîÖðCOÃœzýßC vA¢<öýzÃ>*Ã/Ãœbrbör|ÃßCAu_oãªüÜr¢zAÖÃ/öb*@ÜîÃr", - "1753-01-01 00:00:00.000", - "2000-02-25 21:49:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - null, - array("0.7945", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 0, - 0, - 1746584310, - 2147483647, - 13342, - 60, - 1, - array(("F13C5E31FC809573D1EBF43AE7C9DB0C226C824DB54218DB217F0F277DA6200DBBAFEF05A8C6314555DB61BC197324C4660C8882468DC5AF2383107D716DF62ACAE6"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("D6635B3A3DF7D9"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - ",hã_aü:Ö.ð/ýåÄ<,Bý~Öä*C~ê<|@Ã¥b£~îü|Ähär*ZA+/o. z£Ã.~.ßO~UCåÃvbZr ¢rß@Ö*_ß O©Ã©ö@ãU@ßÜ|ãu_Ößð|Uz,ýU,ßã©ýî,åÜb¢>î|o+ãa|", - null, - "|hÃœ>hZãªböÃoÃœA@üor<ßbhZ:îo©ÄU/ã+ßð.OÃöoý©BZÜÜÜäãoU/îU Ãý_¢ÜUC,übAbBåã@ bOOߣ>A,@äýîäßU|.|/", - null, - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - null, - array("0.7945", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 9 -$values[] = array(array(("007AA6514374FF12EE7B84A3C5CEB1A3C6BB4A00DA497372E074A6BBA2A20624F34E82ED43A5C97223EF01433A598BBCEDCB336620E669C180F0D3C1FBFBBAACAF392D2F302F094678FA7565255F157127C23"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("B86B8689AB16C42D2078638345494012"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("70879C3BABA5053C2FB8932061D60AD5502E042C8FA011D09626F131665A75789144D39EE5AE7BAF0E2A0AC3C63D2E6B8A0E47A35CF3CECF76CBA585B4D3D6D4421B8F1D026BEFA2B3551E65964E0A58F786A75712D11B9D7503281A08F5FB3B543580AAAE11681E1FA5F007D4693927C04D8E2D2B7EC27100F1A7FE43D599BD59E21160CD60A9E51E89386DE113C90CC90059BB67CE0FD5730FAABD6969DA490B32AEF157D95A60A4A80EA2455490EC289B35427BDE558BD33CEDBEB6EB7CB5AEEE62AE65D7B4F0A6F916BFBF6C8F1F7EBD95CE3497CE2CDC221E1BEC9BAAB82C59F457F2E28751E6449C82C796CC67ACCE3FF855AD80162035D142B899040A4F5E936578382C9C3544E7EFFA4C654656B76E1E5CB1627ED9DD36BC1044984D3F9232690367EE74A1A2C311C6901BD4D08AEC012AE6AD9EFB148C9A01708BF1AC99AF6819FB42EFC8FA7CB9EE51B748C262EC8657962259E5946EEEDA54723116D1642CE0B3BAD447FD392F69F3B0A95D43D89E83059915775683858D541474884BA21CED8A670EA8173E595DF3F0F1E4C490967BD3AD11384EB58A303547F3E9C5A71D5093BCD2DCCAF66CC73FB8836691A1C8C37DF8B49E91E49ED4DDDE72CF4A70974BD2A05008BEBAB1DC4149F89531249E947F2F6CCCF27FBFAF1DFEFEEB849C7CCF213893F150BF05C247D85D48EC09A2B83FDE501B03CB5953E1A1EBC0BBC558A4CC2874F990AD43BF29A9851A2276C3145B46ACA54B61CB56DE400FAFD44506817FB87BC394A927919B91F15A453F316ADED7647519B2596B5D1E1A509E94EBE4A3039A69FA007FB21E1A0ABA7071109F0EA4D1FDE09068CD5F1065768ED6F307A60CB642A4C76FDD57B257FEA89FFAEB0D7B42F9C2ED03129ACD0AEA9A09234726031363BACDFA4734F3A62D396A24C01579681394F944B809A4A8D7BC85434D8E10D7A58B97314080FAC5CF47D5D930A84AD520A010B22CBE5B5A1625238AAFB0589E29927F347D4828929A34757033CB782E72B022AD7E5DAAE62F3D1A1B5C70151FEF36AF9E291557D8787D18F849EA264BACF418FE8DAB20CBE79805402E0C36291E5681FC1BDA055C594A340044B29D4516CF6E685817914416E3B6911017AA6B47EED9F3692A30D2EFBBA54276208634FBBF295E9ECDD107C43CE63723734C42A0C8B856D9A9DCEC9E9B789C714ECDF55C829551ED4BCD791DD12E2C44F8AF7B6F63A2AE68305B51E23CC003553495756EF1DAA1DEFB5455FA3E38077AB0A8A6EBFD2D0EAC1F8C2C26446EB08ACA24E83CB9BD521E0E9D740F4CB329F854D632941A17580687C94F64AE7A998CA3CA301A4FCBC1F25587DD5FB9FF5F6C6333D26D3B1C70B3CCE2B4F06DEE6F9403E76B82820FA888153F3F9239F84D8869E3017AA72C5187597D4D6D16D1856617495BD293DDE9538B4957FBDDDE79D3942C868AEDF5ECD795E42805C23A7244BAD87AF09D7AF58CFAADEF5DD455A97B047AD8EA4AFA78225C843CAEB4E39150D71341046388361A36F9903F058F02403BAD01476188F9BF5F5793193E46D7161C9E86C114A5177B44BEB3506A381CCD81FC7307E249EE4F1A81A40CF51DDCEF2279236709412C95958603A3D4A855263BC69741D3D033D3B036A792000B8527E2C32EF015B5C43FD0DD715C464B7B712C7DD12E859A1C51454FB6D29A99958A00DBC11079641185025871F65ED78C65BB3566823A57A247A7DDABC5FEF1A43747E75B6A8A6AF7C76C4B2C2D2926F26EC351176532134FA93979689CB94B8610B33AAE777B21DA503D9F15EE2A741E81EA3C02ACDA3F043497B23110015B0E21C41C79EE787BF2A176C62793FD617FC45AE7B27C48D935733579DFC9F0CA4FE290F42A0DFA30096E4A61361418713636336CB61AA991D8EAE606B9FE9C28B0AC774502093771C92ADE4331E7A3215BC442A89584133538FF1B3F27F4770AD221B0899DD26F2DFFC2A28C3B655FF147C4AAF4BBF3932C98410C03EEF84AD5B3D3710F371A84885515A9D3E287F9D29F29F76249824EDF8E83F05BC66B3F0777CCD867F450BA2C3F229FA649BF4A11483963C400E78C30849281B32A29C2E9D5A25B15AE92DE4F6FCB2227E2A895CF39DF138C087B079040A7E262CE06021660B6D1A7633462B65B145434C4644EDCFCB1794B1635E15A9ACBD407AF7B8068C80CC72CD6ED6C047C5D30F326D2CCDB5905661D8DB7002AD5760D98605B06E094EB6BB89D22060E4574CEB904ED33C8FFF8ADA91563DB110B9C5760E0D3A42156505E3A66133D37FF57D718E94725E743449967AD5DCB31105AAC01CBA62E71D0AC62B48FF861EBBB76A9BD8BC328C4776F1C9B3A3D74F9842D2A0C663C6C592865483CE470584AE3ADAC37B30DA54D32E88A5B8992379237692F61CE55847E095094257AEBE00617D2B88D913D467787DDD97EB211D0962EB5057A41F67B8D282D2D627880D247078343B6D9A901FA23B8DAF17B05CCF87FFEF7C5E665C421562B646310E3FE070A92762ECD19BC84B30A57891C8A11226134E37A880C60A2BE2260AE459EE940B9D2E6E98F9D2B65417212DB0E3B04CB194101DCFF847EFD31D46FAAB90E3F152017D049A42893C4087D3815FA8593831D9495039C0D403453E8B7BA2D80AB3333FCBAF76BBB27F1AE3DD3824B6803AB40EC8C25793C7913D69BF5BB70D99687D105E2FDCB9C17626CA3555E3ABEE9EBE50B7941F486B9F783A720D0C56C80D4A74BB0458605E0A4D42AB8D5E61AD918B10AD6645F55934B4CEB79237B479C002380B7EE0DA365E02AD56CC9CDE41069CE081EBB951B7551B865F99A8BBC288C8DB8B26663EA0A0EA5F6398AE8C97D67DB10557329E06A070A6535A8AACA546EADDF3B1532A906E3FD10169FE1EB68EEE791BE7A67B463AD4DAA5BFDB56164DAA1CBDC088D6835320A727519CACA251FFD47C0180BD907F87C8EC7874D082E9CAB3BFE150E5F617F5C8C70F43F4AE283843C006EA14CCEC7C4E48CFEA0AEE40144D96D8ECC10119DE541BC7F6912B932AC44031108C0FA5F6D36512AD362DEADA86FB065DC54B6742A6288608F4F5EB22A59525CABD2F883DE0D2095BEE6C983620ED6FA615355578D8EE6A6389ACF1ED79EFF790F8102D6762C86CE3320B5449904FD81786450D703FACF1CEB524B443E02BE20E74F73E00B008335CC87246CD8A20C339C4EA5734E287615B2E6EA24E042903062955D49901D7678E01B0A7CD66343484518A7EF5D4AD1BED95E2E25155F5489F7F3094345C3A27FD72C92321EF93F4EA2A1DEC89BA0D217C39E7032EE516381C78085E25AFF038275271731B89F9FDF1FDF8273808BEE5620F180A653709186C9116B9E745CF4A29E5C537488A15F24FE90120ECEEF003C922EC1687F5D2F04969168A733016BC2EBDFD5243779CE7F925CA7C25D82B8D9102F0C469A8C23412D4FDEF739B8F0FD254F23EF1ADC04186042D623B56CA613D88F9603F525275FBC93F7E79BD745DA6BAA9D0D5FC8BE342AF4E658B5FF8BC0A8CA1EAA21ED07FF27C43286E51C7C89FDFCB80BCE98B410D062D8013FCFA9BC08363FD1B89AB97EA39DE59B96374F0E775D5BB94780F210160ED9BE1405C0581A767EAFDDABD062433A6730B218799913CA89E274EF7587432EBAAAC0FA699DDD8D8020B9A030CC8CF8760D18C15DFD9440AA497CDEE32A00F66BAEF025A148D0CA53249859DF739ABD557C79686F81D1BD15804F1F20FEAA561FC23481DC66572D3C4D6E38653A6037AFBF8967B19887E9DD331021F9B0A9557A423A1D3E18E0EE244B2B21139857DB1F1870617442A4863A4AA3A85636348BA319BD230043F897DE9B42656C66D644DB3401E14048E0389DB3E2F3743797EE17A07DF9DB34DADB1FD983D9FF3F19D4745ECD2134534A5B3885854C600522A603544ECCE8DC69A65A26C167C406385D990E7F156A75BAC35521C155AE8E7B19B6DDD6ABF14DEB98F526C70E91E397324D27B252850C3B8D9A8A77C71B14F7CBABA7B0E72F8C6C4C22E72044AEAC32728A8E0886DB2894AAA1481166D6247CE02EFB63E932AF02C0BB652F9EEBF7DFED58043C8C0C71F082130646E77B0676FE6A4015B2494840DB8B47CB75F8F5BEA969230DD1E7EFD4BD0BE0D52B681B45B5EF3134ABE12CA410C46101C4A57A891E479C7D57F8A5167A6B58A76872147270021686C6C013C83C7FFD0C86EE0F189056B6E3CC9DEDBC4A1BB93F89853C9DB0EE90C28EF4C35D34D440D8FA988F8C5C401611BB30A6D2145A3BAB07D43EB742605C975D4C899C9358AFF75C14C1B18B2DE2496E2AE17F48BEAD34B81756609589959A3CF72E0FB67A7E6380AE75FF98891FDAF1F548187EDC4F9F9F54E37B024F61D37F277593FED392F4AB7EFF9A48AD9C2EF77C6EA0417D1B60A88E7061970260314CA28070148CDB1E63546D00DCB457F1B8B48385E0C191020B15EB6F62305DA81DCE4DA52C2B1A6BA0503DED0F1B83F3377FA9E27EDA508B107E64FEA16BBB32F6C4E30E67B1C48C9A18E59083A4F2F092F738E3877A29427DC295AEC61BCA34940AF64B38748C85BA1C4866F5C0076B8C746CA933D8AA7C9AA090A51C16C67E4EB0FB78AEA7B81E6BB2286B32C34CBDBA292152AF2001BC607AB097E3E769A456C91845C48EBDE5BB4E3E81AF3F49D753D3EAF3F473CE2DAF3E1834F6203A8349841468AA07F82C6B3C9661E1800C2030D8430FFC75AFE2FAEA96857588EE9B13BA372A2BE02C4CDABE87B7DD6D4C2F32F8041EDD2A87D3D4326DC07172B6E24EFB4E2648E1F54FABF7353EEEF8048D9E66537C84CFBDE8336456F8726CDB6807A469E21F25E65003B472E6F548E5DC2DE0210A1E395D7E3D66F3E38BFCDD3F1BCC36498F226E79800379D05DD7F2EA7F79CB924AD149CE46EAA4B83103C407D733CA29119B4D0A72A88A1BC08D216574086412314AF44A836802A47DB5477D3E71F9290CC9D8E32BC020A2462E0DB9C93E44299EB2D58C3E7FA5A999A98CBC600DDC4E93F758AB2F80E6712A5203555B48DB3F711EDE8C9C6DEB8D232F4A823364413951BF25D1E599F91ECB6461EB7C504D50CD4D4B422E4FB2260CCB87FF35633EC611CA039B220959D724BFC1370BFF10D3E1F9B5E518FE0A020DFFC51A11FAE867269BDAB14CB22DC3F4731D6F8E7A1B0DBBB41D3CAE58AD12BF018E23443FC52AD759116F245E862FCBF1F0A5E19B7392E3B75F5DDCACB9CDEDC7F24B230A7FB5ADF2983569ACE85C2EED70BD680831248DFC408CE0684E2A9F43815D6C3A0CC1713AF7EE033E2937CF022EEE3CFB65E596CEBD27CAF2D545A46D95252A8AEFF3E1AE4B2D1043204FD208BCDD395FEEA4DE9BC79E7DE4F77F64B24995A9030C44133B0C8DAF37D553AD6E54C135F80B447EF0478EC35176FCB7CCC95DF98AA5CFFB78C9FD5260F4A7C673F00F9036496CA60417B8BC7E81C1C50FD6E26D775922C884C5BD2E0EF11EFC9FC85060666C80825CB7F473ED137DF60738438A11394E1ACE460B708C3D9812F9BC1CB531605EA3556010C065AEDA7147C627D5AE93EA657A35921F6110F61F1FE17A30F7C8C5A6D99F34B1288EB41F6C9A28C516C26465D2B63AFBCE3E414569F1A08333A97A7F0080FB0E5F383DEC8073B3C7E730095C7D155E408DCAD89B5EEF4837ED31EBC21FA8C596C7550599794E7A5A8A7A5D38E744061E4B39F235A760B070F005FE4F88F1A73E6B7395280FC4C73E507E10E3910F4FA82BCD77E6B68FEE66A495A973502AD299D31ECD74AFDD0E8671B3E52850CCDEE1B55523BA79C088DB842E837A1B53EE25AC76107341DBF94DBFF17403AAC616D7B6C8BEFA06171F1296F2F0805A936A1DC5BF7CD9BC6B9FDA5BD6FB884B8530BE17ABC35395F31E9456FEDD4EEB79F32B86573DEB493D1B4B28E49E301B97C610C23268AED01788D2A0E883BD8E50EEE2E3D001F4474574DA8F772AB154EE78BE37CC8CB810445A6FCE6B4B61BA5DBDD7F5BC4D4B96D2C97CBD8E1F58EDD77DB26FC486C1C436D29F759226DCFA132031BA52BB97A12D2865020FFB82240282F7ED98F2F5D7FB13BFEAAED503ACEC82AA6DED9D9A1BE511419B20BEB43B4F7A730F2FEE61212A5ABE3EFCADF25824B9168D50143764E8DB0787B05AB63C5F849A3362F47DFE21894292DF5B1F381EE87667A827EA3A420E2753E6D970CFA381DF7812BEF6303C176BC7D1A4858CBCCF69C7E44E9438995FF153CC2917481973EE5D7BA6255FF2452B90BB23689D899B97166FA8EB55EDA7DEB2CDA263120022AD5EB5271015AA0AD59E23037C76F753CA319200D97087996FD4C7729150E11890997509E8618A816C64D34C31EA2B68BB7AD7328000493CD71BB83B4D387884BC0B73E300ED6B69A71093749D3A6885A95FB61778AF978F4F3832CCA84E3333EB4E9026764867CE0F6D8B706CD59F9CB33292EE82610B0D73E393C8BA9EC058DC60884EAA420F61F8E7070A2427237999951876BC45B18A4282B293B49F1470AD32930C4D50F2712330F59A5A01E84CC0192F2486499C96AEFD0C278F7CAD59D3975DF193172C48A3A7538B50172F419966D29A70EFEA6396FC5E4F44CAE1A7839CC7C2A3F7B8A8EDE680DEF6104697CECA8D28A912E8E3ADFA5B97B9DC04D9C6AC08B3506D9DA2AE2AAD896E0B6C85B6C781793CB8FD71B3E315A99B21EF10E1A71849089346602DEB7A7F645F75A18DD6E0F25B8A9D0DAE97FF6C57F3078D3A9CA8C7F3237C2280204C513E7E0C546D731CC540FDB5570A0B9B7193DA0839E4EC44C8B7AEAE6959BE723A3E4BCF2F8AAAFFE3D6414D3DE750BCE8880B8FA617136D513A449DBFC5B26C828B7B18BE3C6BE6E8DA2462A29F07E066F1C6E14D2ECC222BDE7DF5460D401E24D5CBB26C37FEAC1C24A6CBF2B0CAB1336337CDFFD11E53BE58EDDFBE76A0DB9A063ECDD455742EF31AC52888012F7A14CBC2E2D5721F52B678F3134A1EEDA684CA30EC72FDDDDDEF09D9E50CF5CABD2973CBF2B4BD0628E7EB165B6261E3D52C1EA4BED0430816EDE4F3CB983A46C0AD35EAD652FBF2C6C4080586E6817B8D2504B8ED510CEDE56BB404CFC93A8FADA456AE2D760D82312CDDFAC83E81306B6865A69481596FA147046B9DF622FD897857E290D34E17188287C1731224A7EA8498676393FA2B4BE60DE572A1E43B09E0704FE69EA0981A4E8E12FA98FB8E225C81195DBB99F57E2C97A9C669A4769D4CB792A1D5C14BE4004C1505E831B2EA7EC21520CBDD4E477BD1B907F6D1B1AA1774AEE169EC4D9B35355C6E84EF95FBE4BE47A68E56D26E694B26030743ED2F9B6AEE7383D18192747AEA9581DFC38B3CBE2238890A1BC1A240771DC54993F2482DD219012177123CB531146B3CEC89879BC74820B4A5914F2DD0435964CC2AE7F84D540CAA891A4610FBD4D807556653ED9529824AD1938741C9D45134B9D8E9062C73EE987DDD11AD0210B8D1B948D76AEF6BCEA2A89D27BACCCBE0B5C9115723EAD1DAE277BB70B818D448C0DAB68881FCE6454D09475D8F4C34CBB1FC5CE4F0B995E6E76DF5F233FB168AD45466DF16435FB8E2CC0A19A3734A9543F0BFBBBBEF580645CFA01307F41151F3BC893BE554B56E9CA40223AF7B4374225FAE433B5ACEE4FC609A46A24CB1EEBAB826E6EA0FB0EF3B501F17ED3456CA8C2CF0A745CD1698EDA43E84BA6ECC4FF31368F7545C8A13AEBF08EB3BB7F7C709CFB9755C09A9EDA28DF845D12C34CC84CF23D9CC5F7530A2DDF885C52B5C36AD777BEAB456F3E96A7B55C6634442828B3B666C931DFBAA5989D37F1023277731C29759B04A18"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "åüäããvzr~ã©U©ÃßäbBÃ~zh¢CßÜ@©£ßöO/ð~A@v<,Öý+u¢ª~zÃ~ö/|Ãßà ÖAbÖ£|Ãœ", - "ÖoÃœ ߢZã Ãœ.>rrröbä@ªÃBäÄÜU.£C/ªzhßãoüUhßvî<öðUãã_Ä>ð+Ã¥C£Zb||r£ÄîOB+,+býßÄÄ ߢðß@,~B.ð©ðUÃräåÃ~ª¢O~Ößå/<>ÃÃr©|zA+Zbo¢|zü>oCÃOOBChBÄ¢Aoü£üB~©Uª£.ýbä¢v©ÖÃz £|b~äÃýrz,r@ü¢ÃAî/ a©<<ÄC£Ö.îzÃ¥ußvA©,©ãÃOzzUr:BuÃ¥uÖ .Z£oB*:uð~za@", - "ß*b *.Ãýrðvaãr", - "ã+zÃ: _ B:î.Að//oBÖ >ß*ßÖbCA:,Ãbý.~z~Ö_<ßÖuÃ¥>züv<ãäÖ/+.bA<ý,u>åÄÜß/ª*hä,_Z@Ö|:z/Bä|ª@ö£ªa_vCÃœoUzä@CÃå*ð*ý:rhã©<~o_Z+övOßOߪßäê,buÃ¥b*¢|Ãðr¢bÖ>Ö bÃöuäå+~ Z>bÖCýßÄ,rð.h|¢:.U :£örußa<, :_¢aUðhvðÄCÄ+hrÃO @ß >@.,öâ<*bfv", - "öß.bäCBb.¢Ã||/ß_UÃ¥obö©vÜß_ÃðCªC___Ã> ©UÃvA:aOÖ+:UÃO,üäbv@¢Uz/oöß@hAaÃzr~£<ßîrvªbouÃ,.Ä~©AÄ*Äð@ªÖ>bobvAror:_,ß_*_z @OÄu©ß|Ãœ| ß>ÖäßöBª +Ä£rÃœb£_U©<+ß.ªa/bv, <><ä*Äé~ä:ÃröªäüðoAZ BböýBoä,îßuaÃ¥uO /*üä", - "£~*åðAð@zu<>>*ßß.C|UãvÜ©ÄA@~rrð_O+ä_zvOÖr£bUAÃ~@ÖOßÜ¢Ãö¢B,BãhUߢ¢ýÖv+:ý:bîZOu_£a/aãA+hî>.U:ðo*ªüã.ªAö oZr*ÄÃ,Ãœ._ÃB,h.ÃœUUÄovãv~Zoüß/£ª.|uZªhhaBßÜOA¢BCÄãå,hh/BoOÃbU<£+BßCî¢>Ã¥*Uª <£UürAvCÜÜuZ/ßO/ü.öã CßöãüubOÃœ@h@ßü/ðu_aü+åªä>ÃC:bîözÃœa*ãZCUßröýUbÖ¢ãÃ@ÖäöåüUrb vB¢Cä|ãÖߪª¢BzOÃœ/Ã¥*ßÖbZÄ_oB*Ã¥AC£u၎~ßhÃuýßuhî£|ÖB_*ãZ|rßð|Ã<<äÖ<ö,z Ãœ", - null, - "1987-09-16 09:14:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("0.2465", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("0.5450", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 1, - 0, - -1475161617, - 928353811, - null, - 255, - 1, - array(("007AA6514374FF12EE7B84A3C5CEB1A3C6BB4A00DA497372E074A6BBA2"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("B86B8689AB16C42D2078638345494012C6C8CBEC1FCFD2660F4AC21EB529A29BBEC712880C4948EC2B67471744D30C0BB0454CB45710C3E8981F796053A678F7D5C7C57D37B7067D80DFC1B788BB223C3EB66D70DCED44AE0429F0ECCFE79884EF4F417C0E8ED4F83ECF34784E0B60DCFC3C229D39071AE78317790EC719AD98463DC83879A62EAFE174310DB9CC5FF8C813F093C4FA4BE7345AF8F714C63124229B3111432863550E73BBC4D60D2EB7B78145B4291AD44B9B83FB966"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "åüäããvzr~ã©U©ÃßäbBÃ~zh¢CßÜ@©£ßöO/ð~A@v<,Öý+u¢ª~zÃ~ö/|Ãßà ÖAbÖ£|ÜÖoÃœ ߢZã Ãœ.>rrröbä@ªÃBäÄÜU.£C/ªzhßãoüUhßvî<öðUãã_Ä>ð+Ã¥C£Zb||r£ÄîOB+,+býßÄÄ ߢðß@,~B.ð©ðUÃräåÃ~ª¢O~Ößå/<>ÃÃr©|zA+Zbo¢|zü>oCÃOOBChBÄ¢Aoü£üB~©Uª£.ýbä¢v©ÖÃz £|b~äÃýrz,r@ü¢ÃAî/ a©<<ÄC£Ö.îzÃ¥ußvA©,©ãÃOzzUr:BuÃ¥uÖ .Z£oB*:uð~za@ýÄĪb¢u~¢ã+oU@£_@@©__ß>¢ä~bu¢Ö~aZÄrÃ,¢ãª_Öß:Zýãßß©.b¢<ürubÃ<~aüåU*Ã¥@zîoîÃbaßaA äÃ", - "ÖoÃœ ߢZã Ãœ.>rrröbä@ªÃBäÄÜU.£C/ªzhßãoüUhßvî<öðUãã_Ä>ð+Ã¥C£Zb||r£ÄîOB+,+býßÄÄ ߢðß@,~B.ð©ðUÃräåÃ~ª¢O~Ößå/<>ÃÃr©|zA+Zbo¢|zü>oCÃOOBChBÄ¢Aoü£üB~©Uª£.ýbä¢v©ÖÃz £|b~äÃýrz,r@ü¢ÃAî/ a©<<ÄC£Ö.îzÃ¥ußvA©,©ãÃOzzUr:BuÃ¥uÖ .Z£oB*:uð~za@ýÄĪb¢u~¢ã+oU@£_@@©__ß>¢ä~bu¢Ö~aZÄrÃ,¢ãª_Öß:Zýãßß©.b¢<ürubÃ<~aüåU*Ã¥@zîoîÃbaßaA äÃu,ö*@,ßÃa@zî*a_ÜðuðCz@î>>,_:OOOÖäÖ*ªa@Äübßz,äa b:ABß,Ö.Ä>:ãhã*|ß*_Aª©ðÖ,>üb@r>£", - "ã+zÃ: _ B:î.Að//oBÖ >ß*ßÖbCA:,Ãbý.~z~Ö_<ßÖuÃ¥>züv<ãäÖ/+.bA<ý,u>åÄÜß/ª*hä,_Z@Ö|:z/Bä|ª@ö£ªa_vCÃœoUzä@CÃå*ð*ý:rhã©<~o_Z+övOßOߪßäê,buÃ¥b*¢|Ãðr¢bÖ>Ö bÃöuäå+~ Z>bÖCýßÄ,rð.h|¢:.U :£örußa<, :_¢aUðhvðÄCÄ+hrÃO @ß >@.,öâ<*bîv", - "öß.bäCBb.¢Ã||/ß_UÃ¥obö©vÜß_ÃðCªC___Ã> ©UÃvA:aOÖ+:UÃO,üäbv@¢Uz/oöß@hAaÃzr~£<ßîrvªbouÃ,.Ä~©AÄ*Äð@ªÖ>bobvAror:_,ß_*_z @OÄu©ß|Ãœ| ß>ÖäßöBª +Ä£rÃœb£_U©<+ß.ªa/bv, <><ä*Äé~ä:ÃröªäüðoAZ BböýBoä,îßuaÃ¥uO /*üä©ßA£ª<ýo:CC*Ao..î+ª_ßOü£>rb,©¢>ßb/£Zbz+¢oÄðÜÄäüÄß", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("0.2465", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("0.5450", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 10 -$values[] = array(array(("3BA705746D92126ECD1A9B2D0866DA482936609B39730EBCA6B0055B6213FA9AB2E794FF914A0645D02EEC51D4A04007592B44"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("D7C89948CDBC8CB70CBA20007A21D1DAB6DF301ACB0B87BC"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("70FE0136CEC23F1A10CC66487D49B819223231D3251F357E74650741CCCB40DC60C64C8EB5D5EB5DC1601105746E61E661B2E9558A14E150C882C169BBFE4E448B89E8B7FD2A634C46D411C40D2F0A060320DF1AE2EB524BE4FBC6331381A1C212CD7AEA86AAE8364E9C0EB6031D439877BA669B1E33619F379CD302643038C5324EB240B520805837A9CB45DD9B48C7B82DBB581A06A4DE6BDAB2E9D7E6D21E644883B46A02E2573A0E6475671CC29B530B4A3675DFAB2CAAD4D5DB6D968050D6ADE19418945CB54A631E0CA12E764E171795CB9B9BA8849D5709952C07EAA38D139FBA3B2E83BFAE94EC11C19F8A7B5D86C35601B4B43A045CF6C18AC78842797B7E8E8FBC13E993D61C07AF97C3FECC8E38A64393A2D85E64B5DE37C235E70BA9974520FBEBA073DAC32B98AD8A6D99F8F4FC8FE2FFE6D9A5302B0734B1D0A034FB06FF7DDDD0A2973269D07640B0D948453FCD385829AAB16C7BCBC0993E29F28A63B2846D1B1A245AF7AF951562658051BFEF720367DCD25C4D106205E1FDF440DEECDDFE55DA2924A9DE6079149C0A154A6FDA4FB6EF2286E0281F0BB81F79F9BB5536768882FCE4C64D59897B5948B1064CBA7223A489EFC9561D2DBB1E6C8A6566BDBE65948DC872F5896995990B87DF6D046B6407D616CC47AC3164CF29A7E1CF23149B894C72BFF1FF2E1B2059304A964D4D1900255E90A6F11DE75C64A3CCE187C575F7EB552192AC7207A8FBACED7BE7CD82DC8EE1A9C583F5982E8A638D5B613A9893BB53ABF72B8654B256E8AB442F6B749621F052A51BAF73F6BDF20849E5AC46C0135AC093218BB0088DF76F9B4A53FE1EEEF63A71E7F3770BE0B539387E0ACDAAA1EA31D5ABE175963F4A182440D7AC69F3E95BEE71D4D920E2B5B36C51778A8229C4F95F2229730C81A4319DDA53CA0809D04494AFDF39CD1110F3F52D0845C8BF96FA7CDE4C47CA764E6CCDA33B468EEAAB638132F6D24A8B2C4E11357DDE795F69DE43584E29BC887A1D61726B2AD5CA640B82A5C395C9C4B0F0B2FD3594F96FF36D395D03369C81A6F8B8D871D81F027847D1725CD39BD3147EA75D77BF52E524F17EFEF078C62D6E00FFCBDDF053D8D1AF84EEC3861320D26648848E3E0AE6A5D2B47F592111FF80A3035BD4D0F5232BCF8CC73A64219DDD5E2C8930429F30F618250B3CD37EB09D856D2083B84B01712458BEB242AC41B017A2FBF488CF3D03D364BBAAEAE75751DC29B5F6011925122A180BE9E07EB46BE45A243183BB15B52F1CB0539057863D016DD518F74AAFFD7CE8CA6FC4542354B050C68FC326CE2114FFC6E569D182D392CDE872A89B7399AB26B49AF0A6F1DBF85BA33F90F67ADB9FB404F783CA94AB66F7DDF66BB0AC14DC2CE49C7133306D0EEBEC0D2D42429B530DD3F3977C16E1910934F9C9FA7048AE8F636312DD696C8497E998DD867D23B441A35DDDB97C6016030A67FE37A3147AA45F9DDFDE8975A3676F00927D030B8B11448857126AF512034D0ED3026D4CF3D51D3927E044D5B82DBA75497CCD98FFFFDCEDF21BBC6839698ADC917AA2A1F9220CBD60CAE3ABCC6ECF2A57F4AB9635B40ED3258059D818B400BD5568433D8A197C276F1038351B3020844B921144BEF4A6DD6BCE7B79C834A4ABCA2656642A919F3E788427257BD9B8F45FD58C85A3BBFF60C166D2BB9D025F3A6B8C6BF4DABC59BAF3D2CB3ACC81619216861AF2A81E080E27F5A2C4BFE3A17625EDB9EF6DC31AA27CD292D1F0C59440043FA516E86C914C845DCDE51994FA0F315515DFC87225526C7776D68C371E626062C0803D8A8DB85B6C6A10F63489E7D783A09AB0B578911A53AA70F5BBC04D3F6B84B6D379D1A6C75C835B07155B37EE8EFE089683B2ABEA2F66EAE01DC2A0AC1DD08AB0D2135EB1F9D253311C8905F7728267BCB7B6BBB9D8CCA413E7C6AA99E3AEEA44D1861B7AA0569DB15E7ACDA560F26D9A5BE3F46715D1D8066F4ACA3227BA4E6E9FC126EC66EC1C60180A5B5EFEFEB2F559103DFFBA822EFFAE150B568C6A89A32C5F069FCB81346FC7934E0B7679077197D4D8177C4EB41C92273D50E1C5D4AD80B7329FF71229D8DCCE3826332389340E9ABEB8917226DDABE90DA443E38D01BFA08ABE47687984AA003BAE168DE96F9E084247071C1B0E9054F40E058595F84036679398CAEDA621390D913678C71D009E063AA146FAA3847B2DF2746A7F246B426DE02655D6D31B9C25CE0E8AD8188DD60D677F5A57FA3BC30E1D9CFA32B6B9E826F30AE7F6AAC7321F04202C94FAA315659D5C17A511EB73A46347B936206A6F142664E40DA5DF639F4D3A52D90F2F137987949459A2431408974393620EFCFE9AB1186D96BF07BFBDF97758D6989A94333440AE8CD51572E34FDC76624C0091503CB727F7C17DBA80D5D65F2AEA3E6FFCAE5F2ECE4D72C59BA68A46E3905D71F9E6079AE1729D72B6852EB6FEB648F8116C79B4ECD1C303C9F28C513637D7EF78656643E3A48E8A11ED78481FBBC7E55D92357616A885272203D0EFE64B3B7FAE3E3E995004146DDEEFA3FE597BE40E2AA7F3A40160D8CFE03E21E0D39FA4C5B1F899E07C2E95597DC898270586CEA71FC2C43E58977D4E656FB811E334993E4BDD4C60E10391FC11992739C7B1C9597079C111DB656D1A7B4B7B0D758376492E59DC28396DC51B30C72CBE6F4486B3A55EA169738763E67032E5968884878E23DD0A6DD5A12003B3A6E01338AEAA36C4C76716E20DD6A7F6F6B707872F92AB3F9E9B913919DE043E9DB5CDA74FADC187AFD2C11D71AB15076377DFEA197C841335D4991218FCABE962D8455708FFA648F580D85CD18ECEF38589D6A1FE1A252131B9717B14AF6EB227D8BA9966BEFF63D197F498A42742A605A68D8F2609EA48B223254F8D2A3446112535F9D3358CC68A73D067258C7D0977CB756D21DF8C1DED71E64398AFD5A663AD877EF149D3D019C604B339832226A51CD8C2B5C6243299180B614E492EDB9D2A047805FF172EF7A5545BDBD9E46A4FC09D7CD712F19C8854E4D93D7181D09828150F7EAA20A604A9CB0F72C957C033A0128EACB1038B3867EFB01A3EF0862383C3B2C3D4EBA49FF6782D47E12ABFFC24A59C5FF223E6258AD617BB6F8CC5B944EBEE06A5FC5FB46EEEB638D543573C30B4F5985427305FDC8D39FED4E92F69B27402F63CB8572746E7F0046DAE4F467FDA8BBB962D651DBA8E2A17C360E085BF4C8561406000775CCE516C362CF22D3DF37EF95D73D2850FE53BAD8D3F64E5076B442128EE051ED9135FC8DBF8E78D9CFCD9D0E2E6274B79FF515B2553E91C6A6C65CAE8D0044FED5F1F4A140AAA17FDAD11916593032044F9A7289CA9F98629425E6F931DA5CBF78AE282C6DC366E553A5E774158B6D6684485EE28DCB243669821D1AC3A83E0084599DBB1805968C239D3624B8144D08B87064E97B6A21BD79F1B2DBB65C21AB67E6FFCBFDB25E7EB1C2CEF0850F8E55450452434344851A27D0411652BAD5C77B8ACCD124810B0621E637A89B31387A736D96A1F9DE681351D5942D71382BF6FCF7850A32AD20B3F95C215D1CDC8EF6A7C6A2E52423DD9D12A97F4EECED94F9CA40BBA3AC2E37C63CE2A8ADBD0B068556AD85AA8536D2C929E7E7993DE1C71BB72792CC10EB4A799EECB8318B2FE007A2E9F84F67FA64837DE279C436AE985B1B9CE685783A647B530C0BA88E914DFA07DFE643E987A3CCC31F5E3E618632F0545D25EE496A9F68858CEC5BEECBF4ECA48FCC66B8547193E929C268EC3609DCD91B54216B5FB0AD408DBE5C9B57DDF71A7199E1C9DC47FFEB92051F98368C4F830F82D367C37144DF2F162A9614F8814BCB4E7AAC2812FCC35445D94A91051A017ED99BC4FA9F46F04512AAB61D69D6A973C3DCE811A35AEC8610138D97FA3E1A0C29273B5CCC513C077A57157846B5EA7F3FD6CD2821FFCB8EAF4EEA7BC9685AD480B9E4429B4CB772A0E0975C45644F5A291E682370E1DA395BBCBD7D66B04E020C40F79CC197D3E31F5A8741586F393AFF8042AAF027539EDFBAF6EE9F3A0BE0928550047CB298913A7504266A88E0FC73728DD67D9311D4ED68BD16A0F553E54EA6AEC31F34CE4CF7B1707B233D3446D9FA6B2CBC25730F737B17140571E035212EE7B3DA1DDD3EB440EEBC1296A1B024664CA68A37291923AF4F942DAA3775C13B5A0455707CBC2D2C687B46B6ECECD966EF9F3549AAF9F5CE903ADE49110A0968FAE74CAD5AB3F980895DCEBDD27CB262CAB6CAA8308780500E86E041EDD41B6E61DDCC05902C0C3BA5A4B04D3B37FC23886B8E5A3F61287356ECAEC3D3B7A2A542504BF4A0ACD2AEAA9EF28BC73324C69ABCB97255B13F00A558AEB38D4857FE021B0ADAFBF772DB98DFBFA06E9CBC23B7D83916FE204F81213B4F57FE1F216236F489AC2859DA9349EDE976018EA2089C8AFBA50F410FD1CB79B9B0B81ADD79BE4D696A130FFB6E0D95DD0046D98F929936BB73237C52F5F4B7E0BE9029EB2F107FC7B41537F59C9F8DA779740F9AF1FA1717715F6A4641FA73755B2F4CCDE3BAADFFC711DB7D61412ED395C4E981F52CEEB4790279CFA62975B9A1C29A7D52BFC3CEC60FC3E02EA9B6303086A07D00F1703E308854D130DE49AD9C11DB9EA6B180E6012ABBE5590E95DF5C499F2F7908A8A0425C4CDA6AF28DE7C616440CCE17898F05CA6BE6458AD521E27574F74F2A3493692B4C4A74B67B1E9CC42F42BA890144B01019AE9CC26AC7871AE7E57D86A89BE1B0A392CB3F1A5F5F5D49716B9277596430473F00860696C084C359CE85A1162B8350F9E56E7970F5CA5F3F78E96BE4A39E813D4F699B6BBA0CD2367BB090C9AC3355D8317D63F5B6C6393626C72DE728EEB6200463C6B1F7F54FD89C330545399593358383A3362A8B7689205534F2EEEB1D9D0A60B5487CA3E04C3D9B6983C0F3FC9133E853953B8FF578ED4D8192B04CF9A125784AC8D04AC8EFA357089F21F2B83C58A3CEF69788517A7B4C5B2F125D8BE091B85E48A933B0E92F73F1D4876656FA85553A1C8E0319AD16C05D93FF99DD55DB17FD79947F9324E2E5C713867CF304F560A9BD137F629E9EB131E8D62CE985A69C096F68ED5615C20529983BF616A9CD48AE9EF010325BB56EDAE33DAA5EB60C417CEA7393AC180A93FB1E45CA5298AA881B8D8E4A958669ADA9FA0262D817A6739BFA892C3639AC86846C151415FB0C3238B59ED127B1BBE680C5F9A20597BBE367D22E681F8E51E1E0F7B629714597BEFDD03A2278D54BDA3E929B603FFA80ECA021AC33CC9422BA53BCF0300B82753B2E3AB4356D997A929654397CE2E1A1E676C9894F9953E259BD7FCECBD51BDEBC2D603205365B8F16B719AD786AA5C7620B09B931F92591DA586A77EDCAAECD7A757D5F82AFACB01B4A72988FA679EF07B86CC0DB52ACB2F7CE67ACDBD3914C084C32F2FD4644B25E59ADE7869D1223DE7B70356DEDE227D8BA57C447FFE437B92680337376F1B163AA6281A4B53C6ABC43DE50635BEE1A6A0B2A42FE807A6E65663E500CDEA05779BEC1B38B6073137DFD18042183C45D4865D9685CBEC2E33FF41C4E86DC5DDD9376D6B48A3A9EF99816C4F168764CE1A1D11796F5480ACAEBEE738754348CA19F3A70AAAD1FC30DD6EFD045F9D17AC9B73AACA5C4C6E84B7C1BD8ABB707A5638A23523E059234921E09B01567B4D11A63A9D01997D6ECD852253E91371756DF3A7CCBD67F4CC425060A97BB06D2E3C74ABE481D278BCBA33689EBD063A35AF6B2E0DAFAF90A9B26D5BB138D3F21C17F31674678DF49A7BEA1189FF560E3B152BFA0F82607E8CE402311377C784E6414B86CAEFC5CD21779391C49E06AA5C5F1F9B0CD9E29AB7748A90655BE022B3662A48B3CF444C505ED898EECEBAEEDD57D5B3582C770C5EB0CE55653467C69671DE5E73BEF68C1AD34AAE014AACE9BA9BB30E852CCA87EE1DE90300EFE71BF611017DEF5C7A1E60499984E1C90B1304E59AC981594069A6566F56D4F38B2BEC2EB8F0482FF95E2194AD3C9345A62512D6FE1B541D9A9CD198D699EC24496CBD6E98B2111ACAD4EB3CA8ECAC04D661B04944FEF46FD6CE931B1FB0596071FC4B407E25ACF414F70B29301875AEF0CCC274E8A3E04B774E97CA1EF5FB1A7B8ACAABA06B85B0B4BC344CF3CB3FBD58E52A52EEE477CE1C00C8308DE1D51EFBF1ADA54ECA0D854711C02C6E9357AC0EDA7239CBA92C9713FC8EA0A25523D2E61651011D996F8DD7A50ADC244B6089A81C21B36F9C5351C51692513C6DE2F2EEED465841568F756AE561259E4F73C89B04F786336F634AC2FF3C06061D5069C218EE36040848528CAB49893A2DB275A3CAB638755BBE7E89FB954119886E4C94F76386603CC393BBA5FA8726DA0F954CC7633C6717F467596FAD08720639033DFCF443CA2C156607227CCF613757AEEC00F4522532E47D03FB2CF08EBA20B56CF64A2AE5CBCA20B1FC34459B24052217FE1C7211E7DE0B177740CE0024D84D01C7636BEC2DC92135D4A028FD9FE1DEF609D88F9EAEA385173F126E8BFC208D24E744D4C19A36EA35FA3D3D46B60D779661D8F64B48AF5323DE53AD1A5B9539B41303155B0D1D5BD3EE5AA371663E61319AD829232941DC38F1B1E24788CFDA64038833B2F71C88447C235DED0BE1BCA0F17B80826C158BD126DD6A5B3A144A1F7F3E6DDE0AAE397E4FE5A705E3EE2C0FF442550C4F57F8EEFD628C1AB22D0968D83E6DCD74DE9643420133866E85ED37B05B67F8BE3DD028457FE0A7221ECC22DB3E2DA5815F9A16AC12CA4D23717F7874546A57C511A99B6E4587F5FDEE20233511AA2F5848B651C0504CAC728204D4FF6833CEB4755CCA9591373BD7A419BEF2A71812C64F202C9D189675C5FD28EB40B3F694BA516EC0B5E09AC6107B791B378DBACA6AF20DB04807319BDEE60598694BFB24AE8403A039575CE6B4591C229F7A3E37425D814DA5454CB320D2E1CEDA025DC47F546DB28A1A91129627FBEEEA0FFC64A7CE550BCAAD7F68374F7F66758D0CB2FC57A24B0D8BAD28CC6EB77E4A5EC811D67D270BAD52FAD2D5047B3A9A2B9C081A48F2936DB0571B212C3F5BF5011ED9876E8FF8660005AA5DCF760810728EA0D1AF63016B35A325260BEA80129EDCD4E6A1B22B9BA4A11A4348369D3A4E0BAE9D9E89469D1C6D52CA69D15B168497BEF3A2226FC063052D0D48BE23FE06EBD7003BE374159636CA33606031D34604BFB078AA982C9BEE3A985C38545D4A53C47EF95BDAA123A95C6B573B5F02D45AC744FE73865C52AB644CA8DBB85EE16CE90C9CB2D9EA291E8DCE4D2CD45F8F38C40270105FD7CD672B4EF78DF847F0FE8AEB5252652180DD1AE0E5B70E25D604BD7016B6EDD10D123F34868BF39C2545DE4FA2E90D3CD2A38193F57EF115590677F2CF88303910B9DD4013309AF5E97EBB9FD9BF82EDF6819D73355ADB8F2029DB801300CC9C14A43EE90BCD28A1088BA977C2513B9A838AFB4DEBBD04B6DD4E20E95519E1E9B21F8B358099E12959FB6AAAB064CCCEEE41FD89047EB0579F870BBDAD3E4CEFA9BFC4A3C60519EAA9D2F4F0525A05798251FB141C58E5346E3396F88D247C1AAE7B00F19F2908E82713E03289C0054581923A5B4338D250DA9034936F205C8455DA41D65914D9594913EC0878B2AAAE2D5077FAE921D776A8AF160EF4747806B65BDCAF7E2EEB6DB8E1122F4BCAD827E685651EA9619B3403CB2DA9FECB64D49AE6521CD6FB30FE650942070EA72ADED7EE286E0F595D54C0A8ABB200426FED7D3E956577BB51EA5566B99F1F93265CA04FD20FA2F5C44189FD5C2D4F7DFE229A558ACF12C5661B9D3D19A89FBD352C824F12B1705F4B877953DC7A5E249BD8689705CA08564ADBE8B67264DC1CA1CBC830AB006B4CB23A090355BEE8ECBC78A2404008B76A232B0536C8B9EDE6AB5DB210B12E799A6D1BE4CAC005008BF3FCF9F9C19BDC98145D0C7FFB3483F84C2358F80BD83A8EFF07D32424EDAA1E2B7943CCBFF4240F8119FC5322FA9F3897EB7D9DAD087F1AC16BF9BAC7B77427B3EE5382F1B73958FE4876F2DBE76B05679B8C386F0C7023F9280B54FB6A48C9A5D59F33A61D46E0B2259E65918D3FD9FFF3522B2B3BB8A67D3088C9ACE55C93FFAD4C0647375F4A55AB86496E0B926BE9315407181C9D691DF9B676947D34CDC90A65E3403892C79407D67FB56E38729D1DDF7A90547BE20FE3E94D81E8B5D777B87F63A5AE2AB09E8C7310461EB4AEC044D8779DB93C748F82D45D4F4446AB52FE08FCEFD4CCEFF22FDC04E16A9CF12E3B8D3D103E3ABD7C0BD423DACD615945C7BE4716B7BFEC77E9846EDD379224E5E32E14B7089B13C44A80E300A91425CE82BF1AAC55DB777CA0C526BACCE04479AF3DF69E4CA23C2BAA82CB36DBEBCED45F9F5B7878788F08B69816437559F86A3C6A506B1E8681DF82EE01D30F40A0B4F7515A2C84CB0036ADEEE8B48BF9B6518961FA719B62B6D81D7BF68977BCF875F8614B23FECCE5716919BE2F9ECD548837B04AAD462E19949B820E08D477BFF0F572BC29AE9352BC7C880881A3873A50276E52C6724B2EC948B7887CBDDC9CF259CD392C102ADAA45E8D31BD2598F01348BDE16624F87EAA43F2E1F70AD5FE9FEC1627D64D0D40FD94AE68E41970A4AFC6743400FB35CD604E8EE91D49E399888E91D10E701AC53A7DE4D276909A90D24E313096A1FEA4B749C687BE2422B3B114E056889F953FABAEFA83176356D0B4FE16CF17B202E790ED19C5AA9206155A302061B0440832B8F284E7A67B8D53F2F5D2C00E539CFF11F8C69B86D09419A7E528E742F52070F5E416252D1B40145FC6D2B4BF45060794214FB77E6DACE85D6B40C152A610FDD562CB293D76D1F6B7B13BBFFDE05961DE215221A568F287372DC7C1DB7FC1B9B5D5DE5D7ADCCA1D505800CC32ECEB7D12DEE23153EF576D6F43F20FAF0F1B37EF810A2701FCE7015735D04C6725554B4149AFFC9D9066C57E71ED36EABA6E403CC8DDB1E1C47C16F6E1DA1D1F0A0549EEA6CD655F1F9A5F1A45A88F0EB0169CC790109F48A3697DCD9905BBB548A5746CA34743EC08E01D2E7D969B1BEC0746CA3E568FFA66014A3D5097C619DEA9D19F34236A69D2AFBEAFF271CA0DCD61E091BFF177F2167905A2FC069C711A044FD3C4FBE6C89CDCF78E97E008F5363D6FEEB63459ED32DE89BA1EC86CCC134D94ED22F749D17B18F4E3F9E85B3671106CBF97AEBAE6AA61A1E5722BAAD4702B7B4A4B1AD23FF49D739E3F76F4498352480D5CAD346592AE42C055635F4F6D2072CD6D11EA573D2DCBF93A1BA6346C57621FE39D78C3F716B79DD712E9903E24311F0326DC7267F1CF2FE13A2A76070E54FD013F6CCFA7AFD657594D802B433ABB16EA8730457BEFB8F1CD49E1BCEB4095C9BDEB8FFEB76177535084181A71A3EBA77F138C80DD0E8211877A025ECE1A6B18B8B66D58AABC5E880FDDF5F7D69E80295E393CD8B414EBAF697E773D5F198535180061BA3693FDC2B4037100188A3AE9FAD80095DD9364A3CBBEEE2B452129F558BFDED63CC6F3CA3C7A23B0A3EB96DE438E438719EF58849CBEEF77A4D17C93DA7BA759AC64AD33E435586270B3FFDF2710E8B7DEA1DC68675A3A7DD67B256BFD90D627433D32DFE10FA2E396F514E8BE0F5094E9C5B4389F3FF151292F5D680FED64E0DDD89BEADD08D6CEFFF8D1714551BEC72B918138DB9D88044BE0B5B1B239D4A990340BE67465B7E0EFE639A66E800CD770963C7E6B2C043F50979EFC52241CC52428E01146D852C6D9A148F564E196CAD4DE401CFC553D642FCC96DDC7BA8DCBC97382459FFA91974376A4CAE30FDA6E01D32874E300C8CEEB7CE27A4275FE1F137D2BF05E502BDA4CA9D886B0728020F127DCF39119E7C8ABB35A23181655CD7785B766E3E910B0A84E273A27421E78FCC1881B990B8DFA956245ABF507C136530B0E2BBB193C07172B4A43F1ADA16995E227568D3A336CF7BCD080A465BAA08CF1E61C34F2BCE28581046EC04908E8C1DC1513E06BA819D7A134FE48BB63B170A9B700C68434A22545D10C35C5ABADB4E8BEC74FA7C02048CD4592BEBE99C91D52FD169208213DA773EE9B9A9CE311F5864A5FFD9C62C9E996B7F93FA2E366AB07EFE3C8951BABC7811FDCB82586ACE9339C387339CC81EF332DFB5127FD058F87EF92AC0049606A9E987B7C8610C3100A100B06ABF3723B6EF39467205AA0831A4C3136F300E9EA3BCDD4A15004692369EA4716F2FEE3947D573E0CB4F94A44F7F76139DF8D1C0A2720CADD1ED0EA698A71FD351289911B689CB99CC1DC36428390A959A33043DCCE3CEFD1340676AC6CD58243FB15316E6645D042D480484AEA885A8E2691C6592D2167D699BFE762260C30EB5AF2466E5E3BF25D6001084EF3A5E7ABABE885DA0E2816DAAD5BD4A75E6D1BC7505DEFF741EC30EB9F69F92B1819E2F4D73BB63E8755DC32DC6CC002664AFEF2E541FE96B6EF6ABFB8993BA52EEAC19479E0CF22B6DC40FA80BC1BD52B2D85546A0745D71A4B3EE8D1F786ED193704F1AA2B88857ADB3B08000316F9D395938A14E73B27A4EA7A2EA2E751BF5653E148B8292A00A5FFD98ECB3CD6173444143C63FCD832FCDD4DC0B15AE3938A7CB2AD21CD41AA6B93E4140DAC5B671362F95DD3155B5A7AAF4CDADC694E4B1B46044F8A28F566036AD0D017C3244F81E5AD4F6086BF74205833728166CD014499F9C6A70BDDEA6C935969448B42B68AFC59F6BCFFEA95878F2F32F583E59D9CCF8B7DCA5DA87508C2E2CD2C563094157CE7840CD74544AF633F17DCABF5E8ADBCCC75FFC5EC2D54E3EDF91C1D68305F9A1C789C6090C58946CE661D90995DCDFA1F7089BA945090B93029E94B2892FB0FB7418E7C4B4C2B07ECB8F7850FC34A902C22FDF8A7A505CBE7E33066DC3D1E3C6B0ED957CE50AED24481CEDB590BAA6DDF581F05940267123CC294681CC42BAE9ABA1900E9F3C1DB742AB325D62972DC38C387831E19A768A6C71B0CBC46B67F5620419EF1A983EB974391694F423B1250B82F593DA8908013B15A22D8B51F2D7433CFB6E8D4EA8F3F9BECDA883E19648B08BB04165C42AD8FC08B404F6013E12463F41B7284297BCDA019A1BCB5F8D597F597CFF91B1718F121272749AA40DF880AB52549FEF107DE40367642A1F1D9C8B6A859C944E7094BDC29505D023222603A19B100D38B99048FF0B9E4A1D550304D5F9202E6164323ACD96CD4A0A9DC0D1316088740D9F42A"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "b îýz¢åÃh+ö/£> ÜöbZ>OhCv,a>a_åðuv|Oßa£BvßZ_uCÃ¥výÃ@ðvöörÃœvu~zCäoU/ÄOb _ÃœzýA©åªåh@Ã@vAö©vz+,_ÃœbßhöhZß.ÄO©râZðvzO,Bb|~ªCzÃœ/äÄðýývråÄÖ bÃß|äbz +*AbrhÃüu|*aýaüä:ÃrÖ*Bb|_äz*ZaÄ_ßü.BߣÄäzZüz+à bÖöBhã_üZZ<@*ã ©uãßraªåAßa*/bCÃOß@Uª*ð~ÄvýCß*h+aý¢Ö<|_ :åî", - "/ªZ ýB|ð~.ÃœB>öb~UÜÜhCÖîOÃZ<ßb£@håöÄOö*@C+Äb>Ö~£+:@A*Ãã©ÃÄC~Ä¢Öäðå£r_uå£åU©Ü,Ä,/o¢B*_ªo.bZA<ßÜß>¢äh¢B+Bh~äoBbbvýzÃ.b@,zzÄUovr,ãC/oo_~Ö ß@öÜÃ:b ýaAoh*Ãýu_ßîärãðOAb©îðÃ._+¢||¢¢ã£îOUOUÃÜÄOðýÖä:~©a~©,,/bv¢ªÜÄo.ªîüUüAªÖð ZöÃýãÃîAÃ¥_Aü/v:Bü_¢ðb|ÖzBܪ<|bU© C:Äb_üð", - "/££ðu.ZCð_Ã¥/ßo>o©@/av©¢>:_ß@+B:¢vª*AÃÄ*ßB/Zýä~~aã/uU,..ÃœAo<âÖÃãBUoOß:/üO¢ðã+¢¢åöÜ©öåruäO~a~u©O Ã:îª.,u,aUbÃœ<~,,@~ÃäBßöåazau:ãÃ>/ªhðÃa:_|Zö¢C:+~O¢b£ðzUî>aªC,ýðo@| Ã¥:<£>hOzßã Ãœ|ð.AßÜB~C*u/Ã. h|*ÃœC_C>hÄü|>Ãa:bouArýöC+.ýr_uAðbßbÃ/bru:.v ãu~ãCîov>,hoªU.Ã|îüð©b<ÄÃZaýüa~ÜÄ¢voßhB¢bÃœ/éîhr©@©_:*©ßÖåhßåOz>ß*/ýüUä.ß äî¢Übßoß : bz+ö<,öavÖå:ðîz:hU.£vÃh<ÃœOüã*©bääOÖAbA>äð._|ý/Ãåà a|ðßbzOÃ¥OZ/B£ª©AªÖov.CÃ_Äär|> >BBa©>ðbC/äOãr/ãÖðîüßðÃ<ðîã+BOCª,äß ý£ðoB_¢Ä,Uv@CÃ¥Cv©@>|+¢bÜ©|üðzUÜý.BãCýÃÃ¥ZzabâzUrbäöä*Uü+|o>@~Ä.o:ZBAbývð£uv*_,h* Öä/zî¢OC:|ãU*êªðu*rrobªAuCü¢ãoAhrããüä_£ðð~//©Z_©Ö¢oßhC~ÜãvÃ@r>¢,ßCoãÖ¢bÃÄZZ~£o+ö~zäraUýbvbC~ähÃ.üb¢_CÃðC_zö._hbîA<åÖä,ýß*ÄbßÃ+ÄBAö<,ßßohªä£ãzã.ZªuO*îßÜ©/rÖ@@vüh~ä+~©ÃCu ÖoaÖo@>zßOör©h/äÖ_OÄhßCUîbA>ov+u*A|,å£åß.äv+|bÃœ*,ÖO|ßzaã£Ã,¢uÄ|ßhÃä£|AýB£+zv|Ö©ãZ©B.Bß/bAÄÖrUU~Ãz:UßOhÃœ*Ãœbð.aÃZÃ¥a_*_¢oÄ ZߪãZÜã,ܪüC@ä£oîÖ£AÃ¥abZ/ärÃîbbhÖî@+@ðbýu@£,Ãœo+oÃ¥O£ä>©/ZÖ ./~o<Ã_ß*ª Z£ü©©ß*+©@v++ÃåÃv~>Ãœ@¢ÖöBro~hÃœ:ZU©v o~~*+bÃœÃoBb*+abä ßöb:UB_ãubªäuhÄ.|~©ao.:äaÃœaCßuhĪü/Ãräî>A>haoåöö+ðß/+ão,>r/:b.AýöåvUÃüuéoü£v:ÄhãbÖå.Ã*ÖßbUa///¢hb/£ÄrUuÃ*OC,/@hß oZöhÃ¥zðr@hß,üz|ßÃBUªÃ>Ã¥@:@za£bÖu~Ob¢+@BÃzhÃœ|î.ßÖäZZu¢U£ß£Z.o£ý Ãœ~Ã.ÄöýC£hüaî@uUhähªobÃ¥_AZªaßC,Üýa©O~b£Ü©/ü:Ü¢bAÃ,CB£¢ü/ÃO+. üZ_ÃrAåãA©bu+öÄB/Ãœ.u~Z/ýzC+Au>,©u:Ão/Ä|ªrOÃÃbäZÃv*v,ürÃÃä_/b£ã¢_v,ýîU|©Ärßa.ªÄö/ß©ÜåuýÄAÃazÃ¥Z<ö*_h:£ä,/Ä©|roOzBh~ývýbà v©üOUvÃœ/Ãä©ä:UZ|/Ubu©ß|ã.ðozZuüZßßrüîrr/Ã¥C:Z@/¢îZ.ßÃa@©:CCÃœ Bî,b£b ©Ub¢ _ v üZvaä.uZZhÃäß/ä*.üü,h,üãüÖ|Ã+OChãÜßAã:zb©CÃœ> :åßOZð|+Ã¥r~ .ABã@îBa~aa¢ã>ö©åÄaª:@>äÜ@Bv_Üü~uÄ..rUouÃ<Ã:Ã¥o*Üýî<,,büå£_äü¢ßr:oarü,.Ãà özr+h,~ðhßö*uýüðUãêäãÄ¢bBAözOuÖÜb/ÖÖ>>oZA>ýUCöaUAh|bB:B~|zA+¢ªöªO|o|roà _zã*Ã+öoaÄZA£Ä©¢ Orr:Ü£._,b|Ãœ:AÃ¥.U@ܪÃär*Ã¥*î ðö<Ã¥_uAü_äÖ©/ß z,+|Ã,¢.rÃ¥a~zaã~.Ãbª:~©Uuýîä,ÖCOð@åö>ßuöz@Zý@¢ÄüÃv ð*rÃ¥ÃÄC Ã>ÖA/Üß©ßCÃ¥b¢@hzðzÃĪvöOC/rãÄ¢hU,:_/|>ãuÄüðãßrÄ.rîü/A,Ã._~îý|ýüoý£ÃoüÃÖ.::a¢*B+îÃߢߩÄrýÃCåüªã£ÄýÜäh@_/@o_ü|+Bb:rývÃœ+ouö~B/U@*_ãBÜß+ýuAüößv£|~bÃœ,v,*Ã¥@O@ußa ªÄåÄAbäåaB+_Z£/a/ÃœUbýäåhÃ:rUzßvzhîö©C ªß.bOärbý>ÄrCÖª/ ÖãÖü~ãª~uãb,,ÃÜåBo/£ð©að©:Ã@B©>ÄÄ|Oã++Aüß<üAh©ªßö A~Oß.bßßZCoB>_ *Ãß>ð>Ãh:ä/ÄýÜaãå|Ö£ð_îoCA|bîßÃý.ÄßüßZAßz/Äöü+ãAvZv@ÃÃ¥.ßÜ©,îö£u£o:ZvýAb~_ð <ßüOÖO@ߢ_OU_zürCîCå£ubÃœ@b|AUu©ýß_ðýß>£Ä.äªBýuÖüAãðÃ/bÃvÖr£©¢<|,Ã¥r/ý+ý,¢U,:ߪüßzz~a,zü|Öä©uªãüBðZrußÜܪA:Z:Aªå£ß.*+A.Ãý__üüöa@@hýa.r>ªb:hB oB©îZ©@OÄb+Ub.ßãb@uB+C_h©*~Z©vb+z,ã/äbÃZ>*ßbªÜ ßÃÜýöîaüb_@uöBu@/AaðÃ~+üýßöªAZ<_îaß~Ã¥ZC:Z.Üî*ãÖrvß+A~ä£r ßýCü.öÖbý ðz£¢oî_¢vß:¢obà rî/öb*~UBZa<åßåªrrhbu,.äOãAubÃã¢bZ~Ã,_äîbÖÜÜ¢Z£vãÃaÄzåßüo+î:üUßÃOrZrUor£*Uý:ª£UîOÃœ+*Ãh O:¢|A¢.ðßöA~/ý/aÃ@*+bî**>haZOä:aAÃ¥CÖ¢ªOAðzO@~bßîÃÜÃã,ÃœvvCvbå¢CÖð>A_|åÃ,b z/ÃÄãÜ: B_.Oªªvªã¢*_Z,ß|Ua_>Uab+¢>b*@|v>ª|@| Öî/ZöAbb_AðªO:ãuöbB>b|B@îbZ+ãöÃÃ¥UU<~~huZ bU¢rß+©£å£öCzrÃv<ß@vo/oBhOªb|äZbüÄZrÄÄðZ©Üüä@O©ZÃœ B*Äao¢B@b£ÖªbA+ähªî>|Üäu~, ZUÖð+ö_©BO©Z@bÃö>£Ab,ZÃœ >rbU,+B~Ã@Ã@ßðö/öaz+ßa¢©vÃbC@:ªÃããÖÖC.ßBbu@BªÃÃ¥zr:,~<<äb@oÖ.ßZÄ:OOuÃœUvazÄrz~<_b_ãÖðbbAv*üÄUö©,ZÃœ@bð.>Bbð/BÃ¥vOv|ýBr|b*£,hðäß_Ab£zî@b.¢£AÃÃýªüoýOÄhUä:ßu.ä©Ü>¢z_î+:|+©ro*:Oß++b/üðÃB,ÃœrO>ÃBrÃãh./hbu@ª¢@@ýî,ZýäbßUß~örhCovb©|ý.> Ãœ,uZßð+zO~.åßî>ãb,>b_Ã,Ã¥rZ+o¢vÄ¢vÄÄîöB/ð¢ðzu:,/v©~ð_|åÖ¢üãuuÜã@~_üªAÖåUÃœhu|.O.ßäB>b Ob+üböv:uÃäBߢð/ßuîÃÃÖ/£OrÃœZã*CvÃœO.<~£üߢÜ|/+<¢|<+bÃ¥~+¢/¢ÃÜäOü¢ä~~zªu:ãOUCbîOaZÄ~uß:zU©.|ýãääC¢£oÄäÜoA A_ä¢|_åÖð:¢rCßÃu.ÜãÃ:aO@ävýB/~_<ýîýß<ö@Ãb/UbÄÄßu>+__zßðÃZ¢¢Z|Ãœ îhrrãðÖ:vB¢AÃœBßo<ܪbAaOrüv@ä uOZBîO~>~,~¢BvuOîüOß:ßßOZzîüau|.îhbð öäðu©å+a.ðÖüAbz Aä.,Bß~Z¢>*ßOÃüuZãöBv~ßð.oÃ¥+hýüovbäv£@uhv£ªo_|ã©ü_©rCub:b<ýöðrh¢@O|BåÃ|Öªßߢý¢,ã r.ª ÃœAba.uo_Zrb>ü£*aÖßZüî~z,::.BhªÃß@zÃo>B:<.u ¢ÖCaýAã|.h£.rã_ößb,©© ß|Ãv©£ :/bzbz©åå_.ä_@à ߣo>C bã îb BÃöbar>*AB¢o~Cß+ߣCßCuåª@ouh+rühU*Ãœh~*ã A zÄAýîa@ÃbÃßvrää _äý*ýßC>ðU £Ü/ßßßßðo~_Urª©hZ*ühðZvßý © <~_b£Ãäîªåý<,öÄÖaªßÖ ©ãhöB.+@£ß:hÖü|>ß+@îaßÖ,h|Z<+üb@*ð@hÄ_aZA|h:ß/*/ßAã/öu<¢ß© AUU,|zßoß/.ßv+Uä/©z~Ãuã©*h£~C:î*Auýbßo|hüZ@ý,u~bÃ+Aö>b£ß,|A/©Ub+_bÖb+rbZCO¢,ÃãZC_~îÖ.üãü|O£UCC:ÃUo:z:_Ã>zÖß /UC|vðîB*Zö~uߪãª_bªOzvÃ¥röäv/ÄZho:äöÖ @uz>åýö,/öbzîüU:î©ö*o/Bð/+,ð >£©bZAäZÃ.vöß@îrhßzåÄ|Oý.OBh,b@ªAbðz.U¢ö,orUÃ,*ðOÃz|BAªö:©£BðÃî¢å©ðÃ~ªßåo*>_*özaßoü£Öö>ý<ýäê@A©v~*a<¢*|ð/,Ö@bª>+¢ZÜÖUåöoðUªÃbZ¢ ßZÃœ~Z@Oß,Ãz_Ö<.,**üUvîAÃ.ã|,ÃßähÖ.ÃÜãðbBBö+>|,,/ßu,Ä ,bðUbO/üßb|Ã¥OÃœ Ü£_/väoz@@hA/C|@/hß+*Ãœ@ÜÖ¢hZäߢ,ªb©O©ö/:UÖ,Ãðhýãu|CÖ+.üýCÜâÃä:ÄåÖ.+:,ä<Ã+hª+ß㪣zbzÃåarB©bÃ¥vßÃ+Z©ãOâ+ãâbýv+¢zöBý.ßoaaÃuZÃœ ýuå¢ÖzÃü©äaðuªbßAðÄbÖöOßz~h©ã/> ª>Ä.O+@bð*Zuü<|_üOÄz|Ä~Abb OOAhu+à Ä£,©h.©üãßaªÃ£ÃrÖåî©+BA*åårbãZoã¢z@bîî~O+BU>o~ <+ÄßCbv¢å*obZäå>b uÖÖ©ßz¢ürr,ö.¢b*ßüvä@/>ð¢|Äýz ©ðvãB©ðã~,OhäbUöö@AðbîUußßUãAuª¢£ä@:©|_+/ü+ä©+|/©ÃªOö,ZÜÃbaz.Bß+ß_b_+_:ußo*ª.Ãv~ÖÖ¢|uü*OabhÃ¥|îå/UOZÃ¥uU >_Bªð/Ö,är/ßA¢ßÖÃoÃ¥@z.|£îvð+u,,*@/hah*.Ãv©vÃhCäß/.~à Äabý/Ä£@+o:ßbv~<ߢ*ÃhU£oÃ¥@Ãœ.Cößß©Aß/bAÄ¢ÜuÃ¥@ßäbãv ¢ßuýr:*ðBðöU,/*£hbavBu~B¢©h:hzßðýüC_Z¢O+Ãœ.Cb:vß*ÃZå©|ý@CÃ,ö/î|ÄÄüa<ýOOOAªO+uh_v£ª>öª£>|Ãuð¢O~UUb@Ba.bhöoUvð£ßC*ßo,hC|Bb<:ÜÜv|ý/+özÃA@ä|<_Ãœ++~ðäB|ßArCC>.a_üZ+êvß|Ã@/zvzߣ|ßÃÃî@î©ÃÃöuvßßvAýåvCäoB_au*©U|Ä+:_ÄÃz¢:+ÃýäzªzåÄoZZ/|_Ä¢£hÃ¥Z|a/OUäßvB~Ä*/Ã¥.äzßöÄ*¢>üvZßîãBAoÖoßÃ|ÃbÃ>ª¢||öý©hä äOoðö>:îöbî/ý__ªvBÃzªÄuuÖýö. ZoÖüÜOý~UäOBßCÃ~o¢aa Ä~.|ÄhüðOö|b|£åh:u+|aAu_rߪ_£~+ð,ý|~*.bba£bîªAu:ß/Ã.£/ Ã*ä*î@/Z~~£©~BzßbOz_+ü ðoªoÃrîüvö©åuaüßüßv|UÃöbÄOr_åäoîZßÃ~£Ão, ©u_ðüraü+ |.>.î/©OAåÜBoªbÄÄ +ou¢åU~>,CÖüO|>@ª>Ãœ+Ãœoý_h>|~bðåU>êuCö_vã,A_/bÃr~£a|ð |rOÃ<+Ãý::>öZ.©ö@@+h:CBß*a,î OC.b.:o~äö|Zð~OUZU*ª.ßObÄ|,| ýb|ðoZ+_Uýrßö©/~oO>¢CÄo|Ãð+ö* ãbªväåý+ÖzßÄbvoß_uüü.îÃÃCOý/CBhB¢ hBî©ðBUuüCÃv+hu/ý.UªoÄob Ä@ vu@ã+¢ä*ªåZåÄrðªýäßär©BÄãao/öã u|îöÖªååÃZäãBC+åüß_b<å©C~Cßý*:..Ö_bzßUoaBüB~*ãa~ßBÜÖCr@UUÃœAýÃÖ,AÃ¥@rr/ßÄåÜ/|/A>Aãß*@ßaª_~,zab/BäZ£bßB Öbý+åý©£BrÖÖOO*U~Avü¢ßhߣ:©~vÜý©ð@bv@:C¢åðZBv Uî@AbA_CÄðöOÃ/zb+zrbã£hbÄCî£BÃ@A_ rÃœbUÄühßßC é ß_ýîr>ªÜ/zöCîAa.ð£,u@ahªUöAãaÖ+,uzZh_h*ãªa¢ðÖãuÖßÄ,à :ßß.,£v:ÖÖ©ð_åªÃååßz|¢@+rã.Ävbz+ÃbÃœ>>ÃhÖzzöuB@ªU_Oh᜙Ãä_ÜÜBzo|baaýðÜ:ö~ö|ðooähªhb©a¢:Ö:î~_: ,:oÖåuB_Ã:bu_äBã|Ä©£oüU<_:_U_arð>v|BÖ.v£bb/~ªÜbª£ª åäßO.r©r/Ããa©voÃZzýbh/A~B@£åÖ+ðªðÃzb©bhîbrãörbÃœz:bÃœz£@O/ßubî_ß/bÖUbüüBbrOBaðªB+AÃ*.Ī¢ö.>äU:aU+ZÜÜrüýß**ý|C|@UãÃãbor+Ä/CZ©,îUA>¢öboîb bà U>,*rßßBaã/Auuzý,ªåÖUb*üvä|ßC>ßÃĪÃ|*Ãz~Boa©rðäö~bð:ß|bOh.B Uüü@AÄz¢<âÄZz©BbÃÜ a¢ub|*ðh@|.ð~ü CO>¢>ªu<ä.ro<îýC:£Ö ¢ÃîüÃÜOBîzÃh ý_î|bZÃÃœh+¢*O/u@:üZߣÃ,v_öÄ.h|_Ãbã>+B|,+A|<+vª> ö~:*. ©Zo¢aC|z£~ãîZ öä/Ã¥~ðÜbuAað/,:|BrzÖOuu<_vßÄðå@+ O,Bh|Ö<_ªbh¢B©Oãb<£~@oâü*zhbo >¢~vý+._/ãr~oBã:ª¢åã.åÜßA©:Ou: .O<©örà ~~_CîÄ>O ð¢äßU îuzý>Ãh.:äüO/.o:©Aü*bZ,,ª|z£ý|C.|¢@©><ö/¢>A.Ãœ h ªaîöbU,/ä¢ýv.O:uÃœ~*uZÄÃz|@Ä_výªZ/üAý©O+ uBßä<©a:bߢrýðbÃ¥Z~,@@ãOÃ¥AÄã:h<|ªuaaOðäßå>v>/*,Z>îoÄbÜãß+ððãÜã| |UAýüîäü.Ãœ>av*Ã_¢. _ýÜ<ß+r£výaAbööðrz. ªArZZvß|aO+r+>a*ßÖuªÜvªü,Ö:bz._uuBÃ@Ãœ|z|©bh£@*~BÃ¥hÄZBh._.B_ª£Ãß,A/:ß+ß/Aßå/Ö.©îåZ:ðª@z_a|Ö*.aÄððUâBåÜÄBÄ©:uÄ£CÃO©zOßråÄå:U¢:aACö>@©bßa@Ããîãä zu ,Ãhbäðu/@B£Ã", - "", - ":©+©ß:öZAã_h aßÜÃÄu*<ßh~|ý£ruu>bBÃœOöä©~ZOÖãb©jklßîö¢bä©¢_ÃœBßßÃbß*>b|ßåðA", - "oÄýbðvß_, vÃ¥auC_.b|ß:/ÃAÃOU¢vðüî>BÃö¢<ããav*Ã_:<ß:Z<ã: <Äaü ÖAOZ<*++î@oîöuÃÃœrBüAýC~bCzrä£r~£uîa~aA,||Aöa_ßb,vÃð@ßð/î|A~.ÜöÜåÄ©*ßb+*aBð<üo~@ uᦁZbãz:Baý:üÜ¢vbzÃ¥:UzÃ¥ î/Ã¥/<Öý>vOö ß~£ßA+ü*+|ChãÄBv£äý@Ä£+¢öäü*a>OßãZ:<©b.Ã¥ @ö*zÖ~Ü£Ãã,r/¢oraÃ¥O,ª Ã¥+ z|Uª~ª/BOOÃA rvrÃZ£Üª,£Zv£OU>Ãrbýü/A_bhUüBv:Crßbü@*zßhã,U@C~ÃrßÜ>ß:äh*î©@vîßÜußÃCvýîãZ¢ãÖ£BUCbüö~AýZÃö𪪩Ü|uAªaÄ/:/uððuO¢BbªÃbB@©¢@Ä©CÃœBCä:+ðC vvᄚr*+>Z,/ÄãäÖ~㣠*£üý@_*raß©>Ã~£/äývB A¢ÄzvÃœA~vÖ|ðvÃœh@ðz<@ßzýb¢~Öýãßr:ßÃA_îð|BCu ýªvUß@ððÃÜ|@Ä¢©©ÃabÃ<_Ch~Ã~¢aÃ+,ªýa/* ö:ðä@ÃuOzîuvÃ¥bb<ä/>h©bðZðü,oîâuz*b~ߪA.î,.Ã,ÖãZzhh~ör©å_¢ö|Zbv~Zaö@a>£U,bUv£UîoCBãܪbZBÃOAO£üUÄããã:<ªðö.Cßß@*ÜßAZÄä.hZüÃ:äAö¢£äO©ü>/*ýZvð|Z.|a_vu/+|//ªß,Uö~¢ü|+îÜß:ß+A+ý>îãÜßh_z:ßZ>/ b*ä:o ßA*Ã¥r~ßrB:ä.ý~ö_ÃœaªC.vOovÃ¥~ããbî_äOª C*:ý>ð_ð.<ýß/.ÖbAOr+*¢bU@Bbßh¢ª+*ubýäu>A*~/î@Ãœ./Zã>©ýuý>zu<,ãÄßÜå¢:r ÃœußÃOAÃœOoÃÖâ_zhö+CU~ãÖBO¢Ü©AÃ¥zrC>ªÃüßrrzo>ÄÃro<£öU*üÖB|b~ý.ý>Cã:>@bÃ/ü|ðAv_oA.ü:zAB¢üU+.A¢,ãÜußA£vrÃCßrüÃ.ð<_.aOaöÃÜrZ*>ßCö_zî䢪ußã_ ý/Üð|Ã@Ä>,ö~ýAa:*<ãév_>Uäýo<üý>z_©ü|oZ¢/@<<_<üb,oubhäÃOÖå+¢Ö+u@./Z:*bÃ¥rruUhßbüÜîîÜ Öî*:äßãuß.Öý|Ä*é:+ÃýCÃœOZ@äöå£ß ©z*,ZÖÄýbh,,OßUß @ßzÃ*üvB,hüöå*oª. v+ü,îÖ,¢äÄ+ý>Zb¢*ã>¢z*bZî¢ÃAOßUÜÖ:ý/*Cî_zÃB|Ä,£ÃîUýzBÃœaîz©>ZA*:b<~UAzªvUöü ~v>|>ýAhðßðÃü:bh©ª¢>övabCo¢+ZýÃ+C:.£ßBazar@v|îv|u<ª¢ :,zU/:**©äöb_|@A/ |ªz>ÃÖÖ@uÃ¥bhß+Cb©bC,+£©ö**~ra Ö@ßhAaaö£|>îu:z@b|ö<î:ðAüðö+o,o_>Ã¥h@*©*CbÄ,_î:.r£ßzAv_/ZªÖoBb¢ßv.ö, Ãœo+rã/Ob,h>>Öà BhZü:äð~>Ub.ðhhöbbåä©ðß.BUuu¢©ªzî:OZ©/Ã¥<ä~åößavðvO/.ää©ozääu¢Ãöã£Ã@:öäZ¢r.ýbAä¢BüZü:uO:Aßaüß|©ão¢bzb>ßý.åßr:ä~h:UÃœU*Ã>ª:Z<£ðå>b_~ã:ý||ªoZ||/ýýorÃ¥@_Ãœ.<.C:äÜaob@+éäAhhå©OBbOðoªß@äu_CrhOa öðÖ|ZÃ¥bß<Ö,@_<,éZrbZrãZ<@Ãý*v ßUÜ©ãßäb>üaAã/ýzÃo,@ãýüAÃBAä@+ý+£ÄOÖr©b+:Bâãäbaý.Bu¢b>Cãäoh>ÃühîArA,~ o~£ýhhB îo©bö*Ã¥B/_Äbã_ßUzU+Ãߣaª.bö¢üb*üÖ_výãªaßaðÜ>Ã@>ªÜ.©åÜ+>Uob©ä,UC+OÃzZZß,ÄU~Äåå>ðå~ ã|+bðvãvãÄåAB/U.ªzOhÃãã~Ã@h©ß,¢|uüåa<_ ~Ã~uîÃ+ <¢ß~ößårîðð*.COU~vvbða.o¢Äß>,C|ÃÜÖåî~aãîðzBÃœBb¢ ªbäß C_Z>ãb,/åÜ~rz©b.vu¢ßO.ý*,Ãœb£ß¢~Z©h@r/ßÄUÃ¥v_/uC+:ßOAåäýoÄ|öBbaÖ£UOz@ooåü:@,öîä_Cv.~㪪Ä.Ãýߢ>Ä¢Uh¢zUð~rzðr|*uÃ¥:ýß.ÃÃaÖrvÃo v@©+ ßÖaAªöObubý_ ÜÄðüCrü.ß Ö,ªAAãÄüÃîüýhð Bªäar|OÖovß+*~Zî|ð>Ö£ýîhu@BZ£*Ãœ~a£a~C.ªÜ|¢:Öu<*+.ý,b¢o©BÃÄÜÃ:îBß>U*Ä~Ãßo Ãra_oðÖoÄä©öÄöãüðî,¢îz©@£vüC£ßÃOUÖîü©|_ßA@ÃA/ãÜz@éÜ._..@<ååö¢@ðC<Äöbäz*+ª~,|¢Bß<©~îhüÜÄ <:O©£ã.aßðühßU©.z.ßöAãî,b,ZbªÜoÃý¢|.hÃÄÖo+ÖÄC©£.ð>ã_üÖz¢_ß~v_+,AÖvhrÃvZªÄbÜ£Brv.ÃÃ¥CCöªÃuzðä*C/rßýÃöä@/Ä©ruãîü©_*@O@©Uvbo_îîÖ,ö>väý£*v.Ã¥zo:îÄÜßv¢,@ßöð@|Ä£ßz¢_üvvÃ|býaObCZu@Ãœ**+ãv+äC|* .öÄ:CvÖOO£ýrÖv¢r/Ä£Äaß*Z.|üvª.oOuÃ~b<¢aA: ~Ä+Oü,£o..î¢z¢~.b_z>~Ãœ<ßüAÃ_Ã᎛UÃr¢îuª|ª/ýÃZ~.öýz¢åÃh+ö/£> ÜöbZ>OhCv,a>a_åðuv|Oßa£BvßZ_uCÃ¥výÃ@ðvöörÃœvu~zCäoU/ÄOb _ÃœzýA©åªåh@Ã@vAö©vz+,_ÃœbßhöhZß.ÄO©râZðvzO,Bb|~ªCzÃœ/äÄðýývråÄÖ bÃß|äbz +*AbrhÃüu|*aýaüä:ÃrÖ*Bb|_äz*ZaÄ_ßü.BߣÄäzZüz+à bÖöBhã_üZZ<@*ã ©uãßraªåAßa*/bCÃOß@Uª*ð~ÄvýCß*h+aý¢Ö<|_ :åîZC¢hö+aÃ¥~v+ß/¢ß+/öª.*r:zîv|ÃÖAbÄ©ßÜzz", - "/ªZ ýB|ð~.ÃœB>öb~UÜÜhCÖîOÃZ<ßb£@håöÄOö*@C+Äb>Ö~£+:@A*Ãã©ÃÄC~Ä¢Öäðå£r_uå£åU©Ü,Ä,/o¢B*_ªo.bZA<ßÜß>¢äh¢B+Bh~äoBbbvýzÃ.b@,zzÄUovr,ãC/oo_~Ö ß@öÜÃ:b ýaAoh*Ãýu_ßîärãðOAb©îðÃ._+¢||¢¢ã£îOUOUÃÜÄOðýÖä:~©a~©,,/bv¢ªÜÄo.ªîüUüAªÖð ZöÃýãÃîAÃ¥_Aü/v:Bü_¢ðb|ÖzBܪ<|bU© C:Äb_üðäÃvÃ¥~ö@ý>ð¢uZ.u,ü©£åB/<ßZ+¢ ßÖA/|Äb+ZÃ¥ÃhUªý¢/A+ðåbÃ,Ã¥Bã/u:~uzrOÃîr£~*äCbð@¢bBÃœOöä©~ZOÖãb©jklßîö¢bä©¢_ÃœBßßÃbß*>b|ßåðA", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("-100000000000000000", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("0.6410", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 11 -$values[] = array(array(("EA982235D8DCA9BAEC4C94727937A73EA975D4648D69AB654C544E246FDE0C712D22CE0E4D457AC1AED7C48910C3FA8A91BABD3A5732918AD95EBE613349D566881279D9FE7D6F831DA086B5A422558859C392B521D21AFA914434EE87198E65E7DE552121EAEE01"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("009531925ACB9BAAF1F372D32BD650736063B4A0A99DBCC28EBEE7325B43E5772ABC4A70994578FE2E9326B2195375BF61826ED58315B362D86F049CE4684EAE0DFBA96E8CE0D91BEAB57215760AA83A4828D0D8D50FF31409E7982A4"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("9FA71715A18E3AFA79DB32F6CDEE693D3811C7484656D8E123FAA87D4A62873D7F015EBD727AA06515350849F37A9D0D60273E7696E48948CF36F2A7E6BCAF1654F577CC8FACCABE716133104D8EEDA95C9B1D232310A69B7816ED3B88F87F5885F2F5FE02D17DB45C800434B078FA4148484B4A9E8CF2434EDB5379BE8F3578E1DA8B5B99EFA68A1D69C7A30B72EAFF1BACF33FAD35B9F9FFB1AF267D04F5CD4A8C52D5ED9EA758A1C02C35C44F38DCF1545E129BF6CBAC21915CE73F800D1974FA597F1F15127C049A9EEC088434AF79D33529A6E23154C0A5B45B42C01921B313C1CAF6B8BCDB42E10E0158DEA49C5CAC6E6B4AE37391C804BE7960B63944817C39F4C26AB9C01759E7C4D430FBE20017FF9551ADD7CFA03001F1F9427A8BA7CA7FDBA5B0DAF2F3AD54F0FCAEBD9808DC7849B7A3F2D4A569549268C7C6D5DE5B1C0AB0203D503D0CA2A458E6D2F17269E81DC931D403D7AB363B5B47892BE67F11A191B3E7E89F280CAA014D7BBE96621FFFDFA90BAD86193205E901F5F58377BDD9D53160BF82FF7EA1252A02892F78A4F8278A4C3C73641A7880E1FF9D3216268A8FA2E86502607DDF2B6A885C16A8D5715DFCCDF82A81657117E73EA000E06E0E0378F98D8F3FF6C70D6D1109DBBE61D80C8E4036492E5F0E3C774E1B22DCDE4BFB79589EAE47E11E0606EBBD6F5C9558866693729F38C5D518EB5117F0DAA5308EC60E6265C229C4AC222F4342A29FE0E2F594BC486CABC2B48386C3F6C92D2B61BB2FA94E32F3C33CCFC57D098292764B7135FB46E8E7211B4BAEE13C65CEE2F403E28773F592AF02D17F79A72DB931F10F77859432E578D30D8E02841186D601E28905A00A0255DE86F874696D7AED917CF02CED484EC11E69B465678F2BF04AA877C4A3B491E60D22A60D39700E6593A071072159E9B894C7E636569CEE4F5B082AC0F6B54ADDEA091FCBA24087AD5A4ED2C71E23CFF0BFBF30DCCD5B90703456B231464E6FDD55931C173A1D16533F39DB08CA26313738390036856D0F8F79AF974B8D56660CDEF5718A07B8B6A2A8DC9C947F88A50D257BF80D4D3857E7848D9D8B85CAC5E9D2CF29D5B4A63080825E8EA9D17B0FF1098925125D59BEA59D8906791B2B1CDD17B3C2B05ED0597F4B778AEFAAAE16087DAB81FBA5B38B781083372A9FB6655741B0921EB95E933830A5C7E6CFCFFBEF73FDFB6FA92138632849A0DB992FDF941232ADFA1BF84F1F1849CBFFEBA5A9418847B7A2BAF6AE20772EF56FBEB79F01C632D49C2100D37AADDC5760553878839F5E5351783DDA1B848D82ED02E06060AACA39B9D00D1F87733CF4DD9E4DCA3EB672CF5F644D888958B817947DE678CD7CF6B58BFFBEB2923FCBDE3B48FC7D36C243368F1000FC5D41AF6118F58B098DC42661A8983F3FF7C03D20986F4D405B3BE0D139AFE8BD7BF95849505D999CB3C15077229BD49E82043FCFD3E064EDF46F05DD14B9CFDDDC69E5AE3FF6EE9A26A06CD61ED51EC066424F357019EA9A956B54E057BC88FAF2739BB9B5265F8C15A4A832748DCF1F514577D26A9A6CC27E4A18D4A10E436A1771E4AA39650A3FE2C31FCC70B726089B358F1145B5905706D83A6A0D8BEA07603D1B02A824E5EC3B37EFA77BA052C7240F386B7F322562E18DA4612AA03257D8176B339C245F7650A3D60EC5E9DB65867246FE527E882E17B9201B8083C19A0598CE63351E2BFFB0C11BDC5C179CF8B13EDAB75933045A8F3D1BE30444F6CCE13B092D1A78306747474D9163ABD2001C286A41A80941D19474D3F80347FB6BCA4EFA200E6EDD380E737012E572F6E36B1AC6DCDAB524D8FA3798A6A43864004BE2C614B1B1F2AE4A313235992EEB217DEF8CE8811E91142A2126D6D63694D60F6FC603ACE232BA281891A883EE3FCB4C7629B4AB7B7E2D50B1EA8F8A1BD7139D5B262967E7759BBDD2C0688DE8913BE25B895F0DDF2B94F199B2FAF7A22F0C407E0B28C8156365018EF7D8BB159F6820AEAC2F927806BD309238CE347BA31AA2B3280EDC6872CCF7245D7FE852D8DBF94AD949F5007AC8784EF736DD953086EBDC3507ED908BB73BE0376E1A86D5DDB95CD4E9A15924A2385F968E713CEE61F3D7930C1CD0ED68BF210D15C80CAEF179197D80C047E491B793CE8DFAFD46FFF1D67A42F2C8C1E86F6B2A804C0EDA2CB1A51457BE6EA1ACF2025489015F6EC1DA0E24F2916BFEB0589511541C6445A8B9A2DC55E8EF408334CE4FF07437E60FFD69A53DEAE79B46A664FE295C4C908EBDACD05F8319D8EBCDA1BDC21B0E3C21AE0157B2BF07BED10695AE84F01626E83EC132C767868CE490B96F482AD82A9828DD7A5BF4DDCEB6EEFE58ED6A54FBB77BD4845ADB74BA28757322069775A72E6B165C139BCBF361F27CD9682D9E793BBEB8CD5554D0BCB32252E581BAC81E78572A8254F8E71BAFE122675E9B51FAD6F9E648DEA852D99B59A8F048E572A72A5E6010031C2443D69D1D7432695E17D0580F87D37F40D00550E7FB4FF7D2CA6E7B3C003B40F6B9D5A272418BDDA731FB911FF0D6EAD3AA1E8F0BB23528F4DFCB5205B72C5DDC8F6155B2310DFE4527A9B500BED7E1F384A891E2AB354484ACAE927538D8F0049DB48F8E8137A19C1F4AE40980273607E95F800F9B7B305A1DD105837B890C4FB9B063094753724C06F5C360513A0384778B8CD957E16921833470B0321E21963E9450D0C42DBF95EC3308A5B8D009E2CD800BEFFC33F7D746418513E2C21AE7D32C22BCEA33C04ACBF6EC23D8C369B32B8DE2B11331990A9D788983A7ACB32A1921D28BAD4CF899C0C0230546D3FF82D7D1E754DF69D3A193181F72AA3DA21F43C20AB6F1DE05DE9548F1520D93C3C97806ABD5C55E4AB04E31F7952D85EF821E04C184C94991F9CC08BF97778093FF5455F9A391CC5AE5775A67C0E66FB10F586E3409D23E002F75B109BC9EB338CD9DF841D78B4CF051AADB0D6FE1D1135C57AF3E094C8A2D1555CA2EE0C29D47C4219922ED2ED9840C7DE25EF631904EA49EAD65EF752F8113CB2B82C11E108E0D94484D47AE252D7D2B318B692B37E3C47C89411538584FF2180F220357C3ED6C535161E84307F5787FE7C343A318E79347BD4594CE79D53CF95B2F9E022A2668431E15DAD5FB817594C99650940B4EFF288EBB9A7E599EDA7B25EEE8D7577554C05BFF85030CBE4C3CCF0005E284A8EE0C2F75746F8CE9EA803A5EB00C989D3D53418F380CC9FC1CD06E5DAE5998F8F77C00DBA8E101E534711404C9AD0D40A332C3B1A1F46FE1433359A3002E3B91E74267330BBCF55B525C5BC6F7C5326EC22EBA7B95E757020C1F9874875DE40536831C8646D935E73D675F4498A9EA7E00EBE0D174C0FB00EB6426EC2380D4F477569073A8AC27E9F9D33E2E87126963341A739B7D139075881B9BE1B348C3BC936ACD421876D554C3F7D8A182CD79D8FAC3B7090D387764C51AEB8D104636B010819B1C45274BBE451ED8853AEFF3BAA2C2E6EF91B9AE5AF3687CFE8A48437DC1279100315C93D378F8C4EE60F129D62630EB4E5F461F56D24028DCAB4C33FDDA5AC3135EF389452E137798F5C0FA023FA5D7595DF51E7B083B2DF033B7282BC4C713052D167833A7A51F4BCE86601246269C0575DAA0E53C480B23C5981277EB4CD32EACF4D4292DA3B1B7D6D2AD81DA1BAA0C1A22B5EA262EC9E50A556D0F92A76081B23165515273D8D128E80E699A8948AAED055CC897E9077E2BEE1ED736A1242E712CCEA8B558BAA74AE3C13E205F4A810CC2228D4B97E9C929E7266355009B87C3DFF86EBA9FF4954BB57EB327C85ECCA49CCB737F97E3DA42047ECE32565149483A7B4067DCC76DCE8C41961835A2618959BFB8"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "ÖCrîUZCÄ/UzßßbBA©ãå>BÖýð~Aßo+ÖräüÜýÃ.£>UÄA+Ã¥_ý£üöA:bOAoßãî:bu@bý£ß~<rbÃZuÄã£ð_î@.b |Ã¥bAZbaÖ©AüU|+br*Äâaou<|Öö*ÄÖßr|ÖåvrbU", - ".r©Äru@,ar+ÃÜöö.aðà að+aö©CO", - "vU£ðz~.ãzvbÖîü||/AuvÜ©AA>᜙@B ªßZB:CA¢ß~b,böov>ªhåðÖrh:räoZ©vÃœoU/ u@bo ZîÜÃ/b:+ãößU_Öðbã¢h:/BÖo,å£ÄObý_ý|¢ä.,ah.ß/Ä:@¢>>U,åöÃåro¢ |.ÖOßövUüCb©/¢ýb££AÃåvorö£CªzÃ/Äßb:㢣Örv£+/~öUö¢U|Cö:zv|uhã©öZÃ+ä+ov,aaZ<ª¢ÜÖ+Ãä£Ã:åªäªo*övvBéCÜü:u Ãœ,Ã<ýz_bö@B>Ã¥ZÃßbåð~b<>~.,BrzrAᦜbb.£rÃua>övüuÄÃÃ¥:uÖÖ££öåäßCî>oa~hªUßÃ.,_CÃv@AðO~ îbäOÖý,h@Ãœ,|z@ ö©ãª<îhbãu¢ð,.äöª_|ªbh,+ªãÃr£ýüßuð~*b|B.ÃœAAüCbaÄ©ãÜAÃðvßv<ý£uð,ãUßÃ<ã>Ã¥b/hßAC/U_ßý_ßrðbz©ÜB~|:rã¢OÄz,_äOöê~BuÃßu:ßU,ª£ Ã,ßb.Z<üöý,ðÃöhZC*ä: >u:Bv+C£ö/A_,~aÃÜÜ©v¢¢BßÖBZz_CAÃOýuO*ýhð+ a_+,**h_*~rrÃäC¢ ObßCß.z+,*ß,ߢÃîz¢|:+ÃðoýÜ:_ýZ*bÄ£üã|uOhbaöýzo_ü:Z,U_Oü/h¢CbüÖ,BýÜ©o£î B:.,*ßöãßu_©_ÃðBhOz¢äý_>,z_üzÃœhᛜrÃœZß<©ÃrävCAhru£ã*,@üb~.aª*©håßru~ö:ü~Ã¥O.oÃaßÖOãzbªoßöAÃOaCÃÜßO,zuUÃ¥b~+ îBUr~zrÃ.@*bZ£,Z|vZöhZhðüÜ£.hBÜ¢O+z@öÃÖ@©uãÃz>ýÖ|~_.:Ã¥:+Ö,ÃœaÃ¥auîßU:Ãœ:ÄrÃ¥Boãð//¢:Üîã||*ozîöÖ_îÖ¢£ýöîbArZã,£ zb_ðzÃœzuýo©*:BoÃ¥>ö£ý*@Oð>_|.|ªaoîaäv+a/Ãœ~@£< ~/üå Üåéýßä¢/Ãœrî©+o>//Uî@>>ä,ßäêîB~_UßC/Uab,Ã|©|ÃäZ ßoaÃýßZ,ävoöoa_©ª*._bbr.O@ÜðãzÃü~+ÜÖBä|._Ã|üãCuÃAýü*/ÃZÃö~_:b*_|~r+ßUÃœ~ÖÜaaz@u~bZbîã *_ãBAß@zB*OOý,£Ö/ÖOauÄZZ*ÜäÖåãÃåboz:ßvB.@,~+Ã¥_ªÜzB¢Ü©ðBhv+b*ð+Uvhr/©ªîîu£Ö<ãCð,ßOB:ÃœuðuoboO:OÖ>oh UÃBðo,O,Cî/UA*ýUOãî:brýZª/Ä:bîv Äß©ÃÃUöbªßßzÄãhã<ö~©U©ZAÄß ÄÃÃ*u£C@U©Z@ðîãäu.©|ÃœzåýCzªbA>¢üððu+ä<~£ªzîoO.©Ö.ßb¢AU|.ßÃUîbb>Ãbz~ý©<ýÃãAü:,Ãßãã©r/að©+ß|hU©~_ßOCäã©Bðß ßaZBabð|vu,ZZA,ðªäîä>o/.£uªöãüÃÃ,Buð+Zß>rÃ.+o@Ã¥b<îbuAüã¢~Ã¥/Ãœ:üÄZ<©îrß:_ b.£ßÖbvh*_:öî@Ä.vª >äaAuýhÃ¥<¢ÃZa+ÃoªzaÃ¥>ªZ/ð@Ö+üö<ðrCbZêävU+îO|UîZÃ¥rb:>bßß/©A|zÃÖB~ªO+~ör|,b~,Ã¥>>öZ üOªAÃœBBÄäA~zö|ªßªüb_Ãða>b::,îðobvýÄ~>_ßbvÃ¥boýBBAÃœbî_åâAOCý ¢Äßðå@AöãAzbhýZßäã@ö*/ªãA¢O_+©.,_~Oªð>©|ÃÃÃ_bÃ¥rUãbrUzB_>U:hvAC||ýoã*à Äh>Ãœ_oßöUåÃaA,bÃ@hýo v_.>ð ¢UÃuä@ü,,ýZU*ÄZuv~Abo@z¢Ã*|a/~bZªßoÄ|£ã>ba£ÄUî¢/bbZã<ãoBöb_>öüüßða¢__|£¢Ã_Aoä~,ßÄCö+zußÖ©bÖ>C _BAäßuvBÃ¥<.©Bðßýu*©ÃÜßaª>Ãråä<ßo>.ýöhaßO_ªu<ü£håð©ã,ß+_Ãb/*Ba,U.©UoÃöåäu|>ro~_£ã| ör£.*häväãÃoo+ßßÄ>/ðbߢã O+h@ß>_Ã/AÃœUîAßÃao|ä*bÜÜÄð*C,î+a~aöoÃ¥ÃöªbãåÜ@AýÃOo©Zîö,vÃ*,©vBÃœh¢£Üvðh©ðßbãU<Ãœou.üÄOÃœ:Uh>U:_ Z*öbÃoÃ¥UÃßb>Ã:äCoÜðrOUZ+ª+o.£Ü©©Cª@ÃåOêvü©h©©Aý,a :_*oAb~:*a:ªüo,,hðoUübzðr~oÄð*oa£/rCÃœ/>¢zÃ+ªß+ÃýZZÃ¥buÖ*.U/ö:îä@aã/ö_övªÃ/_öU©ÃBßåo.£:Bßåbßä*>bÄ/r+Ãœ+.obaÃO¢©ßCÖ| C|Ö/ä_ãb:h.Ur,ß@AZîÃ.ÄC.üZBBuãðä@Ã:Ã¥abauß_zvvÖ媣/©¢ ü/|Ãœr*rß>uhzýÜ,ýÜ~£ã.u+öýUUªªåäaßðÃb¢ßöB~ b/üa:uhCÃÜöaaðAßAüvCÃ¥|îo>ªßOvzä¢ubUÃ¥rößÖ+>rBbÜðßAßÖýß<:>_ß@ä.üã,îzÜöÃoªBh@Ö,*ßCÄ©ýh/_åßÖªÃ*,/ü ,£ßrÖýýhÃ¥u©ãZÄbhÃÃßO/ß.ãA@A ößr£OO*Ã_ª+ÄÃãUuÃ¥A>A_~¢Ã¢Üî|Uüouü©Obaa©B+ ªC B>~|OöýaýÜ+O@A,ZÃœ>|/hÖA£Orä+,©uü>+_Z ýbAOuýîßå>ývß~ßAÃr*<ð,ýB¢ªz_,Ãhoäß+<.Z~*bBZr@AuC:åüZZ_O,ªÄZBÃœ/B +ðÄZb:,åª< >ÃœÃA~/Uö ,rÃ¥ ¢üäÄý<|¢aöãªzöbýßî/Z>oüãZ@bCü*üAzãbhUa£|ðZý,Ãœu|î@oßýU.h+@ð©£üZÃâ©~Zä_vßUÃ¥+uo|bbüO~ß@ýÜåé¢z ý~£ªvÃœC¢vAüîaÖ+bðröäªÃÜÃår£åêOðö:UZĪåßö/Ãh¢å@u.ãÖ<©:ÃzßöÃbhuö£äAü_>b zhðoý£bý©.<ääÄU<Ãö<Üåß©Ä>bZªðüÄ @/o@_~zoîÃ,ÄÖBßhÃœ<ß|~|/>Äî*©å_ÖîvÃ~_B£©|ßAo>u*ðÃ,ð:©ÄUß+ªåªßÃäÃbzÃ¥CÖZC>~ªü¢:|,Czüß+üA_ÃœAÃ¥uöubååöZBr*Uo:Z.Ã¥bâ ßrÃœZßå>ÖbÖBbäbOZ@ÄBÃO:rÃœbß*B_oüü.ßh.<,bÖ:@AüÄÃZ|©_Ã>ðu:*@_/ßbh¢rðª©,v£ªu*vª*,_vZCU:åÜåC@ýo@ö~>ba|ZACaîzað:o©¢CöüZhÃã©Ö.b>o,Bh©¢är~ßC>oZrð:özÄ:ååu_©/ßu ßåýªã@uävãz:AzãðußB*Av>äªaObüUýu@_/ÃÃ~Ã<.ýä~+b_~zZCýÜ¢~|A+vv£U>Ãœ ªü~.ßU£Cªðb.ð<_:ªÖÃhý:åüä£ /ão>bUÃœBöO>b:Ã¥oÄ@oCåÄßb<ߣ ob¢_oî ©ÜÜ_*©Ä:@.v¢©BZO/Ä_/ª£Ü©Ã/ð¢Az¢*hhu+ðýß/Ovv¢uOüuäîrãª:ÄAýBãîÖB.ußð<@åîU/îBv*,o/@<ÃÜhBßé/r,ãhîÃBãAãzÃ/*ö£Öð>r+£ã£ /bzZCýOßB©@.za.O>*zhßA ß< äu.Ã>ßÜhö ACýr>_Ãœ.ü.:ýöã.UåðhÃ¥huZÖß,,Býrî_ßåz+öoà ãrOu@+Ã<Ã:>Üå/:©ªzÄozüB*a£ýý>¢_bv/,_|£åýª_åÄ+îähoäuß+AaörB~ßðüðau|rh¢ @O äßoB>bu:~U£.ÄÄu.:*>UäãÄa* ÃZª@ãüÃoüACußo_uAU@Ãb, *uuü|¢¢:ÄÜÃÃ¥*vüüÃý*äZöZÄB ª©Üß~v*öUZ¢ýo_*~,/bvOöbZßÃÄ@A.ö@Ã¥uaüÃîO<_,A©*Ößß@Ä|ã£ßh©ª>Aã_ãîã*CBö_+Ã¥*O._ýÃÖü_Ã,vÃ¥ üêbßZ>vããü,>ªö>zBßoBãªbî>äÃð:ð+Ã¥:C~uªbu/ßâ ~ß_:/~ ©ÃZ,©Ã©©+:üB<öbî*@Ã*r¢,ÃußöU|ý~:aCãOoÄî<ð,ãýä£o,_îOöÖUCbZ|Oãß/C+Ã¥>b*BäÖ£ªu.ßÜ.Ã¥~©_ãߢÄÜýßîª_bÃ>vvZOðÃ_aðöå/Ößîã|Z:ýÄa.ßaðÖö|o~ aÄüzüª+©Üöuo£BhÃ.<üÃ,vz>ã/+Ãr>:ßC©Bîß~aߪb|Ä~ vrîb*~>ªÃ£+C,@_baU :AUÃCÄ>ob ÄãÃÃ¥oooßooäz@ý/@Är>v_Az,CZA>ß ã.:vüߢuu++uäb:o@Uv~az©Üß©_*ÜåC£Aßz/ãUa¢å<ýhî ouðüÖÜUr _ÖÃßuußaÜüå£rîz*|ý h<_vC<©£Ä .ã>h£ßß*©|:+Üð,Ä+/üäß_<~>aý*+ _~BÃ+C+>|C@aÃ¥/åÜĪå,Bu©ã@ä+ð¢£ß:ü aÃ¥vB|v ååä*ߣbÄÃZ,<ýUzîUo>Ã:åðzbA|.ý@¢bªbu*ß>Ãu uî*.üýbÄüAðb_Ã¥aö_vÖB£Är© åðAüBÃ.v.Uü~îBA<ÃA<ÃübOu,vuühÃ>ébBbbÖåZ>ßC¢A+£OĪöÃÃü+ßÖß|ß>:hÄÃvîßaý.öhOroö_OA¢bU@ÖßüÄÄvzUObßb©.åÜ<ãOÃOý__ÃœrÃ¥r¢_O:rªüðzrBÃÃoÖb,h<@©._ßä@ Öv/ð>,B¢£å*äb/üh¢Ä*_uý,~©vu£ävª ~_CObZ.BÃðvb.:z>ÃœZzÃœA_,£>ÖOßuzªaCC<|>Äß+Ãß+uh<ÖÖÖÄObªbðZBZoCCBäb_£UÃ+£ª¢~aU_oCÃ¥AÖð,BoåÄoÃr:r_Ãv£ö Ã>UA,BB|:~ß,hý,h+OÃÜî£ðBaor,båÖ~~ß.,ÃœÃ_zý~/C *b<ß. büO/A|oAuîÃ<|ãußz|a*¢£+ÃœzußÖ©CÄ~©_©¢.,,ߢà .zBäZC_ÃîßbÄ/~.CßÖ.+COBßAoª,îOrÃðzüüãÖüîAî£ýßüBBbOhAýhz*ð~z åå*Ã¥~_uvãZ|ä*är|,.C:ßߪ@U¢äübhbU/£ah_Ãœ.vÖßvÜ¢v©höÖü@<ä@bZߢö©vuÃUz/Äãba¢ýäÃý@ßîýöÃazO@Ãœ~CðãÄh:ª<ª|_zãüZåà _ªo@_<ð£aöUî@Ä_ã~îuZ<@vu,üvªoý/h|ãýo/ª++ß>:£UÃbßÜý++£:äa*öh~rz
C*©Ã+.ý@ã<ýbªýÃî/ßoªb<ßOz./¢u©Ö|ð>éýü£/ä @*/~uu©hCAb@AC.ýÃaÃ>hà ã£åChüîZzCBO.bÃOröb_Ä@,:¢Uav¢Ä*rÃœa~ð C_OuÖÃ.ðöaðüß@|br¢ v.<åü££ªÖî,,<ßãB.î üÜü@ÃUbZßOßo~,v©ð¢£u|BC©A_ª:Oü/hoå£_zîÜ£å*ã/üÖßarhð:Ö©Öå£z>o¢¢CãÖ£ýÄÃÃ+aäa uvÃÃZêUbAü,|ã:ª>::ü¢OÃœZ>bUhÖ~ß:ÄCÖz_@êöÜýozß bðbrý:äÜ@ÖÃ>ö|ÜßoBüß:bobýÖãaãO¢üÃÄouÃ+ãýä U_äaÖAOüßb*aåýÖauu¢ãö CÄßU,_CßUüöCãªOüaßãå+~¢ÃßO<Ão¢+a,î.or>£¢ãAß+bÃœhhßUrßabü_BC/ub/¢¢~öÃðrAÃœ>orA Ãœv£AA äð.|ý öåbbb._..OÃœCAröß:Ä©ßA¢Üß<,OÖ|ÄrAö.ýzüÃ/ãªð~<@aß,b:ÄîOZ BUªÃußãÄýÃO¢u¢ãÖZðO.Ãr*ö,+*@U>hÃœC©uÃBÃ>ãÜuªÖ*,~ÖuÄ.ýaå¢îî£zä/ :<ª,>|.C©_. £<ªªªýܪZ_boÃœUAzß:/£,/ÖChöåA*BAÃ¥|©uozZAzbãAð,:¢U@BZuBÖðrÄ<:©BÃUr>r~BaÃ¥a@.h|_.ãÄÃz¢@UßßÜßÃA:ßã+r*ªã/rä@U¢BÖßöO+ßåÖÜ*bÖÖv@~|Zªý@@ß:© ,|_aßäåvzÃ¥>ÃoCßÃ:.BÃ~_ðhU|:h a.>UzUÖãÖÄrä>ö¢Bßh>oßb,*öÖã£,Ãœu,Öðz:ý>äåbåÃÄîäBªZb//üü@öAoz_,C:ü<ßB|£OBzv¢Cî+©/B£U* *ß/ß,+.ãrz<ð/ÃuBÖ@öCüCo*ã¢|+ÄuîhÖC¢©+zovÖUCühO<£/* CZU+u£BöÜßbCý.*ozB|Ã:+Ã*Äýru£oðð+åÜã@Ö", - "*+<ÄCh_ÃœAo_åªåÖªå£u:äz¢äCÖB£>£|Ã@/Cýu£ªä£/¢ý+bÖaAU¢BO~.bÖª~+AîZ+~ä*oÜÖ,Ub+ß/©¢>baB¢va:@öAbu¢buãÃhU.@Ã~Uvã,bᚁ+CB>*öZA+h*,hÄ_Ãœoß_bz.uý+.ã+.ª,ÄOb©", - "|åå:CÃ¥Zoz:_ÄbuUvÄ©üîCCöBð¢ÖâubA/vªö£ªr_ößbüz£*,o::.Är,/ðÜ@>C_@b,>ZߢzªC:.özbýßZv+|OÖ©zuÃ¥oaäbÄ.,Zý|ß*_Üäã*özUb,öýUuÖý Ãrbð:ZÃAÖý|ab.aöÃ~Ä .ÄaüßÜbåã>©.ö©ã~Zªz|BzOzÃb _ýZoöÖã,O:£*Uhðü_aðü~¢ îCÜß<>ß.uÃü¢UãýB£Äa<©¢ßýüªÃÃÄ@|", - "B@ÃBÃ¥~üÃ|h¢ÃäãU.äÜa@~aObBh£¢+rÃ¥Uu.ü/üZ¢|ua©B,rüAªßýãvOZCöß*ö +*U+v¢î£ ãaÄ_oß+.@rüªöavîr zßhAÃœ@OO<.bîß,ðýU£Oª:uOß©.ý£Ãzܪbz~ãOßUr+ýzö*¢,|©öܪÄ~äBª ++@@/O£<ã>ßbß/.üU<ðU:Aa/üÜ_oÃ¥<:>uä/å©ã_Czäßîêh rÖ ,o+Ã¥ *b_©£<ª*A ãüa@Z.ðßö~UBÄ+¢rühhb äüB@~ZÃ¥<ßv/ä,ÖO ©:ð:+/ZBU+OA/@AoZß@~*îãv:¢ð/OðCÄÃvh_Z+ã|¢+ð©©©îO¢vbªößü,>o£zrÃœz,@Ã¥v,ðÖü ߪCöoðOÃ¥Züß Bã.©î.U Ä@*äb_oÃœ,@UÃ¥ZÄÜ ÄaU|ýÖAUöüZ©<ª<>>¢ªzvUå¢Ü,<ÜÄöA+bbüåO©*ö@~@_/|£A.CßÖö@ü~|/£A/r@v|aöÜöb.: +uBÄA:~Ãã¢î/bUîÄÃÃœB îA:bAüßÖCzvUa~£oß©ªå/~|ªbý_¢o¢ü~ U+ä/:öB@äªO+©.<ªÖåbOuÃ¥ :ãýA.O~> ©îÜußÃ+Ã>*~ý,U.~ba.Ãî.C*b,~<ðÃ*vBãÃÖ¢vßa@*©äz b~~/*ooÃœ/Uß.o,hîßßazÃ¥__£ößr.r|ÖUbà ýå< OB ./öuC/å¢_ääý/@Að>|üÖ|ä>|*.bha <ßu|hCÃÃÃÜ@|AÃ¥,bzßööýÜu Ãœ|ð,äÃ+âr+AÜä,ðuÖäOUýߪoZCUOb*Ä~åß~Ozäa.ßöã@*Z/.AÄuîbBÜà u||C__ã©/Ã¥bßÖðåäÜ/AýBßaÃão*hß_ÃœAãäbz/zU,ýãð,*ªZß*AZ£ÃÖu/|äv+rÄäîCbüåä~/:O£ýUåª*uÃ¥|ßÖvCZB¢£Ã~AÃ¥rZüaZz£,ð֪ߢAo/£r ©BÃœbý,|*ä,z_ÖhÃîÖ,o£bé£<:ýaîöO __~Bbß:ðå*.ß+îC.b<|Zü/C|@,_:ðrüar_¢ãööUÖÃZO¢ý£¢BÃœ@hãüv¢©öuö@ Ã¥~äÜ:Ã,bCAðîöåÄaÃ.ßßî~>Aßß>baªoÃü/:£bÃÃ¥rbuð:Ãî ,ÖC>,Öä îbãü¢O*b>äUö@/bðý_ªÃãA*Ã¥>BzÃ¥CÜðBB_~uÖvaÃo/ª/üßüÃO@äoC£vA>BhbAo,bzã/Ã¥<ÄuZªý@u/îðÃhªÃ~ðÜü/v_éCðÃýA@hC*buÃ.åã¢åÄß BäbU<ßzAÃ,ýãA:©Aýö@O~ýªBÄ~ð a£*u O~CrZÖ|vu¢bZ©ÃürߣðO¢ÖCUü_îåýabbÖ@äå|ãÄröî++_ßAåühua.bÃ.<Ã¥uÃãÃZr|a_oãb£üðÃuÃ,ÖÄO>aîäÜb¢Äã/,©ªü*Uªoß@UÃ¥bÃ¥,ãz,Ãü*ü+üCuöOð¢üA+@üöåå.Ã@|Ã@:OÃzh.|£r>ðCzããßÜýCªäÜbî_:ö/ýa:ÃœUã@ö>¢b>bzßåªä¢Abªî@:C©ÃCßÜ©röðoîUZ¢@@z¢/oðB|Aýr +âC|Äa|ÃœubÖüa+*böh©~@~aZBãüoîß~ðoßvzb*_ZbÖzUÃ¥:o|Ã>bã@ÃäBªß<äA,ä,<ßßrBAaªª,_ÖZÖ.¢Ã..,ÖÖ..öOÃuåÃU©åîÜb£ü:Ã>vuß*ãßb@¢Ã.vb£vÄ@*ÃÖ ý U:Ã¥bã/©U~*uhO/~@ö©,>O:BðÃÄA/+o_ovÃ¥~åöÖrbü/ãðã@BZöãÜbö>oß*ü+C.UÃ¥.ßbäZãÃ|O< _âOBo¢Ö<@+:uÃœÃÃœ_ý+b©Ã|.BÜ©ß/vZªªªü.OOÃ¥u>©Ö+A*ãb|bo~ß.©ßOrbz<ãzuUÜäªß ª*Co~ýüO+ÖöªU+BÄßOöouüB|é*_CvOUv b|ÃBÃœ*:äª:ÃœÃîbýüåB,~AÖßv*ã/O.üa@~ÜÃÃr*>~ß~Cb@Ã<@z:ªbä©ZzÖ/ÃððZ:¢:voh,UÃœ@ªßÜ:£Ãâ+îäü*ð©ð<+ýÖ¢|£_h<ªv~*hb©b:bîã£aÃB|A.<Ã¥ub_ZªOabýuývvö,b_B©ýbßà ªa¢Bbà hvBã::z@ã,+rÃ~_îavîäaOBðªu~rßAo~¢Ã<îZÄ,ÖzÖU:bU¢ððZý,uãã*¢äÃCý¢Äßuß|öC£ªZÃ¥h_vö£ã+ª£Z>AÃœ*ßåhZ/ýZÖ©b/uZ©©ßZ©Ã@bZ~.üa*@ *rÃœ:/Ãœbý*Ozª+Ã¥r>.|Ãðãaã Uv>ZÄz.¢Ö/zä©Üäîªßð<,ðÄ.,î|:©~>**/rovÃœvbÖ©ãö| ã£ÃÜzab+ö |BîßÖüüOðü>U|Ä£ã|åÄÄBÜãbbîÖåZb ~î.oz*£O ðª/ðBz£:ªAßÃä:.,r ÄCvh*z_bªö/.äoäÄ©©~>uÖÜo|:v~Ã:aÖCAöbbßUh,bÃÄaOo£åoÃœÃßÄCö*C|îAö oöÃzãuᜆ.:Cobbo+z¢Ä|Ãvö.ðýÄöu*C/Ã_Ãœ äz.b>îz+~öåZör~,uÖ<©ÃîðuBÄ£b ©/@Zß>ýu£bÜö~ßüC/ýAÖÄrübC~ü,OüßUUAð@*ýhêO~ÃÃÄoBöC£Ä¢z¢:::Ou_~A©v_Ã|Ã¥:Ö:ä o_¢Z,,/ä:b Bªö.:bîßîoAhð/hAªr<*rãjklãobÜß©Cü*OðÃo.,ã¢üOC,ÃäU+ߣüÄCU ßz¢åãAB<îO~UrZ:ßB>|Ã¥CAÃhzß.üAhvuöªöãß>aªåÄuuCªAb+äår_*rÜ¢<~AªC/Öð*zª ¢ý~>üa,ßäÃÃro.£©@üUh*OZ. ~äZü*Bu~rbÃ¥bÃÃUaü/b|U~ u¢OªCâüo:ð©©ü@>~>ªö+ÖªßuaC+Ãräba@b OaAÃß/ÄOOvBüîaåÃü a>Ã. r¢|Ãðo¢ßüvuAuO:oüÃ:Z.CðÃãZýrvCª*B©ãBvÃßa|:hÃÃü©>@oAßuCðbÄãî£o/_ä©äð_*+Ã¥vÃ¥:ÃöýuAäª<©BbÃœCüÃÖ+.ßoãCîvAßACoovÜðÜ,üb<ßAAÃÜ:ÜåÖüß~ý<:ĪªbÄOuß< @ßzã |Ü©å¢ÜCo*/>obbz|oßzuOßð*z~r_*U£ü+î,>@ª|C+ã+£.rrbß:ö¢£oCýÜAÖðå. äAuðÄÃÃêª.*_Ã:£ ö/Ü£/ä rÃœ.Ã<ä¢Uü>@ªrv<+hÃœ Auß~Zz+.ý:£Ã.oÃ¥ÃðvörÖüO,bOä:b:b¢äCãB,rC/C/B@ÃBÃ¥~üÃ|h¢ÃäãU.äÜa@~aObBh£¢+rÃ¥Uu.ü/üZ¢|ua©B,rüAªßýãvOZCöß*ö +*U+v¢î£ ãaÄ_oß+.@rüªöavîr zßhAÃœ@OO<.bîß,ðýU£Oª:uOß©.ý£Ãzܪbz~ãOßUr+ýzö*¢,|©öܪÄ~äBª ++@@/O£<ã>ßbß/.üU<ðU:Aa/üÜ_oÃ¥<:>uä/å©ã_Czäßîêh rÖ ,o+Ã¥ *b_©£<ª*A ãüa@Z.ðßö~UBÄ+¢rühhb äüB@~ZÃ¥<ßv/ä,ÖO ©:ð:+/ZBU+OA/@AoZß@~*îãv:¢ð/OðCÄÃvh_Z+ã|¢+ð©©©îO¢vbªößü,>o£zrÃœz,@Ã¥v,ðÖü ߪCöoðOÃ¥Züß Bã.©î.U Ä@*äb_oÃœ,@UÃ¥ZÄÜ ÄaU|ýÖAUöüZ©<ª<>>¢ªzvUå¢Ü,<ÜÄöA+bbüåO©*ö@~@_/|£A.CßÖö@ü~|/£A/r@v|aöÜöb.: +uBÄA:~Ãã¢î/bUîÄÃÃœB îA:bAüßÖCzvUa~£oß©ªå/~|ªbý_¢o¢ü~ U+ä/:öB@äªO+©.<ªÖåbOuÃ¥ :ãýA.O~> ©îÜußÃ+Ã>*~ý,U.~ba.Ãî.C*b,~<ðÃ*vBãÃÖ¢vßa@*©äz b~~/*ooÃœ/Uß.o,hîßßazÃ¥__£ößr.r|ÖUbà ýå< OB ./öuC/å¢_ääý/@Að>|üÖ|ä>|*.bha <ßu|hCÃÃÃÜ@|AÃ¥,bzßööýÜu Ãœ|ð,äÃ+âr+AÜä,ðuÖäOUýߪoZCUOb*Ä~åß~Ozäa.ßöã@*Z/.AÄuîbBÜà u||C__ã©/Ã¥bßÖðåäÜ/AýBßaÃão*hß_ÃœAãäbz/zU,ýãð,*ªZß*AZ£ÃÖu/|äv+rÄäîCbüåä~/:O£ýUåª*uÃ¥|ßÖvCZB¢£Ã~AÃ¥rZüaZz£,ð֪ߢAo/£r ©BÃœbý,|*ä,z_ÖhÃîÖ,o£bé£<:ýaîöO __~Bbß:ðå*.ß+îC.b<|Zü/C|@,_:ðrüar_¢ãööUÖÃZO¢ý£¢BÃœ@hãüv¢©öuö@ Ã¥~äÜ:Ã,bCAðîöåÄaÃ.ßßî~>Aßß>baªoÃü/:£bÃÃ¥rbuð:Ãî ,ÖC>,Öä îbãü¢O*b>äUö@/bðý_ªÃãA*Ã¥>BzÃ¥CÜðBB_~uÖvaÃo/ª/üßüÃO@äoC£vA>BhbAo,bzã/Ã¥<ÄuZªý@u/îðÃhªÃ~ðÜü/v_éCðÃýA@hC*buÃ.åã¢åÄß BäbU<ßzAÃ,ýãA:©Aýö@O~ýªBÄ~ð a£*u O~CrZÖ|vu¢bZ©ÃürߣðO¢ÖCUü_îåýabbÖ@äå|ãÄröî++_ßAåühua.bÃ.<Ã¥uÃãÃZr|a_oãb£üðÃuÃ,ÖÄO>aîäÜb¢Äã/,©ªü*Uªoß@UÃ¥bÃ¥,ãz,Ãü*ü+üCuöOð¢üA+@üöåå.Ã@|Ã@:OÃzh.|£r>ðCzããßÜýCªäÜbî_:ö/ýa:ÃœUã@ö>¢b>bzßåªä¢Abªî@:C©ÃCßÜ©röðoîUZ¢@@z¢/oðB|Aýr +âC|Äa|ÃœubÖüa+*böh©~@~aZBãüoîß~ðoßvzb*_ZbÖzUÃ¥:o|Ã>bã@ÃäBªß<äA,ä,<ßßrBAaªª,_ÖZÖ.¢Ã..,ÖÖ..öOÃuåÃU©åîÜb£ü:Ã>vuß*ãßb@¢Ã.vb£vÄ@*ÃÖ ý U:Ã¥bã/©U~*uhO/~@ö©,>O:BðÃÄA/+o_ovÃ¥~åöÖrbü/ãðã@BZöãÜbö>oß*ü+C.UÃ¥.ßbäZãÃ|O< _âOBo¢Ö<@+:uÃœÃÃœ_ý+b©Ã|.BÜ©ß/vZªªªü.OOÃ¥u>©Ö+A*ãb|bo~ß.©ßOrbz<ãzuUÜäªß ª*Co~ýüO+ÖöªU+BÄßOöouüB|é*_CvOUv b|ÃBÃœ*:äª:ÃœÃîbýüåB,~AÖßv*ã/O.üa@~ÜÃÃr*>~ß~Cb@Ã<@z:ªbä©ZzÖ/ÃððZ:¢:voh,UÃœ@ªßÜ:£Ãâ+îäü*ð©ð<+ýÖ¢|£_h<ªv~*hb©b:bîã£aÃB|A.<Ã¥ub_ZªOabýuývvö,b_B©ýbßà ªa¢Bbà hvBã::z@ã,+rÃ~_îavîäaOBðªu~rßAo~¢Ã<îZÄ,ÖzÖU:bU¢ððZý,uãã*¢äÃCý¢Äßuß|öC£ªZÃ¥h_vö£ã+ª£Z>AÃœ*ßåhZ/ýZÖ©b/uZ©©ßZ©Ã@bZ~.üa*@ *rÃœ:/Ãœbý*Ozª+Ã¥r>.|Ãðãaã Uv>ZÄz.¢Ö/zä©Üäîªßð<,ðÄ.,î|:©~>**/rovÃœvbÖ©ãö| ã£ÃÜzab+ö |BîßÖüüOðü>U|Ä£ã|åÄÄBÜãbbîÖåZb ~î.oz*£O ðª/ðBz£:ªAßÃä:.,r ÄCvh*z_bªö/.äoäÄ©©~>uÖÜo|:v~Ã:aÖCAöbbßUh,bÃÄaOo£åoÃœÃßÄCö*C|îAö oöÃzãuᜆ.:Cobbo+z¢Ä|Ãvö.ðýÄöu*C/Ã_Ãœ äz.b>îz+~öåZör~,uÖ<©ÃîðuBÄ£b ©/@Zß>ýu£bÜö~ßüC/ýAÖÄrübC~ü,OüßUUAð@*ýhêO~ÃÃÄoBöC£Ä¢z¢:::Ou_~A©v_Ã|Ã¥:Ö:ä o_¢Z,,/ä:b Bªö.:bîßîoAhð/hAªr<*rãjklãobÜß©Cü*OðÃo.,ã¢üOC,ÃäU+ߣüÄCU ßz¢åãAB<îO~UrZ:ßB>|Ã¥CAÃhzß.üAhvuöªöãß>aªåÄuuCªAb+äår_*rÜ¢<~AªC/Öð*zª ¢ý~>üa,ßäÃÃro.£©@üUh*OZ. ~äZü*Bu~rbÃ¥bÃÃUaü/b|U~ u¢OªCâüo:ð©©ü@>~>ªö+ÖªßuaC+Ãräba@b OaAÃß/ÄOOvBüîaåÃü a>Ã. r¢|Ãðo¢ßüvuAuO:oüÃ:Z.CðÃãZýrvCª*B©ãBvÃßa|:hÃÃü©>@oAßuCðbÄãî£o/_ä©äð_*+Ã¥vÃ¥:ÃöýuAäª<©BbÃœCüÃÖ+.ßoãCîvAßACoovÜðÜ,üb<ßAAÃÜ:ÜåÖüß~ý<:ĪªbÄOuß< @ßzã |Ü©å¢ÜCo*/>obbz|oßzuOßð*z~r_*U£ü+î,>@ª|C+ã+£.rrbß:ö¢£oCýÜAÖðå. äAuðÄÃÃêª.*_Ã:£ ö/Ü£/ä rÃœ.Ã<ä¢Uü>@ªrv<+hÃœ Auß~Zz+.ý:£Ã.oÃ¥ÃðvörÖüO,bOä:b:b¢äCãB,rC/C/", - "6909-09-02 06:22:18.782", - "1980-06-08 04:45:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("0.2401", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("0.5885855", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - null, - 0, - 246773244, - null, - -22040, - 128, - 1, - array(("EA982235D8DCA9BAEC4C94727937A73EA975D464956D202092BC1530EEE608F1746C4B852A1A1164BC0F5A4ECC2E118A0E1FA5B657E1497C7702A31BC678CC0644A3FE0DEB21138F636FA78613D25363AB8B21CF4152999322CF0E2877F59D4443540A2830049EED0B1652E739C369A5A10A6AEA1C13EB176DD16343BEC72A33A6EC34C42BFFB15A5F656979388462ED468F181EC51982DA1FFEE416D57FABDD830CCA4F223899F258108BB6AA72DFE96F76FD2EAF0B6D8D6A5AA52D1A9B84DF"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("009531925ACB9BAAF1F372D32BD650736063B4A0A99DBCC28EBEE7325B43E5772ABC4A70994578FE2E9326B2195375BF61826ED58315B362D86F049CE4684EAE0DFBA96E8CE0D91BEAB57215760AA83A4828D0D8D50FF31409E7982A41DB1AC54578A51EAA063381A953EACDA39EECCEC6C953BC9875D9FD079465A447AEDA0F9BACD0FE64899042F44F2F822525706BFBC9647788"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "ÖCrîUZCÄ/UzßßbBA©ãå>BÖýð~Aßo+ÖräüÜýÃ.£>UÄA+Ã¥_ý£üöA:bOAoßãî:bu@bý£ß~<rbÃZuÄã£ð_î@.b |Ã¥bAZbaÖ©AüU|+br*Äâaou<|Öö*ÄÖßr|ÖåvrbU/O Avv:ZvÃ¥*_ßÃ. .©~:_B©bîrÖ©ß>ßýZvCb@Ãœ:ÃœB*v ,ü¢ZÃÖ", - ".r©Äru@,ar+ÃÜöö.aðà að+aö©COÖCrîUZCÄ/UzßßbBA©ãå>BÖýð~Aßo+ÖräüÜýÃ.£>UÄA+Ã¥_ý£üöA:bOAoßãî:bu@bý£ß~<rbÃZuÄã£ð_î@.b |Ã¥bAZbaÖ©AüU|+br*Äâaou<|Öö*ÄÖßr|ÖåvrbU/O Avv:ZvÃ¥*_ßÃ. .©~:_B©bîrÖ©ß>ßýZvCb@Ãœ:ÃœB*v ,ü¢ZÃÖCrîUZCÄ", - "*+<ÄCh_ÃœAo_åªåÖªå£u:äz¢äCÖB£>£|Ã@/Cýu£ªä£/¢ý+bÖaAU¢BO~.bÖª~+AîZ+~ä*oÜÖ,Ub+ß/©¢>baB¢va:@öAbu¢buãÃhU.@Ã~Uvã,bᚁ+CB>*öZA+h*,hÄ_Ãœoß_bz.uý+.ã+.ª,ÄOb©ÖCrîUZCÄ/UzßßbBA©ãå>BÖýð~Aßo+ÖräüÜýÃ.£>UÄA+Ã¥_ý£üöA:bOAoßãî:bu@bý£ß~<rbÃZuÄã£ð_î@.b |Ã¥bAZbaÖ©AüU|+br*Äâaou<|Öö*Ä", - "|åå:CÃ¥Zoz:_ÄbuUvÄ©üîCCöBð¢ÖâubA/vªö£ªr_ößbüz£*,o::.Är,/ðÜ@>C_@b,>ZߢzªC:.özbýßZv+|OÖ©zuÃ¥oaäbÄ.,Zý|ß*_Üäã*özUb,öýUuÖý Ãrbð:ZÃAÖý|ab.aöÃ~Ä .ÄaüßÜbåã>©.ö©ã~Zªz|BzOzÃb _ýZoöÖã,O:£*Uhðü_aðü~¢ îCÜß<>ß.uÃü¢UãýB£Äa<©¢ßýüªÃÃÄ@|zð©ªAOö+î+A.£v>©h|îZv~o£vZÜãäÖbÄÃC©Ä|uöîÃ*BB¢uoÃ:ð~OßUzÃÖa Ã.C@oz Ã_~ßBîªaªî|ßöÜz<ªh£ß©|ߣ,UÃýZª+Ã~ü..Zãä|a.", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("0.2401", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("0.5885855", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 12 -$values[] = array(array(("2A35410DBAA6E4B09FE628A565EF22E4436A2E9FDC8825E0AD0990C7272ABADFA21540D33576B925F6DEEC4AD328752635082EA9C17893D27D93EF948FCFE280A073694BA996503E48863931894542D324E329A9F4F27A73F96E65918714A636FC6B9A6FBF397CAEA7F0614B3F524938410C7FACA6388"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("D18BB53C40779F9A0029230B3658D58CF8CF92BFB6A8B2CCE74AB8BEDCEC18E60110E2DCBC905B9413C635B9A4E231C03E4FF0F60E839A28D9E855F6BEE0BD13103C0A2576F8CF6774FDABF072F9280518F87F8CFB22D77D75B903D33B64D5320821A867D70C494580898111D7AAB5DFB"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("78D37E9B1EE8D1B7B11DF93F3D21F43C9D911CC19FDB1A3CD275D21EFB60E3275C80364F6BECF6470B9057819BD58C94056F14D152A1C49DB842C9A815B3BC5039D1E3BDEAA4D0B09BD4B60E8399A0FDE83E188C4B11D5BABC999728234D3257D40747CAF2CFA9619E63367DEAB5191EA1CBC0D69B2F0428FB2800510DA78D4DA15E3A7C52D60A1895B7EDE8DB57D0077F7887578D5BBCA4021FCEC9CF5853F6D6E8DC8B7A9DCBCBC1A741E3421C4CA458F9D1942036C046A1967A330740493C63F712F37B448CE2ADBB96E619D861E5D22A71D587BFD5A02895E9E32398C4805D04804B095928136CEEE7659FD92CC9349460DDCE74B5A42172CAE55AA9BF14F71589145816DB79015E6998D770004E6820AE8E37E6B1E94FDB2A978E42B8C32FD26E8A5018701EB8E7DB700B1DCCB0727C7D39FA30DD1064A0C57CEC44EBBEA5A568040276FE50B090D5500B41DD310F792512C57EC8FEF55E25B052F43174FB1CCA65B5C7AEFC7D2C89695B8D0422C2936D731FD9F826DD690FA4504EFEBB5F6154EE76787C47CA299499F55EA3685903EDBE86BE552224A4884BB80AAB2F2ECD0AF93816AC491725A8F5445F317EC5A74A4C603F35A00B31CAD2F0308FC07D646A2753A92F4256A3A760155FDDC4720A8610BF5ECFEF986AEE074CDA59E796E878DFF40A2A1B7808F6BC98D48BAB53055FE5AA7E001E957B8EE9BB27F67C4ABFCA2DE6FDCF68DF6A98B7681256C4DE42B3467A733C18E75A4A797B406050117972D8095DC55536F6063FE549B58324CA0A7974B001D682B1468CB356ED9E0A48A5EFA07EBA817D5E0F846DFD339DA5C754B136E6DC61789C55571738A1E00967349CA4C6E2A60F89FB15A07D68857EA8E7EC013C68236C43760429626944BC2F7B05C896F8DEE4AEA1ACEBC6C1FA33CFD96E51B5DECCFE1130614FDE7CAC1FB759700163BCE8B00808539ABC38A6817CD05989FBF20651AE06B765978F0531F48C336C50F9D012F0BE49AD8B3142D88ADFD30D232324C5521F535C058318780221A62F053931820046993CA859C67CC3DEDBB8BCF70DD1686E1931A5740D6DA5D1AB698B13D350AE500DDD8AE71CF959D69826A760BC0A13D5C6971FFFDD8C392B96D7AE5CF1B00B8A0A6BEA3046815120D7CFDBE6C54514A155B64D3C30AE7381A45E50454BFCE5E71B1D56501FE38E4445FF270DB0526651C30D073A63CF595BF8698CE8310CD4DBE9F3CAA8EFC381722708F6318665D051F8FE823EF5AA844CC3FA58B656A613392A4C23BC87BD61FF8B94135AD828D592983BF0328996E63F6F7EE5F46BCA1C81F5BC063A4114CA3A9C82B2609729739A25C51E1BB6E0AF9366E44D09D86D94C17892651C229A508471B59507E485A8450A4ACFA5AC00CA140C72D35DC6C0612FEEF1E705C357788A759AAA004BA8AFE03B08FE8E9EEE1478C7510FAFC7F3BDB0546518F29EA381B3D244301256CAC964C3F221BEC2D391FFDC6F3D7248C8C381DCF9ACE58FBC4298864BBC59BE0221FD88ABECD4D1FFED80D418162F650F6B0981AE355D1DE3D406B0893777173741DA4D40E99CDBEEB0DD0C2B1076A6133377E0B2D80FCCC4DFA4D7E62448C776699CD73BE12425CE2D92FD5850F1D35D5F9C55C097655F310E896045C5F5C9681595C2739F6A320E51118445117373EDD17A8687F206DF043DA52D57C4E00938C89543BB2F5FBDF56A476E85E0191F9A1673744A6F42750EB6BE36F93DDA4A71074B7C7C30B8BE71BC911CAA97592323869241506CBEC9861F34047129C4B790FDFD7D949DA36A5EB4028EDD25CB775C9A1273AA99F9054982F55CFBB34D48F7D98CAC9ADC8E86E228E39C6930EB6154BDA8D617510CF604F73412A7466EAA67BA9604981EDC800B8B9082B65C9B189694073E0A7630A6CC96A8A2A77CDF9BB93835AF99E7C836E028CB8CC08A40A75B1B7B123F2F9FF089EF37027539D88EABF4C87D27EC7FAFCBD05DE2ABA4E5425600468C00CD5C89984425AAB0C6C1A74F3CE0CCC1C2806F9CCC466FD1BC44E509F5AB74F24B8DDE973EE5E2C7D5FF050FB6926FC319809480D7F03486EDB7DBFF40A0BA1A4CB404AE023482DC9DA8C7CD41601A29D521F07417F41A5B5957AA8609E2863444EAC2028EC0C1E4FDB7E32F2867448C9DB12A1EAF45035899A855AA8AFCD3BA219289579D189B15600C776010EB32F2121DE957B86884FEE76969E66B165A01E3280E96DDEDEF841615BEA3CEA247AE7562986C127B02B90D8A7033AFB4A94AB1757CE8DC9C14252E6540A3A9E12F4422BB70097DEDEF68265588527DBBD5626B74F158265FD1D94E723DAC3A5E0F2C5B2B11F443A0CCC48614C18713C1A7738FDE3735EFD71EC2E997BA0BB0422DC7455CF751A9E11ECBA72DD1119D49C68CCBCA1C5C26D93C830D4F0AACE5C11344E7F942B6C956C9AEBF6C8EC2050C24A0D12D40E525D0A06B2073435AD672E5E6198D2AA3070FC374016D9CBA4B7A7E012A69DBB7812E1E1658D69655126ED84D86A7A877E28099C16ACBE4B734E61FA6F4422F6C972D65EB6F134A30796D6192954E33A4D1071FA9D01A4DB54966E272B6EF60D03E6BB65842BC07CE356847FD695221E5B27BD4ED22869C7DE0AFA20831D87A890C90532A98DB6114FF507468D24F4BF645FCC03A1B73ADB8CF3978931AFF9D5DEDBDF0D9FFF74DD71ABFEDD21233AA9063ABC334183F6E98E29A5B5E63CEFA00B2167A145E72F74DAFE8205B85DBA69B8EE72F6E3B6945AE4F58F6659BEE744CC5ED1FFBB89631F0B475852F1E121307F3C0D07B9E8CC449D1CCC4067C91B2C174D49697BBABE2F8B53FB9C6E4158F3D44EF71DB1EF8AD3829D629ECD12B7423C3C4BA4A7B7B4B47DDD7C65FD2FE65BB3C87C28D1FD7A27C265D6EE482F6278ACA9603205B45C1C9A32AFE46314E662B716DAF4969E703EC213D231D0C67C5E6AF3C943BFA4460F7C5F8E9F5D85B4BD8F743731447DE20E29317FE1D22A4BC3C641797F4BA2A302C0CB78470FD0D13C5F24B25C8F3B09FD11BB2A8E34643D5213CAD90C1C4561DCC1347078F59B0E48503392BD67EF348B377C5D907824CE6C9E262344F7D51009B2D59B5ACA80F776BCD23DF0C17FEA24865DC49FF7D154AC6603EADF77C9408B23990A9253E9C0B674D6E3A5E6CD9B2E3C0ECEAA007011D170EEE4B6A6280EE381B2DD387557D09C8D1E2B10FDBEB5A878BC93690AA67AA387292164F51A005CD7F02433F062A8E42E221CE254F0E3C2294C4DE98495E8DB3829A15C266BA7731DCE490D8A81A80613BF3AD889984E86456F4BB8DC16C83207490BC6B15CBDC28A99D486A7D637A79437D5D55B0051AFEB1B16C72FEEA6F70616F64175E144B6F1785DEFC3B7EE6F3794149440E2D706F75BDB9645F6A29BF2DD8090EFE631A2F31ED3E2A261EC233CB79F9BCA12B6134FEF5A1A05AE710AD5C1FCF6375D7F41626191221EE454857FB8698E5706C1D0176D50F09C17AD549DD90CE064F"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - ":hî+Äh_ÖzüýbBßüa£î/,Uüu:£OO¢Ãv@b:ßrªZz*©ÄÄaÜü+©î£.:@aav<î:Ä_üîbb:ðªvª*à _Ü©@ö:výZßýb/|ÄÃߪßÄߣüzbÃœ", - "ÃCüu*Ã@/ü/AÃðÜU:äOÄî©îÃÃbåäýo/>CüÃ~ÃaãBßOUäoÃœaååÄãrªÃ.¢Ü,Uo<Ã¥@Z_öðÃuO|:abîÄ.b|öbýýZzuî.|bßzÖâð£ü uäZ£Ão,O@ÜÄuÃßzB<îUýzA>oo+và :üb/Ã*bÖ*ZhðA+¢ããb<:", - " ", - "aO+C> åÃäb᜚a:u*CªCbv©ä zZÃœUÖzð¢:ÄOÄ£äöUuO ßrbbAuUA.zðÃ>>ßObC:BÖåAzZv@Ãv©åãb*AhUrÖa@Z> _ü©rã©îCÖB|ðoÖÜÖÄ_ÖÖ.B©o:©öã+:~:+ÃÜz~zäªÖÖbýÜBü<.ýzý_Öî:z>ÃoÄbÖz>C|<ü£_<@öãîz+ÖU/£C+:U£+oÃ¥rðâ:bßüvz©UOÖßuü:_Uz_Ã¥_î+üOU£A¢ªª:ªßå>Ãœb C/~O:*", - "oß,ß|b@ß/ß>AÜã*ÃhOzýCÃœ*z>>/Äh|Öýö<.aubîrOUª bZ/ >bZBßåoaßU@bbÖ>_Ö,*ßähÖã~+|ÄZýo©ð*åýîhÃU.hvb@|Öð,>ÃÃö¢:* :@/ :ÄãbãhÄä>£¢©C,ÃåÃza¢¢ ßU<ÃzÃ¥@©:ªZðü./ýÃ.AbåãCaßUÃ<¢h>ßÃhð~~Cãã*ãOa+r.z:⪪|/öh<ðbhAAzC>uã", - "ö,bzßãZߢ~OÃ¥O<*AĪouAãAðåÃ~Cã/ß_ãð>z,AäOBOãÜäOýzBªoß: ßC*o.bUOörߢbvýb£©ÃýhãavbÄÖvaUvüB>B+öU¢ßððU¢üUhÄzbüöÖ_ÄhĪ_Ãü_/o.ý+ÃßZhvAr/oüðuA*UZh/C:b©oCü>vaà ÄOzÖ_AãÜuî|B_OÃœ+>*ÄvzÃz_zb©OoÜÜrÃßUÃðZßr/*rrîüCü ªýîZãö@B¢*öÃã|ßäîuÃœUähãzßÖÖÃb__+AZoÖ*Ü£B:ÃœhB_ÃßA©v/OöÃOz+U©brßAbß|©_.A@h~ÃÜbaÖÜhðÄbßC+. Cã+BC|å©ÃbZüAv¢£ÖzªvîBz¢:_h>ÃUü/o /|£.aaßC*ö<ßC©b.bßÖo.A_Ö¢~/,ö@£ý£,Ã¥<£z,üO~A©v/aÃz_ðvßhu¢bãä/<+£bh~Z Ã|v*,©a<ÖßO©ã.¢|OÃœbÃß@h©/ö<ßöü_ÃoÃüÃv|äohÃÃ¥U£OªbÖBÃ¥*@~@u/ªî:Ou_ÃœZ>@o/Ã¥Zªîî|b¢åav*.AäBOO|oÃZ£©.ýîrBüo©/¢åßv/ªA<~|ª£BÖr©ÃBzöaĪ¢ö>î£ã_îã~îßobãÖ|@©hÃbazUÄ@.äãÃ/ðZozöÃ,¢b£Öbröb¢ã*Ö. |ä~öO©özCAaä<ååî_ßähbBbzöCZ©î|@BzB*.|*ÖA: ðªÖuÃbOÄz_Ã¥ZZ<ÃœZab,Z,|@î_+@: OAãÃÄo<|Ä,rAª|zzÄZ©rßrz_Ã@|¢ßAß*r@ßß,ÄbA£ÜßC:O/U~ |ba/+Äb_hoÜîå*zoßbÃœ@ðaU£_a,OB@B b>:Ö ÃœhbÃã~îvÃÃ¥:ßz**ýBU.>©/ ~äUý¢åooöð©:>|îª*hzÃ¥ bÄßÖ/öß_ +Ã_@uAv~¢~CUã*¢Ãý/O¢b+*_ý>h:Urö+¢ª|öîý*Ã¥*Ö>ÃÃœbU+Ob/@ªðva+ý@Äîß*.£Ö¢ußÃßýbzZzä¢ÜîvÃœZrÖ.ÜÄ*_|î<¢ý@,BBA:A|ä:äzO£v:Ü©z©ÄðbÃ¥/_*ýªaß~ðzðüb_bö~_>_¢~zC¢£rÃäahÃäý_zbªã/ü AAO.:<ª£ß©ãä*ü,ÃäößCÃîorBbaÃåýÃb|z|ZörrÄBrr:Ã¥a¢|ð Ãî¢ä~Cäýz,<äÖßýÜZb.CÜ£ÃBa@ÃÃzB./ß|UÃœ/ÃÄ/.üîßuu:,h.>r@@öª£ð>©ÃßÃ>Üüßåä㢪Üßãb*_¢>bÃœA/_©Ã|ÃÄÃ.ð~~A<ªüö+b¢Zßå.ÃÃåUBzaªöÜZöBvvOoorÃOUß~Ã~©Cühã©výåß:~Cðý£vO©ä©Cå¢å¢ßb£Zãv,+Ã|BÖrª_.bÃaaB£ªb:A_ý@öbß|>/Aä*ä/Ü¢å_o/~ãßÃ,äÃ<ªÃäÜ åÃĪv~äA|aa©¢£>zäoß©Üö_u© £bBåÄoBAýÃba¢öZ.aßßbÃœrUOÖ~bB|öo>öðößöÃzã~ª©Öuä_ä+*ðÄ.ª|:ßîhhCýö:o@<öåAOÃra/b *>aA|Oêh¢O£ßãäååªuà ><:Ö uãAîZZBr ã .uªOö|©b Bß*äªbob@*./ãåvz+Ab©håª.ð~îå_+ðß,/u.>oZuhA©ü/î~**Ã¥~_¢,OrrööAov_h/~b,£ãßrÖß_Cu£ýAa__£ß+rýUAr|uUbÃ¥>_@ãäOÃ>b+ßßäbý|öðaªÄå*b,bh¢¢Uý>Ö©¢©ChoßäA|.~,hOb£ªzU:oªO<:ßbbåã+zbr/z/ßÖrézð:ýðö~ü£ÃvZ>a>¢Ã*ÜßoÃAZ*ðÖÃrß_AÄ£ou~o+ÜÖ£+UbãÃ/Üý+BUUbaî¢Ö+åðUªüb/BýßCªÖß/Ãu<£~Ãðoöé@Uªhzü.aåã,Öva.ª/ýäÜ /ßzß.ýO¢üÖZÃ¥Z_O<äÄ~~_Oý*r*ý><|uðö<Är@~oü*ü@u~UrðaåÃ++Ãœ@vzýâUÃ¥:>åöAü@ðbî_¢u~£ÜÖöob<~åä|oü/Ã¥@î_Ã:@A £uu,bßÜßÃ|ö¢_UÃaB>ý@výZ©Zãü*Ã,uhß,Ã**@*UaßCßUho>Ab|îzÖbUA+üðã~ðhãuhÃÖaC©>ä_rö:Ü©Uã,ßýUãÜ£|>,b,äbÃBîß©oî**ObrbÄîC:~å£OCo~+*br@ĪBý<>ä|üýböb:ßo£Ãð©+ð:@uÃ¥|ðz/ CÄߢ|ürªuA|>~ßÃh©+.ãC_hÃœo~ܪöðU@©üuußur/BÄ¢ß/Ã¥Ã@Öå|åéýOÃã.,/avCzüîã<îäouz_.î *vãvãb+*ßaüb_ýßßaB/@><üîbãA.©,C~v*>o_oouÃühîä.*ä*,z:ü,ãäZöuaaüÃ/Ã¥,ÃœO<£vãðrÃœ_ bA¢@î/|ÖÜ~bîO ¢rA_ZÄBüª<Ãœoa|h~o.vUîußZ:Ãb+bÃoO:*Ã/*z|£ÄÄoaÃÃ/a¢r/¢¢ð.,+ÖB~¢Äîðz~éßÃUäã¢@rbß_ÄUbÃäU@|@ð b~uüaýßUÃhbÃœOöö~Ãœa.u |u+ |ýb©ßAoÃ>¢©¢åª@.ßC,©<åÜî©Ö|CÃý>+ã¢:aÖ|îCÃ¥Aà Zöî,åß>:ßÃä©rhabCZßUo@£UrZß/r:¢ÄãoUÃ¥CbßahOu|ý+~:>Ãb*Öãh+~bßuhÜãb+Ã¥U>OåîðvO<<©| üZbÄä_ª _u_îbU~Ã¥+ OrBCbvªÜoB|*©ÄAÃãðbbý>Ãœ|ÄZ£üä©©zCh>+uärßBb,@ÖO_ãåz,£ßz*©/_,ߢ£ð<.UU_u£>ýÃUüðCUß_Ãßb_ZA<ßÜßð©:.O:hðB@öa¢uß.ð~ /|OÖäbß|,r+Ö/Ö,Ãß_zb@ÃœOhZªÄOäÃÃ¥Cî<*vÖðª**îÖÃÃÖ>Uu>üäª@ߣ+A+©ÄîÄ/*aÃrC.<î_äC_bð£ßr¢ð¢öoå©*Ã¥zÖOCbãuBÃ.voîZ¢*ÃãÜÄåà ªvã_rUåöߪuoü.ãð~ÃöruZ:ð_bÄOÄÄ*B.o|ärzaãã:hî+Äh_ÖzüýbBßüa£î/,Uüu:£OO¢Ãv@b:ßrªZz*©ÄÄaÜü+©î£.:@aav<î:Ä_üîbb:ðªvª*à _Ü©@ö:výZßýb/|ÄÃߪßÄߣüzbÃœ", - "ÃCüu*Ã@/ü/AÃðÜU:äOÄî©îÃÃbåäýo/>CüÃ~ÃaãBßOUäoÃœaååÄãrªÃ.¢Ü,Uo<Ã¥@Z_öðÃuO|:abîÄ.b|öbýýZzuî.|bßzÖâð£ü uäZ£Ão,O@ÜÄuÃßzB<îUýzA>oo+và :üb/Ã*bÖ*ZhðA+¢ããb<:", - "aO+C> åÃäb᜚a:u*CªCbv©ä zZÃœUÖzð¢:ÄOÄ£äöUuO ßrbbAuUA.zðÃ>>ßObC:BÖåAzZv@Ãv©åãb*AhUrÖa@Z> _ü©rã©îCÖB|ðoÖÜÖÄ_ÖÖ.B©o:©öã+:~:+ÃÜz~zäªÖÖbýÜBü<.ýzý_Öî:z>ÃoÄbÖz>C|<ü£_<@öãîz+ÖU/£C+:U£+oÃ¥rðâ:bßüvz©UOÖßuü:_Uz_Ã¥_î+üOU£A¢ªª:ªßå>Ãœb C/~O:*|uZrãð~", - "oß,ß|b@ß/ß>AÜã*ÃhOzýCÃœ*z>>/Äh|Öýö<.aubîrOUª bZ/ >bZBßåoaßU@bbÖ>_Ö,*ßähÖã~+|ÄZýo©ð*åýîhÃU.hvb@|Öð,>ÃÃö¢:* :@/ :ÄãbãhÄä>£¢©C,ÃåÃza¢¢ ßU<ÃzÃ¥@©:ªZðü./ýÃ.AbåãCaßUÃ<¢h>ßÃhð~~Cãã*ãOa+r.z:⪪|/öh<ðbhAAzC>uã>ã,~~rÄb©B¢ªß/Aö>îrhÃü:COÜÃzßäîåý|ߪzba,ã,ZAãzUÄaBßrZbh_,v,ß>,UßrÖ¢U¢/¢:åßZz©.vÃ¥_ýzÃbÃ~î/vrA©uî:h>ãB©ýb|<*z>ßãäýa>v>B_Ã>oÃß*uªÜ©", - "23:59:29.0498764", - "9876-01-31 23:59:59.0498764+08:00", - "9876-01-31 23:59:59.0498764", - array("0.1342", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("0.7331876", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 13 -$values[] = array(array(("08757ECC341053DABF2569D352C8B231845DD236EF0EF8D319BDB269020047C0441746578A98F5C022B6B06EE2005B1319ED0CA132E5CFA7CABB3765DEAD7CE20ECCE11534CB8DACD7633C3A3F2E4F8B7013764203A68D5E57AE8CDF618FF18539CE50251F45EE4E5F5FC9DFB98568AC6BABF5CB273AEE683F82CD863EBE4BA5"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("D70B88DA36D29A75543D70B1B19C21FDD2131F4DA64F47BA35813FFEDE19131275C2EA0039DF1FBFBDD4F1E65CB4495502977C92AAED3287C4CDA4B5929C973B295D3CEFB221C4F2F46090D15E615E8137E9FB469C8168878F623C6C3B692C213DB6BCFE775AA2E1D28A9D4A5D6028626E227DA9A0509EA8"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("50E558B78A336F94A8A56009DD46197412E41D292F3AD9FD19EB8380EC171308C2FB52FC7AD21217EECB80BD0D18A06286DC8CC3D88B2F150B5A9E9EAFEEA890915847E1F31DF93FD1063A63B91CF0004B21FA108A503036CB3CF1203473670023EF2CB5D1BF6E69E34DF1D151CC5CEF6A9D538EF4970541271016B62CD80A5E9F3A3FC9F03A2E8BB2902C3D5E5EE5FEE001B69720D90B8BE9EA8DC1A9B73C0A429332178D92FF2F6FC7DB636AC8E3C92A17D62A22733DC52EB3DF05AD6DE2FB5422FACA2CCE714E02862858FDA8202107C49691E112E7CF556CE9D9B2FA8202E4E00F48CE189D5CA587289C4BE5C88692BEFDE8D65019423B2A74BFE5979B070326731D98D853EE273AEC59E94B8470A46247F4FC9A2B94C6CD0CA3A4DD770DEC20982572ABEDB96B0EC19C64C058F0A6041B8250A70B69FADF57CBCBB04A49C2DE6DC8B25FFDF9758D89EED88F2F05741CFC9FC2F5BC44152F2A925421EEBBC378D985538F3E4706210CC31E1D916BAF5157AFB3AA86CC2228B382346BA5549FCD2A60B5AAB2E4DEB599A0E9F926F89EBFE302CA3E0718F10D632BDCBB139B7CB82450F231B5059F72D0FD11E6E32802BAE47487A28960C6116EA846870723A2FE2331D5D5056C0ADD63586E09070A293A2821D69E13DA2EC38AE1C20DBF176AAD5B12C2B2532CB5776AA93E31F1BC09637E0B5EE64C03EA4902114B24AAFC1E4BEDC1AEF44877E7FC6C5E34699BFCD2CED75E8FB3B98460582878AF42E190CD6D8BF344300E151B06246179EECE81AE053E7D4D4C6C6B92696155CCCFC8A41BDC91C6DEA8B8A1C0C143898F570A7289A7CAA903634280002061DACFB8484C3E7F469D5C08A2064D9DBD8B09E3DB3A5EEECC25041EA1B79910C5BE47DED20778F64A0F116C168004C45C66A6B6771C44277BE0EB23471FDE8E044B42E4BE6ECAB5131298CB28F2D84B203FC23B11E299A8791F62F11C55AF2B318268CA3E2005ED9BF1A3658378FE8907A003075CA27D21B29A6A9F0CD898BE626D99A5890E0D003AD49F1F3F33240121D8E97B51A8CF8E054717A3B666E1C358E50216DE5800F99203CD87035EE27E75697D33EE1D6CBA9E0AA042F66949D3F3DCC759300D22C8D8F0E94EDA478BA664982E4DE9ADD43B794878D4B7BE0329B9FA942796884E6746B45BFE331DE0567FF9DD5D3BFCE946088BE0E8954E1119ECBF95E0B26410DC4AC6063451D5075B682154BEE1A6EDBC104E1F0BA765D2F8A34111738700A41BE9001275F5B5F43284848073F9523846D2D8CF64440434E4F30E9110C393D3687D39D4E09378C65B59EC8622E55FFE5008CBA1AF3040D6E4CB2D82E723E46EF0B118F5C07E58B35A11740A577D11FF93104007F8F124373D62EF1465F6DF37757E9671CD6A3DD2B017479DBC357E66BA2EEED7E2DD75FE8CE0B85E8E444D64323C94D9E79F3129D26E56E0AA867B07E3885A37070854980897E5ECB2DE436CCAE36B7FA1A57F1AFCC504738694540664B07B06E9165C29E7FCD14FBA4477F32BEA16C71AFC663E093F3B4203E0117343DE8801633D82B4864197A4D42F5FB74D11489B7E8CEB68493CFCAADA1A2606EA5D0CDBBC7590EE686240D864C26AD9DF83F4F29876F5B6F31FB82640D8D96C1B90BA762284FAD8815A331B4C130387D222CA5625346E746756E89CBC69AD9089D4BDD308F3B75B891C713E42D96AE01B31531D0D8246E722450ECC9C5D1B17ACA3927755307FDF6B6FCD08C230318EEC08808B158F82068DD6B1C2E4EABF2B0159919F47F28AEBB274B19EB53D24F11480E094DC8F021C208CE97F7EBDFB80AFA107699E9146F97D94D9A1DDE3514FB1BFAD630A2B4E7157747EBA080A6DF57831EE1336BA538239E61647B6603E3C3A99CDBF0F9776845295122A138CF07327E328ACF43E19F76C2B29231406BBF63541806F3CAEE5E0BA6AC0A0A8286FADF6A320D2389148FC80D652E46E52704BDA1BF4D10B813679FD47F28A7E784E43BB7B2D71E6E8BDA4F983EE655B017267F974C58C349EA1746CA6D4F09FBD7E07880F28F225FE65F25D50D78892D01EFCD58A9109B3C10F1DD9B9054013CD043C04E7DD4CD980"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "Z@u:Zßb©CãÜ,bZðOîU|hýAýýouöðAZÃC¢ðrÄ:+/hhb@ß*Öa B~ü¢£Oäv_uZbÃ¥C¢ßo©ÜCBuÄ_ß/ýð>r£,uªã: b:/oý>ß|ü. *ü~Ã¥uvvorã£O@üÃUu@<©äBã+ý,br.üܪ<@ðzª~åðbåöOãvîrUv|A>Ã,Ã:aüuªÖ@Ãœhðýbãrð:zh©uBßîbOh~ Ä>z@*¢Ub~ Z|ã.ßßb_ÃaÖ>:O©ª*ð:ãUÃœ*ob¢ª:ÄrüååýäB+©aCa¢:~|rbhãÃZ ~Ãb@uÃ*or~ã|Ãüä|,~:|ýÄ@*b:.ß+rîva©*:zubv/o,ªßZO:ð,oBCAß_ýv*Ãœ<£ßZOU|bª.vO_rCã ð_ÃœZüåüAßhýãß:bäZßß C/|ÃBä@hý|ððýubÃ~>@ÜäU+C|ußürzU,z>u>übOC*ßAUrbä~+ ZÃœZa<äb:oÃ¥b_ðÃz@ª>a~Ö@ ..ArCî>+u~Ã¥aîzbßå*ð©<+~ß|Ö:Ã¥~:£ö|£>ãüA©ã.Z<£oU¢A*ªhäOuAa¢ß UaUÄ|Ä<öUßuããÃBvOåð£zh.@ýzßÖ ªAÃzu:ä©AðÃÃîCBoðãävoÃoa*, >UZbUåü@¢:ðA£Ãzöb AZZCäÄß@üÄrßz *ýÄbÄOÃCuÖC@z©.|@ãahãÄÃb*O|ß@ÃuAÃvÜÖ<ÃœrAaz:AvÃ¥>@b,r+ßO>£Z£Ä@ªå~<_bßbå©bâüh_v£Ü:©bÃC@äu_Ãœh:ßbÃ/CÄü>rb:¢. <ÃãAö+¢åvä_o<üÃãvvÃuU©ö@aßZo~+r.zßAC+@ßÖßößãåüUýß*ßrÃ¥rBßß Ahbý.,b+rðh|,b£bÃåãUBýä*AÄ@,übªOÄOª<öäÄ>|åߢªÜ<<Ã|£ a/bhhoüÃä©.hzöãu:OhãO*ÃuOuÃUuA£..ZoZB: Üåa¢å<à £övA|/ßh©Ãäüî/Ä<ãaýÄAß|Cýã*öOÄßÃ>îbzöaBýªh<_U>/*o@ßä<üzä+oAÄbäüöðC:a*.@ßðaCÃ¥zAÃ¥>.aÜ©ðöU_~rðßý,îÃO_ªCA.ýÖýüö<ä.|*v@zbbzAýäð.h<Äã@B+ÄÖ.Ö<ýAA©öýÜ©O£~@:bä©å>¢UBaãvvh.+<Ãßö@b Ab¢BýBUu>îr+:UÄãvÃœ>+üOÖ,ß~.r>ã£a.ÖãªU.£oãÜ +bÖüCÜã_Oa©*ãZUZ,©béUOüÄ.BªãC*@Oä,zbªZ/£ZîC>að+,_ z+hÄ£üà ++ߢ*ýßbbr/_~a<Ãa._uÄu¢ÜÄO~Ö+zZ,Ãö>aoO>_>r¢züaÖzÃ¥,Ãu/@ªhÃAîO,bÃÖîAߣ>:|ãðýÄ>ª ßÃ/Zäüªbüu +Zhvzh:@ä,ðuCß.ãäz/ßC@.uuÖBrÃoãb|zCO/|ªãvýÖöî< rv:v@©,îåÜ©ß@öÄbvöîß_ãýb¢Ubrîörî*>ýßðÜZä.zä£*h*C+ z+U£ü+v,£Z¢rýð|Ãœ/aüîo<£A,uü*Ä>åðß~¢ã©Ä_¢__haurzðb|Ä*ob£~@ý/££ðBb:¢O.v~~O@b@ü*,¢¢@,*z|O.h@©>özÃ@ÜÖbzÃÖÖåðÜbäuü©AÄ/Ãî.©hu©O*î~B¢äã_ÃrzböäÜ@/@< rOã¢Ü@ªÜBCªã¢@.äZãorA©u:â>ãhîUªÃ*üobüzCvb:¢~:/.:ðu~+vZ_ü,Ã¥haÜ¢OäaBZ_Ußz@Aöör~Ã¥vrb,uãu:b/r©BoC>/ZÃœ~*oÃ¥rCîb* * ,>ªöð+.>rߣuBbhu*ð:@ðßzðArrCÃœ~Ã¥hÄCßo>ýUýÖhà >hU@rB/AhBÃ¥:ZîÃuöÜz<öÄbüäu.Üîýð~©@zZ*Ã>ßZho¢Ã:uªzAAüßv,Ãœbv£ü©£CooZÜÖ~,ävZ>Oß|/u,aß<åööÖ.h@ubA@h¢Aý:/Ã:AîOÜà b~u¢bvüäOuru_+ªbOÃœuÖ+|Ãœ|üÜÄ_Ãœ|ÄOU. |z~AÃœ|v/ßÜUî äýÃ,_ðü@ß©©v_.Bª@AüCAbÃ~Ö:rBCýÜÃÃ¥+ÃÜã £z:ßÖ£ðÖuhOßvÖZÃœzßUh¢BÃÃ¥,ãåð+OÃzÃœr+£©uzB£ýubðZÄA/CbÃ¥_ÄÃ|~~/îÖý,b,äbb+¢BB>h<¢ä©öBCä>vBãÜb>ßbBÃœ,*£ªO©bu¢£ßaý,,ubÃð¢ ßî>Cö_Ba~_~ýzCßßAÃýåä>rÃUC:,ã/hh<:ª:hãÖUÄ>@bßãä~*©,îîÃÃZvÜär~*öbã£|@.ÃÄ~/_ÄîªCðo/oüðýh* r.@Äý_£+|:Uov O©üß<+aß©häÄÖ.,aBÃzu ýoaªåå**oåÜåCýZüo©ý +Ã+äZÃœUü_*_äAýO@öuª<£:@vBߣ/BbðAzîövßozB£+ßß*îãCßÜaöb@ÃîrCÃœ:hßå_hAðÃßî@ßv£ÃðOü@¢ býüßubb©+äöä> +ã+ÄBÃ¥brA_Ã.UB£CrZ~££uZß//Ör:*ÃZbö/+CÃ¥/*ýoov©z.rö©îÃ.h Ö/ä<Ã*ªÖãÃa>|©Za ãu£ã©/Ã*£ß*ü+Ãh|öý*ÃðbuUbÖ.:zoa|CÃ¥B©/Öa>_ABrA:öî@Uî|zð£ßöUÜðavb<ßßã>ª:hOüäråüAr¢ß@öbv/vruvÄrýBzýu*@¢Z ÃörÃ¥,ðå*¢öa+råö@äß<:/ßoßýüüa,bÃüoý O@ÃAßå>zbhÄz/AÃ<ÃoãîöÜ+î/üåü|uAuu|ÃœaoÜÃ|ðBÃ¥vÃzî:vü:ÄãÜ:ðZüruîhO,ä+OÃ¥|.Ãu|Ãœ|¢ÃývC*bCî©:hß+bUÜ£î~öÄåãðÃ~Ã_Ã>hÃ¥rßUZÄbääavÄ,U*>>Oö|©O¢ðßh ,Ãœ*|oO vª_ãöb~bÄö/O/Ã+br+Üð<Ã~Ã¥/o+ÃB>/B@büÖö©Uîý¢:©>¢Ã£AÃor,vä~a©,ÜÃåör> £*Ä¢h@håöãuð,ß:é,*UÄvu,ÃZv:rB©vOoü ð,|¢b> a*Bvª öu_ävª,hzåãvéãOüÖußCßvãÃOröuA¢a©CvÃ¥C,î|~rª@Ä*+ß<ß,*bßuhöÄCB,:îZvߪ|.öb|ã©UîrÖh+ß,:.£vüåîCð.Zßohz UåÖß,@Öß>,_zör|Ãzaý>¢ýäézA©ZA@Cªä>U¢üýð©ßÖ~ ~oÃã_>hhßÄvAAoB>b_<ä/ÃBÄ C AbãßuZ.boZßÃ@+üßouu~ßî.~vZÄåîba>.OBböîbâBðrvßö><åöh£v_ð~@oBvz *ZîävO<üÃür :*u.aC_,å©ü/Ã*CoBðßðö>~r|A©Z¢~Ãœo/öB~+CoýÃî/©z<.£__äAzb*îãBÃC©A<~ößãOhÄ.ĪZÃåîo,¢/hÃÖäðÃBuuýv_bä/©|᎜U>ãCo/a :ýÃÄC©_*ÄÄABÃöðÄoO_/Zu* ©ýr¢ä£Ã<©ÖÜuz_.+zBöãߢîZ~Cöüå~bu£*bh:ð>ba_.ýOÃ,¢ÖOCrv>@AbðbÃÃÃ~BÄ_|ö>Zî,ªåßUuðBåà uÄ:äüoßab_äÖ+ZüzZ£C/C:ZÄ~,©ZßoãOa+>å¢ã~.h>u+_ã>_Zî¢/ÖZrÖ/Öv|<_OîÃÃÖaßBÃ/bÄz¢UîOÃÃ¥hað@,.ÄoÃ/Ã¥~aö~z ©ãB<ÃOrr+Aü~@@|@Ã*_U+ZvAäb£ßäbÜà îu £,,ßÄ A+Ö~uð bz~bbö,~b¢Ã@, zvUUªZuäZöÄ_bvÃ¥ZZãÃ<ßÖO©vüãÖªÖÄz ýÄC> ª¢BOA£*@îCÄÜ*äßî_|B|ý£:.özã~ðý.£©@ðã|Uu~Ã/:C+åߪ,Öã~UößÄuC,/uhBãauãå_z:ÃC,.o:,Ab>UOOhß,ÄÄr¢:äß*ª ªCobîbbZh<ä_¢Uãb ðüüðÃOßhO Ãaã AßßãvÃ¥aªOBîý*_OBöß+@ä+£.ðð ðÃÃ,ZzhßÄß.|r ßäAÃãZöb+å¢ <£O.ð*_Bä/ĪÃrÄ:Z_UOO@~Ãœ,ubbü©+@£ý©o_,Üߣ:höb,aÃZ>hrbOüÄ__ðîbb@öbr¢ýU/<<Ã¥_äOãÄBh+ßðhCvZü/vba|åÖªðOåäÖA/ãa£ @ ßåã:Ãœ_vC,ãÜð©_ü*:îýßvv©ßu, *bh:öÜ££,U|Ãœ*<<_*BöB<<<|Ö Ãb_rau,rðbU,ãoAî|Bîh£zßÄ ÃZzääª|ĪA~BrU ,o|/Zövßb|ð+£¢Ã/vßåZöB*b/éuC/ß+ä~Ã<Ã*.+:>UZöuðBð~ Zzü+Zß@ýªZA©¢Ö_@bÃ¥rÄî,@ß:äbBuÖ~z@C@bîr:©r*ßB/ߢ@.ÄãuÄå.åÜoð/Ö/ÜãÃÄh>U<¢ä._>|~/+¢U_Ã>ð+ª£Ä£ZÃZaðÜ~ü,ÃÃCZðBýßo+äZ*ÃœZrrÖvð B>ß~rÃ¥~,ªÃüªb£öCUÖ.ð~/vä*öüýßÄAzbb¢O ÃÃu,,|£åÃZC*BöýäýÖ£uðUUîª~åÄ©_ýAÖÜ|Ar_Oå¢ü££ã¢ÃÃœA/bzüö,:BÃýð_ª.vÜ£åä|ßÃ@ÜÄO*aaÖCÃßð>.u_£Äð¢å<ö,Ub©<ýÜBãßÖUbý@åßüåî@UZaßbrðaüo<*.©ýýhÖOýU/¢îZÃîöb*Ã|£~bî¢a*äÃ*O*o_+ýß@©rÃîZß©C@*Züð¢:ðÃãªb+C.zUÃ¥UvÃÖ*îo|Ã<äÃzvU>>a|Ãœ.|Ã¥Uã_,Äh|ß+©,|,+Öý,ß+ðübö@*ãäª,Ã<<Ã+>ðß@:.hÃ/b< aß>|COZãåaßãUîä ªbã¢~A.,î|BABÃ<ß__ß++ßãªýÖü~ßC<ö>uÃ¥*uãÃý,C£|bvÃr@azÃB>üßUu¢ãbß|ßbo@åßh~ÃßãýUhZ_ÃaÖbßßÜãaUbBýuåð|_ä<ðÃä.+.¢åv@Ä©üÃîãZÖÖüZz@¢ÄA~<öî.Ävߢýh*Ã,Oî~<_,hz b¢ßÜåOªuUà ßhö*Bý,obîÄöb+Ãœ,ÃAbzîZhîvªð,¢aÜå©*|büu+_ýýU/ ÃÖ.BßäOO+/_abB>ba_bCßZÄßÃ.>©@zîAÄ||ä@ZßhªÄZîaAðhÃ¥<ý:rz@.b~*++@£b@@Z<@£|ÃÄî@CýßßÄ>*Ãœb :ß.Aoßðð:ÃœBbu,>Ö/¢UîUÖzß,.vÜ£äAÃ~ÃbÃå:*ã£ßUC.bÃœ.zöbÄ_oBÄ>¢£Ã_hv~Ö*:>zßåýv.v/>O+ £ä<Ãßzr/¢+OUߢz>_£+bªvB@bb:Cå£r vzB©©|Ãœ:oaäöª¢O£CÖîãßZÃ+Öö¢Äãb|ýoäý>@¢ãüåðî~.hð:îßaCÖ+>ü|ÄðÄöbªB:åßâ©C>ðöüå,oüªAOßbb:UÃœ+AößÃhaß ßh@*OßU©@rß.ª|.Öz©Üv_Aa£A+~Äzßßðo|~a/vä*|+@v/+Ö:@b,:U_ÜÜCÃ¥ ßu@+Ã,äUbý,zAß.Ão>Ör£¢hrU, êÜßî|öbhAaßä|CÃÖBÖZ©oÄå£@ð£bBoÄO/vÃß*vßbz_Ä£Caî>©b_ßCvãü©CÜÄ¢ÖbC©~©:*üßzÖðAåözå©+oßA¢åbO.ÃœhU>C.|<ªý@*:hÃUubOUöa+uB <ðA>ß>:.*z* ðahß,ßåö<ÖÖa,ßö£:CrAä.|ä©,:©Ü_£bäÜ,:ßuBýª_|ßb|/ß|ßöUC :>/vhý*uoðCÃhª,<äuar.zäÃßBü¢Cüª.ö_zÃ¥a@BB¢,,£Ä+/ß/böÃuîðo£Ä:AaÃCO ÃÖªz>: hî~aîra/©oOAaÃ¥rAªoUzbbBîZu A¢ã>ð¢¢~z îU/Ãa+ä_Bo:>Äýhvovbß©ÃÃåýßã£U,,ZåÄÃðߪbÃu*ObÃ¥_îã+©<+><£o_|Är@rZÃÜoªaZu<_r,~ _£a/ðýZ~ðaß,b_+uîäoAb>vÄðå|hrðoCî ªå_vv_ðÄÖÖ+rüÖãU¢ u|Ub+z@A/h+ýã©>Äð>ã~¢B*Ã@ Ãðßzözãå b|@UÖUBÃœ ÖîÃÄUã*|ýÖª:îzäüÜB£ã/Ãaßo£*ü/î*@ Aýª£aÃüö, bZäOU£Züh:ðîbåî_ª<+CB.bhÃ¥ vzÃœ.©îA+ö>z:Bzoðrb.CðÄÃrÃœub,bß,AßB|ª£bßãbÃÃB¢bU|üî+ª©Ä>£A bhãa@äÖ_Ã¥aB+|Bð~©a£.|Äýa¢Ü+o+~*.öOß/OßvCUª¢ßãb<_ÖaãßOhZABîö_¢>¢<ã,,î*ßZßbO.<.©o~Zß.*Ö@Uጚ väÃ>Öüoý/£~OðÄAÖaÄߪ/Ã*UßBvÄ o~a~<ãÄ/£vb|ª:ÄB, AZ Öý/@hª£a|ð<î.|<:äýaßãA<ähbAðª+ðbßão_bü|bb/B*ª©rÃ¥U£@ A<>Ä@|+B~Ä,Oa,b|.ð,î/üÖ,ðCv<ähÃüä~b* /a>~::*bî*r~¢:zvªîbÄCÃœ.zîu/A. /*ÃUüðå ü>,*AÃœbîUÃ+ÃU©ý¢u£ªO©uAz¢~ðãã+>++Zö_ ÖÖ¢UýýÖ£ÄbÖ+BA zÄ£Ã:BÃ/äubäOa üÄ/z<åýöÃUZ£î>o<Ã*ob©¢ubUazÖAöh|£,CAðß|/ ÖüAö£ >*b:|uðÖCÃa>,C_ößÃÄî©ÃîßO+bv¢¢_ÄÖÜ_~ÃBäCãu+:. |~u+ÄZuéÜ:vvro£CßãÖãr£ä*/OZZä:ßCåßä Ã¥ Cª/ZvªU zZã:O.,Orüßåýß~h@uÜéCü.ª<Äh.ð,UBCäý/.Z,ÄÄ¢î@a*zazÖAã>£r /ãÃÄvÖv~~ÜäöªÃBö,Aö>båªh.hCO*.ª:©©a~~©:ü*zöU<*.ª£b/<ÄÄv_u|Zh¢BuoO.Zö©@+Ä|ýoÖö+£bÄ>@výCÖü,:*a, UBÃœbbb<öCÄÄîÄ|@~äîu.h|üOb© _rO* U£î£OZ~ýU|,|vrA.ååéoÖßZ,UBu ª.*ßZhüäÖÄOAzÃo¢äßb+B*ßöuOãUÄýðUZv*Ãœ+îÖ>©:~£CÃ:ð/b_Z¢ð©ª<Ãð/ßåßh|_©ªh|î~hüÃåß|ãð, h|ª.ÃãÜßUßÖröva*o,h", - "ä/+£uÜÜðîaA<ÖU@hßßãzzBßðA.Ã¥:,:ß*AbÃob", - null, - "A îÖo:@Ößa~zrýÖÜ¢/¢ßîz@:rÃ/Ä_+BÄrãZýObÖ|îC£+Ã¥Z_Bðr:a,Ãœ >*üö,îZb©äãð¢,~ª/ªÃªoªäb,@ßä:©| ßbîZÃ¥_ä>:<>ä©+ðÄ ~A.ÃoOÃ¥<î_Ã¥B/ªýZ>C_Ãœ:>@ZýðÄ*hä>£:>ÖåÜßCß~/uÃœuÃ¥Cöör~<ÃÃA uÄ~z©< Öªä:_+ᆚzîbßý*Ã~ý<Ö£bUr|rßb©@b obv Ob|~C ßoÜ¢u åýazréÃ*£å _ÃœrUu~îÃÃa__.î¢ Ub/ðü>OÃ@ÃœC.,|ü@oUA C~öOÃ>~ä/<Ã¥ZB*/Zuü*bª@Ã::< ObA+OÃ¥zr©CUr<îzUAåß bh©zråüêã>ÃUo.ß.BÃ.ßoÖOüÖåhZZÃ|¢oZA+ÄOzî|ªã¢@üz>>ßöO.@î+>*/aã:zoðBbÃ¥+/Äî,>u_ð_:ß|£ß:Ã,u£,£ä+ÖzbCZ©Ä/î/CbCAßîZ*:üå£/r£ä._ä/>Ã|*ob/CUUãAÃ¥h@Ã*Ä~ *AOîð@vu:. B_Ãöäb©î@|Ã¥UÄ£*hð//.bÃZ.©a+ö£©Ö ra<©ãüÃh+:öZö£Cä|,*ÃœaaZ*UZväÄÃväß_bÄo©zöÖzA~ßa:aî>+vzüzrÄh/_Ãß/¢rb<,ª.ÃîB,AÄüÄ_ußvör*ý|ãUãýAuÖvðä,+Ã_b,>ß:Oîö~Ua~rÃ/Ã¥UZu>£oÃœ.©© hBä|*ßÃÖä/,îߩ壪ý/aaãb¢Couåî¢BhÜãÖãé£Ü@ü|/ã+.ãÖ>o/u© zbu/r~ßäu*vCöîAðð ©>bvªßÖÃ,h,<£îC@:ß>U:rÄB B|v:öÄ~uC|ovA:/Ã:O©:ÖÄ+ý//U.Ãä:Üöð>ußArð Zåßü,,,ßzÃ¥~åð~_¢ Ã¥.<ýv*,ܪãa ª+:ÖÃ:ö,b_/öO>au~zbaä:hÃœ: A_/OÃœ_+>Ä>ChUC~UZ ý_ßÖ*OOZÖý:hUÃz*/.öäÜå|Ãß>B:bü.¢Oᚌ@vrvý<ß,b|v/ßîÃOz.|CýCüãbÃUß|ãü.î,ßor©äZBo¢uU£¢ovUzÖv©ð,Z+OvUýßrªã@.B@ü¢_vaß<Ö~.Bb_ @uou<.åã<ªAC*.¢,hrbÃvÃrý©Cð_ÜÜoO/>Ã,Ãzö©£hhöUî:bhOZÃ¥aßbßaO|*>*bÄ:ä©äÃUÄßv,BC_vÖÖ£ä.~Ã~Ozb.>+uª î<:¢ªÄöZÖ/ßÄ£ðCa£|ð<~¢bðå@+~o ©OO:Av+ü+ðåhýbvÃC.+U@hhövUCoßÃ.:ü/Ã+äb<ã£ÃOý~/¢A©r,öuðß_bUrßhro©>/O|*åßbö@ªãöh*ãªB>ªZ>£ÃüC/öCobãÖ£_A*OAÄäAÜ¢ÃåU©ðßb©,öÄÖzÃ¥A/ UÃ:U~ßbîrzü:aîh£Äª aß:h/.:ßðÜÄCr,b~C~vÃ/_Ãœ|AãCaCh>ãßä£vß**AðÄ£bZÃœ:*¢,Äß.bbß::Ö:O>ÖÄ<ßßý@|,Ã**üh||Uv+¢ß,b*©vo£Öðß.aý Co.@Ã¥*_ÜÃÃu/©öår>b|Ã+©Co_ßåß<îð*üß/.ßÃÃåÄ :zö/bvýb<ÃzÜßå<ä><+ÖªZ,Ãœz<ÄÄzrðÖ~ªå©äý+|Ö.ß|z~ã|*£O.*~ÃoîOh||vÖZß_OOýßa@BßýO>ã:Ä¢o+©a@.aÃ¥ÃÖ©äßý>£>ÖÜ,£|@£©z@~|£bÄ.ð.Ã¥BvvhOzvb £Zß>@ýbà ýªãA*uÃ¥. bÃü:Ã¥~ª_U>£r Z:zaaÄbA©ýAbößbbîbUÜýr++r.uîüÖ~bC£z+Ä ro*Z.Ãð+ðÜ_< /,/ Bböß~>Obî,üüZ>©+~ãb/ÄÜÃ/ß>/*>/h£ÃzrZÃœ>ª©oU>zü.¢ZÃ¥CÃ~za¢z,>aBbß|@¢*ÃAb£C£ý>@©*îÃoAaA~UÃ¥*oßÃüªz,< ~ÄÜðÜv ßZrOüäAr/ö>ååuîZÃý@U*©@*ªarb£Cß,ÜÃrZAÄ+zÖ>@£ß@ªB©/bðvaüoÖä|ö©ö_zCCCOA¢va¢r<ähä_B.C.ubîCÖÃb_oh/¢ßör_©,/bbOAzb_BaÃ¥z<_|_ Ura*/üBBÃ¥r|£îð åÃüA*Abªbr¢A£ä©åbv¢ü ©býBübhC<Ã¥rB|/ß|@OZOªüoßö/Uuz>,Zöýr£üö,üî>|z/öb.äz|B ßÃÄåOo@ãðßý>îh|uo~~ß vB,ã+ý+ß*ýðZüov>ÖÜÜåCýÃ|ß_ß@OÄÄýzý@¢zbßOoa~ßbröüzÃ.ßßîuZ,ßAãO㣣Zöýü¢¢OÃüªÄB,î>ª~ãr:zîO,ÃœUu .U ¢r +ÖªO,.éüª.Ã:+,,ª>_î~bar©*ÄoªÃ£©Oªo*ªÜüväAä|Ãœ+/hðA¢ãBzÖzÖ,@AÃ@bUöÃ|ãuAÜã_vb_r/.Zî~u~Cß~åäãªrvð~ ý¢¢bߪðîUßzßvrC ýuhBZÖ¢£ÃZuAÃœ@<Ã¥oª|<©UÃC_¢_>ªÜß/üoߢ,b ¢h*@ßuÃbb Bvð* Ääîã|>oh:C C>ÄãÖü /*ÃUäO¢Ä >üu/O> +ðÃu/ê<ßãÄðäaaÖCö>ý~:aÃuão©oUÃo¢o*îAÃÃ¥ÃÖv/A<Ãä >uB<©>Öîðýa~_öz|h_ZéBÄ+bÖUzaüv@ýßOðhuÃ.ðv/îöÄ>ªÄÖ,.ðboZuh©£ß_/+hB*CßoÃœ..ß|åý@A U©>b|:öäUÃ¥/vßB¢+ßbÜä++ß|ýüaCz/UBÄî<äã:+ÃöZu+öö._uC uUzBð*ü*C¢©î¢©©:b,ABªuÃÃ¥BÃrßuboZrCAã,ß|@äBUbv,ªÄv>+oraÃa>/ýrhOhb:,Ã@£ßbuß@u>ü©¢ÄC>@ÃÃabO+A*üß*+ÃüOªäÃBb:/h ©ÜuAãßZb¢©.,:z>hªo_ZÖA|Ãœo_Oü:ü+Ää_UÄUßAvbr~CãÜîBý¢üîåO,>üaÃ¥uãbüBîãßäo/ÃÄhvu_//ö,¢Býßa<ã©hÃÃUîß/ýu>uaöbö_Ã¥Uܪöãb_b>~Öbob/ýoÄ.Ä>ßzãUzöuö:~h:öÜÃuÄZ©rO.UÃîb.ÃœaÃœ @ß+/:ÜÃ_C Cbýª¢uzÖÜ/.Ã@ .bo*U_/ð©v¢¢| u/aÖ|Ã¥b*UbväCAu*îÜzbA,ª*Z~Ã¥r,Ãüa£ü@ .©O¢ZbßbB+/.Ãz+ãz@zãß©<ýaãü@CUrh@åðUª©Öý|_vîo:>b*CÃh©©>ÖrO¢/bzüãOZîß+hU£Ö vZ.|ã_*îoý_b<ã+Ob.Azîv|.Ãä|ÃÃuÃ¥b, .u ¢*BBOhýZCßb/Obߪ/.ýÃaßüý£röB>©h/oä.böuÄh~C¢b>v@hb:öð.< ß+B:ýråß/©¢v ©Ö£uÃäoÃ¥Oð>+ÃßAbÄua©@~ÃäÜÃöÃzr+äaO ÄUýa:*BüUvÄ._ã_<îu.+,vazU£ää¢åöÃßbC@Ã:OUÄCß@Ã_+¢A@£.üCö:UÃÖßð£ã@b~z/:ý@Ã¥Ub:©+vrö@vvhî~ãC~ Ãð*vÃœBãåBÃ¥Ãß,ßä:~z|@|üuÃý£,Ī>ÃräCZª@ýv+ã>O uÄåUð~AO©bßî@vÜî", - "2001-01-01 00:00:01.000", - "2079-06-06 23:59:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("0.0559234", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array(-1, null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 1.79E+308, - null, - "9223372036854775807", - -504945714, - 32767, - 182, - 0, - array(("08757ECC341053DABF2569D352C8B231845DD236EF0EF8D319BDB26902F0047C0441746578A98F5C022B6B06EE2005B1319ED0CA132E5CFA7CABB3765DEAD7CE20ECCE11534CB8DACD7633C3A3F2E4F8B7013764203A68D5E57AE8CDF618FF18539CE50251F45EE4E5F5FC9DFB98568AC6BABF5CB273AEE683F82CD863EBE4B5771A70060CE2A6C3A2527F2A3DCBDA4C245E073B59A366E0EEFF7F59C88581F9104726E028507DE314FBB95FF38"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("D70B88DA36D29A75543D70B1B19C21FDD2131F4DA64F47BA35813FFEDE19131275C2EA0039DF1FBFBDD4F1E65CB4495502977C92AAED3287C4CDA4B5929C973B295D3CEFB221C4F2F46090D15E615E8137E9FB469C8168878F623C6C3B692C213DB6BCFE775AA2E1D28A9D4A5D6028626E227DA9A0509EA8"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "Z@u:Zßb©CãÜ,bZðOîU|hýAýýouöðAZÃC¢ðrÄ:+/hhb@ß*Öa B~ü¢£Oäv_uZbÃ¥C¢ßo©ÜCBuÄ_ß/ýð>r£,uªã: b:/oý>ß|ü. *ü~Ã¥uvvorã£O@üÃUu@<©äBã+ý,br.üܪ<@ðzª~åðbåöOãvîrUv|A>ÃãzZ/aÃœB.U>ÃoîßAZ:Ã,Bîa|ß@Aðýå©r,Z<üîß|©ý¢äãÖ,aZÃöo/©Bb~ãÖ>ðüãC@~Zzä/äC:Ã¥a.:boß.>ÃuAÃu@ZUîho £Ãh@ v:boäããÃ_£Ã~@©öb.uªbß*ÖAª_ÃöCUzvöÄî *Ã¥,rA©å~©+:uUu:@/_îÄb_ BÃ¥|z .¢ßÖªÖb*uåÜ©å/öovÃÃ¥aäÃ@Oý¢Zß_Zz:+_ob_Ī>oCz_ÖUᛦoãoÃZ<ÃB¢ÜýåCOäÄüª£rÃO/brßÃäÃððUüv>Uܪ ðuCªv@ýüß>£aîz¢©:ý¢/z©ßÄ*Ovðäêð*vÃý~~O_uäå*bðAð*~AvooaU.,vBUbBCåã¢~£båßbu:Ãœ/¢.h*~v£*ÖrU<|O bZoÖÜ:*v, Ã@o", - " ", - null, - "o@.*ªv~ßO~Ö+ãßß:zÃ|©C~bU.b¢vß@Ub£öüýå+ü¢A~ä:b*Cuåö@/b~ã|öUý/ ßä<ßbߣÃC~Bã¢,üÜî*hö:ßh.zh£ãößBÄbî@.ÖbÃœ*¢Ü@ß@£.UÃ¥@hÃ¥~ãoz/>åð>Aö Uv @|O+ãrª+bAÖbO:Ä*,Ã¥uBC.,/£îðaªãr|vzîÃÃhzÃý:bZÃ.Ã<Ü©_/AouBoÖÄövªÜUãOßå|A©v|_C£ CÖur:UðZÖ_BBß@ö.*/+Ãã:>ªßðÃýZÃ¥Ãß+äöb£ÃÜÖ>ÃãrCÃü>£v¢+_>ðaÃ¥bA_A<öð¢¢aãå+ãäAouv/©+ ~Z,Ää+UäðörªÄUÃAÜð~<ÄhªZ.Ö*,v©|u©ÄöÖBUbU+hîöoUoß.r>@båª/©CzBaäªv+ýðýZýö_ã B|U£Coª_ýbobhÃœC@býðüU@_£|o o>.vü©ÃÃœArAýh>:Ã¥_B ãhåÜðüªåB~a~Böã/b|ßr+.ýbzAýß<Ü£a_u OZÄ>z*ߢraªöAZb.zÃîü/hß<åüß, öab䢩h b<ã|ßßbÃBü>/* Z£UÃ¥A_bCA|Ã¥@vbA|äv©Ö<ß*U/ £Zo:£üUaßCÃœuhß©£Ö/.:Ã_ÃãÖ<üßbÃoîßü|îîvvvîߢCäBßbîßý¢B~ ÄOa*,o Ã>üðäÃO@>O.vßbC+CßÜ|Ã¥uÃ_ßr¢äÃîðäB~bhhðuhÃ¥+zßrßÜZ~ßî_¢ª/@Ö/C©b ߪU_OrýýuO~£Ã>îð ý/*vîý,U>o@,Äß|B_|Ä|©£~ZªªÃüýððªßChUÃbåªäzÃb:~>*a_hðr+©*ãb£zv/v|uî|~à ,<©BÄ*ü@ßaB+ö/ßý.::Ãœ,¢.~ .ACã_Að©Ãh©vC£z, U£uî<ãÜu>ChîöÜbBÖZ ãÃvýOÃvZCÃzßo,b+Ãœ+/ör*ªbðÜÖÃ+¢o*+ý_£ý£Aru_£bub,ð@ð>~ö.oã.+h uÄ£ÃCäªßUäö*.AAäA~Ãœ~£vâoîUîß©>ä£/~Ãoª/ZÃÃܪCZbðhýýî~rr/¢*ý+@:Zr<ÄÄ<¢vÜßööî© /<Ã~bª @ªZ:uoCߪbÖv:|C,~~zäuOö¢z@BOÃ¥vÃœ:äüÃå/vU:oýrÃ¥O¢uÃœzBoýÖÃC ££oÃböî*Zz.<Ã¥Uöz äÄuýb:Ãz äßBßÃCã+ußC_.ubãªärÃîboã©îAÃ¥ ~O<,ÃbCU>¢z£>ahrÃœ/AâªU _uä>A£*+ä<ªÖ.©,ð©>bvÃr@¢ßv,zZ_ã,C>Ã:ªå<îv.<Ãö~îðBBbC@ý£ ÄuÃÜvbbz¢r¢ðýözßAOýZÃß©u*B¢rðªz_Üö~ÃðZ+@b.u¢@o<ÖßãÃZã,/ r,:Ã¥züä£ßßr.au¢ObvÃœ<ªvã ¢ä+bÃ¥aÃ,@U<_ßÜßb~hoýãhO£@öaÃ@.ÃÄ ßð.aýAzUrB©+vOou+_U.höÄoC~ß~ßA|ü>:Ãä,A~Bããb@ZvÖãzß öo|,Z££ã bÖ*åÃ_ß,/v@£ßð Äß~|ªbBhýbvßu©|éÃOÖv:bÃœCß/åßuÄbÃœo>_öîUA|ðrB,~Ö/öü.bî/ÄÖCöî>éOvobýðC|Zßh¢O>Ãzý ZUª| ý+*ßÃ/ýaÖzÃ¥Ãöðzªßz+âðZUrbßü|uv*Ã¥<:©Ö£bÖÖýý<Ã¥@Bhr*UĪÖvý*Ü¢ý:OuÃC<£>>Ã|ÖUBa~ãv*ª@, +v|Öhu< üAbZ~Öv*,媢:Ãœ><>éob/Aß*:Zzäüz.CCîßãßbßüÄüüa+u¢ä¢~+åäýÜÃÃzuãßvßÖ.C:ååÜßOrý:Z_ßýU©/U,ð_UhCCB<ãýh:orªUÖÃýäßr©å_>oð ýO,a/ßäå ÖÖ©z uãýC*rÖ<ð/,~Ãä+ªªüooäßÖ/Ã+Ã+/ýUO>ä:A_,ßÃ~@UöªãBa>ÄhÃœ>o<,b|,ü<,AðrrBä.*buo/|*|ßßåvÃäÄã:à ,böüaãvrÃÃ@zBÃ@Ö<+,BuzO+äUb.hAhÃbðã¢ÃvrÃU>o@©ouýåýÖuÃœ|ÃzÃ¥uv~Oz*£ªãUOîåAuoÄãÄo©ÃÄAB_oZðð>u_Ö@ý¢>ðo>îãîbBýö_bbÄýhu@__BÃœr+ÃðÖö+h@u_rð|©Öäßýý_rÃääÜU,@ý:b.oöhuîoBÃã/ @äß.<<©Ã|BUý¢ßbüÄü¢@Ãœ:ÜÖª_@ö£åvÃÖ.*ýüßðbzÄ> ðü>.ãO,Öuî_bbbßööÄbÃß_Uu:býåb|ubü:ã£ACC<<@:ãäzîðýüC~£ß@+ÃZöÖO>Ä©Ã>B¢roî㪪rzb,*oobÃ¥b:¢.hö:ðÖo*Zýßßo+uZÃãbZüA¢rz öß~Cª¢ubböÃÃUo©vzu>Zuüöå baO£+>Ão~ß+Öo+O~Bzvªa~ðªÄÄ.ÄABüãÄß_¢b.Ã/ö|£ z>oãzÃh©©Z Uzß|@buÄðß ¢£ð/ãåZÄCu<,îîß+Ãa ýð£azAß*rüã@££,+BOBîåUübZÖðî©Ã©<Ã~ã.ý£ª,a_bÖZÄßÖ@@Är£C/Öü~Ã|CðÃ_ÃãÃOräaÖää+b<Ã~Ö/ |zo©.¢ãuýCÃC|/ªbaü:/ÖÃ@Ãö£åäUä.ßbbUO,b/b*ýäåAÃzð//©¢Za|äß:ÄÖÖüüCÄöAb@++ð*©z,AÃOBÃãÃaÜÄBîðßboß>.ß_oðð/üüßbÃ~ãbÃÄb/|@>u,A<êhà uCäU@ãåßv©ÄåîübCvßUBüÄvýð~hC/ußãv>ߪÃ~|ããvOåߢz/ü*/ßå~*>ää ðÃÖ+bãåª,o:,©v,UÄ_+rZvÄobÃœAü©h Öäv¢oBÃ¥hbðAîv@üÜ@<*£ü<©A£ý~zvßîðUßÃ_.£Uhß. Zv+/£Ca@< >+vOuüo@~Ã¥UaB£:ß/+Ã¥Urß|o,ÖZ_öOUZ/~Ö:/*O©b¢U_BvOÄýãã¢Uh©ßBÃœ:â|,¢ö|>îý+bOuo|~AbC~~|C>~©O:B£ |Äü:bãîhéßý:ª<.b~BÃœA©äBb:îAv,bß b@:UÃ>ÃCð/ðÜ|£îCvBÃ¥aäzîäå,ªöäåz¢ý©.bao,b*©BUö >ßaîaZoß<âª+zîßUbCzßîUßCBÄߣrr©î|åÜðöäA:Or©:.ßBO_.AC£@ª~rßî|*ov@öÃß.CäjklãªÄ|*Ãz¢¢Cßo_vAZrÃoöÄý©b©CäbßîîAãBªðÄA<+./ßCðhhU~Ã~Ãœ.ãýzö~/©z<:C+Oîb*_+ý/ßBZr_C_üª:b CªýCZýh~ßbðü©ÖÖUBîb+ª ¢ÖÄC,:îÄÃA.Ãœv|îöb|U,£ÃOz@ß+zåÄ.ÃÄßýU.O~ÃÃB*Ã¥|Ã¥~>oZýöªvAaîßz_ZBÃ¥.ýaö¢++¢ÄÃ:oU|ÄZ£rÃCÄ@¢îå:ZÖoü+ aUzo_/ߢh©vvhBß", - "2919-10-10 09:15:51.361", - "2079-06-06 23:59:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("0.5650345", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("0.8713", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - -1.79E+308, - 3.4E+38, - -1662199111, - 844364759, - 20280, - null, - null, - array(("C29E82197DEC94CE552155C089AA21684D34BC1CF1BBDDCA0DEFE4FF4511ABBFD7ECC6A53924BB859B4F53B92DCC8C50B5BB91CD719E0307C1B80B8E089488437696E89CE37B2F28793780A267249C4062934A897FDE4D39E9BFE481182C7C07C6104396E4EF6C398C69543A1D60A09D1BCB2B754AFFC39749AFB22496AE0F4CBA78DC57E0065C732610FD168A131B891E47654D0107BDC74CAB4D343D48A2DD59077FA48448BC156FB84E3901AC0C369329054F48C24AB3BB150A34B2"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("36B339B858052C119C386074D06C3359AC02D92C54BAC66886F0014A8550CFBDA489336F2C291033C13F213739886E213C903B9FCADC247BECD0316804E18B4C69A3D04639C42C83F1A66B15988F2EFD81DF0EF5142FF2C764B7825BF7A72FE0B3D4CE96B827E87863B8AE23710FE25DA234E624D396383AB008E28141D08B49778A62C3C198445AF795915B81CF5655D425B9C3C10A0D1C9C93E5B0446736922C64ACE6AC1E9B09EF8C2D3D04DCB9E1"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "ÃÖÄZBvbOr@ü/î*ãzZ/aÃœB.U>ÃoîßAZ:Ã,Bîa|ß@Aðýå©r,Z<üîß|©ý¢äãÖ,aZÃöo/©Bb~ãÖ>ðüãC@~Zzä/äC:Ã¥a.:boß.>ÃuAÃu@ZUîho £Ãh@ v:boäããÃ_£Ã~@©öb.uªbß*ÖAª_ÃöCUzvöÄî *Ã¥,rA©å~©+:uUu:@/_îÄb_ BÃ¥|z .¢ßÖªÖb*uåÜ©å/öovÃÃ¥aäÃ@Oý¢Zß_Zz:+_ob_Ī>oCz_ÖUᛦoãoÃZ<ÃB¢ÜýåCOäÄüª£rÃO/brßÃäÃððUüv>Uܪ ðuCªv@ýüß>£aîz¢©:ý¢/z©ßÄ*Ovðäêð*vÃý~~O_uäå*bðAð*~AvooaU.,vBUbBCåã¢~£båßbu:Ãœ/¢.h*~v£*ÖrU<|O bZoÖÜ:*", - null, - "o@.*ªv~ßO~Ö+ãßß:zÃ|©C~bU.b¢vß@Ub£öüýå+ü¢A~ä:b*Cuåö@/b~ã|Ãßã/©ü|ªZCýÜî||©o~rßî*/ýßãßUßZ¢ßoOÃ/A|Ãœ_@O<ü¢BÄbãåU£ßªª *ä¢ßo ßa>OªÄåh¢~ß©zÄå<*ÃhÃ,öAüý_~+BOðü+r,:::öÃ/ðãü_", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("0.5650345", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("0.8713", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 15 -$values[] = array(array(null, null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("6459515CEF4E0D6C859FB097528CC63596AB884B918D383E578B8B0C08089E77004EFD497BEC6B3CF1DF4C085DBC4CE3095D796636C29BD721130563C200637D003997038E7CBAAA404C59B1402FE09A3EA803AB8E37403DB2262868E70779F07BCF07242549548DA3CF26E968EA97F1326A2519FDB8EE4395DFE74D34A29A88"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("74CAA0F9F2809B93DDAE0780559B56596F6507A60F978E6DD62B0FFDEEA0212F8A135DD39A5126874D95DF2A7D4D92DE1C492A23341F3CDD3C3323C6709DA53C8DD7F5423E83BE1E3E805879B863EF889931E0A576FEFB6BD68097672E6016AFEFA29DA5584FA0998A0CE5B3C7C224D8439A2C3FBDFFC68FDC8A6BBEEC57EFDD69A88A15C649304A90471B6A1847A3145BBE249434D6F2EFDC0EC9CDE3E2AC321C07491E380E12F969E1AE75E45C7694E656622D335BA993B69A38B7806366AE9AE84D11FD85DC4CAC2A2A52277C0B809834E68C7F7FFFC05EB6689DD65A78067B00DBFC0E6597617A15810830566C398EB81F4B7CD92F1BE88DB2471C3E7651B124D727A10E72329D851269FEF68F074A2F0E1D3A2D8AA1D0C01381B250443B9E2F560C7F44978045EDAEF6C7C51DF2FDAAFCED2F5917849A3556E1FBF17F4D40D7CBCEA0838705978ED74D7F93DA0BA221830853570B4517D2724AEEB684546D61B6F0FA4E517922F755A181C905065A4A3EDA6D77F9E9CB85B6CD113D17E10A83E01EDDEA1154CF0AE55C016AC44DCC6BEFA377AAE7F567CC2474978368E5DF7497FEE9E7297039EA0A62929DE760C06801A64D746A5F9BEAB610C59CBC4BA1FF7C8DD3C664E5417F698D9F8462C9E4C3B42E82C2976B093762FE3018AA9C2DBFC193215A8DF9041B64CE0ADDBF1688ED2B02E88DC8DF6710839B6365B528D4CD5FE0C3CEA88A36BA2A675DC9612C784C7642EDA8B49FF7E12C10D7F1969F653BF93D7DDE52B945A1CC2D6360819DAF562317EE723B2C18BFE9E2A297B244248A0222CF23AE99F9BCFD4BDC863C965F8E6180CE7EB0F2FFCD24BEF57016ADEE8709D85D5FCE8BA7891D3175EF509283528214363175A5D9102202FFF0A389A6505D547C633800C0D5D944617F7D107C923AA3EE0177BC1264FE4D3CCB8CB6E6948B2592FC1520198154605831435A9212856CB6009BA4D8A0421764EAC4F449225ECDF3587D807D19896CAD1664E020A4BB294520FFE5F8E64CF1BAFDBC2FBC601735D376673A9405EE9C772EF7C13C61F6C02F9BAF282278D40C0B8CC67F5D59AF4DFF2E0DAA3C80943F31636ECAEF8AB45806FCFF21B0EE125AA25A2F083F6E3F1D7FAAC20E534C83AE066FF7BF0A091D80D86D79605A8D9C111BB307E2518C00C223504A44C8A505B979501B41F65350983CAE2FD1F5DFC3F10EB1F3A045A05D7A6E349CB9BFD8B83268BFE709725A1AC8626EA6A35C2FCC448D593617B26474D9F3A0D3C2B696D963F5FC6836F7BA7EB3962A3DD6FABA1D3E68582C65A2A07F044A0E15D1875811CC9542AD5FC6964CD3DA828088269EDE398394595E6A73C1557C64ECE9A4AD2C3E187140B70CBD78D10C598B1F20F7DDAB2FDC4DF2FEB361218058E361AA622F2D56416FB55D262CC61C885211E8A135858740BAC75F3940640F1F37B4DA1627A9295EC9DE88274E08AF87D676615039DE235DDA69A867CD1C010AA63EEF78EC0560A8E789D857FD60F2B117D862CA02BEE558917C3BBE00C290ACDC7D810F18379AD115B969B006CC607A77F650F5BA3607E75897E76FC9A1A725F82C7A80DEE298E03D91B449553552C41BC1470A719BF2A57599B681502B07741144B7D951E09B945E6FB82882BE9223452253075A9DB35D3AD40B442C178F128A4038117D78733B26B3AB01E2C923B607933B2DD7FF1973905A9F7DD5566747E0A69E418680A5CDAF18E1A7F1B17A70B1543D99DB623B8B27C48023D8F566D04B64213C2BE13087ED6E7EBE1D7182DFDF2FCC8119CE6B7D6B121AC10081F4B9B1115C8D8F3E44CF23B6AB6DE37C6DB542A144A929E5B77CF9A4D118C66F866B79C50C0C15F54FA0EAFC5D6222FE9AEFA5657F5E3D4B8FCC5ADF94EB6D2C08B42F3FF31D6F619F4D51A4BE2D875459FA09E17361EF272E2D3640B235606433BEF3A933E20A7C7F44C5320D988D6144068599E18BB8A6C40711C6E72EAE69ECEC700039DE2076EF8C622F9CBC1A95A0D4FBACAFC782A7054CE42CD1449551955B4620288D2866E50FA95529890A08EC7B26133202BA246AC9FAECF9F4A5ABF711994076F96D12AC95C6A01D2D7E7C42DC3F561E811E93A121B65E923B6CF16AFE5D18EFC992F30D13669D3B19063C4B7EAA1DB44E8C75F2069F54420F807128D5361FD0658C784A4B480B1A8057871B791314904F3C1B71E725E92A3DB0FE93F57465E2E76CE5F132D36906815A71AE1738C8FB6BE1D9991BADE840A24D0C9B4978250CF1A5330D2D235D7BD314F08CB33B1305E70327149ACCC73984A54FE5C7CDBD083565BC7204BBCD48835EC506BC5B8C663A07B44650107125DDF98988D377B40556E7574E7FA0002B35DF903C07C07D2818E8AAC01977B824F397940B3245740BA9BFAFD4043DE7990788107080351D1C7C606FEBCDF4ECDF2F325BE80BF5F9F06B84924710A824DB0010B9916647A4BD79788ED2D5883EB5D3D0A5D2E2FF8E118F2005FBA2D60BD33C94EBBC3B5B78E78E9D6E9366271AE245DD73400352F90182A7024642A81AA36CBA95409D827A7857BD444490FAAB670522B6C562CBD0EA9BE939945932A5E583CB27AB91DA45C363FBE9450D"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "ã©ÃåÖüÃÃbUuý©Ö OaovÄ+îBZuÄ©ðãürrC£¢@Ä+¢,,ª+ß.bzbuðÄ.ð~:£bzÜåBbAäahß:£_ä Ö_ZåÖ@oä_îüv.uÃövÄ|BªÃvÖ,¢o+Uã.Ãb+©,z|î_hð rBÖ/ß/äOðüÜÃAv@>h+ߢÃ>vub|z£ÃßAöÃ,b£¢O+ð,©>Ãv£¢hÃœbÖo Z¢Äã_,uCrßå,ß", - "Üð*+oZÃ|>bö:,äî<>zäß©ðabbAª¢aÃœruöo_~hBu*©@Ä:ã>öÃ>@+bü/ðäîZzCÃC_ÄãÃ>u z .B@Cor|u|Bäh<.hv@u©ÃãÜB©|üa¢BýÄ.OOöü:zÜ©*bÃAßýbzÃ<üa£¢<¢aã_OrðC~+:öZ:oßu:a*ßAbã~åÄð¢aÄÖ|à üªÖ~<.öo£+AÜã_©Ö|£ZÃ~ðýüz>a>|©Ã+~hOr:ä_öãå/<_Bä,üvoCZýÃbªßZý.:ªü©©rÄ*:ZÃC*äÄ>>/r¢ª@z@~B~.hª©~_@", - "ªbAv>o£|Ãœ*Ã¥hOC_+OUÃãÄä CÃœ<_/B*~Ãœ:Ãö©:.Ã:Ã/>_~©@~©ãCܪÖA.v+ýb.ßî*u*£Öb<ä+b£/rOöðýÖ¢:äãå,~ZZýACä_ü*åªßbð>ðr|~ObÜîã~ý Ãœ b> öb<Ö<ãUuB<ãz Bðãh@£Zzu@@,h:C_Ü¢z,väa:Ã:+z+bªöª>£ß,rAÃ_ü+ +C© ã_ðbuð+,bvýöhî~Ü£übbhßîOUh<>*CýOÃÖÖ,Ã/UhCUÃCub~¢,~Ã.b¢rA¢ÃßB:Ã¥AüZßU,hOBrÄîðåBÜüO_ãCÃœ/~Cb|ÃÖö¢oÃUrÜã.ãr>r>>ÃoÃzöäÜ£_¢Üv_ÃÃrß..o||rézð| îvðåö*vßîoý¢b:öUhÄ£+Bß*öhöa¢¢ðuzrZäýªßÖã|ª@ý|ðhuÃbßäÃöUCðýÖa*¢OüöÃ.ß~ö¢ ößßA>|îOßÄ<Ãœ*|ßî/oÃ|UÃBUBäÃ/¢_oB+b@î+ýߢ.ÃvAãà ð r£@Ãœhä@ãUð¢ä<|U|+.zAî@:+bo|:b.*@ÃœC/å¢zuOabhboÜöha<: Aî/©rhÄýzbÜã~BB+C*ðbOCAã,,uZu/:~öA>ä©ÖÃ<ªUåöZZ¢O:b|båÜ ¢/a<*Ä¢ÖªÜr>b¢ýýCÄ¢br_Ãv ãA@ßî*aOB~ZBCbßöäîuOý||©CüÄ.O ýAÃîa¢ãh*¢@B v:AÃ:ýÜ>ü>ª*ö|CãåOßý©~_Ãœo/Ö._Ãœ:O©ßUrbUv/.ãa_B|bbÄz:/ÃB©Ãðv|aU+öOA.Ãh îÖ£ð*~ß|aê,ÃÃU A:|o_Zß©UrAîã@©äðÃß_ß>+@ãCð|ö¢_ÃÄäýüCýÜ¢öýÃh_ðÃ>aÃ<öäÃ_@bbz.+UÄ¢£*rü@ÄÖðörªßßuýî,._uo~ßa <Ã¥b©*UÃvb:vä<.vbvÜ©av£îÖO O¢@@Öð*o.Ã,äöÃAðß_>ÃörZb©Ä¢vobZ/ÖÜBªåüÄ*> a_£.ãðý*ã+U/,ü :äbZBý :üBO£.AãßouªrÖ,väÄvßUîOz+CZ.C©b. ÃbÃz©Z~za_BrÜÜ~bªO~ßoB_ÜÜß_CªÄobßZ@©Ãör.Ã>u+bCª*Ä*@<<~oª_î@äÃ:Äz+a+>vî+£ýoC©äOéOr.UOêUCðCÃœ. ÃUßðýbÃBbßýå> :>Ã+uäÃ_A:CÃ.h,*ß~U£<ååöO£öbC¢vzOvߪOÖ||£hu ßz£ªÄÃa,¢ää~¢ßr~ðª/,Oß>h*ÃœoAaÃa £_håý**+¢å© .rý,|ý¢bÄãhýß>Uö|ü,öä@ðB£ ~~UbÖzhOÃhßÜB.ß©>Ä<îZîÃu.bߣbßä¢ÖCoß<+îîåCÃ>Ub<ßUBß.ÃããîAU~ýhýÃ~Z:ÖüvaOCß~aOrUý*|ý,üa:ððvvÃœuÜðÖî+Cußz@|UîÃ~Cß©ÃÜvu.oªÖ|ã@ßÄýuö åßCßBArAb.hbzäßB~~ýö_@ªzª_,v>ß~aü*ÄZÄä~Öbr*Ã:ÃvüÄÃC.Uaß+ußr|¢ð_UãhBöo@+ÃýuÃ¥*oääîv.|ÖÃoÜü| bî©aho£Bî ~ ßO*+.aC/r*öZZu+ß_©*h+ß:Ö|äa~¢_aZã/,ü @.,ZÃœ< Z+hÃza*£rÃ:䢣äC A+ãu£ÖääÄ,ýväîo+O:O©+AÖãä_£å ,Ä üߢÃÃbã|:<ÃAßýÖvÄAÃ¥@ýðA>ÃýuBü,>rîA,_r_Ö+ Bî_bÄßåÜ+oz>O bÖCbÃäÖ~bü>CܪubOZ>Oîa<:üo*zªurÃÄÄ ßbã~_~bZ|zýaýa/Av©b~>/ýãÖß.bv.ýÃvý~,Oåð,CbÃzUrÃÃZZ@.,/ah@ýãÃOýaozzÖa.Aä+ÖÃ:/Äê|CÄÜüî|Zãü/äObOrhb~ýö/ÃZU/h©Äü~/ã,Zª:rAUÃaîAýOà |>ã/A£ühî~ýÃ+*ß<Ä|Ãœh:ßöß*roÃîA|,Äv@ã,>>a ðäUC>ZÃ¥@©A¢.îrÄ|Abß|~<£ABa©>Üâ*OBo|£© öîrðÜÖo¢ÄÃAB*Ã+,*z+ªCbÜÖ @z ©~bßÖahr> C+ßãÄOUÃh©ßÄßUZbuÃ¥C:~U/ãöC¢r *|oÃ¥AOýßv*ª>¢,£@üßß_ÃßvBbÃäööî~|b*ßB£ãÜbzUö@@öüäÜ ý ᜛_zbßZzbä+ÃœA/vª_ähaÃU:ãÃ~BÃœao¢ðvÖ.ÃUß~a<ÖU+~Ö<@ÃUu:ãzb.OÃ|>Ã@zÃ¥u:>vA_*ÖZh_üýbvýB~BÖðObüÃý__UBvü:+ohUrba*a:_Ãã@Ãa~Oß+bv©ßÜüaÖUßäOBbUðAà ªüª,C:Crý¢+öÖU+Ãîab>,aÄZîü ¢ö*vߢéÃO+*_+ÃœahåãCðÃ+ îýuöªvz>oh>U£*| A~©>_£>Cªý|,bä.î~a*ß,Ãœb/äär.Ã¥_*Ãœ @B*bäÜv>C ªÃð:zÄ~£¢aÖðU@U¢Ã/bUb¢ßb_ÃœbäÄuBzZ//Ãö,îÄZåÄÜ~~ããßuBß/ävýîãð_©Ãö.î/£ßB@©Z_+öOîa>öðubC+öÖ@ܪä>AuªÃ.C~ýaBÃ>ß<ÃaðÄüî@ö¢ÄÃ@UÃßZ,Ã_Ö|ðÖ >+Oö îBaZîrÖ~b¢üüB~åüåääOÃÃaübîßUªu~Ä~¢Ã¢ÃU*@bà B|Ãœ< Ã¥*ª> ÖªrbArý,>ýäAv,:äUU£@ hA+ð,z/<+UªB+ãA@|ÃA+C_Ä< ýo+£ÜÃBãåß| U_oUÖB@oÃzb*©uÃ¥<¢>£bä@ßåäz@äåZa|bu¢ýOÃÃ¥rýz£©Baü_ߪu,O><ÃÃv+/|£v,z>Z/ªåZåª/B|ähßã ýbZbª+|Öåvî*/¢ AU+~Ä@ðîz ÃœÃ,îä~<_B~ªý ÃCÃÃÄ¢,_OßÜ_ªÜrCßbhüÃã£oãA©/ß bÄAð©<ýãuvðܪð¢+vßv.Z_:a~ä<~ãÃüöÃãÖb>ÃCÃhüßäbüuÄ@:Öäo,b@vA ßý.a_zübÃœO~Ã¥|Ã_ZzÄ~ü¢¢Üb+ðBß*ßUÃœ:+zÖÃüZ|bî:_ð~rO¢CÃß©bÃou*£ªrOÄüðß.r+Ãœ|*.AîÃOuCÃœÃðoub/C,©o_+zÃ*Ã¥r<ªhoaßã_+,¢oüýýUäåZa<î<ÃA@uUrAÄÃa@êý¢ÄC ÃÃb>uÄO|C:ßbäÃ|h/h<~ª* ©ðo:Z+UuoOÃßu/rßßbÃb~ð£hÃœC<_>U@Ä *+|bUÃ¥räu£åoö*ÃU*£_îZÖÖZa©OåÄÃð£ý:äb¢Ã|o>r*ZÖýãÜÜzh~ýßbªªvb~Äzo<ÃuO<<¢ªªB+.b,<@Aß,B.~ ©O.U:+©ü:Obä|_ß*ZoÃ_>ª,ürhÃAÖzrÖå,_abUoUöCðua_ðýu:CöÃbßbÃœ/Az¢¢uhýzüoýßZbý/~,©oö©uÃßzrvAß~îîuC:Ã+ChzÃ*hbªoßÖÃ<ýß_:.O<ä@ußA+BÃœ>b+ÜÃZv@ªîÖ bUäb|r¢~ÄAåÄÜr|:aßÃCýBr©ü>uß/Örå©C Bª>ÜÃZªOÃ¥+ð:¢@h//ߢ ýbrBbüßhüßab©å:ã@Aaß,îzZAb~ZZzÃœbAß A.,ÄÄ::rb+åßÖbzî.ßßðbÃîba,_ä©bðaÃ¥_//CzZ+ðÃUðhUãuý,*vuävz> Ä ðª_a/åðr¢äßB>:a+>ýîÖ_zävãÜAZhðܪü/zou/öܪÃZ ÃœBîð>ða>_aÄuv>£/ðÄã/Ã¥aO, /*©Oß|äîÄo/ÃBOªÖUÃœh©@ýÜ@äîa¢îåBÄ_o>£ã*Oh*+ß>ýuª|b©£UbÄß/~h ð|uaßbÃhðÃöðå~a hrv@¢z:~uUã©ã+bbÃAv|C+ü/vB©äBÃÖ£b,£Uz¢ÃªC£Ü~ubz|¢åUä /rÃœuãU_£~<>Ã¥ ¢rÃ¥A<+oö@OvBa/ääßhO_©üÃOãªAÖ|*va~äýUßßv©oÄðZ ZrÜîu.bz*££:ÄOÃ¥*.,Oðv@o/¢AAÃ¥>,üb:ã_,vã öOÄUr*ßCª+ Ö Ö*bÃœb ZO,OßöÃ~ýå£ÖßUöîUOZ>¢@>BUbÃäÄã<Ã¥Z© ¢AhÃÃ¥Zã©zööhB_ZBãuöÃZ*ýbÃ|ü>¢Ü|ªýäÜ©*ߢ+U©ÃovÃ:rrã©*ýAvbbý A@+ ouܪÖA~|/¢©ßÜýCÖzÃ¥hîvýß@¢vßzAð¢Oßö~u/ªýªbÃœ|.ªuzýr~Ao+:bo.Uü>uZ_>a>aã.îzÃî@ß>/.î,üßB:Ã¥:ß*Ã¥/ßO¢<+ßub.Bu:~ZZ vä¢|Äý*~_.OB¢_éöýBCb.hAv+aßO>ußo|röÄ Äßãböüªh/Ã¥,|>~rOvãĪz*ý@.Ar+ðb*Öö¢ZZvaãZ UB_.,Z>uåãoÄ¢üZ@o:Ü£_£ Ãa", - "Uzãr~ö+Ö£ZrãߢäÄ©_,O>uÖAb¢ßªÜã,|ðäü~aöCê_äävöãîܪA:/OhA_ßhühÖüßå¢ðö£ZÃÄ.Ä~ü*îÜCÜåßoßöhÃÃöArßo_ã~bý¢AîU>îzbêO©£aAOvößaö>OuÄU>îvoÃ+zðhbå£ v<Ã¥,h|v,OÜüZîÜAAhãÃ_v..Ãœ|äß*Ãåözª¢__bBb£/äzhZåãªZÃ¥auOCbãbBãvÖð£zß©©Zu>übßAB.CZ>©ArbäåÃh>|Ä:övßî@rýz¢u|+ßîö/ha£ÄߪoU|ãß", - "ý@bÖãüA:,ãª+Ãœ~B|A._,Az,r<ýr_©abü*hÖbÜÖü|£*h_b ©ußu_*uýª©häZäÜ>ß,Ü¢a¢ßAßZ:üÃÃOãhhzüÃ:<ÃÖ*_BðîZÃh_ßÖ*ðAå©bÃCåß*O~h/bÃßv|ýAuã@B¢~¢O _ý.rßÃ|£ü©£_:_b©Übrb ZuÃœ Ãr+|:h<Ä:ßOOðª*hoZî >ÃœZ:/ã@/CavÖ£åC@Ob@@oßýÃÃ¥<ã:äîoý /ßðððhªªb~:@/_b>ßöBÃœ+o©ß_~üab|", - "ðö:~ß©ßvö*>rÄäauh,.üCßr>,BrüÜ*|b/~ ,©/./ÃaäÃýÃruuãîü£ÖhÖörÃ:ã<~ß.Ãœh@¢_ÃüüvCö>@£îýÖBãBßßr£ÄUÃýã@vî~übÃ~hüöa+£rU/_ß:B+îozüz ýß/Uý.~êöÃãzoöÃÄB.oa.ZÄð@Öh*ÃvýÖ+ªz@ÃAöb@¢>ãüoð/O:u_h |_Ãz:©/ ä©Ãbð_£uabÜßäü*ßÄOZA/ªbbÜÜ,|v¢OÖßoÃ¥U,Üü/obAãÜÃÃ¥*CãÄh~£U<Ã¥>ä/¢>r:zAýÄbC/öuãå+|uvAîA:/ðAOOî.*îî@ß©ä~@ÖÖova.ÜðÃÜåî©uã_~bÃœ_väßߣÖ_AÃAu>ýÃ@vÃÖ/aoC/.a@ébüuªBAvîö*¢UåÖuÄ_+îüÄ|ßãªÖ,h..ßhbäöÄߪ.ö.ª +hrh,Ã¥Ã䣪Äö>o,a £C<_|îööü_BU/Ã,¢ýUÃ|h@*î:|,~bZzhÜߣaCª,ßîãOãUÄĪéäÜý/öãB*ö@+ä©Uü<£A,@,ää£:Ã¥obrãÃü,¢/*ß*Ãoav/z@ßüðåÜb©uOvýî ýb©AhBß.böîUÄ/ªB,|UÄ,ÜÃ@+äªß<îrärý +CUãöüüz@/ÜýöîoÃ|OÃbÃ|Aü>hßßör:¢Uö.Cßbý,@+aðBªä|ÖAî£î ZüÃzAu|zöÃ:ZªA,OAbv|£_ü~rbÃ¥UAÃ¥<î|Ã,ßAߪ +@B>äðhÖA/ß.,*ÃÖýhBý* ZbU_Ãî¢|hr<ðOC/<Ãvîhuªbb@ra¢ßîâýAUaUzrUC:Zªö:o|z@.ãbuCÃýOA@îýÖU<äöoä.u/b©aÖz:Cbb/zz>Ã¥_Öð|Abv© CÃAhb>aÄîZüÖ.r*+@bü<:Ã/Ö~ OUz,oo, Ã¥bv b_bÃ.A@hC_|O>aZrÃ¥/vZ|ÖzÖ,>Cß+b ZBra..Ãv.U:r£©hîoÃ¥r:/ßýÜZUZ*îîÄÖbuZbzýäÜhOäbC_©BUªöðO¢å@Ão.+bªoa.zvöö£Ö|rBzöÜ~/ ßüäZ©£Öß*@/,vÄOaßðÜz¢@oÜü¢öZb_ÃBÃb|BÃÃ¥ Ö|/ª.u*z<îzoäßCÄZßBzbÄ:ÜßÜ ©Ohbvî~aÖOðÖ_¢z:,Ö+Ã¥Zª+vz£|ZAB©Ü£îÃh ßAvZß+_+b_ÃÖvbA/îAbßåÜ¢ zü£@hB£öOC|ÃC~ߢ uCü£> a/zÃœ :_ä+äÃ,:oC*|h,Bð~©£ªîoUA>/zîîC>B>ÃœBö:ä/.>|Ãöå,<äÃv,Ö/@ßßo,ªä ý,+©+©å/.r <ߪ@ÃoÖ@BCoUÃÖ£ýözßbv~B>o v@Ī/ bbv£ðzBý£bÖrßo£ðÄÃðCªCbîÄB*+Ã¥uß<ÃãO©<ýb£rß/Oßå©a©*ßAÃzÄöa+ößöÃB.ZZ>ýýü/Ä¢Zîß©åbß. Ã@/Öß,Cu©BbUÃ:ÃAÃ¥_,ÃAÃðb..,îvCzC,¢b~ý_v|Ãz<ðÃî@bv<,><+ÜÃãr>@öß|ýaUßvhªzîÜ©//äuÄu/häüu@rB@uößýâðÖ<î/bUð©O<Ãßð ã¢bÄÃð©>ðAA@Ã+zZhOurßÃÃœ+oO+ *Ä£ßýoÄ,,*@aa©|ÄÜ.£ã©ÄÜa¢ýöß @Ä©_~üßoC+@¢bbbîAü~_Ão/oo£/rU_ýÖörbBB,ö_ªð|,ÜÃäaüß_ÃhUz:ªröAÃœ>oüö,©ªÄã~BzîÃ>h/r@Ö£ÖvA uAZî©z/ðÖBܪ/+aUZhU:,U|ßzoý.vhÖåðªo£zßOv©+h~Ä_aß@@*ªa~öý:<~>za©ª*åÃãu+ö:üýÜaBüBÃœoOîÖÜhÃ¥ ©oOãu©ö©Ä@UããO~h|î£aßöå>+ß*hBðãrý*obOBuZbBZ~ÃvðýªÖOÃ¥>ä@ÃvÃ:. ß|ßÃAã>a_ü<©*üÄ£zÃœCÃ¥*_ ÃöÄCã/Ãœ>¢ßöU_<åÃ./ýbßÃuhîßbüð*@üvubÖzÄ£|Ö:äã,ÃOo.bÄ:+<_bÄ~uA+ß~Zzýüz/h+ÃÜ.z*ã+Ão¢ð_ã£b~ßÃaAÃ>¢rðå~@.ÜäÃvÃa©üoOr ãb.:ZãU,oª.C£rou,ßÃå/uÖ¢hðva¢v obß>ÃÖÄö<_©£va~aäÜ>Cª/ÖÜö<Ö ~zÃœ>äCBãåååßär*Ãœ|<¢|ð.UChÄÄzA|va+ªîuÃ¥h,îîAOvä~rÃOußv@Ã*£vÃî>ðvBüð/Ä£ÃÃO@oååÜo*aßA|ö©¢ßO+.ßBÖð<|£ðr*/Ã¥|aîÃB:+ /*Bv Uö~/ª© Uäöa~ð<_:¢*Ã_|ãZraãAO >äUO_ b.Z~Ä_Ã¥hoüãäßa+ÄÜ,£+.Ö£oî+BÖ.,o:|a© UýhÖ ä©b :Ããå:Üð/@rðÜh© :hÃÖß+zß*A<ßvîb_B~ã:©Uäö¢UAAßÖ|CzßzOhðUüoÃ¥>ãýü£_<£ãOzu©rvÃœh~*Zý~<*<<~ä.o Ã¥ZÜÖaü£~>|oð+ßBhBð*Ü©ªäîî+¢>OöªU©ä/ðå:vß<ýãÃã©C_+*>öb,©v.å㣠h.|öîv>,OU£©_Z©ÜhbuUBhã:©rvOo>aªª> ~ZÜð.ªorBãZ@Bý+hߢöðê,©OrÃurýrvzao>u/Zö@äÜuîAßC@:AbãÜ£b äð+*a,åßoOAzÃz_r/üuo©ªO¢O.bßÃ+Ã>ߣbÃ/.ZÃö©ÃãÜßãh>ã,ß.ð©vÖÖOª bBß>_Ãuß|or£U _uÄv~uvä~¢oUÃÖð|Obð <ðý* CÜßå£äZßubrîÃ,©ª|Ãœ,hvü<åýýhÄîä¢u+ö<:<ýöÜä.@©Ã£/©UÄðh~rboZîå©Ä~î*:ü_ßðuh~ã¢Zã/ßo*_<OrÖB£ÃA| züîuÃ<Ã_CUBÄ.ý +îªb~<|~ý Uª,UîÃ/¢ªoÃv£~Ãß_oßOÜÖu/ÄÃhýýäÖ+îå<ä*zu>©Bßã¢CUa*Öö,Öß|<,ö@oª:¢Ã.z*ÃœCãß,ßbÃU:C+b/î/ råü<ãýrCüuîOß:::UÃ¥*,+BU+£@AÃœhrv_aü©£b©BÃZÄ.ªh>/ý©îã|_AöC£~ãzü.üÜü*oãÜß.~ß©B©©¢UhbACU+Ãaýa|a<ßÜÃåã£Ü ~uä>UÜåðv/a ÃuözÃ,v*OßußöÖaöuöaý:ß<ßUÖCîCbÖCahh_Ãœ~ýU+/oÜýb_<Üöà .aåîÖhbýZ+uhbBßߢ:o£öܪA£ÄßOr£hO*.ÃýUuߣ@aüaraÃîvÃãzßb,£.äUAzäߣubÜ©,ß|,©>Cðß~ߢAðî:ßß,zAb|,ýÜ ©u Ãœoua>:O:£Zð:äßbªý,ü ã,£<£*Uðö:~ß©ßvö*>rÄäauh,.üCßr>,BrüÜ*|b/~ ,©/./ÃaäÃýÃruuãîü£ÖhÖörÃ:ã<~ß.Ãœh@¢_ÃüüvCö>@£îýÖBãBßßr£ÄUÃýã@vî~übÃ~hüöa+£rU/_ß:B+îozüz ýß/Uý.~êöÃãzoöÃÄB.oa.ZÄð@Öh*ÃvýÖ+ªz@ÃAöb@¢>ãüoð/O:u_h |_Ãz:©/ ä©Ãbð_£uabÜßäü*ßÄOZA/ªbbÜÜ,|v¢OÖßoÃ¥U,Üü/obAãÜÃÃ¥*CãÄh~£U<Ã¥>ä/¢>r:zAýÄbC/öuãå+|uvAîA:/ðAOOî.*îî@ß©ä~@ÖÖova.ÜðÃÜåî©uã_~bÃœ_väßߣÖ_AÃAu>ýÃ@vÃÖ/aoC/.a@ébüuªBAvîö*¢UåÖuÄ_+îüÄ|ßãªÖ,h..ßhbäöÄߪ.ö.ª +hrh,Ã¥Ã䣪Äö>o,a £C<_|îööü_BU/Ã,¢ýUÃ|h@*î:|,~bZzhÜߣaCª,ßîãOãUÄĪéäÜý/öãB*ö@+ä©Uü<£A,@,ää£:Ã¥obrãÃü,¢/*ß*Ãoav/z@ßüðåÜb©uOvýî ýb©AhBß.böîUÄ/ªB,|UÄ,ÜÃ@+äªß<îrärý +CUãöüüz@/ÜýöîoÃ|OÃbÃ|Aü>hßßör:¢Uö.Cßbý,@+aðBªä|ÖAî£î ZüÃzAu|zöÃ:ZªA,OAbv|£_ü~rbÃ¥UAÃ¥<î|Ã,ßAߪ +@B>äðhÖA/ß.,*ÃÖýhBý* ZbU_Ãî¢|hr<ðOC/<Ãvîhuªbb@ra¢ßîâýAUaUzrUC:Zªö:o|z@.ãbuCÃýOA@îýÖU<äöoä.u/b©aÖz:Cbb/zz>Ã¥_Öð|Abv© CÃAhb>aÄîZüÖ.r*+@bü<:Ã/Ö~ OUz,oo, Ã¥bv b_bÃ.A@hC_|O>aZrÃ¥/vZ|ÖzÖ,>Cß+b ZBra..Ãv.U:r£©hîoÃ¥r:/ßýÜZUZ*îîÄÖbuZbzýäÜhOäbC_©BUªöðO¢å@Ão.+bªoa.zvöö£Ö|rBzöÜ~/ ßüäZ©£Öß*@/,vÄOaßðÜz¢@oÜü¢öZb_ÃBÃb|BÃÃ¥ Ö|/ª.u*z<îzoäßCÄZßBzbÄ:ÜßÜ ©Ohbvî~aÖOðÖ_¢z:,Ö+Ã¥Zª+vz£|ZAB©Ü£îÃh ßAvZß+_+b_ÃÖvbA/îAbßåÜ¢ zü£@hB£öOC|ÃC~ߢ uCü£> a/zÃœ :_ä+äÃ,:oC*|h,Bð~©£ªîoUA>/zîîC>B>ÃœBö:ä/.>|Ãöå,<äÃv,Ö/@ßßo,ªä ý,+©+©å/.r <ߪ@ÃoÖ@BCoUÃÖ£ýözßbv~B>o v@Ī/ bbv£ðzBý£bÖrßo£ðÄÃðCªCbîÄB*+Ã¥uß<ÃãO©<ýb£rß/Oßå©a©*ßAÃzÄöa+ößöÃB.ZZ>ýýü/Ä¢Zîß©åbß. Ã@/Öß,Cu©BbUÃ:ÃAÃ¥_,ÃAÃðb..,îvCzC,¢b~ý_v|Ãz<ðÃî@bv<,><+ÜÃãr>@öß|ýaUßvhªzîÜ©//äuÄu/häüu@rB@uößýâðÖ<î/bUð©O<Ãßð ã¢bÄÃð©>ðAA@Ã+zZhOurßÃÃœ+oO+ *Ä£ßýoÄ,,*@aa©|ÄÜ.£ã©ÄÜa¢ýöß @Ä©_~üßoC+@¢bbbîAü~_Ão/oo£/rU_ýÖörbBB,ö_ªð|,ÜÃäaüß_ÃhUz:ªröAÃœ>oüö,©ªÄã~BzîÃ>h/r@Ö£ÖvA uAZî©z/ðÖBܪ/+aUZhU:,U|ßzoý.vhÖåðªo£zßOv©+h~Ä_aß@@*ªa~öý:<~>za©ª*åÃãu+ö:üýÜaBüBÃœoOîÖÜhÃ¥ ©oOãu©ö©Ä@UããO~h|î£aßöå>+ß*hBðãrý*obOBuZbBZ~ÃvðýªÖOÃ¥>ä@ÃvÃ:. ß|ßÃAã>a_ü<©*üÄ£zÃœCÃ¥*_ ÃöÄCã/Ãœ>¢ßöU_<åÃ./ýbßÃuhîßbüð*@üvubÖzÄ£|Ö:äã,ÃOo.bÄ:+<_bÄ~uA+ß~Zzýüz/h+ÃÜ.z*ã+Ão¢ð_ã£b~ßÃaAÃ>¢rðå~@.ÜäÃvÃa©üoOr ãb.:ZãU,oª.C£rou,ßÃå/uÖ¢hðva¢v obß>ÃÖÄö<_©£va~aäÜ>Cª/ÖÜö<Ö ~zÃœ>äCBãåååßär*Ãœ|<¢|ð.UChÄÄzA|va+ªîuÃ¥h,îîAOvä~rÃOußv@Ã*£vÃî>ðvBüð/Ä£ÃÃO@oååÜo*aßA|ö©¢ßO+.ßBÖð<|£ðr*/Ã¥|aîÃB:+ /*Bv Uö~/ª© Uäöa~ð<_:¢*Ã_|ãZraãAO >äUO_ b.Z~Ä_Ã¥hoüãäßa+ÄÜ,£+.Ö£oî+BÖ.,o:|a© UýhÖ ä©b :Ããå:Üð/@rðÜh© :hÃÖß+zß*A<ßvîb_B~ã:©Uäö¢UAAßÖ|CzßzOhðUüoÃ¥>ãýü£_<£ãOzu©rvÃœh~*Zý~<*<<~ä.o Ã¥ZÜÖaü£~>|oð+ßBhBð*Ü©ªäîî+¢>OöªU©ä/ðå:vß<ýãÃã©C_+*>öb,©v.å㣠h.|öîv>,OU£©_Z©ÜhbuUBhã:©rvOo>aªª> ~ZÜð.ªorBãZ@Bý+hߢöðê,©OrÃurýrvzao>u/Zö@äÜuîAßC@:AbãÜ£b äð+*a,åßoOAzÃz_r/üuo©ªO¢O.bßÃ+Ã>ߣbÃ/.ZÃö©ÃãÜßãh>ã,ß.ð©vÖÖOª bBß>_Ãuß|or£U _uÄv~uvä~¢oUÃÖð|Obð <ðý* CÜßå£äZßubrîÃ,©ª|Ãœ,hvü<åýýhÄîä¢u+ö<:<ýöÜä.@©Ã£/©UÄðh~rboZîå©Ä~î*:ü_ßðuh~ã¢Zã/ßo*_<OrÖB£ÃA| züîuÃ<Ã_CUBÄ.ý +îªb~<|~ý Uª,UîÃ/¢ªoÃv£~Ãß_oßOÜÖu/ÄÃhýýäÖ+îå<ä*zu>©Bßã¢CUa*Öö,Öß|<,ö@oª:¢Ã.z*ÃœCãß,ßbÃU:C+b/î/ råü<ãýrCüuîOß:::UÃ¥*,+BU+£@AÃœhrv_aü©£b©BÃZÄ.ªh>/ý©îã|_AöC£~ãzü.üÜü*oãÜß.~ß©B©©¢UhbACU+Ãaýa|a<ßÜÃåã£Ü ~uä>UÜåðv/a ÃuözÃ,v*OßußöÖaöuöaý:ß<ßUÖCîCbÖCahh_Ãœ~ýU+/oÜýb_<Üöà .aåîÖhbýZ+uhbBßߢ:o£öܪA£ÄßOr£hO*.ÃýUuߣ@aüaraÃîvÃãzßb,£.äUAzäߣubÜ©,ß|,©>Cðß~ߢAðî:ßß,zAb|,ýÜ ©u Ãœoua>:O:£Zð:äßbªý,ü ã,£<£*UO.îo:Ä<ߣ.a.:Z*OB,|rÖ*+ÜöÄå<+>Ö+huaAÃC:Ö+a,rýü>b>r|bbo>ßACuÖßý/h£¢ZUvz/üößßÃöOÜürßýOöÃZýÄOý¢îÃð+~v_*ÃvÃœOÖ~hv Öåä U|ýaUOü*Z@BzßbvÃ¥/o©¢/Zðbß.", - "4733-01-26 22:52:45.974", - "2020-10-11 17:00:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array(null, null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("0.3913", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 3.141592, - 2.718281828, - 131711043, - -1278046619, - -2567, - 255, - 0, - array(null, null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("6459515CEF4E0D6C859FB097528CC63596AB884B918D383E578B8B0C08089E77004EFD497BEC6B3CF1DF4C085DBC4CE3095D796636C29BD721130563C200637D003997038E7CBAAA404C59B1402FE09A3EA803AB8E37403DB2262868E70779F07BCF07242549548DA3CF26E968EA97F1326A2519FDB8EEC9F85950685451C238D269357128BB75F5537508BE1057D4C88E37357CE160F2C3E03E2CCCF8EC38510DED16FFB525C976F530C587B030B8A08488D91D1B45"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "ã©ÃåÖüÃÃbUuý©Ö OaovÄ+îBZuÄ©ðãürrC£¢@Ä+¢,,ª+ß.bzbuðÄ.ð~:£bzÜåBbAäahß:£_ä Ö_ZåÖ@oä_îüv.uÃövÄ|BªÃvÖ,¢o+Uã.Ãb+©,z|î_hð rBÖ/ß/äOðüÜÃAv@>h+ߢÃ>vub|z£ÃßAöÃ,b£¢O+ð,©>Ãv£¢hÃœbÖo Z¢Äã_,uCrßðuö.UÄo o©_<ßãäãz*ÃuüChÃ¥/©Z,aÖB_Br*Ä,:ß*bzÃh>Z aru_vAvßßbbü~.Ö ,~ý,¢>o:ã|+o©,a+h Zrßrã@ã,_¢ >uözü _>îÄ@AÜÃéCh.bÃßUh|_rÖ<_Uö@C* .äÃ", - "Üð*+oZÃ|>bö:,äî<>zäß©ðabbAª¢aÃœruöo_~hBu*©@Ä:ã>öÃ>@+bü/ðäîZzCÃC_ÄãÃ>u z .B@Cor|u|Bäh<.hv@u©ÃãÜB©|üa¢BýÄ.OOöü:zÜ©*bÃAßýbzÃ<üa£¢<¢aã_OrðC~+:öZ:oßu:a*ßAbã~åÄð¢aÄÖ|à üªÖ~<.öo£+AÜã_©Ö|£ZÃ~ðýüz>a>|©Ã+~hOr:ä_öãå/<_Bä,üvoCZýÃbªßZý.:ªü©©rÄ*:ZÃC*äÄ>>/r¢ª@z@~B~.hª©~_@", - "Uzãr~ö+Ö£ZrãߢäÄ©_,O>uÖAb¢ßªÜã,|ðäü~aöCê_äävöãîܪA:/OhA_ßhühÖüßå¢ðö£ZÃÄ.Ä~ü*îÜCÜåßoßöhÃÃöArßo_ã~bý¢AîU>îzbêO©£aAOvößaö>OuÄU>îvoÃ+zðhbå£ v<Ã¥,h|v,OÜüZîÜAAhãÃ_v..Ãœ|äß*Ãåözª¢__bBb£/äzhZåãªZÃ¥auOCbãbBãvÖð£zß©©Zu>übßAB.CZ>©ArbäåÃh>|Ä:övßî@rýz¢u|+ßîö/ha£ÄߪoU|ãß/B@oÄ@AAÜýh|rhüä/:ßvöðZý<îý©*B_ ©.|uBbããÖåo/a/:~ßÃÃÃœÃ~ã@ß_©ß_+©Ã:ጚ~Ö", - "ý@bÖãüA:,ãª+Ãœ~B|A._,Az,r<ýr_©abü*hÖbÜÖü|£*h_b ©ußu_*uýª©häZäÜ>ß,Ü¢a¢ßAßZ:üÃÃOãhhzüÃ:<ÃÖ*_BðîZÃh_ßÖ*ðAå©bÃCåß*O~h/bÃßv|ýAuã@B¢~¢O _ý.rßÃ|£ü©£_:_b©Übrb ZuÃœ Ãr+|:h<Ä:ßOOðª*hoZî >ÃœZ:/ã@/CavÖ£åC@Ob@@oßýÃÃ¥<ã:äîoý /ßðððhªªb~:@/_b>ßöBÃœ+o©ß_~üab|uäv/rbrªvöîðCU@r:ðßüAöbuövoC+*oª@>+~vý aZuÃhä:b/v*CArC@vßb<öîbUîßz,", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array(null, null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - array("0.3913", null, null, SQLSRV_SQLTYPE_NUMERIC(36,4)), - ); -// 16 -$values[] = array(array(("DC2AC223F37157A0D09697457E8AB44F431D051EEFC9CAA3EFA757FED77AF4CE6EB384F4C9280C3FA538F8A143E7BC82EBC8AB6A8EE32B13601E344B1A58C956A1C9D6CA05BF851B11C579B7D1286007EBD5130ECE8A3F8B887094AAF4C0254C6FA1007A10F255483A6E4"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("B106977B3F44B802F1474CE79046CFD78BAED178ADE8E3C655794F0BC70260991EB4407F3619FC277ABC904943DF84AB1B36779944C822D125C5F4AD32CA804A87F7271B970B77B87C856E26350C366F86240B908BC65456916F5971254FF5AF3B46C24CB769140FBEBE1DBE59FE2A028E6F04B8802AAE6E0EC5DB82CF02D9A1"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("0F"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "Öß/Ö>BhüÖ©äv<ÃzhZ<£.öZ/UbÃœbüÃð¢î>_|Zu,|öö~üßZh.,,ßüü<+rÃÃAO<>oo*ãßß~ÄO/A, >©BüÜßbäBåö£/h*Ã¥|rrb:Ãœ.ªö: ßÃu+ð~oä@~ÖU*.¢bZ,r,©UߪußzäÃUuOÃ¥@abvvßZßöã©ãÃhrÖb£~äªhCZZhZBÃðär:OÖ+hC./_ÃœZü£AABZUU㣠v,îÜýh@ObAý<+@,ᛔUo ßãh£vBrß<©A_hð*<*hZî*ÄvýO£@Ã:¢z~>ý/_ÃœhÃA", - "uÖä>å©>r,Äü+ðý>Ãœz_©>öoÜý/ÃœZåÜZbÄaOãZ @rz:åîÜ*býåbB+¢.B¢U*Öor/ßb,oZ:@a:ÃÜÃ<ãrßußÜz:<+åÄUªªÄªz*ýå©ß@¢bª:ä~+~ðöv//hð,åü ©>:bãðzä//êo+ZªÖoäªåaZÃœz OZ<+öBî", - "OaÖ__Ãðou Bbh@ouA*/©r©Ã@Öa+b<ªÃãuZ/Ö. bhbh|üuBhüuÃb_Ã¥<ß>ZUß_+å©¢£vääB.ßÃzZ~bCÃœZ£å.", - "@oöÃ<ðöh£übîB< C+oö£oÃœ@,|O: ,ª@aßoð*ãÃ_üu,åßo~ãã¢håÄåZu:b>OrbªÃA/ABö|ýv£¢ü/ÖîÃuý*ªÄvÄßh_ð:v | ðª£ã,C<Ã¥v£äUBîã:|,vOä/î î*©h+ÜîÃb+ãCÃã>B îbb:üZä.ý_UBßðÃ|@A O,Ã¥", - "AbaªBßaZß©öåbh/BîBöüZ>aA,.Zªz£¢uÃœ>Zß~<+_/z|:_>: .BýðîB_@AüuÃ@,ýhAÃC_ã£@.Ä,@ýð|>åöörr©Ä ©üÜܪUÃAb ./ß,î>Ãœ~|r>||ª:<*Ã|OÃœ< .:~båîOA+<£>hÃßB,ozCîü:ä|öB,öz.å©,OhBîB ßäÃCÖh+ߪ*Oî~ßvbîªB|AZ_ÃA_öÄz©ý>büzAv C.~Zß~h>zÃ¥CCu<ß@z@ übrr£>ö*ÃãªÃýÃ>öª@ubªO/@Ã_£", - "uAoubðöO_:Ã.£î*©Ã|.ä/vv|vZzãb<ü~ü£@Ä£ÜÖoªã¢_o>>u.zîbCOªhîZîÃÄB||îð/rð_ªß>Cä*<*ýÃýb.Ã~ð<ßb,ß+Ã¥b/>vb¢ubzzäÃC_ýzv£|ZoÄ£©ãýý/£h@ÃUð:Ã*B.ÄÖÃÃÖ|b:+<<ßÃvbÄ*ßã/b£ß,oßh:rBzöouã_._U*îÄ¢@Aãußa©Zýý./>äÖ|O<ðÃrv Ü£Ävb¢öÖýßAãACbÃöÖzvZÃœBC¢£übäýA/UÄb~Bê£AU/££AhßãB/*äbÜÄBA~.ßÃ.Ã+vrãå¢ßÃßOOãåÄ ¢ÃÃärZr+Ãœov>Öhüäo>ãC+ßZðuZÄ@Ãîðß,Äö©BÃ¥u,Oî.êCbªäzA b~öß:ᜌ~ÃÃåu/CAOZÃœZZ/ö:,~ CuC©+äªÜßÖhuZUzß.ä:@éÖ*>AÄbüOU|Ãz.@Üð<Ã¥brýao¢@zB|_/¢ßA>ÖAß++Ä©Oîu>©öB. ©<_ZOýObhAvÃ:îÄ.¢ö© ß.bß@ßBðhä*ýhuÖovà ÖãÄU /öÄý@bAa/B@U/ÖÖ+Ã¥UöZÃZ~uo|hå¢îÃ@ýüäªÜö£h*>ãöðo,@¢/bÄ,ovÜßå<*Ã_@©ã¢å£AÃ¥zUbuUªv:C£üA.v/aCãÃBÜîbüöB.ÜÖß:¢B ß,u|Ãoª+üða|.h£ðÄ©ÄözßrÃœ@£Äbî*:b.Aö.oh/B£:_Cö+©vCãCßîoAaO:AA/@<ª<üo,ruÃœ~u¢Zãvbßhü/öbU©hvýðUÃB.£Cýð£ýuZbÃ¥b@|ZäOÃB¢åbý~.+ßßå.výb<ßüOä*/ãðÃ+baÄBä.ßrhbu ©@~_ão äßåýbÖz© ,©ßü,¢>,©Äö<ªbãaU~ß>U*Ã>/©@öb:>ãÄ~BOO|Ãœu:£*bÃœrzãoz/:ühÄBߪOßbabãö£ZbZZÃZ£ß¢hCîu|ä|hßh>Uð@öÖ,Uh.|bö>zZðoîÄÃOð©h_@rZaBhb_z_b㢣_îzhðbo.ðvö,uB:zzU,.Uröhz/a,rÖrb_voÜåÜÃb+BvAbÃîübýuß:r, öOãÃbhðö*Z,*£ü/ßC.ßßAÃ/ªäÃ,raü¢>ÃÄhü r/ÃhC:/~r.ub>Ã¥C_ö|Bðåß*ÃÄð+£î¢öã:<îüC©ðuýa© Oo>b@öÜobb/+Bü.ObÜÃ|<,~Aýh.Z¢äðäoA||/zhüãÜß,b@äoUÖor BäÜ|:B+@<@Oh|ub.äÃÜ£ÄÃêrý:", - "2001-01-01 00:00:01.000", - "2015-02-10 17:05:00", - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("0.4471", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - null, - -1.79E+308, - 0, - -1831503069, - -2147483648, - -11771, - 81, - 0, - array(("DC2AC223F37157A0D09697457E8AB44F431D051EEFC9CAA3EFA757FED77AF4CE6EB384F4C9280C3FA538F8A143E7BC82EBC8AB6A8EE32B13601E344B1A58C956A1C9D6CA05BF851B11C579B7D1286007EBD5130ECE8A3F8B887094AAF4C0254C6FA1007A10F17C2B42965D32A4EE4E81C4C1392FAF4A9C7CE06DE9B818131770B17681697FCD2E2041B79768778B9C345444982DBCCF5CB7DCC48371795C9F0B5EEC712E6CD50207A30"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("B106977B3F44B802F1474CE79046CFD78BAED178ADE8E3C655794F0BD026C70260991EB4407F3619FC277ABC904943DF84AB1B36779944C822D125C5F4AD32CA804A87F7271B970B77B87C856E26350C366F86240B908BC65456916F5971254FF5AF3B46C24CB769140FBEBE1DBE59FC9B198E85260EBFEFBEC0A2D08927B206922D90BFEBEF49E9B81D0FFBD2CCA996FD95D33FE2A028E6F04B8802AAE6E0EC5DB82CF02D9A10ACCFA5CC09FEC31DF"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "Öß/Ö>BhüÖ©äv<ÃzhZ<£.öZ/UbÃœbüÃð¢î>_|Zu,|öö~üßZh.,,ßüü<+rÃÃAO<>oo*ãßß~ÄO/A, >©BüÜßbäBåö£/h*Ã¥|rrb:Ãœ.ªö: ßÃu+ð~oä@~ÖU*.¢bZ,r,©UߪußzäÃUuOÃ¥@abvvßZßöã©ãÃhrÖb£~äªhCZZhZBÃðär:OÖ+hC./_ÃœZü£AABZUU㣠v,îÜýh@ObAý<+@,ᛔUo ßãh£vBrß<©A_hð*<*hZî*ÄvýO£@Ã:¢z~>ý/_ÃœhÃA +~*öðU~A*vßüUýC~h<ª~>+ä+:bh<ßÃu,U~üCîbU.ð©B*<¢Uazü££h:ãaÃü>äBCu©b.Ä©aa©buO£ßv@åßßCoäA|vßÖü,ð|+zß:/Zbî~azZ~/ößaÃub©büö+¢å©>r,Äü+ðý>Ãœz_©>öoÜý/ÃœZåÜZbÄaOãZ @rz:åîÜ*býåbB+¢.B¢U*Öor/ßb,oZ:@a:ÃÜÃ<ãrßußÜz:<+åÄUªªÄªz*ýå©ß@¢bª:ä~+~ðöv//hð,åü ©>:bãðzä//êo+ZªÖoäªåaZÃœz OZ<+öBîßÄzUîrðÜäU/©Ä.Ã¥ £ýC_|a,+|ýhb >ãýZö©_< ª.¢Öý:ª|ý¢BACîähzÄ©uzbb,öh£übîB< C+oö£oÃœ@,|O: ,ª@aßoð*ãÃ_üu,åßo~ãã¢håÄåZu:b>OrbªÃA/ABö|ýv£¢ü/ÖîÃuý*ªÄvÄßh_ð:v | ðª£ã,C<Ã¥v£äUBîã:|,vOä/î î*©h+ÜîÃb+ãCÃã>B îbb:üZä.ý_UBßðÃ|@A O,Ã¥böüÖrzßãÃ,uB|ªhäãª_£ªB£Bv+©>ãZ >ÃoU©äu| UB*+ÄüUCU+ãaÃß_:@Äî|v_oßoÄ*.+:aªbr.@ÖZ,Zh|U::zÖð¢Üoh abÃö_rý¢", - "AbaªBßaZß©öåbh/BîBöüZ>aA,.Zªz£¢uÃœ>Zß~<+_/z|:_>: .BýðîB_@AüuÃ@,ýhAÃC_ã£@.Ä,@ýð|>åöörr©Ä ©üÜܪUÃAb ./ß,î>Ãœ~|r>||ª:<*Ã|OÃœ< .:~båîOA+<£>hÃßB,ozCîü:ä|öB,öz.å©,OhBîB ßäÃCÖh+ߪ*Oî~ßvbîªB|AZ_ÃA_öÄz©ý>büzAv C.~Zß~h>zÃ¥CCu<ß@z@ übrr£>ö*ÃãªÃýÃ>öª@ubªO/@Ã_£ßÄzUîrðÜäU/©Ä.Ã¥ £ýC_|a,+|ýhb >ãýZö©_< ª.¢Öý:ª|ý¢BACîähzÄ©", - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - array("0.4471", null, null, SQLSRV_SQLTYPE_DECIMAL(32,4)), - null, - ); -// 17 -$values[] = array(array(("A82F7923662A496625B3CD58E906DD15019C700D5F48E2AD60858A9437AC118D0E99EFA02BAC0C52A44EB1940E8BEAAC3617AB238573055F4CCBC2E19FB52F78A13F494F173CE9548F1E6911974E9FD59ADE5D1F01EE089B948F545FE92BB2EF1E38F3CE419B95FA2D56936609F4C8FE2CED46C0571077B237AEBB87E8896BDE"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(256)), - array(("304F1D1447944F1CE70A2A62C02D5162E8BC9EBA4D9CA036FA24DC9C61E6F40BC0D00E85A45BE19CC2E44C26694EE3BB0A0CE814DBEFA194AFE71922B7B2BA01151FA2F01FCBBE8DDA01F8694F7ACCAC41219155FDDF2FD12F79D6BC41BFE50F2A4B104AACF39B3F4E5B39D9F6384"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(256)), - array(("2987450EDBC73896120915EE84C828A2664F18523DDD6219E14FFC69E68BB2625E3869FA73EC76F9E5E1DD21BFD1462230449C4208560F4C252C2C8CCACFAF8757B38F4C5F2E2F7AD91F6D5BFF40591009DA39A1EE705908291CD447BFEA2E163255CA0C41F9C201152269E8E9B34CE4D21C82EF950D99E75987234259E7BAEEF15629A9764DCE1018378CCC07701F75CFEF75A2683F8024BCE2B3BAD57FA72D677AF79289A26E854E72D75D81CCE0E868AD736A721886B1D505ED939A143A98905E648C788CEC2C81EF54F56723A2F002CA52280C3DBA4FF446BC84B5DAEB3A373FCD23675370DBC146E883C3904AB262FD91CFC2128B53A3159832D06777A6041ABE28557F88AE5A7666C654378B63FB0922B8491B7825B37A7F51BB3D0B59CF417EA9D2215DE274E2754BABC5FE6B8A3436C645BE5DE9A29874DBFF36F31C6B0CF712B9E190D9AC3F4366A52C79EE7B14C78606451410E4950739AD81E23DEF59E09ECEECB1A8A35D19067AF643751E8660EB6F6C1525F8E62E24DA562C2B5C05774C4CDA208F621FE2A92B0E95ED51911B2621F7CE8343BAA6E7BE8DCE4C30EEA9757FF7294366D18950896CF9E95140D2483B060A6536F408913438D1D45096783D64206E5A68D7BF506D7EF9D7C0BD957DBB85112AE4443E8AEE980F2DFFB5FFF3F3BC8431D177DF6446B9F7B8FA31D077A35319CB844E5920E1E46CD0C89150B87F06E3043864AB43A5ACE32A4C858FBCC7988AAC9B336EA462644FED0B0F9CB4693F38F44DCB3E3EA7D525D76B2FD45C54AF082F06A41C4B983FA2D415BE3796102EC00C4C9B6FFB594C04C60E9639328FF3995C066CADC523ED5C0E296A29E99E8FC73C6EC70FB8400CD6BA9F563C01056F442124DB8FA480FBE95F3F60D2EE221B287ECFFDC4C7E4E01BF995F225CCB05A08B92607C4C2FF22BB73DEAF79DF3AE798842EBD3208AB7C3D44BF960E8DD51CAEE45719C81A57657FAC4151E934663BBFAE5AA19EF227000EA9CA7F0269132D9DC1019488201F7E2CC83E85C65A9209DD84D66033337EBCBD570B1F50D763159814B32833D1B39E8EDC74D5B3A139B316C1A17E92B3FA694244D73015002E556528EDB2A669D50BD038D157BEA8081B1D57088E35E483E51A49E5EEFA83898EAD1F708BB4870DACEB95AD5AE1A2AB9A3CBB12BDF79F1F80A507985D84E7114DAE42A99F377C67964A5BA39F372903779E4BB13E70CB960EFFF0E6ACEEB028048460AB9FF80B62BF547C739CFB7F497C0420BE3835D2DCE2AB9B9F4796BBA79A329F1BD67D647F92E8F1EAF458FA127FA66AA7CEF1EFCEB284C9F35D6C1B2999D4D9695C6473FEA0A12C5043E7E88AB1881FA02C13D9A843D06951B3702125CCC8511F7E91B61B336B062CC59F8BCA9C3E42D54D7AFD85FB30268BFB214A308006837B95C80B1FD1046DC10CACE3A4459EB5752E4CD851CBCC244E6B2A1F1120D6ACBF1B1034D3322203490D3B794E4D81465D6CD502332D32AFC9A4BDB4A7E45BA035FF23893569E1420B488F438F53D500E9E4C2A54AD47892E9606B2F756E5C64B29B434F29F810F38A92F6EA9DEE2076CEBCCAC221B556608691A0D5FEB2CD9CEC2A91F40F86D7F0D3CF1BB7DE8DF4FA3FF460B8A39943FD45E2C897583BF5D8C559FE725C2C6748E5DAEE529A9E82F99DA595095114F953E793AF2CBD3F9798DA43F2848F819E138D17D2C9AAF063A65FCFFAB3A6EB86B060AB8BC87D3A8BA642567857ACFE691B07827C39422FA8F63008CCEE44E4F2EEBC4123E4F6D596538C2C8753780CFA6165DEAC236F4D6E30029498746D9C615E64899A6ADA852532EC28AC15A8547E240D6B886637CE7F6FD3F2E1613C21E6F0CB28EEEF719086E0B87A3746A05988AE1673670113FA2696092B97E8A3C3F5CA42BC4A7F658EB37DE5A8ADBD9639CF796A334BC8B9E4DCD00258E546F99E3CF5856C03E30E084887C0EA1C6B4D69FFD05C1D7D70782C115EE0797D0DA86C83466CB8372936EB6A62EBC308CFC87183995815EADB20CC7C585BA503958B3BDF5366D5FE9431D69A70B21B85B52442966EF6E4B49282A8269F3CDFCE90E8D5D7B0E4B468F272EE67827B79290547C734190F959FA6864CD0BEAF5F5852070988DC44D4D16FD9A3FB077C3217097F205875AAB4815336302998E3AA421396157A72F14CC172F6C5406B9998295335C73E8E06E3A7528834A613AB388B2E60E87067B7A417325FE67EE57415BA9D06D5631C6BB40CB36D27EABCA5ADC7BD39110BB027C29C90B00B2E2064C2DE727A080DD5D56FC2ACDBBDFA8FBA5A8DB21EC1AB620A09764439D26C5F85EC4EAA4977419CA0F1E7CA4F5ED469C80CD5557E7C01A2167B687192C99B03036226EF6F4F454318F0122CDAE113AB340C7B2CFD16E109EAAB94CE0ED009D656E4C6EC37E5FA78C6913C8D69D4C83567BC3C864D6E05EC811C04C3BB773448E73BD6F61A27734F1102656BDA53A016E35E6652938389E7DEDA6B1AE037BD4F5DF3A62DFDD21A4048ED744A099A112D7CEEE05C10E2B463B1EF1740BB586EDA45160EE6AD721810442609A50F3FDE44C83E9EF156613F7A3F437B0EBADA84EF7F9A05F28B2B7F0AD6CF18EAD90E547211D3777DBA7D83E3D694A76AF6095669A11926743AEBB89DBE931B874955A030EE09FDE76306350116211CF7831513CC79AE48C0E7DF38251BD9BF2661FC9A81F3777BED0C6F069AE3245E18CFC01AA8804E0D9A0DE0480FD1D7CB89470EA01A869A040C9926C00AA8ADDCC2811F565BBF16A613EE0074132E6B1C34A37D53D584449134BB0D0F5A8E03F15F03858EB18170C930688D2B8626F1DE2E098B662B59073E3C8D229FB2074C84EB0E140FA8DD6AB332B3226DF7180B0ECA2FE150029CC606FC4751235FD117291B08407711E135509ED83377369A46248213D48226FC814E480E13618B4754619CF951EFD893FCE8FA90B12A684ECF61002DA14BFC94031F86F12E03CDD3C45784D2DB3D48574BEE17EC88954A57E6FA3EB1615EA6086508F63168E765F81B26CB59A596729119A8E2B233100B6DD42E0B34C41B7266B17B6CD34716EF64891ED062CAF1B14F87AD98B87CF3D555FC885D39DB3034FFB465A82A299F4A02B97E29153FA9A3E37DBBAC1782BAEABDA617D6BEB9B58858EAD1AD65488726E76296FEFECAC01553E57ABC20831D7CF97AEA8411C98B4803F5D02A6EFB3D9AF595C5B4ECBBECF3E60282D93FF967D4CC54B4EA5E03141ED74DFA08F995A758A7DFC68ABE68903D8EF79D0F46B1090A8EFC5D1947533CF280A7DC9EC1E4F9515A2A315F33DD1564C4765B676505D72544F07AFD378E1154A3D409178CF17CAAFD4CB41C80A9D7B1E6F954D0F7D48C7BCFB65AD169FC19CC18DDBC5FD3FED7D2F9B16A1850BBA59A18C9163D39405CCE478E47D597AB400032E803A2ED78DDE72181D7466A732B1410BFB8A0E6DC92D881ED1744B6023B4E706AE667B703ACA0EB2E1BB7691664D36333C4D7F39F1A56562A21006099301F4688D5008DC35C0D1317B5B7C0DA6E6B8844E1833D635BD04F8F108CE2AB4051FDFC70E04064459FD6B6C4DBEA63957BBCB6781CB7ADE1E32D04B0FEB2CF2B67D763B9C4698E2A24FE41AD879E7FE6ECF994D02C332499501A8DB33CB347EC068B5D0486AAD1122D8B531D76FF5A44AE1DC3041EFFFF33B3C968A9DCDBED70B755A6FB417B60422882A2A0DF072350C048041EEF42CE078B2951BC412735529DE36521585E0332BD81DB9B5C5F53ACA617E993D6CF8074869F6F172AA400014C5002FCA102F86145D1616E6632AA2CD6A5162A3DA27CCE62F19AB9EEEB081880DA4E0C5D570AA31F874E046D120D53F44241848DD6C3AAD33399B0683F7259C948EE8BE39ECF7E9A720BB654786150E346C4ACAA848BA917A43829BD5A3AE015F43B2423816F5573C2EA1ACB0C565BD149CC217AC6AF45BD44743F66A5C13F372BA0603C3C2C61D66E38E79970DE0A4EDD57738D19654E44F667DF0D30EFCD00AAE29F6CF51E941C6B4B0C8DC8AB4C6569F0894D4EB11EC6511B46D54D54A67E6BE6EE8D3E9E58BEC35622CD58C07C4A1A79A43F667FB2F8C3D91AD9D7BFCF17A58D22E1BDBF26CA98CA8AF78F516D919FFCBBD3AF6618EB34F5CA65847EE603860B28CB9E9A8D5B52D9A79BF4BA69726C893ADB03FB673E7436ACC7F5C14619E487708428A1F2056E6081D5BF7DF7798C57F8FB440B008BA3E95667EEC66691938DB76D4AE549CA96346CA37A917FF73F323B9DC94BD5D886E5E03E4B8B16A8FAEF4231C5892FA60D2D640F9E5D10126A5E28BB8BC8C748B8DEE664050A7F0B1EC4A88A4E4D31B502948F9BE6C53A29D5CDD3EC635B1D3128FA79E7A3CBA103AE59F1D9B485C31D2C72B8F6392C61F3A3BD8F373692A8C2A7766254D34A1237F28CED8DD36074500AB6C0F0A226F8FB998643E53C6ACC1BB12F323D226E4AE25F8104AD3FD31F42483F2D80D1B71F807E66BB4D062CFB88BFD1DCA42D362D9B6BDE9021224BBCC566009CFD3D4B4AF90FB2BFBBB3FCDB32857BED6EF77B1411119114F326FE5E32AAF5E60590FE4B7387E2957B7771E86F025D9BA65E4ECEEBEB8AEBAB3756D211538C11BD16D5A2873079E714E5999A9FA9A5F0C962B7D45B27FB1905129F3C300F8427EA602A592DBA1476E467B90552F04C02916609AE9CB8B5951ABC9D75903CB6046171AF3E7E4CC30E4D41EFF46EF5C614C69DAACA05D58B32AB953E711D9EE5A7B0B4A4E1400D8345A2FB605AC8C02625F588AFF392ECFE8CE57D93E90E52F1AF5C4843BA51E5D4B81A65270A743169EA6E197B94EFF115C4B517FBC37A9937500AB1FD807C6CD30FB4F6D92CC4F418B413A87BF4D65CBF6A327F0439EA113E802ED7FFC233E96E01456CDFF918E900C1FF46AFD21E427AADF2924EFD689A25A3B759FEEDF456812A3A7AAF697DC552497E433AA3616C62EC5C51CFA09B7099AB463B42BFEB12CCA9C48597A39210302D40047E245F81414AF61190A286E18EC9E004224183935D6C7791DFDCD4BE594C21A25F96F1744BC5FAF7C8D9822922E2F6CEC38EEDEFAAACA712ADCEA2647538B84481E481F8D86CB85D478BCB84E94D6EFCE606FBCB5E32F7B3EFDFC67D22958190213DA83E61FDF656D590A71EE122285F5FA67E6F3CECE54EECCE09F9A53B3AB1093B63ACA5309AE810A4F6CE8AD8D2CCB6671D7C1612533458595192F4FDB19A55F615A6BF7CBA2F0B955F98F617C414FC3C423E1DBE9AF7E72E534BDF07F413F25C7994C32C0D39ACEE7081B3BA3E40953C96B7BA737C965B1EBE91286E4F03A04CA5999E2F50CF2F08912F4E19442DEF12B6A2713D587FE0E239A56E7C113E9E6CE06715A5A265EBC688B1E14C4741DA90A115B7AD9263F325B9BBF6AB61B2F75391E238DC18496F50B6AAB5A69E939AF7CADDDAFB86F0D97D73EFEBE76F4065D5BA8DBB6509FD7858A566F7E8AE4C872620FFE084D8FCCCA008BA69295E88E17645DFEC10D735EECD6582551FF9F1CF2767C5C66F5FE0FDBEC01B5C4E944FF91D8580A2CE60764FEB11131F55A6ADA617EB42C198C1711664E22E4FBBD89F391588389A784E00C6F176DB5E4CC1A71713744562528D605B136A3EEC3127931371D909CD6E122EC8EA7BD022E613CAA23B173B47923CE78827DC870E101495C9E2C8ED72B8D9A800467D78D6BD3D74009F9BB01AEB4C8034F281A5918B1FB8D5E506401E2558E9616E3B0014C410CAE968876E74647BDE9647B8803734177527C5A2F1B7FD56DA47B14EABB04C6B70E1B6BB2904B043716A28AAD76DD7B22D56C6D84F221E12EC11AC27C315CF2D4ADB489E8178ED70768130878E537F6C6FFF2154EA731C4EC3A3CF185789805C9AFA5FFB253931B5695562CDF19784989240B7C3F47A43D6BFFADBDDA9D518ED01D4EC94F3834736CE6BEB94114E5C2B1F368517D1BC6820B6883C2A5E530C933D74CB1CABEFA50EF881F5888EAFC5709DAFBD724F011C446CCCDE6A471CD77A5F58569C8240803090771E50C7E09106C3CB8DAE9699433C5886B0DE113AC6DCA97A28365829EE5D2F8DE1D7E2CC7D8C3BD7A77C8E28E4C5141A44FB8FA11E56B142A64DB75AAD99DD307F08B76B598B0924A33A93494D819662213DE4F2CD8E6E8B26A02630D4E0290D9F5EA9DB0DA9C1B8F16BFDE4C267F424B754386FB5EA62F19A2C45B965D94FC31085AAEFD7BA54787C2B0062908AC9BEAB017880A7B958B4FBB841E1630752181B3AD33CBC054DFCFFBFF389EA00610F63860D6460CF1B572DF6F31F79731575D9E84655B68C01A6B37C80E7C6CFCAFA57317EDCFAEEB74C1F7C0FC6112D838CAA7EB001BA0B58D78567B0E44F33915E2CF8C847F4387AEA30F9363A7C3B9486F8F33D481EC8404BD8A757933DD3FDEBEBB19A19653C977203C5D1C0D51E5A4AD22AEB5BF6483F63B87C6E4DE0E7AD5CFC4A4FCAA07B21BC5BCB4459F67F76BA4C0DC013E8A2F5A2723660E8CD4A911B1875C48ACE8FA957092EFF2B04E56C3906B301D3279A735A55AD58691B38EE1139294181BAECEBBC646809BAA2C99D8CCA26E11A49BEF47D016A3421363F3CD03FEEF1DAA4EA7F1AC8446EBE90B9B78B0FCC9FF456D4C87B35C3804FB7CA85242C387AB4D7C3E8DFA7C751044F35ED47B3A8116AA0477A6B2576A5E06A2F62FDBE4A92573F03B44CE3C04C8E82C32B00D6399C0EB81B84E4BE0F8E611D08747864534605A29607AFF4C586DFFBEBE8624BA50A30925345A3C5AD983074522EDB4BD4292E7D255D4031EC1AF294A4E9D3262C1578FD44E5110EA240F5FDD73BA197AB9426C1B9E52548CAFFA539520F20C4C509483DB5DD07EE1820DAE567425AB1785BE810C2126D47F030334540D7BB931F3038CDF264E1CD8D37A8990A3FE087A4F3C0F7261001B29B014C85AAC2AE416446C5370A8CBE14791B838A6AD43CB050ABA04C16C46D0F2E8531F1175C23CB896634C479A97D1734AC7D0A8C7748C7C59C34FE21622B9FD9B608845ACDFC1DF8B801039F7736A72E891769476282B73D6A2C7B2724A9263B94EAC3A0F9BC67B06012D47544274D658DCF984BC15877345046F0A180F5D30EDAC80A8D730635CCAFB9E74FAD41F842DF169A5511DB7A3B738F50B2CCC6306A3FC69A7C1CBFF2869ACB9B087127860E4EAEC16A5B7EFCFF4ECF19E4B68200E9897C10E7A77B251F2313BA454071CA4B0841631D04C50FC1C99B2E4DA50ED017CBB17FF4179F67D288A24647C3B6BFDC04CEDB751EE2E5C36D4E1D0B4CD7791543A27A8EF2D53369D99194627FC7FD12DC5249D7325AF9E8CCBB87C9815B85B1293004213C30E92F9A729B8D4D8243A5510B2FFA8EA1E3E8A992C1364E6AE87D0BE3384FC3DBD6AB681A3CC0BF6B9A4978E177752C8B23874D1B77A96FB2E030891E816A49F4E275358491746422293DF778BE6CF3725180C0B101A7642C3CB5390C36A0E92FFF84FE0240A7F9AF222AF139B1F6F4733D17CC01AA98F23A110948DB3A58EC8854C8783A6DF032A987118FD7B52A7A45C1C5CF1FD3E8856791C4F0375F1D74CA3E69E5E97EBA054CE4237A99091B84E78E031A7588CCB46C4211A8A900FB49699CC4984C4490C606BF833AB06EEC5D763F09A85C02FF4171B479A66D125C4BE9D910FC448237A2FE3C14B5049204FD56B33169F752ED679D822698CBE959F4633750D65DEF19F30766AA20ED493CDFB07B50BAE0F25496ECA85FC25C9FD714E32052CD412DA0679A3E6190A8CD79534547E84F999E1370E2B9E9B58F5697688E953CCDF76F9D82DBE820A290C3ED1F2F0C7C15B7887B440CE1EA8844EBD743A6F9021390AC5CD6A96673B07E71DF1ECBCA8583DA82B1061AF8C01584153EF011DCFB97C1DA8F82AFA66B5014CAC59B02CBBC796553682447FCF997EB46519427839EA276485DF13A02D3341760F14F322513B958C48BBE74D64FC5E0F7976955D629912B2324B96ECA3F627F340DE70AAA64C633983B63F7E02D9FF89810436B6ADE403760230F00594A8D85283E3FC968BA3692D035BD9BC5994AF584C0D6BAFBF61455CDAFAA8655C5D94DADDECFA2EED690DDD9B09B3421A471C6D8197B9974D3893229E9FCB80FB03D0A5C6FEDE7CAA806AE2FF8E04B5BDF34283EE3AE3B4940D8D9549C7805D9D8CD297390D8B1B68AFC3012CD697BF33ED649B5D37A2C0101C01C9614922D2F01D4572AB6D7CB078271B3E77D287CD65DE616C62E4F065EA12771CFBC1AA2F7CF0010F812503C670B423361C64584D84345392D7C6701B2CEF8898E65FB4B67CFBB177D1784C40CA9AFBAEC1CA4985CA3998B77E1757B68FE5E2919C7AE010FF7CC920A2D06C1BF802D83CDAC159629602DEACC70D3865A40AF7FB95AAA44FBADABF450128D2E9DD7CCF6B3F1ADBBE9C700B99AC3359F234537CA7D870764D5E8881F01204617D2FE4CF4A3DDD26892F495F15AF1AA37780BD10ADAC3B116D06721CB698DBAC981E8E79C7640FB4673CF3789F1160AE43E50A7F54F3C3D0F4C36243CBB07FF0439CC6F72090EC1380F8A960B8B2824B95D3C8D19E6C47945ABD84F401BBF8A6BCF334F25A698DA2F8E6FE5C4A5318324116ADF7F7AE51E75522AC731BE801C50002B3B58D8FF12C5615D8A77DEB5A079542A2EB0DF0BF1E915DAD27CD1A8710E5395C479F62B4C12571A1D1D5BA6969D894DDD93526B70F53A5143FD2C96F4B9EB7C2449F1268228DE1C58C5EED38E6E4435F283CBDDBBE724CE3FA4E84EE5C1266431DF824DAB63CAB4B43512AA0666C3D4B9F7A470ED0EC03C2422E7DE409037979F89A2AEEBF7898FB8162886D57AC8D8E11395ED1DB576231E4628897CEC7424FE7A0B7DC414E9C26E15B8D7E53A67AA5A0EFD2CFA497AEA137E90A5CEFF12CBA104875A6F4A14CC2AD37F08FC9F3EE4DF7E2F6EEF482AB4002069458444F36E02EC35E1B20C3A0FFA300127CA71ED7440ABC54FD0A4DC58D9D35A4EE1CE47E6DF100510695D32D2441F74BE4602EA8E32F505D97EA49D5D10DD07DFB2EC2E2BE91B48B96CDD0AE378F30BB0301E12D7E9CB1BEBC86E673B72A8008C56532057F148D52ABA302255EA22608B98EC2DF2CB2FF6E6D042637AB40D47C3DD7E8D25D3B3BFE748E71DB8A9C601A60DDCA2857A0DC1265D3987AFE55A76ADE85E786B953BB8E52D45A9E3123D3D0A40BC6CD76BC149CFBD66C7992B0F02ED7070CF4A4D9510D224BC583564DD1320FC7D6F28EE9F412715A8AD2E731EB91E42BB73A26DF2BCAAAC9AB2AE69146BE052A79DAEA0DAFFD12EA835A5E7E15E06023AF2A86A71B209C1C47C53EC93BD003DCDDCCC810B7D68BCA174C4BC16C2CA3CACAC2F21C276E1F493ECDC5EACE49BEA34671A059284F6BC8AECC8FDEE4CAA01B11740E68EDF7A57FAD7ED1EA10910BDD9DA87AFA8C4AA27B57297D6AEE3064642DAAC94A4B45C55ECC30BBA96D81B80DB0A2414C4FB58ED6D18311B47A4A917D1A6DCC65D7B18257E32C2E5B9EF90DC5717FAA3BFF2814782BF278C82400E481AF3E8F0FC552260F2FF96923873347D8754C42FA90557DF2494449E538CA23D237ACBC506A0E002F49AB1C05CEF0F0E8BAA042D7CDE9E149C09CB6711840C655C586EBDA76E76E674C23B0F54978335853513A98775C145DCB728FDD578F06F8D4B63038051671C331EB8B9A38EBF048695E2DC7162DEE69629E1F0457910FBD1CB8208337FB534CDF67669BC07A14EF72477409614FE2A6D7C6C566F76D6BA0DBDE8D80BB6D106E0AD0E7AEC752A8F8893F92101D064BCCD579BB6125EC45720BB5BFF1FC84240527060E806833EF852DDCEDC7DB4B83D582B0BB41B1AF481E9B65A30192A6FBF51B8CF916F7CB64919A3643490E8DA6B021DA7F3BD8E25387CDFFF4F280DDE276D0E8BC4E527CDDB75C19B78B5BFF001DCEF2DFC837C02C3939615907C66210BC43CC7815C1586DB50CB7DFBF2AD82BCEEFE7239DFE2A4F10CC8304E113C5F0FA08FB379F86EA4C76852ABB8FF06D0E61458A27EF3E82CE298D57594E437C9136C6E5B63F909C47F5AABD08D36EBF206F84A3AB99AE01BB863D0E022F90FE23188984C91AA6CEC0B93222447179EACADE36F9C9AFC0F788B52A3FF2828A2864AC99E049D9F605B8B5D2F2677BCD3A0C424333E8A724735781FC03E066CC4E2F09CD97E789A0E0D8313113DCDE901CF7E033B03F564FCD781698B7EDF21541E994C35DAE35527291625BB76610A678F1EF1C00331C0EC8CA001FC0F0F902E61BEF2A481B326EC28DB11ECD0A985767702FC5FEE6151EDD860A93AE158181A86F75AB76F66E6DBF70497281FD31CE31A8B850C123040FF6D05CB210AEB0AD0C3F666D84ECD06101C2067E9C929CD0443C61F56D20B0756B75A4A6A9AC5D00CFAC765296D43944C8475F0F2C856364437C6DD44CCB3FDB7EE0F21F4839C76564F5BEF01C4A497FF32D2D25D1A158D60230632D3E78C3F46C9AD3A5D229FC9E7FF9AA26C673480932EDE11F9E1A09DD881C0D07D6AA26FC21BA83716693ADC9209EB5E807321F9EE869FA5097B4664291061DB62CE4C7C5C2BDF51BEFD6929F4017FF0F6421C6560C7E1F7CBFD6547D5440E06085E7AC3BD6E015593CF3AF11C75B008FB566EE7F3FC7A9BED55B15FC5977C7AEE63209587BB328740C7D1908CEFDA554C5D4C7ED527120CE4DED667BCA18FFC53C003B9551A814023E6A62C2AE90EFD7A5C13684BFDA4C75014B1A8E4CBCB9F38D53DCCEC20FF31DC0B5AEF81E6E1FBD0D9ED608E26B0561B3C694069D95B581B06F355AC42115A5CE18C3FA2B5466932B6D70BDD6B111124E6B1B0B7BFC803AF02D47AF28C4E8EC89303AC9A71B365F6FF0EB301507AB0F5E231C59060A29D19898FA0E1CB77A66749434A676476B3334BB9B8B114015F8723850F5A88FD67F0669311E112842A88674CB70C2F48906C7E9245A7EAEC04873019BD46674BDE4CDC5A01D065D120C78F2F49C84A5357DBF4A3DCF6B7C005798E89B0F9C6E4201B6D6A30D547BF06AC717E17BAA23C3CDCC1E251C36FE7951C548742DB202700AA0F2D5AE0238194349C2F8439A84BD7524DD437E4925CAC85D51481C07D2B8B4A7E6CAB75E15CB9404D1C273F625C7309B0EF8E4CEF1B18B281D40B0E00D4B770A43ABA3B9F6170F01AF79775356C2278A0780F8E9D3E5B6354F9F40C51AA3F7965B69B28A138694E7E9D32F784A27D14B1926E88C429D365DE57E6A66D639F6FB81D107ED0FAD7670EC10FFF0C63DFB17BDF5ABF3CB6B53CE236"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY('max')), - "ÖbAüßÃ>ð>aÜãUAîbrýªr@ã*b~ãÃ|îZuä/>b/hüübAåÄð~/ãZCጚUO*îÃü+ööðaußð,Öü:öuA_o£C.h©AB £å©ÖÄAÃß >Uö><ðã+ o~ßÖÜ¢a.bߢZßBðCßrÄ©åu<+,©*b¢¢ð©Ö*Ã¥>_îBbÃzüîoAÃhA,©ÄCÃ/ðC/*Bvüß.äü@/u.~hª ö,Örý ßßÜãÃÃ¥.", - "Öb©Aub>üa.£öÄbo<äãOäý,:b+©åv~oA|az¢+u.v,b,o~Ãåðª*vªA£üî*ª¢©_Ou+ã+bð,/ÃœU/~ßߪUÃ¥*<Övî |ýðA,ZܪÃÃrßazrªö@O~aO:ãozBÃvOßîzbv,oåýoa£Ãä~üªð©Ã@UB ,_bäBOA*|ðz@üAß:b@a|+<ªvrÄOb@£_ßüÃåãbaîýüZhðv:£åãZ:ÄbÖöîðß©,ýüßî,>_uhý,b¢Aªb ÃýÃbZuUrãÄrÜýîv >ýaA~U|O BrÃ¥/<ÜÄ@CbÃ¥b/Ã_öUUZvÃÄ ßÜ:Üüß/î: h¢/boOö,aÄBðO._u_a<: uÄü+åãuvßrUuZ¢Z*b_>b~Ãý~uU*Öå~äªz£b~b.ª Ãhr@äÄîÜvÃ¥::ý,Ö>Ã+o£>:,u ¢î¢|Ãv©ðo*:@:Ä> |>ýh.ªðî|äÃvüãvÃüOußvr|ªß@ßAZüv££zöö.BðaýÃaã~,ü bÄ_Z<Äb*ö+ åß媪~Aª£î Ã¥vuß@rÃA,|<ü|.ýoöu~aAÖAuCC.U_ßz/Ö,©avÃ¥_or*ö zîAOªä£.zzzCuýÃ.ß><ªUbrOÖüîbÃ.ß©ßÄðrBu¢©hÃ¥C:B_ßZrh+Ää*bUÄä/¢ü~Ãî>¢z@b>z|äß:*ðÖbrOªä_bCß:¢©>ußýÄ|ouü.ÃœuÃ~zC*ã ,Äß|Ãz_bÃœvÃ¥ ê~C:.bÃüövü*Ã, ärýãÄ+¢@ßÃããßb@o: oý>ý.hAߢ֣,:.@avühßzîãß/ðö ª|Ã¥z:ªußo>ßãßîÖåä,u. ßß|a¢AUo©_>rÄubä öärÄäÖOZa.oöübÃ@aßÖzzß~ü£<üz£B©Ãa@/+uªöb,Ä¢ªBrÄ>ßÃÄ,z|Ahvub+Üå£îÃ:ö/ª Z,åÃðªOüä/Ãœ<>ö,Z,:>zBa@|Z>bÃå£vvî:.z@¢¢.öz*£ªz+Ã>:b<öZuZäz,/_:Buo>~A_bÖÜãb*ߣåüýh*u/~ß ªUC:ruOÃ¥>rh>BÄ o.ÃZ¢äð.¢ü .zOU©¢*Oýß.¢|_£rä~>,Ã¥vßrßÃZo:©ß><äßåÃv_>hðÃ_ÃßhÃý©bßh~:h>ý/ð*rÄzüðoÃÖÖ@ªbýUö_v:aª|Oܪý+Ã¥,£,Ãߢoör/|ߢ:ýuo:Ãœh*@ªU@.zßAðÃý|vZB+ßÜ.ãzÖBÄ£+o|ö¢oru_Ã¥bhO, +ðvÃ|U*Oîã ©+zAÖ>@Ã¥boubz_Ãœ Ã,bu£+B:,>+h,aý©BZ<ãð>h_ªz*OhãöOßð¢Üä¢ÃZvã£vh,.î+z*+C,@ÄîÃÃ~b<öý©<ubou¢ã|/ß.hî~b©ZZAOýhÃ|uÃuÃAäåzbãüÄð@vã+*ÜÖ+A@~ÖaÖ|* u*><ð@,o_~ðBbbª+ÃÃœaOߣ+ýrro£öaB üê¢u/~Ã:ußrãvü<ü,Zý_o@~ý>ZÄ*Öu@<~h /ýhzOZÃ~ãOhðUé_ÃBßßuA£Äoä+ÃaZÄvh£AýUüßU/öÖß+*<£b©ß,Ãœ C.*/hzîA||<åܪ©ÃzÖv_+vZã*ß,Ä¢*B*ðr,ubo.übbb@îÖröîB@äu>ÃUa/ruäÃ>hÃ>ªhaoCob¢ßö ãh*AaöAýUC.:or£üOB+|ðß.oböÃü*ߪÃÜîozãOäÖ:h:+O©ãöOðbu,@o~£rß|,@aã:ýªîCbÃ¥vãZª£©rªü,|:@:>ZÃîÖ<Ãœ:AÄv*r@+>î+Cbß,Cî>î/ªßoßb>,öÄäa+~Bßß| z<Ãu<>r*:£Coã,äAO¢ååuãß~©¢îa_üý~_|O/Ö,*©¢ÄbÖã|Aª>_ðOðÃC<©B|ãªOß>£_ªZu.ZÃœZrv>ßbÖBªZ>ýbᜄ:ÄÜ£ã@b/î|CZöÖ+BvhîÃ.ª©¢*:uzÃß/ßåÖß@¢¢öÖhãªUö:oäð,Ãîðð:ãAoZ,ZÖÖbCZ,Oðä:¢rh~~ý/ÃîZ+,Zb@åüzý>< _<+B@C£büöböCÄß åã~<,Bð+:ý~./åð+üZ££r>< Aäz. *üßB¢/oîaö/_a¢@_uªBAÃüßbhh++v/_ß>ACAÃœ ß:Ãœ:ü*rCräöÜßUu,£+hÖö>bÜ£~.£Aý>>ÜîߢUua<äAö@/<¢Z|C+ÃÃ¥a+z.ýß,ð_br+/aî/ªo/+©îð__*oüCBðªÖ¢Ö_>ö>@:ßãÖahhÖª,U@uUUÃBÄ*aî£u*O*ßB>|@ãÖB¢Ä:+ÃœoA©>A:öüÄvßb@o>¢ß~<|© bvAÃ¥,ÃœC¢_ZÄörüUoOÃ¥U+Bß©@ýÃv<ãhüßCãý.åÄ>£Ü,zîß+ýã©Aãb@abhr.ÃÖBrÃ,>ߪÖßüv*©rz U.Ö|__ü+ÖUÖuh/z<üÖ_~©î.ýÜuaÄÖ<, ÄÖîÜ£or|U*£Ub,ª+£ß+ð£Ä© aÜß _U¢vä.>,__ãß_ý* vOð+ª¢büC<Ãýb|Üä+Ã¥aýÃ++£åuãBü|+©**bãä.ÄðO©o,Ã> ýÜü/©ü*ªåßZ<,Üüý|Ca£Ã>öaöoBßbuýî,åä aCÃÃýÖOA|ö hr©ªZÃä~>ãö>>Ã¥__©ß+B>ð¢ý/. £ýzî/aãöª,¢ÄO@z+>b¢rß:r@UOãßr|©oä,uªOö><£ððhAbr~r|hößr_rÜÜ©voAÖAÃ:ãÜzÃO_|h_ä@bvî,CªaBOöZäÜvzãî©££îð|ýCUß*ÄýO*hCÜü.ubv.*.©ª+U.Ã¥ba:¢>vÃ¥uO bÃrv>ðr<ÖBãÄbðüßÄ.b,£ßöÄ¢îãuZ,>ü~ª£îzBðÖ,墪Býr+ªåC:Zb@¢> aÃœ ¢©vð |ªßaßßð,CßBß>à C,ßða+ãO/£aãaðB©¢u~U*C|B£Ã ~äãÄã©+u>ܪzBýrÃr ,O:¢ã:ªð<üýãÄZ bUz~öoO¢ÃªåAî>vo,+ÖU>äüA,CðO.U©b ýAÃÃ¥oUou|Ãœaö/Ã.rߪ:öAßrZZOãuýu:ößÄð:_vuC~AöböBü.>üª¢rCZoã:ªü.ðU* bCÄÜö©_+*+aäC Ou.Ãa¢ßÃÄ.Ö¢B£UãܪÖåýU£_zbväÃZZÄC/ÃaruÄ>*,za ~Ã¥C£Äuv*:rýÜOÄ||åýh:îüu@©/_Ã¥v@~ö¢uÃohrabA+©uÖUA@oö:.o|.ª©ªýv©+Bðªbhüü,/Ãüb/_zýÖ/.Aà bOÃœbßC>@rÖ+£vâÄBb_Ö@Ãœ:BhäZ©ä|䣣Üä/vboãîîår/AîÃäªzU.*~ÄZßU>oÃö|Īhuabuß ößÜÃOªZ/ßCrý>v~ C|Ã¥.+~z_z<ãåéß,<î:_ßABOöb+|Ö~u |åî>*.ö+~ßA@B:Ã|b ¢a£uZA~rýhªÄC@ýuÃ¥Oð>,ã*©öåªðýZ>CCåüýhAý/ß~+oÄC@Z:ßuz¢|öߣüZ,.£uÄ¢<~U_aBär<Ãß:röã+ |öO oÖ~Ao+£@u@ZÃ/Ä*CoÃ¥h*/A:ABob£/ýAZ+©ðBª:@.ßÃÄzb~b:ÄB*©ßßîhz|öýzaBU>a+ÜÜU@Ã*Ä_Özhö>Cªö*£*>_ÜßßA@@Bߪ<ßbuv©:|£ bZhßoª/åÄbÃ~o@ßýý:ÜýA.U£ü£/b£,Öäå£|.Z©ýB<>:|ýýßÃOAÃœ_üªhB|*aßbBOuýZöà ä|v>©@@öãüãÄrÃ¥Z@:ãð©Ob+ u|ÃbC,z:@bZbªhäÃAý*r_¢ß.©OäA¢ª ¢orÄhüðÄz~Obo~rðzüU¢rÃoܪÜv+ð@ZßðÄ©ð,.ÃÄCvAª+_£ß:v|b,ühðý.C+Ã_ßÜz*o/ðZ@Ã¥U@>ö£|.bh:î£üU/ö~Ãöüz+A|:vza,rîOéßå:Ov.abuU£hßbðrãO |~Ã¥>ö<,U.©ð|Ãðz/_<,<@üUhÜðz/UBO+ÃäOBð,@ÄäOª+zÃîu@£+Ä/Ãß~ ÃÄaoöýz>_ßäÃr>oüê +åßBaBðªÄ:ðAA~Ör©>_îOZåÖäbÃOð|*Zvb><åß©ãhbr ããÃz~. h<ä+A:,ðB*/ävßoA.üÃß*.hîOÖZܪÜ,ª@Uuß.Ã:a<ä~>ÄOö@Ãßz*îªüà örZo¢ zöß*A~ ¢Ä>© <ߣrýßÖBß>:>oãä_Z_åãðö*rªãuZ>îÃÖu<,@_ãîC.ÖbãOu ß:ö_B håßÖ~är>£Ü~ßÃZÃ/*a/£Ö.oä@A,>,ß_+~/b~~ÜöüÄ¢Üý>a<ð£+CöB:|ÄÃöU|<ÄBßuuuvuBä|b<ðh£o+>|:ãAĪvhÃ¥*ä£_+Oã¢:ZzBß+:h:äOzÄßä >r¢~ß,O/A£Z||ðäz:ö~ðCUz_ßhßußa|åöO©_Ab£åh£ÜÄo~åözaväv.rvÃ¥bß©|ü¢ab.ª,U v,ZÃß<:ª,rUåîßÖĪ_ü.©üvü OaÖ¢Öü>ÃýÖä|~¢ÃßZ©~.©bãv+ßb.ߣZhz+ª<Ãü£UÖrðbÃ¥hAðUª+z©|£Co>vöîbä :ÃUuaUC A ©zOÄÃ*© ~~oîßðý>bZ*Ã¥Oý+hArÜî£b>@öðª ýah_rr ðrOÄuÄCä_>ðbÃ>~Ö©_Cî. .b~/ö*Ã¥A/OðÃv|ÃöãÖo¢üaaäðß.UbÖhaUaAÃÄ*ÃÃœ*.AÄ>hö¢|/B¢Ö£Ü~ßba,¢:öUÄð~,rÃZÃ|hv,C.v¢ß*öª©@U~U/ð£* oÃ¥:Ãœa~__.ßh*bª,+<,ß+~v@ ~r©ahÜߣ,uý+ð .ÃBvðC:z:£Oäü@äüÖå+Crßî_.£a>üãªA:oýBZz*,ävÖAßvßZÃœ.A¢a+zÃ:bvððCü£ãaÖaÖA|a|r|îãÃ/,ußv_hÜßo:ðý<Ä aðîbZãýÃß öAbßß.îâåÃhä/@brª|ßbbZUr£ýÜ/~,<ýð~BÄý AAväã¢åb:_u¢+OýZåÖã~o.hZA_ÃœUrOzUvà a~ð~|+Ãœ|ü+¢.<£Ö~Ä+ߣ orOð.©Ã>î.ÖühB+Av hÄuîUäCZ~:~~Ã¥/Aaã,ãoÄbbböå/ |+|:bÃðBvözßCBuî @zöhUýZ¢o.£hãZüÃüaBAhãÖCî~/.ãbªäOÃ.|>rz,vßåvZü.£ ChBhäOÃ_ßu©_äÜCßßüCä.OÜÃÄÖzü>ßöoÃœBÄî@Äz|Ãa©ý£ã.bÃœ:>azßðö+,/CäÃbýv>ÖÜbrZb£_h,>ßAä*vOb öãýoÃœ_hZrÃaü©Ähb¢ü|o.+Ö©_/~*|Ö~_a© ©Ü¢z©hÃœ/ãC.ãbîÜÄZ¢åî~b ª öÜÃ:C<üa+:ÃœAZðhÖÖ~:aüÄ+býã:ßÃ.<Ä¢Ääv£oß<Ã: ö.ß|ãU¢îäýÄr£~öOå©ÜßAz ©rv>B.£_uUAC~îB¢|b@¢Äî@r©Äýîßü£Z£bo*|Ö_ö¢<*vÃœArÃ@©ªÜÃbß+/ÖÃUªÖ<>ÖÃbß@b/v,|~r*ÄCÜÜîÖü<,©<ðOA:ÃU*äaä@bü+u¢aâö<Ãý>a+b~î aý_huÄr>Obß,ÃZÃha|ýzoZßÜÜ£ä>ãoü+<Ã¥:UßuåÜ/@__hBaÄÖå*@~ªOÄoÖ+|ããAB/ðßß> Ü©uä¢ÃZ£Ã£oÖüß:ßåÄÖBAä ðÃÃ¥@/ÄOrÃ¥U_|ä+Ã_üäCÃîÃðÃu££bBb,Zð*ZhÖuãð@:Z>.Uª.hoÃœuðo.UßrzßbhBßäU¢@bªßÖArO:..vüAuîözo+Öðbz_îAä+*<£oªB*_*arÃýöß,ð*Öãuo>Ãåå~aZväý ßCöÜB+öå*~Zü£Öýuðª_vaoOüüZÃ> vBor,rãå¢ãýßüå|rÄuCªÜ*£zÄ £ðüßߢ:_b>hÃœv|h*ZÃœ|:AvªÜUbªî_î/ãv¢üCîzbu~~v/ðßbaªa+|Ãœ ªhéöÜýäöã.*ür*+ÄB ÃCßÃüz:>:îZÃöäbîÃb+rßäbÃÃ>ärv/uoOaäAåöý<@*>ã+Ã.Ch/£©ÃC@Ã¥ACaðýAaÃœbå©:*U ýBÖbßß|.b>ýýÜö>bvC>ouuäâ+ãÃa~Cý<ã©Bé¢@Üßãza>îîouªCÃ¥Cßßåßöh+hz~zz£îÜÃöz*bZoîh Bߣ/î:_ývUbzýA@_/¢z*ubuÄrª:¢ðßrhÖåÜîãÄ,hÃœ +©Ã/üZß@ä_ªÜCA~öUzýîb:bö_CvªÃÃUo|îÃ|aÖ£Oß.ö©_üÜÖ î:BhÃ¥vî¢ÖåãaußhZ/ur Að>©ßB£@+,~*@ZßvoAãÜöUäßo~~ß+CÖ|.Zýü/ ܪ,U*Z<>ðAäh._|_b©¢ä£>bÃaZãßBaßZÃÃîb,Äî~ £©|öuÄraÃ¥uz*|©Z_Ãub:Ã.ðu|.Ä¢>o+Ãœb@aã>ÖÜ~¢ÜuÃœ>ðoaª@Ã<ßC¢UÖ +ßý|>Äã@zb©/Aö:>ßîrhÄ¢vÄAý:îüðåÖbo+ü~bZ>öü¢B O<ÖA zÃAª¢Ã_Z_>vö|,oî*ä~<îÖ¢ÃrÃao+ã>Ä䢪oÃ<_¢.Z>Ãœuý.Uö.ߢo_.|C*.Ã¥Ã,ðÃý|böÜaOÃ¥züb¢Uã:r ÃœuöAÃ¥_ rbb,b©åa+ßÜ©ð<:öuðzr:ßzvÃ¥u_öOü©CaÃÃÃößã£bbU/bßZäzZÃC~ Ã¥.ÖÜ>ãCðã.h©£OÖÃý.b©h,|äubðãîU_@ÃäzýOZa@huãz+oCB~ZzAðr<äå_/î.Zöo~oüz/ÖßhbhýOÄ|ãðß~bªö@ª<,:C¢äBBãv©ÄuOß©ã>OüÜrZhzÄ>bãU*ub|<ðü£ovüAoðAä¢ÃAîrBÖ|ßvBzßð*avªî/¢©ÃäÄr îýÖb+B/BÃœbðAU:@Öuã£ãUîöÜ_aªuöAoraÖä|brÄu*ß~ü*|£üCð+>rhÃåÄa<ªöÃaC/<. ý/ü*¢CA|hh£©ä_BÃ*ÄoÃãb¢|o+oU:î¢å©©A__aÃ¥BBOÖ|+ßÖ.~zÜÄ Ä<ðß.OUößBu@ÖaO>|o ãb~©zbO*¢Ö£ohî|öOCÃCã¢hZB@bªß|îAo/Ã¥vhᦙuߢOoöBªrba,ßb>rðZ~Ãœa~_bä¢ýÃAzß+>ýåaaöhh//,Ö_@rBö©*ãUããZ_ _ö.|îu£åão@î¢*ö:<+©ßAýCb©röU@uªðÖà _uzÃÃvªÄbövî~zjklÃœU.£>o+©_Z,,ÖAÄÃAhªO<Ãœu.vbB,z+ðuA@h@zÃbböahüuzzð|o_OÃaªaaa,/bü+O+o+UÃ¥O.*b_u:£vOöÖoãhßÃ@Aîh@zZªýý£ö~böbb+ÖrBoÃœo*Ãß:Ãîhu+¢£ Ö©Bb:_UßÄ©hý©C<+¢£*UÃÜÜ<ãðBä.|ü.ªÃZãh¢¢_+£bÃ:aÜ£äÖýZBªýåªr_/,+hÄÄ+öãÜÄvboîU_©*bß<ãZ@ ªaUª~Z¢ðü@@rÃœ<,åß|:åð.rîßB>.åüO>v¢Üü¢ ðü.UðÄa:~~.ªAÃb:.rß ß<ßuAa*AöoÄ/U<¢>:Ã<<î @îaZð::ßu|oZ@:OOrð+ý@/åü*_B|ýÄà /<üåuÖbÃ¥~CðhZüüOzZ<_BîßbAAð,*åã>ãßäA>zö:ä<.,>ÃÃ,|ðvÃuOu_©åärß©_ÃZ/Z:BðÃý A©¢<.~Z @ß/¢hv£ª *ª:Ö¢röÃBb/UZ£Uv+r@ªbvz|äZãCboo£äÜ|", - "AaßÃ_îaãh>ßöBBzýB.hý>üoýbÃ¥b_ßCãÃîð.©uBî@@hB@ouba£Ã<üÃ:Bbãß©£ãBð~ã+ã Z<Ä", - null, - "9999-12-31 23:59:59.997", - null, - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("100000000000000000", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("0.3273", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 2.718281828E+20, - 2.718281828E-20, - 893569276, - -1, - -7764, - 184, - 0, - array(("A82F7923662A496625B3CD58E906DD15019C700D5F48E2AD60858A9437AC118D0E99EFA02BAC0C52A44EB1940E8BEAAC3617AB238573055F4CCBC2E19FB52F78A13F494F173CE9548F1E6911974E9FD59ADE5D1F01EE089B948F545FE92BB2EF1E38F3CE419B95FA2D56936609F4C8FE2CED46C0571077B237AEBB87E8896B646B7AF35E5BD193FF4963F1AA5BA191A0C75533FBE5F2970EC1409693E00D11A4EB2EFA8F0069A35A5A4677F41ECC56961D1BBF92566F7F79E3E59D1A3A001F3B"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(("304F1D1447944F1CE70A2A62C02D5162E8BC9EBA4D9CA036FA24DC9C61E6F40BC0D00E85A45BE19CC2E44C26694EE3BB0A0CE814DBEFA194AFE71922B7B2BA01151FA2F01FCBBE8DDA01F8694F7ACCAC41219155FDDF2FD12F79D6BC41BFE50F2A4B104AACF39B3F4E5B39D9F63845351A6DE09520650336EFD0C1A6F4014B1B1CE83F036A81004E865207A2A555DAF634A1A1D4DE4FEEC448D95BDB32F54A4C0F1EBD0DF941CB996C920FCE5E609199F6CA71535F773CCCFA7ABB902A001F3B"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - "ÖbAüßÃ>ð>aÜãUAîbrýªr@ã*b~ãÃ|îZuä/>b/hüübAåÄð~/ãZCጚUO*îÃü+ööðaußð,Öü:öuA_o£C.h©AB £å©ÖÄAÃß >Uö><ðã+ o~ßÖÜ¢a.bߢZßBðCßrÄ©åu<+,©*b¢¢ð©Ö*Ã¥>_îBbÃzüîoAÃhA,©ÄCÃ/ðC/*Bvüß.äü@/u.~îC£ö~v|r*ýuAuür_bðü,B_ýÜÜ>_Ar@UðªßÖª:Öã©_uýCbð|:£.:@Ua<*CÃ¥obÄ@aãÃZ./rªb bzäå|åÄãöB,Ãœbé~ .<~~Öü", - "Öb©Aub>üa.£öÄbo<äãOäý,:b+©åv~oA|az¢+u.v,b,o~Ãåðª*vªA£üî*ª¢©_Ou+ã+bð,/ÃœU/~ßߪUÃ¥*<Övî |ýðA,ZܪÃÃrßazrªö@O~aO:ãozBÃvOßîzbv,oåýoa£Ãä~üªð©Ã@UB ,_bäBOA*|ðz@üAß:b@a|+<ªvrÄOb@£_ßüÃåãbaîýüZhðv:£åãZ:ÄbÖöîðß©,ýüßî,>_uhý,b¢Aªb ÃýÃbZuUrãÄrÜýîv >ýaC壢Oäz:ß+b+AvU|öö+ðýrߣßA<ßåãäãb/¢/bðü+î", - "äð>vboîU_©*bß<ãZ@ ªaUª~Z¢ðü@@rÃœ<,åß|:åð.rîßB>.åüO>v¢Üü¢ ðü.UðÄa:~~.ªAÃb:.rß ß<ßuAa*AöoÄ/U<¢>:Ã<<î @îaZð::ßu|oZ@:OOrð+ý@/åü*_B|ýÄà /<üåuÖbÃ¥~CðhZüüOzZ<_BîßbAAð,*åã>ãßäA>zö:ä<.,>ÃÃ,|ðvÃuOu_©åärß©_ÃZ/Z:BðÃý A©¢<.~Z @ß/¢hv£ª *ª:Ö¢röÃBb/UZ£Uv+r@ªbvz|äZãCboo£äÜ|b_|,<", - "AaßÃ_îaãh>ßöBBzýB.hý>üoýbÃ¥b_ßCãÃîð.©uBî@@hB@ouba£Ã<üÃ:Bbãß©£ãBð~ã+ã Z<Ä_oh@©î/ /+£*rÃ¥uUÃ¥+.|*ÜÃ,,<>ª@ßß+ühU>bÃ_avÃ¥,h/~+üo>Öüåð*Ãœ<ÃCO,ßߢ_Z+b¢ÄrOß.Z<,_ößC@:AAä~B@Ã.C +|b|_£üüU<ä>,C:b<ÄZbÃBÃöÃu¢*ö~..Ãö*vvÄÃoãA+Uýäý|aðÄ©ä,<Üßðaßbv/A ÖbüÃBýÄbUrßOA/ AßCoß, O¢ ªÖA£îbÃ:ðUßAåðöaª¢r£ªÖ<©ÜUhvuö.ä**>|.ð.ßrürC*ý.ãÜobzåßÜaÃ", - "ð.<ü_/îo_î¢Zöüß/üC,zuu¢+O|a@ýÃ>äÜb<,Ãa£Ã:ßåöoäB*b*ßåZ ¢ÄßCÜý.+Oß.Ö>ä<.Äö:Ää.*©u :©:¢ðª|:/ܪªU©vb¢b@,+uä©£ã.~:u+Cî:@Öî_äßOb.ÄßBÄÜ:ßÜZoßöußßýBrýýu,<Äßo@obrÃrzßoä|h¢h©/Büb>ZöC|ܪObOz +ð¢ð¢Äb/¢£/£ö.ö|äzßz>£Zä|b>.CaUªã_ruarbÄa_:ãäZOhãA Ö,ýßÄ¢Ö*|r©zª,ßß.uÄ|©vBzür/aZÃ¥+|bÃ>uãÄ<ªUã<@ Uä:b<ÜîC<£ÜßÃUaö_:>boýoãÃÃÄCOOÃœv~,£ªåå£ãßßÄßbýåÃÄ,:> AZ:/r r/ÖÜuÃ@rZª,b©ß£v*Z*ÜÃäý,ÃœÃvð_BOO©|:,bA:~£r.u,C+vr ßvUîvãb:ö~vÃœoabvðCöÃbü¢öh,ÄBö© ,|ª*ßoÄbªz__Ã¥zäA,hhOUz>vã£zÜåhü>vväu_©UBrA*,>.âv*zöß>bBb >oäör<*AZ¢:£>ÃÃ¥büuüCÃœ/î©>@ßaÄÜOÃœoroBuUßåAZ~ßßÄ.BBZZß<î£UööU*£u¢bãaßhuÃo¢Ã>C~ývðß/AãZÃœ:Ä_A..+ZBAZ£>üßîZÃ¥,h©BýÜbUÃa/ðª+*Z+zvBßZÃœ*ª:ßð£OBC* >@öoÃœ,Ã¥@Ãœ:ðuOa~>o¢|.>/Að£vâUüb ÜÜärßß, < ,üýbA~ðoa:|öãb>ÄUZß+~/,o+Ã*uuðoAbð|uBÄböªÜßößÄÜZîªUAª~,¢£/_<©U A©A.Cövª_rãÜߪ/+©îb ªÃh/<@ö~©OªZ+:ßZÖz£*¢aÃãÖß~ArbÃ¥|ªîÄ*_~ð~aÃö*ª|.*ßoª*vÄĪýZéðî,ªzAÃ:Ö:ÖO+b*öb*UO,UrA:@zÃ¥rB+UîU£BZ b|äüª+..üO+Ãœ|£b>vÃÄ*hÜÄßU©b>vh.v£ÄZ© /£ã¢A_C@/@ÃO.ýü+öOZãbÃðZîoãÜBCã*+OãAäÃCuªZßh*zäh/åÃ@aßvÄÄ|ÄåÄ . Ã+ßUoö@ubbUÖðßðªZÃ*u.z*|îCzzau@vå¢ý<+<:ö|>ጛC~aýOî+aU A¢rrä@ýå:+ðB©Äääöä.ý. .©î£@vÃ~bývZüäoZª/~¢*_Ãaîzh ü@ªvðÃu_ZuýÃOäzöã,Öýãvü.ÜýäüåÃuh+ª_,ý*v£¢ý,/ä:bÃœ|¢,BzßrÖ ÃZC~bö>ßÄßöbabüäZ©zCb+ĪuOÃå£ß,*_Ãî¢|©~vý+£©hÄ||.uaoå¢Z |>+Ã¥vªoÃ¥.~_vO*z¢BAh©ÄA<@¢C:/ßU<î:~bbzÃa+uÖv_OZ+bî,bä>ªª_AãvhoÄbOC¢ßªZvz,AÖ:üÜð* Ö,@.Oª.ãü@a:/Ã¥u>Ãb/_<ÜÜ£BÃãÃo£ÖC>ð,Ãœr£a>rü¢ÜUãöh_övÃ<ßãüªå >ÜÃà Öîü@>:zAbö @hý,UÃÃru,b¢Ãb.oz @Ä@vza*Ã¥/£B£u¢_< ߪ>BÖB<<ðÄZv/bZýÄ_ußUãýb*zAC|o.buÖ~_ßUÖªAß+zbªý~ߣ@bz£bý¢:Ã:b+îÜZrA©<Ão<ᦚ/:ÃüÃz/zî¢ Ö|êð+>z_£îªaä~ Bb>bZÄå¢@ö<ýh¢býhö/:ýåßÜüoî~ð:b/C rUÄbᛙB©Z+ãovß||î,Üößãå:zo_ª.~©b£oaÃ>u/öü,Ã_©a£Ö*îOb*,+,ãª@:r>UÃÃÜ@CßäbÃößü.o ßZ@ßU@.¢ÄUÜ¢ãåÄüÖ@ð>ävo<îßaäÖ©ð_Oãß, bC<îÃbAð.ð@>b*@Ã¥.@ßÃB>ðÃZä:ÃB|ßhZü..ÄðhzzhbCãà *vðß©ðܪaOܢߢ*ãäB,rCöBO>+>,ö.ßä|_B.oÖhu>*,UözzÃüzua:@r,OÖ Z.,©~+ðbZ@ð©BüoÃÜ_ãUObß.¢rß.A Ãu..äßC©ßäAUaOA£CbZß/ä @huªüb,~>AößÃv©C*~CÃýröb¢,ýBÃhrîãîåZ¢Oh¢.Z@ub* +/öðU C*zOoý~bZä+ßbüß+,bB@*ü||hZuÖ<ßðîª,bhOßýäýÜÄ©ÄZ*ߣÃ<ßã©ð~Ö.Öu,äߣUä*>î>C©bu@|Uåããð,CÃœ Cbð<>,ÃœbÃœAuO>äÖÃh.|Ar@ÜãßÜ+£îU~h>ßZAÄ:oü+Uýöü*ä~A,AC¢Bvü~bð@©äðU/bÃÃ|Ãa©Uª£ü_ãä:ðߢ>u<@,@ /¢_£z> Ã>å©oaz+*äA/z/Ãu:~äU.Aü|ZoUZ,hªZo¢ß*üßÜÜzCäª<örîÃ.Bb/UîÄ_ +ýÜaýrvÃZâ,U£ßBðzo*b*ü*~ýb|äîÃBߣ/Ãœazh©*Ö£@vzo/U* ª_OUaA./aoÄä,ö@|ªuAb¢O>a Ä/bvߢz@|h|¢,.Ö~äC>£åU~ÃÃ_rrvÃ/boîzª*oO_,*ãßüðußZ<Öbý+::ä*_Z.BvåÄ>:>BoUÄðüU_.ÖÖß ,ðÜö+bß:ãÃýªîhZböC<:ÃÄCUACACîbB~Ãüao>ö :©AU.UBAãö::£Z ªÃß_îvAÃ_ߣ.åöCC~aÃœaßÃ:Äýöbro:~:z.+z*öbb/ý<:ÜãuBÄãä/äv@îUB_üOß@BuOC<~ßÜ¢üÄ~ßraöhÄý< ~hªAÃ¥:bv.bîª:,U~ÃÖ@Ã¥bub~b~CÖ>uåßý*ßÜ+.ö<ªv*©:*oÖßBaãÄvvîoa£o,h|a~zÃ+Ã¥<ßýZÃÃðh,CÃ/*ö,ubCÃbîª*ÜÜB:Ä*Bh£ßå*za_a@*ÖÜåîÃAroÃœ_rüZüå/u>ªZußzüB¢<ªBr@BrÃoã:äbbv_||ö©>î:åßA,UCü:ðßB|ªb~ABÃœ|ßbü+:|a©uÖA.C|hu.<ÄüªO+_Aö>©<ý_ãîöB.î/|ãu/ÃÄî,ýý<ão_,ßö¢©. @b*rUuO@O<ÄzýîäÃo:©££ü>r¢ßý/Bý:Ã>Äåß>Ö_O¢_bßOaÜ©_|h|.uÖ.OAüßåäbãðüOðÖoý/b£+Ã¥ArZÃÄ/@uÖÃbvö<¢<ÃœÃZÃœh©brz.BC£Ã_¢ã/¢C¢ ðä£ãAAC,v/_¢Ã UörobA~>Ã¥oåßCÃ@oßÃÄÖüýäZ£abîo£|AAu+îu:b~Ã/ý<üUð,Ã¥B£ãðö*UBð@bö©*uªäÖî*¢ð ¢Ca_a+z.b©AbaÄBBäCßzöA£bBÜåCzOAÃ¥bÄãb+/Ãœ|a+ßýß/|öª ý+aU,Oð£å/@+:©©<ãBbß zÖîüO©äãz©hÄ_*b¢ð*ý<é.br<à Ö|båã|hÖ:.ªÄª+ößäZO@üB*©<Ä, ä~ßuöO~ãObZ.*bÄå|Ã/ZbörU:Z_Z*UOª/rbðª+Ö/ßB£ÜaÖOî+ªÄå Ãœaî|rîÜv¢z~üzB_ý+ªß/*©uãvuªäOü ~@ððBÃ*Ãu©A©ª||Ã>~ßhað/üBßbzüZîb£ýa/,O/UAð|Özý¢vÃ>CZ+ã ©bößzÃbÄðÜöA¢|h zh~BB*ãBoᦔb.Aüh¢ü©_öåäCÖa>ßZãCÖ¢*ªÄa~üß.ö+£Zo|OßÃ*b+~ößão+o|Ua_,r/b/UCAýãCa>uãª: ý_壣ªåÖ£ß_Ã¥+öÜjklÃ,C~A~Ã¥b/|oBý_|Oð~ |¢ZAýÜrÖO>üÄvzÃ¥a__ãuaC_£ßBb*bª.Aªð:bz @A©ab.©vUÃœC@a¢B+ /b¢uåÃäÃ|BðC ããÃuAß|ýbCý|äZU_~Üöuªaü©C£ ß > Öoîðvßߪzß*,ö£©a,ªoýzýåÃßåaB+:ã|ßu.ü*üÄßO£.U¢aA:¢ðrO>aÃœvoC,ßÃuãbÃßo.vAb£/rh*:ßbß*b<£b+B>rr©åÃÃ_<.ª £_<ý/ðßb:ü:bÃAv~Z_Ö,Ãœ|<__vo|.Ãœ~©/hãZäbbÄuz¢@Öhöã:ovö£r_Bbö:OÖv£©,Ü¢~OýÄu*£ÃAãßZZ*£ ¢U>îo>ÜÃ|<ßbãb¢ÃüÃb᜛Bãߪ a|Oã.¢+>~+öÃä äãÖu.ãýuCz~väÃýh©b|oÖC,£,/<_Azaüu<ÜÜ ¢,Ö ß:ävoÜÃ@/ÄåOÃ|Ö:Cãü+:OßZ¢A,îrßb©ßãü©_+u©ÜA~ªhÃœ_îA uÃœ/,墣|¢C,Ãœz£©r,ä~Bðêz~/~rhã<<åå~vU.ö*aüÖCZaÄöðö,à ö.A~Ä¢vr:ßoö:BBCª©BuÖ,Ou*ãÖöüözß+/ö>,AA©:.ÃÖOAAbbýª.A+Ãî, ª¢ªo örrb h+b:£/Ö ©.bzß©åý îßýÃABb.,bzBÃœ_ýýÃöÃðoÖ~ª,uüvzãöZ ¢z,b¢BvýAv+:Uîåb BÄObOhr+zßhßC|CaÖou~öbä/ðC>roh BuýÃuBZ/or© zhOîÃv/<ã<+bäÖhb£ýuo:A~hßîð<Äb,vÃ_zÃrßöÄ£~/ÖªÃ**h,@u/oãBªÜå+@aA~bCzz/,ããA@>äªã_h_~ z|oß|Ärh/ð~O£|ä©<+AîÃ_©Oð£ü>ÃœuÖßö©ÄaðZêUv,ð: äzÃObÖ@Ã~ÜîýîðÖÄ+uoÖUüããÖr£/©å_£rUÃœvðBªÜvUÖî|ÖoÄoß_/ã+ß/䪩|u¢Ä*/ÃÖ.z.ã<@* ý Ca./+C© Cßã/*,z.b|£@.<äaÖ,Ãýbî>AßCÃ~UCã@AazåðýüA@b<ßÃ:î£o.Ãü+©vuZߢU£ÃOöÄC>,oüBÃœ/Ä*>h|aUªãä,ß @ßZ_håðÃå üAßA>:ýoß* zA|o_î,o|ãr:Oü@uª*,Böu*ß:ÖüÖhãê:arßÄ+~ý__z>Ãœ~¢büU+ß.U+B©ÖÃßð£aÄhªýOÃœÃ>uÖ,|.îãAÃ¥CîÜî:Ã¥rr¢BhüÃßbî@ª+O~:ZUÃobaÃürßÄßÃAäCUU,ß_©ª@_ö*£åZ<äZ£ª_Ozßr>£îzväBAb.+CÜßð¢£r¢C£bbî*ä_A.zöð:rZåÜZv©ä ª<ð*b<ÃýîüÃÃ_:~ýö. bªÃüÃh_AÃ.uAüýö¢ZÃœo >rîðÃÃbabª<ª|ª@ Zbªzãð>*/ß ß,Uba|_AªÜýa:rÖß|o/ÄÃåO¢UZUbî+Orªð~+~ãCü+Ä<îZ/äåC>ÃýýB:ü㢩|a/Cuªß,_ª,uÃA<*ý.Z ß.+ö.©rr@<ª@öo_öð~UZ>b vÃZüö@Ã<|_zäh£<Ä_.ªåbýäv|<@.,OaßÖBBrCÄU©Üýbü£z.Z/åÃäåovväa.:ýzB,_ßr£:ü_¢@*Äo.ðC Öb¢©hArã Ä.AA@öb©v@+vö~üªr~Ö*ü @ã_.ß,|:ª+zhvC~>ãÜBãªöBO©©oUh~öv._©ÜrövZrð/îÜã>åÜZåÜ h_Ãß|ð+Z,:CAb:bo.ß~ÃÖ£ªbr,ãoZ:äÃOðvýüü£aîýß:zäÄ+/Ä+>zb~>ö¢¢oBraöª.ähObr:o|ãÖßhÖ*Ã:Ã|b*,C*ÖA* <ðÃýäãîý~r*@î*Ãuã©ßäZ<¢:/äUAÃÃœ.AãC©£hÜüZã*BU,boa£î@Bu>baZU_a~Ã¥vßÜ Ãß©BbbÄ@að++b,ÃU+ßv ß*b©*Ã¥UüÄüBz/_/@ ÃaÃOßÖßAãbîaA îßýßüBäðA/ÃB£*<©ÖA_A¢ ߣ£¢å/Z~aoBý:Ã/Ö<.<ßýZ:ÃB<|Ozä:aÃ|ý*o r/£//|ý©åÜua|C_ß*@Oî@.äBvhÄBZîÃb~huAo_äîÃ/~ßöZz :Ã¥r£ß©vObãbhüß+¢.Ãrî /<¢+_bãbªÃßäî.öU,ßîbÃö>,r>üuob|~Czß_ÜßüðuöéÃÃÃCbB|@~Üü+ãýî_ÃC @ýAZÃ¥ A>o>Ö|+£¢ä£Ö/ý+uzr*Ãœ+ª_ðZ壪ܩCîÖå~hªAߣCÃuhUÃœZhüzÃãöCö.üUuåðÜåöÃÖý_.U¢v+ã£,,©>~@£:üZö¢öoO+ßöÄ*ãv,>ÜÖÄÄÃ/:vÃ¥|,ý:ªu£/¢CröC::*Ü©B,ãbåý,<ßð,££av:@ðhä~oÖ/ÃœCCh_Avü¢£ßð*|:BözA|b@rÜ¢<.zß>o>hhbb/ð¢ýüAzßr@.£A©A~AAÃœbv*:ãÖ|Ã¥+öÜÜhB,|+aä*ö,|ð:b+îªB~ UoUvÄhB,|¢ðã/Ãv_¢B£|O©ßÃÃ/aÃœ+©vv*/_ýãhäUî:ß <Ä>Äßßv@/ao| ||ZC aî*£>ýOZÄ>U.£Äå>uýöZý|,Öß Zý£Z~~C~/åüßu>_@CÃœvA*/©ã*~ ß :@ªßoÄO¢.î©ZbZa@.Ö/ÖýO u,öbuüOÃ<äbo@hvÃZzhvü@Ouz>ÄbÃœ/*hð:ßÃå>>ßà rã~ îub |ßÜß*~£z*¢ýa©av Äßü::ð¢ÃÃœ_~+o ÃC.Ä>Äzãý,*A/¢ÜÄ|BÃÄßBUªä¢rbaßývoaöåªö>|Aäß~C/>ýöbý>Ã:_~äßäå@vãO,bÃ¥.ÃîBª~C©ðZ©|Ã<*aßB¢bÖA¢rA_BZbý+î|ܪzr /ÖzoÃ¥+î@Oð*îZ@öbðå£:/Ã¥Aðbü/~ýðäAß<£åÖ~oUZ@+oöhÃäö>äãÃÃ¥_Ã,Cðö @vbzbßäb Ã,oð@äüîv|¢>bh+B~Bh*Brð.Ãœ ã+|Ö @CC@¢bZöv|öZýîhÄb©UðrÃ>o+b¢BB+Zßöéßß:~ãa~öhðð>¢ªÖÄ ßCã¢üo~:räîvh zU ðªb|ZOC_A:CC:<ª|£bAo¢.UäÄßåaÖ~/OUb>|Ãœ,vb¢Ã,ãÃ", - "ðZoühÃ~äªð¢,åªÃÃð@>*Aª+|ÄäZ*Aä¢Ä|_a.*:b_:¢öäÜårAýÄîo@üüzðªvZ.UãÃzîß ~ *::öî,£@Üß©v*zü:rª~Ä<ÃrÃœhußu|Ã¥bä/£+/Ä|b|£.ß/ªhÃ¥@ÜãÃuÃåãªv̧̻/öîÖßßÄö@Ö£@AbbðÜoý v/ÄräC|ßÖOaÖ+ür:ð©ß O~UBAÃÄßÜÃ<@ß**ýUÃî©UîO*ÃîobäªaB*©oÃrªoO,ÖAbÃ<ðAªruuÃœhbv,î+h_£üo©+Ohr£ß¢", - "äB*£:ZhÖvOzãüðv>ßbðO£ß>üðð|rü**ß,@Ãœaãår/><£öuCb.|<ß*h©A bbüßüu:<Äößb_bÄî+BUboo_ä.o£>_aÃÄ>:öÜî,B,ß~üðÄUb~bCðýý¢üAüðv|.zCãA£Z@ZbZßý©©bv@Au.Ãœ.ãåý.:öOüU_ý>:uvêßb", - "|AýaAãC:,|bÖv*ÃœC~£ÄU££h ~h@@Bu.hÖß©¢,bb,aAC~.BãýãÜ.ÃZÃAðå£/Ã¥zª+ooöý:©oÄ>~ uª_|ð>z_©zß©ãAßÖÃãOb.©ö..+ Ãð~Üðb*ßoýoÄ/äZBîöÄzoßýUZ@Övª~hA|,öÜZüåUã>ßrî_Äö.Büä<_ÃÖU<î,ýUu~ähobð>ßöb<<£aÃœ Ãßßã>¢v|A©r@ßb|Ö>£Ü£*©ß:ð|rbä.<~z|/Öz>îvb¢öü£ÃßBÃ<ßoU/ß.£,Ã:huîrb_ªBbß/aÃ@|ÃÃßßrÖB +~z åÖaCðÖ/CäÃÃðCCÄAðbß.ÃäbÃß~_ÄAî@¢Ãrà b©@UZ,ÄýýîÄbAb, ÖåªaÃzz rÃBörCOî,,ýv_~C*hBÄîå<©ü@räãªÄ£Ã+OA|ZÜÜäÃßãbhÃœOÃœv媪,:Ãbh/ýv*z©ÃUÃ¥*_|b+zãObÃœaÃ¥,.©b ãACüÃz>o*ߪ,>aîßU/£rr>zÃœu,öbUA~~vüãÃhî¢ubîA@Uß|ÃœBbhýaäh Ã*Ö */îzraC@Orà ß.hz<ß@/ßOöOî~|.ã>üÜoZå¢ýÄzUÃAªî+ßrªÃãäöãz@o>Ã.¢/ÄéUðO>_>zÖ©£bßzðb_Ã¥__~u@©~bÃã©:+CßÜÜýîhÃÃ_üãîz:Är£ßBîß*/*ÃÃ¥Uv@ußrvüý_hb£ÃßäAUÃÜv~ü>aä*ªßUä.z,aü_vªCª¢îZZuOßb£r:~C_BC:éÄ>vöî/,_ý+uvð©£üO*_<+Ã_Ob.ãräîäb|ö.B*ö+ãã¢ßÄ.ö_î:|ðÜîrZaüBî<ªO©b@oÃüv_ªUüãÖäãÖCåöÃZýbo+£,hUbîU£öÖrvU|¢ðöÃßu|_oh ܪ£:+b/ý ä.+Ob:ýßB£>ä zß*zvößBuaðvZ~r~ÃÖvðuÖ>~>ÃÃœ|Öb>ßîv*¢AÃœOA@U¢./ðÜäßr.Ã¥UöB_ Öß@OU o@B*AU~_î~U,ýhBh|üîÜ_£ª~üoßzZü£ãZî<î*~êu©>zý|C.uªU|v:Ãœ@î>,Ã¥/vvðö,Bb£rÜýuz_Ã¥hzª.A,b©ÃB~ßCöýäOäoö.~*ª:AÖAßU üîî+ªîüãbO~rãßvüÄuðß*hª:|öaüa¢bã<Öªðu~,üuABÜß+>ÃœObbßî:üßz/ᜆaß/CÖÄBr*ÃCßü~ Aã¢r/uÜð~UðAÜߪÜÜÃbrr©£b£.|ß ðýöä£Ü<ߢr©uîääßÃz.Öü*bÃ¥,C*O|_A£üª£výohahüo_.üzßÜUüBob*~îb<@.ZUbOavZrªß@O.ªar_<.ß_, |ð>Ã¥Ub+öê~ãÃb*,ÃaªO.:ðÃ:*,*|bÄååð@v>,bhUåß|ãÜZCrBÄßðuîCýÜ.å©a+¢îÃöß|bîzö<ö_¢+îÃbvo<*îü©a.ZÃüor©hã<|CO.ä:v+<¢äÃß,ãîZýÃvßoßo*ßýÄA/ÄÜãªÄbZöb@,£ð£äu*b,ªð©.|©.zAbb:| bîBîuÃܪ*öuÃ¥huoZh:ðz.@A+vðzÖvåýbãzÃ¥ÃÃœzC+ªý~ÃOÃ_OoÃðª_Uð@äâz,A*:öoAß*.@ß _Ä.ðzO~+C~_ö+>~,*äbÃßý|£CÃœA£ÄãÖuCZa_>ª|_bðv/ß>|£:.ý_:C@|hÜß_oð©ZãzãB>bîÖz@£åÄhãªr+rZãrÖýÖ:.ÃÃœa,hvü~>©:o.@uuö<¢ãýðr ßÄ+/¢|ã*b¢OüzC@¢Ã_ öÖußÖ¢ýß@hª.ß~©~.Ã¥_öößCBÃœ*vðßüýB:ªv~~ðªß£Ö u/u+,¢@BÖr~,C¢Ca.+bý_h@ör Baî£_ýäZZäUîärhü:îr:|ÖÜbðvOb_@¢<ßÃÃîªbýrh||bvÖ/ÄßÃZßßÖÜUÖ.ªý>uý:ÜöÜbBOzOOÃ¥,öb.u@ÜðîÃO¢ÄAð:hr<ýÃî|CZzý*¢¢.B¢ð*ßO rBu~zvvoUÃo¢ACÄOO>,öhð@.ZZzbýß©BZßa,ÃœCªZãhbUrý~z,:Ãä/urZ|ßZAöOo:bb*uCz,bîBBýäZrzÃaÃ_bߣbªÄãüß| £ýªoh¢+Obb£ü Cr.vªü*ö>ª,v.Zª,.ßzö.Z|+ãu,o,uÃ¥Ã*Öß~ª.£~©rOh_zaCÃ¥Bý/B rBîb,B+ZªAUz |AaåýOÃZrC/ß_rvZZu/£a|ÃÃ|a_r,ªhÃ¥uý:Bärýv+baýZ£:bÃ¥Z,îuu|/ßCÄvÃä>hßbÃœz©uåüî_ZU>*aöbhý*ÃœC/¢~ob..OÜ©U__vCbüð<@©å|zo£*Cî.*|br>äÃ@h:_ÄÜ¢aüßÖßýobäv/|bvý/bzZ* Ã< ÖU*:ÃœAüöBðzrß~ZðvvÃßÜöß:@Ä,U//z >Z<©>ÄBîUb*U ßðOhÖÖur~üß>ýCUîZb>Bhov Ö,uüêãaöýãor©©ä+*_vÄ_Ã*+/äZ,C.|ÃUBOva +ÃÃœAz/à .vÖrCz ö|ÖbÄö/A¢ßuÜ©b~zbÃ_hUA£b©B_äh+r:,îh¢@ðª/ZÖ.>rð©ZãüÃ@|oBðîÃ.¢.~îUªÜ+ÜîöÜCbCä>¢oäÖ/UAo©©åUßuÃß@ýaÄãoî/_Ã¥+îO|//ZªzÃã£OO_Ã~ðåhüÃrüªBCo£:v.r_Ã¥CýBu/öuðaüäßãoC~ü|,ðÃÃuü* höãuUbbh.üO~¢ACaý*ß ,<é/zhÃZOÃœaĪ.Zð_îßb©ãC üÜC/Ä+Ö<ßßÄrßÜðîzäUOOäA¢ßb//ývÄ¢~*<@Az|ßa@ðßÃbCÖüðÖߪöhBÖ>ª£ö >_BB¢_/*@öÄ:h_zãªav_*bü.Ußrh|Ã.+/oa¢brAÜäüÃözzU _åªrÃzî_äoBà Zßß@ Aü_©Ü,ý>©++ACö|Ã¥~©CrÃCv<ÖîC*/~oÃ¥.üZvÜåCurr¢@OãboC:Äh©z¢îÄbðåv£bBr<ªh©ZBÄ_îa><ÃZîãüuýäArB +|ÄüUö|h_zÃUî<ß*ý~Ãœzh/ýÜaAz/buOv>ßzÃ,Özü/*ö.ª©ö<,¢å//ßrö|ðör¢Cbzh/ÃCOBöUvßäß<|O BãA/*ÃᎦ*o<ßC v£ZoðßZz©£o¢ð.:ýÖªCÃœB£ö,ªA@ßð|a.zZ<ýýo ãÃ/bC¢ý@£/üävbahÄ åö~©C_ÃÖ*ZßOÄzvuÃœ.ÃßÄßýî+£ÄåÃ|äÜüðßð_,rr|£<ÄßBböÄ*ÄÃa@¢BüÃZ:ãßbß/brÃœ@ ©+r¢uOÖîUUbäzCßÃað/ |vÄv*/|b©äZä:¢..ZÃœ>,aab ABUrAr¢£ÖvUBö¢¢,©:bö<|COüv.Ä/A _u+uUO+A~B@Ã.C +|b|_£üüU<ä>,C:b<ÄZbÃBÃöÃu¢*ö~..Ãö*vvÄÃoãA+ü ýuÄ¢å ª>hªåäZ~>hýO* vß/ªääÜ£BêOb_oÃvÃœ_ähüÄÜbãÄßðäåzo£boßBÃœa|ü,/ýÃßÖ.ßår¢Uýäý|aðÄ©ä,<Üßðaßbv/A ÖbüÃBýÄbUrßOA/ AßCoß, O¢ ªÖA£îbÃ:ðUßAåðöaª¢r£ªÖ<©ÜUhvuö.ä**>|.ð.ßrürC*ý.ãÜobzåßÜaÃ", - "ðZoühÃ~äªð¢,åªÃÃð@>*Aª+|ÄäZ*Aä¢Ä|_a.*:b_:¢öäÜårAýÄîo@üüzðªvZ.UãÃzîß ~ *::öî,£@Üß©v*zü:rª~Ä<ÃrÃœhußu|Ã¥bä/£+/Ä|b|£.ß/ªhÃ¥@ÜãÃuÃåãªv̧̻/öîÖßßÄö@Ö£@AbbðÜoý v/ÄräC|ßÖOaÖ+ür:ð©ß O~UBAÃÄßÜÃ<@ß**ýUÃî©UîO*ÃîobäªaB*©oÃrªoO,ÖAbÃ<ðAªruuÃœhbv,î+h_£üo©+Ohr£ß¢_ÃZ>Ã¥B£Uüh:£,Ãœ uªåZZUa£ÖÜä,zzßßü.ª©bã|,ZvCO.rzÜîbü_z+@+_b_ÄaÄ öOz>UÃœCv+A>ýAhãu_v*U@~îUhüzä+~ðOýBÄbýßÜ~@ß+£:ZhÖvOzãüðv>ßbðO£ß>üðð|rü**ß,@Ãœaãår/><£öuCb.|<ß*h©A bbüßüu:<Äößb_bÄî+BUboo_ä.o£>_aÃÄ>:öÜî,B,ß~üðÄUb~bCðýý¢üAüðv|.zCãA£Z@ZbZßý©©bv@Au.Ãœ.ãåý.:öOüU_ý>:uvêßbÃœb£OýözüÄ.u ý@ÄîÜAUß|BbAvaOrãýã/.*a*rÃý:oý<|arýüÖ >£*ý@©ýªîýÜBhÃ~ጜ@+ÃbAß©|¢@Abau:>£@ä.vzB/ßOCå£ýüuAªh_v a*A>Ãaª .ÄaCäÜö+£az֪éÖCýC_Zßüîu @>+Ãœ~bã<Ãœ,Ã¥@COZ£öCU@ÄvÃZ+ÃîbbªÖBî@v/aÃ¥/uîCöväªäðv¢ãã©Ã¢Ußb,ãOß/O£Üª~îä£bÃA¢~z/.Üåüß.Öî/uîA¢ö<ªACÃÜ¢Býva.ýÜo£bAð+£¢öraA.à ,ÃåuÃ~bý>î@ß ܪü,.ýÖÜhäBUZä£ßC£+åÖ~~COjkl©|h.| ã*U¢ª/ý|UßÜý.Ã/o,Uoð|rAãrhß", - "Z.©ßBCouÖ:ÖuCCZîOªÄo£/¢uCr£az*üö/¢,î >äzU+ðão,*ßrörð ýäß:©îå~ößZuB.ÖAB£_CåßüãB:_vß+î¢.£v:>.Ä*îäÃBOðßýÄßÃãOv©äÜ~ßãü¢Ö|î©ß:hz@ÃðruaBå£O¢|_AÃb:oü.Ãð~Ã¥,vCoO@B+ã.výðýA©Öåð£r.BÄåãr©<Ī~BbßüîöÄhbvÄCÖÃh,:ª£ß~£ýzᆎhßz>O@ª>UuÃãä+ý+<ã<ö<£<Ã_:*öuðbvýrZÃAU, ðÖÄß|£©<:,Äß,Ãœzä<:£OäåZã+zZzO¢îübý©rý:åð.ß.BuUärÃ¥/v~~u:b<ä/U++zã>ÃÃ.CBäa|ÃÖb>:BÃœ|UAä:,|ZUzßZö|Ã/ðävbö¢©ãoýb.,u*Ã¥Oå£*.ÃÖbAüß@Ã@ CãO>üüAÄChß_hª,îbb<Äöã,aOBrbýßoB¢£ð~uÃ+îoo@u~rª<£bÃ:Uß+v/ozäÜýÃzýÄî>©ßÃ*Ã~_ßzO/ý>Ã.ðBÃãßZ©ob.>Co.uUäZCßã*+Ü©v~övà +b~ßuZäBrý££ðr<,+Bö|Ä,ð|~ÄîO_ÄÄå BCb~¢ýöaOîßrÄ:~OÃ¥rã OªühAu>+©|>ZÖzãb.ßÖBý__ä|o| ª.@/,¢@ÃœB:Ö ¢b>a©*UA©ðý.ro*ð©u+uî©ðüzhUu@hO©zOz:ßU£ Ãœ_ãßAßðÜðb©ua|ÃA Ã<ÃAAOB©~.ðOÃ/|AÃ¥hhbOZ /~b@/Z¢ª/<|O¢zÃ+ÖC/,ýªu£Brh.|üh.h+~/C~UZÃœzÖrä,.~,Ö bãö*ã+..Uorb: oAvÄzvoÃ¥hª©ãAÜÃß_AߣB|OaÖbÃoýh~vZäÄ*hÃ¥B>ooÄ ©ý*£Ã:¢oUßUªãUöU|aÃ¥aA,|ob £©öz>ðää ÖÃb/_@.A<¢C>îðãöhZÃZZ/O|,ßöuözzÖýîr@@~*>ª+b@üßÖÖB**£o~b _r©ÜChä*,a vZ¢.>aZZO+£oö¢@ÄåCoBð|zßÄ>ßO¢o,_ß+Ã~@|+äo,rÄãbÃhª/ýUuî,ÄýßÄýOC£AüB+|ööOzÖzð@+ªö©ÜÃ|roo åýüªÄä+©bÃoÃÖÃOO©U©>ðüä*+äAu>_:äßÜ<~ý <:+¢åßrärý~>îü<£.îC¢uãßoo>Zzîýrªr ÃœrCZBbo>£b¢a¢>+o*|:+B¢£u<+>åüuÜýz~Ov_CßA:ÃrÖ@oåßðâ£hãuh|åßÃ~v_.Z£ÃbzC¢uÖ/î+/îÄz>ÄüuªCä<*bÄ£ B:rOÃœ:Ã¥:ß<ßC,îÃðäÄ£O>uäZ ßoÃœuÄv,ýî£ýÄO£/ãä©uoBuuo_< b:ßä~îãöZBuBu/h_Ã¥zC¢,/z/ *bªaý©,vãüo+.¢/,Ã¥>Ozv,*@, o:éu>öã.ªav+ðZýÜîöâåÄOrª*ß,îðUýöCä~Z<äð|hvAZ¢/Aüz©@ab ÜÖ£ßvOä:ýAaßߢãÖAbÖbÖ/oðýîß/ªvÃýåÜb㢩ªÖÃÃZýZߣ,h|£/Uý~*ªÃß*,/ãÄzaöa_aC*:a+boo£Ov,Zb¢ZÃÃ¥rÃUîãv*vã|Ãvä£ãh:ZüU¢_O/b£býhßBavbßßÖr@r£Cuª<+zª_oª >v©|ßßrO+Cb<©ýßãA+UÜÜ~@z|öà <:ürÖÃÜßhvö+zaZýOhß,>Ö>ß*|_ðÃ*ýäCåÄðhãÄýuAÃÜ.ä:Öz_h/hzãÄöOãOhãbßvBU/z*ã .ão:bÃ+>Bܪrr|ÄßAýýüa@ã+üî>zªA:öÄßazh+£rß*b+Uä~Ä+/,z+äzÃ¥aÃœ,_.©£ Ãœo©/:hCzßðO¢h|ã>Cz*Ã¥<ýÃýß+OrA_äÖå|/bC>ý@îöb ©að£Ãb£üZ*>+ðîßÄå£ß|Boo¢. _O¢.ýß,O ß/@>üåå:@bbhuÃZ£UhäzU+@< Ã¥+ÃýÃ~aZ+häÃuÄ¢|*O <*ÜéîüßöCZC:,|~o+b,£ÃývCZßaßuÖårOaýC£ABÜÖqwty,Z£C:rZßÃÜ+>ª,BöaÄ+,ðv,~:> üªå¢ßOBÃ¥@*©Ö_~,>uª¢A£ýßÖ*ßÃBhÃ¥b©üäzOßh_z@>~ª*ößß@ÖOüã<ÃýÜhZA>aâvaßÃZuÃbbz@h.u,.üå|+ÄCßý+<Ã¥bvã*UzªuZ@¢Öãöý:rb,arhߪ|>bî*büoBãªuUrb ðîßîrýbrBZðý:©ö/ußÖUÄåU|:©hZ,uöåzÃvuAÄ+@ýÃUÖAO¢aÄoa::ãªA©u/@ãÜßä*b©Ü,Ä.>/uUA¢<_Zbh|uu<.ãªýÖ>Au~~.rÃo<Är~£ä¢rÄoO~U~_AoUb_ßr@~<Ã:rÃ¥<ß*hC*üaZÃ|*zh/ßov:©|.ohh+Zr¢Bð./zu ã:Cu~aÃ¥>>rhüÖ¢ Ãœ/Ãœ@a~båߣ@,+ÖaAÃ/+UÖbvößüZ©,BoCoC¢:r££b.> OB zªU:,@++|ÄoßÃbZ,<Ã<,uvC_ßbÃœ ãvÄBã>©©.ðâÃß_îz+üªÖaaªÃÄCA<ã Öb_zabýr+ªü|Aã£.ÃrU/CO|ßUC,C,¢rÄv+åÃUuîu,Ãœuhb/~ãbÃzv/ur|<Ãbå©äßÖ.Uz£üöO>zAb ö/Üß@|bà ðZbOzr>oa_@ü<ÃßhðåC>ßü.Oä¢büv+CßC.<üU<£Ã© Z<@+:Äü©ª©öO@üäåzÃo_î£/ÄãbUãå:U+ã<©ð£>o ý~Ãœ:äðî@B+Oö~h.£bß@.vö|*v<~*îåbCövÜî*ðîýbß@¢Üîª>h,UýZßîüUÃ_aýz:_,vvu Ö¢>C>ߢ£Ü©Ü+åýÜ*Azî©ÃýB*ߣß +~ü< ÃU©ö+ßbªvÃ+vb:>ÃO*ö*_£Ü@oZãazZrÃzbrÖB.©>äb.UÃbÖOz~OuACßA:hÃœBübZý_ÃüavÖ¢ÜÃ/>:u@ßB@©ÃÄBü©OvoZö¢|_ßß/ÄACÖð|ö._~¢ ßö*Ã|Z*|Ã¥|AÃœÃB:ÃhCåéßßÜýÜÖhÖÃ/b|ö* ªöav:u_oa aãBvv.Ãö£ah~Äß_rßOhörßÃhbrB¢u:U£Ö£Öðb>/a+¢Z OAuuÃU.*AbCrBörb bÄrÖ>ßbA¢ozÃC~~@,öbýååübî¢Ä__ãBZ,ÃZîb~ÃroOCBB¢î<@ÃÃaob/OÃhääC@>ßba*/Z©/ßUãßÄÜåüvÃuaähbh UBOöÄãB¢Ã Abv>C.ÃrA:äåaUAhªZzßB@à ÖCÃuBb©brUZa*ÖÖ+~AÄöýz~åã+A*©bð©>OÖýÄ@BüÜßÃÜüAßä_öUbßà /_ß+Äa ,oz¢@oaÖ£bðÃOr¢ßðß~OßÄßåäÖäz/îo,ü:,@<*@¢äÜZOhb|CßÖå_hã>Ã*/ÖÖzà Öî_ÜÄßB |zö b/¢ÃÃœrÖ<ÃaªUCöÃOA~Özo:_v~zãZÃ. UO@:vÄÃý||u¢ÃzZ£ÜCîZü_£o~Ã¥+o<*:ÖZ©*bª>/<*ªrÖöãßöZ£boßC*Ä_*Ã¥+ _@rC:*büubZÃ.ö+ Ã¥uÖB/äßZZöO ¢@U¢boßBý£<<Ãýbªß<,oý+£hCZî©Ãðß@Oà Ãߣ¢vhZ UÃv*:ßr>ß_a UÃ¥C>Ãœvh,bãüzý£+ßhäCaÄA.,äð:ä>/ýüo,bß©oãaaÃ¥v.Üö+ß/+*ü.ý¢u:Ä£äßvå£ãýoª|_hüaCaßÜý¢î|£/hbA|OÜä*ö@ürÃaª|ÜßÜuîu+özýß/ÖzäåÄoßåZz Ã+Ã¥+|C£*/o,Ãœ*£ä<ýB@Zzðh:Ã|OªÖOö~Zvߣ.o|£,h_ý:ßß@/C,b_îÄhBöý©*Ã¥~Uuß>bãrbã_åîO+oaîÜ©O¢Üäb+|£|:oÃ¥Z>_ZÜ£zÄ©Ub|_£a@Öððbî©Äö<*ÃC>r¢ª©£~,¢B,+~býð|r ,+bî¢@Bý¢ß~ß.îrr<ª:ååb¢å£_|<äÃÜ+vA Ö:Zýb|©üªbzü:,ã@zC / ÖoUbü¢Cðau@,|/ÃOª,zuCaã*~Cbao:vö~aýzbðCö_A~ãÜ|åöB ÄÄaurÃzî>ã|CÃœ_ö~ß_:/hÃZ+©B/a|aUCîßÖ_ð+ßvýo.v+/UbÃ¥rÖ >©ra/£_Czz >ª/röß,£/ÃO/üZaýUã+C*UîªU¢ÖCZßÄöÃßð©öuzv>_ ~ uuÃ/©zvðOüUÄßüzAZ+ªAuÖh+*Aa_C¢ßðzZa,A©ª>/A>ä©öCUýurOÄ|B:b*b¢zÃß_o ,@öðaªÖC~rUZÃ,ðÖ_î/¢rÖr| ©ªýrÄuüà |~zbrª.ªüîrß©ÃÃ_rîC@C£ ,ª*aÜîåüZª*ß ::ÃãuªÜZ*@ýðZ©@zãouðvî<ã~£ÄBððOzä+bÃrZz@äAbb*B>O<Äv<Öoöz@Ã*bêO©¢Ä©A|¢~_Ã¥~/ããÃ|*Ã./üBö~@BªbbÖ@hBÃ¥|UÃã<¢ãhêoÃC<ãoazzOr|Ãhhðö|_Ãœ*:*öv:Ã¥@Uß~ã+ .ßýCUåÖrßîÃý_|Ã|©Z~öUÄ,Or+Z>b@ª*r*Ä*ðãz©Ao¢/vZîzß©hZU/BÄ,ßîä~vCZZ ßrä*¢ßüðîBböðð<ü©.,zCÃœUýöCð:bÃœA*oð>Ã::Azz @a¢:åý,ª~ÄåýuAüä.>Ã¥@ãCbÃßý/ß@u+BýÜUU+Ãhvv_böªäCb<:ߪ<¢@oCrö|brZZaCðªÃÜýZU©ßÃZÃýC|b/ÃÄðö OBã__Ão*v>hU© +ä:zoob/îÜ_>:ö>¢ßz,,ªÄzÃÖ|@//ܪÖ:UÃ~uÖrbýAurä£ÃßävÄ£_Ä|~£¢aCOßbuvî,,_ªhã@UÖÄ*z~ßBÜår@bA>äOh¢ÖbÃ¥rýýööUzZUCÃ¥CZüãU brA.îäÄräå@*:@B/+_Ä**¢_Ãœ<£b£ðÃ_aöß|_b", - "UvahbÜåª..C:", - "z+¢bý.Ã|.ZãýÃÃ.êâÃäBÃà ¢o|oÜ©ÃrhuåßZ¢b|ªÃU:ãÃîîÖabÖ@+Ãœ*ÜÖ*Uo,¢ªBÃœ_<@ä b|Ov*>v<Ã¥U£,Ü¢üÃÜ/_A@Zã,rB¢*br/,å©ýr*C.ä>v:ý@ýãýhBÖAýãßÜÄßbvåð.Ãßv>C¢Ü*©A©@vßãåuU£uü/.,bÃ¥u<_boý~ãÜOU<Ã*+ÖBýzz_üåîãauÃ:U,Ã¥/u.ZߪåÃð.A£¢Ã>Zäu*.bÖ Uª¢rhÖî,_Ãœ+A@ðbööãuåýßÃåOÃo.ÃöAa<Ã¥vÃý+ÃœväZ:,rz+*B£ßbo£ß|*¢Ö,ßb,à C:*a.@~büaÃ*ZÃäzßzBCýããBà +ª+<ßåÖbBäA Ãhuür¢BubðåBýÜz| vªv bhüÄ>uߪuªaZ:¢öåa£raÜÜ@/aü¢ÃðßÃä/Bã:ðåu@.Oã>OßZAãÃubr |CZBh,b~Ä~_ý*~,üBzaîB~ðavr©ªîu~©£B_ÃoãÃã:ßaä*vb£ßv|o/vCÃABÃüüAªÃ¢ ä~|Ã¥aßCahýUCßr©üb* ååýã>UÃrÖ:£å uA.:BðvÃœb.Ch/ðZ_zaüv*Ã:Ã¥Ãb¢v~*_>BðåßÜrÃ¥oB|ß:äÄb :©::êü>|_ÄAZýrÄäªA@A>ÖBhßOßðýaAaZßðåBß>ö,uU<ãäB*C~U©_züî+ ä/O|~@©AaoZãÜß_|£Z>ªZöb/ß,Ãœ+:ýäa>ÖöbÖߢÃßßäöbvß:hü£©,Öbð+ÖÜOÖz~azv Aã>ßߣÜß>Bh+@~zU> O¢ÖÄA©ab:ü>uã¢ßöߪ¢oãAªö_ßU+U~/bb©h£~zßßßÜOßzvv>~~ßê*>haßou.¢ßå_.hO>h+<ßß*bäߣv_ÖߣßÃ<Ãoý£Üo~.OüzvÃ¥hBa:u åßa:Zroå©©ãobßö,a@>~zu,OB__Öh:à /:ä©ÃÄÄ/¢Zîà ßu<öÃb>ö£a£,ß*é,Örð>êðZ£ZßböA ©ãZ bß,,U,ß:rvî*üÜu_Ãßßbª_©bÜÃîÃü Ãüb.ªª*ßÃCöoÃaÖöãvÄ>¢åAuÄ>hzª|ðß_ðßOävUbã>ü>@*zßýZÃoU~vhzðbOzª|OÖß *£+öZý+~/.r@<ãrzBßÜ_ü©£åörbÖUöÜÃÃAüo¢*.oäýßÃB,~ CZ:+ýU*Ãý¢:Ãœ:Ã¥AÃÖ.ª©ýãðß_ªÃ£ö<,Ö>©voo Uv¢zC¢¢C/bªÄü+|*ãbÃ<Z>*o¢uA_höbö/BÄãB>,ÃB:î:¢åý*+î@ªvb ¢rüÄAÃCö~+öö,ÃÄå>¢+.ã~Ã¥_Öu©Ou.:Ã*Öb£hÃœ+bü> BÃ𪩣aAüå©Ü//î*~ÖAîÃZ.|b¢.<£öüöbÃœU:Crßh£,bªv,Ãý/¢<ßu,aZߪÖåo©@b:|ãO:u,öåUzußZ©å.bAö©ßBZAî¢oªðî>@Üýröý*,ªh>>azüoÃAÄÃßrÃ|AÃœ: Bðr¢* îÄ|b<<£/¢ÖCö¢ÃüîCÖ|:>BðÃ:bý+ObÃýü.|©Ö ß©¢bîö|üüBzb~hBCߣÃäbã@vU|ahýroZhÜöîß/rýCv<@,@ÜÖ,+ýüÜ~Zªö+îrUßðbðÖ+ohzà A.@ð.Üß>ar,bv:o+¢bAߣÃbà .ª¢h¢¢Béö+b*ro|,Ob.ãß<ªa_Bvä© +ߪýOo*,|Z,~@ÃœZvãåUU@aZ>ªCAhb@ãßýªývÖrü.bÜÖa+aCo äßý_ vB@hÃœhoÃœb©ü+*Ãß*~ãA*Bü©£@ªðå*åÜ>vîßÃãðrÖÖü/bAa©+*üvüüOUz/ä¢OrZ/Oå©£vªC_~ª@ð|@+¢ß@B©>zýZa|<_vO|îu/z:ªzÃ~CÜãvCU¢Öb_büaß:Ö@,£ÃC~v.bªÄCuðA>ß@_Ä<ÄoÖðÃ>ÄCªî/ý_ör©ý:_>/r@_>@h@|h£r.r+O_:.rvUhýÜub:ðhÖßv+ãhÄÃz/u>Uz|Ã*OÄ+ÖýZbÃ¥/äãb~ü_ZîÖãöb+u<ßhüüÖaß.BäîÖ_+Ãœ@O<,ZåÃÄ<©u |ÃÖzß<îüãzCh.A.üÄbßÃ_ü Ö|üð~C.>Z_©vÃ*|BZ£,ðrb*üv:Äý/Ã¥<Ãœ~,ýÃã_zä.ßãªÖÖâoö@BhbCÃuý©h,/b,båÃZ*ßvà _>+UÖu>ÄBã<++¢@Zz@ãäO:ýÖ/_äb/v ©Üܪ_Ã::ÖO.z~~AÃUoabz.ähB@¢ßu+/Zã@ߪ~Aý_äbbÃÖo.O,ãªî/uAUî¢@AC£U,Ãh:bÖ/BuOz,.v¢îbrÃÃzð|ã£/rü*¢Cð¢baîCä|v©o/Ãœ~o+oCvrÖÜîo@äOhÜÜ©ð ,,Ö UÄßu£.bo<ÃœU>bbãb.*hÃð>bðÄÜ<ýUB,r,bî.<ýC+ßaAbð£ÃÃããðAvA.ãå@äðBð,,bBah.ª/UÖîð<üýbbÖh,O*ö+£OÖ~C>vÃå:ßoö@rABÃ+ü£aüa oãßvB> OÄoÄ|:UhðbÄa/vß|/©Üã*ªÜr@ßOö£/.Üü<ýu+><£rÄÃ/Ö/üz|©ÜåUª:,:ubã.ääv.bß©r.@äz_Ä:<:ßäߢã<¢CUð_| ÃÃýb ä:aĪZChoð~Ãåýîý/rAÃœa,£zßåh¢bßC <+ªa/BU>@ÃzÄ@|b|© Z_öZ <ßÄîÄo+Ã¥_*ö@vzbðÜUã|üÖ>~+ObCrÃO_*b¢üîv*_OÜÜöª/Aa.åð£äª@ ãuÃ¥A:vÃßo,Or©BA@îZ+rü|ýöhAÄ ß_©Ö_zCÃ+zBðBüÜrUß*zä,Ö~u *Zuöå._+ý¢Ã/©*ü h~r:CýÜßà Ã,<.ÃœB@uöã<./©,Äýo<ßoý @vo+Ãœr+>/v|Ãhî|ä¢î:,häÄo_b,a¢ äoÄböoªÄÖa~*AoäöUß.zãðÃ~r£_aãÃß+äB,,abö", - null, - null, - "1973-01-31", - "23:59:29.04987", - "2002-01-31 23:59:59.04987+08:00", - "2002-01-31 23:59:59.04987", - array("0.3936", null, null, SQLSRV_SQLTYPE_DECIMAL(28,4)), - array("-100000000000000000", null, null, SQLSRV_SQLTYPE_NUMERIC(32,4)), - 1, - 1, - -1138956117, - null, - -26881, - 1, - 0, - array(("1A0BEC66F89701C5BF23A683CB4F47CB7115B598E3CD4F89528BDF6B9086CA95ED897115611686F38D26F520F53E880639B929525D47504DBC62146DB795353958109541DE316A5B8EC20ABDD82E931403D832D65C7738A5392459FE01193BF7F0BCC7E5A84628074FCEC49B3D93758DCE006ADE635FDCE0D8F008B9D86B59758613CC1B67AE6BBCB02A868EB39D68A5AECC3000"), null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_BINARY(384)), - array(null, null, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR), SQLSRV_SQLTYPE_VARBINARY(384)), - " ", - "z,B:>£@ä.vzB/ßOCå£ýüuAªh_v a*A>Ãaª .ÄaCäÜö+£az֪éÖCýC_Zßüîu @>+Ãœ~bã<Ãœ,Ã¥@COZ£öCU@ÄvÃZ+ÃîbbªÖBî@v/aÃ¥/uîCöväªäðv¢ãã©Ã¢Ußb,ãOß/O£Üª~îä£bÃA¢~z/.Üåüß.Öî/uîA¢ö<ªACÃÜ¢Býva.ýÜo£bAð+£¢öraA.à ,ÃåuÃ~bý>î@ß ܪü,.ýÖÜhäBUZä£ßC£+åÖ~~COjkl©|h.| ã*U¢ª/ý|UßÜý.Ã/o,Uoð|rAãrhß©C~ÃÃÃüaz~>br*Ãœ*rÃ¥:£>OîU¢Ö£ ,.<¢z/©BBZ:ÄC~Ã¥ruA*ãU>aU*A¢.ß/br+ýOB|z~ÜéUî/Ã@îZÄA~.ßbî,uZZßUv£uU |UCߪ>ÄCB.<ß~ðZà aZü/.ª©O,,@Ãhvv_böªäCb<:ߪ<¢@oCrö|brZZaCðªÃÜýÃðÄ©*å©ÖZvOv~aÜä:>Ä.ZÃ|rbvÜð>ð@ êBCÃv+Ãã|CîBb<*/üäbv B|>ÃU//z ÃœOzv*boÄOZ|A+~+COýÃ~,ãäCO>@¢Aaðz_U ÄßßhZZU©ßÃZÃýC|b/ÃÄðö OBã__Ão*v>hU© +ä:zoob/îÜ_>:ö>¢ßz,,ªÄzÃÖ|@//ܪÖ:UÃ~u.ÜÜUÖrbýAurä£ÃßävÄ£_Ä|~£¢aCOßbuvî,,_ªhã@UÖÄ*z~ßBÜår@bA>äOh¢ÖbÃ¥rýýööUzZUCÃ¥CZüãU brA.îäÄräå@*:@B/+_Ä**¢_Ãœ<£b£ðÃ_aöß|_b_©>@Ãœ/vZZÃCo¢hß_©u ð.ýuß|ÃÃh_Zov*ýßCoäUöãOÄb OÄÜ*ÃAuß*B+:¢bB:Ã¥,u_äߣ Ä,ßü,BUvo:,aîÜã", - "h<üß>@<+b:£ZuU.UäCzO©b|B_öª,ᚌbäýãîh/Ö¢ýaö:h|AÃAÖaz**hbðäU_ðb|Z_~/U,@C.*Ãr£CBüýðî*bÖªÄ@ãßüZrrÄî:©î_hßz@¢ªö£h>ðßåU¢aÄ>_öåî,ª_ãuhýzO:O/ähªã/o©b+*äh,ö~ÃOöCü©u,Ãüß+ß©ãz:£:,>,Ã¥b+vß_AÖ,/v|ZCU rÖ£Ãðå¢Üä,@A,>ö|bzZäÖÄäÃ*ö|*CU,Äbh/ÃÃÜÃ<ýß+~>Z_~/U,@C.*Ãr£", - null, - " .@Ãã+UãAÃœ >hÃb_ÃßC¢äåzb¢ßå.ýß_*výÄ,@ð/ªßý¢äßãUUüUhAÃaß,>ãäU:~>bÃœ,bhåüÃ@vZbã+Cüh+ª>åªb*ðäUr£ä~ýÄ/ßC*a>¢Ü,ööa,Ã¥Ãåãbª/ÃAhZ¢bbîb£uv,.@*vv<+üvöýîU>ßå ᚁ_ü,©", - null, - "/ÃÄÖÄâý*,ª~~u<:Bo+,|vaÃß/Zã/hÃ/@_öu_rvrª@:>ÄUOÃb>z>:*,OÃýðZ,ß_uÄObbZUðåîoÃœAß@A~v|ÖðßOýOhÖ>|vüßßýUÃœ_OC/ß~.ªZ|++uö AãA¢,_råî_Ö*ªð*üA|Ã>,hCð ,|âÜ,ZªööýÜ*ãU zuîbUßÃð~ ß<:ðåÄBZýü>Uhh:éü+r©Ãvv¢a ÄÖÃ+üå*üÜ.u<ü /bA|ã@üü oäÜ©ðACBÃœ*B_Ã,*<<|:ÃZ©OO/zAýbÃoª©@B:hBbÄ~+ÄhäÜéÃB+ö@UA>Ã~U¢aCabO>Ã¥oZî*+@ÃhðöA £|+zý*rB/<ªÖ_ã.Ä:*©ö+î~+ÃœÃý/ÃœBC_£vªb£ßAÃü,/©Ãoß@CAÃ~ZBöÃüý@U+Ãœ +bBvU*b¢züüü<îâð:zÃ,@U|zäB£Zß ObBZ¢b_ZÜübvzCüvbzî î<~öãß_ö,U.*@£Üî+ªb~+O_Ö vð/ ªA ,:.Īå@_Äé bÃ¥:U üÃðãª~îBªÜuBöbUb/rovbÃœZAu¢@vCvÄ~Bb>Ãߣ|äî,ýCz:ÖÃ.v£ÃZüUr/îªýb¢ý*b¢aÃ¥||vߣz+©ÜOÄÖäããã¢AZCßzUîA~ßv.£A©:Ã¥Cäaãbüª|.vOähußhÄb*Ã¥bvz*Öb@CvhhAzüÄüãÜýz@UåýÄO:|:Zðý |ßä|£bü:Ua<*>ã Aü/ၜ.BUbO*Ã¥Ã/büzîaüUr.~O+OOr£*rª~îCã ,uîÃäCãBÖßö@A~ß*ßÖAußZUîA.ãovo_öCý@ßb>*ZÖb¢,UêÖ,UÃœhB¢_BýaÄ:Z©ßªUa,bbaZz.Ã<Ä©ã£*ßbÖA+.ã>:.ßbßåabvahUZ|Ã+ö<Ä+oã/h+*£rðßUãoÃ¥~£rU>_Ä,:£Ä,bU+,BåÃ<*¢ýÃÃ/Ußo:ÃœZB,übOÃýO£Ö,Ãœ.üZ,ã@Ä*h©_ü£@|bZðUýä.z©Zzu,î@ãªUrvÄhãü¢Ö/o.äa.©hðåÃ>,ðý©¢C@zz:¢ãöÄ/Uäð:ö¢u/våßvî_vý£CBãZvb@Ã:z/ü@~îZü.Zý/Üäo<ÃÜßãªÖb<üÄãåoýß,Ä>zß:Ãzîýöhýýu u©ß_ZÃîã|,<_äb.ß/<ªÄZ|+aý:ðã©¢b>Ö+ªr+/@ÄߣÖ.ýO:ã~hÃ>+>b©zÃœ_ßÜboðzýß
ZãCvüChüäA.AÃåãua¢¢åb|ähÃ¥uýäß*hãßåBä~Z@@ZÃ¥<Ã*ÃœaîßÜÄuðv/Ãýü+ÖCZo>î|ußßý¢ÃCB~OBoßBrBbb£ðð_:+å¢îÖ.aÃüßÜ_uCßhýBß©U¢ßOv|auBäü£Ã .Baß+Ã~/h*a|üýUî£ßBärÖb/¢r,ߢBßhäåÃÄhOÃCBß<:oÄ~Aüruo_ßC©A|Oozßßb@åÃbZ_ÖÃ//Öb|AÄ./Zv_öü+oäZ£zZäO<@O<.ß©ýåCª,ýC~AýbªuZÃAä ªvbý+O:|vv|,|z~ÃœZ+.*~,AZU£U©£åÖ< oß_~ý@ðhᎄa£/Öa/a*ª>ÃuUßÃÄöÜð+@bb,>brZã©rÃ¥,åß.~zßUßý+î* ö~AvüUÄ>hzü_b_hCrUýîÃ,AA<ãã ãzaOuÄüoîÖ>/bvvC _UrãuÃ*b*uã©<£.BazãbAýb ã:@ýZ~><ÖÃÃᎌ<Ã~£:o*ð¢ãß*ÃuA.Cö£ªÃrü¢zb¢,Ä.Ö¢,CßB<,_åýðß©rðäv:*~ßhCoZÖö +@ürrboöª.ððÄ.Öªß+z/+u+ð+ãö>Zü_*_bo+Aê~Aüvä@.Ã¥,,>*î:>üohAã+h©©£ä~,£U+~bÖÄ.,ö>.öö|Bߪ¢îABauaÃ¥U:*z¢aöaUoaðZoÖB|r£¢å/||Ã¥AUÃ+_uh~O@UbB >:ðÄ,BäzA<ähoüÄO>ðå>ö:höOª£ýUîÃOvãAZ ,aAð+bãÖÃborÄ*>Ã|r©<©*Ö hob+zâB©ãr¢ü @©¢vªîÖoª_a:Ä¢*ߣbvÄßra<:A/b@Ã¥håÜuÃaä,Öã|ü|>+ãýýÖåOo*U@ £hß|<ãÃ,C©<ö ðvz/oã£>Cªb/z¢Ãý@@Ä*+Ä~O+ä ßCUO>A.üðãðBÃÃhAr@A,aA,ªhܪür©CbZ>ß,ä/_,î©Öoª/a©uÃ+© ZUýÄÖðÖ@z:ÄßrA©u.ßü/ð>.êßß<@ªÃÄýý><£zý>Ãuövhaýãbð*o©b.ÜßvCðhvýÄÜaßu@r~Cß:ÖÃ|o~ªãö.ªU@Aö|ã+rb+/UU>hrÃœ Ã¥ U*uZÄu©©>BOä,UßîÖ~_.zböbOh©ª©Ã~¢ý<|ö¢uÄzrüh*ZߢBÖUr©+~ðbb@r¢_A©äv_Ã¥Ur î~ß,>~ã/<îU.|>ähbÄ<Ö:¢h£U~/îü©ßC+~o>ÃÖ>U~bbZ,hߢ©hö>Z<ĪåAßåb£~:ÖBv< ðÄ¢o@ßz£aý¢:vObÄå£uo/©|ßrªö©Ã>au ZÃ/z Ü©,öäAo.Ãœ<@Öå:A Ã¥Uhãüðã£ð>ý,hü/¢,_ä_Ö>ß_¢ßãZ<+äãÖA+bÖvßAî* ~>|+/ªroB+@ðä.uh|ö*ðoaÄa£_CuB_.o~Ãœ|îah*¢bðZv|:_Ä@å©©üUUÃœ+öã ,ß Ãuh,îåhb_ð|Ä@öãðra.ÄuäÖr @Ãœ@bzAuUª~b/b¢ß+ãhÃ,@Äã_A£zýBCß:ðb,C¢ýUî <ð*ßð+ÜÖb:BU~o£ü~OB*:|uöhhî|@ßoÜÜa:u:oÜå*ÄÃh/Ö£_BÃ¥ ©öà î_ª*Ãœ/,rýÃÃ¥B@,<*:£>>ßbÃ|Ãäßý,o>/ãCZBã©+îvÖÄ~A:BüzhÖ~ÖÜ>,Ã¥hÖ£¢,ßãU/U,C,@ßZªub+B_uÖßÃv,ðÃb¢årÃðÄväðå*~uÃäîªöZh_.Aܪ@åßÄ|/ ÃoÃœurßä|*+o>ÖöabüÜ£:¢|@rä.b.ã:o+/aãbî¢ÄßBÃßOv_A©@ßãðîýÄO£~+ÜÖ:ÃîrßbÃBüo bä*îÜ*ðz,.îaªu::z©. vöA£ýîü<UC©r|Ãbvªýhåähaß:ÃUðî_îruZ>u*uªöÜðr.Oä|C¢£v,zöuÄ _ª£oîªÃr:ývräî,,OCöð~£ªoCZUZ@îZbÜåߪößåz>uÃäzðÜÃÃ¥aCãZÜÖîý>A¢*.Ã_Är.üBß/,u¢/+ðÖãr>@,@Ãœ/Öýhîª/Övöb@OoB>ߢB|ê©*,ýU©ãCßaOÃœv/.ãrOa£ü©*ãÃ~ÃÃÄîzübßB£Öð*ÜðoCb*.å£bü/î|B£ýa_U£ðh,ðÄuÄ/*ÃœBZßuÄß_Z> ÄB¢Ö<..üOB£r©ÃäÜÖr*hÃ/zzähb +ß/Äbðöã@£/ZßÃvUhåªh*öb*ð*¢vÃœzÃ¥ *bª+©ðoÖÃAvCÖb üObOý_öý.ÖBÜÃZÃbrÖCöÃhÃ¥~rÃä,zÃö~äA©bÄhîß.bOª|h::býb*£h~ßZ<üUÃ_©Ãß>ð©¢¢ bä _£öZabZ.* ¢ªöÃ/ãÜuo£Cä.:ßbuÜß/£ý> ~ðåa<ðüZr Br|rUÜãCîru©U><öã>~ðãB¢h>üÖÖU©ã£B*ª@a@A~_BZCðÃ+_@B:ÃZ¢:BðbÃßb*©@+Aohä/ßý_ o|ß@uß~ruhuh+:Ã¥>~öÜu>O.ÃœÃÃ¥_uÃBZÃœUª@£ä*a£~vuÄvC©Ö* röU~zb.,OÃ¥::oz_©>@Ãœ/vZZCüu:î_ðv>üÄa,ZäÄ<>bÃœaA>Ö|ÃÃaAä£ _aüü/u.ß/ü.O©>h,¢ßZðr@ZB:<îrãîu£ +<+ îvvbv AªzBb_z,U|ÄhaÃÃ¥>ü rö_ÖÜäZaÖbÄ_Aãðrv_.aÖÃUA_Cª bªzÜý~.¢>oß~@<+b:£ZuU.UäCzO©b|B_öª,ᚌbäýãîh/Ö¢ýaö:h|AÃAÖaz**hbðäU_ðb|Z_~/U,@C.*Ãr£CBüýðî*bÖªÄ@ãßüZrrÄî:©î_hßz@¢ªö£h>ðßåU¢aÄ>_öåî,ª_ãuhýzO:O/ähªã/o©b+*äh,ö~ÃOöCü©u,Ãüß+ß©ãz:£:,>,Ã¥b+vß_AÖ,/v|ZCU rÖ£Ãðå¢Üä,@A,>ö|bzZäÖÄäÃ*ö|*CU,Äbh/ÃÃÜÃ<ýß+~>îü+ýzv£¢ýßüvbUßzßZ.hZ.©*~©ä.Ö.åÄ_ðßÜ.>ÃOöß/rABßUÃ¥oo_ö/rü:z<ãª_ªU*ÖBÄz<,BbÜÄðäZ", - " .@Ãã+UãAÃœ >hÃb_ÃßC¢äåzb¢ßå.ýß_*výÄ,@ð/ªßý¢äßãUUüUhAÃaß,>ãäU:~>bÃœ,bhåüÃ@vZbã+Cüh+ª>åªb*ðäUr£ä~ýÄ/ßC*a>¢Ü,ööa,Ã¥Ãåãbª/ÃAhZ¢bbîb£uv,.@*vv<+üvöýîU>ßå ᚁ_ü,©", - null, - "23:59:29.0498764", - "2002-01-31 23:59:59.0498764+08:00", - "2002-01-31 23:59:59.0498764", - null, - null, - ); - +?> \ No newline at end of file diff --git a/test/functional/sqlsrv/srv_013_sqlsrv_get_field.phpt b/test/functional/sqlsrv/srv_013_sqlsrv_get_field.phpt index 75f33f669..0cae534ef 100644 --- a/test/functional/sqlsrv/srv_013_sqlsrv_get_field.phpt +++ b/test/functional/sqlsrv/srv_013_sqlsrv_get_field.phpt @@ -1,6 +1,7 @@ --TEST-- -sqlsrv_get_field() using SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR) +Test sqlsrv_get_field() using SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR) --SKIPIF-- + --FILE-- --FILE-- +--FILE-- + + +--EXPECT-- +float(0) +NULL +int(0) +NULL +NULL +Done From a76ac3b4606b4169cda8b82569872cd5da0c97b1 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 5 Aug 2020 16:43:40 -0700 Subject: [PATCH 231/249] Added new tests to compare types of fixed and variable widths (#1171) --- .../pdo_sqlsrv/pdo_ae_fixed_var_widths.phpt | 153 +++++++++++++++ .../pdo_sqlsrv/pdo_ansi_locale_zh.phpt | 2 +- .../sqlsrv/sqlsrv_ae_fixed_var_widths.phpt | 184 ++++++++++++++++++ .../sqlsrv/sqlsrv_ansi_locale_zh.phpt | 2 +- 4 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_fixed_var_widths.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_ae_fixed_var_widths.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_ae_fixed_var_widths.phpt b/test/functional/pdo_sqlsrv/pdo_ae_fixed_var_widths.phpt new file mode 100644 index 000000000..ee100defa --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_fixed_var_widths.phpt @@ -0,0 +1,153 @@ +--TEST-- +Test Always Encrypted in Windows by comparing fetched values from fields of fixed and variable widths +--DESCRIPTION-- +See Internal issue 2824 for details. In the plaintext case, the padding is added by SQL, not the driver. For AE, the motivation was to facilitate matching between char and varchar types, that is, a deterministic encryption of char(10) with “abcd†to match varchar(10 with “abcdâ€. +--SKIPIF-- + +--FILE-- + $type) { + $colDef = getColDef($name, $type) . ', '; + $tsql .= $colDef; + } + + $tsql = rtrim($tsql, ', ') . ')'; + return $tsql; + +} + +function createTablePlainQuery($conn, $tableName, $columns) +{ + $tsql = "CREATE TABLE $tableName ("; + foreach ($columns as $name => $type) { + $colDef = '[' . $name . '] ' . $type . ', '; + $tsql .= $colDef; + } + + $tsql = rtrim($tsql, ', ') . ')'; + return $tsql; +} + +function compareFieldValues($f1, $f2, $qualified) +{ + $matched = true; + if ($qualified) { + if ($f1 != $f2) { + echo "Always Encrypted: values do not match!\n"; + $matched = false; + } + } else { + if (strpos($f1, $f2) != 0) { + echo "Plain text: values do not match!\n"; + $matched = false; + }; + } + + if (!$matched) { + var_dump($f1); + var_dump($f2); + } +} + +require_once("MsSetup.inc"); +require_once("MsCommon_mid-refactor.inc"); + +try { + // This test requires to connect with the Always Encrypted feature + // First check if the system is qualified to run this test + $dsn = getDSN($server, null); + $conn = new PDO($dsn, $uid, $pwd); + $qualified = isAEQualified($conn) && (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); + + if ($qualified) { + unset($conn); + + // Now connect with ColumnEncryption enabled + $connectionInfo = "ColumnEncryption = Enabled;"; + $conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd); + } + + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $tableName = 'pdo_fixed_var_types_ae'; + dropTable($conn, $tableName); + + // Define the column definitions + $columns = array('c_char' => 'CHAR(10)', 'c_varchar' => 'VARCHAR(10)', + 'c_nchar' => 'NCHAR(10)', 'c_nvarchar' => 'NVARCHAR(10)', + 'c_binary' => 'BINARY(10)', 'c_varbinary' => 'VARBINARY(10)'); + + if ($qualified) { + $tsql = createTableEncryptedQuery($conn, $tableName, $columns); + } else { + $tsql = createTablePlainQuery($conn, $tableName, $columns); + } + $conn->exec($tsql); + + // Insert values + $values = array('ABCDE', 'ABCDE', + 'WXYZ', 'WXYZ', + '41424344', '41424344'); + + $tsql = "INSERT INTO $tableName (c_char, c_varchar, c_nchar, c_nvarchar, c_binary, c_varbinary) VALUES (?,?,?,?,?,?)"; + $stmt = $conn->prepare($tsql); + + for ($i = 0; $i < count($values); $i++) { + if ($i < 4) { + $stmt->bindParam($i+1, $values[$i]); + } else { + $stmt->bindParam($i+1, $values[$i], PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + } + } + $stmt->execute(); + unset($stmt); + + // Now fetch the values + if ($qualified) { + $tsql = "SELECT CAST(c_char AS VARCHAR(10)), c_varchar, + CAST(c_nchar AS NVARCHAR(10)), c_nvarchar, + CAST(c_binary AS VARBINARY(10)), c_varbinary FROM $tableName"; + } else { + $tsql = "SELECT c_char, c_varchar, + c_nchar, c_nvarchar, + c_binary, c_varbinary FROM $tableName"; + } + + $stmt = $conn->prepare($tsql); + $stmt->execute(); + + $row = $stmt->fetch(PDO::FETCH_NUM); + + compareFieldValues($row[0], $row[1], $qualified); + compareFieldValues($row[2], $row[3], $qualified); + compareFieldValues($row[4], $row[5], $qualified); + + dropTable($conn, $tableName); + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + echo $e->getMessage(); +} + +echo "Done\n"; + +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ansi_locale_zh.phpt b/test/functional/pdo_sqlsrv/pdo_ansi_locale_zh.phpt index 6500a0cdc..50596026e 100644 --- a/test/functional/pdo_sqlsrv/pdo_ansi_locale_zh.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ansi_locale_zh.phpt @@ -49,7 +49,7 @@ function runTest($conn, $tempDB) $conn->exec($query); shell_exec("export LC_ALL=zh_CN.gb18030"); - shell_exec("iconv -c -f GB2312 -t GB18030 pdo_test_gb18030.php > test_gb18030.php"); + shell_exec("iconv -c -f GB2312 -t GB18030 ".dirname(__FILE__)."/pdo_test_gb18030.php > ".dirname(__FILE__)."/test_gb18030.php"); print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/test_gb18030.php $tempDB")); } diff --git a/test/functional/sqlsrv/sqlsrv_ae_fixed_var_widths.phpt b/test/functional/sqlsrv/sqlsrv_ae_fixed_var_widths.phpt new file mode 100644 index 000000000..590a21917 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_fixed_var_widths.phpt @@ -0,0 +1,184 @@ +--TEST-- +Test Always Encrypted in Windows by comparing fetched values from fields of fixed and variable widths +--DESCRIPTION-- +See Internal issue 2824 for details. In the plaintext case, the padding is added by SQL, not the driver. For AE, the motivation was to facilitate matching between char and varchar types, that is, a deterministic encryption of char(10) with “abcd†to match varchar(10 with “abcdâ€. +--SKIPIF-- + +--FILE-- + $type) { + $colDef = getColDef($name, $type) . ', '; + $tsql .= $colDef; + } + + $tsql = rtrim($tsql, ', ') . ')'; + return $tsql; + + } + + function createTablePlainQuery($conn, $tableName, $columns) + { + $tsql = "CREATE TABLE $tableName ("; + foreach ($columns as $name => $type) { + $colDef = '[' . $name . '] ' . $type . ', '; + $tsql .= $colDef; + } + + $tsql = rtrim($tsql, ', ') . ')'; + return $tsql; + } + + function compareFieldValues($f1, $f2, $qualified) + { + $matched = true; + if ($qualified) { + if ($f1 != $f2) { + echo "Always Encrypted: values do not match!\n"; + $matched = false; + } + } else { + if (strpos($f1, $f2) != 0) { + echo "Plain text: values do not match!\n"; + $matched = false; + }; + } + + if (!$matched) { + var_dump($f1); + var_dump($f2); + } + } + + require_once("MsCommon.inc"); + + // This test requires to connect with the Always Encrypted feature + // First check if the system is qualified to run this test + $options = array("Database" => $database, "UID" => $userName, "PWD" => $userPassword); + $conn = sqlsrv_connect($server, $options); + if ($conn === false) { + fatalError("Failed to connect to $server."); + } + + $qualified = AE\isQualified($conn) && (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); + if ($qualified) { + sqlsrv_close($conn); + + // Now connect with ColumnEncryption enabled + $connectionOptions = array_merge($options, array('ColumnEncryption' => 'Enabled')); + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) { + fatalError("Failed to connect to $server."); + } + } + + $tableName = 'srv_fixed_var_types_ae'; + + dropTable($conn, $tableName); + + // Define the column definitions + $columns = array('c_char' => 'CHAR(10)', 'c_varchar' => 'VARCHAR(10)', + 'c_nchar' => 'NCHAR(10)', 'c_nvarchar' => 'NVARCHAR(10)', + 'c_binary' => 'BINARY(10)', 'c_varbinary' => 'VARBINARY(10)'); + + if ($qualified) { + $tsql = createTableEncryptedQuery($conn, $tableName, $columns); + } else { + $tsql = createTablePlainQuery($conn, $tableName, $columns); + } + $stmt = sqlsrv_query($conn, $tsql); + if (!$stmt) { + fatalError("Failed to create table $tableName\n"); + } + + // Insert values + $values = array('ABCDE', 'ABCDE', + 'WXYZ', 'WXYZ', + '41424344', '41424344'); + + $params = array( + $values[0], + $values[1], + $values[2], + $values[3], + array( + $values[4], + SQLSRV_PARAM_IN, + null, + SQLSRV_SQLTYPE_BINARY(10) + ), + array( + $values[5], + SQLSRV_PARAM_IN, + null, + SQLSRV_SQLTYPE_VARBINARY(10) + ) + ); + + $tsql = "INSERT INTO $tableName (c_char, c_varchar, c_nchar, c_nvarchar, c_binary, c_varbinary) VALUES (?,?,?,?,?,?)"; + $stmt = sqlsrv_prepare($conn, $tsql, $params); + if (!$stmt) { + fatalError("Failed to prepare insert statement"); + } + $result = sqlsrv_execute($stmt); + if (!$result) { + fatalError("Failed to insert values"); + } + sqlsrv_free_stmt($stmt); + + // Now fetch the values + if ($qualified) { + $tsql = "SELECT CAST(c_char AS VARCHAR(10)), c_varchar, + CAST(c_nchar AS NVARCHAR(10)), c_nvarchar, + CAST(c_binary AS VARBINARY(10)), c_varbinary FROM $tableName"; + } else { + $tsql = "SELECT c_char, c_varchar, + c_nchar, c_nvarchar, + c_binary, c_varbinary FROM $tableName"; + } + $stmt = sqlsrv_query($conn, $tsql); + if (!$stmt) { + fatalError("Failed to select from $tableName"); + } + + while (sqlsrv_fetch($stmt)) { + $f0 = sqlsrv_get_field($stmt, 0); + $f1 = sqlsrv_get_field($stmt, 1); + + compareFieldValues($f0, $f1, $qualified); + + $f2 = sqlsrv_get_field($stmt, 2); + $f3 = sqlsrv_get_field($stmt, 3); + + compareFieldValues($f2, $f3, $qualified); + + $f4 = sqlsrv_get_field($stmt, 4, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); + $f5 = sqlsrv_get_field($stmt, 5, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)); + + compareFieldValues($f4, $f5, $qualified); + } + + dropTable($conn, $tableName); + + // Close connection + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); + print "Done" +?> + +--EXPECT-- +Done diff --git a/test/functional/sqlsrv/sqlsrv_ansi_locale_zh.phpt b/test/functional/sqlsrv/sqlsrv_ansi_locale_zh.phpt index bfbee8b27..bcdfa22a8 100644 --- a/test/functional/sqlsrv/sqlsrv_ansi_locale_zh.phpt +++ b/test/functional/sqlsrv/sqlsrv_ansi_locale_zh.phpt @@ -60,7 +60,7 @@ try { } shell_exec("export LC_ALL=zh_CN.gb18030"); - shell_exec("iconv -c -f GB2312 -t GB18030 sqlsrv_test_gb18030.php > test_gb18030.php"); + shell_exec("iconv -c -f GB2312 -t GB18030 ".dirname(__FILE__)."/sqlsrv_test_gb18030.php > ".dirname(__FILE__)."/test_gb18030.php"); print_r(shell_exec(PHP_BINARY." ".dirname(__FILE__)."/test_gb18030.php $tempDB")); } catch (Error $err) { From 9fc32baf6944aefdd0af96cb6ad585e3ff1fea8f Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 6 Aug 2020 09:46:15 -0700 Subject: [PATCH 232/249] Fixed get_field_as_string when using display size for large types (#1172) --- source/shared/core_stmt.cpp | 11 ++- .../pdo_1170_direct_query_with_textsize.phpt | 71 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1170_direct_query_with_textsize.phpt diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index cccee0a97..649e1c3d4 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2590,9 +2590,14 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind break; } - // if this is a large type, then read the first few bytes to get the actual length from SQLGetData - if( sql_display_size == 0 || sql_display_size == INT_MAX || - sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 ) { + // If this is a large type, then read the first few bytes to get the actual length from SQLGetData + // The user may use "SET TEXTSIZE" to specify the size of varchar(max), nvarchar(max), + // varbinary(max), text, ntext, and image data returned by a SELECT statement. + // For varchar(max) and nvarchar(max), sql_display_size will be 0, regardless + if (sql_display_size == 0 || sql_display_size == INT_MAX || + sql_display_size == INT_MAX >> 1 || sql_display_size == UINT_MAX - 1 || + (sql_display_size > SQL_SERVER_MAX_FIELD_SIZE && + (sql_field_type == SQL_WLONGVARCHAR || sql_field_type == SQL_LONGVARCHAR || sql_field_type == SQL_LONGVARBINARY))) { field_len_temp = intial_field_len; diff --git a/test/functional/pdo_sqlsrv/pdo_1170_direct_query_with_textsize.phpt b/test/functional/pdo_sqlsrv/pdo_1170_direct_query_with_textsize.phpt new file mode 100644 index 000000000..6ec78170c --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1170_direct_query_with_textsize.phpt @@ -0,0 +1,71 @@ +--TEST-- +GitHub issue 1170 - PDO::SQLSRV_ATTR_DIRECT_QUERY with SET TEXTSIZE +--DESCRIPTION-- +This test verifies that setting PDO::SQLSRV_ATTR_DIRECT_QUERY to true with a user defined TEXTSIZE will work +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + true); + + $sql = "SET TEXTSIZE $size"; + $stmt = $conn->prepare($sql, $options); + $stmt->execute(); + unset($stmt); + + $sql = composeQuery($input, $type); + $stmt = $conn->prepare($sql, $options); + $stmt->execute(); + + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row['C1'] != $input || !is_null($row['C2'])) { + var_dump($row); + } + unset($stmt); +} + +try { + $conn = connect(); + + $options = array(PDO::SQLSRV_ATTR_DIRECT_QUERY => true); + + runTest($conn, 'TEXT', 4800); + runTest($conn, 'NTEXT', 129024); + runTest($conn, 'IMAGE', 10000); + + unset($conn); + + echo "Done\n"; +} catch (PdoException $e) { + echo $e->getMessage() . PHP_EOL; +} + +?> +--EXPECT-- +Test with TEXT and 4800 +Test with NTEXT and 129024 +Test with IMAGE and 10000 +Done + From 3dddbbb2e262f6b8d1c99249ba6595e611f5c2c5 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 6 Aug 2020 14:02:40 -0700 Subject: [PATCH 233/249] Updated ODBC version in appveyor and changed to VS 2019 (#1173) --- appveyor.yml | 20 +++++++++---------- test/functional/sqlsrv/MsHelper.inc | 4 ++-- .../sqlsrv/sqlsrv_378_out_param_error.phpt | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 8f81e3d47..d123fbd47 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,19 +22,19 @@ environment: BUILD_PLATFORM: x64 TEST_PHP_SQL_SERVER: (local)\SQL2017 SQL_INSTANCE: SQL2017 - PHP_VC: 15 - PHP_MAJOR_VER: 7.3 + PHP_VC: vc15 + PHP_MAJOR_VER: 7.4 PHP_MINOR_VER: latest PHP_EXE_PATH: x64\Release_TS THREAD: ts platform: x64 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 BUILD_PLATFORM: x86 - TEST_PHP_SQL_SERVER: (local)\SQL2016 - SQL_INSTANCE: SQL2016 - PHP_VC: 15 - PHP_MAJOR_VER: 7.4 - PHP_MINOR_VER: latest + TEST_PHP_SQL_SERVER: (local)\SQL2019 + SQL_INSTANCE: SQL2019 + PHP_VC: vs16 + PHP_MAJOR_VER: 8.0 + PHP_MINOR_VER: 0beta1 PHP_EXE_PATH: Release THREAD: nts platform: x86 @@ -83,7 +83,7 @@ install: } - echo Downloading MSODBCSQL 17 # AppVeyor build works are x64 VMs and 32-bit ODBC driver cannot be installed on it - - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/en-US/17.5.2.1/x64/msodbcsql.msi', 'c:\projects\msodbcsql.msi') + - ps: (new-object net.webclient).DownloadFile('https://download.microsoft.com/download/6/b/3/6b3dd05c-678c-4e6b-b503-1d66e16ef23d/en-US/17.6.1.1/x64/msodbcsql.msi', 'c:\projects\msodbcsql.msi') - cmd /c start /wait msiexec /i "c:\projects\msodbcsql.msi" /q IACCEPTMSODBCSQLLICENSETERMS=YES ADDLOCAL=ALL - echo Checking the version of MSODBCSQL - reg query "HKLM\SOFTWARE\ODBC\odbcinst.ini\ODBC Driver 17 for SQL Server" @@ -106,7 +106,7 @@ build_script: - cd c:\projects - python -V - python builddrivers.py --PHPVER=%PHP_VERSION% --ARCH=%BUILD_PLATFORM% --THREAD=%THREAD% --SOURCE=%APPVEYOR_BUILD_FOLDER%\source --TESTING --NO_RENAME - - cd c:\projects\php-sdk\phpdev\vc%PHP_VC%\%BUILD_PLATFORM%\php-%PHP_VERSION%-src\ + - cd c:\projects\php-sdk\phpdev\%PHP_VC%\%BUILD_PLATFORM%\php-%PHP_VERSION%-src\ - set PHP_SRC_DIR=%CD%\ext - cd %PHP_EXE_PATH% - set PHP_EXE_PATH=%CD% diff --git a/test/functional/sqlsrv/MsHelper.inc b/test/functional/sqlsrv/MsHelper.inc index 2058e5e01..590e8b333 100644 --- a/test/functional/sqlsrv/MsHelper.inc +++ b/test/functional/sqlsrv/MsHelper.inc @@ -1088,7 +1088,7 @@ function getColSize($k) case 19: return ("1073741823"); case 20: return ("512"); case 21: return ("512"); - case 22: return ("0)"); + case 22: return ("0"); case 23: return ("2147483647"); case 24: return ("36"); //case 25: return ("23"); @@ -1097,7 +1097,7 @@ function getColSize($k) case 28: return ("0"); default: break; } - return (""); + return NULL; } function getColPrecision($k) diff --git a/test/functional/sqlsrv/sqlsrv_378_out_param_error.phpt b/test/functional/sqlsrv/sqlsrv_378_out_param_error.phpt index a0169225d..27942a21a 100644 --- a/test/functional/sqlsrv/sqlsrv_378_out_param_error.phpt +++ b/test/functional/sqlsrv/sqlsrv_378_out_param_error.phpt @@ -78,7 +78,7 @@ function executeSP($conn, $procName, $noRef, $prepare) $expected = 3; $v1 = 1; $v2 = 2; - $v3 = 'str'; + $v3 = 0; $res = true; $tsql = "{call $procName( ?, ?, ?)}"; From 9365fc52018226f9d2987e6bf7bdcfaa5aa97a31 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 7 Aug 2020 09:42:57 -0700 Subject: [PATCH 234/249] Added insertion tests for encrypted smalldatetime and datetime columns (#1174) --- source/shared/core_stmt.cpp | 12 +- .../pdo_ae_insert_datetime_encrypted.phpt | 139 ++++++++++++++++ .../sqlsrv_ae_insert_datetime_encrypted.phpt | 149 ++++++++++++++++++ 3 files changed, 296 insertions(+), 4 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 649e1c3d4..f88f6fc7d 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -748,10 +748,14 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ core::SQLBindParameter( stmt, param_num + 1, direction, c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr ); - if ( stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP ) - { - if( decimal_digits == 3 ) - core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER ); + + // When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively. + // For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error. + // This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns. + // To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column. + if (stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP) { + if (decimal_digits == 3) + core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER); else if (decimal_digits == 0) core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER ); } diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt new file mode 100644 index 000000000..5d84a1d64 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt @@ -0,0 +1,139 @@ +--TEST-- +Test for inserting and retrieving encrypted data of datetime and smalldatetime types encrypted +--DESCRIPTION-- +Verify that inserting into smalldatetime column might trigger "Datetime field overflow" error +--SKIPIF-- + +--FILE-- + $type) { + $colDef = getColDef($name, $type) . ', '; + $tsql .= $colDef; + } + + $tsql = rtrim($tsql, ', ') . ')'; + return $tsql; + +} + +function createTablePlainQuery($conn, $tableName, $columns) +{ + $tsql = "CREATE TABLE $tableName ("; + foreach ($columns as $name => $type) { + $colDef = '[' . $name . '] ' . $type . ', '; + $tsql .= $colDef; + } + + $tsql = rtrim($tsql, ', ') . ')'; + return $tsql; +} + +try { + // This test requires to connect with the Always Encrypted feature + // First check if the system is qualified to run this test + $dsn = getDSN($server, null); + $conn = new PDO($dsn, $uid, $pwd); + $qualified = isAEQualified($conn) && (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); + + if ($qualified) { + unset($conn); + + // Now connect with ColumnEncryption enabled + $connectionInfo = "ColumnEncryption = Enabled;"; + $conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd); + } + + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $tableName = 'pdo_datetime_encrypted'; + dropTable($conn, $tableName); + + // Define the column definitions + $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(4)'); + + if ($qualified) { + $tsql = createTableEncryptedQuery($conn, $tableName, $columns); + } else { + $tsql = createTablePlainQuery($conn, $tableName, $columns); + } + $conn->exec($tsql); + + // Insert values that cause errors + $val1 = '9999-12-31 23:59:59'; + $val2 = null; + $val3 = '9999-12-31 23:59:59.9999'; + + $tsql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?,?,?)"; + $stmt = $conn->prepare($tsql); + + $stmt->bindParam(1, $val1); + $stmt->bindParam(2, $val2); + $stmt->bindParam(3, $val3); + + try { + $stmt->execute(); + } catch (PDOException $e) { + $error = ($qualified)? '*Datetime field overflow' : '*The conversion of a nvarchar data type to a smalldatetime data type resulted in an out-of-range value.'; + if (!fnmatch($error, $e->getMessage())) { + echo "The error message is unexpected:\n"; + var_dump($e->getMessage()); + } + } + + // These values should work + $val1 = '2021-11-03 11:49:00'; + $val2 = '2015-10-23 07:03:00.000'; + + try { + $stmt->execute(); + } catch (PDOException $e) { + echo "Errors unexpected!!\n"; + var_dump($e->getMessage()); + } + + unset($stmt); + + // Now fetch the values + $tsql = "SELECT * FROM $tableName"; + + $stmt = $conn->prepare($tsql); + $stmt->execute(); + + $row = $stmt->fetch(PDO::FETCH_NUM); + var_dump($row); + + dropTable($conn, $tableName); + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + echo $e->getMessage(); +} + +echo "Done\n"; + +?> +--EXPECT-- +array(3) { + [0]=> + string(19) "2021-11-03 11:49:00" + [1]=> + string(23) "2015-10-23 07:03:00.000" + [2]=> + string(24) "9999-12-31 23:59:59.9999" +} +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt new file mode 100644 index 000000000..8527d861f --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt @@ -0,0 +1,149 @@ +--TEST-- +Test for inserting and retrieving encrypted data of datetime and smalldatetime types encrypted +--DESCRIPTION-- +Verify that inserting into smalldatetime column might trigger "Datetime field overflow" error +--SKIPIF-- + +--FILE-- + $type) { + $colDef = getColDef($name, $type) . ', '; + $tsql .= $colDef; + } + + $tsql = rtrim($tsql, ', ') . ')'; + return $tsql; + + } + + function createTablePlainQuery($conn, $tableName, $columns) + { + $tsql = "CREATE TABLE $tableName ("; + foreach ($columns as $name => $type) { + $colDef = '[' . $name . '] ' . $type . ', '; + $tsql .= $colDef; + } + + $tsql = rtrim($tsql, ', ') . ')'; + return $tsql; + } + + require_once("MsCommon.inc"); + + // This test requires to connect with the Always Encrypted feature + // First check if the system is qualified to run this test + $options = array('Database' => $database, 'UID' => $userName, 'PWD' => $userPassword, 'ReturnDatesAsStrings' => true); + $conn = sqlsrv_connect($server, $options); + if ($conn === false) { + fatalError("Failed to connect to $server."); + } + + $qualified = AE\isQualified($conn) && (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); + if ($qualified) { + sqlsrv_close($conn); + + // Now connect with ColumnEncryption enabled + $connectionOptions = array_merge($options, array('ColumnEncryption' => 'Enabled')); + $conn = sqlsrv_connect($server, $connectionOptions); + if ($conn === false) { + fatalError("Failed to connect to $server."); + } + } + + $tableName = 'srv_datetime_encrypted'; + dropTable($conn, $tableName); + + // Define the column definitions + $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(4)'); + + if ($qualified) { + $tsql = createTableEncryptedQuery($conn, $tableName, $columns); + } else { + $tsql = createTablePlainQuery($conn, $tableName, $columns); + } + + $stmt = sqlsrv_query($conn, $tsql); + if (!$stmt) { + fatalError("Failed to create table $tableName\n"); + } + + // Insert values that cause errors + $val1 = '9999-12-31 23:59:59'; + $val2 = null; + $val3 = '9999-12-31 23:59:59.9999'; + + $tsql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?,?,?)"; + $params = array($val1, $val2, $val3); + + $stmt = sqlsrv_prepare($conn, $tsql, $params); + if (!$stmt) { + fatalError("Failed to prepare insert statement"); + } + $result = sqlsrv_execute($stmt); + if ($result) { + echo "Inserting invalid values should have failed!\n"; + } else { + $error = ($qualified)? '*Datetime field overflow' : '*The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value.'; + if (!fnmatch($error, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + } + + sqlsrv_free_stmt($stmt); + + // These values should work + $val1 = '2021-11-03 11:49:00'; + $val2 = '2015-10-23 07:03:00.000'; + + $params = array($val1, $val2, $val3); + $stmt = sqlsrv_prepare($conn, $tsql, $params); + if (!$stmt) { + fatalError("Failed to prepare insert statement"); + } + $result = sqlsrv_execute($stmt); + if (!$result) { + fatalError("Failed to insert valid values\n"); + } + + sqlsrv_free_stmt($stmt); + + // Now fetch the values + $tsql = "SELECT * FROM $tableName"; + + $stmt = sqlsrv_query($conn, $tsql); + if (!$stmt) { + fatalError("Failed to select from $tableName"); + } + + $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); + var_dump($row); + + dropTable($conn, $tableName); + + sqlsrv_free_stmt($stmt); + sqlsrv_close($conn); + + echo "Done\n"; + +?> +--EXPECT-- +array(3) { + ["c1"]=> + string(19) "2021-11-03 11:49:00" + ["c2"]=> + string(23) "2015-10-23 07:03:00.000" + ["c3"]=> + string(24) "9999-12-31 23:59:59.9999" +} +Done From 284aca85ce4528fb48c79e82ae9e1d05a6501a01 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 7 Aug 2020 18:33:12 -0700 Subject: [PATCH 235/249] Updated checks to distinguish smalldatetime, datetime and datetime2 fields (#1175) --- source/shared/core_stmt.cpp | 35 +++++++++++++++---- ...indColumn_pdoparam_datetime_precision.phpt | 14 +++++++- .../pdo_ae_insert_datetime_encrypted.phpt | 15 +++++--- ...ae_insert_pdoparam_datetime_precision.phpt | 20 ++++++++++- .../sqlsrv_ae_insert_datetime_encrypted.phpt | 20 ++++++----- 5 files changed, 82 insertions(+), 22 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index f88f6fc7d..1b2ec22ee 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -746,19 +746,40 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ ind_ptr = SQL_NULL_DATA; } - core::SQLBindParameter( stmt, param_num + 1, direction, - c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr ); - // When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively. // For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error. // This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns. // To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column. + // Check SQL_DESC_OCTET_LENGTH of the implementation parameter descriptor (IPD) to distinguish Smalldatetime or Datetime fields from Datetime2(0) or Datetime2(3) fields, as described in + // https://docs.microsoft.com/sql/relational-databases/native-client-odbc-date-time/metadata-parameter-and-result + // This has to be done before SQLBindParameter() if (stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP) { - if (decimal_digits == 3) - core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER); - else if (decimal_digits == 0) - core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER ); + if (decimal_digits == 0 || decimal_digits == 3) { + SQLHDESC hIpd = NULL; + core::SQLGetStmtAttr(stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0); + if (hIpd != NULL) { + SQLULEN octetLength = 0; + SQLINTEGER dummy = 0; + + SQLRETURN r = ::SQLGetDescField(hIpd, param_num + 1, SQL_DESC_OCTET_LENGTH, (SQLPOINTER)&octetLength, 0, &dummy); + if (SQL_SUCCEEDED(r)) { + // The octet length for datetime2 is 16 but no action required + if (octetLength == 8) { + r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER); + } else if (octetLength == 4) { + r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER); + } + } + CHECK_SQL_ERROR_OR_WARNING(r, stmt) { + throw core::CoreException(); + } + } + } } + + core::SQLBindParameter( stmt, param_num + 1, direction, + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr ); + } catch( core::CoreException& e ){ stmt->free_param_data(); diff --git a/test/functional/pdo_sqlsrv/pdo_ae_bindColumn_pdoparam_datetime_precision.phpt b/test/functional/pdo_sqlsrv/pdo_ae_bindColumn_pdoparam_datetime_precision.phpt index 9a68d7a9f..cb92c9508 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_bindColumn_pdoparam_datetime_precision.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_bindColumn_pdoparam_datetime_precision.phpt @@ -31,7 +31,7 @@ function compareDate($dtout, $dtin, $dataType) { } $dataTypes = array("datetime2", "datetimeoffset", "time"); -$precisions = array(/*0,*/ 1, 2, 4, 7); +$precisions = array(0, 1, 2, 4, 7); $inputValuesInit = array("datetime2" => array("0001-01-01 00:00:00", "9999-12-31 23:59:59"), "datetimeoffset" => array("0001-01-01 00:00:00 -14:00", "9999-12-31 23:59:59 +14:00"), "time" => array("00:00:00", "23:59:59")); @@ -101,6 +101,10 @@ try { } ?> --EXPECT-- +Testing datetime2(0): +****Retrieving datetime2(0) as PDO::PARAM_STR is supported**** +****Retrieving datetime2(0) as PDO::PARAM_LOB is supported**** + Testing datetime2(1): ****Retrieving datetime2(1) as PDO::PARAM_STR is supported**** ****Retrieving datetime2(1) as PDO::PARAM_LOB is supported**** @@ -117,6 +121,10 @@ Testing datetime2(7): ****Retrieving datetime2(7) as PDO::PARAM_STR is supported**** ****Retrieving datetime2(7) as PDO::PARAM_LOB is supported**** +Testing datetimeoffset(0): +****Retrieving datetimeoffset(0) as PDO::PARAM_STR is supported**** +****Retrieving datetimeoffset(0) as PDO::PARAM_LOB is supported**** + Testing datetimeoffset(1): ****Retrieving datetimeoffset(1) as PDO::PARAM_STR is supported**** ****Retrieving datetimeoffset(1) as PDO::PARAM_LOB is supported**** @@ -133,6 +141,10 @@ Testing datetimeoffset(7): ****Retrieving datetimeoffset(7) as PDO::PARAM_STR is supported**** ****Retrieving datetimeoffset(7) as PDO::PARAM_LOB is supported**** +Testing time(0): +****Retrieving time(0) as PDO::PARAM_STR is supported**** +****Retrieving time(0) as PDO::PARAM_LOB is supported**** + Testing time(1): ****Retrieving time(1) as PDO::PARAM_STR is supported**** ****Retrieving time(1) as PDO::PARAM_LOB is supported**** diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt index 5d84a1d64..1e1a9157b 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt @@ -63,7 +63,7 @@ try { dropTable($conn, $tableName); // Define the column definitions - $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(4)'); + $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(0)', 'c4' => 'datetime2(3)'); if ($qualified) { $tsql = createTableEncryptedQuery($conn, $tableName, $columns); @@ -75,14 +75,16 @@ try { // Insert values that cause errors $val1 = '9999-12-31 23:59:59'; $val2 = null; - $val3 = '9999-12-31 23:59:59.9999'; + $val3 = null; + $val4 = '9999-12-31 23:59:59.999'; - $tsql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?,?,?)"; + $tsql = "INSERT INTO $tableName (c1, c2, c3, c4) VALUES (?,?,?,?)"; $stmt = $conn->prepare($tsql); $stmt->bindParam(1, $val1); $stmt->bindParam(2, $val2); $stmt->bindParam(3, $val3); + $stmt->bindParam(4, $val4); try { $stmt->execute(); @@ -97,6 +99,7 @@ try { // These values should work $val1 = '2021-11-03 11:49:00'; $val2 = '2015-10-23 07:03:00.000'; + $val3 = '0001-01-01 01:01:01'; try { $stmt->execute(); @@ -128,12 +131,14 @@ echo "Done\n"; ?> --EXPECT-- -array(3) { +array(4) { [0]=> string(19) "2021-11-03 11:49:00" [1]=> string(23) "2015-10-23 07:03:00.000" [2]=> - string(24) "9999-12-31 23:59:59.9999" + string(19) "0001-01-01 01:01:01" + [3]=> + string(23) "9999-12-31 23:59:59.999" } Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime_precision.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime_precision.phpt index a3db5c7fc..0d7cd1556 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime_precision.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime_precision.phpt @@ -29,7 +29,7 @@ function compareDate($dtout, $dtin, $dataType) { } $dataTypes = array("datetime2", "datetimeoffset", "time"); -$precisions = array(/*0,*/ 1, 2, 4, 7); +$precisions = array(0, 1, 2, 4, 7); $inputValuesInit = array("datetime2" => array("0001-01-01 00:00:00", "9999-12-31 23:59:59"), "datetimeoffset" => array("0001-01-01 00:00:00 -14:00", "9999-12-31 23:59:59 +14:00"), "time" => array("00:00:00", "23:59:59")); @@ -102,6 +102,12 @@ try { } ?> --EXPECT-- +Testing datetime2(0): +****Conversion from PDO::PARAM_BOOL to datetime2(0) is supported**** +****Conversion from PDO::PARAM_INT to datetime2(0) is supported**** +****Conversion from PDO::PARAM_STR to datetime2(0) is supported**** +****Conversion from PDO::PARAM_LOB to datetime2(0) is supported**** + Testing datetime2(1): ****Conversion from PDO::PARAM_BOOL to datetime2(1) is supported**** ****Conversion from PDO::PARAM_INT to datetime2(1) is supported**** @@ -126,6 +132,12 @@ Testing datetime2(7): ****Conversion from PDO::PARAM_STR to datetime2(7) is supported**** ****Conversion from PDO::PARAM_LOB to datetime2(7) is supported**** +Testing datetimeoffset(0): +****Conversion from PDO::PARAM_BOOL to datetimeoffset(0) is supported**** +****Conversion from PDO::PARAM_INT to datetimeoffset(0) is supported**** +****Conversion from PDO::PARAM_STR to datetimeoffset(0) is supported**** +****Conversion from PDO::PARAM_LOB to datetimeoffset(0) is supported**** + Testing datetimeoffset(1): ****Conversion from PDO::PARAM_BOOL to datetimeoffset(1) is supported**** ****Conversion from PDO::PARAM_INT to datetimeoffset(1) is supported**** @@ -150,6 +162,12 @@ Testing datetimeoffset(7): ****Conversion from PDO::PARAM_STR to datetimeoffset(7) is supported**** ****Conversion from PDO::PARAM_LOB to datetimeoffset(7) is supported**** +Testing time(0): +****Conversion from PDO::PARAM_BOOL to time(0) is supported**** +****Conversion from PDO::PARAM_INT to time(0) is supported**** +****Conversion from PDO::PARAM_STR to time(0) is supported**** +****Conversion from PDO::PARAM_LOB to time(0) is supported**** + Testing time(1): ****Conversion from PDO::PARAM_BOOL to time(1) is supported**** ****Conversion from PDO::PARAM_INT to time(1) is supported**** diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt index 8527d861f..68ed4f58a 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt @@ -65,7 +65,7 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov dropTable($conn, $tableName); // Define the column definitions - $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(4)'); + $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(0)', 'c4' => 'datetime2(3)'); if ($qualified) { $tsql = createTableEncryptedQuery($conn, $tableName, $columns); @@ -81,10 +81,11 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov // Insert values that cause errors $val1 = '9999-12-31 23:59:59'; $val2 = null; - $val3 = '9999-12-31 23:59:59.9999'; + $val3 = null; + $val4 = '9999-12-31 23:59:59.999'; - $tsql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?,?,?)"; - $params = array($val1, $val2, $val3); + $tsql = "INSERT INTO $tableName (c1, c2, c3, c4) VALUES (?,?,?,?)"; + $params = array($val1, $val2, $val3, $val4); $stmt = sqlsrv_prepare($conn, $tsql, $params); if (!$stmt) { @@ -105,8 +106,9 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov // These values should work $val1 = '2021-11-03 11:49:00'; $val2 = '2015-10-23 07:03:00.000'; - - $params = array($val1, $val2, $val3); + $val3 = '0001-01-01 01:01:01'; + + $params = array($val1, $val2, $val3, $val4); $stmt = sqlsrv_prepare($conn, $tsql, $params); if (!$stmt) { fatalError("Failed to prepare insert statement"); @@ -138,12 +140,14 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov ?> --EXPECT-- -array(3) { +array(4) { ["c1"]=> string(19) "2021-11-03 11:49:00" ["c2"]=> string(23) "2015-10-23 07:03:00.000" ["c3"]=> - string(24) "9999-12-31 23:59:59.9999" + string(19) "0001-01-01 01:01:01" + ["c4"]=> + string(23) "9999-12-31 23:59:59.999" } Done From f02aefd6bdf816a3b6624993ca0bc22534a25135 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 11 Aug 2020 17:09:26 -0700 Subject: [PATCH 236/249] Reverted the use of octet length and fixed tests (#1176) --- source/shared/core_stmt.cpp | 20 ++++++------------- .../pdo_ae_insert_datetime_encrypted.phpt | 8 ++++---- .../sqlsrv_ae_insert_datetime_encrypted.phpt | 8 ++++---- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 1b2ec22ee..99e38977a 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -750,25 +750,17 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ // For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error. // This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns. // To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column. - // Check SQL_DESC_OCTET_LENGTH of the implementation parameter descriptor (IPD) to distinguish Smalldatetime or Datetime fields from Datetime2(0) or Datetime2(3) fields, as described in - // https://docs.microsoft.com/sql/relational-databases/native-client-odbc-date-time/metadata-parameter-and-result - // This has to be done before SQLBindParameter() if (stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP) { if (decimal_digits == 0 || decimal_digits == 3) { SQLHDESC hIpd = NULL; + SQLRETURN r = SQL_SUCCESS; + core::SQLGetStmtAttr(stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0); if (hIpd != NULL) { - SQLULEN octetLength = 0; - SQLINTEGER dummy = 0; - - SQLRETURN r = ::SQLGetDescField(hIpd, param_num + 1, SQL_DESC_OCTET_LENGTH, (SQLPOINTER)&octetLength, 0, &dummy); - if (SQL_SUCCEEDED(r)) { - // The octet length for datetime2 is 16 but no action required - if (octetLength == 8) { - r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER); - } else if (octetLength == 4) { - r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER); - } + if (decimal_digits == 0 && column_size == 16) { + r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER); + } else if (decimal_digits == 3) { + r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER); } CHECK_SQL_ERROR_OR_WARNING(r, stmt) { throw core::CoreException(); diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt index 1e1a9157b..136e8559c 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt @@ -63,7 +63,7 @@ try { dropTable($conn, $tableName); // Define the column definitions - $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(0)', 'c4' => 'datetime2(3)'); + $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(0)', 'c4' => 'datetime2(4)'); if ($qualified) { $tsql = createTableEncryptedQuery($conn, $tableName, $columns); @@ -76,7 +76,7 @@ try { $val1 = '9999-12-31 23:59:59'; $val2 = null; $val3 = null; - $val4 = '9999-12-31 23:59:59.999'; + $val4 = '9999-12-31 23:59:59.9999'; $tsql = "INSERT INTO $tableName (c1, c2, c3, c4) VALUES (?,?,?,?)"; $stmt = $conn->prepare($tsql); @@ -89,7 +89,7 @@ try { try { $stmt->execute(); } catch (PDOException $e) { - $error = ($qualified)? '*Datetime field overflow' : '*The conversion of a nvarchar data type to a smalldatetime data type resulted in an out-of-range value.'; + $error = ($qualified || isColEncrypted())? '*Datetime field overflow' : '*The conversion of a nvarchar data type to a smalldatetime data type resulted in an out-of-range value.'; if (!fnmatch($error, $e->getMessage())) { echo "The error message is unexpected:\n"; var_dump($e->getMessage()); @@ -139,6 +139,6 @@ array(4) { [2]=> string(19) "0001-01-01 01:01:01" [3]=> - string(23) "9999-12-31 23:59:59.999" + string(24) "9999-12-31 23:59:59.9999" } Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt index 68ed4f58a..62d8116d9 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt @@ -65,7 +65,7 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov dropTable($conn, $tableName); // Define the column definitions - $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(0)', 'c4' => 'datetime2(3)'); + $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(0)', 'c4' => 'datetime2(4)'); if ($qualified) { $tsql = createTableEncryptedQuery($conn, $tableName, $columns); @@ -82,7 +82,7 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov $val1 = '9999-12-31 23:59:59'; $val2 = null; $val3 = null; - $val4 = '9999-12-31 23:59:59.999'; + $val4 = '9999-12-31 23:59:59.9999'; $tsql = "INSERT INTO $tableName (c1, c2, c3, c4) VALUES (?,?,?,?)"; $params = array($val1, $val2, $val3, $val4); @@ -95,7 +95,7 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov if ($result) { echo "Inserting invalid values should have failed!\n"; } else { - $error = ($qualified)? '*Datetime field overflow' : '*The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value.'; + $error = ($qualified || AE\isDataEncrypted())? '*Datetime field overflow' : '*The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value.'; if (!fnmatch($error, sqlsrv_errors()[0]['message'])) { var_dump(sqlsrv_errors()); } @@ -148,6 +148,6 @@ array(4) { ["c3"]=> string(19) "0001-01-01 01:01:01" ["c4"]=> - string(23) "9999-12-31 23:59:59.999" + string(24) "9999-12-31 23:59:59.9999" } Done From 6349d06fee455362fbab75b4c9a4c5694a9dd248 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 12 Aug 2020 17:24:39 -0700 Subject: [PATCH 237/249] Further reverted changes to distinguish datetime fields (#1178) --- source/shared/core_stmt.cpp | 29 +-- .../pdo_ae_insert_datetime_encrypted.phpt | 8 +- .../sqlsrv_ae_insert_datetime_encrypted.phpt | 5 +- .../sqlsrv/sqlsrv_testConnection_unix.phpt | 208 ++++++++++++++++++ 4 files changed, 225 insertions(+), 25 deletions(-) create mode 100644 test/functional/sqlsrv/sqlsrv_testConnection_unix.phpt diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 99e38977a..bbe865051 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -746,32 +746,23 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ ind_ptr = SQL_NULL_DATA; } + core::SQLBindParameter( stmt, param_num + 1, direction, + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr ); + + // When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively. // For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error. // This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns. // To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column. + // Note this must be called after SQLBindParameter() or SQLSetDescField() may fail. + // TODO: how to correctly distinguish datetime from datetime2(3)? Both have the same decimal_digits and column_size if (stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP) { - if (decimal_digits == 0 || decimal_digits == 3) { - SQLHDESC hIpd = NULL; - SQLRETURN r = SQL_SUCCESS; - - core::SQLGetStmtAttr(stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0); - if (hIpd != NULL) { - if (decimal_digits == 0 && column_size == 16) { - r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER); - } else if (decimal_digits == 3) { - r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER); - } - CHECK_SQL_ERROR_OR_WARNING(r, stmt) { - throw core::CoreException(); - } - } + if (decimal_digits == 3) { + core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER); + } else if (decimal_digits == 0 && column_size == 16) { + core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER); } } - - core::SQLBindParameter( stmt, param_num + 1, direction, - c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr ); - } catch( core::CoreException& e ){ stmt->free_param_data(); diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt index 136e8559c..05d7c64e3 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt @@ -1,7 +1,7 @@ --TEST-- Test for inserting and retrieving encrypted data of datetime and smalldatetime types encrypted --DESCRIPTION-- -Verify that inserting into smalldatetime column might trigger "Datetime field overflow" error +Verify that inserting into smalldatetime column (if encrypted) might trigger "Datetime field overflow" error --SKIPIF-- --FILE-- @@ -45,7 +45,7 @@ function createTablePlainQuery($conn, $tableName, $columns) try { // This test requires to connect with the Always Encrypted feature // First check if the system is qualified to run this test - $dsn = getDSN($server, null); + $dsn = "sqlsrv:Server=$server; Database=$databaseName;"; $conn = new PDO($dsn, $uid, $pwd); $qualified = isAEQualified($conn) && (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); @@ -89,9 +89,9 @@ try { try { $stmt->execute(); } catch (PDOException $e) { - $error = ($qualified || isColEncrypted())? '*Datetime field overflow' : '*The conversion of a nvarchar data type to a smalldatetime data type resulted in an out-of-range value.'; + $error = ($qualified)? '*Datetime field overflow' : '*The conversion of a nvarchar data type to a smalldatetime data type resulted in an out-of-range value.'; if (!fnmatch($error, $e->getMessage())) { - echo "The error message is unexpected:\n"; + echo "Expected $error but got:\n"; var_dump($e->getMessage()); } } diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt index 62d8116d9..f57dab9c7 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt @@ -1,7 +1,7 @@ --TEST-- Test for inserting and retrieving encrypted data of datetime and smalldatetime types encrypted --DESCRIPTION-- -Verify that inserting into smalldatetime column might trigger "Datetime field overflow" error +Verify that inserting into smalldatetime column (if encrypted) might trigger "Datetime field overflow" error --SKIPIF-- --FILE-- @@ -95,8 +95,9 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov if ($result) { echo "Inserting invalid values should have failed!\n"; } else { - $error = ($qualified || AE\isDataEncrypted())? '*Datetime field overflow' : '*The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value.'; + $error = ($qualified)? '*Datetime field overflow' : '*The conversion of a varchar data type to a smalldatetime data type resulted in an out-of-range value.'; if (!fnmatch($error, sqlsrv_errors()[0]['message'])) { + echo "Expected $error but got:\n"; var_dump(sqlsrv_errors()); } } diff --git a/test/functional/sqlsrv/sqlsrv_testConnection_unix.phpt b/test/functional/sqlsrv/sqlsrv_testConnection_unix.phpt new file mode 100644 index 000000000..1ce203956 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_testConnection_unix.phpt @@ -0,0 +1,208 @@ +--TEST-- +variety of connection parameters. +--DESCRIPTION-- +The equivalent of sqlsrv_testConnection.phpt but for running outside Windows to verify that GitHub issue 430 is fixed +--SKIPIF-- + +--FILE-- + $userName, "PWD" => "$userPassword;Driver={SQL Server Native Client 11.0}}" )); + + if (!$conn) { + var_dump(sqlsrv_errors()); + } else { + sqlsrv_close($conn); + die("Shouldn't have opened the connection."); + } + + echo "Test sqlsrv_connect with driver injection (2)\n"; + $conn = sqlsrv_connect( $server, array( "UID" => $userName, "PWD" => "{$userPassword};Driver={SQL Server Native Client 11.0}" )); + + if (!$conn) { + var_dump(sqlsrv_errors()); + } else { + sqlsrv_close($conn); + die("Shouldn't have opened the connection."); + } + + echo "Test sqlsrv_connect with driver injection (3)\n"; + $conn = sqlsrv_connect( $server, array( "UID" => "sa", "PWD" => "{$userPassword}};Driver={SQL Server Native Client 11.0}" )); + + if (!$conn) { + var_dump(sqlsrv_errors()); + } else { + sqlsrv_close($conn); + die("Shouldn't have opened the connection."); + } + + // Test a bunch of options. The Failover_Partner keyword does not work + // on Unix, so we replace it with MultiSubnetFailover instead. + $conn_options_all = array( "APP" => "PHP Unit Test", + "ConnectionPooling" => true, + "Database" => $databaseName, + "Encrypt" => 0, + "LoginTimeout" => 120, + "MultipleActiveResultSets" => false, + "QuotedId" => false, + "TraceOn" => true, + "TraceFile" => "trace.odbc", + "TransactionIsolation" => SQLSRV_TXN_READ_COMMITTED, + "TrustServerCertificate" => 1, + "WSID" => "JAYKINT1" ); + $conn_options_int = array( "APP" => "PHP Unit Test", + "ConnectionPooling" => false, + "Database" => $databaseName, + "Encrypt" => 0, + "LoginTimeout" => 120, + "MultipleActiveResultSets" => false, + "QuotedId" => true, + "TraceOn" => true, + "TraceFile" => "trace.odbc", + "TransactionIsolation" => SQLSRV_TXN_READ_COMMITTED, + "TrustServerCertificate" => 1, + "WSID" => "JAYKINT1" ); + + echo "Test sqlsrv_connect with all options\n"; + $conn_options_all['MultiSubnetFailover'] = true; + $conn = connect($conn_options_all); + print_r(sqlsrv_errors()[0]); + print_r(sqlsrv_errors()[1]); + if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); + } + + echo "Test sqlsrv_connect with all options and integrated auth\n"; + $conn_options_int['MultiSubnetFailover'] = true; + $conn = connect($conn_options_int); + print_r(sqlsrv_errors()[0]); + print_r(sqlsrv_errors()[1]); + if ($conn === false) { + die(print_r(sqlsrv_errors(), true)); + } + + sqlsrv_close($conn); + echo "Test succeeded.\n"; +?> +--EXPECTREGEX-- +Test sqlsrv_connect with driver injection +array\(1\) \{ + \[0\]=> + array\(6\) \{ + \[0\]=> + string\(5\) "28000" + \["SQLSTATE"\]=> + string\(5\) "28000" + \[1\]=> + int\(18456\) + \["code"\]=> + int\(18456\) + \[2\]=> + string\(81\) ".*Login failed for user 'sa'." + \["message"\]=> + string\(81\) ".*Login failed for user 'sa'." + \} +\} +Test sqlsrv_connect with driver injection \(2\) +array\(1\) \{ + \[0\]=> + array\(6\) \{ + \[0\]=> + string\(5\) "IMSSP" + \["SQLSTATE"\]=> + string\(5\) "IMSSP" + \[1\]=> + int\(-4\) + \["code"\]=> + int\(-4\) + \[2\]=> + string\(140\) "An unescaped right brace \(\}\) was found in either the user name or password. All right braces must be escaped with another right brace \(\}\}\)." + \["message"\]=> + string\(140\) "An unescaped right brace \(\}\) was found in either the user name or password. All right braces must be escaped with another right brace \(\}\}\)." + \} +\} +Test sqlsrv_connect with driver injection \(3\) +array\(1\) \{ + \[0\]=> + array\(6\) \{ + \[0\]=> + string\(5\) "IMSSP" + \["SQLSTATE"\]=> + string\(5\) "IMSSP" + \[1\]=> + int\(-4\) + \["code"\]=> + int\(-4\) + \[2\]=> + string\(140\) "An unescaped right brace \(\}\) was found in either the user name or password. All right braces must be escaped with another right brace \(\}\}\)." + \["message"\]=> + string\(140\) "An unescaped right brace \(\}\) was found in either the user name or password. All right braces must be escaped with another right brace \(\}\}\)." + \} +\} +Test sqlsrv_connect with all options +Array +\( + \[0\] => 01000 + \[SQLSTATE\] => 01000 + \[1\] => 5701 + \[code\] => 5701 + \[2\] => .*Changed database context to '.*'. + \[message\] => .*Changed database context to '.*'. +\) +Array +\( + \[0\] => 01000 + \[SQLSTATE\] => 01000 + \[1\] => 5703 + \[code\] => 5703 + \[2\] => .*Changed language setting to us_english. + \[message\] => .*Changed language setting to us_english. +\) +Test sqlsrv_connect with all options and integrated auth +Array +\( + \[0\] => 01000 + \[SQLSTATE\] => 01000 + \[1\] => 5701 + \[code\] => 5701 + \[2\] => .*Changed database context to '.*'. + \[message\] => .*Changed database context to '.*'. +\) +Array +\( + \[0\] => 01000 + \[SQLSTATE\] => 01000 + \[1\] => 5703 + \[code\] => 5703 + \[2\] => .*Changed language setting to us_english. + \[message\] => .*Changed language setting to us_english. +\) +Test succeeded. From 721d8e7b041bda33f51cd8cf08745e1e6dff26ed Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 13 Aug 2020 12:22:07 -0700 Subject: [PATCH 238/249] Added tests for passwords with non alpha characters and braces (#1179) --- .../pdo_test_passwords_with_braces.phpt | 80 +++++++++++++++++++ .../sqlsrv_test_passwords_with_braces.phpt | 75 +++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_test_passwords_with_braces.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_test_passwords_with_braces.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_test_passwords_with_braces.phpt b/test/functional/pdo_sqlsrv/pdo_test_passwords_with_braces.phpt new file mode 100644 index 000000000..88e548523 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_test_passwords_with_braces.phpt @@ -0,0 +1,80 @@ +--TEST-- +Test passwords with non alphanumeric characters and braces +--DESCRIPTION-- +The first two cases should fail with a message about login failures. Only the last case fails because the right curly brace was not escaped with another right brace. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +getMessage())) { + echo "Expected $error but got:\n"; + var_dump($e->getMessage()); + } +} + +try { + $randomPwd = generateRandomPassword(); + trace($randomPwd . PHP_EOL); + $conn = new PDO("sqlsrv:Server=$server;", $uid, $randomPwd); + + echo "Incorrect password '$randomPwd' with right braces should have failed!" . PHP_EOL; +} catch (PDOException $e) { + $error = '*Login failed for user*'; + if (!fnmatch($error, $e->getMessage())) { + echo "Expected $error but got:\n"; + var_dump($e->getMessage()); + } +} + +try { + $randomPwd = generateRandomPassword(true, false); + trace($randomPwd . PHP_EOL); + $conn = new PDO("sqlsrv:Server=$server;", $uid, $randomPwd); + + echo ("Shouldn't have connected without escaping braces!" . PHP_EOL); +} catch (PDOException $e) { + echo $e->getMessage() . PHP_EOL; +} + +echo "Done" . PHP_EOL; +?> +--EXPECT-- +SQLSTATE[IMSSP]: An unescaped right brace (}) was found in either the user name or password. All right braces must be escaped with another right brace (}}). +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_test_passwords_with_braces.phpt b/test/functional/sqlsrv/sqlsrv_test_passwords_with_braces.phpt new file mode 100644 index 000000000..50a85c9ef --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_test_passwords_with_braces.phpt @@ -0,0 +1,75 @@ +--TEST-- +Test passwords with non alphanumeric characters and braces +--DESCRIPTION-- +The first two cases should fail with a message about login failures. Only the last case fails because the right curly brace was not escaped with another right brace. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + $userName, "pwd" => $randomPwd)); +checkErrorMessages($conn, 'Password without right braces', $randomPwd); + +$randomPwd = generateRandomPassword(); +trace($randomPwd . PHP_EOL); +$conn = sqlsrv_connect($server, array("UID" => $userName, "pwd" => $randomPwd)); +checkErrorMessages($conn, 'Password with right braces', $randomPwd); + +$randomPwd = generateRandomPassword(true, false); +trace($randomPwd . PHP_EOL); +$conn = sqlsrv_connect($server, array("UID" => $userName, "pwd" => $randomPwd)); +if ($conn) { + echo ("Shouldn't have connected without escaping braces!" . PHP_EOL); +} +$errors = sqlsrv_errors(); +echo $errors[0]["message"] . PHP_EOL; + +echo "Done" . PHP_EOL; +?> +--EXPECT-- +An unescaped right brace (}) was found in either the user name or password. All right braces must be escaped with another right brace (}}). +Done \ No newline at end of file From 402403e7540d79fe4587cfe2c5cd3d8d71230bda Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 14 Aug 2020 16:37:54 -0700 Subject: [PATCH 239/249] Allowed fetching numerics as binaries (#1180) --- source/shared/core_stmt.cpp | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index bbe865051..cc0028dd2 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2577,25 +2577,19 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) { sqlsrv_php_type.typeinfo.encoding = stmt->conn->encoding(); } - // For numbers, no need to convert - if (is_a_numeric_type(sql_field_type)) { - sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_CHAR; - } - // Set the C type and account for null characters at the end of the data. - switch( sqlsrv_php_type.typeinfo.encoding ) { - case CP_UTF8: - c_type = SQL_C_WCHAR; - extra = sizeof( SQLWCHAR ); - break; - case SQLSRV_ENCODING_BINARY: + if (sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_BINARY) { c_type = SQL_C_BINARY; extra = 0; - break; - default: + } else { c_type = SQL_C_CHAR; - extra = sizeof( SQLCHAR ); - break; + extra = sizeof(SQLCHAR); + + // For numbers, no need to convert + if (sqlsrv_php_type.typeinfo.encoding == CP_UTF8 && !is_a_numeric_type(sql_field_type)) { + c_type = SQL_C_WCHAR; + extra = sizeof(SQLWCHAR); + } } // If this is a large type, then read the first few bytes to get the actual length from SQLGetData @@ -2708,8 +2702,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind } } // if( r == SQL_SUCCESS_WITH_INFO ) - if( sqlsrv_php_type.typeinfo.encoding == SQLSRV_ENCODING_UTF8 ) { - + if (c_type == SQL_C_WCHAR) { bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), &field_value_temp, field_len_temp ); @@ -2752,8 +2745,7 @@ void get_field_as_string( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_ind return; } - if( sqlsrv_php_type.typeinfo.encoding == CP_UTF8 ) { - + if (c_type == SQL_C_WCHAR) { bool converted = convert_string_from_utf16_inplace( static_cast( sqlsrv_php_type.typeinfo.encoding ), &field_value_temp, field_len_temp ); From 9f1f27ec7915bd3d814177191aaf12d630331e61 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 19 Aug 2020 10:51:32 -0700 Subject: [PATCH 240/249] Updated zend api calls for php 8 beta2 (#1181) --- appveyor.yml | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index d123fbd47..942d557d4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,7 +34,7 @@ environment: SQL_INSTANCE: SQL2019 PHP_VC: vs16 PHP_MAJOR_VER: 8.0 - PHP_MINOR_VER: 0beta1 + PHP_MINOR_VER: 0beta2 PHP_EXE_PATH: Release THREAD: nts platform: x86 diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 8cda4670d..0de83d783 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -588,15 +588,23 @@ void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error) int zr = object_init_ex( &ex_obj, ex_class ); SQLSRV_ASSERT( zr != FAILURE, "Failed to initialize exception object" ); +#if PHP_VERSION_ID >= 80000 + zend_object *zendobj = Z_OBJ_P(&ex_obj); +#endif + sqlsrv_malloc_auto_ptr ex_msg; size_t ex_msg_len = strnlen_s(reinterpret_cast(error->native_message)) + SQL_SQLSTATE_BUFSIZE + 12 + 1; // 12 = "SQLSTATE[]: " ex_msg = reinterpret_cast(sqlsrv_malloc(ex_msg_len)); snprintf(ex_msg, ex_msg_len, EXCEPTION_MSG_TEMPLATE, error->sqlstate, error->native_message); - zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof( EXCEPTION_PROPERTY_MSG ) - 1, - ex_msg ); - zend_update_property_string( ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof( EXCEPTION_PROPERTY_CODE ) - 1, - reinterpret_cast( error->sqlstate ) ); + +#if PHP_VERSION_ID < 80000 + zend_update_property_string(ex_class, &ex_obj, EXCEPTION_PROPERTY_MSG, sizeof(EXCEPTION_PROPERTY_MSG) - 1, ex_msg); + zend_update_property_string(ex_class, &ex_obj, EXCEPTION_PROPERTY_CODE, sizeof(EXCEPTION_PROPERTY_CODE) - 1, reinterpret_cast(error->sqlstate)); +#else + zend_update_property_string(ex_class, zendobj, EXCEPTION_PROPERTY_MSG, sizeof(EXCEPTION_PROPERTY_MSG) - 1, ex_msg); + zend_update_property_string(ex_class, zendobj, EXCEPTION_PROPERTY_CODE, sizeof(EXCEPTION_PROPERTY_CODE) - 1, reinterpret_cast(error->sqlstate)); +#endif zval ex_error_info; ZVAL_UNDEF( &ex_error_info ); @@ -609,8 +617,11 @@ void pdo_sqlsrv_throw_exception(_In_ sqlsrv_error const* error) //zend_update_property makes an entry in the properties_table in ex_obj point to the Z_ARRVAL( ex_error_info ) //and the refcount of the zend_array is incremented by 1 - zend_update_property( ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof( EXCEPTION_PROPERTY_ERRORINFO ) - 1, - &ex_error_info ); +#if PHP_VERSION_ID < 80000 + zend_update_property(ex_class, &ex_obj, EXCEPTION_PROPERTY_ERRORINFO, sizeof(EXCEPTION_PROPERTY_ERRORINFO) - 1, &ex_error_info); +#else + zend_update_property(ex_class, zendobj, EXCEPTION_PROPERTY_ERRORINFO, sizeof(EXCEPTION_PROPERTY_ERRORINFO) - 1, &ex_error_info); +#endif //DELREF ex_error_info here to decrement the refcount of the zend_array is 1 //the global hashtable EG(exception) then points to the zend_object in ex_obj in zend_throw_exception_object; From f2cae4c34f40363137021f85aae05567d8b545ff Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 1 Sep 2020 16:54:26 -0700 Subject: [PATCH 241/249] zend_list_close() returns void starting in php 8 beta3 (#1184) --- source/sqlsrv/conn.cpp | 21 +++++++++++++++++---- source/sqlsrv/stmt.cpp | 8 ++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index b083aa1e7..7009a3376 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -812,9 +812,13 @@ PHP_FUNCTION( sqlsrv_close ) // cause any variables still holding a reference to this to be invalid so they cause // an error when passed to a sqlsrv function. There's nothing we can do if the // removal fails, so we just log it and move on. - if( zend_list_close( Z_RES_P( conn_r ) ) == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P( conn_r )); +#if PHP_VERSION_ID < 80000 + if (zend_list_close(Z_RES_P(conn_r)) == FAILURE) { + LOG(SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P(conn_r)); } +#else + zend_list_close(Z_RES_P(conn_r)); +#endif // when conn_r is first parsed in zend_parse_parameters, conn_r becomes a zval that points to a zend_resource with a refcount of 2 // need to DELREF here so the refcount becomes 1 and conn_r can be appropriate destroyed by the garbage collector when it goes out of scope @@ -1265,9 +1269,14 @@ PHP_FUNCTION( sqlsrv_query ) void free_stmt_resource( _Inout_ zval* stmt_z ) { - if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) { +#if PHP_VERSION_ID < 80000 + if (FAILURE == zend_list_close(Z_RES_P(stmt_z))) { LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z)); } +#else + zend_list_close(Z_RES_P(stmt_z)); +#endif + ZVAL_NULL( stmt_z ); zval_ptr_dtor(stmt_z); } @@ -1317,9 +1326,13 @@ void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn ) // this would call the destructor on the statement. // There's nothing we can do if the removal fails, so we just log it and move on. - if( zend_list_close( Z_RES_P(rsrc_ptr) ) == FAILURE ) { +#if PHP_VERSION_ID < 80000 + if (zend_list_close(Z_RES_P(rsrc_ptr)) == FAILURE) { LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr)); } +#else + zend_list_close(Z_RES_P(rsrc_ptr)); +#endif } ZEND_HASH_FOREACH_END(); zend_hash_destroy( conn->stmts ); diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index ea1bfe546..a9b4590bf 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1396,9 +1396,13 @@ PHP_FUNCTION( sqlsrv_free_stmt ) } // delete the resource from Zend's master list, which will trigger the statement's destructor - if( zend_list_close( Z_RES_P(stmt_r) ) == FAILURE ) { - LOG( SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P( stmt_r )->handle); +#if PHP_VERSION_ID < 80000 + if (zend_list_close(Z_RES_P(stmt_r)) == FAILURE) { + LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P(stmt_r)->handle); } +#else + zend_list_close(Z_RES_P(stmt_r)); +#endif // when stmt_r is first parsed in zend_parse_parameters, stmt_r becomes a zval that points to a zend_resource with a refcount of 2 // need to DELREF here so the refcount becomes 1 and stmt_r can be appropriate destroyed by the garbage collector when it goes out of scope From 86adf470cbb147bf10282914bdadfdb312d62dd5 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 2 Sep 2020 10:39:54 -0700 Subject: [PATCH 242/249] Added rank info to sensitivity classification (#1183) --- source/shared/core_conn.cpp | 3 + source/shared/core_sqlsrv.h | 10 +- source/shared/core_stmt.cpp | 21 +- source/shared/core_util.cpp | 29 +- source/shared/msodbcsql.h | 5 + .../pdo_data_classification_ranks.phpt | 357 ++++++++++++++++++ .../sqlsrv_data_classification_ranks.phpt | 347 +++++++++++++++++ 7 files changed, 763 insertions(+), 9 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_data_classification_ranks.phpt create mode 100644 test/functional/sqlsrv/sqlsrv_data_classification_ranks.phpt diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index f3b93bf1a..e39363a93 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -383,6 +383,9 @@ SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& con sqlsrv_malloc_auto_ptr wconn_string; unsigned int wconn_len = static_cast( conn_str.length() + 1 ) * sizeof( SQLWCHAR ); + // Set the desired data classification version before connecting, but older ODBC drivers will generate a warning message 'Driver's SQLSetConnectAttr failed' + SQLSetConnectAttr(conn->handle(), SQL_COPT_SS_DATACLASSIFICATION_VERSION, reinterpret_cast(data_classification::VERSION_RANK_AVAILABLE), SQL_IS_POINTER); + // We only support UTF-8 encoding for connection string. // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast( conn_str.length() ), &wconn_len, true ); diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index f1cd5e3db..1b148f743 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -1440,13 +1440,15 @@ struct sqlsrv_output_param { }; namespace data_classification { + const int VERSION_RANK_AVAILABLE = 2; // Rank info is available when data classification version is 2+ + const int RANK_NOT_DEFINED = -1; // *** data classficiation metadata structures and helper methods -- to store and/or process the sensitivity classification data *** struct name_id_pair; struct sensitivity_metadata; void name_id_pair_free(name_id_pair * pair); void parse_sensitivity_name_id_pairs(_Inout_ sqlsrv_stmt* stmt, _Inout_ USHORT& numpairs, _Inout_ std::vector>* pairs, _Inout_ unsigned char **pptr); - void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr); + void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr, _In_ bool getRankInfo); USHORT fill_column_sensitivity_array(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno, _Inout_ zval *column_data); struct name_id_pair { @@ -1467,8 +1469,9 @@ namespace data_classification { struct label_infotype_pair { USHORT label_idx; USHORT infotype_idx; + int rank; // Default value is "not defined" - label_infotype_pair() : label_idx(0), infotype_idx(0) + label_infotype_pair() : label_idx(0), infotype_idx(0), rank(RANK_NOT_DEFINED) { } }; @@ -1494,8 +1497,9 @@ namespace data_classification { std::vector> infotypes; USHORT num_columns; std::vector columns_sensitivity; + int rank; // Default value is "not defined" - sensitivity_metadata() : num_labels(0), num_infotypes(0), num_columns(0) + sensitivity_metadata() : num_labels(0), num_infotypes(0), num_columns(0), rank(RANK_NOT_DEFINED) { } diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index cc0028dd2..6ef4e1cb9 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1017,7 +1017,8 @@ field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQL void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt ) { sqlsrv_malloc_auto_ptr dcbuf; - SQLINTEGER dclen = 0; + DWORD dcVersion = 0; + SQLINTEGER dclen = 0, dcIRD = 0; SQLINTEGER dclenout = 0; SQLHANDLE ird; SQLRETURN r; @@ -1039,14 +1040,14 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt ) // Reference: https://docs.microsoft.com/sql/connect/odbc/data-classification // To retrieve sensitivity classfication data, the first step is to retrieve the IRD(Implementation Row Descriptor) handle by // calling SQLGetStmtAttr with SQL_ATTR_IMP_ROW_DESC statement attribute - r = ::SQLGetStmtAttr(stmt->handle(), SQL_ATTR_IMP_ROW_DESC, (SQLPOINTER)&ird, SQL_IS_POINTER, 0); + r = ::SQLGetStmtAttr(stmt->handle(), SQL_ATTR_IMP_ROW_DESC, reinterpret_cast(&ird), SQL_IS_POINTER, 0); CHECK_SQL_ERROR_OR_WARNING(r, stmt) { LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in getting Implementation Row Descriptor handle." ); throw core::CoreException(); } // First call to get dclen - r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, dcbuf, 0, &dclen); + r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, reinterpret_cast(dcbuf.get()), 0, &dclen); if (r != SQL_SUCCESS || dclen == 0) { // log the error first LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW first time." ); @@ -1073,7 +1074,7 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt ) // Call again to read SQL_CA_SS_DATA_CLASSIFICATION data dcbuf = static_cast(sqlsrv_malloc(dclen * sizeof(char))); - r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, dcbuf, dclen, &dclenout); + r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION, reinterpret_cast(dcbuf.get()), dclen, &dclenout); if (r != SQL_SUCCESS) { LOG(SEV_ERROR, "core_sqlsrv_sensitivity_metadata: failed in calling SQLGetDescFieldW again." ); @@ -1084,6 +1085,16 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt ) // Start parsing the data (blob) using namespace data_classification; + + // If make it this far, must be using ODBC 17.2 or above. Prior to ODBC 17.4, checking Data Classification version will fail. + // When the function is successful and the version is right, rank info is available for retrieval + bool getRankInfo = false; + r = ::SQLGetDescFieldW(ird, 0, SQL_CA_SS_DATA_CLASSIFICATION_VERSION, reinterpret_cast(&dcVersion), SQL_IS_INTEGER, &dcIRD); + if (r == SQL_SUCCESS && dcVersion >= VERSION_RANK_AVAILABLE) { + getRankInfo = true; + } + + // Start parsing the data (blob) unsigned char *dcptr = dcbuf; sqlsrv_malloc_auto_ptr sensitivity_meta; @@ -1094,7 +1105,7 @@ void core_sqlsrv_sensitivity_metadata( _Inout_ sqlsrv_stmt* stmt ) parse_sensitivity_name_id_pairs(stmt, sensitivity_meta->num_infotypes, &sensitivity_meta->infotypes, &dcptr); // Next parse the sensitivity properties - parse_column_sensitivity_props(sensitivity_meta, &dcptr); + parse_column_sensitivity_props(sensitivity_meta, &dcptr, getRankInfo); unsigned char *dcend = dcbuf; dcend += dclen; diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index 4bcdff31e..d616d78d0 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -489,6 +489,7 @@ namespace data_classification { const char* INFOTYPE = "Information Type"; const char* NAME = "name"; const char* ID = "id"; + const char* RANK = "rank"; void convert_sensivity_field(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_ENCODING encoding, _In_ unsigned char *ptr, _In_ int len, _Inout_updates_bytes_(cchOutLen) char** field_name) { @@ -566,10 +567,18 @@ namespace data_classification { *pptr = ptr; } - void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr) + void parse_column_sensitivity_props(_Inout_ sensitivity_metadata* meta, _Inout_ unsigned char **pptr, _In_ bool getRankInfo) { unsigned char *ptr = *pptr; unsigned short ncols; + int queryrank, colrank; + + // Get rank info + if (getRankInfo) { + queryrank = *(reinterpret_cast(ptr)); + ptr += sizeof(long); + meta->rank = queryrank; + } // Get number of columns meta->num_columns = ncols = *(reinterpret_cast(ptr)); @@ -594,6 +603,12 @@ namespace data_classification { typeidx = *(reinterpret_cast(ptr)); ptr += sizeof(unsigned short); + if (getRankInfo) { + colrank = *(reinterpret_cast(ptr)); + ptr += sizeof(long); + pair.rank = colrank; + } + pair.label_idx = labelidx; pair.infotype_idx = typeidx; @@ -641,6 +656,7 @@ namespace data_classification { USHORT labelidx = meta->columns_sensitivity[colno].label_info_pairs[j].label_idx; USHORT typeidx = meta->columns_sensitivity[colno].label_info_pairs[j].infotype_idx; + int column_rank = meta->columns_sensitivity[colno].label_info_pairs[j].rank; char *label = meta->labels[labelidx]->name; char *label_id = meta->labels[labelidx]->id; @@ -657,10 +673,21 @@ namespace data_classification { add_assoc_zval(&sensitivity_properties, INFOTYPE, &infotype_array); + // add column sensitivity rank info to sensitivity_properties + if (column_rank > RANK_NOT_DEFINED) { + add_assoc_long(&sensitivity_properties, RANK, column_rank); + } + // add the pair of sensitivity properties to data_classification add_next_index_zval(&data_classification, &sensitivity_properties); } + // add query sensitivity rank info to data_classification + int query_rank = meta->rank; + if (query_rank > RANK_NOT_DEFINED) { + add_assoc_long(&data_classification, RANK, query_rank); + } + // add data classfication as associative array add_assoc_zval(return_array, DATA_CLASS, &data_classification); diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index bcca30870..7ed1c33ea 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -94,6 +94,10 @@ #define SQL_COPT_SS_AUTHENTICATION (SQL_COPT_SS_BASE_EX+15)// The authentication method used for the connection #define SQL_COPT_SS_ACCESS_TOKEN (SQL_COPT_SS_BASE_EX+16)// The authentication access token used for the connection +/* SQLSetConnectAttr MS driver additional specific defines. */ +#define SQL_COPT_SS_BASE_ADD 1400 +#define SQL_COPT_SS_DATACLASSIFICATION_VERSION (SQL_COPT_SS_BASE_ADD + 0) // The flag to Set/Get DATACLASSIFICATION version support + // SQLColAttributes driver specific defines. // SQLSetDescField/SQLGetDescField driver specific defines. // Microsoft has 1200 thru 1249 reserved for Microsoft ODBC Driver for SQL Server usage. @@ -146,6 +150,7 @@ // Data Classification #define SQL_CA_SS_DATA_CLASSIFICATION (SQL_CA_SS_BASE+37) // retrieve data classification information +#define SQL_CA_SS_DATA_CLASSIFICATION_VERSION (SQL_CA_SS_BASE+38) // retrieve data classification version #define SQL_CA_SS_MAX_USED (SQL_CA_SS_BASE+38) diff --git a/test/functional/pdo_sqlsrv/pdo_data_classification_ranks.phpt b/test/functional/pdo_sqlsrv/pdo_data_classification_ranks.phpt new file mode 100644 index 000000000..f97be99e5 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_data_classification_ranks.phpt @@ -0,0 +1,357 @@ +--TEST-- +Test data classification feature - retrieving sensitivity metadata if supported +--DESCRIPTION-- +If both ODBC and server support this feature, this test verifies that sensitivity metadata can be added and correctly retrieved. If not, it will at least test the new statement attribute and some error cases. +T-SQL reference: https://docs.microsoft.com/sql/t-sql/statements/add-sensitivity-classification-transact-sql +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "NONE", 10 => "LOW", 20 => "MEDIUM", 30 => "HIGH", 40 => "CRITICAL"); + +function testConnAttrCases() +{ + // Attribute PDO::SQLSRV_ATTR_DATA_CLASSIFICATION is limited to statement level only + global $server, $databaseName, $driver, $uid, $pwd; + + $stmtErr = '*The given attribute is only supported on the PDOStatement object.'; + $noSupportErr = '*driver does not support that attribute'; + + try { + $dsn = getDSN($server, $databaseName, $driver); + $attr = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => true); + $conn = new PDO($dsn, $uid, $pwd, $attr); + } catch (PDOException $e) { + if (!fnmatch($stmtErr, $e->getMessage())) { + echo "Connection attribute test (1) unexpected\n"; + var_dump($e->getMessage()); + } + } + + try { + $dsn = getDSN($server, $databaseName, $driver); + $attr = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + $conn = new PDO($dsn, $uid, $pwd, $attr); + $conn->setAttribute(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION, true); + } catch (PDOException $e) { + if (!fnmatch($stmtErr, $e->getMessage())) { + echo "Connection attribute test (2) unexpected\n"; + var_dump($e->getMessage()); + } + } + + try { + $dsn = getDSN($server, $databaseName, $driver); + $attr = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + $conn = new PDO($dsn, $uid, $pwd, $attr); + $conn->getAttribute(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION); + } catch (PDOException $e) { + if (!fnmatch($noSupportErr, $e->getMessage())) { + echo "Connection attribute test (3) unexpected\n"; + var_dump($e->getMessage()); + } + } +} + +function testNotAvailable($conn, $tableName, $isSupported, $driverCapable) +{ + // If supported, the query should return a column with no classification + $options = array(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => true); + $tsql = ($isSupported)? "SELECT PatientId FROM $tableName" : "SELECT * FROM $tableName"; + $stmt = $conn->prepare($tsql, $options); + $stmt->execute(); + + $notAvailableErr = '*Failed to retrieve Data Classification Sensitivity Metadata. If the driver and the server both support the Data Classification feature, check whether the query returns columns with classification information.'; + + $unexpectedErrorState = '*Failed to retrieve Data Classification Sensitivity Metadata: Check if ODBC driver or the server supports the Data Classification feature.'; + + $error = ($driverCapable) ? $notAvailableErr : $unexpectedErrorState; + try { + $metadata = $stmt->getColumnMeta(0); + echo "testNotAvailable: expected getColumnMeta to fail\n"; + } catch (PDOException $e) { + if (!fnmatch($error, $e->getMessage())) { + echo "testNotAvailable: exception unexpected\n"; + var_dump($e->getMessage()); + } + } +} + +function isDataClassSupported($conn, &$driverCapable) +{ + // Check both SQL Server version and ODBC driver version + $msodbcsqlVer = $conn->getAttribute(PDO::ATTR_CLIENT_VERSION)["DriverVer"]; + $version = explode(".", $msodbcsqlVer); + + // ODBC Driver must be 17.2 or above + $driverCapable = true; + if ($version[0] < 17 || $version[1] < 2) { + $driverCapable = false; + return false; + } + + // SQL Server must be SQL Server 2019 or above + $serverVer = $conn->getAttribute(PDO::ATTR_SERVER_VERSION); + if (explode('.', $serverVer)[0] < 15) + return false; + + return true; +} + +function getRegularMetadata($conn, $tsql) +{ + // Run the query without data classification metadata + $stmt1 = $conn->query($tsql); + + // Run the query with the attribute set to false + $options = array(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => false); + $stmt2 = $conn->prepare($tsql, $options); + $stmt2->execute(); + + // The metadata for each column should be identical + $numCol = $stmt1->columnCount(); + for ($i = 0; $i < $numCol; $i++) { + $metadata1 = $stmt1->getColumnMeta($i); + $metadata2 = $stmt2->getColumnMeta($i); + + $diff = array_diff($metadata1, $metadata2); + if (!empty($diff)) { + print_r($diff); + } + } + + return $stmt1; +} + +function verifyClassInfo($rank, $input, $actual) +{ + // For simplicity of this test, only one set of sensitivity data (Label, Information Type) + // plus overall rank info + if (count($actual) != 2) { + echo "Expected an array with only two elements\n"; + return false; + } + + if (count($actual[0]) != 3) { + echo "Expected a Label pair and Information Type pair plus column rank info\n"; + return false; + } + + // Label should be name and id pair (id should be empty) + if (count($actual[0]['Label']) != 2) { + echo "Expected only two elements for the label\n"; + return false; + } + $label = $input[0]; + if ($actual[0]['Label']['name'] !== $label || !empty($actual[0]['Label']['id'])){ + return false; + } + + // Like Label, Information Type should also be name and id pair (id should be empty) + if (count($actual[0]['Information Type']) != 2) { + echo "Expected only two elements for the information type\n"; + return false; + } + $info = $input[1]; + if ($actual[0]['Information Type']['name'] !== $info || !empty($actual[0]['Information Type']['id'])){ + return false; + } + + if ($actual[0]['rank'] != $rank) { + return false; + } + + if ($actual['rank'] != $rank) { + return false; + } + + return true; +} + +function assignDataClassification($conn, $tableName, $classData, $rankId = 0) +{ + global $ranks; + + $rank = ", RANK = $ranks[$rankId]"; + + // column SSN + $label = $classData[1][0]; + $infoType = $classData[1][1]; + $sql = "ADD SENSITIVITY CLASSIFICATION TO [$tableName].SSN WITH (LABEL = '$label', INFORMATION_TYPE = '$infoType' $rank)"; + $conn->query($sql); + + // column BirthDate + $label = $classData[4][0]; + $infoType = $classData[4][1]; + $sql = "ADD SENSITIVITY CLASSIFICATION TO [$tableName].BirthDate WITH (LABEL = '$label', INFORMATION_TYPE = '$infoType' $rank)"; + $conn->query($sql); +} + +function compareDataClassification($stmt1, $stmt2, $classData, $rank) +{ + global $dataClassKey; + + $numCol = $stmt1->columnCount(); + $noClassInfo = array($dataClassKey => array()); + + for ($i = 0; $i < $numCol; $i++) { + $metadata1 = $stmt1->getColumnMeta($i); + $metadata2 = $stmt2->getColumnMeta($i); + + // If classification sensitivity data exists, only the + // 'flags' field should be different + foreach ($metadata2 as $key => $value) { + if ($key == 'flags') { + // Is classification input data empty? + if (empty($classData[$i])) { + // Then it should be equivalent to $noClassInfo + if ($value !== $noClassInfo) { + var_dump($value); + } + } else { + // Verify the classification metadata + if (!verifyClassInfo($rank, $classData[$i], $value[$dataClassKey])) { + var_dump($value); + } + } + } else { + // The other fields should be identical + if ($metadata1[$key] !== $value) { + var_dump($value); + } + } + } + } +} + +function runBatchQuery($conn, $tableName) +{ + global $dataClassKey; + + $options = array(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => true); + $tsql = "SELECT SSN, BirthDate FROM $tableName"; + + // Run a batch query + $batchQuery = $tsql . ';' . $tsql; + $stmt = $conn->prepare($batchQuery, $options); + $stmt->execute(); + + $numCol = $stmt->columnCount(); + + // The metadata returned should be the same + $c = rand(0, $numCol - 1); + $metadata1 = $stmt->getColumnMeta($c); + $stmt->nextRowset(); + $metadata2 = $stmt->getColumnMeta($c); + + // Check the returned flags + $data1 = $metadata1['flags']; + $data2 = $metadata2['flags']; + + if (!array_key_exists($dataClassKey, $data1) || !array_key_exists($dataClassKey, $data2)) { + echo "Metadata returned with no classification data\n"; + var_dump($data1); + var_dump($data2); + } else { + $jstr1 = json_encode($data1[$dataClassKey]); + $jstr2 = json_encode($data2[$dataClassKey]); + if ($jstr1 !== $jstr2) { + echo "The JSON encoded strings should be identical\n"; + var_dump($jstr1); + var_dump($jstr2); + } + } +} + +function checkResults($conn, $stmt, $tableName, $classData, $rank = 0) +{ + $tsql = "SELECT * FROM $tableName"; + + $options = array(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION => true); + $stmt1 = $conn->prepare($tsql, $options); + $stmt1->execute(); + + compareDataClassification($stmt, $stmt1, $classData, $rank); + + // $stmt2 should produce the same result as the previous $stmt1 + $stmt2 = $conn->prepare($tsql); + $stmt2->execute(); + $stmt2->setAttribute(PDO::SQLSRV_ATTR_DATA_CLASSIFICATION, true); + + compareDataClassification($stmt, $stmt2, $classData, $rank); + + unset($stmt1); + unset($stmt2); + + runBatchQuery($conn, $tableName); +} + +/////////////////////////////////////////////////////////////////////////////////////// +try { + testConnAttrCases(); + + $conn = connect(); + $driverCapable = true; + $isSupported = isDataClassSupported($conn, $driverCapable); + + // Create a test table + $tableName = 'pdoPatients'; + $colMeta = array(new ColumnMeta('INT', 'PatientId', 'IDENTITY NOT NULL'), + new ColumnMeta('CHAR(11)', 'SSN'), + new ColumnMeta('NVARCHAR(50)', 'FirstName'), + new ColumnMeta('NVARCHAR(50)', 'LastName'), + new ColumnMeta('DATE', 'BirthDate')); + createTable($conn, $tableName, $colMeta); + + // If data classification is supported, then add sensitivity classification metadata + // to columns SSN and Birthdate + $classData = [ + array(), + array('Highly Confidential - GDPR', 'Credentials'), + array(), + array(), + array('Confidential Personal Data', 'Birthdays') + ]; + + if ($isSupported) { + assignDataClassification($conn, $tableName, $classData); + } + + // Test another error condition + testNotAvailable($conn, $tableName, $isSupported, $driverCapable); + + // Run the query without data classification metadata + $tsql = "SELECT * FROM $tableName"; + $stmt = getRegularMetadata($conn, $tsql); + + // Proceeed to retrieve sensitivity metadata, if supported + if ($isSupported) { + checkResults($conn, $stmt, $tableName, $classData); + + // Test another rank (get a random one) + $random = rand(1, 4); + $rank = $random * 10; + + trace("Testing with $rank\n"); + assignDataClassification($conn, $tableName, $classData, $rank); + checkResults($conn, $stmt, $tableName, $classData, $rank); + } + + dropTable($conn, $tableName); + + unset($stmt); + unset($conn); + + echo "Done\n"; +} catch (PDOException $e) { + var_dump($e->getMessage()); +} +?> +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/sqlsrv_data_classification_ranks.phpt b/test/functional/sqlsrv/sqlsrv_data_classification_ranks.phpt new file mode 100644 index 000000000..c79f302b1 --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_data_classification_ranks.phpt @@ -0,0 +1,347 @@ +--TEST-- +Test data classification feature - retrieving sensitivity metadata if supported +--DESCRIPTION-- +If both ODBC and server support this feature, this test verifies that sensitivity metadata can be added and correctly retrieved. If not, it will at least test the new statement attribute and some error cases. +T-SQL reference: https://docs.microsoft.com/sql/t-sql/statements/add-sensitivity-classification-transact-sql +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "NONE", 10 => "LOW", 20 => "MEDIUM", 30 => "HIGH", 40 => "CRITICAL"); + +function testErrorCases($conn, $tableName, $isSupported, $driverCapable) +{ + // This function will check two error cases: + // (1) if supported, the query should return a column with no classification + $options = array('DataClassification' => true); + $tsql = ($isSupported)? "SELECT PatientId FROM $tableName" : "SELECT * FROM $tableName"; + $stmt = sqlsrv_query($conn, $tsql, array(), $options); + if (!$stmt) { + fatalError("testErrorCases (1): failed with sqlsrv_query '$tsql'.\n"); + } + + $notAvailableErr = '*Failed to retrieve Data Classification Sensitivity Metadata. If the driver and the server both support the Data Classification feature, check whether the query returns columns with classification information.'; + + $unexpectedErrorState = '*Failed to retrieve Data Classification Sensitivity Metadata: Check if ODBC driver or the server supports the Data Classification feature.'; + + $error = ($driverCapable) ? $notAvailableErr : $unexpectedErrorState; + + $metadata = sqlsrv_field_metadata($stmt); + if ($metadata) { + echo "testErrorCases (1): expected sqlsrv_field_metadata to fail\n"; + } + + if (!fnmatch($error, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } + + // (2) call sqlsrv_prepare() with DataClassification but do not execute the stmt + $stmt = sqlsrv_prepare($conn, $tsql, array(), $options); + if (!$stmt) { + fatalError("testErrorCases (2): failed with sqlsrv_prepare '$tsql'.\n"); + } + + $executeFirstErr = '*The statement must be executed to retrieve Data Classification Sensitivity Metadata.'; + $metadata = sqlsrv_field_metadata($stmt); + if ($metadata) { + echo "testErrorCases (2): expected sqlsrv_field_metadata to fail\n"; + } + + if (!fnmatch($executeFirstErr, sqlsrv_errors()[0]['message'])) { + var_dump(sqlsrv_errors()); + } +} + +function isDataClassSupported($conn, &$driverCapable) +{ + // Check both SQL Server version and ODBC driver version + $msodbcsqlVer = sqlsrv_client_info($conn)['DriverVer']; + $version = explode(".", $msodbcsqlVer); + + // ODBC Driver must be 17.2 or above + $driverCapable = true; + if ($version[0] < 17 || $version[1] < 2) { + $driverCapable = false; + return false; + } + + // SQL Server must be SQL Server 2019 or above + $serverVer = sqlsrv_server_info($conn)['SQLServerVersion']; + if (explode('.', $serverVer)[0] < 15) { + return false; + } + + return true; +} + +function getRegularMetadata($conn, $tsql) +{ + // Run the query without data classification metadata + $stmt1 = sqlsrv_query($conn, $tsql); + if (!$stmt1) { + fatalError("getRegularMetadata (1): failed in sqlsrv_query.\n"); + } + + // Run the query with the attribute set to false + $options = array('DataClassification' => false); + $stmt2 = sqlsrv_query($conn, $tsql, array(), $options); + if (!$stmt2) { + fatalError("getRegularMetadata (2): failed in sqlsrv_query.\n"); + } + + // The metadata for each statement, column by column, should be identical + $numCol = sqlsrv_num_fields($stmt1); + $metadata1 = sqlsrv_field_metadata($stmt1); + $metadata2 = sqlsrv_field_metadata($stmt2); + + for ($i = 0; $i < $numCol; $i++) { + $diff = array_diff($metadata1[$i], $metadata2[$i]); + if (!empty($diff)) { + print_r($diff); + } + } + + return $stmt1; +} + +function verifyClassInfo($rank, $input, $actual) +{ + // For simplicity of this test, only one set of sensitivity data. Namely, + // an array with one set of Label (name, id) and Information Type (name, id) + // plus overall rank info + if (count($actual) != 2) { + echo "Expected an array with only two elements\n"; + return false; + } + + if (count($actual[0]) != 3) { + echo "Expected a Label pair and Information Type pair plus column rank info\n"; + return false; + } + + // Label should be name and id pair (id should be empty) + if (count($actual[0]['Label']) != 2) { + echo "Expected only two elements for the label\n"; + return false; + } + $label = $input[0]; + if ($actual[0]['Label']['name'] !== $label || !empty($actual[0]['Label']['id'])){ + return false; + } + + // Like Label, Information Type should also be name and id pair (id should be empty) + if (count($actual[0]['Information Type']) != 2) { + echo "Expected only two elements for the information type\n"; + return false; + } + $info = $input[1]; + if ($actual[0]['Information Type']['name'] !== $info || !empty($actual[0]['Information Type']['id'])){ + return false; + } + + if ($actual[0]['rank'] != $rank) { + return false; + } + + if ($actual['rank'] != $rank) { + return false; + } + + return true; +} + +function assignDataClassification($conn, $tableName, $classData, $rankId = 0) +{ + global $ranks; + + $rank = ", RANK = $ranks[$rankId]"; + + // column SSN + $label = $classData[1][0]; + $infoType = $classData[1][1]; + $sql = "ADD SENSITIVITY CLASSIFICATION TO [$tableName].SSN WITH (LABEL = '$label', INFORMATION_TYPE = '$infoType' $rank)"; + $stmt = sqlsrv_query($conn, $sql); + if (!$stmt) { + fatalError("SSN: Add sensitivity $label and $infoType failed.\n"); + } + + // column BirthDate + $label = $classData[4][0]; + $infoType = $classData[4][1]; + $sql = "ADD SENSITIVITY CLASSIFICATION TO [$tableName].BirthDate WITH (LABEL = '$label', INFORMATION_TYPE = '$infoType' $rank)"; + $stmt = sqlsrv_query($conn, $sql); + if (!$stmt) { + fatalError("BirthDate: Add sensitivity $label and $infoType failed.\n"); + } +} + +function compareDataClassification($stmt1, $stmt2, $classData, $rank) +{ + global $dataClassKey; + + $numCol = sqlsrv_num_fields($stmt1); + + $metadata1 = sqlsrv_field_metadata($stmt1); + $metadata2 = sqlsrv_field_metadata($stmt2); + + // The built-in array_diff_assoc() function compares the keys and values + // of two (or more) arrays, and returns an array that contains the entries + // from array1 that are not present in array2 or array3, etc. + // + // For this test, $metadata2 should have one extra key 'Data Classification', + // which should not be present in $metadata1 + // + // If the column does not have sensitivity metadata, the value should be an + // empty array. Otherwise, it should contain an array with one set of + // Label (name, id) and Information Type (name, id) + + $noClassInfo = array($dataClassKey => array()); + for ($i = 0; $i < $numCol; $i++) { + $diff = array_diff_assoc($metadata2[$i], $metadata1[$i]); + + // Is classification input data empty? + if (empty($classData[$i])) { + // Then it should be equivalent to $noClassInfo + if ($diff !== $noClassInfo) { + var_dump($diff); + } + } else { + // Verify the classification metadata + if (!verifyClassInfo($rank, $classData[$i], $diff[$dataClassKey])) { + var_dump($diff); + } + } + } +} + +function checkResults($conn, $stmt, $tableName, $classData, $rank = 0) +{ + $tsql = "SELECT * FROM $tableName"; + $options = array('DataClassification' => true); + + $stmt1 = sqlsrv_prepare($conn, $tsql, array(), $options); + if (!$stmt1) { + fatalError("Error when calling sqlsrv_prepare '$tsql'.\n"); + } + if (!sqlsrv_execute($stmt1)) { + fatalError("Error in executing statement.\n"); + } + + compareDataClassification($stmt, $stmt1, $classData, $rank); + sqlsrv_free_stmt($stmt1); + + // $stmt2 should produce the same result as the previous $stmt1 + $stmt2 = sqlsrv_query($conn, $tsql, array(), $options); + if (!$stmt2) { + fatalError("Error when calling sqlsrv_query '$tsql'.\n"); + } + + compareDataClassification($stmt, $stmt2, $classData, $rank); + sqlsrv_free_stmt($stmt2); + + runBatchQuery($conn, $tableName); +} + +function runBatchQuery($conn, $tableName) +{ + global $dataClassKey; + + $options = array('DataClassification' => true); + $tsql = "SELECT SSN, BirthDate FROM $tableName"; + $batchQuery = $tsql . ';' . $tsql; + + $stmt = sqlsrv_query($conn, $batchQuery, array(), $options); + if (!$stmt) { + fatalError("Error when calling sqlsrv_query '$tsql'.\n"); + } + + $numCol = sqlsrv_num_fields($stmt); + $c = rand(0, $numCol - 1); + + $metadata1 = sqlsrv_field_metadata($stmt); + if (!$metadata1 || !array_key_exists($dataClassKey, $metadata1[$c])) { + fatalError("runBatchQuery(1): failed to get metadata"); + } + $result = sqlsrv_next_result($stmt); + if (is_null($result) || !$result) { + fatalError("runBatchQuery: failed to get next result"); + } + $metadata2 = sqlsrv_field_metadata($stmt); + if (!$metadata2 || !array_key_exists($dataClassKey, $metadata2[$c])) { + fatalError("runBatchQuery(2): failed to get metadata"); + } + + $jstr1 = json_encode($metadata1[$c][$dataClassKey]); + $jstr2 = json_encode($metadata2[$c][$dataClassKey]); + if ($jstr1 !== $jstr2) { + echo "The JSON encoded strings should be identical\n"; + var_dump($jstr1); + var_dump($jstr2); + } +} + +/////////////////////////////////////////////////////////////////////////////////////// +require_once('MsCommon.inc'); + +$conn = AE\connect(); +if (!$conn) { + fatalError("Failed to connect.\n"); +} + +$driverCapable = true; +$isSupported = isDataClassSupported($conn, $driverCapable); + +// Create a test table +$tableName = 'srvPatients'; +$colMeta = array(new AE\ColumnMeta('INT', 'PatientId', 'IDENTITY NOT NULL'), + new AE\ColumnMeta('CHAR(11)', 'SSN'), + new AE\ColumnMeta('NVARCHAR(50)', 'FirstName'), + new AE\ColumnMeta('NVARCHAR(50)', 'LastName'), + new AE\ColumnMeta('DATE', 'BirthDate')); +AE\createTable($conn, $tableName, $colMeta); + +// If data classification is supported, then add sensitivity classification metadata +// to columns SSN and Birthdate +$classData = [ + array(), + array('Highly Confidential - GDPR', 'Credentials'), + array(), + array(), + array('Confidential Personal Data', 'Birthdays') + ]; + +if ($isSupported) { + assignDataClassification($conn, $tableName, $classData); +} + +testErrorCases($conn, $tableName, $isSupported, $driverCapable); + +// Run the query without data classification metadata +$tsql = "SELECT * FROM $tableName"; +$stmt = getRegularMetadata($conn, $tsql); + +// Proceeed to retrieve sensitivity metadata, if supported +if ($isSupported) { + checkResults($conn, $stmt, $tableName, $classData); + + // Test another rank (get a random one) + $random = rand(1, 4); + $rank = $random * 10; + + trace("Testing with $rank\n"); + assignDataClassification($conn, $tableName, $classData, $rank); + checkResults($conn, $stmt, $tableName, $classData, $rank); +} + +sqlsrv_free_stmt($stmt); + +dropTable($conn, $tableName); +sqlsrv_close($conn); + +echo "Done\n"; +?> +--EXPECT-- +Done From 0735c06f058b92eea23d59e92f667e61027466d3 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 3 Sep 2020 14:55:13 -0700 Subject: [PATCH 243/249] Used size of integers for rank field instead (#1187) long is 64 bits on Linux and the data sent back from server will always be 32 bits for ranks --- source/shared/core_util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index d616d78d0..e76eacc10 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -576,7 +576,7 @@ namespace data_classification { // Get rank info if (getRankInfo) { queryrank = *(reinterpret_cast(ptr)); - ptr += sizeof(long); + ptr += sizeof(int); meta->rank = queryrank; } @@ -605,7 +605,7 @@ namespace data_classification { if (getRankInfo) { colrank = *(reinterpret_cast(ptr)); - ptr += sizeof(long); + ptr += sizeof(int); pair.rank = colrank; } From 00fe340a45f58312e9af28c4c68d6f2afb7b2700 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 9 Sep 2020 16:02:41 -0700 Subject: [PATCH 244/249] Updated Change Log, Linux instructions and READMEs (#1185) --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++- Linux-mac-install.md | 16 ++++++++-------- README.md | 2 -- appveyor.yml | 4 ---- buildscripts/README.md | 12 ++++++------ 5 files changed, 56 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f2552503..cb6b50344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,54 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.9.0-preview1 - 2020-09-18 +Updated PECL release packages. Here is the list of updates: + +### Added +- Support for PHP 8.0 Beta 3 +- Support for Ubuntu 20.04 and Alpine 3.12 +- Support for GB18030 locale [#1115]( +https://github.com/microsoft/msphpsql/pull/1115) +- Feature Request [#924](https://github.com/microsoft/msphpsql/issues/924) - extended PDO errorinfo to include [additional odbc messages if available](https://github.com/microsoft/msphpsql/wiki/Features#pdoErrorInfo) - pull request [#1133]( +https://github.com/microsoft/msphpsql/pull/1133) +- [Data Classification with rank info](https://github.com/microsoft/msphpsql/wiki/Features#dataClass), which requires [MS ODBC Driver 17.4.2+](https://docs.microsoft.com/sql/connect/odbc/download-odbc-driver-for-sql-server?view=sql-server-ver15) and [SQL Server 2019](https://www.microsoft.com/sql-server/sql-server-2019) or an Azure SQL instance that supports it + +### Removed +- Dropped support for Ubuntu 19.10 and Debian 8. + +### Fixed +- Pull Request [#1127](https://github.com/microsoft/msphpsql/pull/1127) - removal of TSRMLS macros in preparation for PHP 8 by remicollet +- Pull Request [#1136](https://github.com/microsoft/msphpsql/pull/1136) - improved performance when handling decimal numbers as inputs or outputs and removed unncessary conversions for numeric values +- Pull Request [#1143](https://github.com/microsoft/msphpsql/pull/1143) - if an exception occurs when executing a query, will not change the output parameters +- Pull Request [#1144](https://github.com/microsoft/msphpsql/pull/1144) - use the correct C types when binding output parameters with integer values +- Pull Request [#1146](https://github.com/microsoft/msphpsql/pull/1146) - improved performance when fetching numbers using client buffers +- Pull Request [#1165](https://github.com/microsoft/msphpsql/pull/1165) - setting query timeout without using LOCK TIMEOUT, which saves an extra trip to the server +- Issue [#1170](https://github.com/microsoft/msphpsql/issues/1170) - when fetching large data types such as ntext will check more than only the display size - pull request [#1172](https://github.com/microsoft/msphpsql/pull/1172) + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- This preview release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver. +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) + ## 5.8.1 - 2020-04-15 Updated PECL release packages. Here is the list of updates: ### Fixed - Pull Request [#1094](https://github.com/microsoft/msphpsql/pull/1094) - Fixed default locale issues in Alpine Linux - Pull Request [#1095](https://github.com/microsoft/msphpsql/pull/1095) - Removed unnecessary data structure to support Client-Side Cursors feature in Alpine Linux -- Pull Request [#1095](https://github.com/microsoft/msphpsql/pull/1107) - Fixed logging issues when both drivers are enabled in Alpine Linux +- Pull Request [#1107](https://github.com/microsoft/msphpsql/pull/1107) - Fixed logging issues when both drivers are enabled in Alpine Linux ### Limitations - No support for inout / output params when using sql_variant type diff --git a/Linux-mac-install.md b/Linux-mac-install.md index 86f275675..bb8f0b5f8 100644 --- a/Linux-mac-install.md +++ b/Linux-mac-install.md @@ -1,7 +1,7 @@ # Linux and macOS Installation Tutorial for the Microsoft Drivers for PHP for SQL Server -The following instructions assume a clean environment and show how to install PHP 7.x, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 19.10, RedHat 7 and 8, Debian 8, 9, and 10, Suse 12 and 15, Alpine 3.11, and macOS 10.13, 10.14, and 10.15. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup). +The following instructions assume a clean environment and show how to install PHP 7.2+, the Microsoft ODBC driver, the Apache web server, and the Microsoft Drivers for PHP for SQL Server on Ubuntu 16.04, 18.04, and 20.04, RedHat 7 and 8, Debian 9 and 10, Suse 12 and 15, Alpine 3.11 and 3.12, and macOS 10.13, 10.14, and 10.15. These instructions advise installing the drivers using PECL, but you can also download the prebuilt binaries from the [Microsoft Drivers for PHP for SQL Server](https://github.com/Microsoft/msphpsql/releases) Github project page and install them following the instructions in [Loading the Microsoft Drivers for PHP for SQL Server](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver). For an explanation of extension loading and why we do not add the extensions to php.ini, see the section on [loading the drivers](https://docs.microsoft.com/sql/connect/php/loading-the-php-sql-driver#loading-the-driver-at-php-startup). -These instructions install PHP 7.4 by default using `pecl install`. You may need to run `pecl channel-update pecl.php.net` first. Note that some supported Linux distros default to PHP 7.1 or earlier, which is not supported for the latest version of the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.2 or 7.3 instead. +The following instructions install PHP 7.4 by default using `pecl install`. You may need to run `pecl channel-update pecl.php.net` first. Note that some supported Linux distros default to PHP 7.1 or earlier, which is not supported for the latest version of the PHP drivers for SQL Server -- please see the notes at the beginning of each section to install PHP 7.2 or 7.3 instead. Also included are instructions for installing the PHP FastCGI Process Manager, PHP-FPM, on Ubuntu. This is needed if using the nginx web server instead of Apache. @@ -10,9 +10,9 @@ Also included are instructions for installing the PHP FastCGI Process Manager, P - [Installing the drivers on Ubuntu 16.04, 18.04, and 19.10](#installing-the-drivers-on-ubuntu-1604-1804-and-1910) - [Installing the drivers with PHP-FPM on Ubuntu](#installing-the-drivers-with-php-fpm-on-ubuntu) - [Installing the drivers on Red Hat 7 and 8](#installing-the-drivers-on-red-hat-7-and-8) -- [Installing the drivers on Debian 8, 9, and 10](#installing-the-drivers-on-debian-8-9-and-10) +- [Installing the drivers on Debian 9 and 10](#installing-the-drivers-on-debian-9-and-10) - [Installing the drivers on Suse 12 and 15](#installing-the-drivers-on-suse-12-and-15) -- [Installing the drivers on Alpine 3.11](#installing-the-drivers-on-alpine-311) +- [Installing the drivers on Alpine 3.11 and 3.12](#installing-the-drivers-on-alpine-311-and-312) - [Installing the drivers on macOS High Sierra, Mojave, and Catalina](#installing-the-drivers-on-macos-high-sierra-mojave-and-catalina) ## Installing the drivers on Ubuntu 16.04, 18.04, and 19.10 @@ -189,7 +189,7 @@ sudo apachectl restart ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on Debian 8, 9, and 10 +## Installing the drivers on Debian 9 and 10 > [!NOTE] > To install PHP 7.2 or 7.3, replace 7.4 in the following commands with 7.2 or 7.3. @@ -290,13 +290,13 @@ sudo systemctl restart apache2 ``` To test your installation, see [Testing your installation](#testing-your-installation) at the end of this document. -## Installing the drivers on Alpine 3.11 +## Installing the drivers on Alpine 3.11 and 3.12 > [!NOTE] -> The default version of PHP is 7.3. Alternate versions of PHP may be available from other repositories for Alpine 3.11. You can instead compile PHP from source. +> The default version of PHP is 7.3. Alternate versions of PHP may be available from other repositories for Alpine. You can instead compile PHP from source. ### Step 1. Install PHP -PHP packages for Alpine can be found in the `edge/community` repository. Please check [Enable Community Repository](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository) on their WIKI page. Add the following line to `/etc/apt/repositories`, replacing `` with the URL of an Alpine repository mirror: +PHP packages for Alpine can be found in the `edge/community` repository. Please check [Enable Community Repository](https://wiki.alpinelinux.org/wiki/Enable_Community_Repository) on their WIKI page. Add the following line to `/etc/apk/repositories`, replacing `` with the URL of an Alpine repository mirror: ``` http:///alpine/edge/community ``` diff --git a/README.md b/README.md index 94953e96e..8b937e5af 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,6 @@ Azure Pipelines | AppVeyor (Windows) | Travis CI (Linux) | Co * [**RedHat + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/rhel) * [**SUSE + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/sles) * [**macOS + SQL Server + PHP 7**](https://www.microsoft.com/sql-server/developer-get-started/php/mac/) -* [**Docker**](https://hub.docker.com/r/lbosqmsft/mssql-php-msphpsql/) - ## Announcements diff --git a/appveyor.yml b/appveyor.yml index 942d557d4..9cc8cf8e5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,6 @@ version: '{branch}.{build}' branches: - # whitelist - #only: - - # blacklist except: - PHP-7.0-Linux - PHP5 diff --git a/buildscripts/README.md b/buildscripts/README.md index 8ff3473da..3d1481b4f 100644 --- a/buildscripts/README.md +++ b/buildscripts/README.md @@ -6,15 +6,15 @@ To build extensions for 1. PHP 7.0* or PHP 7.1* * install Visual Studio 2015 and make sure C++ tools are enabled. 2. PHP 7.2* or above - * install Visual Studio 2017, including Visual C++ toolset and the Windows SDK components. + * install Visual Studio 2017 (PHP 7.*) or Visual Studio 2019 (PHP 8.*), including Visual C++ toolset and the Windows SDK components. -To use the sample build scripts `builddrivers.py` and `buildtools.py`, install Python 3.x and Git for Windows (which comes with Visual Studio 2017). If `git` is unrecognized in a regular command prompt, make sure the environment path is set up correctly. +To use the sample build scripts `builddrivers.py` and `buildtools.py`, install Python 3.x and Git for Windows (which comes with Visual Studio 2017 or 2019). If `git` is unrecognized in a regular command prompt, make sure the environment path is set up correctly. ## Compile the drivers -You must first be able to build PHP 7.* without including our PHP extensions. For help with building PHP 7.0* or PHP 7.1* in Windows, see the [official PHP website](https://wiki.php.net/internals/windows/stepbystepbuild). For PHP 7.2 or above, visit [PHP SDK page](https://github.com/OSTC/php-sdk-binary-tools) for new instructions. +You must first be able to build PHP source without including our PHP extensions. For help with building PHP 7.0* or PHP 7.1* in Windows, see the [official PHP website](https://wiki.php.net/internals/windows/stepbystepbuild). For PHP 7.2 or above, visit [PHP SDK page](https://github.com/OSTC/php-sdk-binary-tools) for new instructions. -The Microsoft Drivers for PHP for SQL Server have been compiled and tested with PHP 7.0.* and 7.1.* using Visual C++ 2015 as well as PHP 7.2+ using Visual C++ 2017 v15.*. +The Microsoft Drivers for PHP for SQL Server have been compiled and tested with PHP 7.2+ using Visual Studio 2017 and PHP 8.0 previews using Visual Studio 2019. The drivers for Windows that are published for each release (including previews) are digitally signed. You are recommended to sign the binaries you have compiled locally for your own development or testing purposes, using tools like Authenticode. It verifies the publisher's identity and prevents malicious actors from posing as legitimate developers. ### Manually building from source @@ -45,7 +45,7 @@ The sample build scripts, `builddrivers.py` and `buildtools.py`, can be used to #### Overview -When asked to provide the PHP version, you should enter values like `7.3.17`. If it's alpha, beta, or RC version, make sure the name you provide matches the PHP tag name without the prefix `php-`. For example, for PHP 7.4 beta 2, the tag name is `php-7.4.0beta2`, so you will enter `7.4.0beta2`. Visit [PHP SRC]( https://github.com/php/php-src) to find the appropriate tag names. +When asked to provide the PHP version, you should enter values like `7.3.17`. If it's alpha, beta, or RC version, make sure the name you provide matches the PHP tag name without the prefix `php-`. For example, for PHP 8.0.0 beta 3, the tag name is `php-8.0.0beta3`, so you will enter `8.0.0beta3`. Visit [PHP SRC]( https://github.com/php/php-src) to find the appropriate tag names. PHP recommends to unzip the PHP SDK into the shortest possible path, preferrably somewhere near the root drive. Therefore, this script will, by default, create a `php-sdk` folder in the C:\ drive, and this `php-sdk` directory tree will remain unless you remove it yourself. For ongoing development, we suggest you keep it around. The build scripts will handle updating the PHP SDK if a new version is available. @@ -68,7 +68,7 @@ PHP recommends to unzip the PHP SDK into the shortest possible path, preferrably 4. Use Command-line arguments * Type `py builddrivers.py -h` to get a list of options and their descriptions * For example, - * `py builddrivers.py --PHPVER=7.4.5 --ARCH=x64 --THREAD=nts --DRIVER=sqlsrv --SOURCE=C:\local\source` + * `py builddrivers.py --PHPVER=7.4.10 --ARCH=x64 --THREAD=nts --DRIVER=sqlsrv --SOURCE=C:\local\source` * `py builddrivers.py --PHPVER=7.2.30 --ARCH=x86 --THREAD=ts --DEBUG` 5. Based on the given configuration, if the script detects the presence of the PHP source directory, you can choose whether to rebuild, clean or superclean: From 7f9f3ddea09fbd4aa4f1beb77abed75efe31d14d Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 15 Sep 2020 14:25:04 -0700 Subject: [PATCH 245/249] Change version 5.9.0-preview1 (#1189) --- CHANGELOG.md | 4 ++-- source/pdo_sqlsrv/config.m4 | 2 +- source/pdo_sqlsrv/config.w32 | 2 +- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- source/pdo_sqlsrv/pdo_init.cpp | 2 +- source/pdo_sqlsrv/pdo_parser.cpp | 2 +- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- source/pdo_sqlsrv/pdo_util.cpp | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv.h | 2 +- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 2 +- source/pdo_sqlsrv/template.rc | 2 +- source/shared/FormattedPrint.cpp | 2 +- source/shared/FormattedPrint.h | 2 +- source/shared/StringFunctions.cpp | 2 +- source/shared/StringFunctions.h | 2 +- source/shared/core_conn.cpp | 2 +- source/shared/core_init.cpp | 2 +- source/shared/core_results.cpp | 2 +- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 2 +- source/shared/core_stream.cpp | 2 +- source/shared/core_util.cpp | 2 +- source/shared/globalization.h | 2 +- source/shared/interlockedatomic.h | 2 +- source/shared/interlockedatomic_gcc.h | 2 +- source/shared/interlockedslist.h | 2 +- source/shared/localization.hpp | 2 +- source/shared/localizationimpl.cpp | 2 +- source/shared/msodbcsql.h | 2 +- source/shared/sal_def.h | 2 +- source/shared/typedefs_for_linux.h | 2 +- source/shared/version.h | 12 ++++++------ source/shared/xplat.h | 2 +- source/shared/xplat_intsafe.h | 2 +- source/shared/xplat_winerror.h | 2 +- source/shared/xplat_winnls.h | 2 +- source/sqlsrv/config.m4 | 2 +- source/sqlsrv/config.w32 | 2 +- source/sqlsrv/conn.cpp | 2 +- source/sqlsrv/init.cpp | 2 +- source/sqlsrv/php_sqlsrv.h | 2 +- source/sqlsrv/php_sqlsrv_int.h | 2 +- source/sqlsrv/stmt.cpp | 2 +- source/sqlsrv/template.rc | 2 +- source/sqlsrv/util.cpp | 2 +- .../pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt | 2 +- test/functional/pdo_sqlsrv/pdo_get_set_attr.phpt | 2 +- test/functional/sqlsrv/sqlsrv_client_info.phpt | 2 +- 48 files changed, 54 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6b50344..8295223db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) -## 5.9.0-preview1 - 2020-09-18 +## 5.9.0-preview1 - 2020-10-02 Updated PECL release packages. Here is the list of updates: ### Added -- Support for PHP 8.0 Beta 3 +- Support for PHP 8.0 RC 1 - Support for Ubuntu 20.04 and Alpine 3.12 - Support for GB18030 locale [#1115]( https://github.com/microsoft/msphpsql/pull/1115) diff --git a/source/pdo_sqlsrv/config.m4 b/source/pdo_sqlsrv/config.m4 index b2149a753..93e2c3296 100644 --- a/source/pdo_sqlsrv/config.m4 +++ b/source/pdo_sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.8 for PHP for SQL Server +dnl Microsoft Drivers 5.9 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/pdo_sqlsrv/config.w32 b/source/pdo_sqlsrv/config.w32 index a82fbb713..1f3ed1a30 100644 --- a/source/pdo_sqlsrv/config.w32 +++ b/source/pdo_sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index fa9ef60fd..03f192f28 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDO object for PDO_SQLSRV // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index fdccf5346..82ff1e101 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -3,7 +3,7 @@ // // Contents: initialization routines for PDO_SQLSRV // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index fed2e2ca5..1d0b72990 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -5,7 +5,7 @@ // // Copyright Microsoft Corporation // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 2c4fc16db..5e82a0465 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Implements the PDOStatement object for the PDO_SQLSRV // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index 0de83d783..4d76cccb2 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -3,7 +3,7 @@ // // Contents: Utility functions used by both connection or statement functions // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv.h b/source/pdo_sqlsrv/php_pdo_sqlsrv.h index d7f5ac0cb..6418cb603 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Declarations for the extension // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 7d377496f..79dfa7f01 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -6,7 +6,7 @@ // // Contents: Internal declarations for the extension // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/pdo_sqlsrv/template.rc b/source/pdo_sqlsrv/template.rc index 350a2f825..561a65df2 100644 --- a/source/pdo_sqlsrv/template.rc +++ b/source/pdo_sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.cpp b/source/shared/FormattedPrint.cpp index 94c7b28f2..23afab351 100644 --- a/source/shared/FormattedPrint.cpp +++ b/source/shared/FormattedPrint.cpp @@ -6,7 +6,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/FormattedPrint.h b/source/shared/FormattedPrint.h index 79b824b41..1d853b15e 100644 --- a/source/shared/FormattedPrint.h +++ b/source/shared/FormattedPrint.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.cpp b/source/shared/StringFunctions.cpp index 7426644f6..7b08fcf66 100644 --- a/source/shared/StringFunctions.cpp +++ b/source/shared/StringFunctions.cpp @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/StringFunctions.h b/source/shared/StringFunctions.h index 7593a534f..b78509547 100644 --- a/source/shared/StringFunctions.h +++ b/source/shared/StringFunctions.h @@ -3,7 +3,7 @@ // // Contents: Contains functions for handling UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index e39363a93..3b43f2ba4 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_init.cpp b/source/shared/core_init.cpp index 9c3616d5e..0cd004076 100644 --- a/source/shared/core_init.cpp +++ b/source/shared/core_init.cpp @@ -3,7 +3,7 @@ // // Contents: common initialization routines shared by PDO and sqlsrv // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 64c3e6e58..d7a59ac1b 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -3,7 +3,7 @@ // // Contents: Result sets // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 1b148f743..6907e2c71 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -6,7 +6,7 @@ // // Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 6ef4e1cb9..ad33e572a 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Core routines that use statement handles shared between sqlsrv and pdo_sqlsrv // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 4dbd1bdb3..72b22b61a 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -3,7 +3,7 @@ // // Contents: Implementation of PHP streams for reading SQL Server data // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/core_util.cpp b/source/shared/core_util.cpp index e76eacc10..127a68e21 100644 --- a/source/shared/core_util.cpp +++ b/source/shared/core_util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/globalization.h b/source/shared/globalization.h index 81091fc5f..060bbb24c 100644 --- a/source/shared/globalization.h +++ b/source/shared/globalization.h @@ -4,7 +4,7 @@ // Contents: Contains functions for handling Windows format strings // and UTF-16 on non-Windows platforms // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic.h b/source/shared/interlockedatomic.h index c2ae4d26a..4e7ef9ca0 100644 --- a/source/shared/interlockedatomic.h +++ b/source/shared/interlockedatomic.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedatomic_gcc.h b/source/shared/interlockedatomic_gcc.h index 9eff99f5b..fbda09283 100644 --- a/source/shared/interlockedatomic_gcc.h +++ b/source/shared/interlockedatomic_gcc.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, atomic // operations on int32_t and pointer types. // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/interlockedslist.h b/source/shared/interlockedslist.h index 8def1dc2d..bb32fb862 100644 --- a/source/shared/interlockedslist.h +++ b/source/shared/interlockedslist.h @@ -4,7 +4,7 @@ // Contents: Contains a portable abstraction for interlocked, singly // linked list. // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localization.hpp b/source/shared/localization.hpp index 4f2df190e..da918d6d0 100644 --- a/source/shared/localization.hpp +++ b/source/shared/localization.hpp @@ -3,7 +3,7 @@ // // Contents: Contains portable classes for localization // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/localizationimpl.cpp b/source/shared/localizationimpl.cpp index f3ca7f5cb..581e953f2 100644 --- a/source/shared/localizationimpl.cpp +++ b/source/shared/localizationimpl.cpp @@ -5,7 +5,7 @@ // Must be included in one c/cpp file per binary // A build error will occur if this inclusion policy is not followed // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/msodbcsql.h b/source/shared/msodbcsql.h index 7ed1c33ea..436fa7fa5 100644 --- a/source/shared/msodbcsql.h +++ b/source/shared/msodbcsql.h @@ -20,7 +20,7 @@ // pecuniary loss) arising out of the use of or inability to use // this SDK, even if Microsoft has been advised of the possibility // of such damages. -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/sal_def.h b/source/shared/sal_def.h index 7b191eb76..a79ee9e3a 100644 --- a/source/shared/sal_def.h +++ b/source/shared/sal_def.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/typedefs_for_linux.h b/source/shared/typedefs_for_linux.h index b4fb63a6a..8df22852e 100644 --- a/source/shared/typedefs_for_linux.h +++ b/source/shared/typedefs_for_linux.h @@ -1,7 +1,7 @@ //--------------------------------------------------------------------------------------------------------------------------------- // File: typedefs_for_linux.h // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/version.h b/source/shared/version.h index 40ae57f62..616b28b9b 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -4,7 +4,7 @@ // File: version.h // Contents: Version number constants // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License @@ -26,12 +26,12 @@ // Increase Minor with backward compatible new functionalities and API changes. // Increase Patch for backward compatible fixes. #define SQLVERSION_MAJOR 5 -#define SQLVERSION_MINOR 8 -#define SQLVERSION_PATCH 1 +#define SQLVERSION_MINOR 9 +#define SQLVERSION_PATCH 0 #define SQLVERSION_BUILD 0 -// For previews, set this constant to 1. Otherwise, set it to 0 -#define PREVIEW 0 +// For previews, set this constant to 1, 2 and so on. Otherwise, set it to 0 +#define PREVIEW 1 #define SEMVER_PRERELEASE // Semantic versioning build metadata, build meta data is not counted in precedence order. @@ -52,7 +52,7 @@ // "preview" for ETP #if PREVIEW > 0 #undef SEMVER_PRERELEASE -#define SEMVER_PRERELEASE "preview" +#define SEMVER_PRERELEASE "preview" STRINGIFY(PREVIEW) #define VER_FILEVERSION_STR VER_APIVERSION_STR "-" SEMVER_PRERELEASE SEMVER_BUILDMETA #else #define VER_FILEVERSION_STR VER_APIVERSION_STR SEMVER_PRERELEASE SEMVER_BUILDMETA diff --git a/source/shared/xplat.h b/source/shared/xplat.h index dbf862b13..188255558 100644 --- a/source/shared/xplat.h +++ b/source/shared/xplat.h @@ -3,7 +3,7 @@ // // Contents: include for definition of Windows types for non-Windows platforms // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_intsafe.h b/source/shared/xplat_intsafe.h index e64e4c518..a1bde6c8f 100644 --- a/source/shared/xplat_intsafe.h +++ b/source/shared/xplat_intsafe.h @@ -4,7 +4,7 @@ // Contents: This module defines helper functions to prevent // integer overflow bugs. // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winerror.h b/source/shared/xplat_winerror.h index 8ae8ee402..1de5524ac 100644 --- a/source/shared/xplat_winerror.h +++ b/source/shared/xplat_winerror.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/shared/xplat_winnls.h b/source/shared/xplat_winnls.h index f6d75a614..a66983220 100644 --- a/source/shared/xplat_winnls.h +++ b/source/shared/xplat_winnls.h @@ -3,7 +3,7 @@ // // Contents: Contains the minimal definitions to build on non-Windows platforms // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/config.m4 b/source/sqlsrv/config.m4 index 240c09cf6..8b3bf49e3 100644 --- a/source/sqlsrv/config.m4 +++ b/source/sqlsrv/config.m4 @@ -4,7 +4,7 @@ dnl dnl Contents: the code that will go into the configure script, indicating options, dnl external libraries and includes, and what source files are to be compiled. dnl -dnl Microsoft Drivers 5.8 for PHP for SQL Server +dnl Microsoft Drivers 5.9 for PHP for SQL Server dnl Copyright(c) Microsoft Corporation dnl All rights reserved. dnl MIT License diff --git a/source/sqlsrv/config.w32 b/source/sqlsrv/config.w32 index 57cae3e7f..bb8d1ecb3 100644 --- a/source/sqlsrv/config.w32 +++ b/source/sqlsrv/config.w32 @@ -3,7 +3,7 @@ // // Contents: JScript build configuration used by buildconf.bat // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index 7009a3376..d5c61cfae 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use connection handles // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index 7ddbaffa0..cd2220a2e 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -2,7 +2,7 @@ // File: init.cpp // Contents: initialization routines for the extension // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv.h b/source/sqlsrv/php_sqlsrv.h index 45fb8baec..fcfb387a9 100644 --- a/source/sqlsrv/php_sqlsrv.h +++ b/source/sqlsrv/php_sqlsrv.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index e04bceeb4..87a15c20e 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -8,7 +8,7 @@ // // Comments: Also contains "internal" declarations shared across source files. // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index a9b4590bf..2aef9dd61 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -3,7 +3,7 @@ // // Contents: Routines that use statement handles // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/template.rc b/source/sqlsrv/template.rc index 7aa9c0cf7..a44a52383 100644 --- a/source/sqlsrv/template.rc +++ b/source/sqlsrv/template.rc @@ -3,7 +3,7 @@ // // Contents: Version resource // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index d1de883e5..a5dd00bb7 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -5,7 +5,7 @@ // // Comments: Mostly error handling and some type handling // -// Microsoft Drivers 5.8 for PHP for SQL Server +// Microsoft Drivers 5.9 for PHP for SQL Server // Copyright(c) Microsoft Corporation // All rights reserved. // MIT License diff --git a/test/functional/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt b/test/functional/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt index a98dafda1..4c463e2bd 100644 --- a/test/functional/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt +++ b/test/functional/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt @@ -23,5 +23,5 @@ Array \[(DriverDllName|DriverName)\] => (msodbcsql1[1-9].dll|(libmsodbcsql-[0-9]{2}\.[0-9]\.so\.[0-9]\.[0-9]|libmsodbcsql.[0-9]{2}.dylib)) \[DriverODBCVer\] => [0-9]{1,2}\.[0-9]{1,2} \[DriverVer\] => [0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4} - \[ExtensionVer\] => [0-9].[0-9]\.[0-9](-(RC[0-9]?|preview))?(\.[0-9]+)?(\+[0-9]+)? + \[ExtensionVer\] => [0-9].[0-9]\.[0-9](-(RC[1-9]?|preview[1-9]))?(\.[0-9]+)?(\+[0-9]+)? \) diff --git a/test/functional/pdo_sqlsrv/pdo_get_set_attr.phpt b/test/functional/pdo_sqlsrv/pdo_get_set_attr.phpt index e6ee2ccb2..032740abf 100644 --- a/test/functional/pdo_sqlsrv/pdo_get_set_attr.phpt +++ b/test/functional/pdo_sqlsrv/pdo_get_set_attr.phpt @@ -131,7 +131,7 @@ array\(4\) { \["DriverVer"\]=> string\(10\) "[0-9]{2}.[0-9]{2}.[0-9]{4}" \["ExtensionVer"\]=> - string\([0-9]*\) \"[0-9].[0-9]\.[0-9](-(RC[0-9]?|preview))?(\.[0-9]+)?(\+[0-9]+)?\" + string\([0-9]*\) \"[0-9].[0-9]\.[0-9](-(RC[1-9]?|preview[1-9]))?(\.[0-9]+)?(\+[0-9]+)?\" } Test_6: diff --git a/test/functional/sqlsrv/sqlsrv_client_info.phpt b/test/functional/sqlsrv/sqlsrv_client_info.phpt index bfcc2d0e8..8b91140df 100644 --- a/test/functional/sqlsrv/sqlsrv_client_info.phpt +++ b/test/functional/sqlsrv/sqlsrv_client_info.phpt @@ -21,5 +21,5 @@ array\(4\) { \[\"DriverVer\"\]=> string\(10\) \"[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4}\" \[\"ExtensionVer\"\]=> - string\([0-9]+\) \"[0-9].[0-9]\.[0-9](-(RC[0-9]?|preview))?(\.[0-9]+)?(\+[0-9]+)?\" + string\([0-9]+\) \"[0-9].[0-9]\.[0-9](-(RC[1-9]?|preview[1-9]))?(\.[0-9]+)?(\+[0-9]+)?\" } \ No newline at end of file From cb6364b3436dd5d186628d1a9bc2eaccee011f8c Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 16 Sep 2020 12:25:03 -0700 Subject: [PATCH 246/249] Removed duplicate tests and updated client info regex (#1190) --- .../bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt | 18 +++------- test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt | 35 ------------------- .../pdo_getAttribute_clientInfo.phpt | 2 +- .../functional/sqlsrv/sqlsrv_client_info.phpt | 2 +- 4 files changed, 6 insertions(+), 51 deletions(-) delete mode 100644 test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt diff --git a/test/bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt b/test/bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt index 50da236bb..051b9fa88 100644 --- a/test/bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt +++ b/test/bvt/pdo_sqlsrv/msdn_pdo_getAttribute.phpt @@ -10,31 +10,21 @@ $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); $attributes1 = array( "ERRMODE" ); foreach ( $attributes1 as $val ) { - echo "PDO::ATTR_$val: "; - var_dump ($conn->getAttribute( constant( "PDO::ATTR_$val" ) )); + echo "PDO::ATTR_$val: "; + var_dump ($conn->getAttribute( constant( "PDO::ATTR_$val" ) )); } $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $attributes1 = array( "ERRMODE" ); foreach ( $attributes1 as $val ) { - echo "PDO::ATTR_$val: "; - var_dump ($conn->getAttribute( constant( "PDO::ATTR_$val" ) )); + echo "PDO::ATTR_$val: "; + var_dump ($conn->getAttribute( constant( "PDO::ATTR_$val" ) )); } -// An example using PDO::ATTR_CLIENT_VERSION -print_r($conn->getAttribute( PDO::ATTR_CLIENT_VERSION )); - //free the connection unset($conn); ?> --EXPECTREGEX-- PDO::ATTR_ERRMODE: int\(0\) PDO::ATTR_ERRMODE: int\(2\) -Array -\( - \[DriverDllName\]|\[DriverName\] => (msodbcsql[0-9]{2}\.dll|(libmsodbcsql-[0-9]{2}\.[0-9]\.so\.[0-9]\.[0-9]|libmsodbcsql.[0-9]{2}.dylib)) - \[DriverODBCVer\] => [0-9]{1,2}\.[0-9]{1,2} - \[DriverVer\] => [0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4} - \[ExtensionVer\] => [0-9].[0-9]\.[0-9](-(RC[0-9]?|preview))?(\.[0-9]+)?(\+[0-9]+)? -\) \ No newline at end of file diff --git a/test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt b/test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt deleted file mode 100644 index 290bab885..000000000 --- a/test/bvt/sqlsrv/msdn_sqlsrv_client_info.phpt +++ /dev/null @@ -1,35 +0,0 @@ ---TEST-- -client information. ---SKIPIF-- - ---FILE-- -"$databaseName", "UID"=>"$uid", "PWD"=>"$pwd"); -$conn = sqlsrv_connect( $server, $connectionInfo); -if( $conn === false ) -{ - echo "Could not connect.\n"; - die( print_r( sqlsrv_errors(), true)); -} - -if( $client_info = sqlsrv_client_info( $conn)) -{ - foreach( $client_info as $key => $value) - { - echo $key.": ".$value."\n"; - } -} -else -{ - echo "Client info error.\n"; -} - -/* Close connection resources. */ -sqlsrv_close( $conn); -?> ---EXPECTREGEX-- -DriverDllName|DriverName: (msodbcsql[0-9]{2}\.dll|(libmsodbcsql-[0-9]{2}\.[0-9]\.so\.[0-9]\.[0-9]|libmsodbcsql.[0-9]{2}.dylib)) -DriverODBCVer: [0-9]{1,2}\.[0-9]{1,2} -DriverVer: [0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4} -ExtensionVer: [0-9].[0-9]\.[0-9](-(RC[0-9]?|preview))?(\.[0-9]+)?(\+[0-9]+)? \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt b/test/functional/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt index 4c463e2bd..4a7b799d4 100644 --- a/test/functional/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt +++ b/test/functional/pdo_sqlsrv/pdo_getAttribute_clientInfo.phpt @@ -23,5 +23,5 @@ Array \[(DriverDllName|DriverName)\] => (msodbcsql1[1-9].dll|(libmsodbcsql-[0-9]{2}\.[0-9]\.so\.[0-9]\.[0-9]|libmsodbcsql.[0-9]{2}.dylib)) \[DriverODBCVer\] => [0-9]{1,2}\.[0-9]{1,2} \[DriverVer\] => [0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4} - \[ExtensionVer\] => [0-9].[0-9]\.[0-9](-(RC[1-9]?|preview[1-9]))?(\.[0-9]+)?(\+[0-9]+)? + \[ExtensionVer\] => [0-9]\.[0-9]\.[0-9](-(RC[1-9]?|preview[1-9]))?(\.[0-9]+)?(\+[0-9]+)? \) diff --git a/test/functional/sqlsrv/sqlsrv_client_info.phpt b/test/functional/sqlsrv/sqlsrv_client_info.phpt index 8b91140df..ef8f54353 100644 --- a/test/functional/sqlsrv/sqlsrv_client_info.phpt +++ b/test/functional/sqlsrv/sqlsrv_client_info.phpt @@ -21,5 +21,5 @@ array\(4\) { \[\"DriverVer\"\]=> string\(10\) \"[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4}\" \[\"ExtensionVer\"\]=> - string\([0-9]+\) \"[0-9].[0-9]\.[0-9](-(RC[1-9]?|preview[1-9]))?(\.[0-9]+)?(\+[0-9]+)?\" + string\([0-9]+\) \"[0-9]\.[0-9]\.[0-9](-(RC[1-9]?|preview[1-9]))?(\.[0-9]+)?(\+[0-9]+)?\" } \ No newline at end of file From d2c7254442f19724ebfb97015101d19f665a8f4e Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 22 Sep 2020 17:07:07 -0700 Subject: [PATCH 247/249] Updated Appveyor to test against PHP 8.0.0 beta 4 (#1193) --- appveyor.yml | 4 +- test/functional/sqlsrv/sqlsrv_configure.phpt | 52 ++++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9cc8cf8e5..83fce717f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,7 +30,7 @@ environment: SQL_INSTANCE: SQL2019 PHP_VC: vs16 PHP_MAJOR_VER: 8.0 - PHP_MINOR_VER: 0beta2 + PHP_MINOR_VER: 0beta4 PHP_EXE_PATH: Release THREAD: nts platform: x86 @@ -119,7 +119,7 @@ test_script: - ps: >- If ($env:BUILD_PLATFORM -Match "x86") { Write-Host "Running phpt tests via OpenCppCoverage..." - OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --excluded_sources ${env:PHP_SRC_DIR}\pdo_sqlsrv\shared\core_stream.cpp --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8; + OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --excluded_sources ${env:PHP_SRC_DIR}\pdo_sqlsrv\shared\core_stream.cpp --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ --no-color | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8; Write-Host "Showing the last 25 lines of the log file..." Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -Tail 25; ls *.xml diff --git a/test/functional/sqlsrv/sqlsrv_configure.phpt b/test/functional/sqlsrv/sqlsrv_configure.phpt index c582c97ed..5cb297b28 100644 --- a/test/functional/sqlsrv/sqlsrv_configure.phpt +++ b/test/functional/sqlsrv/sqlsrv_configure.phpt @@ -146,34 +146,34 @@ sqlsrv_configure. } ?> ---EXPECT-- -sqlsrv_configure() expects exactly 2 parameters, 1 given +--EXPECTREGEX-- +sqlsrv_configure\(\) expects exactly 2 (parameters|arguments), 1 given Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -14 - [code] => -14 - [2] => An invalid parameter was passed to sqlsrv_configure. - [message] => An invalid parameter was passed to sqlsrv_configure. - ) - -) +\( + \[0] => Array + \( + \[0\] => IMSSP + \[SQLSTATE\] => IMSSP + \[1\] => -14 + \[code\] => -14 + \[2\] => An invalid parameter was passed to sqlsrv_configure. + \[message\] => An invalid parameter was passed to sqlsrv_configure. + \) + +\) Array -( - [0] => Array - ( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -14 - [code] => -14 - [2] => An invalid parameter was passed to sqlsrv_get_config. - [message] => An invalid parameter was passed to sqlsrv_get_config. - ) - -) +\( + \[0\] => Array + \( + \[0\] => IMSSP + \[SQLSTATE\] => IMSSP + \[1\] => -14 + \[code\] => -14 + \[2\] => An invalid parameter was passed to sqlsrv_get_config. + \[message\] => An invalid parameter was passed to sqlsrv_get_config. + \) + +\) sqlsrv.LogSubsystems = -1 sqlsrv_configure: entering sqlsrv.LogSubsystems = 8 From ec60302825944acee41cbbff131716820396f670 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 29 Sep 2020 17:53:58 -0700 Subject: [PATCH 248/249] =?UTF-8?q?Updated=20drivers=20and=20tests=20to=20?= =?UTF-8?q?support=C2=A0PHP=208.0.0=20RC1=20(#1194)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appveyor.yml | 2 +- source/shared/core_stmt.cpp | 6 +++- source/shared/core_stream.cpp | 9 +++-- source/sqlsrv/init.cpp | 2 ++ .../pdo_sqlsrv/pdo_connection_quote.phpt | 20 ++++++++--- .../pdo_sqlsrv/pdo_fetch_column_twice.phpt | 32 +++++++++++++---- .../pdostatement_getColumnMeta.phpt | 34 ++++++++++++++----- ...tement_getColumnMeta_unicode_col_name.phpt | 32 +++++++++++++---- 8 files changed, 107 insertions(+), 30 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 83fce717f..15a2962d7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,7 +30,7 @@ environment: SQL_INSTANCE: SQL2019 PHP_VC: vs16 PHP_MAJOR_VER: 8.0 - PHP_MINOR_VER: 0beta4 + PHP_MINOR_VER: 0rc1 PHP_EXE_PATH: Release THREAD: nts platform: x86 diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index ad33e572a..4da01dfb7 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1938,7 +1938,11 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i throw core::CoreException(); } - stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL ); + // For a sqlsrv stream, only REPORT_ERRORS may be used. For "mode", the 'b' option + // is ignored on POSIX systems, which treat text and binary files the same. Yet, the + // 'b' option might be important in other systems. + // For details check https://www.php.net/manual/en/internals2.ze1.streams.php + stream = php_stream_open_wrapper("sqlsrv://sqlncli10", "rb", REPORT_ERRORS, NULL); CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) { throw core::CoreException(); diff --git a/source/shared/core_stream.cpp b/source/shared/core_stream.cpp index 72b22b61a..05f4224dd 100644 --- a/source/shared/core_stream.cpp +++ b/source/shared/core_stream.cpp @@ -238,9 +238,12 @@ static php_stream* sqlsrv_stream_opener( _In_opt_ php_stream_wrapper* wrapper, _ ss = static_cast( sqlsrv_malloc( sizeof( sqlsrv_stream ))); memset( ss, 0, sizeof( sqlsrv_stream )); - // check for valid options - if( options != REPORT_ERRORS ) { - php_stream_wrapper_log_error( wrapper, options, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream" ); + // The function core_get_field_common() is changed to pass REPORT_ERRORS for + // php_stream_open_wrapper(). Whether the error flag is toggled or cleared, + // the argument "options" will be zero. + // For details check this pull request: https://github.com/php/php-src/pull/6190 + if (options != 0) { + php_stream_wrapper_log_error(wrapper, options, "Invalid option: no options except REPORT_ERRORS may be specified with a sqlsrv stream"); return NULL; } diff --git a/source/sqlsrv/init.cpp b/source/sqlsrv/init.cpp index cd2220a2e..4cc02597c 100644 --- a/source/sqlsrv/init.cpp +++ b/source/sqlsrv/init.cpp @@ -98,6 +98,8 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_arginfo, 0, 0, 1 ) ZEND_ARG_INFO( 0, stmt ) + ZEND_ARG_INFO( 0, row ) + ZEND_ARG_INFO( 0, offset ) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX( sqlsrv_fetch_array_arginfo, 0, 0, 1 ) diff --git a/test/functional/pdo_sqlsrv/pdo_connection_quote.phpt b/test/functional/pdo_sqlsrv/pdo_connection_quote.phpt index d3865d0a2..26b39fcd6 100644 --- a/test/functional/pdo_sqlsrv/pdo_connection_quote.phpt +++ b/test/functional/pdo_sqlsrv/pdo_connection_quote.phpt @@ -19,11 +19,23 @@ try { $output3 = $conn->quote("The quick brown fox jumps over the lazy dog0123456789"); var_dump($output3); - $stmt = $conn->query(""); - if ($stmt != false) { - echo("Empty query was expected to fail!\n"); + if (PHP_MAJOR_VERSION < 8) { + $stmt = $conn->query(""); + if ($stmt != false) { + echo("Empty query was expected to fail!\n"); + } + unset($stmt); + } else { + try { + $stmt = $conn->query(""); + echo("Empty query was expected to fail!\n"); + } catch (ValueError $ve) { + $error = '*PDO::query(): Argument #1 ($query) cannot be empty'; + if (!fnmatch($error, $ve->getMessage())) { + var_dump($ve->getMessage()); + } + } } - unset($stmt); $stmt1 = $conn->prepare($output2); $result = $stmt1->execute(); diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_column_twice.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_column_twice.phpt index 7e2ec1946..692b737b0 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_column_twice.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_column_twice.phpt @@ -88,21 +88,42 @@ function fetchColumnOutOfBound1($conn, $tableName, $col) echo "Error message unexpected in fetchColumnOutOfBound1\n"; var_dump($e->getMessage()); } + } catch (ValueError $ve) { + $error = '*Column index must be greater than or equal to 0'; + if (!fnmatch($error, $ve->getMessage())) { + echo "Error message unexpected in fetchColumnOutOfBound1\n"; + var_dump($ve->getMessage()); + } } } function fetchColumnOutOfBound2($conn, $tableName, $col) { + $error = '*Invalid column index'; try { $tsql = "SELECT * FROM $tableName"; $stmt = $conn->query($tsql, PDO::FETCH_COLUMN, $col); $result = $stmt->fetch(); unset($stmt); - } catch (PDOException $e) { - var_dump($e->getMessage()); + } catch (Error $e) { + if (!fnmatch($error, $e->getMessage())) { + var_dump($e->getMessage()); + } + } catch (ValueError $ve) { + if (!fnmatch($error, $ve->getMessage())) { + var_dump($ve->getMessage()); + } } } +// When testing with PHP 8.0 some test cases throw ValueError instead of exceptions or warnings. +// Thus implement a custom warning handler such that with PHP 7.x the warning would be handled +// to throw an Error (ValueError not available). +function warningHandler($errno, $errstr) +{ + throw new Error($errstr); +} + try { $conn = connect(); $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); @@ -135,7 +156,9 @@ try { // Change to warning mode $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + set_error_handler("warningHandler", E_WARNING); fetchColumnOutOfBound2($conn, $tableName, $numCols + 1); + restore_error_handler(); dropTable($conn, $tableName); unset($conn); @@ -144,8 +167,5 @@ try { var_dump($e); } ?> ---EXPECTREGEX-- -Warning: PDOStatement::fetch\(\): SQLSTATE\[HY000\]: General error: Invalid column index in .+(\/|\\)pdo_fetch_column_twice.php on line [0-9]+ - -Warning: PDOStatement::fetch\(\): SQLSTATE\[HY000\]: General error in .+(\/|\\)pdo_fetch_column_twice.php on line [0-9]+ +--EXPECT-- Done diff --git a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt index 40805dc23..6493bf94e 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta.phpt @@ -49,22 +49,43 @@ function fetchBoth($conn, $tbname) unset($meta["sqlsrv:decl_type"]); var_dump($meta); - // Test invalid arguments, set error mode to silent to reduce the amount of error messages generated + // Test invalid arguments, set error mode to silent to reduce the number of error messages generated $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); - // Test negative column number, ignore the error messages - $meta = $stmt->getColumnMeta(-1); - var_dump($meta); + // Test negative column number + try { + $meta = $stmt->getColumnMeta(-1); + echo "Expect getColumnMeta to fail with -1\n"; + } catch (Error $e) { + if (PHP_MAJOR_VERSION == 8) { + $error = '*PDOStatement::getColumnMeta(): Argument #1 ($index) must be greater than or equal to 0*'; + } else { + $error = '*Invalid column reference: column number must be non-negative*'; + } + if (!fnmatch($error, $e->getMessage())) { + echo "Unexpected error:"; + var_dump($e->getMessage()); + } + } // Test non-existent column number $meta = $stmt->getColumnMeta(10); var_dump($meta); } +// When testing with PHP 8.0 the negative test case throws an Error instead of a warning. +// Implement a custom warning handler such that with PHP 7.x the warning would be handled +// to throw an Error. +function warningHandler($errno, $errstr) +{ + throw new Error($errstr); +} + try { $db = connect(); $tbname = "PDO_MainTypes"; createAndInsertTableMainTypes($db, $tbname); + set_error_handler("warningHandler", E_WARNING); fetchBoth($db, $tbname); } catch (PDOException $e) { var_dump($e); @@ -73,7 +94,7 @@ try { ?> ---EXPECTF-- +--EXPECT-- array(8) { ["flags"]=> @@ -217,7 +238,4 @@ array(7) { ["precision"]=> int(0) } - -Warning: PDOStatement::getColumnMeta(): SQLSTATE[42P10]: Invalid column reference: column number must be non-negative in %s on line %x -bool(false) bool(false) diff --git a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt index e7075ee46..2aba3fa9d 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_getColumnMeta_unicode_col_name.phpt @@ -57,15 +57,35 @@ function fetchBoth($conn, $tbname) // Test invalid arguments, set error mode to silent to reduce the amount of error messages generated $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); - // Test negative column number, ignore the error messages - $meta = $stmt->getColumnMeta(-1); - var_dump($meta); + // Test negative column number + try { + $meta = $stmt->getColumnMeta(-1); + echo "Expect getColumnMeta to fail with -1\n"; + } catch (Error $e) { + if (PHP_MAJOR_VERSION == 8) { + $error = '*PDOStatement::getColumnMeta(): Argument #1 ($index) must be greater than or equal to 0*'; + } else { + $error = '*Invalid column reference: column number must be non-negative*'; + } + if (!fnmatch($error, $e->getMessage())) { + echo "Unexpected error:"; + var_dump($e->getMessage()); + } + } // Test non-existent column number $meta = $stmt->getColumnMeta(10); var_dump($meta); } +// When testing with PHP 8.0 the negative test case throws an Error instead of a warning. +// Implement a custom warning handler such that with PHP 7.x the warning would be handled +// to throw an Error. +function warningHandler($errno, $errstr) +{ + throw new Error($errstr); +} + function createAndInsertTableUnicode($conn, $tbname) { try { @@ -106,13 +126,14 @@ try { $db = connect(); $tbname = "PDO_MainTypes"; createAndInsertTableUnicode($db, $tbname); + set_error_handler("warningHandler", E_WARNING); fetchBoth($db, $tbname); } catch (PDOException $e) { var_dump($e); exit; } ?> ---EXPECTF-- +--EXPECT-- array(8) { ["flags"]=> @@ -256,7 +277,4 @@ array(7) { ["precision"]=> int(0) } - -Warning: PDOStatement::getColumnMeta(): SQLSTATE[42P10]: Invalid column reference: column number must be non-negative in %s on line %x -bool(false) bool(false) \ No newline at end of file From 26656bce06b60b8f4abb8342fedcf08635e80442 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 30 Sep 2020 16:06:41 -0700 Subject: [PATCH 249/249] Fixed appveyor.yml (#1195) --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 15a2962d7..f3f0f728c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -119,7 +119,7 @@ test_script: - ps: >- If ($env:BUILD_PLATFORM -Match "x86") { Write-Host "Running phpt tests via OpenCppCoverage..." - OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --excluded_sources ${env:PHP_SRC_DIR}\pdo_sqlsrv\shared\core_stream.cpp --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ --no-color | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8; + OpenCppCoverage.exe --sources ${env:PHP_SRC_DIR}\*sqlsrv --modules ${env:PHP_EXE_PATH}\php*sqlsrv.dll --excluded_sources ${env:PHP_SRC_DIR}\pdo_sqlsrv\shared\core_stream.cpp --export_type=cobertura:c:\projects\coverage.xml --quiet --cover_children --continue_after_cpp_exception --optimized_build -- .\php.exe run-tests.php -P --no-color ${env:APPVEYOR_BUILD_FOLDER}\test\functional\ | out-file -filePath ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -encoding UTF8; Write-Host "Showing the last 25 lines of the log file..." Get-Content ${env:APPVEYOR_BUILD_FOLDER}\test\functional\tests.log -Tail 25; ls *.xml