int sqlsrv_param_tvp::parse_tv_param_arrays()

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