• S
    Correctly deallocate prepared statements if we fail inside a transaction · 50c53340
    Sam Davies 提交于
    - Addresses issue #12330
    
    Overview
    ========
    
    Cached postgres prepared statements become invalidated if the schema
    changes in a way that it affects the returned result.
    
    Examples:
    - adding or removing a column then doing a 'SELECT *'
    - removing the foo column  then doing a 'SELECT bar.foo'
    
    In normal operation this isn't a problem, we can rescue the error,
    deallocate the prepared statement and re-issue the command.
    
    However in PostgreSQL transactions, once any command fails, the
    transaction becomes 'poisoned' and any subsequent commands will raise
    InFailedSQLTransaction.
    
    This includes DEALLOCATE statements, so the default deallocation
    strategy instead of removing the cached prepared statement instead
    raises InFailedSQLTransaction.
    
    Why this is bad
    ===============
    
    1. InFailedSQLTransaction is a fairly cryptic error and doesn't
    communicate any useful information about what has actually gone wrong.
    2. In the naive implementation the prepared statement never gets
    deallocated - it stays alive for the length of the session taking up
    memory on the postgres server.
    3. It is unsafe to retry the transaction because the bad prepared
    statement is still in the cache and we would see the exact same failure
    repeated.
    
    Solution
    ========
    
    If we are outside a transaction we can continue to handle these failures
    gracefully in the usual way.
    
    Inside a transaction instead of issuing a DEALLOCATE command that will
    certainly fail, we now raise
    ActiveRecord::PreparedStatementCacheExpired.
    
    This can be handled further up the stack, notably inside
    TransactionManager#within_new_transaction. Here we can make sure to
    first rollback the transaction, then safely issue DEALLOCATE statements
    to invalidate the rest of the cached prepared statements.
    
    This also allows the user (or some gem) the opportunity to catch this error and
    voluntarily retry the transaction if a schema change causes the prepared
    statement cache to become invalidated.
    
    Because the outdated statement has been deallocated, we can expect the
    transaction to succeed on the second try.
    50c53340
postgresql_adapter.rb 30.1 KB