From ca4beb594c5e886ae8fcc75960eda24574cfcb2d Mon Sep 17 00:00:00 2001 From: fynsta <63241108+fynsta@users.noreply.github.com> Date: Wed, 10 Apr 2024 23:08:56 +0200 Subject: [PATCH] Add support for Rails-style multiple dbs (#80) * Add support for Rails-style multiple dbs * Bump version --- CHANGELOG.md | 2 ++ bin/squasher | 6 +++- lib/squasher/cleaner.rb | 29 +++++++++++---- lib/squasher/config.rb | 80 ++++++++++++++++++++++++++++++++--------- lib/squasher/render.rb | 5 +-- lib/squasher/worker.rb | 50 ++++++++++++++++++++------ spec/lib/worker_spec.rb | 6 ++-- 7 files changed, 138 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc221b1..260ef1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- 0.8.0 + - Support multiple databases in Rails-style ([@fynsta](https://github.com/fynsta)) - 0.7.0 - Support the presence of multiverse ([@mlohbihler](https://github.com/mlohbihler)) - 0.6.1 diff --git a/bin/squasher b/bin/squasher index c1b98cd..d27478b 100755 --- a/bin/squasher +++ b/bin/squasher @@ -32,7 +32,11 @@ parser = OptionParser.new do |config| options[:engine] = value end - config.on('--databases=DB_KEY,...', 'alternate database configuration keys to be included dbconfig (for multiverse)') do |value| + config.on('--multi-db_format=FORMAT', 'format of the multi-db configuration (rails, multiverse)') do |value| + options[:multi_db_format] = value + end + + config.on('--databases=DB_KEY,...', 'alternate database configuration keys to be included') do |value| options[:databases] = value&.split(",") end diff --git a/lib/squasher/cleaner.rb b/lib/squasher/cleaner.rb index dc2cc4e..5878aa7 100644 --- a/lib/squasher/cleaner.rb +++ b/lib/squasher/cleaner.rb @@ -9,16 +9,31 @@ def self.process(*args) end def process - Squasher.error(:migration_folder_missing) unless config.migrations_folder? + Squasher.error(:migration_folder_missing) unless config.migrations_folders? + + if config.multi_db_format == 'rails' + config.databases.each do |database| + process_database(database) + end + else + process_database + end + end - migration_file = config.migration_file(now_timestamp, MIGRATION_NAME) - if prev_migration + def process_database(database = nil) + migration_file = config.migration_file(now_timestamp, MIGRATION_NAME, database) + if (prev_migration = prev_migration(database)) FileUtils.rm(prev_migration) end File.open(migration_file, 'wb') do |stream| - stream << ::Squasher::Render.render(MIGRATION_NAME, config) + stream << ::Squasher::Render.render(MIGRATION_NAME, config, database) + end + + if database.nil? + Squasher.rake("db:migrate", :db_cleaning) + else + Squasher.rake("db:migrate:#{database}", :db_cleaning) end - Squasher.rake("db:migrate", :db_cleaning) end private @@ -27,10 +42,10 @@ def config Squasher.config end - def prev_migration + def prev_migration(database = nil) return @prev_migration if defined?(@prev_migration) - @prev_migration = config.migration_files.detect do |file| + @prev_migration = config.migration_files(database).detect do |file| File.basename(file).include?(MIGRATION_NAME) end end diff --git a/lib/squasher/config.rb b/lib/squasher/config.rb index b1540ba..525ad37 100644 --- a/lib/squasher/config.rb +++ b/lib/squasher/config.rb @@ -34,12 +34,12 @@ def inspect end end - attr_reader :schema_file, :migration_version + attr_reader :migration_version, :multi_db_format, :databases def initialize @root_path = Dir.pwd.freeze - @migrations_folder = File.join(@root_path, 'db', 'migrate') @flags = [] + @multi_db_format = nil @databases = [] set_app_path(@root_path) end @@ -59,9 +59,9 @@ def set(key, value) elsif key == :migration Squasher.error(:invalid_migration_version, value: value) unless value.to_s =~ /\A\d.\d\z/ @migration_version = "[#{value}]" - elsif key == :sql - @schema_file = File.join(@app_path, 'db', 'structure.sql') - @flags << key + elsif key == :multi_db_format + Squasher.error(:invalid_multi_db_format, value: value) unless %w[rails multiverse].include?(value) + @multi_db_format = value elsif key == :databases @databases = value else @@ -73,16 +73,46 @@ def set?(k) @flags.include?(k) end - def migration_files - Dir.glob(File.join(migrations_folder, '**.rb')) + def schema_files + return [schema_file] unless @multi_db_format == 'rails' + + @databases.map { |db| schema_file(db) } + end + + def schema_file(database = nil) + prefix = database.nil? || database == 'primary' ? '' : "#{ database }_" + file = set?(:sql) ? 'structure.sql' : 'schema.rb' + + File.join(@app_path, 'db', "#{ prefix }#{ file }") + end + + def migration_files(database = nil) + Dir.glob(File.join(migrations_folder(database), '**.rb')) end - def migration_file(timestamp, migration_name) - File.join(migrations_folder, "#{ timestamp }_#{ migration_name }.rb") + def migration_file(timestamp, migration_name, database = nil) + File.join(migrations_folder(database), "#{ timestamp }_#{ migration_name }.rb") + end + + def migrations_folder(database = nil) + return default_migration_folder if database.nil? + + migrations_paths = dbconfig['development'][database]['migrations_paths'] + return default_migration_folder unless migrations_paths + + File.join(@app_path, migrations_paths) + end + + def migrations_folders? + if @multi_db_format != 'rails' + Dir.exist?(migrations_folder) + else + @databases.all? { |db| Dir.exist?(migrations_folder(db)) } + end end - def migrations_folder? - Dir.exist?(migrations_folder) + def default_migration_folder + File.join(@root_path, 'db', 'migrate') end def dbconfig? @@ -92,7 +122,7 @@ def dbconfig? def stub_dbconfig return unless dbconfig? - list = [dbconfig_file, schema_file] + list = [dbconfig_file, *schema_files] list.each do |file| next unless File.exist?(file) FileUtils.mv file, "#{ file }.sq" @@ -115,7 +145,7 @@ def in_app_root(&block) private - attr_reader :migrations_folder, :dbconfig_file + attr_reader :dbconfig_file def dbconfig return @dbconfig if defined?(@dbconfig) @@ -126,8 +156,27 @@ def dbconfig begin content, soft_error = Render.process(dbconfig_file) if content.has_key?('development') - @dbconfig = { 'development' => content['development'].merge('database' => 'squasher') } - @databases&.each { |database| @dbconfig[database] = content[database] } + if @multi_db_format == 'rails' + @dbconfig = { 'development' => {} } + @databases.each do |database| + @dbconfig['development'][database] = content['development'][database].merge('database' => "#{database}_squasher") + + database_name = content['development'][database]['database'] + content['development'].select { |_, v| v['database'] == database_name && v['replica'] }.each do |k, v| + @dbconfig['development'][k] = v.merge('database' => "#{database}_squasher") + end + end + else + @dbconfig = { 'development' => content['development'].merge('database' => 'squasher') } + + multiverse_by_default = @multi_db_format.nil? && @databases.any? + if multiverse_by_default + puts "Using multiverse format by default is deprecated and will be removed in the next major release. Please specify --multi-db_format=rails or --multi-db_format=multiverse explicitly." + end + if multiverse_by_default || @multi_db_format == 'multiverse' + @databases&.each { |database| @dbconfig[database] = content[database] } + end + end end rescue end @@ -140,7 +189,6 @@ def dbconfig def set_app_path(path) @app_path = path - @schema_file = File.join(path, 'db', 'schema.rb') @dbconfig_file = File.join(path, 'config', 'database.yml') end end diff --git a/lib/squasher/render.rb b/lib/squasher/render.rb index 57e6326..f77938b 100644 --- a/lib/squasher/render.rb +++ b/lib/squasher/render.rb @@ -8,9 +8,10 @@ def self.render(*args) attr_reader :name, :config - def initialize(name, config) + def initialize(name, config, database = nil) @name = name @config = config + @database = database end def render @@ -22,7 +23,7 @@ def render end def each_schema_line(&block) - File.open(config.schema_file, 'r') do |stream| + File.open(config.schema_file(@database), 'r') do |stream| if @config.set?(:sql) stream_structure(stream, &block) else diff --git a/lib/squasher/worker.rb b/lib/squasher/worker.rb index 4304c06..0c4cf26 100644 --- a/lib/squasher/worker.rb +++ b/lib/squasher/worker.rb @@ -20,9 +20,13 @@ def process Squasher.tell(:dry_mode_finished) Squasher.print(Render.render(:init_schema, config)) else - path = config.migration_file(finish_timestamp, :init_schema) - File.open(path, 'wb') { |io| io << Render.render(:init_schema, config) } - migrations.each { |file| FileUtils.rm(file) } + if config.multi_db_format == 'rails' + config.databases.each do |database| + clean_migrations(database) + end + else + clean_migrations + end end Squasher.rake("db:drop") unless Squasher.ask(:keep_database) @@ -38,30 +42,48 @@ def config end def check! - Squasher.error(:migration_folder_missing) unless config.migrations_folder? + Squasher.error(:migration_folder_missing) unless config.migrations_folders? Squasher.error(:dbconfig_invalid) unless config.dbconfig? - if migrations.empty? + + if config.multi_db_format == 'rails' + config.databases.each do |database| + check_migrations_exist(database) + end + else + check_migrations_exist + end + end + + def check_migrations_exist(database = nil) + if migrations(database).empty? print_date = date.strftime("%Y/%m/%d") - Squasher.error(:no_migrations, :date => print_date) + + Squasher.error(:no_migrations, :date => date.strftime("%Y/%m/%d")) end end - def migrations - @migrations ||= config.migration_files.select { |file| before_date?(get_timestamp(file)) }.sort + def migrations(database = nil) + config.migration_files(database).select { |file| before_date?(get_timestamp(file)) }.sort end def get_timestamp(file) File.basename(file)[/\A\d+/] end + def clean_migrations(database = nil) + path = config.migration_file(finish_timestamp(database), :init_schema, database) + migrations(database).each { |file| FileUtils.rm(file) } # Remove all migrations before creating the new one + File.open(path, 'wb') { |io| io << Render.render(:init_schema, config, database) } + end + def before_date?(timestamp) @point ||= date.strftime("%Y%m%d").to_i return unless timestamp timestamp[0...8].to_i < @point end - def finish_timestamp - @finish_timestamp ||= get_timestamp(migrations.last) + def finish_timestamp(database = nil) + get_timestamp(migrations(database).last) end def under_squash_env @@ -72,7 +94,13 @@ def under_squash_env return unless Squasher.rake("db:drop db:create", :db_create) end - return unless Squasher.rake("db:migrate VERSION=#{ finish_timestamp }", :db_migrate) + if config.multi_db_format == 'rails' + config.databases.each do |database| + return unless Squasher.rake("db:migrate:#{ database } VERSION=#{ finish_timestamp(database) }", :db_migrate) + end + else + return unless Squasher.rake("db:migrate VERSION=#{ finish_timestamp }", :db_migrate) + end yield diff --git a/spec/lib/worker_spec.rb b/spec/lib/worker_spec.rb index 7993d42..1968de1 100644 --- a/spec/lib/worker_spec.rb +++ b/spec/lib/worker_spec.rb @@ -6,7 +6,7 @@ let(:worker) { described_class.new(Time.new(2012, 6, 20)) } specify 'command was run not in application root' do - allow_any_instance_of(Squasher::Config).to receive(:migrations_folder?).and_return(false) + allow_any_instance_of(Squasher::Config).to receive(:migrations_folders?).and_return(false) expect_exit_with(:migration_folder_missing) end @@ -31,7 +31,7 @@ def expect_exit_with(*args) worker = described_class.new(Time.new(2014)) allow(worker).to receive(:under_squash_env).and_yield.and_return(true) new_migration_path = File.join(Dir.tmpdir, 'init_schema.rb') - allow_any_instance_of(Squasher::Config).to receive(:migration_file).with('20131213090719', :init_schema).and_return(new_migration_path) + allow_any_instance_of(Squasher::Config).to receive(:migration_file).with('20131213090719', :init_schema, nil).and_return(new_migration_path) expect(FileUtils).to receive(:rm).with(File.join(fake_root, 'db', 'migrate', '20131205160936_first_migration.rb')) expect(FileUtils).to receive(:rm).with(File.join(fake_root, 'db', 'migrate', '20131213090719_second_migration.rb')) @@ -60,7 +60,7 @@ def expect_exit_with(*args) worker = described_class.new(Time.new(2014)) allow(worker).to receive(:under_squash_env).and_yield.and_return(true) new_migration_path = File.join(Dir.tmpdir, 'init_schema.rb') - allow_any_instance_of(Squasher::Config).to receive(:migration_file).with('20131213090719', :init_schema).and_return(new_migration_path) + allow_any_instance_of(Squasher::Config).to receive(:migration_file).with('20131213090719', :init_schema, nil).and_return(new_migration_path) expect(FileUtils).to receive(:rm).with(File.join(fake_root, 'db', 'migrate', '20131205160936_first_migration.rb')) expect(FileUtils).to receive(:rm).with(File.join(fake_root, 'db', 'migrate', '20131213090719_second_migration.rb'))