Hash concurrent foreign key names similar to Rails

This was initially not implemented simply because I forgot about the
size limit of constraint names in PostgreSQL (63 bytes). Using the old
technique we can't add foreign keys for certain tables. For example,
adding a foreign key on
protected_branch_merge_access_levels.protected_branch_id would lead to
the following key name:

    fk_protected_branch_merge_access_levels_protected_branches_protected_branch_id

This key is 78 bytes long, thus violating the PostgreSQL size
requirements.

The hashing strategy is copied from Rails' foreign_key_name() method,
which unfortunately is private and subject to change without notice.
This commit is contained in:
Yorick Peterse 2017-02-21 15:07:02 +01:00
parent 459a97d468
commit 79696f5b7a
No known key found for this signature in database
GPG Key ID: EDD30D2BEB691AC9
2 changed files with 20 additions and 1 deletions

View File

@ -54,7 +54,7 @@ module Gitlab
disable_statement_timeout
key_name = "fk_#{source}_#{target}_#{column}"
key_name = concurrent_foreign_key_name(source, column)
# Using NOT VALID allows us to create a key without immediately
# validating it. This means we keep the ALTER TABLE lock only for a
@ -74,6 +74,15 @@ module Gitlab
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};")
end
# Returns the name for a concurrent foreign key.
#
# PostgreSQL constraint names have a limit of 63 bytes. The logic used
# here is based on Rails' foreign_key_name() method, which unfortunately
# is private so we can't rely on it directly.
def concurrent_foreign_key_name(table, column)
"fk_#{Digest::SHA256.hexdigest("#{table}_#{column}_fk").first(10)}"
end
# Long-running migrations may take more than the timeout allowed by
# the database. Disable the session's statement timeout to ensure
# migrations don't get killed prematurely. (PostgreSQL only)

View File

@ -101,6 +101,16 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
end
end
describe '#concurrent_foreign_key_name' do
it 'returns the name for a foreign key' do
name = model.concurrent_foreign_key_name(:this_is_a_very_long_table_name,
:with_a_very_long_column_name)
expect(name).to be_an_instance_of(String)
expect(name.length).to eq(13)
end
end
describe '#disable_statement_timeout' do
context 'using PostgreSQL' do
it 'disables statement timeouts' do