Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/database/migration_helpers.rb')
-rw-r--r--lib/gitlab/database/migration_helpers.rb154
1 files changed, 66 insertions, 88 deletions
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 9b041c18da4..291f483e6e4 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -14,7 +14,8 @@ module Gitlab
include DynamicModelHelpers
include RenameTableHelpers
include AsyncIndexes::MigrationHelpers
- include AsyncForeignKeys::MigrationHelpers
+ include AsyncConstraints::MigrationHelpers
+ include WraparoundVacuumHelpers
def define_batchable_model(table_name, connection: self.connection)
super(table_name, connection: connection)
@@ -79,63 +80,6 @@ module Gitlab
end
end
- # @deprecated Use `create_table` in V2 instead
- #
- # Creates a new table, optionally allowing the caller to add check constraints to the table.
- # Aside from that addition, this method should behave identically to Rails' `create_table` method.
- #
- # Example:
- #
- # create_table_with_constraints :some_table do |t|
- # t.integer :thing, null: false
- # t.text :other_thing
- #
- # t.check_constraint :thing_is_not_null, 'thing IS NOT NULL'
- # t.text_limit :other_thing, 255
- # end
- #
- # See Rails' `create_table` for more info on the available arguments.
- def create_table_with_constraints(table_name, **options, &block)
- helper_context = self
-
- with_lock_retries do
- check_constraints = []
-
- create_table(table_name, **options) do |t|
- t.define_singleton_method(:check_constraint) do |name, definition|
- helper_context.send(:validate_check_constraint_name!, name) # rubocop:disable GitlabSecurity/PublicSend
-
- check_constraints << { name: name, definition: definition }
- end
-
- t.define_singleton_method(:text_limit) do |column_name, limit, name: nil|
- # rubocop:disable GitlabSecurity/PublicSend
- name = helper_context.send(:text_limit_name, table_name, column_name, name: name)
- helper_context.send(:validate_check_constraint_name!, name)
- # rubocop:enable GitlabSecurity/PublicSend
-
- column_name = helper_context.quote_column_name(column_name)
- definition = "char_length(#{column_name}) <= #{limit}"
-
- check_constraints << { name: name, definition: definition }
- end
-
- t.instance_eval(&block) unless block.nil?
- end
-
- next if check_constraints.empty?
-
- constraint_clauses = check_constraints.map do |constraint|
- "ADD CONSTRAINT #{quote_table_name(constraint[:name])} CHECK (#{constraint[:definition]})"
- end
-
- execute(<<~SQL)
- ALTER TABLE #{quote_table_name(table_name)}
- #{constraint_clauses.join(",\n")}
- SQL
- end
- end
-
# Creates a new index, concurrently
#
# Example:
@@ -292,23 +236,34 @@ module Gitlab
# order of the ALTER TABLE. This can be useful in situations where the foreign
# key creation could deadlock with another process.
#
- # rubocop: disable Metrics/ParameterLists
- def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, on_update: nil, target_column: :id, name: nil, validate: true, reverse_lock_order: false)
+ def add_concurrent_foreign_key(source, target, column:, **options)
+ options.reverse_merge!({
+ on_delete: :cascade,
+ on_update: nil,
+ target_column: :id,
+ validate: true,
+ reverse_lock_order: false,
+ allow_partitioned: false,
+ column: column
+ })
+
# Transactions would result in ALTER TABLE locks being held for the
# duration of the transaction, defeating the purpose of this method.
if transaction_open?
raise 'add_concurrent_foreign_key can not be run inside a transaction'
end
- options = {
- column: column,
- on_delete: on_delete,
- on_update: on_update,
- name: name.presence || concurrent_foreign_key_name(source, column),
- primary_key: target_column
- }
+ if !options.delete(:allow_partitioned) && table_partitioned?(source)
+ raise ArgumentError, 'add_concurrent_foreign_key can not be used on a partitioned ' \
+ 'table. Please use add_concurrent_partitioned_foreign_key on the partitioned table ' \
+ 'as we need to create foreign keys on each partition and a FK on the parent table'
+ end
- if foreign_key_exists?(source, target, **options)
+ options[:name] ||= concurrent_foreign_key_name(source, column)
+ options[:primary_key] = options[:target_column]
+ check_options = options.slice(:column, :on_delete, :on_update, :name, :primary_key)
+
+ if foreign_key_exists?(source, target, **check_options)
warning_message = "Foreign key not created because it exists already " \
"(this may be due to an aborted migration or similar): " \
"source: #{source}, target: #{target}, column: #{options[:column]}, "\
@@ -317,23 +272,7 @@ module Gitlab
Gitlab::AppLogger.warn warning_message
else
- # Using NOT VALID allows us to create a key without immediately
- # 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
- # data.
-
- with_lock_retries do
- execute("LOCK TABLE #{target}, #{source} IN SHARE ROW EXCLUSIVE MODE") if reverse_lock_order
- execute <<-EOF.strip_heredoc
- ALTER TABLE #{source}
- ADD CONSTRAINT #{options[:name]}
- FOREIGN KEY (#{multiple_columns(options[:column])})
- REFERENCES #{target} (#{multiple_columns(target_column)})
- #{on_update_statement(options[:on_update])}
- #{on_delete_statement(options[:on_delete])}
- NOT VALID;
- EOF
- end
+ execute_add_concurrent_foreign_key(source, target, options)
end
# Validate the existing constraint. This can potentially take a very
@@ -345,13 +284,12 @@ module Gitlab
#
# Note this is a no-op in case the constraint is VALID already
- if validate
+ if options[:validate]
disable_statement_timeout do
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{options[:name]};")
end
end
end
- # rubocop: enable Metrics/ParameterLists
def validate_foreign_key(source, column, name: nil)
fk_name = name || concurrent_foreign_key_name(source, column)
@@ -379,7 +317,14 @@ module Gitlab
end
end
- fks = Gitlab::Database::PostgresForeignKey.by_constrained_table_name(source)
+ # Since we may be migrating in one go from a previous version without
+ # `constrained_table_name` then we may see that this column exists
+ # (as above) but the schema cache is still outdated for the model.
+ unless Gitlab::Database::PostgresForeignKey.column_names.include?('constrained_table_name')
+ Gitlab::Database::PostgresForeignKey.reset_column_information
+ end
+
+ fks = Gitlab::Database::PostgresForeignKey.by_constrained_table_name_or_identifier(source)
fks = fks.by_referenced_table_name(target) if target
fks = fks.by_name(options[:name]) if options[:name]
@@ -1239,6 +1184,12 @@ into similar problems in the future (e.g. when new tables are created).
end
end
+ def table_partitioned?(table_name)
+ Gitlab::Database::PostgresPartitionedTable
+ .find_by_name_in_current_schema(table_name)
+ .present?
+ end
+
private
def multiple_columns(columns, separator: ', ')
@@ -1354,6 +1305,33 @@ into similar problems in the future (e.g. when new tables are created).
Must end with `_at`}
MESSAGE
end
+
+ def execute_add_concurrent_foreign_key(source, target, options)
+ # Using NOT VALID allows us to create a key without immediately
+ # 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
+ # data.
+ not_valid = 'NOT VALID'
+ lock_mode = 'SHARE ROW EXCLUSIVE'
+
+ if table_partitioned?(source)
+ not_valid = ''
+ lock_mode = 'ACCESS EXCLUSIVE'
+ end
+
+ with_lock_retries do
+ execute("LOCK TABLE #{target}, #{source} IN #{lock_mode} MODE") if options[:reverse_lock_order]
+ execute(<<~SQL.squish)
+ ALTER TABLE #{source}
+ ADD CONSTRAINT #{options[:name]}
+ FOREIGN KEY (#{multiple_columns(options[:column])})
+ REFERENCES #{target} (#{multiple_columns(options[:target_column])})
+ #{on_update_statement(options[:on_update])}
+ #{on_delete_statement(options[:on_delete])}
+ #{not_valid};
+ SQL
+ end
+ end
end
end
end