Commit c6f14728 authored by manojmj's avatar manojmj

Allow adding foreign key with a different name for the same column

parent 3c86cc65
...@@ -164,19 +164,25 @@ module Gitlab ...@@ -164,19 +164,25 @@ module Gitlab
raise 'add_concurrent_foreign_key can not be run inside a transaction' raise 'add_concurrent_foreign_key can not be run inside a transaction'
end end
on_delete = 'SET NULL' if on_delete == :nullify if name
key_name = name
key_name = name || concurrent_foreign_key_name(source, column) foreign_key_exists = foreign_key_exists?(source, target, name: name)
else
key_name = concurrent_foreign_key_name(source, column)
foreign_key_exists = foreign_key_exists?(source, target, column: column)
end
unless foreign_key_exists?(source, target, column: column) if foreign_key_exists
Rails.logger.warn "Foreign key not created because it exists already " \ Rails.logger.warn "Foreign key not created because it exists already " \
"(this may be due to an aborted migration or similar): " \ "(this may be due to an aborted migration or similar): " \
"source: #{source}, target: #{target}, column: #{column}" "source: #{source}, target: #{target}, column: #{column}"
else
# Using NOT VALID allows us to create a key without immediately # Using NOT VALID allows us to create a key without immediately
# validating it. This means we keep the ALTER TABLE lock only for a # validating it. This means we keep the ALTER TABLE lock only for a
# short period of time. The key _is_ enforced for any newly created # short period of time. The key _is_ enforced for any newly created
# data. # data.
on_delete = 'SET NULL' if on_delete == :nullify
execute <<-EOF.strip_heredoc execute <<-EOF.strip_heredoc
ALTER TABLE #{source} ALTER TABLE #{source}
ADD CONSTRAINT #{key_name} ADD CONSTRAINT #{key_name}
...@@ -198,10 +204,12 @@ module Gitlab ...@@ -198,10 +204,12 @@ module Gitlab
end end
# rubocop:enable Gitlab/RailsLogger # rubocop:enable Gitlab/RailsLogger
def foreign_key_exists?(source, target = nil, column: nil) def foreign_key_exists?(source, target = nil, column: nil, name: nil)
foreign_keys(source).any? do |key| foreign_keys(source).any? do |key|
if column if column
key.options[:column].to_s == column.to_s key.options[:column].to_s == column.to_s
elsif name
key.options[:name].to_s == name.to_s
else else
key.to_table.to_s == target.to_s key.to_table.to_s == target.to_s
end end
......
...@@ -212,6 +212,7 @@ describe Gitlab::Database::MigrationHelpers do ...@@ -212,6 +212,7 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:transaction_open?).and_return(false) allow(model).to receive(:transaction_open?).and_return(false)
end end
context 'when no custom key name is supplied' do
it 'creates a concurrent foreign key and validates it' do it 'creates a concurrent foreign key and validates it' do
expect(model).to receive(:disable_statement_timeout).and_call_original expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).with(/statement_timeout/)
...@@ -241,8 +242,11 @@ describe Gitlab::Database::MigrationHelpers do ...@@ -241,8 +242,11 @@ describe Gitlab::Database::MigrationHelpers do
model.add_concurrent_foreign_key(:projects, :users, column: :user_id) model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
end end
end
it 'allows the use of a custom key name' do context 'when a custom key name is supplied' do
context 'for creating a new foreign key for a column that does not presently exist' do
it 'creates a new foreign key' do
expect(model).to receive(:disable_statement_timeout).and_call_original expect(model).to receive(:disable_statement_timeout).and_call_original
expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).with(/statement_timeout/)
expect(model).to receive(:execute).ordered.with(/NOT VALID/) expect(model).to receive(:execute).ordered.with(/NOT VALID/)
...@@ -252,6 +256,33 @@ describe Gitlab::Database::MigrationHelpers do ...@@ -252,6 +256,33 @@ describe Gitlab::Database::MigrationHelpers do
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo) model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
end end
end end
context 'for creating a duplicate foreign key for a column that presently exists' do
context 'when the supplied key name is the same as the existing foreign key name' do
it 'does not create a new foreign key' do
expect(model).to receive(:foreign_key_exists?).with(:projects, :users, name: :foo).and_return(true)
expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/)
expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/)
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
end
end
context 'when the supplied key name is different from the existing foreign key name' do
it 'creates a new foreign key' 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.+bar/)
expect(model).to receive(:execute).with(/RESET ALL/)
model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :bar)
end
end
end
end
end
end end
describe '#concurrent_foreign_key_name' do describe '#concurrent_foreign_key_name' do
...@@ -266,7 +297,7 @@ describe Gitlab::Database::MigrationHelpers do ...@@ -266,7 +297,7 @@ describe Gitlab::Database::MigrationHelpers do
describe '#foreign_key_exists?' do describe '#foreign_key_exists?' do
before do before do
key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(:projects, :users, { column: :non_standard_id }) key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(:projects, :users, { column: :non_standard_id, name: :fk_projects_users_non_standard_id })
allow(model).to receive(:foreign_keys).with(:projects).and_return([key]) allow(model).to receive(:foreign_keys).with(:projects).and_return([key])
end end
...@@ -274,6 +305,10 @@ describe Gitlab::Database::MigrationHelpers do ...@@ -274,6 +305,10 @@ describe Gitlab::Database::MigrationHelpers do
expect(model.foreign_key_exists?(:projects, :users, column: :non_standard_id)).to be_truthy expect(model.foreign_key_exists?(:projects, :users, column: :non_standard_id)).to be_truthy
end end
it 'finds existing foreign keys by name' do
expect(model.foreign_key_exists?(:projects, :users, name: :fk_projects_users_non_standard_id)).to be_truthy
end
it 'finds existing foreign keys by target table only' do it 'finds existing foreign keys by target table only' do
expect(model.foreign_key_exists?(:projects, :users)).to be_truthy expect(model.foreign_key_exists?(:projects, :users)).to be_truthy
end end
...@@ -282,6 +317,10 @@ describe Gitlab::Database::MigrationHelpers do ...@@ -282,6 +317,10 @@ describe Gitlab::Database::MigrationHelpers do
expect(model.foreign_key_exists?(:projects, :users, column: :user_id)).to be_falsey expect(model.foreign_key_exists?(:projects, :users, column: :user_id)).to be_falsey
end end
it 'compares by foreign key name if given' do
expect(model.foreign_key_exists?(:projects, :users, name: :non_existent_foreign_key_name)).to be_falsey
end
it 'compares by target if no column given' do it 'compares by target if no column given' do
expect(model.foreign_key_exists?(:projects, :other_table)).to be_falsey expect(model.foreign_key_exists?(:projects, :other_table)).to be_falsey
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