Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added TVP support to non-procedure statements #1309

Merged
merged 4 commits into from
Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions source/shared/core_stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down
97 changes: 97 additions & 0 deletions test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure.phpt
Original file line number Diff line number Diff line change
@@ -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
105 changes: 105 additions & 0 deletions test/functional/pdo_sqlsrv/pdo_1307_tvp_non_procedure_schema.phpt
Original file line number Diff line number Diff line change
@@ -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
Loading