in source/shared/core_stmt.cpp [3126:3228]
int sqlsrv_param_tvp::parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
{
// If this is not a table-valued parameter, simply return
if (sql_data_type != SQL_SS_TABLE) {
return 0;
}
// This method verifies if the table-valued parameter (i.e. param_z) provided by the user is valid.
// 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;
zend_hash_internal_pointer_reset_ex(inputs_ht, &pos);
if (zend_hash_has_more_elements_ex(inputs_ht, &pos) == SUCCESS) {
zend_ulong num_index = -1;
size_t key_len = 0;
int key_type = zend_hash_get_current_key(inputs_ht, &tvp_name, &num_index);
if (key_type == HASH_KEY_IS_STRING) {
key_len = ZSTR_LEN(tvp_name);
tvp_data_z = zend_hash_get_current_data_ex(inputs_ht, &pos);
}
CHECK_CUSTOM_ERROR((key_type == HASH_KEY_IS_LONG || key_len == 0), stmt, SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME, param_pos + 1) {
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();
}
// 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);
}
}
// Save the TVP multi-dim array data, which should be something like this
// [
// [r1c1, r1c2, r1c3],
// [r2c1, r2c2, r2c3],
// [r3c1, r3c2, r3c3]
// ]
param_ptr_z = tvp_data_z;
HashTable* rows_ht = Z_ARRVAL_P(tvp_data_z);
this->num_rows = zend_hash_num_elements(rows_ht);
if (this->num_rows == 0) {
// TVP has no data
return 0;
}
// Given the table type name, get its column meta data next
size_t total_num_columns = 0;
get_tvp_metadata(stmt, tvp_name, schema_name);
total_num_columns = tvp_columns.size();
// (1) Is the array empty?
// (2) Check individual rows and see if their sizes are consistent?
zend_ulong id = -1;
zend_string *key = NULL;
zval* row_z = NULL;
int num_columns = 0;
int type = HASH_KEY_NON_EXISTENT;
// Loop through the rows to check the number of columns
ZEND_HASH_FOREACH_KEY_VAL(rows_ht, id, key, row_z) {
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
CHECK_CUSTOM_ERROR(type == HASH_KEY_IS_STRING, stmt, SQLSRV_ERROR_TVP_STRING_KEYS, param_pos + 1) {
throw core::CoreException();
}
if (Z_ISREF_P(row_z)) {
ZVAL_DEREF(row_z);
}
// Individual row must be an array
CHECK_CUSTOM_ERROR(Z_TYPE_P(row_z) != IS_ARRAY, stmt, SQLSRV_ERROR_TVP_ROW_NOT_ARRAY, param_pos + 1) {
throw core::CoreException();
}
// Are all the TVP's rows the same size
num_columns = zend_hash_num_elements(Z_ARRVAL_P(row_z));
CHECK_CUSTOM_ERROR(num_columns != total_num_columns, stmt, SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE, param_pos + 1, total_num_columns) {
throw core::CoreException();
}
} ZEND_HASH_FOREACH_END();
// Return the number of columns
return num_columns;
}