Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
c914883a
Commit
c914883a
authored
Mar 20, 2018
by
Andreas Brandl
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Shortcut concurrent foreign key creation if already exists.
Closes #43887.
parent
1362d9fe
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
92 additions
and
51 deletions
+92
-51
db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
...0530130129_project_foreign_keys_with_cascading_deletes.rb
+1
-7
db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
...rate/20170703102400_add_stage_id_foreign_key_to_builds.rb
+2
-10
db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb
...grate/20170713104829_add_foreign_key_to_merge_requests.rb
+2
-10
db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
...te/20180223124427_build_user_interacted_projects_table.rb
+5
-5
lib/gitlab/database/migration_helpers.rb
lib/gitlab/database/migration_helpers.rb
+37
-18
spec/lib/gitlab/database/migration_helpers_spec.rb
spec/lib/gitlab/database/migration_helpers_spec.rb
+45
-1
No files found.
db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
View file @
c914883a
...
@@ -154,7 +154,7 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration
...
@@ -154,7 +154,7 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration
end
end
def
add_foreign_key_if_not_exists
(
source
,
target
,
column
:)
def
add_foreign_key_if_not_exists
(
source
,
target
,
column
:)
return
if
foreign_key_exists?
(
source
,
column
)
return
if
foreign_key_exists?
(
source
,
target
,
column:
column
)
add_concurrent_foreign_key
(
source
,
target
,
column:
column
)
add_concurrent_foreign_key
(
source
,
target
,
column:
column
)
end
end
...
@@ -175,12 +175,6 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration
...
@@ -175,12 +175,6 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration
rescue
ArgumentError
rescue
ArgumentError
end
end
def
foreign_key_exists?
(
table
,
column
)
foreign_keys
(
table
).
any?
do
|
key
|
key
.
options
[
:column
]
==
column
.
to_s
end
end
def
connection
def
connection
# Rails memoizes connection objects, but this causes them to be shared
# Rails memoizes connection objects, but this causes them to be shared
# amongst threads; we don't want that.
# amongst threads; we don't want that.
...
...
db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
View file @
c914883a
...
@@ -10,13 +10,13 @@ class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
...
@@ -10,13 +10,13 @@ class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
add_concurrent_index
(
:ci_builds
,
:stage_id
)
add_concurrent_index
(
:ci_builds
,
:stage_id
)
end
end
unless
foreign_key_exists?
(
:ci_builds
,
:stage_id
)
unless
foreign_key_exists?
(
:ci_builds
,
:
ci_stages
,
column: :
stage_id
)
add_concurrent_foreign_key
(
:ci_builds
,
:ci_stages
,
column: :stage_id
,
on_delete: :cascade
)
add_concurrent_foreign_key
(
:ci_builds
,
:ci_stages
,
column: :stage_id
,
on_delete: :cascade
)
end
end
end
end
def
down
def
down
if
foreign_key_exists?
(
:ci_builds
,
:stage_id
)
if
foreign_key_exists?
(
:ci_builds
,
column:
:stage_id
)
remove_foreign_key
(
:ci_builds
,
column: :stage_id
)
remove_foreign_key
(
:ci_builds
,
column: :stage_id
)
end
end
...
@@ -24,12 +24,4 @@ class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
...
@@ -24,12 +24,4 @@ class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
remove_concurrent_index
(
:ci_builds
,
:stage_id
)
remove_concurrent_index
(
:ci_builds
,
:stage_id
)
end
end
end
end
private
def
foreign_key_exists?
(
table
,
column
)
foreign_keys
(
:ci_builds
).
any?
do
|
key
|
key
.
options
[
:column
]
==
column
.
to_s
end
end
end
end
db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb
View file @
c914883a
...
@@ -23,23 +23,15 @@ class AddForeignKeyToMergeRequests < ActiveRecord::Migration
...
@@ -23,23 +23,15 @@ class AddForeignKeyToMergeRequests < ActiveRecord::Migration
merge_requests
.
update_all
(
head_pipeline_id:
nil
)
merge_requests
.
update_all
(
head_pipeline_id:
nil
)
end
end
unless
foreign_key_exists?
(
:merge_requests
,
:head_pipeline_id
)
unless
foreign_key_exists?
(
:merge_requests
,
column:
:head_pipeline_id
)
add_concurrent_foreign_key
(
:merge_requests
,
:ci_pipelines
,
add_concurrent_foreign_key
(
:merge_requests
,
:ci_pipelines
,
column: :head_pipeline_id
,
on_delete: :nullify
)
column: :head_pipeline_id
,
on_delete: :nullify
)
end
end
end
end
def
down
def
down
if
foreign_key_exists?
(
:merge_requests
,
:head_pipeline_id
)
if
foreign_key_exists?
(
:merge_requests
,
column:
:head_pipeline_id
)
remove_foreign_key
(
:merge_requests
,
column: :head_pipeline_id
)
remove_foreign_key
(
:merge_requests
,
column: :head_pipeline_id
)
end
end
end
end
private
def
foreign_key_exists?
(
table
,
column
)
foreign_keys
(
table
).
any?
do
|
key
|
key
.
options
[
:column
]
==
column
.
to_s
end
end
end
end
db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
View file @
c914883a
...
@@ -26,11 +26,11 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
...
@@ -26,11 +26,11 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
def
down
def
down
execute
"TRUNCATE user_interacted_projects"
execute
"TRUNCATE user_interacted_projects"
if
foreign_key_exists?
(
:user_interacted_projects
,
:user
_id
)
if
foreign_key_exists?
(
:user_interacted_projects
,
:user
s
)
remove_foreign_key
:user_interacted_projects
,
:users
remove_foreign_key
:user_interacted_projects
,
:users
end
end
if
foreign_key_exists?
(
:user_interacted_projects
,
:project
_id
)
if
foreign_key_exists?
(
:user_interacted_projects
,
:project
s
)
remove_foreign_key
:user_interacted_projects
,
:projects
remove_foreign_key
:user_interacted_projects
,
:projects
end
end
...
@@ -115,7 +115,7 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
...
@@ -115,7 +115,7 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
end
end
def
create_fk
(
table
,
target
,
column
)
def
create_fk
(
table
,
target
,
column
)
return
if
foreign_key_exists?
(
table
,
column
)
return
if
foreign_key_exists?
(
table
,
target
,
column:
column
)
add_foreign_key
table
,
target
,
column:
column
,
on_delete: :cascade
add_foreign_key
table
,
target
,
column:
column
,
on_delete: :cascade
end
end
...
@@ -158,11 +158,11 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
...
@@ -158,11 +158,11 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration
add_concurrent_index
:user_interacted_projects
,
[
:project_id
,
:user_id
],
unique:
true
,
name:
UNIQUE_INDEX_NAME
add_concurrent_index
:user_interacted_projects
,
[
:project_id
,
:user_id
],
unique:
true
,
name:
UNIQUE_INDEX_NAME
end
end
unless
foreign_key_exists?
(
:user_interacted_projects
,
:user_id
)
unless
foreign_key_exists?
(
:user_interacted_projects
,
:user
s
,
column: :user
_id
)
add_concurrent_foreign_key
:user_interacted_projects
,
:users
,
column: :user_id
,
on_delete: :cascade
add_concurrent_foreign_key
:user_interacted_projects
,
:users
,
column: :user_id
,
on_delete: :cascade
end
end
unless
foreign_key_exists?
(
:user_interacted_projects
,
:project_id
)
unless
foreign_key_exists?
(
:user_interacted_projects
,
:project
s
,
column: :project
_id
)
add_concurrent_foreign_key
:user_interacted_projects
,
:projects
,
column: :project_id
,
on_delete: :cascade
add_concurrent_foreign_key
:user_interacted_projects
,
:projects
,
column: :project_id
,
on_delete: :cascade
end
end
end
end
...
...
lib/gitlab/database/migration_helpers.rb
View file @
c914883a
...
@@ -155,6 +155,13 @@ module Gitlab
...
@@ -155,6 +155,13 @@ module Gitlab
# of PostgreSQL's "VALIDATE CONSTRAINT". As a result we'll just fall
# of PostgreSQL's "VALIDATE CONSTRAINT". As a result we'll just fall
# back to the normal foreign key procedure.
# back to the normal foreign key procedure.
if
Database
.
mysql?
if
Database
.
mysql?
if
foreign_key_exists?
(
source
,
target
,
column:
column
)
Rails
.
logger
.
warn
"Foreign key not created because it exists already "
\
"(this may be due to an aborted migration or similar): "
\
"source:
#{
source
}
, target:
#{
target
}
, column:
#{
column
}
"
return
end
return
add_foreign_key
(
source
,
target
,
return
add_foreign_key
(
source
,
target
,
column:
column
,
column:
column
,
on_delete:
on_delete
)
on_delete:
on_delete
)
...
@@ -166,25 +173,43 @@ module Gitlab
...
@@ -166,25 +173,43 @@ module Gitlab
key_name
=
concurrent_foreign_key_name
(
source
,
column
)
key_name
=
concurrent_foreign_key_name
(
source
,
column
)
# Using NOT VALID allows us to create a key without immediately
unless
foreign_key_exists?
(
source
,
target
,
column:
column
)
# validating it. This means we keep the ALTER TABLE lock only for a
Rails
.
logger
.
warn
"Foreign key not created because it exists already "
\
# short period of time. The key _is_ enforced for any newly created
"(this may be due to an aborted migration or similar): "
\
# data.
"source:
#{
source
}
, target:
#{
target
}
, column:
#{
column
}
"
execute
<<-
EOF
.
strip_heredoc
ALTER TABLE
#{
source
}
# Using NOT VALID allows us to create a key without immediately
ADD CONSTRAINT
#{
key_name
}
# validating it. This means we keep the ALTER TABLE lock only for a
FOREIGN KEY (
#{
column
}
)
# short period of time. The key _is_ enforced for any newly created
REFERENCES
#{
target
}
(id)
# data.
#{
on_delete
?
"ON DELETE
#{
on_delete
.
upcase
}
"
:
''
}
execute
<<-
EOF
.
strip_heredoc
NOT VALID;
ALTER TABLE
#{
source
}
EOF
ADD CONSTRAINT
#{
key_name
}
FOREIGN KEY (
#{
column
}
)
REFERENCES
#{
target
}
(id)
#{
on_delete
?
"ON DELETE
#{
on_delete
.
upcase
}
"
:
''
}
NOT VALID;
EOF
end
# Validate the existing constraint. This can potentially take a very
# Validate the existing constraint. This can potentially take a very
# long time to complete, but fortunately does not lock the source table
# long time to complete, but fortunately does not lock the source table
# while running.
# while running.
#
# Note this is a no-op in case the constraint is VALID already
execute
(
"ALTER TABLE
#{
source
}
VALIDATE CONSTRAINT
#{
key_name
}
;"
)
execute
(
"ALTER TABLE
#{
source
}
VALIDATE CONSTRAINT
#{
key_name
}
;"
)
end
end
def
foreign_key_exists?
(
source
,
target
=
nil
,
column:
nil
)
foreign_keys
(
source
).
any?
do
|
key
|
if
column
key
.
options
[
:column
].
to_s
==
column
.
to_s
else
key
.
to_table
.
to_s
==
target
.
to_s
end
end
end
# Returns the name for a concurrent foreign key.
# Returns the name for a concurrent foreign key.
#
#
# PostgreSQL constraint names have a limit of 63 bytes. The logic used
# PostgreSQL constraint names have a limit of 63 bytes. The logic used
...
@@ -875,12 +900,6 @@ into similar problems in the future (e.g. when new tables are created).
...
@@ -875,12 +900,6 @@ into similar problems in the future (e.g. when new tables are created).
end
end
end
end
def
foreign_key_exists?
(
table
,
column
)
foreign_keys
(
table
).
any?
do
|
key
|
key
.
options
[
:column
]
==
column
.
to_s
end
end
# Rails' index_exists? doesn't work when you only give it a table and index
# Rails' index_exists? doesn't work when you only give it a table and index
# name. As such we have to use some extra code to check if an index exists for
# name. As such we have to use some extra code to check if an index exists for
# a given name.
# a given name.
...
...
spec/lib/gitlab/database/migration_helpers_spec.rb
View file @
c914883a
...
@@ -183,6 +183,10 @@ describe Gitlab::Database::MigrationHelpers do
...
@@ -183,6 +183,10 @@ describe Gitlab::Database::MigrationHelpers do
end
end
describe
'#add_concurrent_foreign_key'
do
describe
'#add_concurrent_foreign_key'
do
before
do
allow
(
model
).
to
receive
(
:foreign_key_exists?
).
and_return
(
false
)
end
context
'inside a transaction'
do
context
'inside a transaction'
do
it
'raises an error'
do
it
'raises an error'
do
expect
(
model
).
to
receive
(
:transaction_open?
).
and_return
(
true
)
expect
(
model
).
to
receive
(
:transaction_open?
).
and_return
(
true
)
...
@@ -199,14 +203,23 @@ describe Gitlab::Database::MigrationHelpers do
...
@@ -199,14 +203,23 @@ describe Gitlab::Database::MigrationHelpers do
end
end
context
'using MySQL'
do
context
'using MySQL'
do
it
'creates a regular foreign key'
do
before
do
allow
(
Gitlab
::
Database
).
to
receive
(
:mysql?
).
and_return
(
true
)
allow
(
Gitlab
::
Database
).
to
receive
(
:mysql?
).
and_return
(
true
)
end
it
'creates a regular foreign key'
do
expect
(
model
).
to
receive
(
:add_foreign_key
)
expect
(
model
).
to
receive
(
:add_foreign_key
)
.
with
(
:projects
,
:users
,
column: :user_id
,
on_delete: :cascade
)
.
with
(
:projects
,
:users
,
column: :user_id
,
on_delete: :cascade
)
model
.
add_concurrent_foreign_key
(
:projects
,
:users
,
column: :user_id
)
model
.
add_concurrent_foreign_key
(
:projects
,
:users
,
column: :user_id
)
end
end
it
'does not create a foreign key if it exists already'
do
expect
(
model
).
to
receive
(
:foreign_key_exists?
).
with
(
:projects
,
:users
,
column: :user_id
).
and_return
(
true
)
expect
(
model
).
not_to
receive
(
:add_foreign_key
)
model
.
add_concurrent_foreign_key
(
:projects
,
:users
,
column: :user_id
)
end
end
end
context
'using PostgreSQL'
do
context
'using PostgreSQL'
do
...
@@ -231,6 +244,14 @@ describe Gitlab::Database::MigrationHelpers do
...
@@ -231,6 +244,14 @@ describe Gitlab::Database::MigrationHelpers do
column: :user_id
,
column: :user_id
,
on_delete: :nullify
)
on_delete: :nullify
)
end
end
it
'does not create a foreign key if it exists already'
do
expect
(
model
).
to
receive
(
:foreign_key_exists?
).
with
(
:projects
,
:users
,
column: :user_id
).
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
)
end
end
end
end
end
end
end
...
@@ -245,6 +266,29 @@ describe Gitlab::Database::MigrationHelpers do
...
@@ -245,6 +266,29 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
end
describe
'#foreign_key_exists?'
do
before
do
key
=
ActiveRecord
::
ConnectionAdapters
::
ForeignKeyDefinition
.
new
(
:projects
,
:users
,
{
column: :non_standard_id
})
allow
(
model
).
to
receive
(
:foreign_keys
).
with
(
:projects
).
and_return
([
key
])
end
it
'finds existing foreign keys by column'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:users
,
column: :non_standard_id
)).
to
be_truthy
end
it
'finds existing foreign keys by target table only'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:users
)).
to
be_truthy
end
it
'compares by column name if given'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:users
,
column: :user_id
)).
to
be_falsey
end
it
'compares by target if no column given'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:other_table
)).
to
be_falsey
end
end
describe
'#disable_statement_timeout'
do
describe
'#disable_statement_timeout'
do
context
'using PostgreSQL'
do
context
'using PostgreSQL'
do
it
'disables statement timeouts'
do
it
'disables statement timeouts'
do
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment