hot_compatibility_test.rb 4.2 KB
Newer Older
1 2
require "cases/helper"
require "support/connection_helper"
3 4

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

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

15
      def self.name; "HotCompatibility"; end
16 17 18 19
    end
  end

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

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

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

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

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

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

70 71 72 73 74 75 76 77 78
        # 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 已提交
79
        assert_empty get_prepared_statement_cache(@klass.connection),
80 81 82 83 84 85
          "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|
86
        record = @klass.create! bar: "bar"
87 88 89 90 91 92

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

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

96 97 98 99 100 101 102 103 104 105 106 107 108
        # 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 已提交
109
        assert_empty get_prepared_statement_cache(@klass.connection),
110 111 112 113 114 115 116
          "expected prepared statement cache to be empty but it wasn't"
      end
    end
  end

  private

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

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