Skip to content

Commit

Permalink
Changed how schema is provided for TVP input (#1264)
Browse files Browse the repository at this point in the history
  • Loading branch information
yitam authored Jun 2, 2021
1 parent b49cb51 commit a14cb70
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 57 deletions.
2 changes: 1 addition & 1 deletion source/shared/core_sqlsrv.h
Original file line number Diff line number Diff line change
Expand Up @@ -1514,7 +1514,7 @@ struct sqlsrv_param_tvp : public sqlsrv_param

// The following methods are only applicable to a table-valued parameter or its individual columns
int parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
void get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ SQLCHAR* table_type_name);
void get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ zend_string* table_type_name, _In_ zend_string* schema_name);
void process_param_column_value(_Inout_ sqlsrv_stmt* stmt);
void process_null_param_value(_Inout_ sqlsrv_stmt* stmt);
void populate_cell_placeholder(_Inout_ sqlsrv_stmt* stmt, _In_ int ordinal);
Expand Down
34 changes: 17 additions & 17 deletions source/shared/core_stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3084,13 +3084,14 @@ void sqlsrv_param_inout::resize_output_string_buffer(_Inout_ zval* param_z, _In_
}
}

void sqlsrv_param_tvp::get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ SQLCHAR* table_type_name)
void sqlsrv_param_tvp::get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ zend_string* table_type_name, _In_ zend_string* schema_name)
{
SQLHANDLE chstmt = SQL_NULL_HANDLE;
SQLRETURN rc;
SQLSMALLINT data_type, dec_digits;
SQLINTEGER col_size;
SQLLEN cb_data_type, cb_col_size, cb_dec_digits;
char* table_type = ZSTR_VAL(table_type_name);

core::SQLAllocHandle(SQL_HANDLE_STMT, *(stmt->conn), &chstmt);

Expand All @@ -3100,21 +3101,11 @@ void sqlsrv_param_tvp::get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ SQLCHAR* ta
}

// Check table type name and see if the schema is specified. Otherwise, assume DBO
std::string type_name(reinterpret_cast<char *>(table_type_name));
std::size_t pos = type_name.find_first_of(".");
if (pos != std::string::npos) {
std::string str1 = type_name.substr(0, pos);
std::string str2 = type_name.substr(pos + 1);

char schema[SS_MAXCOLNAMELEN] = { '\0' };
char type[SS_MAXCOLNAMELEN] = { '\0' };

strcpy_s(schema, SS_MAXCOLNAMELEN, str1.c_str());
strcpy_s(type, SS_MAXCOLNAMELEN, str2.c_str());

rc = SQLColumns(chstmt, NULL, 0, reinterpret_cast<SQLCHAR *>(schema), SQL_NTS, reinterpret_cast<SQLCHAR *>(type), SQL_NTS, NULL, 0);
if (schema_name != NULL) {
char* schema = ZSTR_VAL(schema_name);
rc = SQLColumns(chstmt, NULL, 0, reinterpret_cast<SQLCHAR*>(schema), SQL_NTS, reinterpret_cast<SQLCHAR*>(table_type), SQL_NTS, NULL, 0);
} else {
rc = SQLColumns(chstmt, NULL, 0, NULL, 0, table_type_name, SQL_NTS, NULL, 0);
rc = SQLColumns(chstmt, NULL, 0, NULL, SQL_NTS, reinterpret_cast<SQLCHAR*>(table_type), SQL_NTS, NULL, 0);
}

CHECK_CUSTOM_ERROR(!SQL_SUCCEEDED(rc), stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
Expand Down Expand Up @@ -3208,6 +3199,7 @@ int sqlsrv_param_tvp::parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ z
// The number of columns in the given table-valued parameter is returned, which may be zero.
HashTable* inputs_ht = Z_ARRVAL_P(param_z);
zend_string *tvp_name = NULL;
zend_string *schema_name = NULL;
zval *tvp_data_z = NULL;
HashPosition pos;

Expand All @@ -3227,12 +3219,20 @@ int sqlsrv_param_tvp::parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ z
throw core::CoreException();
}
}

// TODO: Find the docs page somewhere that says a TVP can not be null but it may have null columns??
CHECK_CUSTOM_ERROR(tvp_data_z == NULL || Z_TYPE_P(tvp_data_z) == IS_NULL || Z_TYPE_P(tvp_data_z) != IS_ARRAY, stmt, SQLSRV_ERROR_TVP_INVALID_INPUTS, param_pos + 1) {
throw core::CoreException();
}

// 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);
}
}

// Save the TVP multi-dim array data, which should be something like this
// [
// [r1c1, r1c2, r1c3],
Expand All @@ -3249,7 +3249,7 @@ int sqlsrv_param_tvp::parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ z

// Given the table type name, get its column meta data next
size_t total_num_columns = 0;
get_tvp_metadata(stmt, reinterpret_cast<SQLCHAR*>(ZSTR_VAL(tvp_name)));
get_tvp_metadata(stmt, tvp_name, schema_name);
total_num_columns = tvp_columns.size();

// (1) Is the array empty?
Expand Down
8 changes: 4 additions & 4 deletions test/functional/pdo_sqlsrv/pdo_test_TVP_double_tvps.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ try {
['klmop', 45678, '2007-04-08 06:15:15.333'],
];

$tvpType1 = "$schema.SupplierType";
$tvpType2 = "$schema.TestTVP3";
$tvpType1 = "SupplierType";
$tvpType2 = "TestTVP3";

$tvpInput1 = array($tvpType1 => $inputs1);
$tvpInput2 = array($tvpType2 => $inputs2);
$tvpInput1 = array($tvpType1 => $inputs1, $schema);
$tvpInput2 = array($tvpType2 => $inputs2, $schema);

$image = fopen($tvpIncPath. 'superlight_black_f_large.gif', 'rb');

Expand Down
25 changes: 11 additions & 14 deletions test/functional/pdo_sqlsrv/pdo_test_TVP_error_cases.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ try {

// Use a different schema instead of dbo
$schema = 'Sales DB';
$tvpType = 'TestTVP3';
$tvpTypeName = 'TestTVP3';
$procName = 'SelectTVP3';

cleanup($conn, $schema, $tvpType, $procName, $pre2016);
cleanup($conn, $schema, $tvpTypeName, $procName, $pre2016);

// Create the table type and stored procedure
$conn->exec($createSchema);
Expand All @@ -89,9 +89,6 @@ try {
$tvpInput = array("" => array());
invokeProc($conn, $callSelectTVP3, $tvpInput, 2);

// The TVP name should include the schema
$tvpTypeName = "$schema.$tvpType";

// Case (3) - null inputs
$tvpInput = array($tvpTypeName => null);
invokeProc($conn, $callSelectTVP3, $tvpInput, 3);
Expand All @@ -105,37 +102,37 @@ try {
invokeProc($conn, $callSelectTVP3, $tvpInput, 5);

// Case (6) - input rows are not the same size
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 6);

// Case (7) - input row wrong size
unset($inputs);
$inputs = [
['ABC', 12345, null, null]
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 7);

// Case (8) - use string keys
unset($inputs);
$inputs = [
['A' => null, null, null]
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 8);

// Case (9) - a row is not an array
unset($inputs);
$inputs = [null];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 9);

// Case (10) - a column value used a string key
unset($inputs);
$inputs = [
['ABC', 12345, "key"=>null]
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 10);

// Case (11) - invalid input object for a TVP column
Expand All @@ -149,7 +146,7 @@ try {
['ABC', 1234, $bar],
['DEF', 6789, null],
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 11);

// Case (12) - invalid input type for a TVP column
Expand All @@ -158,7 +155,7 @@ try {
['ABC', &$str, null],
['DEF', 6789, null],
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 12);

// Case (13) - bind a TVP as an OUTPUT param
Expand All @@ -175,10 +172,10 @@ try {
[$utf8, 1234, null],
['DEF', 6789, null],
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 14);

cleanup($conn, $schema, $tvpType, $procName, $pre2016);
cleanup($conn, $schema, $tvpTypeName, $procName, $pre2016);

unset($conn);
echo "Done" . PHP_EOL;
Expand Down
8 changes: 4 additions & 4 deletions test/functional/sqlsrv/sqlsrv_test_TVP_double_tvps.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ $inputs2 = [
['KLMOP', 45678, '2007-04-08 06:15:15.333'],
];

$tvpType1 = "$schema.SupplierType";
$tvpType2 = "$schema.TestTVP3";
$tvpType1 = "SupplierType";
$tvpType2 = "TestTVP3";

$tvpInput1 = array($tvpType1 => $inputs1);
$tvpInput2 = array($tvpType2 => $inputs2);
$tvpInput1 = array($tvpType1 => $inputs1, $schema);
$tvpInput2 = array($tvpType2 => $inputs2, $schema);

$image = fopen($tvpIncPath. 'awc_tee_male_large.gif', 'rb');

Expand Down
29 changes: 12 additions & 17 deletions test/functional/sqlsrv/sqlsrv_test_TVP_error_cases.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ function invokeProc($conn, $proc, $tvpInput, $caseNo, $dir = SQLSRV_PARAM_IN)

$stmt = sqlsrv_query($conn, $proc, $params);
if (!$stmt) {
// $errors = sqlsrv_errors(SQLSRV_ERR_ALL);
$errors = sqlsrv_errors();
$errors = sqlsrv_errors(SQLSRV_ERR_ALL);
if (!empty($errors)) {
$count = count($errors);
}
$count = 1;
for ($i = 0; $i < $count; $i++) {
echo "Error $caseNo: ";
echo $errors[$i]['message'] . PHP_EOL;
Expand Down Expand Up @@ -56,7 +54,7 @@ $conn = connect(array('CharacterSet'=>'UTF-8'));

// Use a different schema instead of dbo
$schema = 'Sales DB';
$tvpType = 'TestTVP3';
$tvpTypeName = 'TestTVP3';
$procName = 'SelectTVP3';

$stmt = sqlsrv_query($conn, "SELECT @@VERSION");
Expand All @@ -66,7 +64,7 @@ if (sqlsrv_fetch($stmt)) {
$version = explode(' ', $result);
$pre2016 = ($version[3] < '2016');

cleanup($conn, $schema, $tvpType, $procName, $pre2016);
cleanup($conn, $schema, $tvpTypeName, $procName, $pre2016);

// Create table type and a stored procedure
sqlsrv_query($conn, $createSchema);
Expand All @@ -89,9 +87,6 @@ invokeProc($conn, $callSelectTVP3, $tvpInput, 1);
$tvpInput = array("" => array());
invokeProc($conn, $callSelectTVP3, $tvpInput, 2);

// The TVP name should include the schema
$tvpTypeName = "$schema.$tvpType";

// Case (3) - null inputs
$tvpInput = array($tvpTypeName => null);
invokeProc($conn, $callSelectTVP3, $tvpInput, 3);
Expand All @@ -105,37 +100,37 @@ $tvpInput = array($str => $inputs);
invokeProc($conn, $callSelectTVP3, $tvpInput, 5);

// Case (6) - input rows are not the same size
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 6);

// Case (7) - input row wrong size
unset($inputs);
$inputs = [
['ABC', 12345, null, null]
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 7);

// Case (8) - use string keys
unset($inputs);
$inputs = [
['A' => null, null, null]
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 8);

// Case (9) - a row is not an array
unset($inputs);
$inputs = [null];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 9);

// Case (10) - a column value used a string key
unset($inputs);
$inputs = [
['ABC', 12345, "key"=>null]
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 10);

// Case (11) - invalid input object for a TVP column
Expand All @@ -149,7 +144,7 @@ $inputs = [
['ABC', 1234, $bar],
['DEF', 6789, null],
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 11);

// Case (12) - invalid input type for a TVP column
Expand All @@ -158,7 +153,7 @@ $inputs = [
['ABC', &$str, null],
['DEF', 6789, null],
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 12);

// Case (13) - bind a TVP as an OUTPUT param
Expand All @@ -175,10 +170,10 @@ $inputs = [
[$utf8, 1234, null],
['DEF', 6789, null],
];
$tvpInput = array($tvpTypeName => $inputs);
$tvpInput = array($tvpTypeName => $inputs, $schema);
invokeProc($conn, $callSelectTVP3, $tvpInput, 14);

cleanup($conn, $schema, $tvpType, $procName, $pre2016);
cleanup($conn, $schema, $tvpTypeName, $procName, $pre2016);

sqlsrv_close($conn);

Expand Down

0 comments on commit a14cb70

Please sign in to comment.