aboutsummaryrefslogblamecommitdiffstats
path: root/activerecord/test/cases/hot_compatibility_test.rb
blob: e107ff236267f2f142b2c6945cc6b2bad200bb9c (plain) (tree)
1
2
3
4
5
6
7
8
9
                                   
                                                   
                                      
                          

                                             
                                                                      


                     
                                            


             
                                                                 















                                                                 
                                      
                 
                                  

                                      
                                      



                                                              
                                  
                 
                                  
     


                                                                         
                                          




                                                       

                                                                     







                                                                     
                                                                     




                                                                               
                                          




                                                       

                                                                     











                                                                     
                                                                     





                                                                       


                                                    
 



                                                                                  

                                                                                        
             




                                                                      
              
                                                   
           
         
   
require "cases/helper"
require "support/connection_helper"

class HotCompatibilityTest < ActiveRecord::TestCase
  self.use_transactional_tests = false
  include ConnectionHelper

  setup do
    @klass = Class.new(ActiveRecord::Base) do
      connection.create_table :hot_compatibilities, force: true do |t|
        t.string :foo
        t.string :bar
      end

      def self.name; "HotCompatibility"; end
    end
  end

  teardown do
    ActiveRecord::Base.connection.drop_table :hot_compatibilities
  end

  test "insert after remove_column" do
    # warm cache
    @klass.create!

    # we have 3 columns
    assert_equal 3, @klass.columns.length

    # remove one of them
    @klass.connection.remove_column :hot_compatibilities, :bar

    # we still have 3 columns in the cache
    assert_equal 3, @klass.columns.length

    # but we can successfully create a record so long as we don't
    # reference the removed column
    record = @klass.create! foo: "foo"
    record.reload
    assert_equal "foo", record.foo
  end

  test "update after remove_column" do
    record = @klass.create! foo: "foo"
    assert_equal 3, @klass.columns.length
    @klass.connection.remove_column :hot_compatibilities, :bar
    assert_equal 3, @klass.columns.length

    record.reload
    assert_equal "foo", record.foo
    record.foo = "bar"
    record.save!
    record.reload
    assert_equal "bar", record.foo
  end

  if current_adapter?(:PostgreSQLAdapter)
    test "cleans up after prepared statement failure in a transaction" do
      with_two_connections do |original_connection, ddl_connection|
        record = @klass.create! bar: "bar"

        # prepare the reload statement in a transaction
        @klass.transaction do
          record.reload
        end

        assert get_prepared_statement_cache(@klass.connection).any?,
          "expected prepared statement cache to have something in it"

        # add a new column
        ddl_connection.add_column :hot_compatibilities, :baz, :string

        assert_raise(ActiveRecord::PreparedStatementCacheExpired) do
          @klass.transaction do
            record.reload
          end
        end

        assert_empty get_prepared_statement_cache(@klass.connection),
          "expected prepared statement cache to be empty but it wasn't"
      end
    end

    test "cleans up after prepared statement failure in nested transactions" do
      with_two_connections do |original_connection, ddl_connection|
        record = @klass.create! bar: "bar"

        # prepare the reload statement in a transaction
        @klass.transaction do
          record.reload
        end

        assert get_prepared_statement_cache(@klass.connection).any?,
          "expected prepared statement cache to have something in it"

        # add a new column
        ddl_connection.add_column :hot_compatibilities, :baz, :string

        assert_raise(ActiveRecord::PreparedStatementCacheExpired) do
          @klass.transaction do
            @klass.transaction do
              @klass.transaction do
                record.reload
              end
            end
          end
        end

        assert_empty get_prepared_statement_cache(@klass.connection),
          "expected prepared statement cache to be empty but it wasn't"
      end
    end
  end

  private

    def get_prepared_statement_cache(connection)
      connection.instance_variable_get(:@statements)
        .instance_variable_get(:@cache)[Process.pid]
    end

    # Rails will automatically clear the prepared statements on the connection
    # that runs the migration, so we use two connections to simulate what would
    # actually happen on a production system; we'd have one connection running the
    # migration from the rake task ("ddl_connection" here), and we'd have another
    # connection in a web worker.
    def with_two_connections
      run_without_connection do |original_connection|
        ActiveRecord::Base.establish_connection(original_connection.merge(pool_size: 2))
        begin
          ddl_connection = ActiveRecord::Base.connection_pool.checkout
          begin
            yield original_connection, ddl_connection
          ensure
            ActiveRecord::Base.connection_pool.checkin ddl_connection
          end
        ensure
          ActiveRecord::Base.clear_all_connections!
        end
      end
    end
end