int pdo_sqlsrv_stmt_execute()

in source/pdo_sqlsrv/pdo_stmt.cpp [520:629]


int pdo_sqlsrv_stmt_execute( _Inout_ pdo_stmt_t *stmt )
{
    PDO_RESET_STMT_ERROR;
    PDO_VALIDATE_STMT;
    PDO_LOG_STMT_ENTRY;

    try {

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

        // prepare for execution by flushing anything remaining in the result set if it wasn't already
        // done before binding parameters
        if( driver_stmt && driver_stmt->executed && !driver_stmt->past_next_result_end ) {

            while( driver_stmt->past_next_result_end == false ) {

                core_sqlsrv_next_result( driver_stmt, false );
            }
        }
        
        const char* query = NULL;
        unsigned int query_len = 0;

        // if the user is doing a direct query (PDO::SQLSRV_ATTR_DIRECT_QUERY), set the query here
        if( driver_stmt->direct_query ) {

            query = driver_stmt->direct_query_subst_string;
            query_len = static_cast<unsigned int>( driver_stmt->direct_query_subst_string_len );
        }

        // if the user is using prepare emulation (PDO::ATTR_EMULATE_PREPARES), set the query to the 
        // subtituted query provided by PDO
        if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
            // reset the placeholders hashtable internal in case the user reexecutes a statement
            // Normally it's not a good idea to alter the internal pointer in a hashed array 
            // (see pull request 634 on GitHub) but in this case this is for internal use only

            zend_hash_internal_pointer_reset(driver_stmt->placeholders);

#if PHP_VERSION_ID < 80100
            query = stmt->active_query_string;
            query_len = static_cast<unsigned int>(stmt->active_query_stringlen);
#else
            query = ZSTR_VAL(stmt->active_query_string);
            query_len = ZSTR_LEN(stmt->active_query_string);
#endif
        }

        // The query timeout setting is inherited from the corresponding connection attribute, but
        // the user may have changed the query timeout setting again before this via 
        // PDOStatement::setAttribute()
        driver_stmt->set_query_timeout();
 
        SQLRETURN execReturn = core_sqlsrv_execute( driver_stmt, query, query_len );

        if ( execReturn == SQL_NO_DATA ) {
            stmt->column_count = 0;
            stmt->row_count = 0;
            driver_stmt->column_count = 0;
            driver_stmt->row_count = 0;
        }
        else {
            if (driver_stmt->column_count == ACTIVE_NUM_COLS_INVALID) {
                stmt->column_count = core::SQLNumResultCols( driver_stmt );
                driver_stmt->column_count = stmt->column_count;
            }
            else {
                stmt->column_count = driver_stmt->column_count;
            }

            if (driver_stmt->row_count == ACTIVE_NUM_ROWS_INVALID) {
                // return the row count regardless if there are any rows or not
                stmt->row_count = core::SQLRowCount( driver_stmt );
                driver_stmt->row_count = stmt->row_count;
            }
            else {
                stmt->row_count = driver_stmt->row_count;
            }
        }

        // workaround for a bug in the PDO driver manager.  It is fairly simple to crash the PDO driver manager with 
        // the following sequence:
        // 1) Prepare and execute a statement (that has some results with it)
        // 2) call PDOStatement::nextRowset until there are no more results
        // 3) execute the statement again
        // 4) call PDOStatement::getColumnMeta
        // It crashes from what I can tell because there is no metadata because there was no call to 
        // pdo_stmt_sqlsrv_describe_col and stmt->columns is NULL on the second call to
        // PDO::execute.  My guess is that because stmt->executed is true, it optimizes away a necessary call to
        // pdo_sqlsrv_stmt_describe_col.  By setting the stmt->executed flag to 0, this call is not optimized away
        // and the crash disappears.
        if( stmt->columns == NULL ) {
            stmt->executed = 0;
        }
    }
    catch( core::CoreException& /*e*/ ) {

        return 0;

    }
    catch( ... ) {

        DIE( "pdo_sqlsrv_stmt_execute: Unexpected exception occurred." );

    }

    // success
    return 1;
}