Added TVP support to non-procedure statements (#1309)

This commit is contained in:
Jenny Tam 2021-09-29 12:27:52 -07:00 committed by GitHub
parent c87af63d57
commit 36d2704c0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 474 additions and 6 deletions

View file

@ -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<char *>(buffer);
SQLRETURN r = ::SQLSetDescField(hIpd, param_pos + 1, SQL_CA_SS_TYPE_NAME, reinterpret_cast<SQLCHAR*>(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<SQLCHAR*>(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<sqlsrv_param*>(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<sqlsrv_param*>(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;
}

View file

@ -0,0 +1,97 @@
--TEST--
Verify Github Issue 1307 is fixed.
--DESCRIPTION--
To show that table-valued parameters work with non-procedure statements
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
function cleanup($conn, $tvpname, $testTable)
{
$conn->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 = <<<QRY
MERGE INTO $testTable t
USING ? s ON s.id = t.id
WHEN NOT MATCHED THEN
INSERT (id) VALUES(s.id);
QRY;
unset($tvpinput);
$tvpinput = array($tvpname => [[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

View file

@ -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--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
function cleanup($conn, $tvpname, $testTable, $schema)
{
$conn->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 = <<<QRY
MERGE INTO [$schema].[$testTable] t
USING ? s ON s.id = t.id
WHEN NOT MATCHED THEN
INSERT (id) VALUES(s.id);
QRY;
unset($tvpinput);
$tvpinput = array($tvpname => [[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

View file

@ -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--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsCommon.inc");
function cleanup($conn, $tvpname, $testTable)
{
$dropTableType = dropTableTypeSQL($conn, $tvpname);
sqlsrv_query($conn, $dropTableType);
sqlsrv_query($conn, "DROP TABLE IF EXISTS [$testTable]");
}
function readData($conn, $testTable)
{
$tsql = "SELECT id FROM $testTable ORDER BY id";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
while ($result = sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC)) {
$ID = sqlsrv_get_field($stmt, 0);
echo $ID . PHP_EOL;
}
sqlsrv_free_stmt($stmt);
}
$conn = connect();
$tvpname = 'srv_id_table';
$testTable = 'srv_test_table';
cleanup($conn, $tvpname, $testTable);
// Create the table type and test table
$tsql = "CREATE TYPE $tvpname AS TABLE(id INT PRIMARY KEY)";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
$tsql = "CREATE TABLE $testTable (id INT PRIMARY KEY)";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
// Populate the table using the table type
$tsql = "INSERT INTO $testTable SELECT * FROM ?";
$params = [
[[$tvpname => [[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 = <<<QRY
MERGE INTO $testTable t
USING ? s ON s.id = t.id
WHEN NOT MATCHED THEN
INSERT (id) VALUES(s.id);
QRY;
$params = [
[[$tvpname => [[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

View file

@ -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--
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsCommon.inc");
function cleanup($conn, $tvpname, $testTable, $schema)
{
$dropTableType = dropTableTypeSQL($conn, $tvpname, $schema);
sqlsrv_query($conn, $dropTableType);
sqlsrv_query($conn, "DROP TABLE IF EXISTS [$schema].[$testTable]");
sqlsrv_query($conn, "DROP SCHEMA IF EXISTS [$schema]");
}
function readData($conn, $schema, $testTable)
{
$tsql = "SELECT id FROM [$schema].[$testTable] ORDER BY id";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
while ($result = sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC)) {
$ID = sqlsrv_get_field($stmt, 0);
echo $ID . PHP_EOL;
}
sqlsrv_free_stmt($stmt);
}
$conn = connect();
$tvpname = 'srv_id_table2';
$testTable = 'srv_test_table2';
$schema = 'srv schema';
cleanup($conn, $tvpname, $testTable, $schema);
// Create the schema
$tsql = "CREATE SCHEMA [$schema]";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
// Create the table type and test table
$tsql = "CREATE TYPE [$schema].[$tvpname] AS TABLE(id INT PRIMARY KEY)";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
$tsql = "CREATE TABLE [$schema].[$testTable] (id INT PRIMARY KEY)";
$stmt = sqlsrv_query($conn, $tsql);
if (!$stmt) {
print_r(sqlsrv_errors());
}
// Populate the table using the table type
$tsql = "INSERT INTO [$schema].[$testTable] SELECT * FROM ?";
$params = [
[[$tvpname => [[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 = <<<QRY
MERGE INTO [$schema].[$testTable] t
USING ? s ON s.id = t.id
WHEN NOT MATCHED THEN
INSERT (id) VALUES(s.id);
QRY;
$params = [
[[$tvpname => [[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