int pdo_sqlsrv_stmt_param_hook()

in source/pdo_sqlsrv/pdo_stmt.cpp [1254:1490]


int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
                               _Inout_ struct pdo_bound_param_data *param, _In_ enum pdo_param_event event_type)
{
    PDO_RESET_STMT_ERROR;

    try {

        switch( event_type ) {

            // since the param isn't reliable, we don't do anything here
            case PDO_PARAM_EVT_ALLOC:
                {
                    pdo_sqlsrv_stmt* driver_stmt = reinterpret_cast<pdo_sqlsrv_stmt*>(stmt->driver_data);
                    if (driver_stmt->conn->ce_option.enabled) {
                        if (driver_stmt->direct_query) {
                            THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_CE_DIRECT_QUERY_UNSUPPORTED);
                        }
                        if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
                            THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED);
                        }
                    }
                    if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE && (param->param_type & PDO_PARAM_INPUT_OUTPUT)) {
                        THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_EMULATE_INOUT_UNSUPPORTED);
                    }
                }
                break;
            case PDO_PARAM_EVT_FREE:
                break;
            // bind the parameter in the core layer
            case PDO_PARAM_EVT_EXEC_PRE:
                {
                    PDO_VALIDATE_STMT;
                    PDO_LOG_STMT_ENTRY;

                    // skip column bindings
                    if( !param->is_param ) {
                        break;
                    }

                    sqlsrv_stmt* driver_stmt = reinterpret_cast<sqlsrv_stmt*>( stmt->driver_data );
                    SQLSRV_ASSERT( driver_stmt != NULL, "pdo_sqlsrv_stmt_param_hook: driver_data object was null" );

                    // prepare for binding parameters by flushing anything remaining in the result set
                    if( driver_stmt->executed && !driver_stmt->past_next_result_end ) {

                        while( driver_stmt->past_next_result_end == false ) {

                            core_sqlsrv_next_result( driver_stmt, false );
                        }
                    }

                    int direction = SQL_PARAM_INPUT;
                    SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
                    SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
                    SQLSMALLINT decimal_digits = 0;
                    // determine the direction of the parameter.  By default it's input, but if the user specifies a size
                    // that means they want output, and if they include the flag, then it's input/output.
                    // It's invalid to specify the input/output flag but not specify a length
                    CHECK_CUSTOM_ERROR( (param->param_type & PDO_PARAM_INPUT_OUTPUT) && (param->max_value_len == 0),
                                        driver_stmt, PDO_SQLSRV_ERROR_INVALID_PARAM_DIRECTION, param->paramno + 1 ) {
                        throw pdo::PDOException();
                    }
                    // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
                    // and the SQLSRV_PHPTYPE_* constant
                    // vso 2829: derive the pdo_type for input/output parameter as well
                    // also check if the user has specified PARAM_STR_NATL or PARAM_STR_CHAR for string params
                    int pdo_type = param->param_type;
                    if( param->max_value_len > 0 || param->max_value_len == SQLSRV_DEFAULT_SIZE ) {
                        if( param->param_type & PDO_PARAM_INPUT_OUTPUT ) {
                            direction = SQL_PARAM_INPUT_OUTPUT;
                            pdo_type = param->param_type & ~PDO_PARAM_INPUT_OUTPUT;
                        }
                        else {
                            direction = SQL_PARAM_OUTPUT;
                        }
                    }

                    // check if the user has specified the character set to use, take it off but ignore 
#if PHP_VERSION_ID >= 70200
                    if ((pdo_type & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
                        pdo_type = pdo_type & ~PDO_PARAM_STR_NATL;
                        LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_NATL set but is ignored.");
                    }
                    if ((pdo_type & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
                        pdo_type = pdo_type & ~PDO_PARAM_STR_CHAR;
                        LOG(SEV_NOTICE, "PHP Extended String type PDO_PARAM_STR_CHAR set but is ignored.");
                    }
#endif

                    // if the parameter is output or input/output, translate the type between the PDO::PARAM_* constant
                    // and the SQLSRV_PHPTYPE_* constant
                    SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
                    switch (pdo_type) {
                        case PDO_PARAM_BOOL:
                        case PDO_PARAM_INT:
                            php_out_type = SQLSRV_PHPTYPE_INT;
                            break;
                        case PDO_PARAM_STR:
                            php_out_type = SQLSRV_PHPTYPE_STRING;
                            break;
                        // when the user states PDO::PARAM_NULL, they mean send a null no matter what the variable is
                        // since the core layer keys off the zval type, we substitute a null for what they gave us
                        case PDO_PARAM_NULL:
                        {
                            zval null_zval;
                            php_out_type = SQLSRV_PHPTYPE_NULL;
                            
                            ZVAL_NULL( &null_zval );
                            zval_ptr_dtor( &param->parameter );
                            param->parameter = null_zval;
                            break;
                        }
                        case PDO_PARAM_LOB:
                            php_out_type = SQLSRV_PHPTYPE_STREAM;
                            break;
                        case PDO_PARAM_STMT:
                            THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_PDO_STMT_UNSUPPORTED );
                            break;
                        default:
                            SQLSRV_ASSERT( false, "Unknown PDO::PARAM_* constant given." );
                            break;
                    }
                    // set the column size parameter for bind_param if we are expecting something back
                    if( direction != SQL_PARAM_INPUT ) {
                        switch( php_out_type ) {
                            case SQLSRV_PHPTYPE_NULL:
                            case SQLSRV_PHPTYPE_STREAM:
                            {
                                zval *zv = &param->parameter;
                                if (Z_ISREF_P(zv)) {
                                    ZVAL_DEREF(zv);
                                }
                                // Table-valued parameters are input-only
                                CHECK_CUSTOM_ERROR(Z_TYPE_P(zv) == IS_ARRAY, driver_stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
                                    throw pdo::PDOException();
                                }
                                // For other types, simply throw the following error
                                THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
                                break;
                            }
                            case SQLSRV_PHPTYPE_INT:
                                column_size = SQLSRV_UNKNOWN_SIZE;
                                break;
                            case SQLSRV_PHPTYPE_STRING:
                            {
                                CHECK_CUSTOM_ERROR( param->max_value_len <= 0, driver_stmt, 
                                                    PDO_SQLSRV_ERROR_INVALID_OUTPUT_STRING_SIZE, param->paramno + 1 ) {
                                    throw pdo::PDOException();
                                }
                                column_size = param->max_value_len;
                                break;
                            }
                            default:
                                SQLSRV_ASSERT( false, "Invalid PHP type for output parameter.  Should have been caught already." );
                                break;
                        }
                    }
                    // block all objects from being bound as input or input/output parameters since there is a
                    // weird case: 
                    // $obj = date_create(); 
                    // $s->bindParam( n, $obj, PDO::PARAM_INT ); // anything different than PDO::PARAM_STR
                    // that succeeds since the core layer implements DateTime object handling for the sqlsrv
                    // 2.0 driver.  To be consistent and avoid surprises of one object type working and others
                    // not, we block all objects here.
                    CHECK_CUSTOM_ERROR( direction != SQL_PARAM_OUTPUT && Z_TYPE( param->parameter ) == IS_OBJECT,
                                        driver_stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param->paramno + 1 ) {
                        throw pdo::PDOException();
                    }

                    // the encoding by default is that set on the statement
                    SQLSRV_ENCODING encoding = driver_stmt->encoding();
                    // if the statement's encoding is the default, then use the one on the connection
                    if( encoding == SQLSRV_ENCODING_DEFAULT ) {
                        encoding = driver_stmt->conn->encoding();
                    }

                    // Beginning with PHP7.2 the user can specify whether to use PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL
                    // But this extended type will be ignored in real prepared statements, so the encoding deliberately 
                    // set in the statement or driver options will still take precedence
                    if( !Z_ISUNDEF(param->driver_params) ) {
                        CHECK_CUSTOM_ERROR( Z_TYPE( param->driver_params ) != IS_LONG, driver_stmt, 
                                            PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM ) {
                            throw pdo::PDOException();
                        }
                        CHECK_CUSTOM_ERROR( pdo_type != PDO_PARAM_STR && pdo_type != PDO_PARAM_LOB, driver_stmt,
                                            PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_TYPE, param->paramno + 1 ) {
                            throw pdo::PDOException();
                        }
                        encoding = static_cast<SQLSRV_ENCODING>( Z_LVAL( param->driver_params ));

                        switch( encoding ) {
                            case SQLSRV_ENCODING_SYSTEM:
                            case SQLSRV_ENCODING_BINARY:
                            case SQLSRV_ENCODING_UTF8:
                                break;
                            default:
                                THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_DRIVER_PARAM_ENCODING,
                                                 param->paramno + 1 );
                                break;
                        }
                    }

                    // and bind the parameter
                    core_sqlsrv_bind_param( driver_stmt, static_cast<SQLUSMALLINT>( param->paramno ), direction, &(param->parameter) , php_out_type, encoding,
                                            sql_type, column_size, decimal_digits);
                }
                break;
            // undo any work done by the core layer after the statement is executed
            case PDO_PARAM_EVT_EXEC_POST:
                {
                    PDO_VALIDATE_STMT;
                    PDO_LOG_STMT_ENTRY;
                }

                break;
            case PDO_PARAM_EVT_FETCH_PRE:
                break;
            case PDO_PARAM_EVT_FETCH_POST:
                break;
            case PDO_PARAM_EVT_NORMALIZE:
                break;
            default:
                DIE( "pdo_sqlsrv_stmt_param_hook: Unknown event type" );
                break;
        }
    }
    catch( core::CoreException& ) {

        return 0;
    }
    catch( ... ) {

        DIE( "pdo_sqlsrv_stmt_param_hook: Unknown exception" );
    }

    return 1;
}