Skip to content

Commit 45eb0f3

Browse files
eileencodesseejohnrundsounded
committed
Fix migration ordering across databases
Previously if there were 2 migrations in one db and 1 migration in the other db all the migrations for db one would run and then all migrations for db two would run. If a migration in one database depended on a migration in another database then it could fail. This is probably pretty rare, however in a multi-db application that's moving tables from one db to another, running them out of order could result in a migration error. In this this change we collect all the versions for each migration and the corresponding db_config so we can run them in the order they are created rather than per-db. Closes rails#41664 Related rails#41538 Co-authored-by: John Crepezzi <[email protected]> Co-authored-by: Kiril Dokh <[email protected]>
1 parent 6ae78e9 commit 45eb0f3

File tree

4 files changed

+61
-7
lines changed

4 files changed

+61
-7
lines changed

activerecord/lib/active_record/migration.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1140,7 +1140,11 @@ def current_version # :nodoc:
11401140
end
11411141

11421142
def needs_migration? # :nodoc:
1143-
(migrations.collect(&:version) - get_all_versions).size > 0
1143+
pending_migration_versions.size > 0
1144+
end
1145+
1146+
def pending_migration_versions # :nodoc:
1147+
migrations.collect(&:version) - get_all_versions
11441148
end
11451149

11461150
def migrations # :nodoc:

activerecord/lib/active_record/railties/databases.rake

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,23 @@ db_namespace = namespace :db do
8686

8787
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
8888
task migrate: :load_config do
89-
original_db_config = ActiveRecord::Base.connection_db_config
90-
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
91-
ActiveRecord::Base.establish_connection(db_config)
89+
db_configs = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env)
90+
91+
if db_configs.size == 1
9292
ActiveRecord::Tasks::DatabaseTasks.migrate
93+
else
94+
original_db_config = ActiveRecord::Base.connection_db_config
95+
mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions(db_configs)
96+
97+
mapped_versions.sort.each do |version, db_config|
98+
ActiveRecord::Base.establish_connection(db_config)
99+
ActiveRecord::Tasks::DatabaseTasks.migrate(version)
100+
end
93101
end
102+
94103
db_namespace["_dump"].invoke
95104
ensure
96-
ActiveRecord::Base.establish_connection(original_db_config)
105+
ActiveRecord::Base.establish_connection(original_db_config) if original_db_config
97106
end
98107

99108
# IMPORTANT: This task won't dump the schema if ActiveRecord.dump_schema_after_migration is set to false

activerecord/lib/active_record/tasks/database_tasks.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,13 +268,13 @@ def truncate_all(environment = env)
268268
end
269269
end
270270

271-
def migrate
271+
def migrate(version = nil)
272272
check_target_version
273273

274274
scope = ENV["SCOPE"]
275275
verbose_was, Migration.verbose = Migration.verbose, verbose?
276276

277-
Base.connection.migration_context.migrate(target_version) do |migration|
277+
Base.connection.migration_context.migrate(target_version || version) do |migration|
278278
scope.blank? || scope == migration.scope
279279
end.tap do |migrations_ran|
280280
Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
@@ -285,6 +285,23 @@ def migrate
285285
Migration.verbose = verbose_was
286286
end
287287

288+
def db_configs_with_versions(db_configs) # :nodoc:
289+
db_configs_with_versions = {}
290+
291+
db_configs.each do |db_config|
292+
ActiveRecord::Base.establish_connection(db_config)
293+
versions_to_run = ActiveRecord::Base.connection.migration_context.pending_migration_versions
294+
target_version = ActiveRecord::Tasks::DatabaseTasks.target_version
295+
296+
versions_to_run.each do |version|
297+
next if target_version && target_version != version
298+
db_configs_with_versions[version] = db_config
299+
end
300+
end
301+
302+
db_configs_with_versions
303+
end
304+
288305
def migrate_status
289306
unless ActiveRecord::Base.connection.schema_migration.table_exists?
290307
Kernel.abort "Schema migrations table does not exist yet."

railties/test/application/rake/multi_dbs_test.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,30 @@ def generate_models_for_animals
427427
end
428428
end
429429

430+
test "db:migrate respects timestamp ordering across databases" do
431+
require "#{app_path}/config/environment"
432+
app_file "db/migrate/01_one_migration.rb", <<-MIGRATION
433+
class OneMigration < ActiveRecord::Migration::Current
434+
end
435+
MIGRATION
436+
437+
app_file "db/animals_migrate/02_two_migration.rb", <<-MIGRATION
438+
class TwoMigration < ActiveRecord::Migration::Current
439+
end
440+
MIGRATION
441+
442+
app_file "db/migrate/03_three_migration.rb", <<-MIGRATION
443+
class ThreeMigration < ActiveRecord::Migration::Current
444+
end
445+
MIGRATION
446+
447+
Dir.chdir(app_path) do
448+
output = rails "db:migrate"
449+
entries = output.scan(/^== (\d+).+migrated/).map(&:first).map(&:to_i)
450+
assert_equal [1, 2, 3], entries
451+
end
452+
end
453+
430454
test "db:migrate and db:schema:dump and db:schema:load works on all databases" do
431455
db_migrate_and_schema_dump_and_load
432456
end

0 commit comments

Comments
 (0)