From 48b6a6013f69a8183b5e7570e0405960386ac7bf Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 14 Sep 2021 17:36:35 -0700 Subject: [PATCH 01/15] Fixed a flawed emulate prepare test with AE (#1301) --- .../pdo_prepare_emulatePrepare_unicode.phpt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_unicode.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_unicode.phpt index 71a35f82..5b719891 100644 --- a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_unicode.phpt +++ b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_unicode.phpt @@ -47,17 +47,21 @@ try { print_r($row); //with emulate prepare and no bind param options - print_r("Prepare with emulate prepare and no bindParam options:\n"); if (!isAEConnected()) { $options = array(PDO::ATTR_EMULATE_PREPARES => true); } else { $options = array(PDO::ATTR_EMULATE_PREPARES => false); } - $stmt = prepareStmt($conn, $query, $options); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - print_r($row); - if ($stmt->rowCount() == 0) { - print_r("No results for this query\n"); + + print_r("Prepare with emulate prepare and no bindParam options:\n"); + // This test only makes sense without AE because the default encoding is PDO::SQLSRV_ENCODING_UTF8 + if (!isAEConnected()) { + $stmt = prepareStmt($conn, $query, $options); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($stmt->rowCount() != 0) { + print_r("Do not expect results for this query!\n"); + print_r($row); + } } //with emulate prepare and SQLSRV_ENCODING_UTF8 @@ -117,7 +121,6 @@ Array [age] => 30 ) Prepare with emulate prepare and no bindParam options: -No results for this query Prepare with emulate prepare and SQLSRV_ENCODING_UTF8: Array ( From c87af63d5728fb46ff0490000594f586337c8038 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 22 Sep 2021 09:40:57 -0700 Subject: [PATCH 02/15] Updated tests to run against AE v2 server (#1305) --- azure-pipelines.yml | 2 ++ .../pdo_sqlsrv/MsCommon_mid-refactor.inc | 19 +++++++++++++++++++ .../pdo_sqlsrv/pdo_azure_ad_access_token.phpt | 2 +- .../pdo_sqlsrv/pdo_connect_encrypted.phpt | 13 ++++--------- test/functional/pdo_sqlsrv/pdo_construct.phpt | 3 ++- .../pdo_construct_conn_options.phpt | 3 ++- test/functional/pdo_sqlsrv/skipif_azure.inc | 6 ++++++ test/functional/pdo_sqlsrv/skipif_not_hgs.inc | 2 +- test/functional/sqlsrv/MsCommon.inc | 15 +++++++++++++++ test/functional/sqlsrv/skipif_azure.inc | 4 ++++ test/functional/sqlsrv/skipif_not_hgs.inc | 2 +- .../sqlsrv/sqlsrv_azure_ad_access_token.phpt | 2 +- .../sqlsrv/sqlsrv_connect_encrypted.phpt | 8 ++------ 13 files changed, 60 insertions(+), 21 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 57824f77..944cc954 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -206,10 +206,12 @@ jobs: coveralls -i ./source/ -e ./source/shared/ -e ./test/ -e ./source/pdo_sqlsrv/shared/core_stream.cpp \ -E r'.*localization*' -E r'.*globalization*' --gcov-options '\-lp' displayName: 'Invoke coveralls using repo token' + condition: false env: COVERALLS_REPO_TOKEN: $(repo_token) TRAVIS_JOB_ID: $(Build.BuildId) TRAVIS_BRANCH: $(Build.SourceBranchName) + PYTHONWARNINGS: ignore::yaml.YAMLLoadWarning - script: | cd $(Build.SourcesDirectory)/test/functional/ diff --git a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc index 44d88454..5c120200 100644 --- a/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc +++ b/test/functional/pdo_sqlsrv/MsCommon_mid-refactor.inc @@ -636,6 +636,25 @@ function IsDaasMode() return ($daasMode ? true : false); } +function isServerHGSEnabled() +{ + $enabled = false; + try { + $conn = connect(); + $tsql = "SELECT @@SERVERNAME"; + $stmt = $conn->query($tsql); + + $result = $stmt->fetch(PDO::FETCH_NUM); + $name = $result[0]; + + $enabled = (strpos($result[0], 'HGS') != false); + } catch (Exception $e) { + echo $e->getMessage(); + die("Could not fetch server property."); + } + return $enabled; +} + function isSQLAzure() { // 'SQL Azure' indicates SQL Database or SQL Data Warehouse diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt index ce36df03..56f271a4 100644 --- a/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_access_token.phpt @@ -3,7 +3,7 @@ Test some basics of Azure AD Access Token support --DESCRIPTION-- This test also expects certain exceptions to be thrown under some conditions. --SKIPIF-- - --FILE-- --FILE-- getAttribute(PDO::ATTR_CLIENT_VERSION)['DriverVer']; $version = explode(".", $msodbcsqlVer); $msodbcsqlMaj = $version[0]; - - // Next, check if the server is HGS enabled - $serverInfo = $conn->getAttribute(PDO::ATTR_SERVER_INFO); - if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) { - $hgsEnabled = false; - } } catch (PDOException $e) { echo "Failed to connect\n"; print_r($e->getMessage()); @@ -32,7 +28,6 @@ try { testColumnEncryption($server, $uid, $pwd, $msodbcsqlMaj); echo "Done"; - function verifyOutput($PDOerror, $expected, $caseNum) { if (strpos($PDOerror->getMessage(), $expected) === false) { diff --git a/test/functional/pdo_sqlsrv/pdo_construct.phpt b/test/functional/pdo_sqlsrv/pdo_construct.phpt index eba2c640..d5ab56d6 100644 --- a/test/functional/pdo_sqlsrv/pdo_construct.phpt +++ b/test/functional/pdo_sqlsrv/pdo_construct.phpt @@ -1,7 +1,8 @@ --TEST-- Test PDO::__Construct by passing connection options and attributes. --SKIPIF-- - + --FILE-- + --FILE-- \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/skipif_not_hgs.inc b/test/functional/pdo_sqlsrv/skipif_not_hgs.inc index ccf177ab..a7d72bb6 100644 --- a/test/functional/pdo_sqlsrv/skipif_not_hgs.inc +++ b/test/functional/pdo_sqlsrv/skipif_not_hgs.inc @@ -34,7 +34,7 @@ if ($msodbcsqlMin < 4 and $msodbcsqlMaj == 17) { // Get SQL Server $serverInfo = $conn->getAttribute(PDO::ATTR_SERVER_INFO); -if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) { +if (strpos($serverInfo['SQLServerName'], 'HGS') === false) { die("skip Server is not HGS enabled"); } ?> \ No newline at end of file diff --git a/test/functional/sqlsrv/MsCommon.inc b/test/functional/sqlsrv/MsCommon.inc index ddefaac6..cc260a77 100644 --- a/test/functional/sqlsrv/MsCommon.inc +++ b/test/functional/sqlsrv/MsCommon.inc @@ -94,6 +94,21 @@ function isLocaleDisabled() return ($daasMode || $localeDisabled); } +function isServerHGSEnabled() +{ + $conn = connect(); + $tsql = "SELECT @@SERVERNAME"; + $stmt = sqlsrv_query($conn, $tsql); + + if (sqlsrv_fetch($stmt)) { + $name = sqlsrv_get_field($stmt, 0); + if (strpos($name, 'HGS') != false) { + return true; + } + } + return false; +} + function isSQLAzure() { // 'SQL Azure' indicates SQL Database or SQL Data Warehouse diff --git a/test/functional/sqlsrv/skipif_azure.inc b/test/functional/sqlsrv/skipif_azure.inc index 81c557a1..caffc08e 100644 --- a/test/functional/sqlsrv/skipif_azure.inc +++ b/test/functional/sqlsrv/skipif_azure.inc @@ -7,4 +7,8 @@ if (!extension_loaded("sqlsrv")) { require 'MsSetup.inc'; if ($daasMode) die("skip test not applicable in Azure\n"); +require 'MsCommon.inc'; +if (isServerHGSEnabled()) { + die("skip test not applicable in Azure VM\n"); +} ?> \ No newline at end of file diff --git a/test/functional/sqlsrv/skipif_not_hgs.inc b/test/functional/sqlsrv/skipif_not_hgs.inc index 5295a73a..be0c822f 100644 --- a/test/functional/sqlsrv/skipif_not_hgs.inc +++ b/test/functional/sqlsrv/skipif_not_hgs.inc @@ -34,7 +34,7 @@ if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) { // Get SQL Server $server_info = sqlsrv_server_info($conn); -if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) { +if (strpos($server_info['SQLServerName'], 'HGS') === false) { die("skip Server is not HGS enabled"); } ?> diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt index fcbc4080..62c1e3d1 100644 --- a/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_access_token.phpt @@ -3,7 +3,7 @@ Test some basics of Azure AD Access Token support --DESCRIPTION-- This test also expects certain exceptions to be thrown under some conditions. --SKIPIF-- - --FILE-- $database,"UID"=>$userName, "PWD"=>$userPassword); testColumnEncryption($server, $connectionOptions); @@ -25,11 +25,7 @@ function testColumnEncryption($server, $connectionOptions) $msodbcsqlMaj = explode(".", $msodbcsql_ver)[0]; // Next, check if the server is HGS enabled - $hgsEnabled = true; - $serverInfo = sqlsrv_server_info($conn); - if (strpos($serverInfo['SQLServerName'], 'PHPHGS') === false) { - $hgsEnabled = false; - } + $hgsEnabled = isServerHGSEnabled(); // Only works for ODBC 17 $connectionOptions['ColumnEncryption'] = 'Enabled'; From 36d2704c0aca75ac3724bb2ff8a9bced4d12b5fa Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 29 Sep 2021 12:27:52 -0700 Subject: [PATCH 03/15] Added TVP support to non-procedure statements (#1309) --- source/shared/core_stmt.cpp | 48 ++++++- .../pdo_1307_tvp_non_procedure.phpt | 97 ++++++++++++++ .../pdo_1307_tvp_non_procedure_schema.phpt | 105 ++++++++++++++++ .../sqlsrv/srv_1307_tvp_non_procedure.phpt | 111 ++++++++++++++++ .../srv_1307_tvp_non_procedure_schema.phpt | 119 ++++++++++++++++++ 5 files changed, 474 insertions(+), 6 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure_schema.phpt create mode 100644 test/functional/sqlsrv/srv_1307_tvp_non_procedure.phpt create mode 100644 test/functional/sqlsrv/srv_1307_tvp_non_procedure_schema.phpt diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 400ac5c1..f3ffc397 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -3107,8 +3107,6 @@ void sqlsrv_param_tvp::process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* pa int num_columns = parse_tv_param_arrays(stmt, param_z); column_size = num_rows; - buffer = NULL; - buffer_length = 0; strlen_or_indptr = (num_columns == 0)? SQL_DEFAULT_PARAM : SQL_DATA_AT_EXEC; } else { // This is one of the constituent columns of the table-valued parameter @@ -3154,11 +3152,16 @@ int sqlsrv_param_tvp::parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ z throw core::CoreException(); } + // Save the TVP type name for SQLSetDescField later + buffer = ZSTR_VAL(tvp_name); + buffer_length = SQL_NTS; + // Check if schema is provided by the user if (zend_hash_move_forward_ex(inputs_ht, &pos) == SUCCESS) { zval *schema_z = zend_hash_get_current_data_ex(inputs_ht, &pos); if (schema_z != NULL && Z_TYPE_P(schema_z) == IS_STRING) { schema_name = Z_STR_P(schema_z); + ZVAL_NEW_STR(&placeholder_z, schema_name); } } @@ -3308,6 +3311,34 @@ void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt) return; } + // Set Table-Valued parameter type name (and the schema where it is defined) + SQLHDESC hIpd = NULL; + core::SQLGetStmtAttr(stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0); + + if (buffer != NULL) { + // SQL_CA_SS_TYPE_NAME is optional for stored procedure calls, but it must be + // specified for SQL statements that are not procedure calls to enable the + // server to determine the type of the table-valued parameter. + char *tvp_name = reinterpret_cast(buffer); + SQLRETURN r = ::SQLSetDescField(hIpd, param_pos + 1, SQL_CA_SS_TYPE_NAME, reinterpret_cast(tvp_name), SQL_NTS); + CHECK_SQL_ERROR_OR_WARNING(r, stmt) { + throw core::CoreException(); + } + } + if (Z_TYPE(placeholder_z) == IS_STRING) { + // If the table type for the table-valued parameter is defined in a different + // schema than the default, SQL_CA_SS_SCHEMA_NAME must be specified. If not, + // the server will not be able to determine the type of the table-valued parameter. + char * schema_name = Z_STRVAL(placeholder_z); + SQLRETURN r = ::SQLSetDescField(hIpd, param_pos + 1, SQL_CA_SS_SCHEMA_NAME, reinterpret_cast(schema_name), SQL_NTS); + CHECK_SQL_ERROR_OR_WARNING(r, stmt) { + throw core::CoreException(); + } + // Free and reset the placeholder_z + zend_string_release(Z_STR(placeholder_z)); + ZVAL_UNDEF(&placeholder_z); + } + // Bind the TVP columns one by one // Register this object first using SQLSetDescField() for sending TVP data post execution SQLHDESC desc; @@ -3606,12 +3637,17 @@ bool sqlsrv_params_container::get_next_parameter(_Inout_ sqlsrv_stmt* stmt) // Done now, reset current_param current_param = NULL; return false; + } else if (r == SQL_NEED_DATA) { + if (param != NULL) { + current_param = reinterpret_cast(param); + SQLSRV_ASSERT(current_param != NULL, "sqlsrv_params_container::get_next_parameter - The parameter requested is missing!"); + current_param->init_data_from_zval(stmt); + } else { + // Do not reset current_param when param is NULL, because + // it means that data is expected from the existing current_param + } } - current_param = reinterpret_cast(param); - SQLSRV_ASSERT(current_param != NULL, "sqlsrv_params_container::get_next_parameter - The parameter requested is missing!"); - current_param->init_data_from_zval(stmt); - return true; } diff --git a/test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure.phpt b/test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure.phpt new file mode 100644 index 00000000..a5cc1a0e --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure.phpt @@ -0,0 +1,97 @@ +--TEST-- +Verify Github Issue 1307 is fixed. +--DESCRIPTION-- +To show that table-valued parameters work with non-procedure statements +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + + $dropTableType = dropTableTypeSQL($conn, $tvpname); + $conn->exec($dropTableType); + $conn->exec("DROP TABLE IF EXISTS [$testTable]"); + + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +} + +function readData($conn, $testTable) +{ + $tsql = "SELECT id FROM $testTable ORDER BY id"; + $stmt = $conn->query($tsql); + $stmt->bindColumn('id', $ID); + while ($row = $stmt->fetch( PDO::FETCH_BOUND ) ){ + echo $ID . PHP_EOL; + } +} + +try { + $conn = new PDO("sqlsrv:Server=$server;Database=$databaseName;", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $tvpname = 'pdo_id_table'; + $testTable = 'pdo_test_table'; + + cleanup($conn, $tvpname, $testTable); + + // Create the table type and test table + $tsql = "CREATE TYPE $tvpname AS TABLE(id INT PRIMARY KEY)"; + $conn->exec($tsql); + + $tsql = "CREATE TABLE $testTable (id INT PRIMARY KEY)"; + $conn->exec($tsql); + + // Populate the table using the table type + $tsql = "INSERT INTO $testTable SELECT * FROM ?"; + $tvpinput = array($tvpname => [[1], [2], [3]]); + + $stmt = $conn->prepare($tsql); + $stmt->bindParam(1, $tvpinput, PDO::PARAM_LOB); + $result = $stmt->execute(); + + // Verify the results + readData($conn, $testTable); + + // Use Merge statement next + $tsql = << [[5], [4], [3], [2]]); + + $stmt = $conn->prepare($tsql); + $stmt->bindParam(1, $tvpinput, PDO::PARAM_LOB); + $result = $stmt->execute(); + + // Verify the results + readData($conn, $testTable); + + cleanup($conn, $tvpname, $testTable); + + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e); +} +?> +--EXPECT-- +1 +2 +3 +1 +2 +3 +4 +5 +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure_schema.phpt b/test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure_schema.phpt new file mode 100644 index 00000000..a924b12f --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure_schema.phpt @@ -0,0 +1,105 @@ +--TEST-- +Verify Github Issue 1307 is fixed but TVP and table are defined in a different schema +--DESCRIPTION-- +To show that table-valued parameters work with non-procedure statements +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + + $dropTableType = dropTableTypeSQL($conn, $tvpname, $schema); + $conn->exec($dropTableType); + $conn->exec("DROP TABLE IF EXISTS [$schema].[$testTable]"); + $conn->exec("DROP SCHEMA IF EXISTS [$schema]"); + + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +} + +function readData($conn, $schema, $testTable) +{ + $tsql = "SELECT id FROM [$schema].[$testTable] ORDER BY id"; + $stmt = $conn->query($tsql); + $stmt->bindColumn('id', $ID); + while ($row = $stmt->fetch( PDO::FETCH_BOUND ) ){ + echo $ID . PHP_EOL; + } +} + +try { + $conn = new PDO("sqlsrv:Server=$server;Database=$databaseName;", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $tvpname = 'pdo_id_table2'; + $testTable = 'pdo_test_table2'; + $schema = 'pdo schema'; + + cleanup($conn, $tvpname, $testTable, $schema); + + // Create the schema + $tsql = "CREATE SCHEMA [$schema]"; + $conn->exec($tsql); + + // Create the table type and test table + $tsql = "CREATE TYPE [$schema].[$tvpname] AS TABLE(id INT PRIMARY KEY)"; + $conn->exec($tsql); + + $tsql = "CREATE TABLE [$schema].[$testTable] (id INT PRIMARY KEY)"; + $conn->exec($tsql); + + // Populate the table using the table type + $tsql = "INSERT INTO [$schema].[$testTable] SELECT * FROM ?"; + $tvpinput = array($tvpname => [[5], [3], [1]], $schema); + + $stmt = $conn->prepare($tsql); + $stmt->bindParam(1, $tvpinput, PDO::PARAM_LOB); + $result = $stmt->execute(); + + // Verify the results + readData($conn, $schema, $testTable); + + // Use Merge statement next + $tsql = << [[2], [4], [6], [7]], $schema); + + $stmt = $conn->prepare($tsql); + $stmt->bindParam(1, $tvpinput, PDO::PARAM_LOB); + $result = $stmt->execute(); + + // Verify the results + readData($conn, $schema, $testTable); + + cleanup($conn, $tvpname, $testTable, $schema); + + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e); +} +?> +--EXPECT-- +1 +3 +5 +1 +2 +3 +4 +5 +6 +7 +Done \ No newline at end of file diff --git a/test/functional/sqlsrv/srv_1307_tvp_non_procedure.phpt b/test/functional/sqlsrv/srv_1307_tvp_non_procedure.phpt new file mode 100644 index 00000000..b718433a --- /dev/null +++ b/test/functional/sqlsrv/srv_1307_tvp_non_procedure.phpt @@ -0,0 +1,111 @@ +--TEST-- +Verify Github Issue 1307 is fixed. +--DESCRIPTION-- +To show that table-valued parameters work with non-procedure statements +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + [[5], [7], [9]]]], +]; + +$stmt = sqlsrv_query($conn, $tsql, $params); +if (!$stmt) { + print_r(sqlsrv_errors()); +} +sqlsrv_free_stmt($stmt); + +// Verify the results +readData($conn, $testTable); + +// Use Merge statement next +$tsql = << [[2], [6], [4], [8], [3]]]], +]; + +$stmt = sqlsrv_prepare($conn, $tsql, $params); +if (!$stmt) { + print_r(sqlsrv_errors()); +} +$result = sqlsrv_execute($stmt); +if (!$result) { + print_r(sqlsrv_errors()); +} + +// Verify the results +readData($conn, $testTable); + +cleanup($conn, $tvpname, $testTable); + +echo "Done\n"; + +sqlsrv_close($conn); +?> +--EXPECT-- +5 +7 +9 +2 +3 +4 +5 +6 +7 +8 +9 +Done diff --git a/test/functional/sqlsrv/srv_1307_tvp_non_procedure_schema.phpt b/test/functional/sqlsrv/srv_1307_tvp_non_procedure_schema.phpt new file mode 100644 index 00000000..75aa1f39 --- /dev/null +++ b/test/functional/sqlsrv/srv_1307_tvp_non_procedure_schema.phpt @@ -0,0 +1,119 @@ +--TEST-- +Verify Github Issue 1307 is fixed but TVP and table are defined in a different schema +--DESCRIPTION-- +To show that table-valued parameters work with non-procedure statements +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + [[15], [13], [11]], $schema]], +]; + +$stmt = sqlsrv_query($conn, $tsql, $params); +if (!$stmt) { + print_r(sqlsrv_errors()); +} +sqlsrv_free_stmt($stmt); + +// Verify the results +readData($conn, $schema, $testTable); + +// Use Merge statement next +$tsql = << [[10], [16], [14], [12]], $schema]], +]; + +$stmt = sqlsrv_prepare($conn, $tsql, $params); +if (!$stmt) { + print_r(sqlsrv_errors()); +} +$result = sqlsrv_execute($stmt); +if (!$result) { + print_r(sqlsrv_errors()); +} + +// Verify the results +readData($conn, $schema, $testTable); + +cleanup($conn, $tvpname, $testTable, $schema); + +echo "Done\n"; + +sqlsrv_close($conn); +?> +--EXPECT-- +11 +13 +15 +10 +11 +12 +13 +14 +15 +16 +Done From 8e461ca0ee4c0e6dea1b424d7cc4a2b8d8cdc6fd Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 12 Oct 2021 14:43:13 -0700 Subject: [PATCH 04/15] Use Invoke-WebRequest instead of Msxml2.XMLHTTP for source indexing (#1315) --- buildscripts/indexsymbols.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/buildscripts/indexsymbols.py b/buildscripts/indexsymbols.py index bcf6ffbd..7bc761ef 100644 --- a/buildscripts/indexsymbols.py +++ b/buildscripts/indexsymbols.py @@ -25,7 +25,7 @@ def write_index(index_filename, tag_version): 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)" + SRCSRVCMD=powershell -Command "Invoke-WebRequest -Uri '%SRCURL%' -OutFile '%SRCSRVTRG%'" SRCVERSION=v5.6.0 PDBVERSION=v5.6.0 For example @@ -38,9 +38,7 @@ def write_index(index_filename, tag_version): 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('\"Invoke-WebRequest -Uri \'%SRCURL%\' -OutFile \'%SRCSRVTRG%\'\"' + os.linesep) f.write('SRCVERSION=' + tag_version + os.linesep) f.write('PDBVERSION=' + tag_version + os.linesep) From 608a08005c5fb38cb2553a40d989868f07198ccd Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 19 Oct 2021 11:36:53 -0700 Subject: [PATCH 05/15] Simplified a skipif file for checking tcp protocol (#1317) --- .../pdo_sqlsrv/skipif_protocol_not_tcp.inc | 42 ++++++++--------- .../sqlsrv/skipif_protocol_not_tcp.inc | 45 +++++++++---------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/test/functional/pdo_sqlsrv/skipif_protocol_not_tcp.inc b/test/functional/pdo_sqlsrv/skipif_protocol_not_tcp.inc index e59d550b..4ba27a99 100644 --- a/test/functional/pdo_sqlsrv/skipif_protocol_not_tcp.inc +++ b/test/functional/pdo_sqlsrv/skipif_protocol_not_tcp.inc @@ -3,31 +3,31 @@ if (!extension_loaded("pdo_sqlsrv")) { die("skip Extension not loaded"); } -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$conn = new PDO( "sqlsrv:server = $server ;", $uid, $pwd ); -if( $conn === false ) -{ - die( "skip Could not connect during SKIPIF." ); +try { + $conn = new PDO("sqlsrv:server = $server", $uid, $pwd); +} catch (PDOException $e) { + die("skip Could not connect during SKIPIF."); } -// Get process ID. Not the same as the one during the actual test, but -// we only need to know the protocol for a particular connection. -$stmt = $conn->query( "SELECT @@SPID" ); -if ( $stmt ) -{ - $spid = $stmt->fetch(PDO::FETCH_NUM)[0]; -} -else -{ - die( "skip Could not fetch SPID during SKIPIF."); -} +$tsql = <<query( "SELECT * FROM sys.dm_exec_connections WHERE session_id = ".$spid); -$prot = $stmt->fetchColumn(3); +try { + // Check the transport protocol for the current connection + $stmt = $conn->query($tsql); + $prot = $stmt->fetchColumn(1); -if ($prot != 'TCP') -{ - die("skip Not using a TCP protocol." ); + if ($prot != 'TCP') { + die("skip Not using a TCP protocol."); + } +} catch (PDOException $e) { + die("skip Failed to fetch SPID and transport protocol."); } ?> diff --git a/test/functional/sqlsrv/skipif_protocol_not_tcp.inc b/test/functional/sqlsrv/skipif_protocol_not_tcp.inc index 26d960ca..44cf6875 100644 --- a/test/functional/sqlsrv/skipif_protocol_not_tcp.inc +++ b/test/functional/sqlsrv/skipif_protocol_not_tcp.inc @@ -3,35 +3,32 @@ if (!extension_loaded("sqlsrv")) { die("skip Extension not loaded"); } -require_once( "MsSetup.inc" ); +require_once("MsSetup.inc"); -$connectionInfo = array( "UID"=>$userName, "PWD"=>$userPassword ); +$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword); -$conn = sqlsrv_connect( $server, $connectionInfo ); -if( $conn === false ) -{ - die( "skip Could not connect during SKIPIF." ); +$conn = sqlsrv_connect($server, $connectionInfo); +if ($conn === false) { + die("skip Could not connect during SKIPIF."); } -// Get process ID. Not the same as the one during the actual test, but -// we only need to know the protocol for a particular connection. -$stmt = sqlsrv_query( $conn, "SELECT @@SPID" ); -if ( sqlsrv_fetch( $stmt ) ) -{ - $spid = sqlsrv_get_field( $stmt, 0 ); -} -else -{ - die("skip Could not fetch SPID."); -} +$tsql = << From 8de09789af8a1f52f1913e11df81e61abb5ec24a Mon Sep 17 00:00:00 2001 From: "George J. Carrette" Date: Wed, 20 Oct 2021 12:27:13 -0400 Subject: [PATCH 06/15] adjust sql_data_type and column_size for NULL parameters (#1311) Fix for #1310 - Co-authored-by: George Carrette --- source/shared/core_stmt.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index f3ffc397..6cf50d49 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -2148,16 +2148,17 @@ void sqlsrv_param::process_null_param(_Inout_ zval* param_z) // Derive the param SQL type only if it is unknown if (sql_data_type == SQL_UNKNOWN_TYPE) { // Use the encoding to guess whether the sql_type is binary type or char type. For NULL cases, - // if the server type is a binary type, than the server expects the sql_type to be binary type + // if the server type is a binary type, then the server expects the sql_type to be binary type // as well, otherwise an error stating "Implicit conversion not allowed.." is thrown by the - // server. For all other server types, setting the sql_type to sql_char works fine. - sql_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_BINARY : SQL_CHAR; + // server. For all other server types, setting the sql_type to sql_varchar works fine. + // It must be varchar with column size 0 for ISNULL to work properly. + sql_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_BINARY : SQL_VARCHAR; } c_data_type = (encoding == SQLSRV_ENCODING_BINARY) ? SQL_C_BINARY : SQL_C_CHAR; if (column_size == SQLSRV_UNKNOWN_SIZE) { - column_size = 1; + column_size = (encoding == SQLSRV_ENCODING_BINARY) ? 1 : 0; decimal_digits = 0; } buffer = NULL; From e3042e1ed4117bfad07b98c88c60bf098dae8db1 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 27 Oct 2021 11:07:33 -0700 Subject: [PATCH 07/15] Added new test cases for Issue 1310 (#1321) --- .../pdo_sqlsrv/pdo_1310_null_varchar.phpt | 88 +++++++++++++++++++ .../sqlsrv/srv_1310_null_varchar.phpt | 64 ++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_1310_null_varchar.phpt create mode 100644 test/functional/sqlsrv/srv_1310_null_varchar.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_1310_null_varchar.phpt b/test/functional/pdo_sqlsrv/pdo_1310_null_varchar.phpt new file mode 100644 index 00000000..47d1b68f --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1310_null_varchar.phpt @@ -0,0 +1,88 @@ +--TEST-- +GitHub issue 1310 - bind null field as varchar(max) if not binary +--DESCRIPTION-- +The test shows null fields are no longer bound as char(1) if not binary such that it solves both issues 1310 and 1102. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // Issue 1310 + $query = "SELECT CAST(ISNULL(:K, -1) AS INT) AS K"; + $k = null; + + $stmt = $conn->prepare($query); + $stmt->bindParam(':K', $k, PDO::PARAM_NULL); + $stmt->execute(); + + $row = $stmt->fetchAll(PDO::FETCH_ASSOC); + var_dump($row); + + $stmt->bindParam(':K', $k, PDO::PARAM_INT); + $stmt->execute(); + + $row = $stmt->fetchAll(PDO::FETCH_NUM); + var_dump($row); + + // Issue 1102 + $query = "DECLARE @d DATETIME = ISNULL(:K, GETDATE()); SELECT @d AS D;"; + $k = null; + + $stmt = $conn->prepare($query); + $stmt->bindParam(':K', $k, PDO::PARAM_NULL); + $stmt->execute(); + + $row = $stmt->fetchAll(PDO::FETCH_NUM); + var_dump($row); + + $stmt->bindParam(':K', $k, PDO::PARAM_INT); + $stmt->execute(); + + $row = $stmt->fetchAll(PDO::FETCH_ASSOC); + var_dump($row); + + echo "Done\n"; +} catch (PdoException $e) { + echo $e->getMessage(); +} + +?> +--EXPECTREGEX-- +array\(1\) { + \[0\]=> + array\(1\) { + \["K"\]=> + string\(2\) "-1" + } +} +array\(1\) { + \[0\]=> + array\(1\) { + \[0\]=> + string\(2\) "-1" + } +} +array\(1\) { + \[0\]=> + array\(1\) { + \[0\]=> + string\(23\) "20[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:00.000" + } +} +array\(1\) { + \[0\]=> + array\(1\) { + \["D"\]=> + string\(23\) "20[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:00.000" + } +} +Done + diff --git a/test/functional/sqlsrv/srv_1310_null_varchar.phpt b/test/functional/sqlsrv/srv_1310_null_varchar.phpt new file mode 100644 index 00000000..b701e3c6 --- /dev/null +++ b/test/functional/sqlsrv/srv_1310_null_varchar.phpt @@ -0,0 +1,64 @@ +--TEST-- +GitHub issue 1310 - bind null field as varchar(max) if not binary +--DESCRIPTION-- +The test shows null fields are no longer bound as char(1) if not binary such that it solves both issues 1310 and 1102. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + true); +$stmt = sqlsrv_query($conn, $query, $params, $options); +if (!$stmt) { + fatalError("Failed to query statement (2)."); +} + +while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { + var_dump($row); +} + +sqlsrv_free_stmt($stmt); +sqlsrv_close($conn); + +echo "Done\n"; + +?> +--EXPECTREGEX-- +array\(1\) { + \["K"\]=> + int\(-1\) +} +array\(1\) { + \["D"\]=> + string\(23\) "20[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:00.000" +} +Done From 3826f1a522af29b176ee0d375de2faafc8425396 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 2 Nov 2021 08:12:09 -0700 Subject: [PATCH 08/15] PDO::ATTR_EMULATE_PREPARES at the connection level (#1324) --- source/pdo_sqlsrv/pdo_dbh.cpp | 22 ++- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 1 + ...ment_bindParam_output_emulate_prepare.phpt | 55 ++++--- .../pdo_sqlsrv/pdo_1310_null_varchar.phpt | 4 +- .../pdo_1320_pdo_emulate_prepare.phpt | 62 ++++++++ .../pdo_prepare_emulatePrepare_unicode2.phpt | 137 ++++++++++++++++++ ...dostatement_fetchmode_emulate_prepare.phpt | 10 -- .../sqlsrv/srv_1310_null_varchar.phpt | 3 +- 8 files changed, 247 insertions(+), 47 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1320_pdo_emulate_prepare.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_unicode2.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index a2e19e1b..bddbd542 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -542,7 +542,8 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo fetch_datetime( false ), format_decimals( false ), decimal_places( NO_CHANGE_DECIMAL_PLACES ), - use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED) + use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED), + emulate_prepare(false) { if( client_buffer_max_size < 0 ) { client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT; @@ -734,7 +735,8 @@ bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql_zstr, // assign the methods for the statement object. This is necessary even if the // statement fails so the user can retrieve the error information. stmt->methods = &pdo_sqlsrv_stmt_methods; - stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; // we support parameterized queries with ?, not names + // if not emulate_prepare, we support parameterized queries with ?, not names + stmt->supports_placeholders = (driver_dbh->emulate_prepare) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; // the statement options may override this later // Initialize the options array to be passed to the core layer ALLOC_HASHTABLE( pdo_stmt_options_ht ); @@ -1288,8 +1290,15 @@ bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR ); } - // Statement level only case PDO_ATTR_EMULATE_PREPARES: + { + driver_dbh->emulate_prepare = zend_is_true(val); + if (driver_dbh->emulate_prepare && driver_dbh->ce_option.enabled) { + THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED); + } + } + break; + // Statement level only case PDO_ATTR_CURSOR: case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: case SQLSRV_ATTR_DATA_CLASSIFICATION: @@ -1362,8 +1371,13 @@ int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ #endif } - // Statement level only case PDO_ATTR_EMULATE_PREPARES: + { + ZVAL_BOOL(return_value, driver_dbh->emulate_prepare); + break; + } + + // Statement level only case PDO_ATTR_CURSOR: case SQLSRV_ATTR_CURSOR_SCROLL_TYPE: case SQLSRV_ATTR_DATA_CLASSIFICATION: diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 7e472744..18a65acb 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -194,6 +194,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { bool format_decimals; short decimal_places; short use_national_characters; + bool emulate_prepare; pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver ); }; diff --git a/test/functional/pdo_sqlsrv/pdoStatement_bindParam_output_emulate_prepare.phpt b/test/functional/pdo_sqlsrv/pdoStatement_bindParam_output_emulate_prepare.phpt index 35f4197f..bd96f469 100644 --- a/test/functional/pdo_sqlsrv/pdoStatement_bindParam_output_emulate_prepare.phpt +++ b/test/functional/pdo_sqlsrv/pdoStatement_bindParam_output_emulate_prepare.phpt @@ -1,49 +1,44 @@ --TEST-- Tests error returned when binding output parameter with emulate prepare +--DESCRIPTION-- +The test shows that the option sets in prepared statements overrides the +connection setting of PDO::ATTR_EMULATE_PREPARES --SKIPIF-- --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); $count = 0; $query = "select ? = count(* ) from cd_info"; - $stmt = $conn->prepare($query, array(PDO::ATTR_EMULATE_PREPARES => true)); - $stmt->bindParam(1, $count, PDO::PARAM_STR, 10); - $stmt->execute(); - echo "Result: ".$count."\n"; - - $query = "select bigint_type, int_type, money_type from [test_types] where int_type < 0"; - $stmt1 = $conn->prepare($query); - $stmt1->execute(); - $row = $stmt1->fetch(PDO::FETCH_ASSOC); - print_r($row); - - $int = 0; - $bigint = 100; - $query = "select ? = bigint_type, ? = int_type, ? = money_type from [test_types] where int_type < 0"; - $stmt2 = $conn->prepare($query, array(PDO::ATTR_EMULATE_PREPARES => true)); - $stmt2->bindparam(1, $bigint, PDO::PARAM_STR, 256); - $stmt2->bindParam(2, $int, PDO::PARAM_INT, 4); - $stmt2->bindParam(3, $money, PDO::PARAM_STR, 1024); - $stmt2->execute(); - echo "Big integer: ".$bigint."\n"; - echo "Integer: ".$int."\n"; - echo "Money: ".$money."\n"; - - //free the statement and connection - unset($stmt); - unset($stmt1); - unset($stmt2); - unset($conn); + $stmt = $conn->prepare($query); } catch (PDOException $e) { print("Error: " . $e->getMessage() . "\n"); } + +try { + $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + + $int = 0; + $bigint = 100; + $query = "select ? = bigint_type, ? = int_type, ? = money_type from [test_types] where int_type < 0"; + $stmt = $conn->prepare($query, array(PDO::ATTR_EMULATE_PREPARES => true)); +} catch (PDOException $e) { + print("Error: " . $e->getMessage() . "\n"); +} + +// free the statement and connection +unset($stmt); +unset($conn); ?> --EXPECT-- Error: SQLSTATE[IMSSP]: Statement with emulate prepare on does not support output or input_output parameters. +Error: SQLSTATE[IMSSP]: Statement with emulate prepare on does not support output or input_output parameters. diff --git a/test/functional/pdo_sqlsrv/pdo_1310_null_varchar.phpt b/test/functional/pdo_sqlsrv/pdo_1310_null_varchar.phpt index 47d1b68f..3f0998e4 100644 --- a/test/functional/pdo_sqlsrv/pdo_1310_null_varchar.phpt +++ b/test/functional/pdo_sqlsrv/pdo_1310_null_varchar.phpt @@ -2,6 +2,7 @@ GitHub issue 1310 - bind null field as varchar(max) if not binary --DESCRIPTION-- The test shows null fields are no longer bound as char(1) if not binary such that it solves both issues 1310 and 1102. +Note that this test does not connect with AE enabled because SQLDescribeParam() does not work with these queries. --ENV-- PHPT_EXEC=true --SKIPIF-- @@ -9,10 +10,9 @@ PHPT_EXEC=true --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Issue 1310 diff --git a/test/functional/pdo_sqlsrv/pdo_1320_pdo_emulate_prepare.phpt b/test/functional/pdo_sqlsrv/pdo_1320_pdo_emulate_prepare.phpt new file mode 100644 index 00000000..c1c886e3 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1320_pdo_emulate_prepare.phpt @@ -0,0 +1,62 @@ +--TEST-- +GitHub issue 1320 - support PDO::ATTR_EMULATE_PREPARES at the connection level +--DESCRIPTION-- +Supports PDO::ATTR_EMULATE_PREPARES at the connection level but setting it to true with column +encryption enabled will fail with an exception. Also, the options in the prepared statement will +override the connection setting. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + echo "setAttribute should have failed because column encryption is enabled.\n\n"; +} catch (PDOException $e) { + echo $e->getMessage() . "\n"; +} + +unset($conn); + +try { + // Connection with column encryption enabled + $options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => true); + $connectionInfo = "ColumnEncryption = Enabled;"; + $conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd, $options); +} catch (PDOException $e) { + echo $e->getMessage() . "\n"; +} + +unset($conn); + +try { + // Connection with column encryption enabled - PDO::ATTR_EMULATE_PREPARES is false by default + $connectionInfo = "ColumnEncryption = Enabled;"; + $conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + echo "Connected successfully with column encryption enabled.\n"; + $enabled = $conn->getAttribute(PDO::ATTR_EMULATE_PREPARES); + echo "By default, the emulation of prepared statements is:\n"; + var_dump($enabled); +} catch (PDOException $e) { + echo $e->getMessage() . "\n"; +} + +?> + +--EXPECT-- +SQLSTATE[IMSSP]: Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection. +SQLSTATE[IMSSP]: Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection. +Connected successfully with column encryption enabled. +By default, the emulation of prepared statements is: +bool(false) diff --git a/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_unicode2.phpt b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_unicode2.phpt new file mode 100644 index 00000000..93d7efec --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_prepare_emulatePrepare_unicode2.phpt @@ -0,0 +1,137 @@ +--TEST-- +Prepare with emulate prepare and binding uft8 characters +--DESCRIPTION-- +This is the same as pdo_prepare_emulatePrepare_unicode.phpt except that +PDO::ATTR_EMULATE_PREPARES at the connection level +--SKIPIF-- + +--FILE-- +prepare($query, $prepareOptions); + $stmt->bindParam(':name', $name, $dataType, $length, $driverOptions); + } else { + $status = 1; + $stmt = $conn->prepare($query, $prepareOptions); + $stmt->bindParam(':name', $name, $dataType, $length, $driverOptions); + $stmt->bindParam(':status', $status); + } + $stmt->execute(); + return $stmt; +} + +try { + $conn = connect("", array(), PDO::ERRMODE_SILENT); + + $tableName = "users"; + createTable($conn, $tableName, array("name" => "nvarchar(max)", "status" => "int", "age" => "int")); + + if (!isColEncrypted()) { + $conn->exec("INSERT INTO [$tableName] (name, status, age) VALUES (N'Belle', 1, 34)"); + $conn->exec("INSERT INTO [$tableName] (name, status, age) VALUES (N'Абрам', 1, 40)"); + $conn->exec("INSERT INTO [$tableName] (name, status, age) VALUES (N'가각', 1, 30)"); + $query = "SELECT * FROM [$tableName] WHERE name = :name AND status = 1"; + } else { + insertRow($conn, $tableName, array("name" => "Belle", "status" => 1, "age" => 34)); + insertRow($conn, $tableName, array("name" => "Абрам", "status" => 1, "age" => 40)); + insertRow($conn, $tableName, array("name" => "가각", "status" => 1, "age" => 30)); + $query = "SELECT * FROM [$tableName] WHERE name = :name AND status = :status"; + } + + //without emulate prepare + print_r("Prepare without emulate prepare:\n"); + $stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + print_r($row); + + if (!isAEConnected()) { + $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + } else { + $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + } + + //with emulate prepare and no bind param options + print_r("Prepare with emulate prepare and no bindParam options:\n"); + // This test only makes sense without AE because the default encoding is PDO::SQLSRV_ENCODING_UTF8 + if (!isAEConnected()) { + $stmt = prepareStmt($conn, $query, array()); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($stmt->rowCount() != 0) { + print_r("Do not expect results for this query!\n"); + print_r($row); + } + } + + //with emulate prepare and SQLSRV_ENCODING_UTF8 + print_r("Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:\n"); + $stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + print_r($row); + + //with emulate prepare and SQLSRV_ENCODING_SYSTEM + print_r("Prepare with emulate prepare and and SQLSRV_ENCODING_SYSTEM:\n"); + $stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_SYSTEM); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + // The combination of Column Encryption and Unix platforms support SQLSRV_ENCODING_SYSTEM because: + // With Column Encryption enabled, binding parameters uses exact datatypes as the column definition + // the default encoding in Linux and Mac is UTF8 + $success = true; + if (!(strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN') && isAEConnected()) { + if ($row['name'] != "가각" || $row['status'] != 1 || $row['age'] != 30) { + print_r("Incorrect results retrieved.\n"); + $success = false; + } + } else { + // the default encoding in Windows is non-UTF8, thus binding UTF8 parameters does not work + if ($stmt->rowCount() != 0) { + print_r("Binding UTF8 data when encoding is SQLSRV_ENCODING_SYSTEM should not work.\n"); + $success = false; + } + } + if ($success) { + print_r("Binding UTF8 data with SQLSRV_ENCODING_SYSTEM is tested successfully.\n"); + } + + //with emulate prepare and encoding SQLSRV_ENCODING_BINARY + print_r("Prepare with emulate prepare and encoding SQLSRV_ENCODING_BINARY:\n"); + $stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_BINARY); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + print_r($row); + if ($stmt->rowCount() == 0) { + print_r("No results for this query\n"); + } + + dropTable($conn, $tableName); + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> + +--EXPECT-- +Prepare without emulate prepare: +Array +( + [name] => 가각 + [status] => 1 + [age] => 30 +) +Prepare with emulate prepare and no bindParam options: +Prepare with emulate prepare and SQLSRV_ENCODING_UTF8: +Array +( + [name] => 가각 + [status] => 1 + [age] => 30 +) +Prepare with emulate prepare and and SQLSRV_ENCODING_SYSTEM: +Binding UTF8 data with SQLSRV_ENCODING_SYSTEM is tested successfully. +Prepare with emulate prepare and encoding SQLSRV_ENCODING_BINARY: +No results for this query diff --git a/test/functional/pdo_sqlsrv/pdostatement_fetchmode_emulate_prepare.phpt b/test/functional/pdo_sqlsrv/pdostatement_fetchmode_emulate_prepare.phpt index bc21e3dc..bfa16afd 100644 --- a/test/functional/pdo_sqlsrv/pdostatement_fetchmode_emulate_prepare.phpt +++ b/test/functional/pdo_sqlsrv/pdostatement_fetchmode_emulate_prepare.phpt @@ -17,15 +17,6 @@ try { $tableName = "pdo_test_table"; createTable($conn1, $tableName, array(new ColumnMeta("int", "ID", "NOT NULL PRIMARY KEY"), "Policy" => "varchar(2)", "Label" => "varchar(10)", "Budget" => "money")); - try { - $res = $conn1->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); - if ($res) { - echo "setAttribute should have failed.\n\n"; - } - } catch (Exception $e) { - echo $e->getMessage() . "\n"; - } - try { $query = "SELECT * FROM [$tableName]"; $stmt = $conn1->query($query); @@ -186,7 +177,6 @@ try { ?> --EXPECTF-- -SQLSTATE[IMSSP]: The given attribute is only supported on the PDOStatement object. SQLSTATE[IMSSP]: An invalid attribute was designated on the PDOStatement object. Start inserting data... ....Done.... diff --git a/test/functional/sqlsrv/srv_1310_null_varchar.phpt b/test/functional/sqlsrv/srv_1310_null_varchar.phpt index b701e3c6..9c91bbb9 100644 --- a/test/functional/sqlsrv/srv_1310_null_varchar.phpt +++ b/test/functional/sqlsrv/srv_1310_null_varchar.phpt @@ -2,6 +2,7 @@ GitHub issue 1310 - bind null field as varchar(max) if not binary --DESCRIPTION-- The test shows null fields are no longer bound as char(1) if not binary such that it solves both issues 1310 and 1102. +Note that this test does not connect with AE enabled because SQLDescribeParam() does not work with these queries. --ENV-- PHPT_EXEC=true --SKIPIF-- @@ -10,7 +11,7 @@ PHPT_EXEC=true Date: Fri, 19 Nov 2021 01:14:55 +0900 Subject: [PATCH 09/15] Fix restoring PDO::ATTR_ERRMODE after PDO::lastInsertId() call failed (#1330) --- source/pdo_sqlsrv/pdo_dbh.cpp | 3 ++ ...restore_original_errmode_after_failed.phpt | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_lastInsertId_restore_original_errmode_after_failed.phpt diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index bddbd542..ddd5ca5e 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -1598,6 +1598,9 @@ zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_str driver_stmt->~sqlsrv_stmt(); } catch( core::CoreException& ) { + // restore error handling to its previous mode + dbh->error_mode = prev_err_mode; + // copy any errors on the statement to the connection so that the user sees them, since the statement is released // before this method returns strcpy_s( dbh->error_code, sizeof( dbh->error_code ), diff --git a/test/functional/pdo_sqlsrv/pdo_lastInsertId_restore_original_errmode_after_failed.phpt b/test/functional/pdo_sqlsrv/pdo_lastInsertId_restore_original_errmode_after_failed.phpt new file mode 100644 index 00000000..700c20c7 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_lastInsertId_restore_original_errmode_after_failed.phpt @@ -0,0 +1,52 @@ +--TEST-- +Confirm that PDO::ATTR_ERRMODE value should be restored whether PDO::lastInsertId() call succeeded or not. +--SKIPIF-- + +--FILE-- + "int")); + createTable($conn, "table2", array(new columnMeta("int", "id", "IDENTITY(200,2)"), "val" => "int")); + createTable($conn, "table3", array("id" => "int", "val" => "int")); + + insertRow($conn, "table1", array("val" => 1), "exec"); + insertRow($conn, "table2", array("val" => 2), "exec"); + $conn->lastInsertId(); + var_dump($conn->getAttribute(PDO::ATTR_ERRMODE)); + + insertRow($conn, "table2", array("val" => 3), "exec"); + insertRow($conn, "table1", array("val" => 4), "exec"); + $conn->lastInsertId(); + var_dump($conn->getAttribute(PDO::ATTR_ERRMODE)); + + // Should restore original value even if PDO::lastInsertId() failed. + insertRow($conn, "table3", array("id" => 1, "val" => 1), "exec"); + $conn->lastInsertId(); + var_dump($conn->getAttribute(PDO::ATTR_ERRMODE)); + + dropTable($conn, "table1"); + dropTable($conn, "table2"); + dropTable($conn, "table3"); + + // Should trigger exception + $tsql = "SELECT * FROM dummy"; + $conn->exec($tsql); + + unset($conn); +} catch (PDOException $e) { + print_r($e->getMessage()); + exit; +} + + +?> +--EXPECTREGEX-- +int\(2\) +int\(2\) +int\(2\) +.*Invalid object name \'dummy\'\. From 14aa44933d79b6baa0f8c3b0a837614903b4a201 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Thu, 18 Nov 2021 16:38:19 -0800 Subject: [PATCH 10/15] Changed return value for PDO::lastInsertId for 8.1+ (#1332) --- source/pdo_sqlsrv/pdo_dbh.cpp | 2 +- test/functional/pdo_sqlsrv/pdo_lastInsertId.phpt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index ddd5ca5e..a69127c1 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -1616,7 +1616,7 @@ zend_string * pdo_sqlsrv_dbh_last_id(_Inout_ pdo_dbh_t *dbh, _In_ const zend_str str[0] = '\0'; return str; #else - return NULL; + return ZSTR_EMPTY_ALLOC(); #endif } diff --git a/test/functional/pdo_sqlsrv/pdo_lastInsertId.phpt b/test/functional/pdo_sqlsrv/pdo_lastInsertId.phpt index 99ea03a5..7dfe4bbb 100644 --- a/test/functional/pdo_sqlsrv/pdo_lastInsertId.phpt +++ b/test/functional/pdo_sqlsrv/pdo_lastInsertId.phpt @@ -40,7 +40,7 @@ try { ?> ---EXPECTREGEX-- -string\(3\) "200" -string\(3\) "102" -(string\(0\) ""|bool\(false\)) +--EXPECT-- +string(3) "200" +string(3) "102" +string(0) "" From a5705228639958ea8bffeae7387ef574416205ba Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 22 Nov 2021 13:59:32 -0800 Subject: [PATCH 11/15] Reset sql type and column size for input params that were bound as nulls (#1333) --- source/shared/core_stmt.cpp | 7 ++ source/sqlsrv/stmt.cpp | 4 +- .../pdo_1329_string_truncation.phpt | 118 ++++++++++++++++++ .../sqlsrv/srv_1329_string_truncation.phpt | 112 +++++++++++++++++ 4 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_1329_string_truncation.phpt create mode 100644 test/functional/sqlsrv/srv_1329_string_truncation.phpt diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 6cf50d49..29511a82 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -395,6 +395,13 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ stmt->params_container.insert_param(param_num, new_param); param_ptr = new_param; new_param.transferred(); + } else if (direction == SQL_PARAM_INPUT + && param_ptr->sql_data_type != SQL_SS_TABLE + && param_ptr->strlen_or_indptr == SQL_NULL_DATA) { + // reset the followings for regular input parameters if it was bound as a null param before + param_ptr->sql_data_type = sql_type; + param_ptr->column_size = column_size; + param_ptr->strlen_or_indptr = 0; } SQLSRV_ASSERT(param_ptr != NULL, "core_sqlsrv_bind_param: param_ptr is null. Something went wrong."); diff --git a/source/sqlsrv/stmt.cpp b/source/sqlsrv/stmt.cpp index 609209c3..100273fa 100644 --- a/source/sqlsrv/stmt.cpp +++ b/source/sqlsrv/stmt.cpp @@ -1187,8 +1187,6 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt ) try { - stmt->free_param_data(); - stmt->executed = false; zval* params_z = stmt->params_z; @@ -1265,6 +1263,8 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt ) } ZEND_HASH_FOREACH_END(); } catch( core::CoreException& ) { + stmt->free_param_data(); + SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS ); zval_ptr_dtor( stmt->params_z ); sqlsrv_free( stmt->params_z ); diff --git a/test/functional/pdo_sqlsrv/pdo_1329_string_truncation.phpt b/test/functional/pdo_sqlsrv/pdo_1329_string_truncation.phpt new file mode 100644 index 00000000..7b1faf29 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_1329_string_truncation.phpt @@ -0,0 +1,118 @@ +--TEST-- +GitHub issue 1329 - string truncation error when binding some parameters as non-nulls the second time +--DESCRIPTION-- +The test shows the same parameters, though bound as nulls in the first insertion, can be bound as non-nulls in the subsequent insertions. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +exec($drop); +} + +try { + $conn = new PDO("sqlsrv:server=$server; Database = $databaseName;", $uid, $pwd); + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + dropTable($conn, 'domains'); + +$tsql = <<exec($tsql); + +$tsql = <<prepare($tsql); + $authority = 'foo.com'; + $base = null; + $notFound = null; + $invalid = null; + $stmt->bindParam(1, $authority); + $stmt->bindParam(2, $base); + $stmt->bindParam(3, $notFound); + $stmt->bindParam(4, $invalid); + $stmt->execute(); + + $authority = 'detached-with-ředirects.com'; + $base = 'fŏő.com'; + $notFound = 'baŗ.com'; + $invalid = null; + $stmt->bindParam(1, $authority); + $stmt->bindParam(2, $base); + $stmt->bindParam(3, $notFound); + $stmt->bindParam(4, $invalid); + $stmt->execute(); + + $authority = 'Őther-redirects.com'; + $base = 'fooš.com'; + $notFound = null; + $invalid = 'ŷëå'; + $stmt->bindParam(1, $authority); + $stmt->bindParam(2, $base); + $stmt->bindParam(3, $notFound); + $stmt->bindParam(4, $invalid); + $stmt->execute(); + + // fetch the data + $stmt = $conn->prepare("SELECT * FROM domains"); + $stmt->execute(); + $row = $stmt->fetchAll(PDO::FETCH_NUM); + print_r($row); + + dropTable($conn, 'domains'); + + echo "Done\n"; +} catch (PdoException $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Array +( + [0] => Array + ( + [0] => 1 + [1] => foo.com + [2] => + [3] => + [4] => + ) + + [1] => Array + ( + [0] => 2 + [1] => detached-with-ředirects.com + [2] => fŏő.com + [3] => baŗ.com + [4] => + ) + + [2] => Array + ( + [0] => 3 + [1] => Őther-redirects.com + [2] => fooš.com + [3] => + [4] => ŷëå + ) + +) +Done diff --git a/test/functional/sqlsrv/srv_1329_string_truncation.phpt b/test/functional/sqlsrv/srv_1329_string_truncation.phpt new file mode 100644 index 00000000..1395c92f --- /dev/null +++ b/test/functional/sqlsrv/srv_1329_string_truncation.phpt @@ -0,0 +1,112 @@ +--TEST-- +GitHub issue 1329 - string truncation error when binding some parameters as non-nulls the second time +--DESCRIPTION-- +The test shows the same parameters, though bound as nulls in the first insertion, can be bound as non-nulls in the subsequent insertions. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- +'UTF-8'); +$conn = AE\connect($connectionInfo); + +dropTable($conn, 'srv_domains'); + +$tsql = << +--EXPECT-- +Array +( + [0] => 1 + [1] => foo.com + [2] => + [3] => + [4] => +) +Array +( + [0] => 2 + [1] => detached-with-ředirects.com + [2] => fŏő.com + [3] => baŗ.com + [4] => +) +Array +( + [0] => 3 + [1] => Őther-redirects.com + [2] => fooš.com + [3] => + [4] => ŷëå +) +Done From cbfc7638e294085e51f1a80d6e6e6ac7213827dd Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 23 Nov 2021 12:52:08 -0800 Subject: [PATCH 12/15] Let ODBC driver verify azure ad authentications (#1326) --- source/pdo_sqlsrv/pdo_parser.cpp | 25 -------- source/pdo_sqlsrv/pdo_util.cpp | 8 --- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 4 -- source/shared/core_conn.cpp | 64 ++----------------- source/shared/core_sqlsrv.h | 6 -- source/sqlsrv/conn.cpp | 10 --- source/sqlsrv/php_sqlsrv_int.h | 1 - source/sqlsrv/util.cpp | 8 --- .../pdo_azure_ad_authentication.phpt | 18 ------ .../pdo_azure_ad_managed_identity.phpt | 55 ---------------- .../sqlsrv_azure_ad_authentication.phpt | 26 -------- .../sqlsrv_azure_ad_managed_identity.phpt | 31 --------- 12 files changed, 6 insertions(+), 250 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_parser.cpp b/source/pdo_sqlsrv/pdo_parser.cpp index 6d160893..2574a5ad 100644 --- a/source/pdo_sqlsrv/pdo_parser.cpp +++ b/source/pdo_sqlsrv/pdo_parser.cpp @@ -173,31 +173,6 @@ void conn_string_parser::validate_key( _In_reads_(key_len) const char *key, _Ino THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_DSN_KEY, static_cast( key_name ) ); } -void conn_string_parser::add_key_value_pair( _In_reads_(len) const char* value, _In_ int len ) -{ - // if the keyword is 'Authentication', check whether the user specified option is supported - bool valid = true; - if ( stricmp( this->current_key_name, ODBCConnOptions::Authentication ) == 0 ) { - if (len <= 0) - valid = false; - else { - // extract option from the value by len - sqlsrv_malloc_auto_ptr option; - option = static_cast( sqlsrv_malloc( len + 1 ) ); - memcpy_s( option, len + 1, value, len ); - option[len] = '\0'; - - valid = AzureADOptions::isAuthValid(option, len); - } - } - if( !valid ) { - THROW_PDO_ERROR( this->ctx, PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, this->current_key_name ); - } - - string_parser::add_key_value_pair( value, len ); -} - - inline bool sql_string_parser::is_placeholder_char( char c ) { // placeholder only accepts numbers, upper and lower case alphabets and underscore diff --git a/source/pdo_sqlsrv/pdo_util.cpp b/source/pdo_sqlsrv/pdo_util.cpp index cb0b0620..1edec362 100644 --- a/source/pdo_sqlsrv/pdo_util.cpp +++ b/source/pdo_sqlsrv/pdo_util.cpp @@ -377,10 +377,6 @@ pdo_error PDO_ERRORS[] = { PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED, { IMSSP, (SQLCHAR*) "Statement with emulate prepare on does not support output or input_output parameters.", -72, false } }, - { - PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, - { IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal is supported.", -73, false } - }, { SQLSRV_ERROR_CE_DRIVER_REQUIRED, { IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server (or above) for %1!s!.", -78, true } @@ -441,10 +437,6 @@ 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} - }, { SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION, { IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -94, false} diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index 18a65acb..f4915de8 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -149,9 +149,6 @@ class conn_string_parser : private string_parser int discard_trailing_white_spaces( _In_reads_(len) const char* str, _Inout_ int len ); void validate_key( _In_reads_(key_len) const char *key, _Inout_ int key_len); - protected: - void add_key_value_pair( _In_reads_(len) const char* value, _In_ int len); - public: conn_string_parser( _In_ sqlsrv_context& ctx, _In_ const char* dsn, _In_ int len, _In_ HashTable* conn_options_ht ); void parse_conn_string( void ); @@ -391,7 +388,6 @@ enum PDO_ERROR_CODES { 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, PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID diff --git a/source/shared/core_conn.cpp b/source/shared/core_conn.cpp index f4733fe6..4fb2caff 100644 --- a/source/shared/core_conn.cpp +++ b/source/shared/core_conn.cpp @@ -704,41 +704,6 @@ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t v return true; } -namespace AzureADOptions { - enum AAD_AUTH_TYPE { - MIN_AAD_AUTH_TYPE = 0, - SQL_PASSWORD = 0, - AAD_PASSWORD, - AAD_MSI, - AAD_SPA, - MAX_AAD_AUTH_TYPE - }; - - const char *AADAuths[] = { "SqlPassword", "ActiveDirectoryPassword", "ActiveDirectoryMsi", "ActiveDirectoryServicePrincipal" }; - - bool isAuthValid(_In_z_ const char* value, _In_ size_t value_len) - { - if (value_len <= 0) - return false; - - bool isValid = false; - for (short i = MIN_AAD_AUTH_TYPE; i < MAX_AAD_AUTH_TYPE && !isValid; i++) - { - if (!stricmp(value, AADAuths[i])) { - isValid = true; - } - } - - return isValid; - } - - bool isAADMsi(_In_z_ const char* value) - { - return (value != NULL && !stricmp(value, AADAuths[AAD_MSI])); - } -} - - // *** internal connection functions and classes *** namespace { @@ -792,10 +757,11 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou access_token_used = true; } - // Check if Authentication is ActiveDirectoryMSI + // Check if Authentication is ActiveDirectoryMSI because we have to handle this case differently // https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview bool activeDirectoryMSI = false; if (authentication_option_used) { + const char aadMSIoption[] = "ActiveDirectoryMSI"; zval* auth_option = NULL; auth_option = zend_hash_index_find(options, SQLSRV_CONN_OPTION_AUTHENTICATION); @@ -804,34 +770,16 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou option = Z_STRVAL_P(auth_option); } - //if (option != NULL && !stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) { - activeDirectoryMSI = AzureADOptions::isAADMsi(option); - if (activeDirectoryMSI) { - // 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(); - } + if (option != NULL && !stricmp(option, aadMSIoption)) { + activeDirectoryMSI = true; } } // Add the server name common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string ); - // If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used, - // because they are incompatible + // If uid is not present then we use trusted connection -- but not when connecting + // using the access token or Authentication is ActiveDirectoryMSI 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 8209bf3d..766b3f45 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -190,11 +190,6 @@ const int SQL_SERVER_2005_DEFAULT_DATETIME_SCALE = 3; const int SQL_SERVER_2008_DEFAULT_DATETIME_PRECISION = 34; const int SQL_SERVER_2008_DEFAULT_DATETIME_SCALE = 7; -namespace AzureADOptions { - bool isAuthValid(_In_z_ const char* value, _In_ size_t value_len); - bool isAADMsi(_In_z_ const char* value); -} - // the message returned by ODBC Driver for SQL Server const char ODBC_CONNECTION_BUSY_ERROR[] = "Connection is busy with results for another command"; @@ -2011,7 +2006,6 @@ 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, SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION, SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE, SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED, diff --git a/source/sqlsrv/conn.cpp b/source/sqlsrv/conn.cpp index c582c774..d83f4458 100644 --- a/source/sqlsrv/conn.cpp +++ b/source/sqlsrv/conn.cpp @@ -1362,16 +1362,6 @@ int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In throw ss::SSException(); } - bool valid = true; - if( stricmp( SS_CONN_OPTS[i].sqlsrv_name, SSConnOptionNames::Authentication ) == 0 ) { - valid = AzureADOptions::isAuthValid(value, value_len); - } - - CHECK_CUSTOM_ERROR( !valid, ctx, SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, SS_CONN_OPTS[i].sqlsrv_name ) { - - throw ss::SSException(); - } - break; } case CONN_ATTR_INVALID: diff --git a/source/sqlsrv/php_sqlsrv_int.h b/source/sqlsrv/php_sqlsrv_int.h index 8937e821..d4011d20 100644 --- a/source/sqlsrv/php_sqlsrv_int.h +++ b/source/sqlsrv/php_sqlsrv_int.h @@ -206,7 +206,6 @@ enum SS_ERROR_CODES { 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 }; diff --git a/source/sqlsrv/util.cpp b/source/sqlsrv/util.cpp index f7f50f9d..eaa801e0 100644 --- a/source/sqlsrv/util.cpp +++ b/source/sqlsrv/util.cpp @@ -363,10 +363,6 @@ ss_error SS_ERRORS[] = { "Output or bidirectional variable parameters (SQLSRV_PARAM_OUT and SQLSRV_PARAM_INOUT) passed to sqlsrv_prepare or sqlsrv_query should be passed by reference, not by value." , -61, true } }, - { - SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, - { IMSSP, (SQLCHAR*)"Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal is supported.", -62, false } - }, { SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED, { IMSSP, (SQLCHAR*)"Must specify the SQL type for each parameter in a parameterized query when using sqlsrv_query in a column encryption enabled connection.", -63, false } @@ -429,10 +425,6 @@ 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} - }, { SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION, { IMSSP, (SQLCHAR*) "The statement must be executed to retrieve Data Classification Sensitivity Metadata.", -119, false} diff --git a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt index 3d5147b5..8171625f 100644 --- a/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_authentication.phpt @@ -34,22 +34,6 @@ if ($stmt === false) { unset($conn); -/////////////////////////////////////////////////////////////////////////////////////////// -// Test Azure AD with integrated authentication. This should fail because -// we don't support it. -// -$connectionInfo = "Authentication = ActiveDirectoryIntegrated; TrustServerCertificate = true;"; - -try { - $conn = new PDO("sqlsrv:server = $server ; $connectionInfo"); - echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n"; - unset($conn); -} catch (PDOException $e) { - echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n"; - print_r($e->getMessage()); - echo "\n"; -} - /////////////////////////////////////////////////////////////////////////////////////////// // Test Azure AD on an Azure database instance. Replace $azureServer, etc with // your credentials to test, or this part is skipped. @@ -95,6 +79,4 @@ if ($azureServer != 'TARGET_AD_SERVER') { --EXPECTF-- Connected successfully with Authentication=SqlPassword. string(1) "%d" -Could not connect with Authentication=ActiveDirectoryIntegrated. -SQLSTATE[IMSSP]: Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal 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 index 00a0c950..23271a36 100644 --- a/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt +++ b/test/functional/pdo_sqlsrv/pdo_azure_ad_managed_identity.phpt @@ -18,58 +18,6 @@ function verifyErrorMessage($exception, $expectedError, $msg) } } -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; @@ -102,9 +50,6 @@ function connectInvalidServer() require_once('MsSetup.inc'); -// Test some error conditions -connectWithInvalidOptions(); - // Make a connection to an invalid server connectInvalidServer(); diff --git a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt index 5614ab00..903ce9dc 100644 --- a/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_authentication.phpt @@ -34,22 +34,6 @@ if (sqlsrv_fetch($stmt)) { sqlsrv_free_stmt($stmt); sqlsrv_close($conn); -/////////////////////////////////////////////////////////////////////////////////////////// -// Test Azure AD with integrated authentication. This should fail because -// we don't support it. -// -$connectionInfo = array( "Authentication"=>"ActiveDirectoryIntegrated", "TrustServerCertificate"=>true ); - -$conn = sqlsrv_connect($server, $connectionInfo); -if ($conn === false) { - echo "Could not connect with Authentication=ActiveDirectoryIntegrated.\n"; - $errors = sqlsrv_errors(); - print_r($errors[0]); -} else { - echo "Connected successfully with Authentication=ActiveDirectoryIntegrated.\n"; - sqlsrv_close($conn); -} - /////////////////////////////////////////////////////////////////////////////////////////// // Test Azure AD on an Azure database instance. Replace $azureServer, etc with // your credentials to test, or this part is skipped. @@ -99,14 +83,4 @@ if ($azureServer != 'TARGET_AD_SERVER') { --EXPECTF-- Connected successfully with Authentication=SqlPassword. string(1) "%d" -Could not connect with Authentication=ActiveDirectoryIntegrated. -Array -( - [0] => IMSSP - [SQLSTATE] => IMSSP - [1] => -62 - [code] => -62 - [2] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal is supported. - [message] => Invalid option for the Authentication keyword. Only SqlPassword, ActiveDirectoryPassword, ActiveDirectoryMsi or ActiveDirectoryServicePrincipal 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 index 644731eb..e7abba6f 100644 --- a/test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt +++ b/test/functional/sqlsrv/sqlsrv_azure_ad_managed_identity.phpt @@ -19,34 +19,6 @@ function verifyErrorMessage($conn, $expectedError, $msg) } } -function connectWithInvalidOptions() -{ - global $server; - - $expectedError = 'When using ActiveDirectoryMsi Authentication, PWD must be NULL. UID can be NULL, but if not, an empty string is not accepted'; - - $connectionInfo = array("UID"=>"", "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; @@ -76,9 +48,6 @@ function connectInvalidServer() } } -// Test some error conditions -connectWithInvalidOptions($server); - // Make a connection to an invalid server connectInvalidServer(); From 64d96e6d18823a28ea1155f6511edcdfcce48f49 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 23 Nov 2021 17:50:26 -0800 Subject: [PATCH 13/15] Test emulate prepare binary encoding with extended chars (#1334) --- ..._140_emulate_prepare_pos_placehodlers.phpt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt b/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt index a99fbddf..c04eb5b7 100644 --- a/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt +++ b/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt @@ -44,6 +44,28 @@ EOF; $data = selectAll($cnn, $tbname); var_dump($data); + $system_param = 'another string'; + $utf8_param = 'Привет'; + $binary_param = fopen('php://memory', 'a'); + fwrite($binary_param, hex2bin('80838790a9')); // testing some extended characters + rewind($binary_param); + + $st->bindParam(1, $system_param, PDO::PARAM_STR); + $st->bindParam(2, $utf8_param, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8); + $st->bindParam(3, $binary_param, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); + + $st->execute(); + + $sql = "SELECT * FROM $tbname WHERE system_encoding = 'another string'"; + $st = $cnn->query($sql); + $st->bindColumn('utf8_encoding', $param2); + $st->bindColumn('binary_encoding', $param3); + $row = $st->fetch(PDO::FETCH_BOUND); + if ($param2 != $utf8_param) + echo "$param2\n"; + if (bin2hex($param3) != '80838790a9') + echo "$param3\n"; + dropTable($cnn, $tbname); unset($st); unset($cnn); From 7f9099a35dba6b34dde8873439c4a010a7772a22 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 29 Nov 2021 15:17:08 -0800 Subject: [PATCH 14/15] Fixed emulate prepare test to run with always encrypted enabled (#1336) --- ..._140_emulate_prepare_pos_placehodlers.phpt | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt b/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt index c04eb5b7..6f964654 100644 --- a/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt +++ b/test/functional/pdo_sqlsrv/pdo_140_emulate_prepare_pos_placehodlers.phpt @@ -41,23 +41,29 @@ EOF; $st->execute(); - $data = selectAll($cnn, $tbname); - var_dump($data); - - $system_param = 'another string'; + $system_param2 = 'another string'; $utf8_param = 'Привет'; $binary_param = fopen('php://memory', 'a'); fwrite($binary_param, hex2bin('80838790a9')); // testing some extended characters rewind($binary_param); - $st->bindParam(1, $system_param, PDO::PARAM_STR); + $st->bindParam(1, $system_param2, PDO::PARAM_STR); $st->bindParam(2, $utf8_param, PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8); $st->bindParam(3, $binary_param, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY); $st->execute(); - $sql = "SELECT * FROM $tbname WHERE system_encoding = 'another string'"; - $st = $cnn->query($sql); + $select = "SELECT * FROM $tbname WHERE system_encoding = ?"; + $st = $cnn->prepare($select); + $st->bindParam(1, $system_param); + $st->execute(); + + $data = $st->fetchAll(PDO::FETCH_BOTH); + var_dump($data); + + $st->bindParam(1, $system_param2); + $st->execute(); + $st->bindColumn('utf8_encoding', $param2); $st->bindColumn('binary_encoding', $param3); $row = $st->fetch(PDO::FETCH_BOUND); From 1a45ee10e25f6a4066469bb197beb540f92a37b3 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Mon, 29 Nov 2021 17:25:16 -0800 Subject: [PATCH 15/15] 5.10 beta2 version bump and change log (#1337) --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ source/shared/version.h | 6 +++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e04aab20..e91dafaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) +## 5.10.0-beta2 - 2021-12-02 +Updated PECL release packages. Here is the list of updates: + +### Added +- Support for PHP 8.1 +- Support for Ubuntu 21.10 +- Feature Request [#1320](https://github.com/microsoft/msphpsql/issues/1320) - allow PDO::ATTR_EMULATE_PREPARES to be set at the connection level + +### Fixed +- Issue [#1307](https://github.com/microsoft/msphpsql/issues/1307) - added TVP support to non-procedure statements +- Issue [#1310](https://github.com/microsoft/msphpsql/issues/1310) - adjusted sql_data_type and column size for NULL parameters - pull request [#1311](https://github.com/microsoft/msphpsql/pull/1311) by gjcarrette +- Pull request [#1326](https://github.com/microsoft/msphpsql/pull/1326) - php drivers simply pass Azure AD Authentication to ODBC driver, which will verify the settings +- Issue [#1329](https://github.com/microsoft/msphpsql/issues/1329) - reset sql type and column size for input params +- Issue [#1331](https://github.com/microsoft/msphpsql/issues/1331) - restore PDO::ATTR_ERRMODE if calling PDO::lastInsertId() call fails - pull request [#1330](https://github.com/microsoft/msphpsql/pull/1330) by mpyw and pull request [#1332](https://github.com/microsoft/msphpsql/pull/1332) + +### Limitations +- No support for inout / output params when using sql_variant type +- No support for inout / output params when formatting decimal values +- In Linux and macOS, setlocale() only takes effect if it is invoked before the first connection. Attempting to set the locale after connecting will not work +- Always Encrypted requires [MS ODBC Driver 17+](https://docs.microsoft.com/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server) + - Only Windows Certificate Store and Azure Key Vault are supported. Custom Keystores are not yet supported + - Issue [#716](https://github.com/Microsoft/msphpsql/issues/716) - With Always Encrypted enabled, named parameters in subqueries are not supported + - Issue [#1050](https://github.com/microsoft/msphpsql/issues/1050) - With Always Encrypted enabled, insertion requires the column list for any tables with identity columns + - [Always Encrypted limitations](https://docs.microsoft.com/sql/connect/php/using-always-encrypted-php-drivers#limitations-of-the-php-drivers-when-using-always-encrypted) + +### Known Issues +- This release requires ODBC Driver 17.4.2 or above. Otherwise, a warning about failing to set an attribute may be suppressed when using an older ODBC driver. +- Connection pooling on Linux or macOS is not recommended with [unixODBC](http://www.unixodbc.org/) < 2.3.7 +- When pooling is enabled in Linux or macOS + - unixODBC <= 2.3.4 (Linux and macOS) might not return proper diagnostic information, such as error messages, warnings and informative messages + - due to this unixODBC bug, fetch large data (such as xml, binary) as streams as a workaround. See the examples [here](https://github.com/Microsoft/msphpsql/wiki/Features#pooling) + + ## 5.10.0-beta1 - 2021-09-08 Updated PECL release packages. Here is the list of updates: diff --git a/source/shared/version.h b/source/shared/version.h index ebabdfe4..cf04824e 100644 --- a/source/shared/version.h +++ b/source/shared/version.h @@ -31,7 +31,7 @@ #define SQLVERSION_BUILD 0 // For previews, set this constant to 1, 2 and so on. Otherwise, set it to 0 -#define PREVIEW 1 +#define PREVIEW 2 #define SEMVER_PRERELEASE // Semantic versioning build metadata, build meta data is not counted in precedence order. @@ -59,7 +59,7 @@ #define _FILEVERSION SQLVERSION_MAJOR,SQLVERSION_MINOR,SQLVERSION_PATCH,SQLVERSION_BUILD // PECL package version ('-' or '+' is not allowed) - to support Pickle do not use macros below -#define PHP_SQLSRV_VERSION "5.10.0beta1" -#define PHP_PDO_SQLSRV_VERSION "5.10.0beta1" +#define PHP_SQLSRV_VERSION "5.10.0beta2" +#define PHP_PDO_SQLSRV_VERSION "5.10.0beta2" #endif // VERSION_H