From 10ff344fca84f1393ba809c604395b6ad3006fbd Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 5 Jun 2018 13:09:51 -0700 Subject: [PATCH 01/92] 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 02/92] 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 03/92] 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 04/92] 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 05/92] 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 06/92] 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 07/92] 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 08/92] 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 09/92] 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 10/92] 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 11/92] 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 12/92] 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 13/92] 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 14/92] 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 15/92] 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 16/92] 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 17/92] 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 18/92] 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 19/92] 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 20/92] 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 21/92] 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 22/92] 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 23/92] 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 24/92] 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 25/92] 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 26/92] 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 27/92] 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 28/92] 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 29/92] 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 30/92] 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 31/92] 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 32/92] 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 33/92] 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 34/92] 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 35/92] 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 36/92] 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 37/92] 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 38/92] 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 39/92] 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 40/92] 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 41/92] 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 42/92] 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 43/92] 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 44/92] 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 45/92] 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 46/92] 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 47/92] 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 48/92] 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 49/92] 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_Connectionbecause 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 50/92] 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 51/92] 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 52/92] 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 53/92] 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 54/92] 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 55/92] 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 56/92] 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 57/92] 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 58/92] 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 59/92] 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 60/92] 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 61/92] 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 62/92] 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 63/92] 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 64/92] 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 65/92] 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 66/92] 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 67/92] 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 68/92] 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 69/92] 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 70/92] 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 71/92] 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 72/92] 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 73/92] 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 74/92] 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 75/92] 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 76/92] 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 77/92] 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 78/92] 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 79/92] 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 80/92] 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 81/92] 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 82/92] 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 83/92] 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 84/92] 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 85/92] 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 andPWD as well. // No need to check the keyword Trusted_Connectionbecause 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 86/92] 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 87/92] 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 88/92] 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 89/92] 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 90/92] 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 91/92] 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 92/92] 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?