hot_compatibility_test.rb 4.3 KB
Newer Older
1 2
# frozen_string_literal: true

3 4
require "cases/helper"
require "support/connection_helper"
5 6

class HotCompatibilityTest < ActiveRecord::TestCase
7
  self.use_transactional_tests = false
8
  include ConnectionHelper
9 10 11

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

17
      def self.name; "HotCompatibility"; end
18 19 20 21
    end
  end

  teardown do
22
    ActiveRecord::Base.connection.drop_table :hot_compatibilities
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
  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
40
    record = @klass.create! foo: "foo"
41
    record.reload
42
    assert_equal "foo", record.foo
43 44 45
  end

  test "update after remove_column" do
46
    record = @klass.create! foo: "foo"
47 48 49 50 51
    assert_equal 3, @klass.columns.length
    @klass.connection.remove_column :hot_compatibilities, :bar
    assert_equal 3, @klass.columns.length

    record.reload
52 53
    assert_equal "foo", record.foo
    record.foo = "bar"
54 55
    record.save!
    record.reload
56
    assert_equal "bar", record.foo
57
  end
58

59
  if current_adapter?(:PostgreSQLAdapter) && ActiveRecord::Base.connection.prepared_statements
60 61
    test "cleans up after prepared statement failure in a transaction" do
      with_two_connections do |original_connection, ddl_connection|
62
        record = @klass.create! bar: "bar"
63 64 65 66 67 68

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

S
Sam Davies 已提交
69 70 71
        assert get_prepared_statement_cache(@klass.connection).any?,
          "expected prepared statement cache to have something in it"

72 73 74 75 76 77 78 79 80
        # add a new column
        ddl_connection.add_column :hot_compatibilities, :baz, :string

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

S
Sam Davies 已提交
81
        assert_empty get_prepared_statement_cache(@klass.connection),
82 83 84 85 86 87
          "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|
88
        record = @klass.create! bar: "bar"
89 90 91 92 93 94

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

S
Sam Davies 已提交
95 96 97
        assert get_prepared_statement_cache(@klass.connection).any?,
          "expected prepared statement cache to have something in it"

98 99 100 101 102 103 104 105 106 107 108 109 110
        # 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

S
Sam Davies 已提交
111
        assert_empty get_prepared_statement_cache(@klass.connection),
112 113 114 115 116 117 118
          "expected prepared statement cache to be empty but it wasn't"
      end
    end
  end

  private

119 120 121 122
    def get_prepared_statement_cache(connection)
      connection.instance_variable_get(:@statements)
        .instance_variable_get(:@cache)[Process.pid]
    end
S
Sam Davies 已提交
123

124 125 126 127 128
    # 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.
129 130 131
    def with_two_connections
      run_without_connection do |original_connection|
        ActiveRecord::Base.establish_connection(original_connection.merge(pool_size: 2))
132
        begin
133 134 135 136 137 138
          ddl_connection = ActiveRecord::Base.connection_pool.checkout
          begin
            yield original_connection, ddl_connection
          ensure
            ActiveRecord::Base.connection_pool.checkin ddl_connection
          end
139
        ensure
140
          ActiveRecord::Base.clear_all_connections!
141 142 143
        end
      end
    end
144
end