1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
# frozen_string_literal: true
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) && ActiveRecord::Base.connection.prepared_statements
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
|