void sqlsrv_param_tvp::bind_param()

in source/shared/core_stmt.cpp [3308:3409]


void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt)
{
    core::SQLBindParameter(stmt, param_pos + 1, direction, c_data_type, sql_data_type, column_size, decimal_digits, buffer, buffer_length, &strlen_or_indptr);

    // No need to continue if this is one of the constituent columns of the table-valued parameter
    if (sql_data_type != SQL_SS_TABLE) {
        return;
    }

    if (num_rows == 0) {
        // TVP has no data
        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;
    core::SQLGetStmtAttr(stmt, SQL_ATTR_APP_PARAM_DESC, &desc, 0, 0);
    SQLRETURN r = ::SQLSetDescField(desc, param_pos + 1, SQL_DESC_DATA_PTR, reinterpret_cast<SQLPOINTER>(this), 0);
    CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
        throw core::CoreException();
    }

    // First set focus on this parameter
    size_t ordinal = param_pos + 1;
    core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(ordinal), SQL_IS_INTEGER);

    // Bind the TVP columns
    HashTable* rows_ht = Z_ARRVAL_P(param_ptr_z);
    zval* row_z = zend_hash_index_find(rows_ht, 0);

    if (Z_ISREF_P(row_z)) {
        ZVAL_DEREF(row_z);
    }

    HashTable* cols_ht = Z_ARRVAL_P(row_z);
    zend_ulong id = -1;
    zend_string *key = NULL;
    zval* data_z = NULL;
    int num_columns = 0;

    // In case there are null values in the first row, have to loop 
    // through the entire first row of column values using the Zend macros. 
    ZEND_HASH_FOREACH_KEY_VAL(cols_ht, id, key, data_z) {
        int 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();
        }

        // Assume the user has supplied data for all columns in the right order
        SQLUSMALLINT pos = static_cast<SQLUSMALLINT>(id);
        sqlsrv_param* column_param = tvp_columns[pos];
        SQLSRV_ASSERT(column_param != NULL, "sqlsrv_param_tvp::bind_param -- column param should not be null");

        // If data_z is NULL, will need to keep looking in the subsequent rows of 
        // the same column until a non-null value is found. Since Zend macros must be 
        // used to traverse the array items, nesting Zend macros in different directions
        // does not work.
        // Therefore, save data_z for later processing and binding.
        column_param->param_ptr_z = data_z;
        num_columns++;
    } ZEND_HASH_FOREACH_END();

    // Process the columns and bind each of them using the saved data
    for (int i = 0; i < num_columns; i++) {
        sqlsrv_param* column_param = tvp_columns[i];

        column_param->process_param(stmt, NULL);
        column_param->bind_param(stmt);
    }

    // Reset focus
    core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(0), SQL_IS_INTEGER);
}