lib/strong_migrations/checker.rb in strong_migrations-2.0.2 vs lib/strong_migrations/checker.rb in strong_migrations-2.1.0

- old
+ new

@@ -9,14 +9,20 @@ attr_accessor :safe end def initialize(migration) @migration = migration + reset + end + + def reset @new_tables = [] @new_columns = [] @timeouts_set = false @committed = false + @transaction_disabled = false + @skip_retries = false end def self.safety_assured previous_value = safe begin @@ -25,11 +31,14 @@ ensure self.safe = previous_value end end - def perform(method, *args) + def perform(method, *args, &block) + return yield if skip? + + check_adapter check_version_supported set_timeouts check_lock_timeout if !safe? || safe_by_default_method?(method) @@ -94,23 +103,30 @@ result = if retry_lock_timeouts?(method) # TODO figure out how to handle methods that generate multiple statements # like add_reference(table, ref, index: {algorithm: :concurrently}) # lock timeout after first statement will cause retry to fail - retry_lock_timeouts { yield } + retry_lock_timeouts { perform_method(method, *args, &block) } else - yield + perform_method(method, *args, &block) end # outdated statistics + a new index can hurt performance of existing queries if StrongMigrations.auto_analyze && direction == :up && method == :add_index adapter.analyze_table(args[0]) end result end + def perform_method(method, *args) + if StrongMigrations.remove_invalid_indexes && direction == :up && method == :add_index && postgresql? + remove_invalid_index_if_needed(*args) + end + yield + end + def retry_lock_timeouts(check_committed: false) retries = 0 begin yield rescue ActiveRecord::LockWaitTimeout => e @@ -127,12 +143,26 @@ def version_safe? version && version <= StrongMigrations.start_after end + def skip? + StrongMigrations.skipped_databases.map(&:to_s).include?(db_config_name) + end + private + def check_adapter + return if defined?(@adapter_checked) + + if adapter.instance_of?(Adapters::AbstractAdapter) + warn "[strong_migrations] Unsupported adapter: #{connection.adapter_name}. Use StrongMigrations.skip_database(#{db_config_name.to_sym.inspect}) to silence this warning." + end + + @adapter_checked = true + end + def check_version_supported return if defined?(@version_checked) min_version = adapter.min_version if min_version @@ -198,14 +228,50 @@ def connection @migration.connection end + def db_config_name + connection.pool.db_config.name + end + def retry_lock_timeouts?(method) ( StrongMigrations.lock_timeout_retries > 0 && !in_transaction? && - method != :transaction + method != :transaction && + !@skip_retries ) + end + + def without_retries + previous_value = @skip_retries + begin + @skip_retries = true + yield + ensure + @skip_retries = previous_value + end + end + + # REINDEX INDEX CONCURRENTLY leaves a new invalid index if it fails, so use remove_index instead + def remove_invalid_index_if_needed(*args) + options = args.extract_options! + + # ensures has same options as existing index + # check args to avoid errors with index_exists? + return unless args.size == 2 && connection.index_exists?(*args, **options.merge(valid: false)) + + table, columns = args + index_name = options.fetch(:name, connection.index_name(table, columns)) + + # valid option is ignored for Active Record < 7.1, so check name as well + return if ar_version < 7.1 && !adapter.index_invalid?(table, index_name) + + @migration.say("Attempting to remove invalid index") + without_retries do + # TODO pass index schema for extra safety? + @migration.remove_index(table, **{name: index_name}.merge(options.slice(:algorithm))) + end end end end