Commit fb43f374 authored by charlieablett's avatar charlieablett

Skip validation for concurrent foreign key

Add `validate: false` option for adding concurrent foreign key.
This allows the table lock to be held for a minimal amount of time
while still performing the checks that `add_concurrent_foreign_key`
performs for adding a foreign key to a table that may contain invalid
data, allowing us to put off any validation to a different time.
parent 3cdd5ddb
......@@ -158,7 +158,7 @@ module Gitlab
# name - The name of the foreign key.
#
# rubocop:disable Gitlab/RailsLogger
def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, name: nil)
def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, name: nil, validate: true)
# Transactions would result in ALTER TABLE locks being held for the
# duration of the transaction, defeating the purpose of this method.
if transaction_open?
......@@ -197,10 +197,14 @@ module Gitlab
# Validate the existing constraint. This can potentially take a very
# long time to complete, but fortunately does not lock the source table
# while running.
# Disable this check by passing `validate: false` to the method call
#
# Note this is a no-op in case the constraint is VALID already
disable_statement_timeout do
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{options[:name]};")
if validate
disable_statement_timeout do
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{options[:name]};")
end
end
end
# rubocop:enable Gitlab/RailsLogger
......
......@@ -325,6 +325,25 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
describe 'validate option' do
args = [:projects, :users]
options = { column: :user_id, on_delete: nil }
context 'when validate is supplied with a falsey value' do
it_behaves_like 'skips validation', args, options.merge(validate: false)
it_behaves_like 'skips validation', args, options.merge(validate: nil)
end
context 'when validate is supplied with a truthy value' do
it_behaves_like 'performs validation', args, options.merge(validate: true)
it_behaves_like 'performs validation', args, options.merge(validate: :yes)
end
context 'when validate is not supplied' do
it_behaves_like 'performs validation', args, options
end
end
end
end
......
# frozen_string_literal: true
shared_examples 'skips validation' do |args, options|
it 'skips validation' do
expect(model).not_to receive(:disable_statement_timeout)
expect(model).to receive(:execute).with(/ADD CONSTRAINT/)
expect(model).not_to receive(:execute).with(/VALIDATE CONSTRAINT/)
model.add_concurrent_foreign_key(*args, **options)
end
end
shared_examples 'performs validation' do |args, options|
it 'performs validation' do
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(/NOT VALID/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
expect(model).to receive(:execute).with(/RESET ALL/)
model.add_concurrent_foreign_key(*args, **options)
end
end
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