Commit 52fcf198 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'cablett-validate-existing-unvalidated-fk-constraint' into 'master'

Standalone foreign key validation

See merge request gitlab-org/gitlab!22463
parents 7fdeed5e 6ef7347b
...@@ -211,6 +211,18 @@ module Gitlab ...@@ -211,6 +211,18 @@ module Gitlab
end end
# rubocop:enable Gitlab/RailsLogger # rubocop:enable Gitlab/RailsLogger
def validate_foreign_key(source, column, name: nil)
fk_name = name || concurrent_foreign_key_name(source, column)
unless foreign_key_exists?(source, name: fk_name)
raise "cannot find #{fk_name} on #{source} table"
end
disable_statement_timeout do
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{fk_name};")
end
end
def foreign_key_exists?(source, target = nil, **options) def foreign_key_exists?(source, target = nil, **options)
foreign_keys(source).any? do |foreign_key| foreign_keys(source).any? do |foreign_key|
tables_match?(target.to_s, foreign_key.to_table.to_s) && tables_match?(target.to_s, foreign_key.to_table.to_s) &&
......
...@@ -347,6 +347,48 @@ describe Gitlab::Database::MigrationHelpers do ...@@ -347,6 +347,48 @@ describe Gitlab::Database::MigrationHelpers do
end end
end end
describe '#validate_foreign_key' do
context 'when name is provided' do
it 'does not infer the foreign key constraint name' do
expect(model).to receive(:foreign_key_exists?).with(:projects, name: :foo).and_return(true)
aggregate_failures do
expect(model).not_to receive(:concurrent_foreign_key_name)
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/ALTER TABLE projects VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
end
model.validate_foreign_key(:projects, :user_id, name: :foo)
end
end
context 'when name is not provided' do
it 'infers the foreign key constraint name' do
expect(model).to receive(:foreign_key_exists?).with(:projects, name: anything).and_return(true)
aggregate_failures do
expect(model).to receive(:concurrent_foreign_key_name)
expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/ALTER TABLE projects VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).ordered.with(/RESET ALL/)
end
model.validate_foreign_key(:projects, :user_id)
end
context 'when the inferred foreign key constraint does not exist' do
it 'raises an error' do
expect(model).to receive(:foreign_key_exists?).and_return(false)
expect { model.validate_foreign_key(:projects, :user_id) }.to raise_error(/cannot find/)
end
end
end
end
describe '#concurrent_foreign_key_name' do describe '#concurrent_foreign_key_name' do
it 'returns the name for a foreign key' do it 'returns the name for a foreign key' do
name = model.concurrent_foreign_key_name(:this_is_a_very_long_table_name, name = model.concurrent_foreign_key_name(:this_is_a_very_long_table_name,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment