Skip to content

Commit ef75e63

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 60b4e6d commit ef75e63

File tree

1 file changed

+39
-33
lines changed

1 file changed

+39
-33
lines changed

lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,29 @@ def check_all_foreign_keys_valid!
3636
def disable_referential_integrity
3737
foreign_keys = all_foreign_keys
3838

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)
3962
statements = foreign_keys.map do |foreign_key|
4063
# We do not use the `#remove_foreign_key` method here because it
4164
# checks for foreign keys existance in the schema cache. This method
@@ -46,45 +69,28 @@ def disable_referential_integrity
4669
schema_creation.accept(at)
4770
end
4871
execute_batch(statements, "Disable referential integrity -> remove foreign keys")
72+
end
4973

50-
yield
51-
52-
# Prefixes and suffixes are added in add_foreign_key
53-
# in AR7+ so we need to temporarily disable them here,
54-
# otherwise prefixes/suffixes will be erroneously added.
55-
old_prefix = ActiveRecord::Base.table_name_prefix
56-
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] }
5784

58-
ActiveRecord::Base.table_name_prefix = ""
59-
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
6088

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

86-
private
87-
8894
# NOTE: Copy/paste of the `#foreign_keys(table)` method adapted
8995
# to return every single foreign key in the database.
9096
def all_foreign_keys

0 commit comments

Comments
 (0)