From a14cb70ad3613f3877e4532b61bba0bc15d25f32 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Wed, 2 Jun 2021 12:16:51 -0700 Subject: [PATCH] Changed how schema is provided for TVP input (#1264) --- source/shared/core_sqlsrv.h | 2 +- source/shared/core_stmt.cpp | 34 +++++++++---------- .../pdo_sqlsrv/pdo_test_TVP_double_tvps.phpt | 8 ++--- .../pdo_sqlsrv/pdo_test_TVP_error_cases.phpt | 25 ++++++-------- .../sqlsrv/sqlsrv_test_TVP_double_tvps.phpt | 8 ++--- .../sqlsrv/sqlsrv_test_TVP_error_cases.phpt | 29 +++++++--------- 6 files changed, 49 insertions(+), 57 deletions(-) diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index eb6dff30c..827a6b44d 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -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); diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index fdaaec084..639209801 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -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); @@ -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(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(schema), SQL_NTS, reinterpret_cast(type), SQL_NTS, NULL, 0); + if (schema_name != NULL) { + char* schema = ZSTR_VAL(schema_name); + rc = SQLColumns(chstmt, NULL, 0, reinterpret_cast(schema), SQL_NTS, reinterpret_cast(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(table_type), SQL_NTS, NULL, 0); } CHECK_CUSTOM_ERROR(!SQL_SUCCEEDED(rc), stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) { @@ -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; @@ -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], @@ -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(ZSTR_VAL(tvp_name))); + get_tvp_metadata(stmt, tvp_name, schema_name); total_num_columns = tvp_columns.size(); // (1) Is the array empty? diff --git a/test/functional/pdo_sqlsrv/pdo_test_TVP_double_tvps.phpt b/test/functional/pdo_sqlsrv/pdo_test_TVP_double_tvps.phpt index b08fa1b80..d7320500c 100644 --- a/test/functional/pdo_sqlsrv/pdo_test_TVP_double_tvps.phpt +++ b/test/functional/pdo_sqlsrv/pdo_test_TVP_double_tvps.phpt @@ -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'); diff --git a/test/functional/pdo_sqlsrv/pdo_test_TVP_error_cases.phpt b/test/functional/pdo_sqlsrv/pdo_test_TVP_error_cases.phpt index bb27cb2ee..423e9033b 100644 --- a/test/functional/pdo_sqlsrv/pdo_test_TVP_error_cases.phpt +++ b/test/functional/pdo_sqlsrv/pdo_test_TVP_error_cases.phpt @@ -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); @@ -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); @@ -105,7 +102,7 @@ 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 @@ -113,7 +110,7 @@ try { $inputs = [ ['ABC', 12345, null, null] ]; - $tvpInput = array($tvpTypeName => $inputs); + $tvpInput = array($tvpTypeName => $inputs, $schema); invokeProc($conn, $callSelectTVP3, $tvpInput, 7); // Case (8) - use string keys @@ -121,13 +118,13 @@ try { $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 @@ -135,7 +132,7 @@ try { $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 @@ -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 @@ -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 @@ -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; diff --git a/test/functional/sqlsrv/sqlsrv_test_TVP_double_tvps.phpt b/test/functional/sqlsrv/sqlsrv_test_TVP_double_tvps.phpt index 67602c932..39968110b 100644 --- a/test/functional/sqlsrv/sqlsrv_test_TVP_double_tvps.phpt +++ b/test/functional/sqlsrv/sqlsrv_test_TVP_double_tvps.phpt @@ -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'); diff --git a/test/functional/sqlsrv/sqlsrv_test_TVP_error_cases.phpt b/test/functional/sqlsrv/sqlsrv_test_TVP_error_cases.phpt index 1fb1df68a..1f411adf9 100644 --- a/test/functional/sqlsrv/sqlsrv_test_TVP_error_cases.phpt +++ b/test/functional/sqlsrv/sqlsrv_test_TVP_error_cases.phpt @@ -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; @@ -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"); @@ -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); @@ -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); @@ -105,7 +100,7 @@ $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 @@ -113,7 +108,7 @@ 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 @@ -121,13 +116,13 @@ 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 @@ -135,7 +130,7 @@ 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 @@ -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 @@ -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 @@ -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);