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
1
Merge Requests
1
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
nexedi
gitlab-ce
Commits
398ea5bb
Commit
398ea5bb
authored
Nov 26, 2019
by
manojmj
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add explicit options
parent
3e1e8b31
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
125 additions
and
58 deletions
+125
-58
lib/gitlab/database/migration_helpers.rb
lib/gitlab/database/migration_helpers.rb
+29
-19
spec/lib/gitlab/database/migration_helpers_spec.rb
spec/lib/gitlab/database/migration_helpers_spec.rb
+96
-39
No files found.
lib/gitlab/database/migration_helpers.rb
View file @
398ea5bb
...
@@ -155,6 +155,7 @@ module Gitlab
...
@@ -155,6 +155,7 @@ module Gitlab
# column - The name of the column to create the foreign key on.
# column - The name of the column to create the foreign key on.
# on_delete - The action to perform when associated data is removed,
# on_delete - The action to perform when associated data is removed,
# defaults to "CASCADE".
# defaults to "CASCADE".
# name - The name of the foreign key.
#
#
# rubocop:disable Gitlab/RailsLogger
# 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
)
...
@@ -164,21 +165,17 @@ module Gitlab
...
@@ -164,21 +165,17 @@ 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
options
=
{}
options
=
{
options
[
:on_delete
]
=
on_delete
column:
column
,
on_delete:
on_delete
,
if
name
name:
name
.
presence
||
concurrent_foreign_key_name
(
source
,
column
)
key_name
=
name
}
options
[
:name
]
=
name
else
key_name
=
concurrent_foreign_key_name
(
source
,
column
)
options
[
:column
]
=
column
end
if
foreign_key_exists?
(
source
,
target
,
options
)
if
foreign_key_exists?
(
source
,
target
,
options
)
warning_message
=
"Foreign key not created because it exists already "
\
warning_message
=
"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
}
, name:
#{
name
}
, on_delete:
#{
on_delete
}
"
"source:
#{
source
}
, target:
#{
target
}
, column:
#{
options
[
:column
]
}
, "
\
"name:
#{
options
[
:name
]
}
, on_delete:
#{
options
[
:on_delete
]
}
"
Rails
.
logger
.
warn
warning_message
Rails
.
logger
.
warn
warning_message
else
else
...
@@ -187,14 +184,12 @@ module Gitlab
...
@@ -187,14 +184,12 @@ module Gitlab
# 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
#{
options
[
:name
]
}
FOREIGN KEY (
#{
column
}
)
FOREIGN KEY (
#{
options
[
:column
]
}
)
REFERENCES
#{
target
}
(id)
REFERENCES
#{
target
}
(id)
#{
on_delete
?
"ON DELETE
#{
on_delete
.
upcase
}
"
:
''
}
#{
on_delete
_statement
(
options
[
:on_delete
])
}
NOT VALID;
NOT VALID;
EOF
EOF
end
end
...
@@ -205,15 +200,15 @@ module Gitlab
...
@@ -205,15 +200,15 @@ module Gitlab
#
#
# Note this is a no-op in case the constraint is VALID already
# Note this is a no-op in case the constraint is VALID already
disable_statement_timeout
do
disable_statement_timeout
do
execute
(
"ALTER TABLE
#{
source
}
VALIDATE CONSTRAINT
#{
key_name
}
;"
)
execute
(
"ALTER TABLE
#{
source
}
VALIDATE CONSTRAINT
#{
options
[
:name
]
}
;"
)
end
end
end
end
# rubocop:enable Gitlab/RailsLogger
# rubocop:enable Gitlab/RailsLogger
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
|
(
target
.
nil?
||
foreign_key
.
to_table
.
to_s
==
target
.
to_s
)
&&
tables_match?
(
target
.
to_s
,
foreign_key
.
to_table
.
to_s
)
&&
options
.
all?
{
|
k
,
v
|
foreign_key
.
options
[
k
].
to_s
==
v
.
to_s
}
options
_match?
(
foreign_key
.
options
,
options
)
end
end
end
end
...
@@ -1059,6 +1054,21 @@ into similar problems in the future (e.g. when new tables are created).
...
@@ -1059,6 +1054,21 @@ into similar problems in the future (e.g. when new tables are created).
private
private
def
tables_match?
(
target_table
,
foreign_key_table
)
target_table
.
blank?
||
foreign_key_table
==
target_table
end
def
options_match?
(
foreign_key_options
,
options
)
options
.
all?
{
|
k
,
v
|
foreign_key_options
[
k
].
to_s
==
v
.
to_s
}
end
def
on_delete_statement
(
on_delete
)
return
''
if
on_delete
.
blank?
return
'ON DELETE SET NULL'
if
on_delete
==
:nullify
"ON DELETE
#{
on_delete
.
upcase
}
"
end
def
create_column_from
(
table
,
old
,
new
,
type:
nil
)
def
create_column_from
(
table
,
old
,
new
,
type:
nil
)
old_col
=
column_for
(
table
,
old
)
old_col
=
column_for
(
table
,
old
)
new_type
=
type
||
old_col
.
type
new_type
=
type
||
old_col
.
type
...
...
spec/lib/gitlab/database/migration_helpers_spec.rb
View file @
398ea5bb
...
@@ -212,31 +212,71 @@ describe Gitlab::Database::MigrationHelpers do
...
@@ -212,31 +212,71 @@ 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
context
'ON DELETE statements'
do
it
'creates a concurrent foreign key and validates it'
do
context
'on_delete: :nullify'
do
it
'appends ON DELETE SET NULL statement'
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
(
/VALIDATE CONSTRAINT/
)
expect
(
model
).
to
receive
(
:execute
).
ordered
.
with
(
/VALIDATE CONSTRAINT/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/RESET ALL/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/RESET ALL/
)
model
.
add_concurrent_foreign_key
(
:projects
,
:users
,
column: :user_id
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/ON DELETE SET NULL/
)
model
.
add_concurrent_foreign_key
(
:projects
,
:users
,
column: :user_id
,
on_delete: :nullify
)
end
end
end
it
'appends a valid ON DELETE statement'
do
context
'on_delete: :cascade'
do
it
'appends ON DELETE CASCADE statement'
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
).
with
(
/ON DELETE SET NULL/
)
expect
(
model
).
to
receive
(
:execute
).
ordered
.
with
(
/VALIDATE CONSTRAINT/
)
expect
(
model
).
to
receive
(
:execute
).
ordered
.
with
(
/VALIDATE CONSTRAINT/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/RESET ALL/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/RESET ALL/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/ON DELETE CASCADE/
)
model
.
add_concurrent_foreign_key
(
:projects
,
:users
,
model
.
add_concurrent_foreign_key
(
:projects
,
:users
,
column: :user_id
,
column: :user_id
,
on_delete: :nullify
)
on_delete: :cascade
)
end
end
context
'on_delete: nil'
do
it
'appends no ON DELETE statement'
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
(
/VALIDATE CONSTRAINT/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/RESET ALL/
)
expect
(
model
).
not_to
receive
(
:execute
).
with
(
/ON DELETE/
)
model
.
add_concurrent_foreign_key
(
:projects
,
:users
,
column: :user_id
,
on_delete:
nil
)
end
end
end
context
'when no custom key name is supplied'
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
(
: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
(
:projects
,
:users
,
column: :user_id
)
end
end
it
'does not create a foreign key if it exists already'
do
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
,
on_delete: :cascade
).
and_return
(
true
)
name
=
model
.
concurrent_foreign_key_name
(
:projects
,
:user_id
)
expect
(
model
).
to
receive
(
:foreign_key_exists?
).
with
(
:projects
,
:users
,
column: :user_id
,
on_delete: :cascade
,
name:
name
).
and_return
(
true
)
expect
(
model
).
not_to
receive
(
:execute
).
with
(
/ADD CONSTRAINT/
)
expect
(
model
).
not_to
receive
(
:execute
).
with
(
/ADD CONSTRAINT/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/VALIDATE CONSTRAINT/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/VALIDATE CONSTRAINT/
)
...
@@ -260,7 +300,10 @@ describe Gitlab::Database::MigrationHelpers do
...
@@ -260,7 +300,10 @@ describe Gitlab::Database::MigrationHelpers do
context
'for creating a duplicate foreign key for a column that presently exists'
do
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
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
it
'does not create a new foreign key'
do
expect
(
model
).
to
receive
(
:foreign_key_exists?
).
with
(
:projects
,
:users
,
name: :foo
,
on_delete: :cascade
).
and_return
(
true
)
expect
(
model
).
to
receive
(
:foreign_key_exists?
).
with
(
:projects
,
:users
,
name: :foo
,
on_delete: :cascade
,
column: :user_id
).
and_return
(
true
)
expect
(
model
).
not_to
receive
(
:execute
).
with
(
/ADD CONSTRAINT/
)
expect
(
model
).
not_to
receive
(
:execute
).
with
(
/ADD CONSTRAINT/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/VALIDATE CONSTRAINT/
)
expect
(
model
).
to
receive
(
:execute
).
with
(
/VALIDATE CONSTRAINT/
)
...
@@ -301,43 +344,57 @@ describe Gitlab::Database::MigrationHelpers do
...
@@ -301,43 +344,57 @@ describe Gitlab::Database::MigrationHelpers do
allow
(
model
).
to
receive
(
:foreign_keys
).
with
(
:projects
).
and_return
([
key
])
allow
(
model
).
to
receive
(
:foreign_keys
).
with
(
:projects
).
and_return
([
key
])
end
end
shared_examples_for
'foreign key checks'
do
it
'finds existing foreign keys by column'
do
it
'finds existing foreign keys by column'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:users
,
column: :non_standard_id
)).
to
be_truthy
expect
(
model
.
foreign_key_exists?
(
:projects
,
target_table
,
column: :non_standard_id
)).
to
be_truthy
end
end
it
'finds existing foreign keys by name'
do
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
expect
(
model
.
foreign_key_exists?
(
:projects
,
target_table
,
name: :fk_projects_users_non_standard_id
)).
to
be_truthy
end
end
it
'finds existing foreign_keys by name and column'
do
it
'finds existing foreign_keys by name and column'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:users
,
name: :fk_projects_users_non_standard_id
,
column: :non_standard_id
)).
to
be_truthy
expect
(
model
.
foreign_key_exists?
(
:projects
,
target_table
,
name: :fk_projects_users_non_standard_id
,
column: :non_standard_id
)).
to
be_truthy
end
end
it
'finds existing foreign_keys by name, column and on_delete'
do
it
'finds existing foreign_keys by name, column and on_delete'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:users
,
name: :fk_projects_users_non_standard_id
,
column: :non_standard_id
,
on_delete: :cascade
)).
to
be_truthy
expect
(
model
.
foreign_key_exists?
(
:projects
,
target_table
,
name: :fk_projects_users_non_standard_id
,
column: :non_standard_id
,
on_delete: :cascade
)).
to
be_truthy
end
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
,
target_table
)).
to
be_truthy
end
end
it
'compares by column name if given'
do
it
'compares by column name if given'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:users
,
column: :user_id
)).
to
be_falsey
expect
(
model
.
foreign_key_exists?
(
:projects
,
target_table
,
column: :user_id
)).
to
be_falsey
end
end
it
'compares by foreign key name if given'
do
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
expect
(
model
.
foreign_key_exists?
(
:projects
,
target_table
,
name: :non_existent_foreign_key_name
)).
to
be_falsey
end
end
it
'compares by foreign key name and column if given'
do
it
'compares by foreign key name and column if given'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:users
,
name: :non_existent_foreign_key_name
,
column: :non_standard_id
)).
to
be_falsey
expect
(
model
.
foreign_key_exists?
(
:projects
,
target_table
,
name: :non_existent_foreign_key_name
,
column: :non_standard_id
)).
to
be_falsey
end
end
it
'compares by foreign key name, column and on_delete if given'
do
it
'compares by foreign key name, column and on_delete if given'
do
expect
(
model
.
foreign_key_exists?
(
:projects
,
:users
,
name: :fk_projects_users_non_standard_id
,
column: :non_standard_id
,
on_delete: :nullify
)).
to
be_falsey
expect
(
model
.
foreign_key_exists?
(
:projects
,
target_table
,
name: :fk_projects_users_non_standard_id
,
column: :non_standard_id
,
on_delete: :nullify
)).
to
be_falsey
end
end
context
'without specifying a target table'
do
let
(
:target_table
)
{
nil
}
it_behaves_like
'foreign key checks'
end
context
'specifying a target table'
do
let
(
:target_table
)
{
:users
}
it_behaves_like
'foreign key checks'
end
end
it
'compares by target if no column given'
do
it
'compares by target
table
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
end
end
...
...
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