Skip to content

Commit d2ba518

Browse files
committed
Add bulk versions of all constraint management for Postgres
1 parent 20444dd commit d2ba518

File tree

3 files changed

+113
-4
lines changed

3 files changed

+113
-4
lines changed

activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,8 @@ def exclusion_constraint(*args)
311311
# t.remove_exclusion_constraint(name: "price_check")
312312
#
313313
# See {connection.remove_exclusion_constraint}[rdoc-ref:SchemaStatements#remove_exclusion_constraint]
314-
def remove_exclusion_constraint(*args)
315-
@base.remove_exclusion_constraint(name, *args)
314+
def remove_exclusion_constraint(*args, **options)
315+
@base.remove_exclusion_constraint(name, *args, **options)
316316
end
317317

318318
# Adds a unique constraint.
@@ -329,8 +329,8 @@ def unique_constraint(*args)
329329
# t.remove_unique_constraint(name: "unique_position")
330330
#
331331
# See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint]
332-
def remove_unique_constraint(*args)
333-
@base.remove_unique_constraint(name, *args)
332+
def remove_unique_constraint(*args, **options)
333+
@base.remove_unique_constraint(name, *args, **options)
334334
end
335335
end
336336

activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,76 @@ def change_column_null_for_alter(table_name, column_name, null, default = nil)
10101010
end
10111011
end
10121012

1013+
def add_foreign_key_for_alter(from_table, to_table, **options)
1014+
assert_valid_deferrable(options[:deferrable])
1015+
return unless use_foreign_keys?
1016+
1017+
return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
1018+
1019+
options = foreign_key_options(from_table, to_table, options)
1020+
td = create_table_definition from_table
1021+
fk = td.new_foreign_key_definition to_table, options
1022+
1023+
"ADD #{schema_creation.accept(fk)}"
1024+
end
1025+
1026+
def remove_foreign_key_for_alter(from_table, to_table = nil, **options)
1027+
return unless use_foreign_keys?
1028+
return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
1029+
1030+
fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1031+
remove_constraint_for_alter(from_table, fk_name_to_delete, **options)
1032+
end
1033+
1034+
def add_check_constraint_for_alter(table_name, expression, if_not_exists: false, **options)
1035+
options = check_constraint_options(table_name, expression, options)
1036+
return if if_not_exists && check_constraint_exists?(table_name, **options)
1037+
1038+
td = create_table_definition(table_name)
1039+
constraint = td.new_check_constraint_definition(expression, options)
1040+
1041+
"ADD #{schema_creation.accept(constraint)}"
1042+
end
1043+
1044+
def remove_constraint_for_alter(table_name, constraint, if_exists: false, **options)
1045+
"DROP CONSTRAINT#{' IF EXISTS' if if_exists} #{quote_column_name(constraint)}"
1046+
end
1047+
1048+
def remove_check_constraint_for_alter(table_name, expression = nil, **options)
1049+
chk_name_to_delete = check_constraint_name(table_name, expression: expression, **options)
1050+
1051+
remove_constraint_for_alter(table_name, chk_name_to_delete, **options)
1052+
end
1053+
1054+
def add_exclusion_constraint_for_alter(table_name, expression, options)
1055+
options = exclusion_constraint_options(table_name, expression, options)
1056+
td = create_table_definition(table_name)
1057+
constraint = td.new_exclusion_constraint_definition(expression, **options)
1058+
1059+
"ADD #{schema_creation.accept(constraint)}"
1060+
end
1061+
1062+
def remove_exclusion_constraint_for_alter(table_name, expression = nil, **options)
1063+
excl_name_to_delete = exclusion_constraint_name(table_name, expression: expression, **options)
1064+
1065+
remove_constraint_for_alter(table_name, excl_name_to_delete, **options)
1066+
end
1067+
1068+
def add_unique_constraint_for_alter(table_name, column_name, options = {})
1069+
options = unique_constraint_options(table_name, column_name, options)
1070+
1071+
td = create_table_definition(table_name)
1072+
constraint = td.new_unique_constraint_definition(column_name, options)
1073+
1074+
"ADD #{schema_creation.accept(constraint)}"
1075+
end
1076+
1077+
def remove_unique_constraint_for_alter(table_name, column_name = nil, **options)
1078+
unique_name_to_delete = unique_constraint_name(table_name, column: column_name, **options)
1079+
1080+
remove_constraint_for_alter(table_name, unique_name_to_delete, **options)
1081+
end
1082+
10131083
def add_index_opclass(quoted_columns, **options)
10141084
opclasses = options_for_index_columns(options[:opclass])
10151085
quoted_columns.each do |name, column|

activerecord/test/cases/migration_test.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,13 +1214,15 @@ def test_add_and_remove_index
12141214
class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
12151215
def setup
12161216
@connection = Person.lease_connection
1217+
@connection.create_table(:delete_me2, force: true) { |t| }
12171218
@connection.create_table(:delete_me, force: true) { |t| }
12181219
Person.reset_column_information
12191220
Person.reset_sequence_name
12201221
end
12211222

12221223
teardown do
12231224
Person.lease_connection.drop_table(:delete_me) rescue nil
1225+
Person.lease_connection.drop_table(:delete_me2) rescue nil
12241226
end
12251227

12261228
def test_adding_multiple_columns
@@ -1485,6 +1487,43 @@ def test_updating_auto_increment
14851487
end
14861488
end
14871489

1490+
if current_adapter?(:PostgreSQLAdapter)
1491+
def test_constraints
1492+
conn = Person.lease_connection
1493+
1494+
conn.add_reference :delete_me, :delete_me2
1495+
1496+
assert_queries_count(1, include_schema: true) do
1497+
with_bulk_change_table do |t|
1498+
t.foreign_key :delete_me2
1499+
t.check_constraint "id IS NOT NULL", name: "id_chk"
1500+
t.exclusion_constraint "id WITH =", name: "id_exclusion"
1501+
t.unique_constraint "id", name: "id_uniq"
1502+
end
1503+
end
1504+
1505+
assert conn.foreign_key_exists?(:delete_me, :delete_me2)
1506+
assert conn.check_constraint_exists?(:delete_me, name: "id_chk")
1507+
assert conn.exclusion_constraints(:delete_me).any? { |c| c.name == "id_exclusion" }
1508+
assert conn.unique_constraints(:delete_me).any? { |c| c.name == "id_uniq" }
1509+
1510+
assert_queries_count(2, include_schema: true) do # one extra query to find the FK name
1511+
with_bulk_change_table do |t|
1512+
t.remove_foreign_key :delete_me2
1513+
t.remove_check_constraint name: "id_chk"
1514+
t.remove_exclusion_constraint name: "id_exclusion"
1515+
t.remove_unique_constraint name: "id_uniq", if_exists: true
1516+
t.remove_unique_constraint name: "non_existinent", if_exists: true
1517+
end
1518+
end
1519+
1520+
assert_not conn.foreign_key_exists?(:delete_me, :delete_me2)
1521+
assert_not conn.check_constraint_exists?(:delete_me, name: "id_chk")
1522+
assert_not conn.exclusion_constraints(:delete_me).any? { |c| c.name == "id_exclusion" }
1523+
assert_not conn.unique_constraints(:delete_me).any? { |c| c.name == "id_uniq" }
1524+
end
1525+
end
1526+
14881527
def test_changing_index
14891528
with_bulk_change_table do |t|
14901529
t.string :username

0 commit comments

Comments
 (0)