diff --git a/lib/strong_migrations/adapters/postgresql_adapter.rb b/lib/strong_migrations/adapters/postgresql_adapter.rb index 3cd0b7a..a7c6640 100644 --- a/lib/strong_migrations/adapters/postgresql_adapter.rb +++ b/lib/strong_migrations/adapters/postgresql_adapter.rb @@ -169,6 +169,22 @@ def default_volatile?(default) rows.empty? || rows.any? { |r| r["provolatile"] == "v" } end + def invalid_index?(index_name) + query = <<~SQL + SELECT + pg_class.relname + FROM + pg_class + INNER JOIN + pg_index ON pg_index.indexrelid = pg_class.oid + WHERE + pg_index.indisvalid = false AND + pg_class.relname = #{connection.quote(index_name)} + SQL + + select_all(query.squish).any? + end + private def set_timeout(setting, timeout) diff --git a/lib/strong_migrations/safe_methods.rb b/lib/strong_migrations/safe_methods.rb index d7379d7..d01174f 100644 --- a/lib/strong_migrations/safe_methods.rb +++ b/lib/strong_migrations/safe_methods.rb @@ -4,10 +4,15 @@ def safe_by_default_method?(method) StrongMigrations.safe_by_default && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method) end - # TODO check if invalid index with expected name exists and remove if needed - def safe_add_index(*args, **options) + def safe_add_index(table, columns, **options) + index_name = options.fetch(:name, connection.index_name(table, columns)) + if postgresql? && adapter.invalid_index?(index_name) + safe_remove_index(table, columns, **options) + end + disable_transaction - @migration.add_index(*args, **options.merge(algorithm: :concurrently)) + + @migration.add_index(table, columns, **options.merge(algorithm: :concurrently)) end def safe_remove_index(*args, **options) @@ -124,5 +129,11 @@ def disable_transaction def in_transaction? @migration.connection.open_transactions > 0 end + + private + + def postgresql? + adapter.instance_of?(Adapters::PostgreSQLAdapter) + end end end diff --git a/test/migrations/add_index.rb b/test/migrations/add_index.rb index 0aa11c3..8641aff 100644 --- a/test/migrations/add_index.rb +++ b/test/migrations/add_index.rb @@ -59,6 +59,12 @@ def change end end +class SafeAddIndexColumnsUnique < TestMigration + def change + add_index :users, :name, unique: true + end +end + class AddIndexName < TestMigration def change add_index :users, :name, name: "my_index" diff --git a/test/safe_by_default_test.rb b/test/safe_by_default_test.rb index 7a90552..f6de5d8 100644 --- a/test/safe_by_default_test.rb +++ b/test/safe_by_default_test.rb @@ -152,4 +152,21 @@ def test_change_column_null_default ensure User.delete_all end + + def test_add_index_with_invalid_present + skip unless postgresql? + + User.create(name: 'same') + duplicate = User.create(name: 'same') + + assert_raises(ActiveRecord::RecordNotUnique) do + migrate SafeAddIndexColumnsUnique + end + + duplicate.delete + + assert_safe SafeAddIndexColumnsUnique + ensure + User.delete_all + end end