int pdo_sqlsrv_dbh_prepare()

in source/pdo_sqlsrv/pdo_dbh.cpp [713:872]


int pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_reads_(sql_len) const char *sql,
                           _Inout_ size_t sql_len, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options)
#else
bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql_zstr, _Inout_ pdo_stmt_t *stmt, _In_ zval *driver_options)
#endif
{
    PDO_RESET_DBH_ERROR;
    PDO_VALIDATE_CONN;
    PDO_LOG_DBH_ENTRY;

    hash_auto_ptr pdo_stmt_options_ht;
    sqlsrv_malloc_auto_ptr<char> sql_rewrite;
    size_t sql_rewrite_len = 0;
    sqlsrv_malloc_auto_ptr<pdo_sqlsrv_stmt> driver_stmt;
    hash_auto_ptr placeholders;
    sqlsrv_malloc_auto_ptr<sql_string_parser> sql_parser;

    pdo_sqlsrv_dbh* driver_dbh = reinterpret_cast<pdo_sqlsrv_dbh*>( dbh->driver_data );
    SQLSRV_ASSERT(( driver_dbh != NULL ), "pdo_sqlsrv_dbh_prepare: dbh->driver_data was null");

    try {

        // assign the methods for the statement object.  This is necessary even if the 
        // statement fails so the user can retrieve the error information.
        stmt->methods = &pdo_sqlsrv_stmt_methods;
        // if not emulate_prepare, we support parameterized queries with ?, not names
        stmt->supports_placeholders = (driver_dbh->emulate_prepare) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; // the statement options may override this later

        // Initialize the options array to be passed to the core layer
        ALLOC_HASHTABLE( pdo_stmt_options_ht );
        core::sqlsrv_zend_hash_init( *driver_dbh , pdo_stmt_options_ht, 3 /* # of buckets */, 
                                     ZVAL_PTR_DTOR, 0 /*persistent*/ );
        
        // Either of g_pdo_henv_cp or g_pdo_henv_ncp can be used to propogate the error.
        validate_stmt_options( *driver_dbh, driver_options, pdo_stmt_options_ht );

        driver_stmt = static_cast<pdo_sqlsrv_stmt*>( core_sqlsrv_create_stmt( driver_dbh, core::allocate_stmt<pdo_sqlsrv_stmt>,
                                                                              pdo_stmt_options_ht, PDO_STMT_OPTS, 
                                                                              pdo_sqlsrv_handle_stmt_error, stmt ));

        // if the user didn't set anything in the prepare options, then set the buffer limit
        // to the value set on the connection.
        if( driver_stmt->buffered_query_limit== sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_INVALID ) {
            
             driver_stmt->buffered_query_limit = driver_dbh->client_buffer_max_size;
        }

#if PHP_VERSION_ID >= 80100
        zend_string* sql_rewrite_zstr = NULL;
        const char* sql = ZSTR_VAL(sql_zstr);
        size_t sql_len = ZSTR_LEN(sql_zstr);
#endif

        // rewrite named parameters in the query to positional parameters if we aren't letting PDO do the
        // parameter substitution for us
        if( stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {

            // rewrite the query to map named parameters to positional parameters.  We do this rather than use the ODBC named
            // parameters for consistency with the PDO MySQL and PDO ODBC drivers.
#if PHP_VERSION_ID < 80100
            int zr = pdo_parse_params( stmt, const_cast<char*>( sql ), sql_len, &sql_rewrite, &sql_rewrite_len );
            CHECK_ZEND_ERROR(zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) {
                throw core::CoreException();
            }
            // if parameter substitution happened, use that query instead of the original
            if (sql_rewrite != 0) {
                sql = sql_rewrite;
                sql_len = sql_rewrite_len;
            }
#else
            int zr = pdo_parse_params(stmt, sql_zstr, &sql_rewrite_zstr);
            CHECK_ZEND_ERROR(zr, driver_dbh, PDO_SQLSRV_ERROR_PARAM_PARSE) {
                throw core::CoreException();
            }

            // if parameter substitution happened, use that query instead of the original
            if (sql_rewrite_zstr != NULL) {
                sql = ZSTR_VAL(sql_rewrite_zstr);
                sql_len = ZSTR_LEN(sql_rewrite_zstr);
            }
#endif
        }

        if( !driver_stmt->direct_query && stmt->supports_placeholders != PDO_PLACEHOLDER_NONE ) {
            core_sqlsrv_prepare(driver_stmt, sql, sql_len);
        }
        else if( driver_stmt->direct_query ) {

            if( driver_stmt->direct_query_subst_string ) {
                // we use efree rather than sqlsrv_free since sqlsrv_free may wrap another allocation scheme
                // and we use estrdup below to allocate the new string, which uses emalloc
                efree( reinterpret_cast<void*>( const_cast<char*>( driver_stmt->direct_query_subst_string )));
            }
            driver_stmt->direct_query_subst_string = estrdup( sql );
            driver_stmt->direct_query_subst_string_len = sql_len;
        }

#if PHP_VERSION_ID >= 80100
        if (sql_rewrite_zstr != NULL) {
            zend_string_release(sql_rewrite_zstr);
        }
#endif

        // else if stmt->support_placeholders == PDO_PLACEHOLDER_NONE means that stmt->active_query_string will be
        // set to the substituted query
        if ( stmt->supports_placeholders == PDO_PLACEHOLDER_NONE ) {
            // parse placeholders in the sql query into the placeholders ht
            ALLOC_HASHTABLE( placeholders );
            core::sqlsrv_zend_hash_init(*driver_dbh, placeholders, 5, ZVAL_PTR_DTOR /* dtor */, 0 /* persistent */);
#if PHP_VERSION_ID < 80100
            sql_parser = new (sqlsrv_malloc(sizeof(sql_string_parser))) sql_string_parser(*driver_dbh, stmt->query_string,
                static_cast<int>(stmt->query_stringlen), placeholders);
#else
            sql_parser = new (sqlsrv_malloc(sizeof(sql_string_parser))) sql_string_parser(*driver_dbh, ZSTR_VAL(stmt->query_string),
                ZSTR_LEN(stmt->query_string), placeholders);
#endif
            sql_parser->parse_sql_string();
            driver_stmt->placeholders = placeholders;
            placeholders.transferred();
        }

        stmt->driver_data = driver_stmt;
        driver_stmt.transferred();                   
    }
    // everything is cleaned up by this point
    // catch everything so the exception doesn't spill into the calling PDO code
    catch( core::CoreException& ) {

        if( driver_stmt ) {

            driver_stmt->~pdo_sqlsrv_stmt();
        }

        // in the event that the statement caused an error that was copied to the connection, update the
        // connection with the error's SQLSTATE.
        if( driver_dbh->last_error() ) {

            strcpy_s( dbh->error_code, sizeof( dbh->error_code ), 
                      reinterpret_cast<const char*>( driver_dbh->last_error()->sqlstate ));
        }

#if PHP_VERSION_ID < 80100
        return 0;
#else
        return false;
#endif
    }

    // catch any errant exception and die
    catch(...) {

        DIE( "pdo_sqlsrv_dbh_prepare: Unknown exception caught." );
    }

#if PHP_VERSION_ID < 80100
    return 1;
#else
    return true;
#endif
}