Skip to content

Commit 6ee8245

Browse files
committed
fix(referential-integrity): make sure fks are added back
This would silently fail in the test `test_bulk_insert_with_a_multi_statement_query_raises_an_exception_when_any_insert_fails`. Removing some foreign keys and thus failing afterwards in the test suite. Moreover, the `#disable_referential_integrity` method is unfortunately public, so we need it to be robust.
1 parent 8411a08 commit 6ee8245

File tree

1 file changed

+39
-43
lines changed

1 file changed

+39
-43
lines changed

lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,30 @@ def check_all_foreign_keys_valid!
3535

3636
def disable_referential_integrity
3737
foreign_keys = all_foreign_keys
38-
dropped_fks = foreign_keys
3938

39+
remove_foreign_keys(foreign_keys)
40+
41+
# Prefixes and suffixes are added in add_foreign_key
42+
# in AR7+ so we need to temporarily disable them here,
43+
# otherwise prefixes/suffixes will be erroneously added.
44+
old_prefix = ActiveRecord::Base.table_name_prefix
45+
old_suffix = ActiveRecord::Base.table_name_suffix
46+
47+
begin
48+
yield
49+
ensure
50+
ActiveRecord::Base.table_name_prefix = ""
51+
ActiveRecord::Base.table_name_suffix = ""
52+
add_foreign_keys(foreign_keys) # Never raises.
53+
54+
ActiveRecord::Base.table_name_prefix = old_prefix if defined?(old_prefix)
55+
ActiveRecord::Base.table_name_suffix = old_suffix if defined?(old_suffix)
56+
end
57+
end
58+
59+
private
60+
61+
def remove_foreign_keys(foreign_keys)
4062
statements = foreign_keys.map do |foreign_key|
4163
# We do not use the `#remove_foreign_key` method here because it
4264
# checks for foreign keys existance in the schema cache. This method
@@ -47,54 +69,28 @@ def disable_referential_integrity
4769
schema_creation.accept(at)
4870
end
4971
execute_batch(statements, "Disable referential integrity -> remove foreign keys")
72+
end
5073

51-
STDOUT.puts "BEFORE FK"
52-
yield
53-
STDOUT.puts "AFTER FK"
54-
55-
# Prefixes and suffixes are added in add_foreign_key
56-
# in AR7+ so we need to temporarily disable them here,
57-
# otherwise prefixes/suffixes will be erroneously added.
58-
old_prefix = ActiveRecord::Base.table_name_prefix
59-
old_suffix = ActiveRecord::Base.table_name_suffix
74+
# NOTE: This method should never raise, otherwise we risk polluting table name
75+
# prefixes and suffixes. The good thing is: if this happens, tests will crash
76+
# hard, no way we miss it.
77+
def add_foreign_keys(foreign_keys)
78+
# We avoid using `foreign_key_exists?` here because it checks the schema cache
79+
# for every key. This method is performance critical for the test suite, hence
80+
# we use the `#all_foreign_keys` method that only make one query to the database.
81+
already_inserted_foreign_keys = all_foreign_keys
82+
statements = foreign_keys.map do |foreign_key|
83+
next if already_inserted_foreign_keys.any? { |fk| fk.from_table == foreign_key.from_table && fk.options[:name] == foreign_key.options[:name] }
6084

61-
ActiveRecord::Base.table_name_prefix = ""
62-
ActiveRecord::Base.table_name_suffix = ""
85+
options = foreign_key_options(foreign_key.from_table, foreign_key.to_table, foreign_key.options)
86+
at = create_alter_table foreign_key.from_table
87+
at.add_foreign_key foreign_key.to_table, options
6388

64-
begin
65-
# Avoid having PG:DuplicateObject error if a test is ran in transaction.
66-
# TODO: verify that there is no cache issue related to running this (e.g: fk
67-
# still in cache but not in db)
68-
#
69-
# We avoid using `foreign_key_exists?` here because it checks the schema cache
70-
# for every key. This method is performance critical for the test suite, hence
71-
# we use the `#all_foreign_keys` method that only make one query to the database.
72-
already_inserted_foreign_keys = all_foreign_keys
73-
new_fks = []
74-
statements = foreign_keys.map do |foreign_key|
75-
next if already_inserted_foreign_keys.any? { |fk| fk.from_table == foreign_key.from_table && fk.options[:name] == foreign_key.options[:name] }
76-
77-
options = foreign_key_options(foreign_key.from_table, foreign_key.to_table, foreign_key.options)
78-
at = create_alter_table foreign_key.from_table
79-
at.add_foreign_key foreign_key.to_table, options
80-
81-
new_fks << foreign_key
82-
schema_creation.accept(at)
83-
end
84-
execute_batch(statements.compact, "Disable referential integrity -> add foreign keys")
85-
86-
new_fks = new_fks.map { _1.from_table + " -> " + _1.to_table + (_1.options[:name] ? " (#{_1.options[:name]})" : "") }
87-
dropped_fks = dropped_fks.map { _1.from_table + " -> " + _1.to_table + (_1.options[:name] ? " (#{_1.options[:name]})" : "") }
88-
STDOUT.puts "FK DIFF NEW:\n", (new_fks - dropped_fks).map{ "\t#{_1}" } if (new_fks - dropped_fks).any?
89-
STDOUT.puts "FK DIFF OLD:\n", (dropped_fks - new_fks).map{ "\t#{_1}" } if (dropped_fks - new_fks).any?
90-
ensure
91-
ActiveRecord::Base.table_name_prefix = old_prefix
92-
ActiveRecord::Base.table_name_suffix = old_suffix
89+
schema_creation.accept(at)
9390
end
91+
execute_batch(statements.compact, "Disable referential integrity -> add foreign keys")
9492
end
9593

96-
private
97-
9894
# NOTE: Copy/paste of the `#foreign_keys(table)` method adapted
9995
# to return every single foreign key in the database.
10096
def all_foreign_keys

0 commit comments

Comments
 (0)