Skip to content

Commit 12343bd

Browse files
authored
Merge pull request rails#50893 from rails/rm-fix-schema-cache-multiple-configs
Unify the logic to determine the default schema cache path for a database configuration
2 parents fac3f20 + de645c6 commit 12343bd

File tree

8 files changed

+186
-62
lines changed

8 files changed

+186
-62
lines changed

activerecord/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
* Deprecate passing strings to `ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename`.
2+
3+
A `ActiveRecord::DatabaseConfigurations::DatabaseConfig` object should be passed instead.
4+
5+
*Rafael Mendonça França*
6+
17
* Add row_count field to sql.active_record notification
28

39
This field returns the amount of rows returned by the query that emitted the notification.

activerecord/lib/active_record/database_configurations/hash_config.rb

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,54 @@
11
# frozen_string_literal: true
22

3+
# :markup: markdown
4+
35
module ActiveRecord
46
class DatabaseConfigurations
5-
# = Active Record Database Hash Config
7+
# # Active Record Database Hash Config
68
#
7-
# A +HashConfig+ object is created for each database configuration entry that
8-
# is created from a hash.
9+
# A `HashConfig` object is created for each database configuration entry that is
10+
# created from a hash.
911
#
1012
# A hash config:
1113
#
12-
# { "development" => { "database" => "db_name" } }
14+
# { "development" => { "database" => "db_name" } }
1315
#
1416
# Becomes:
1517
#
16-
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
17-
# @env_name="development", @name="primary", @config={database: "db_name"}>
18+
# #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
19+
# @env_name="development", @name="primary", @config={database: "db_name"}>
1820
#
1921
# See ActiveRecord::DatabaseConfigurations for more info.
2022
class HashConfig < DatabaseConfig
2123
attr_reader :configuration_hash
2224

23-
24-
# Initialize a new +HashConfig+ object
25+
# Initialize a new `HashConfig` object
26+
#
27+
# #### Parameters
2528
#
26-
# ==== Options
29+
# * `env_name` - The Rails environment, i.e. "development".
30+
# * `name` - The db config name. In a standard two-tier database configuration
31+
# this will default to "primary". In a multiple database three-tier database
32+
# configuration this corresponds to the name used in the second tier, for
33+
# example "primary_readonly".
34+
# * `configuration_hash` - The config hash. This is the hash that contains the
35+
# database adapter, name, and other important information for database
36+
# connections.
2737
#
28-
# * <tt>:env_name</tt> - The \Rails environment, i.e. "development".
29-
# * <tt>:name</tt> - The db config name. In a standard two-tier
30-
# database configuration this will default to "primary". In a multiple
31-
# database three-tier database configuration this corresponds to the name
32-
# used in the second tier, for example "primary_readonly".
33-
# * <tt>:config</tt> - The config hash. This is the hash that contains the
34-
# database adapter, name, and other important information for database
35-
# connections.
3638
def initialize(env_name, name, configuration_hash)
3739
super(env_name, name)
3840
@configuration_hash = configuration_hash.symbolize_keys.freeze
3941
end
4042

4143
# Determines whether a database configuration is for a replica / readonly
42-
# connection. If the +replica+ key is present in the config, +replica?+ will
43-
# return +true+.
44+
# connection. If the `replica` key is present in the config, `replica?` will
45+
# return `true`.
4446
def replica?
4547
configuration_hash[:replica]
4648
end
4749

48-
# The migrations paths for a database configuration. If the
49-
# +migrations_paths+ key is present in the config, +migrations_paths+
50-
# will return its value.
50+
# The migrations paths for a database configuration. If the `migrations_paths`
51+
# key is present in the config, `migrations_paths` will return its value.
5152
def migrations_paths
5253
configuration_hash[:migrations_paths]
5354
end
@@ -92,8 +93,8 @@ def checkout_timeout
9293
(configuration_hash[:checkout_timeout] || 5).to_f
9394
end
9495

95-
# +reaping_frequency+ is configurable mostly for historical reasons, but it could
96-
# also be useful if someone wants a very low +idle_timeout+.
96+
# `reaping_frequency` is configurable mostly for historical reasons, but it
97+
# could also be useful if someone wants a very low `idle_timeout`.
9798
def reaping_frequency
9899
configuration_hash.fetch(:reaping_frequency, 60)&.to_f
99100
end
@@ -107,15 +108,18 @@ def adapter
107108
configuration_hash[:adapter]&.to_s
108109
end
109110

110-
# The path to the schema cache dump file for a database.
111-
# If omitted, the filename will be read from ENV or a
112-
# default will be derived.
111+
# The path to the schema cache dump file for a database. If omitted, the
112+
# filename will be read from ENV or a default will be derived.
113113
def schema_cache_path
114114
configuration_hash[:schema_cache_path]
115115
end
116116

117-
def default_schema_cache_path
118-
"db/schema_cache.yml"
117+
def default_schema_cache_path(db_dir = "db")
118+
if primary?
119+
File.join(db_dir, "schema_cache.yml")
120+
else
121+
File.join(db_dir, "#{name}_schema_cache.yml")
122+
end
119123
end
120124

121125
def lazy_schema_cache_path
@@ -126,14 +130,14 @@ def primary? # :nodoc:
126130
Base.configurations.primary?(name)
127131
end
128132

129-
# Determines whether to dump the schema/structure files and the
130-
# filename that should be used.
133+
# Determines whether to dump the schema/structure files and the filename that
134+
# should be used.
131135
#
132-
# If +configuration_hash[:schema_dump]+ is set to +false+ or +nil+
133-
# the schema will not be dumped.
136+
# If `configuration_hash[:schema_dump]` is set to `false` or `nil` the schema
137+
# will not be dumped.
134138
#
135-
# If the config option is set that will be used. Otherwise \Rails
136-
# will generate the filename from the database config name.
139+
# If the config option is set that will be used. Otherwise Rails will generate
140+
# the filename from the database config name.
137141
def schema_dump(format = ActiveRecord.schema_format)
138142
if configuration_hash.key?(:schema_dump)
139143
if config = configuration_hash[:schema_dump]

activerecord/lib/active_record/railtie.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,7 @@ class Railtie < Rails::Railtie # :nodoc:
148148
ActiveSupport.on_load(:active_record) do
149149
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
150150

151-
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
152-
db_config.name,
153-
schema_cache_path: db_config.schema_cache_path
154-
)
151+
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config)
155152

156153
cache = ActiveRecord::ConnectionAdapters::SchemaCache._load_from(filename)
157154
next if cache.nil?

activerecord/lib/active_record/railties/databases.rake

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ db_namespace = namespace :db do
509509
task dump: :load_config do
510510
ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection_for_each do |conn|
511511
db_config = conn.pool.db_config
512-
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config.name, schema_cache_path: db_config.schema_cache_path)
512+
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config)
513513

514514
ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename)
515515
end
@@ -518,10 +518,7 @@ db_namespace = namespace :db do
518518
desc "Clear a db/schema_cache.yml file."
519519
task clear: :load_config do
520520
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
521-
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
522-
db_config.name,
523-
schema_cache_path: db_config.schema_cache_path,
524-
)
521+
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(db_config)
525522
ActiveRecord::Tasks::DatabaseTasks.clear_schema_cache(
526523
filename,
527524
)

activerecord/lib/active_record/tasks/database_tasks.rb

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -437,14 +437,26 @@ def schema_dump_path(db_config, format = ActiveRecord.schema_format)
437437
end
438438
end
439439

440-
def cache_dump_filename(db_config_name, schema_cache_path: nil)
441-
filename = if ActiveRecord::Base.configurations.primary?(db_config_name)
442-
"schema_cache.yml"
440+
def cache_dump_filename(db_config_or_name, schema_cache_path: nil)
441+
if db_config_or_name.is_a?(DatabaseConfigurations::DatabaseConfig)
442+
schema_cache_path ||
443+
db_config_or_name.schema_cache_path ||
444+
ENV["SCHEMA_CACHE"] ||
445+
db_config_or_name.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir)
443446
else
444-
"#{db_config_name}_schema_cache.yml"
445-
end
447+
ActiveRecord.deprecator.deprecation_warning(<<~MSG.squish)
448+
Passing a database name to `cache_dump_filename` is deprecated and will be removed in Rails 7.3. Pass a
449+
`ActiveRecord::DatabaseConfigurations::DatabaseConfig` object instead.
450+
MSG
446451

447-
schema_cache_path || ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
452+
filename = if ActiveRecord::Base.configurations.primary?(db_config_or_name)
453+
"schema_cache.yml"
454+
else
455+
"#{db_config_or_name}_schema_cache.yml"
456+
end
457+
458+
schema_cache_path || ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
459+
end
448460
end
449461

450462
def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)

activerecord/test/cases/database_configurations/hash_config_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,31 @@ def test_schema_cache_path_default_for_primary
141141
assert_equal "db/schema_cache.yml", config.default_schema_cache_path
142142
end
143143

144+
def test_schema_cache_path_default_for_custom_name
145+
config = HashConfig.new("default_env", "alternate", adapter: "abstract")
146+
assert_equal "db/alternate_schema_cache.yml", config.default_schema_cache_path
147+
end
148+
149+
def test_schema_cache_path_default_for_different_db_dir
150+
config = HashConfig.new("default_env", "alternate", adapter: "abstract")
151+
assert_equal "my_db/alternate_schema_cache.yml", config.default_schema_cache_path("my_db")
152+
end
153+
144154
def test_schema_cache_path_configuration_hash
145155
config = HashConfig.new("default_env", "primary", { schema_cache_path: "db/config_schema_cache.yml", adapter: "abstract" })
146156
assert_equal "db/config_schema_cache.yml", config.schema_cache_path
147157
end
148158

159+
def test_lazy_schema_cache_path
160+
config = HashConfig.new("default_env", "primary", { schema_cache_path: "db/config_schema_cache.yml", adapter: "abstract" })
161+
assert_equal "db/config_schema_cache.yml", config.lazy_schema_cache_path
162+
end
163+
164+
def test_lazy_schema_cache_path_uses_default_if_config_is_not_present
165+
config = HashConfig.new("default_env", "alternate", { adapter: "abstract" })
166+
assert_equal "db/alternate_schema_cache.yml", config.lazy_schema_cache_path
167+
end
168+
149169
def test_validate_checks_the_adapter_exists
150170
config = HashConfig.new("default_env", "primary", adapter: "abstract")
151171
assert config.validate!

activerecord/test/cases/tasks/database_tasks_test.rb

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,38 @@ def test_cache_dump_default_filename
315315
old_path = ENV["SCHEMA_CACHE"]
316316
ENV.delete("SCHEMA_CACHE")
317317

318+
config = DatabaseConfigurations::HashConfig.new("development", "primary", {})
319+
320+
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "db") do
321+
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(config)
322+
assert_equal "db/schema_cache.yml", path
323+
end
324+
ensure
325+
ENV["SCHEMA_CACHE"] = old_path
326+
end
327+
328+
def test_cache_dump_default_filename_with_custom_db_dir
329+
old_path = ENV["SCHEMA_CACHE"]
330+
ENV.delete("SCHEMA_CACHE")
331+
332+
config = DatabaseConfigurations::HashConfig.new("development", "primary", {})
333+
334+
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "my_db") do
335+
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(config)
336+
assert_equal "my_db/schema_cache.yml", path
337+
end
338+
ensure
339+
ENV["SCHEMA_CACHE"] = old_path
340+
end
341+
342+
def test_deprecated_cache_dump_default_filename
343+
old_path = ENV["SCHEMA_CACHE"]
344+
ENV.delete("SCHEMA_CACHE")
345+
318346
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "db") do
319-
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename("primary")
347+
path = assert_deprecated(ActiveRecord.deprecator) do
348+
ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename("primary")
349+
end
320350
assert_equal "db/schema_cache.yml", path
321351
end
322352
ensure
@@ -327,8 +357,24 @@ def test_cache_dump_alternate_filename
327357
old_path = ENV["SCHEMA_CACHE"]
328358
ENV.delete("SCHEMA_CACHE")
329359

360+
config = DatabaseConfigurations::HashConfig.new("development", "alternate", {})
361+
330362
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "db") do
331-
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename("alternate")
363+
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(config)
364+
assert_equal "db/alternate_schema_cache.yml", path
365+
end
366+
ensure
367+
ENV["SCHEMA_CACHE"] = old_path
368+
end
369+
370+
def test_deprecated_cache_dump_alternate_filename
371+
old_path = ENV["SCHEMA_CACHE"]
372+
ENV.delete("SCHEMA_CACHE")
373+
374+
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "db") do
375+
path = assert_deprecated(ActiveRecord.deprecator) do
376+
ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename("alternate")
377+
end
332378
assert_equal "db/alternate_schema_cache.yml", path
333379
end
334380
ensure
@@ -339,8 +385,24 @@ def test_cache_dump_filename_with_env_override
339385
old_path = ENV["SCHEMA_CACHE"]
340386
ENV["SCHEMA_CACHE"] = "tmp/something.yml"
341387

388+
config = DatabaseConfigurations::HashConfig.new("development", "primary", {})
389+
390+
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "db") do
391+
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(config)
392+
assert_equal "tmp/something.yml", path
393+
end
394+
ensure
395+
ENV["SCHEMA_CACHE"] = old_path
396+
end
397+
398+
def test_deprecated_cache_dump_filename_with_env_override
399+
old_path = ENV["SCHEMA_CACHE"]
400+
ENV["SCHEMA_CACHE"] = "tmp/something.yml"
401+
342402
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "db") do
343-
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename("primary")
403+
path = assert_deprecated(ActiveRecord.deprecator) do
404+
ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename("primary")
405+
end
344406
assert_equal "tmp/something.yml", path
345407
end
346408
ensure
@@ -351,8 +413,39 @@ def test_cache_dump_filename_with_path_from_db_config
351413
old_path = ENV["SCHEMA_CACHE"]
352414
ENV.delete("SCHEMA_CACHE")
353415

416+
config = DatabaseConfigurations::HashConfig.new("development", "primary", { schema_cache_path: "tmp/something.yml" })
417+
418+
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "db") do
419+
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(config)
420+
assert_equal "tmp/something.yml", path
421+
end
422+
ensure
423+
ENV["SCHEMA_CACHE"] = old_path
424+
end
425+
426+
427+
def test_cache_dump_filename_with_path_from_the_argument_has_precedence
428+
old_path = ENV["SCHEMA_CACHE"]
429+
ENV.delete("SCHEMA_CACHE")
430+
431+
config = DatabaseConfigurations::HashConfig.new("development", "primary", { schema_cache_path: "tmp/something.yml" })
432+
354433
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "db") do
355-
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename("primary", schema_cache_path: "tmp/something.yml")
434+
path = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(config, schema_cache_path: "tmp/another.yml")
435+
assert_equal "tmp/another.yml", path
436+
end
437+
ensure
438+
ENV["SCHEMA_CACHE"] = old_path
439+
end
440+
441+
def test_deprecated_cache_dump_filename_with_path_from_the_argument
442+
old_path = ENV["SCHEMA_CACHE"]
443+
ENV.delete("SCHEMA_CACHE")
444+
445+
ActiveRecord::Tasks::DatabaseTasks.stub(:db_dir, "db") do
446+
path = assert_deprecated(ActiveRecord.deprecator) do
447+
ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename("primary", schema_cache_path: "tmp/something.yml")
448+
end
356449
assert_equal "tmp/something.yml", path
357450
end
358451
ensure

railties/test/application/rake/multi_dbs_test.rb

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -810,9 +810,7 @@ class TwoMigration < ActiveRecord::Migration::Current
810810
rails "db:drop" rescue nil
811811
end
812812

813-
# Note that schema cache loader depends on the connection and
814-
# does not work for all connections.
815-
test "schema_cache is loaded on primary db in multi-db app" do
813+
test "schema_cache is loaded on all connection db in multi-db app if it exists for the connection" do
816814
require "#{app_path}/config/environment"
817815
db_migrate_and_schema_cache_dump
818816

@@ -822,11 +820,8 @@ class TwoMigration < ActiveRecord::Migration::Current
822820
cache_tables_a = rails("runner", "p ActiveRecord::Base.connection.schema_cache.columns('books')").strip
823821
assert_includes cache_tables_a, "title", "expected cache_tables_a to include a title entry"
824822

825-
expired_warning = capture(:stderr) do
826-
cache_size_b = rails("runner", "p AnimalsBase.connection.schema_cache.size", stderr: true).strip
827-
assert_equal "0", cache_size_b
828-
end
829-
assert_match(/Ignoring .*\.yml because it has expired/, expired_warning)
823+
cache_size_b = rails("runner", "p AnimalsBase.connection.schema_cache.size", stderr: true).strip
824+
assert_equal "12", cache_size_b, "expected the cache size for animals to be valid since it was dumped"
830825

831826
cache_tables_b = rails("runner", "p AnimalsBase.connection.schema_cache.columns('dogs')").strip
832827
assert_includes cache_tables_b, "name", "expected cache_tables_b to include a name entry"

0 commit comments

Comments
 (0)