void core_sqlsrv_bind_param()

in source/shared/core_stmt.cpp [364:447]


void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z,
                             _In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size,
                             _Inout_ SQLSMALLINT decimal_digits)
{
    // check is only < because params are 0 based
    CHECK_CUSTOM_ERROR(param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1) {
        throw core::CoreException();
    }

    // Dereference the parameter if necessary
    zval* param_ref = param_z;
    if (Z_ISREF_P(param_z)) {
        ZVAL_DEREF(param_z);
    }

    sqlsrv_param* param_ptr = stmt->params_container.find_param(param_num, (direction == SQL_PARAM_INPUT));
    try {
        if (param_ptr == NULL) {
            sqlsrv_malloc_auto_ptr<sqlsrv_param> new_param;
            if (direction == SQL_PARAM_INPUT) {
                // Check if it's a Table-Valued Parameter first
                if (Z_TYPE_P(param_z) == IS_ARRAY) {
                    new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param_tvp))) sqlsrv_param_tvp(param_num, encoding, SQL_SS_TABLE, 0, 0, NULL);
                } else {
                    new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param))) sqlsrv_param(param_num, direction, encoding, sql_type, column_size, decimal_digits);
                }
            } else if (direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT) {
                new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param_inout))) sqlsrv_param_inout(param_num, direction, encoding, sql_type, column_size, decimal_digits, php_out_type);
            } else {
                SQLSRV_ASSERT(false, "sqlsrv_params_container::insert_param - Invalid parameter direction.");
            }
            stmt->params_container.insert_param(param_num, new_param);
            param_ptr = new_param;
            new_param.transferred();
        } else if (direction == SQL_PARAM_INPUT 
                && param_ptr->sql_data_type != SQL_SS_TABLE
                && param_ptr->strlen_or_indptr == SQL_NULL_DATA) {
            // reset the followings for regular input parameters if it was bound as a null param before
            param_ptr->sql_data_type = sql_type;
            param_ptr->column_size = column_size;
            param_ptr->strlen_or_indptr = 0;
        }

        SQLSRV_ASSERT(param_ptr != NULL, "core_sqlsrv_bind_param: param_ptr is null. Something went wrong.");

        bool result = param_ptr->prepare_param(param_ref, param_z);
        if (!result && direction == SQL_PARAM_INPUT_OUTPUT) {
            CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_INPUT_OUTPUT_PARAM_TYPE_MATCH, param_num + 1) {
                throw core::CoreException();
            }
        }

        // If Always Encrypted is enabled, transfer the known param meta data if applicable, which might alter param_z for decimal types
        if (stmt->conn->ce_option.enabled) {
            if (param_ptr->sql_data_type == SQL_UNKNOWN_TYPE || param_ptr->column_size == SQLSRV_UNKNOWN_SIZE) {
                // meta data parameters are always sorted based on parameter number
                param_ptr->copy_param_meta_ae(param_z, stmt->params_container.params_meta_ae[param_num]);
            }
        }

        // Get all necessary values to prepare for SQLBindParameter
        param_ptr->process_param(stmt, param_z);
        param_ptr->bind_param(stmt);

        // When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively.
        // For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error.
        // This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns.
        // To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column.
        // Note this must be called after SQLBindParameter() or SQLSetDescField() may fail. 
        // VSO BUG 2693: how to correctly distinguish datetime from datetime2(3)? Both have the same decimal_digits and column_size
        if (stmt->conn->ce_option.enabled && param_ptr->sql_data_type == SQL_TYPE_TIMESTAMP) {
            if (param_ptr->decimal_digits == 3) {
                core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER);
            } else if (param_ptr->decimal_digits == 0 && param_ptr->column_size == 16) {
                core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER);
            }
        }
    }
    catch( core::CoreException& e ){
        stmt->free_param_data();
        SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
        throw e;
    }
}