From 36d2704c0aca75ac3724bb2ff8a9bced4d12b5fa Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 29 Sep 2021 12:27:52 -0700 Subject: [PATCH] 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