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