Skip to content

Commit 462545c

Browse files
authored
Merge pull request rails#45450 from eileencodes/only-remove-connection-if-config-is-different
Only remove connection for an existing pool if the config is different
2 parents c3c7475 + adb64db commit 462545c

File tree

9 files changed

+65
-19
lines changed

9 files changed

+65
-19
lines changed

activerecord/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
* Don't establish a new connection if an identical pool exists already.
2+
3+
Previously, if `establish_connection` was called on a class that already had an established connection, the existing connection would be removed regardless of whether it was the same config. Now if a pool is found with the same values as the new connection, the existing connection will be returned instead of creating a new one.
4+
5+
This has a slight change in behavior if application code is depending on a new connection being established regardless of whether it's identical to an existing connection. If the old behavior is desirable, applications should call `ActiveRecord::Base#remove_connection` before establishing a new one. Calling `establish_connection` with a different config works the same way as it did previously.
6+
7+
*Eileen M. Uchitelle*
8+
19
* Update `db:prepare` task to load schema when an uninitialized database exists, and dump schema after migrations.
210

311
*Ben Sheldon*

activerecord/lib/active_record/connection_adapters/abstract/connection_handler.rb

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,30 +103,32 @@ def connection_pool_list(role = ActiveRecord::Base.current_role)
103103

104104
def establish_connection(config, owner_name: Base, role: ActiveRecord::Base.current_role, shard: Base.current_shard)
105105
owner_name = StringConnectionOwner.new(config.to_s) if config.is_a?(Symbol)
106-
107106
pool_config = resolve_pool_config(config, owner_name, role, shard)
108107
db_config = pool_config.db_config
109108

110-
# Protects the connection named `ActiveRecord::Base` from being removed
111-
# if the user calls `establish_connection :primary`.
112-
if owner_to_pool_manager.key?(pool_config.connection_specification_name)
109+
# If there is an existing pool with the same values as the pool_config
110+
# don't remove the connection. Connections should only be removed if we are
111+
# establishing a connection on a class that is already connected to a different
112+
# configuration.
113+
pool = retrieve_connection_pool(pool_config.connection_specification_name, role: role, shard: shard)
114+
115+
unless pool && pool.db_config == db_config
113116
remove_connection_pool(pool_config.connection_specification_name, role: role, shard: shard)
114-
end
115117

116-
message_bus = ActiveSupport::Notifications.instrumenter
117-
payload = {}
118-
if pool_config
119-
payload[:spec_name] = pool_config.connection_specification_name
120-
payload[:shard] = shard
121-
payload[:config] = db_config.configuration_hash
118+
owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
119+
pool_manager = get_pool_manager(pool_config.connection_specification_name)
120+
pool_manager.set_pool_config(role, shard, pool_config)
121+
pool = pool_config.pool
122122
end
123123

124-
owner_to_pool_manager[pool_config.connection_specification_name] ||= PoolManager.new
125-
pool_manager = get_pool_manager(pool_config.connection_specification_name)
126-
pool_manager.set_pool_config(role, shard, pool_config)
124+
payload = {
125+
spec_name: pool_config.connection_specification_name,
126+
shard: shard,
127+
config: db_config.configuration_hash
128+
}
127129

128-
message_bus.instrument("!connection.active_record", payload) do
129-
pool_config.pool
130+
ActiveSupport::Notifications.instrumenter.instrument("!connection.active_record", payload) do
131+
pool
130132
end
131133
end
132134

activerecord/lib/active_record/railties/databases.rake

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ db_namespace = namespace :db do
488488
namespace :load do
489489
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
490490
desc "Loads a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the #{name} database"
491-
task name => [:load_config, :check_protected_environments] do
491+
task name => [:load_config, :check_protected_environments, "db:test:purge:#{name}"] do
492492
original_db_config = ActiveRecord::Base.connection_db_config
493493
db_config = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env, name: name)
494494
schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
@@ -604,8 +604,11 @@ db_namespace = namespace :db do
604604
# desc "Empty the #{name} test database"
605605
namespace :purge do
606606
task name => %w(load_config check_protected_environments) do
607+
original_db_config = ActiveRecord::Base.connection_db_config
607608
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "test", name: name)
608609
ActiveRecord::Tasks::DatabaseTasks.purge(db_config)
610+
ensure
611+
ActiveRecord::Base.establish_connection(original_db_config) if original_db_config
609612
end
610613
end
611614

activerecord/lib/active_record/tasks/sqlite_database_tasks.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ def drop
3333

3434
def purge
3535
drop
36+
connection.disconnect!
3637
rescue NoDatabaseError
3738
ensure
3839
create
40+
connection.reconnect!
3941
end
4042

4143
def charset

activerecord/test/cases/connection_adapters/connection_handler_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ def test_connection_specification_name_should_fallback_to_parent
273273
ActiveRecord::Base.connection_specification_name = "readonly"
274274
assert_equal "readonly", klassC.connection_specification_name
275275
ensure
276+
ApplicationRecord.remove_connection
276277
Object.send :remove_const, :ApplicationRecord
277278
ActiveRecord::Base.connection_specification_name = "ActiveRecord::Base"
278279
end

activerecord/test/cases/connection_adapters/connection_swapping_nested_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ def test_application_record_prevent_writes_can_be_changed
448448
end
449449
end
450450
ensure
451+
ApplicationRecord.remove_connection
451452
ActiveRecord.application_record_class = nil
452453
Object.send(:remove_const, :ApplicationRecord)
453454
ActiveRecord::Base.establish_connection :arunit

activerecord/test/cases/primary_class_test.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def test_application_record_shares_a_connection_with_active_record_by_default
110110
assert_predicate ApplicationRecord, :application_record_class?
111111
assert_equal ActiveRecord::Base.connection, ApplicationRecord.connection
112112
ensure
113+
ApplicationRecord.remove_connection
113114
ActiveRecord.application_record_class = nil
114115
Object.send(:remove_const, :ApplicationRecord)
115116
ActiveRecord::Base.establish_connection :arunit
@@ -125,6 +126,7 @@ def test_application_record_shares_a_connection_with_the_primary_abstract_class_
125126
assert_predicate PrimaryClassTest::PrimaryAppRecord, :abstract_class?
126127
assert_equal ActiveRecord::Base.connection, PrimaryClassTest::PrimaryAppRecord.connection
127128
ensure
129+
PrimaryClassTest::PrimaryAppRecord.remove_connection
128130
ActiveRecord.application_record_class = nil
129131
ActiveRecord::Base.establish_connection :arunit
130132
end

activerecord/test/cases/relation/load_async_test.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,10 @@ class LoadAsyncNullExecutorTest < ActiveRecord::TestCase
231231
def setup
232232
@old_config = ActiveRecord.async_query_executor
233233
ActiveRecord.async_query_executor = nil
234-
ActiveRecord::Base.establish_connection :arunit
235234
end
236235

237236
def teardown
238237
ActiveRecord.async_query_executor = @old_config
239-
ActiveRecord::Base.establish_connection :arunit
240238
end
241239

242240
def test_scheduled?

railties/test/application/rake/migrations_test.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,35 @@ class TwoMigration < ActiveRecord::Migration::Current
508508
assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output)
509509
end
510510
end
511+
512+
test "migrations with execute run when connections are established from a loaded model" do
513+
Dir.chdir(app_path) do
514+
app_file "app/models/application_record.rb", <<-RUBY
515+
class ApplicationRecord < ActiveRecord::Base
516+
primary_abstract_class
517+
518+
establish_connection :primary
519+
end
520+
RUBY
521+
522+
rails "generate", "model", "user", "username:string", "password:string"
523+
524+
rails("db:migrate")
525+
526+
app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION
527+
class AMigration < ActiveRecord::Migration::Current
528+
def change
529+
User.first
530+
execute("SELECT 1")
531+
end
532+
end
533+
MIGRATION
534+
535+
output = rails("db:migrate")
536+
537+
assert_match(/execute\("SELECT 1"\)/, output)
538+
end
539+
end
511540
end
512541
end
513542
end

0 commit comments

Comments
 (0)