Skip to content

Commit be9e6f8

Browse files
Merge pull request #141 from doctolib/explicitly-ensure-indexes-removed
Added new check to ensure composite indexes are dropped ahead of removal of column
2 parents e0f008b + c800962 commit be9e6f8

File tree

6 files changed

+83
-4
lines changed

6 files changed

+83
-4
lines changed

lib/safe-pg-migrations/base.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
require 'safe-pg-migrations/plugins/blocking_activity_logger'
1212
require 'safe-pg-migrations/plugins/statement_insurer/add_column'
1313
require 'safe-pg-migrations/plugins/statement_insurer/change_column_null'
14+
require 'safe-pg-migrations/plugins/statement_insurer/remove_column_index'
1415
require 'safe-pg-migrations/plugins/statement_insurer'
1516
require 'safe-pg-migrations/plugins/statement_retrier'
1617
require 'safe-pg-migrations/plugins/idempotent_statements'

lib/safe-pg-migrations/plugins/statement_insurer.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module StatementInsurer
55
include Helpers::SessionSettingManagement
66
include AddColumn
77
include ChangeColumnNull
8+
include RemoveColumnIndex
89

910
def validate_check_constraint(table_name, **options)
1011
Helpers::Logger.say_method_call :validate_check_constraint, table_name, **options
@@ -19,7 +20,7 @@ def add_check_constraint(table_name, expression, **options)
1920

2021
Helpers::Logger.say_method_call :add_check_constraint, table_name, expression, **options,
2122
validate: false
22-
super table_name, expression, **options, validate: false
23+
super(table_name, expression, **options, validate: false)
2324

2425
return unless options.fetch(:validate, true)
2526

@@ -74,6 +75,7 @@ def remove_column(table_name, column_name, type = nil, **options)
7475
foreign_key = foreign_key_for(table_name, column: column_name)
7576

7677
remove_foreign_key(table_name, name: foreign_key.name) if foreign_key
78+
remove_column_with_composite_index(table_name, column_name)
7779
super
7880
end
7981

lib/safe-pg-migrations/plugins/statement_insurer/add_column.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def add_column(table_name, column_name, type, **options)
2121
null = options.delete(:null)
2222

2323
Helpers::Logger.say_method_call(:add_column, table_name, column_name, type, options)
24-
super table_name, column_name, type, **options
24+
super(table_name, column_name, type, **options)
2525

2626
Helpers::Logger.say_method_call(:change_column_default, table_name, column_name, default)
2727
change_column_default(table_name, column_name, default)

lib/safe-pg-migrations/plugins/statement_insurer/change_column_null.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def change_column_null(table_name, column_name, null, default = nil)
1616
add_check_constraint table_name, expression, name: constraint_name
1717

1818
Helpers::Logger.say_method_call :change_column_null, table_name, column_name, false
19-
super table_name, column_name, false
19+
super(table_name, column_name, false)
2020

2121
return unless should_remove_constraint? default_name, constraint_name
2222

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
3+
module SafePgMigrations
4+
module StatementInsurer
5+
module RemoveColumnIndex
6+
def remove_column_with_composite_index(table, column)
7+
existing_indexes = indexes(table).select { |index|
8+
index.columns.size > 1 && index.columns.include?(column.to_s)
9+
}
10+
11+
return unless existing_indexes.any?
12+
13+
error_message = <<~ERROR
14+
Cannot drop column #{column} from table #{table} because composite index(es): #{existing_indexes.map(&:name).join(', ')} is/are present.
15+
If they are still required, create the index(es) without #{column} before dropping the existing index(es).
16+
Then you will be able to drop the column.
17+
ERROR
18+
19+
raise StandardError, error_message
20+
end
21+
end
22+
end
23+
end

test/StatementInsurer/remove_column_test.rb

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def change
4141
assert_equal ['ALTER TABLE "users" DROP COLUMN "name"'], calls[2]
4242
end
4343

44-
def test_can_remove_column_without_foreign_key
44+
def test_can_remove_column_without_foreign_key_or_index
4545
@connection.create_table(:users) { |t| t.string :name }
4646

4747
@migration =
@@ -55,5 +55,58 @@ def change
5555

5656
assert_equal ['ALTER TABLE "users" DROP COLUMN "name"'], calls[2]
5757
end
58+
59+
def test_can_remove_column_with_index_on_other_columns
60+
@connection.create_table(:users) { |t| t.string :name, :email }
61+
@connection.add_index(:users, :email)
62+
63+
@migration =
64+
Class.new(ActiveRecord::Migration::Current) do
65+
def change
66+
remove_column(:users, :name)
67+
end
68+
end.new
69+
70+
calls = record_calls(@connection, :execute) { run_migration }
71+
72+
assert_equal ['ALTER TABLE "users" DROP COLUMN "name"'], calls[2]
73+
end
74+
75+
def test_can_remove_column_with_dependent_index
76+
@connection.create_table(:users) { |t| t.string :name, :email }
77+
@connection.add_index(:users, :name)
78+
79+
@migration =
80+
Class.new(ActiveRecord::Migration::Current) do
81+
def change
82+
remove_column(:users, :name)
83+
end
84+
end.new
85+
86+
calls = record_calls(@connection, :execute) { run_migration }
87+
88+
assert_equal ['ALTER TABLE "users" DROP COLUMN "name"'], calls[2]
89+
end
90+
91+
def test_can_not_remove_column_with_dependent_composite_index
92+
@connection.create_table(:users) { |t| t.string :name, :email }
93+
@connection.add_index(:users, %i[name email], name: 'index_users_on_name_and_email')
94+
95+
@migration =
96+
Class.new(ActiveRecord::Migration::Current) do
97+
def change
98+
remove_column(:users, :name)
99+
end
100+
end.new
101+
102+
error_message = <<~ERROR
103+
Cannot drop column name from table users because composite index(es): index_users_on_name_and_email is/are present.
104+
If they are still required, create the index(es) without name before dropping the existing index(es).
105+
Then you will be able to drop the column.
106+
ERROR
107+
108+
exception = assert_raises(StandardError, error_message) { run_migration }
109+
assert_match error_message, exception.message
110+
end
58111
end
59112
end

0 commit comments

Comments
 (0)