diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 21:42:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 21:42:06 +0300 |
commit | 6e4e1050d9dba2b7b2523fdd1768823ab85feef4 (patch) | |
tree | 78be5963ec075d80116a932011d695dd33910b4e /lib/gitlab/background_migration | |
parent | 1ce776de4ae122aba3f349c02c17cebeaa8ecf07 (diff) |
Add latest changes from gitlab-org/gitlab@13-3-stable-ee
Diffstat (limited to 'lib/gitlab/background_migration')
29 files changed, 174 insertions, 1326 deletions
diff --git a/lib/gitlab/background_migration/archive_legacy_traces.rb b/lib/gitlab/background_migration/archive_legacy_traces.rb deleted file mode 100644 index 79f38aed9f1..00000000000 --- a/lib/gitlab/background_migration/archive_legacy_traces.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class ArchiveLegacyTraces - def perform(start_id, stop_id) - # This background migration directly refers to ::Ci::Build model which is defined in application code. - # In general, migration code should be isolated as much as possible in order to be idempotent. - # However, `archive!` method is too complicated to be replicated by coping its subsequent code. - # So we chose a way to use ::Ci::Build directly and we don't change the `archive!` method until 11.1 - ::Ci::Build.finished.without_archived_trace - .where(id: start_id..stop_id).find_each do |build| - build.trace.archive! - rescue => e - Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}" # rubocop:disable Gitlab/RailsLogger - end - end - end - end -end diff --git a/lib/gitlab/background_migration/backfill_designs_relative_position.rb b/lib/gitlab/background_migration/backfill_designs_relative_position.rb new file mode 100644 index 00000000000..efbb1b950ad --- /dev/null +++ b/lib/gitlab/background_migration/backfill_designs_relative_position.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # This migration is not needed anymore and was disabled, because we're now + # also backfilling design positions immediately before moving a design. + # + # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39555 + class BackfillDesignsRelativePosition + def perform(issue_ids) + # no-op + end + end + end +end diff --git a/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb b/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb deleted file mode 100644 index a6194616663..00000000000 --- a/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Class that will fill the project_repositories table for projects that - # are on hashed storage and an entry is is missing in this table. - class BackfillHashedProjectRepositories < BackfillProjectRepositories - private - - def projects - Project.on_hashed_storage - end - end - end -end diff --git a/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb b/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb deleted file mode 100644 index 2a079060380..00000000000 --- a/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb +++ /dev/null @@ -1,213 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This module is used to write the full path of all projects to - # the git repository config file. - # Storing the full project path in the git config allows admins to - # easily identify a project when it is using hashed storage. - module BackfillProjectFullpathInRepoConfig - OrphanedNamespaceError = Class.new(StandardError) - - module Storage - # Class that returns the disk path for a project using hashed storage - class Hashed - attr_accessor :project - - ROOT_PATH_PREFIX = '@hashed' - - def initialize(project) - @project = project - end - - def disk_path - "#{ROOT_PATH_PREFIX}/#{disk_hash[0..1]}/#{disk_hash[2..3]}/#{disk_hash}" - end - - def disk_hash - @disk_hash ||= Digest::SHA2.hexdigest(project.id.to_s) if project.id - end - end - - # Class that returns the disk path for a project using legacy storage - class LegacyProject - attr_accessor :project - - def initialize(project) - @project = project - end - - def disk_path - project.full_path - end - end - end - - # Concern used by Project and Namespace to determine the full - # route to the project - module Routable - extend ActiveSupport::Concern - - def full_path - @full_path ||= build_full_path - end - - def build_full_path - return path unless has_parent? - - raise OrphanedNamespaceError if parent.nil? - - parent.full_path + '/' + path - end - - def has_parent? - read_attribute(association(:parent).reflection.foreign_key) - end - end - - # Class used to interact with repository using Gitaly - class Repository - attr_reader :storage - - def initialize(storage, relative_path) - @storage = storage - @relative_path = relative_path - end - - def gitaly_repository - Gitaly::Repository.new(storage_name: @storage, relative_path: @relative_path) - end - end - - # Namespace can be a user or group. It can be the root or a - # child of another namespace. - class Namespace < ActiveRecord::Base - self.table_name = 'namespaces' - self.inheritance_column = nil - - include Routable - - belongs_to :parent, class_name: 'Namespace', inverse_of: 'namespaces' - has_many :projects, inverse_of: :parent - has_many :namespaces, inverse_of: :parent - end - - # Project is where the repository (etc.) is stored - class Project < ActiveRecord::Base - self.table_name = 'projects' - - include Routable - include EachBatch - - FULLPATH_CONFIG_KEY = 'gitlab.fullpath' - - belongs_to :parent, class_name: 'Namespace', foreign_key: :namespace_id, inverse_of: 'projects' - delegate :disk_path, to: :storage - - def add_fullpath_config - entries = { FULLPATH_CONFIG_KEY => full_path } - - repository_service.set_config(entries) - end - - def remove_fullpath_config - repository_service.delete_config([FULLPATH_CONFIG_KEY]) - end - - def cleanup_repository - repository_service.cleanup - end - - def storage - @storage ||= - if hashed_storage? - Storage::Hashed.new(self) - else - Storage::LegacyProject.new(self) - end - end - - def hashed_storage? - self.storage_version && self.storage_version >= 1 - end - - def repository - @repository ||= Repository.new(repository_storage, disk_path + '.git') - end - - def repository_service - @repository_service ||= Gitlab::GitalyClient::RepositoryService.new(repository) - end - end - - # Base class for Up and Down migration classes - class BackfillFullpathMigration - RETRY_DELAY = 15.minutes - MAX_RETRIES = 2 - - # Base class for retrying one project - class BaseRetryOne - def perform(project_id, retry_count) - project = Project.find(project_id) - - return unless project - - migration_class.new.safe_perform_one(project, retry_count) - end - end - - def perform(start_id, end_id) - Project.includes(:parent).where(id: start_id..end_id).each do |project| - safe_perform_one(project) - end - end - - def safe_perform_one(project, retry_count = 0) - perform_one(project) - rescue GRPC::NotFound, GRPC::InvalidArgument, OrphanedNamespaceError - nil - rescue GRPC::BadStatus - schedule_retry(project, retry_count + 1) if retry_count < MAX_RETRIES - end - - def schedule_retry(project, retry_count) - # Constants provided to BackgroundMigrationWorker must be within the - # scope of Gitlab::BackgroundMigration - retry_class_name = self.class::RetryOne.name.sub('Gitlab::BackgroundMigration::', '') - - BackgroundMigrationWorker.perform_in(RETRY_DELAY, retry_class_name, [project.id, retry_count]) - end - end - - # Class to add the fullpath to the git repo config - class Up < BackfillFullpathMigration - # Class used to retry - class RetryOne < BaseRetryOne - def migration_class - Up - end - end - - def perform_one(project) - project.cleanup_repository - project.add_fullpath_config - end - end - - # Class to rollback adding the fullpath to the git repo config - class Down < BackfillFullpathMigration - # Class used to retry - class RetryOne < BaseRetryOne - def migration_class - Down - end - end - - def perform_one(project) - project.cleanup_repository - project.remove_fullpath_config - end - end - end - end -end diff --git a/lib/gitlab/background_migration/copy_merge_request_target_project_to_merge_request_metrics.rb b/lib/gitlab/background_migration/copy_merge_request_target_project_to_merge_request_metrics.rb new file mode 100644 index 00000000000..6014ccc12eb --- /dev/null +++ b/lib/gitlab/background_migration/copy_merge_request_target_project_to_merge_request_metrics.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class CopyMergeRequestTargetProjectToMergeRequestMetrics + extend ::Gitlab::Utils::Override + + def perform(start_id, stop_id) + ActiveRecord::Base.connection.execute <<~SQL + WITH merge_requests_batch AS ( + SELECT id, target_project_id + FROM merge_requests WHERE id BETWEEN #{Integer(start_id)} AND #{Integer(stop_id)} + ) + UPDATE + merge_request_metrics + SET + target_project_id = merge_requests_batch.target_project_id + FROM merge_requests_batch + WHERE merge_request_metrics.merge_request_id=merge_requests_batch.id + SQL + end + end + end +end diff --git a/lib/gitlab/background_migration/fill_file_store_job_artifact.rb b/lib/gitlab/background_migration/fill_file_store_job_artifact.rb deleted file mode 100644 index 103bd98af14..00000000000 --- a/lib/gitlab/background_migration/fill_file_store_job_artifact.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class FillFileStoreJobArtifact - class JobArtifact < ActiveRecord::Base - self.table_name = 'ci_job_artifacts' - end - - def perform(start_id, stop_id) - FillFileStoreJobArtifact::JobArtifact - .where(file_store: nil) - .where(id: (start_id..stop_id)) - .update_all(file_store: 1) - end - end - end -end diff --git a/lib/gitlab/background_migration/fill_file_store_lfs_object.rb b/lib/gitlab/background_migration/fill_file_store_lfs_object.rb deleted file mode 100644 index 77c1f1ffaf0..00000000000 --- a/lib/gitlab/background_migration/fill_file_store_lfs_object.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class FillFileStoreLfsObject - class LfsObject < ActiveRecord::Base - self.table_name = 'lfs_objects' - end - - def perform(start_id, stop_id) - FillFileStoreLfsObject::LfsObject - .where(file_store: nil) - .where(id: (start_id..stop_id)) - .update_all(file_store: 1) - end - end - end -end diff --git a/lib/gitlab/background_migration/fill_store_upload.rb b/lib/gitlab/background_migration/fill_store_upload.rb deleted file mode 100644 index cba3e21cea6..00000000000 --- a/lib/gitlab/background_migration/fill_store_upload.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class FillStoreUpload - class Upload < ActiveRecord::Base - self.table_name = 'uploads' - self.inheritance_column = :_type_disabled - end - - def perform(start_id, stop_id) - FillStoreUpload::Upload - .where(store: nil) - .where(id: (start_id..stop_id)) - .update_all(store: 1) - end - end - end -end diff --git a/lib/gitlab/background_migration/fix_cross_project_label_links.rb b/lib/gitlab/background_migration/fix_cross_project_label_links.rb deleted file mode 100644 index 20a98c8e141..00000000000 --- a/lib/gitlab/background_migration/fix_cross_project_label_links.rb +++ /dev/null @@ -1,140 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class FixCrossProjectLabelLinks - GROUP_NESTED_LEVEL = 10.freeze - - class Project < ActiveRecord::Base - self.table_name = 'projects' - end - - class Label < ActiveRecord::Base - self.inheritance_column = :_type_disabled - self.table_name = 'labels' - end - - class LabelLink < ActiveRecord::Base - self.table_name = 'label_links' - end - - class Issue < ActiveRecord::Base - self.table_name = 'issues' - end - - class MergeRequest < ActiveRecord::Base - self.table_name = 'merge_requests' - end - - class Namespace < ActiveRecord::Base - self.inheritance_column = :_type_disabled - self.table_name = 'namespaces' - - def self.groups_with_descendants_ids(start_id, stop_id) - # To isolate migration code, we avoid usage of - # Gitlab::GroupHierarchy#base_and_descendants which already - # does this job better - ids = Namespace.where(type: 'Group', id: Label.where(type: 'GroupLabel').select('distinct group_id')).where(id: start_id..stop_id).pluck(:id) - group_ids = ids - - GROUP_NESTED_LEVEL.times do - ids = Namespace.where(type: 'Group', parent_id: ids).pluck(:id) - break if ids.empty? - - group_ids += ids - end - - group_ids.uniq - end - end - - def perform(start_id, stop_id) - group_ids = Namespace.groups_with_descendants_ids(start_id, stop_id) - project_ids = Project.where(namespace_id: group_ids).select(:id) - - fix_issues(project_ids) - fix_merge_requests(project_ids) - end - - private - - # select IDs of issues which reference a label which is: - # a) a project label of a different project, or - # b) a group label of a different group than issue's project group - def fix_issues(project_ids) - issue_ids = Label - .joins('INNER JOIN label_links ON label_links.label_id = labels.id AND label_links.target_type = \'Issue\' - INNER JOIN issues ON issues.id = label_links.target_id - INNER JOIN projects ON projects.id = issues.project_id') - .where('issues.project_id in (?)', project_ids) - .where('(labels.project_id is not null and labels.project_id != issues.project_id) '\ - 'or (labels.group_id is not null and labels.group_id != projects.namespace_id)') - .select('distinct issues.id') - - Issue.where(id: issue_ids).find_each { |issue| check_resource_labels(issue, issue.project_id) } - end - - # select IDs of MRs which reference a label which is: - # a) a project label of a different project, or - # b) a group label of a different group than MR's project group - def fix_merge_requests(project_ids) - mr_ids = Label - .joins('INNER JOIN label_links ON label_links.label_id = labels.id AND label_links.target_type = \'MergeRequest\' - INNER JOIN merge_requests ON merge_requests.id = label_links.target_id - INNER JOIN projects ON projects.id = merge_requests.target_project_id') - .where('merge_requests.target_project_id in (?)', project_ids) - .where('(labels.project_id is not null and labels.project_id != merge_requests.target_project_id) '\ - 'or (labels.group_id is not null and labels.group_id != projects.namespace_id)') - .select('distinct merge_requests.id') - - MergeRequest.where(id: mr_ids).find_each { |merge_request| check_resource_labels(merge_request, merge_request.target_project_id) } - end - - def check_resource_labels(resource, project_id) - local_labels = available_labels(project_id) - - # get all label links for the given resource (issue/MR) - # which reference a label not included in available_labels - # (other than its project labels and labels of ancestor groups) - cross_labels = LabelLink - .select('label_id, labels.title as title, labels.color as color, label_links.id as label_link_id') - .joins('INNER JOIN labels ON labels.id = label_links.label_id') - .where(target_type: resource.class.name.demodulize, target_id: resource.id) - .where('labels.id not in (?)', local_labels.select(:id)) - - cross_labels.each do |label| - matching_label = local_labels.find {|l| l.title == label.title && l.color == label.color} - - next unless matching_label - - Rails.logger.info "#{resource.class.name.demodulize} #{resource.id}: replacing #{label.label_id} with #{matching_label.id}" # rubocop:disable Gitlab/RailsLogger - LabelLink.update(label.label_link_id, label_id: matching_label.id) - end - end - - # get all labels available for the project (including - # group labels of ancestor groups) - def available_labels(project_id) - @labels ||= {} - @labels[project_id] ||= Label - .where("(type = 'GroupLabel' and group_id in (?)) or (type = 'ProjectLabel' and id = ?)", - project_group_ids(project_id), - project_id) - end - - def project_group_ids(project_id) - ids = [Project.find(project_id).namespace_id] - - GROUP_NESTED_LEVEL.times do - group = Namespace.find(ids.last) - break unless group.parent_id - - ids << group.parent_id - end - - ids - end - end - end -end diff --git a/lib/gitlab/background_migration/migrate_build_stage.rb b/lib/gitlab/background_migration/migrate_build_stage.rb deleted file mode 100644 index 268c6083d3c..00000000000 --- a/lib/gitlab/background_migration/migrate_build_stage.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class MigrateBuildStage - module Migratable - class Stage < ActiveRecord::Base - self.table_name = 'ci_stages' - end - - class Build < ActiveRecord::Base - self.table_name = 'ci_builds' - self.inheritance_column = :_type_disabled - - def ensure_stage!(attempts: 2) - find_stage || create_stage! - rescue ActiveRecord::RecordNotUnique - retry if (attempts -= 1) > 0 - raise - end - - def find_stage - Stage.find_by(name: self.stage || 'test', - pipeline_id: self.commit_id, - project_id: self.project_id) - end - - def create_stage! - Stage.create!(name: self.stage || 'test', - pipeline_id: self.commit_id, - project_id: self.project_id) - end - end - end - - def perform(start_id, stop_id) - stages = Migratable::Build.where('stage_id IS NULL') - .where('id BETWEEN ? AND ?', start_id, stop_id) - .map { |build| build.ensure_stage! } - .compact.map(&:id) - - MigrateBuildStageIdReference.new.perform(start_id, stop_id) - MigrateStageStatus.new.perform(stages.min, stages.max) - end - end - end -end diff --git a/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb b/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb deleted file mode 100644 index 0a8a4313cd5..00000000000 --- a/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class MigrateBuildStageIdReference - def perform(start_id, stop_id) - sql = <<-SQL.strip_heredoc - UPDATE ci_builds - SET stage_id = - (SELECT id FROM ci_stages - WHERE ci_stages.pipeline_id = ci_builds.commit_id - AND ci_stages.name = ci_builds.stage) - WHERE ci_builds.id BETWEEN #{start_id.to_i} AND #{stop_id.to_i} - AND ci_builds.stage_id IS NULL - SQL - - ActiveRecord::Base.connection.execute(sql) - end - end - end -end diff --git a/lib/gitlab/background_migration/migrate_stage_index.rb b/lib/gitlab/background_migration/migrate_stage_index.rb deleted file mode 100644 index 55608529cee..00000000000 --- a/lib/gitlab/background_migration/migrate_stage_index.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class MigrateStageIndex - def perform(start_id, stop_id) - migrate_stage_index_sql(start_id.to_i, stop_id.to_i).tap do |sql| - ActiveRecord::Base.connection.execute(sql) - end - end - - private - - def migrate_stage_index_sql(start_id, stop_id) - <<~SQL - WITH freqs AS ( - SELECT stage_id, stage_idx, COUNT(*) AS freq FROM ci_builds - WHERE stage_id BETWEEN #{start_id} AND #{stop_id} - AND stage_idx IS NOT NULL - GROUP BY stage_id, stage_idx - ), indexes AS ( - SELECT DISTINCT stage_id, first_value(stage_idx) - OVER (PARTITION BY stage_id ORDER BY freq DESC) AS index - FROM freqs - ) - - UPDATE ci_stages SET position = indexes.index - FROM indexes WHERE indexes.stage_id = ci_stages.id - AND ci_stages.position IS NULL; - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table.rb b/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table.rb deleted file mode 100644 index fcbcaacb2d6..00000000000 --- a/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true -# -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class PopulateClusterKubernetesNamespaceTable - include Gitlab::Database::MigrationHelpers - - BATCH_SIZE = 1_000 - - module Migratable - class KubernetesNamespace < ActiveRecord::Base - self.table_name = 'clusters_kubernetes_namespaces' - end - - class ClusterProject < ActiveRecord::Base - include EachBatch - - self.table_name = 'cluster_projects' - - belongs_to :project - - def self.with_no_kubernetes_namespace - where.not(id: Migratable::KubernetesNamespace.select(:cluster_project_id)) - end - - def namespace - slug = "#{project.path}-#{project.id}".downcase - slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '') - end - - def service_account - "#{namespace}-service-account" - end - end - - class Project < ActiveRecord::Base - self.table_name = 'projects' - end - end - - def perform - cluster_projects_with_no_kubernetes_namespace.each_batch(of: BATCH_SIZE) do |cluster_projects_batch, index| - sql_values = sql_values_for(cluster_projects_batch) - - insert_into_cluster_kubernetes_namespace(sql_values) - end - end - - private - - def cluster_projects_with_no_kubernetes_namespace - Migratable::ClusterProject.with_no_kubernetes_namespace - end - - def sql_values_for(cluster_projects) - cluster_projects.map do |cluster_project| - values_for_cluster_project(cluster_project) - end - end - - def values_for_cluster_project(cluster_project) - { - cluster_project_id: cluster_project.id, - cluster_id: cluster_project.cluster_id, - project_id: cluster_project.project_id, - namespace: cluster_project.namespace, - service_account_name: cluster_project.service_account, - created_at: 'NOW()', - updated_at: 'NOW()' - } - end - - def insert_into_cluster_kubernetes_namespace(rows) - Gitlab::Database.bulk_insert(Migratable::KubernetesNamespace.table_name, # rubocop:disable Gitlab/BulkInsert - rows, - disable_quote: [:created_at, :updated_at]) - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_personal_snippet_statistics.rb b/lib/gitlab/background_migration/populate_personal_snippet_statistics.rb new file mode 100644 index 00000000000..e8f436b183e --- /dev/null +++ b/lib/gitlab/background_migration/populate_personal_snippet_statistics.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # This class creates/updates those personal snippets statistics + # that haven't been created nor initialized. + # It also updates the related root storage namespace stats + class PopulatePersonalSnippetStatistics + def perform(snippet_ids) + personal_snippets(snippet_ids).group_by(&:author).each do |author, author_snippets| + upsert_snippet_statistics(author_snippets) + update_namespace_statistics(author.namespace) + end + end + + private + + def personal_snippets(snippet_ids) + PersonalSnippet + .where(id: snippet_ids) + .includes(author: :namespace) + .includes(:statistics) + .includes(snippet_repository: :shard) + end + + def upsert_snippet_statistics(snippets) + snippets.each do |snippet| + response = Snippets::UpdateStatisticsService.new(snippet).execute + + error_message("#{response.message} snippet: #{snippet.id}") if response.error? + end + end + + def update_namespace_statistics(namespace) + Namespaces::StatisticsRefresherService.new.execute(namespace) + rescue => e + error_message("Error updating statistics for namespace #{namespace.id}: #{e.message}") + end + + def logger + @logger ||= Gitlab::BackgroundMigration::Logger.build + end + + def error_message(message) + logger.error(message: "Snippet Statistics Migration: #{message}") + end + end + end +end diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb deleted file mode 100644 index 43698b7955f..00000000000 --- a/lib/gitlab/background_migration/populate_untracked_uploads.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class processes a batch of rows in `untracked_files_for_uploads` by - # adding each file to the `uploads` table if it does not exist. - class PopulateUntrackedUploads - def perform(start_id, end_id) - return unless migrate? - - files = Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile.where(id: start_id..end_id) - processed_files = insert_uploads_if_needed(files) - processed_files.delete_all - - drop_temp_table_if_finished - end - - private - - def migrate? - Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile.table_exists? && - Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::Upload.table_exists? - end - - def insert_uploads_if_needed(files) - filtered_files, error_files = filter_error_files(files) - filtered_files = filter_existing_uploads(filtered_files) - filtered_files = filter_deleted_models(filtered_files) - insert(filtered_files) - - processed_files = files.where.not(id: error_files.map(&:id)) - processed_files - end - - def filter_error_files(files) - files.partition do |file| - file.to_h - true - rescue => e - msg = <<~MSG - Error parsing path "#{file.path}": - #{e.message} - #{e.backtrace.join("\n ")} - MSG - Rails.logger.error(msg) # rubocop:disable Gitlab/RailsLogger - false - end - end - - def filter_existing_uploads(files) - paths = files.map(&:upload_path) - existing_paths = Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::Upload.where(path: paths).pluck(:path).to_set - - files.reject do |file| - existing_paths.include?(file.upload_path) - end - end - - # There are files on disk that are not in the uploads table because their - # model was deleted, and we don't delete the files on disk. - def filter_deleted_models(files) - ids = deleted_model_ids(files) - - files.reject do |file| - ids[file.model_type].include?(file.model_id) - end - end - - def deleted_model_ids(files) - ids = { - 'Appearance' => [], - 'Namespace' => [], - 'Note' => [], - 'Project' => [], - 'User' => [] - } - - # group model IDs by model type - files.each do |file| - ids[file.model_type] << file.model_id - end - - ids.each do |model_type, model_ids| - model_class = "Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::#{model_type}".constantize - found_ids = model_class.where(id: model_ids.uniq).pluck(:id) - deleted_ids = ids[model_type] - found_ids - ids[model_type] = deleted_ids - end - - ids - end - - def insert(files) - rows = files.map do |file| - file.to_h.merge(created_at: 'NOW()') - end - - Gitlab::Database.bulk_insert('uploads', # rubocop:disable Gitlab/BulkInsert - rows, - disable_quote: :created_at) - end - - def drop_temp_table_if_finished - if Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile.all.empty? && !Rails.env.test? # Dropping a table intermittently breaks test cleanup - Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile.connection.drop_table(:untracked_files_for_uploads, - if_exists: true) - end - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb b/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb deleted file mode 100644 index 23e8be4a9ab..00000000000 --- a/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb +++ /dev/null @@ -1,190 +0,0 @@ -# frozen_string_literal: true -module Gitlab - module BackgroundMigration - module PopulateUntrackedUploadsDependencies - # This class is responsible for producing the attributes necessary to - # track an uploaded file in the `uploads` table. - class UntrackedFile < ActiveRecord::Base # rubocop:disable Metrics/ClassLength - self.table_name = 'untracked_files_for_uploads' - - # Ends with /:random_hex/:filename - FILE_UPLOADER_PATH = %r{/\h+/[^/]+\z}.freeze - FULL_PATH_CAPTURE = /\A(.+)#{FILE_UPLOADER_PATH}/.freeze - - # These regex patterns are tested against a relative path, relative to - # the upload directory. - # For convenience, if there exists a capture group in the pattern, then - # it indicates the model_id. - PATH_PATTERNS = [ - { - pattern: %r{\A-/system/appearance/logo/(\d+)/}, - uploader: 'AttachmentUploader', - model_type: 'Appearance' - }, - { - pattern: %r{\A-/system/appearance/header_logo/(\d+)/}, - uploader: 'AttachmentUploader', - model_type: 'Appearance' - }, - { - pattern: %r{\A-/system/note/attachment/(\d+)/}, - uploader: 'AttachmentUploader', - model_type: 'Note' - }, - { - pattern: %r{\A-/system/user/avatar/(\d+)/}, - uploader: 'AvatarUploader', - model_type: 'User' - }, - { - pattern: %r{\A-/system/group/avatar/(\d+)/}, - uploader: 'AvatarUploader', - model_type: 'Namespace' - }, - { - pattern: %r{\A-/system/project/avatar/(\d+)/}, - uploader: 'AvatarUploader', - model_type: 'Project' - }, - { - pattern: FILE_UPLOADER_PATH, - uploader: 'FileUploader', - model_type: 'Project' - } - ].freeze - - def to_h - @upload_hash ||= { - path: upload_path, - uploader: uploader, - model_type: model_type, - model_id: model_id, - size: file_size, - checksum: checksum - } - end - - def upload_path - # UntrackedFile#path is absolute, but Upload#path depends on uploader - @upload_path ||= - if uploader == 'FileUploader' - # Path relative to project directory in uploads - matchd = path_relative_to_upload_dir.match(FILE_UPLOADER_PATH) - matchd[0].sub(%r{\A/}, '') # remove leading slash - else - path - end - end - - def uploader - matching_pattern_map[:uploader] - end - - def model_type - matching_pattern_map[:model_type] - end - - def model_id - return @model_id if defined?(@model_id) - - pattern = matching_pattern_map[:pattern] - matchd = path_relative_to_upload_dir.match(pattern) - - # If something is captured (matchd[1] is not nil), it is a model_id - # Only the FileUploader pattern will not match an ID - @model_id = matchd[1] ? matchd[1].to_i : file_uploader_model_id - end - - def file_size - File.size(absolute_path) - end - - def checksum - Digest::SHA256.file(absolute_path).hexdigest - end - - private - - def matching_pattern_map - @matching_pattern_map ||= PATH_PATTERNS.find do |path_pattern_map| - path_relative_to_upload_dir.match(path_pattern_map[:pattern]) - end - - unless @matching_pattern_map - raise "Unknown upload path pattern \"#{path}\"" - end - - @matching_pattern_map - end - - def file_uploader_model_id - matchd = path_relative_to_upload_dir.match(FULL_PATH_CAPTURE) - not_found_msg = <<~MSG - Could not capture project full_path from a FileUploader path: - "#{path_relative_to_upload_dir}" - MSG - raise not_found_msg unless matchd - - full_path = matchd[1] - project = Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::Project.find_by_full_path(full_path) - return unless project - - project.id - end - - # Not including a leading slash - def path_relative_to_upload_dir - upload_dir = Gitlab::BackgroundMigration::PrepareUntrackedUploads::RELATIVE_UPLOAD_DIR - base = %r{\A#{Regexp.escape(upload_dir)}/} - @path_relative_to_upload_dir ||= path.sub(base, '') - end - - def absolute_path - File.join(Gitlab.config.uploads.storage_path, path) - end - end - - # Avoid using application code - class Upload < ActiveRecord::Base - self.table_name = 'uploads' - end - - # Avoid using application code - class Appearance < ActiveRecord::Base - self.table_name = 'appearances' - end - - # Avoid using application code - class Namespace < ActiveRecord::Base - self.table_name = 'namespaces' - end - - # Avoid using application code - class Note < ActiveRecord::Base - self.table_name = 'notes' - end - - # Avoid using application code - class User < ActiveRecord::Base - self.table_name = 'users' - end - - # Since project Markdown upload paths don't contain the project ID, we have to find the - # project by its full_path. Due to MySQL/PostgreSQL differences, and historical reasons, - # the logic is somewhat complex, so I've mostly copied it in here. - class Project < ActiveRecord::Base - self.table_name = 'projects' - - def self.find_by_full_path(path) - order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)") - where_full_path_in(path).reorder(order_sql).take - end - - def self.where_full_path_in(path) - where = "(LOWER(routes.path) = LOWER(#{connection.quote(path)}))" - joins("INNER JOIN routes ON routes.source_id = projects.id AND routes.source_type = 'Project'").where(where) - end - end - end - end -end diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb deleted file mode 100644 index 3d943205783..00000000000 --- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb +++ /dev/null @@ -1,173 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class finds all non-hashed uploaded file paths and saves them to a - # `untracked_files_for_uploads` table. - class PrepareUntrackedUploads # rubocop:disable Metrics/ClassLength - # For bulk_queue_background_migration_jobs_by_range - include Database::MigrationHelpers - include ::Gitlab::Utils::StrongMemoize - - FIND_BATCH_SIZE = 500 - RELATIVE_UPLOAD_DIR = "uploads" - ABSOLUTE_UPLOAD_DIR = File.join( - Gitlab.config.uploads.storage_path, - RELATIVE_UPLOAD_DIR - ) - FOLLOW_UP_MIGRATION = 'PopulateUntrackedUploads' - START_WITH_ROOT_REGEX = %r{\A#{Gitlab.config.uploads.storage_path}/}.freeze - EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*" - EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*" - - # This class is used to iterate over batches of - # `untracked_files_for_uploads` rows. - class UntrackedFile < ActiveRecord::Base - include EachBatch - - self.table_name = 'untracked_files_for_uploads' - end - - def perform - ensure_temporary_tracking_table_exists - - # Since Postgres < 9.5 does not have ON CONFLICT DO NOTHING, and since - # doing inserts-if-not-exists without ON CONFLICT DO NOTHING would be - # slow, start with an empty table for Postgres < 9.5. - # That way we can do bulk inserts at ~30x the speed of individual - # inserts (~20 minutes worth of inserts at GitLab.com scale instead of - # ~10 hours). - # In all other cases, installations will get both bulk inserts and the - # ability for these jobs to retry without having to clear and reinsert. - clear_untracked_file_paths unless can_bulk_insert_and_ignore_duplicates? - - store_untracked_file_paths - - if UntrackedFile.all.empty? - drop_temp_table - else - schedule_populate_untracked_uploads_jobs - end - end - - private - - def ensure_temporary_tracking_table_exists - table_name = :untracked_files_for_uploads - - unless ActiveRecord::Base.connection.table_exists?(table_name) - UntrackedFile.connection.create_table table_name do |t| - t.string :path, limit: 600, null: false - t.index :path, unique: true - end - end - end - - def clear_untracked_file_paths - UntrackedFile.delete_all - end - - def store_untracked_file_paths - return unless Dir.exist?(ABSOLUTE_UPLOAD_DIR) - - each_file_batch(ABSOLUTE_UPLOAD_DIR, FIND_BATCH_SIZE) do |file_paths| - insert_file_paths(file_paths) - end - end - - def each_file_batch(search_dir, batch_size, &block) - cmd = build_find_command(search_dir) - - Open3.popen2(*cmd) do |stdin, stdout, status_thread| - yield_paths_in_batches(stdout, batch_size, &block) - - raise "Find command failed" unless status_thread.value.success? - end - end - - def yield_paths_in_batches(stdout, batch_size, &block) - paths = [] - - stdout.each_line("\0") do |line| - paths << line.chomp("\0").sub(START_WITH_ROOT_REGEX, '') - - if paths.size >= batch_size - yield(paths) - paths = [] - end - end - - yield(paths) if paths.any? - end - - def build_find_command(search_dir) - cmd = %W[find -L #{search_dir} - -type f - ! ( -path #{EXCLUDED_HASHED_UPLOADS_PATH} -prune ) - ! ( -path #{EXCLUDED_TMP_UPLOADS_PATH} -prune ) - -print0] - - ionice = which_ionice - cmd = %W[#{ionice} -c Idle] + cmd if ionice - - log_msg = "PrepareUntrackedUploads find command: \"#{cmd.join(' ')}\"" - Rails.logger.info log_msg # rubocop:disable Gitlab/RailsLogger - - cmd - end - - def which_ionice - Gitlab::Utils.which('ionice') - rescue StandardError - # In this case, returning false is relatively safe, - # even though it isn't very nice - false - end - - def insert_file_paths(file_paths) - sql = insert_sql(file_paths) - - ActiveRecord::Base.connection.execute(sql) - end - - def insert_sql(file_paths) - if postgresql_pre_9_5? - "INSERT INTO #{table_columns_and_values_for_insert(file_paths)};" - else - "INSERT INTO #{table_columns_and_values_for_insert(file_paths)}"\ - " ON CONFLICT DO NOTHING;" - end - end - - def table_columns_and_values_for_insert(file_paths) - values = file_paths.map do |file_path| - ActiveRecord::Base.send(:sanitize_sql_array, ['(?)', file_path]) # rubocop:disable GitlabSecurity/PublicSend - end.join(', ') - - "#{UntrackedFile.table_name} (path) VALUES #{values}" - end - - def can_bulk_insert_and_ignore_duplicates? - !postgresql_pre_9_5? - end - - def postgresql_pre_9_5? - strong_memoize(:postgresql_pre_9_5) do - Gitlab::Database.version.to_f < 9.5 - end - end - - def schedule_populate_untracked_uploads_jobs - bulk_queue_background_migration_jobs_by_range( - UntrackedFile, FOLLOW_UP_MIGRATION) - end - - def drop_temp_table - unless Rails.env.test? # Dropping a table intermittently breaks test cleanup - UntrackedFile.connection.drop_table(:untracked_files_for_uploads, - if_exists: true) - end - end - end - end -end diff --git a/lib/gitlab/background_migration/remove_restricted_todos.rb b/lib/gitlab/background_migration/remove_restricted_todos.rb deleted file mode 100644 index 9ef6d8654ae..00000000000 --- a/lib/gitlab/background_migration/remove_restricted_todos.rb +++ /dev/null @@ -1,158 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation -# rubocop:disable Metrics/ClassLength - -module Gitlab - module BackgroundMigration - class RemoveRestrictedTodos - PRIVATE_FEATURE = 10 - PRIVATE_PROJECT = 0 - - class Project < ActiveRecord::Base - self.table_name = 'projects' - end - - class ProjectAuthorization < ActiveRecord::Base - self.table_name = 'project_authorizations' - end - - class ProjectFeature < ActiveRecord::Base - self.table_name = 'project_features' - end - - class Todo < ActiveRecord::Base - include EachBatch - - self.table_name = 'todos' - end - - class Issue < ActiveRecord::Base - include EachBatch - - self.table_name = 'issues' - end - - def perform(start_id, stop_id) - projects = Project.where('EXISTS (SELECT 1 FROM todos WHERE todos.project_id = projects.id)') - .where(id: start_id..stop_id) - - projects.each do |project| - remove_confidential_issue_todos(project.id) - - if project.visibility_level == PRIVATE_PROJECT - remove_non_members_todos(project.id) - else - remove_restricted_features_todos(project.id) - end - end - end - - private - - def remove_non_members_todos(project_id) - batch_remove_todos_cte(project_id) - end - - def remove_confidential_issue_todos(project_id) - # min access level to access a confidential issue is reporter - min_reporters = authorized_users(project_id) - .select(:user_id) - .where('access_level >= ?', 20) - - confidential_issues = Issue.select(:id, :author_id).where(confidential: true, project_id: project_id) - confidential_issues.each_batch(of: 100, order_hint: :confidential) do |batch| - batch.each do |issue| - assigned_users = IssueAssignee.select(:user_id).where(issue_id: issue.id) - - todos = Todo.where(target_type: 'Issue', target_id: issue.id) - .where('user_id NOT IN (?)', min_reporters) - .where('user_id NOT IN (?)', assigned_users) - todos = todos.where('user_id != ?', issue.author_id) if issue.author_id - - todos.delete_all - end - end - end - - def remove_restricted_features_todos(project_id) - ProjectFeature.where(project_id: project_id).each do |project_features| - target_types = [] - target_types << 'Issue' if private?(project_features.issues_access_level) - target_types << 'MergeRequest' if private?(project_features.merge_requests_access_level) - target_types << 'Commit' if private?(project_features.repository_access_level) - - next if target_types.empty? - - batch_remove_todos_cte(project_id, target_types) - end - end - - def private?(feature_level) - feature_level == PRIVATE_FEATURE - end - - def authorized_users(project_id) - ProjectAuthorization.select(:user_id).where(project_id: project_id) - end - - def unauthorized_project_todos(project_id) - Todo.where(project_id: project_id) - .where('user_id NOT IN (?)', authorized_users(project_id)) - end - - def batch_remove_todos_cte(project_id, target_types = nil) - loop do - count = remove_todos_cte(project_id, target_types) - - break if count == 0 - end - end - - def remove_todos_cte(project_id, target_types = nil) - sql = [] - sql << with_all_todos_sql(project_id, target_types) - sql << as_deleted_sql - sql << "SELECT count(*) FROM deleted" - - result = Todo.connection.exec_query(sql.join(' ')) - result.rows[0][0].to_i - end - - def with_all_todos_sql(project_id, target_types = nil) - if target_types - table = Arel::Table.new(:todos) - in_target = table[:target_type].in(target_types) - target_types_sql = " AND #{in_target.to_sql}" - end - - <<-SQL - WITH all_todos AS ( - SELECT id - FROM "todos" - WHERE "todos"."project_id" = #{project_id} - AND (user_id NOT IN ( - SELECT "project_authorizations"."user_id" - FROM "project_authorizations" - WHERE "project_authorizations"."project_id" = #{project_id}) - #{target_types_sql} - ) - ), - SQL - end - - def as_deleted_sql - <<-SQL - deleted AS ( - DELETE FROM todos - WHERE id IN ( - SELECT id - FROM all_todos - LIMIT 5000 - ) - RETURNING id - ) - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/set_confidential_note_events_on_services.rb b/lib/gitlab/background_migration/set_confidential_note_events_on_services.rb deleted file mode 100644 index bc434b0cb64..00000000000 --- a/lib/gitlab/background_migration/set_confidential_note_events_on_services.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - # Ensures services which previously received all notes events continue - # to receive confidential ones. - class SetConfidentialNoteEventsOnServices - class Service < ActiveRecord::Base - self.table_name = 'services' - - include ::EachBatch - - def self.services_to_update - where(confidential_note_events: nil, note_events: true) - end - end - - def perform(start_id, stop_id) - Service.services_to_update - .where(id: start_id..stop_id) - .update_all(confidential_note_events: true) - end - end - end -end diff --git a/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks.rb b/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks.rb deleted file mode 100644 index 28d8d2c640b..00000000000 --- a/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - # Ensures hooks which previously received all notes events continue - # to receive confidential ones. - class SetConfidentialNoteEventsOnWebhooks - class WebHook < ActiveRecord::Base - self.table_name = 'web_hooks' - - include ::EachBatch - - def self.hooks_to_update - where(confidential_note_events: nil, note_events: true) - end - end - - def perform(start_id, stop_id) - WebHook.hooks_to_update - .where(id: start_id..stop_id) - .update_all(confidential_note_events: true) - end - end - end -end diff --git a/lib/gitlab/background_migration/set_merge_request_diff_files_count.rb b/lib/gitlab/background_migration/set_merge_request_diff_files_count.rb new file mode 100644 index 00000000000..9f765d03d62 --- /dev/null +++ b/lib/gitlab/background_migration/set_merge_request_diff_files_count.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Sets the MergeRequestDiff#files_count value for old rows + class SetMergeRequestDiffFilesCount + COUNT_SUBQUERY = <<~SQL + files_count = ( + SELECT count(*) + FROM merge_request_diff_files + WHERE merge_request_diff_files.merge_request_diff_id = merge_request_diffs.id + ) + SQL + + class MergeRequestDiff < ActiveRecord::Base # rubocop:disable Style/Documentation + include EachBatch + + self.table_name = 'merge_request_diffs' + end + + def perform(start_id, end_id) + MergeRequestDiff.where(id: start_id..end_id).each_batch do |relation| + relation.update_all(COUNT_SUBQUERY) + end + end + end + end +end diff --git a/lib/gitlab/background_migration/set_null_external_diff_store_to_local_value.rb b/lib/gitlab/background_migration/set_null_external_diff_store_to_local_value.rb new file mode 100644 index 00000000000..71f3483987e --- /dev/null +++ b/lib/gitlab/background_migration/set_null_external_diff_store_to_local_value.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # This class is responsible for migrating a range of merge request diffs + # with external_diff_store == NULL to 1. + # + # The index `index_merge_request_diffs_external_diff_store_is_null` is + # expected to be used to find the rows here and in the migration scheduling + # the jobs that run this class. + class SetNullExternalDiffStoreToLocalValue + LOCAL_STORE = 1 # equal to ObjectStorage::Store::LOCAL + + # Temporary AR class for merge request diffs + class MergeRequestDiff < ActiveRecord::Base + self.table_name = 'merge_request_diffs' + end + + def perform(start_id, stop_id) + MergeRequestDiff.where(external_diff_store: nil, id: start_id..stop_id).update_all(external_diff_store: LOCAL_STORE) + end + end + end +end diff --git a/lib/gitlab/background_migration/set_null_package_files_file_store_to_local_value.rb b/lib/gitlab/background_migration/set_null_package_files_file_store_to_local_value.rb new file mode 100644 index 00000000000..9ac92aab637 --- /dev/null +++ b/lib/gitlab/background_migration/set_null_package_files_file_store_to_local_value.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # This class is responsible for migrating a range of package files + # with file_store == NULL to 1. + # + # The index `index_packages_package_files_file_store_is_null` is + # expected to be used to find the rows here and in the migration scheduling + # the jobs that run this class. + class SetNullPackageFilesFileStoreToLocalValue + LOCAL_STORE = 1 # equal to ObjectStorage::Store::LOCAL + + # Temporary AR class for package files + class PackageFile < ActiveRecord::Base + self.table_name = 'packages_package_files' + end + + def perform(start_id, stop_id) + Packages::PackageFile.where(file_store: nil, id: start_id..stop_id).update_all(file_store: LOCAL_STORE) + end + end + end +end diff --git a/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb b/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb index d71a50a0af6..b3876018553 100644 --- a/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb +++ b/lib/gitlab/background_migration/user_mentions/create_resource_user_mention.rb @@ -12,26 +12,22 @@ module Gitlab ISOLATION_MODULE = 'Gitlab::BackgroundMigration::UserMentions::Models' def perform(resource_model, join, conditions, with_notes, start_id, end_id) + return unless Feature.enabled?(:migrate_user_mentions, default_enabled: true) + resource_model = "#{ISOLATION_MODULE}::#{resource_model}".constantize if resource_model.is_a?(String) model = with_notes ? Gitlab::BackgroundMigration::UserMentions::Models::Note : resource_model resource_user_mention_model = resource_model.user_mention_model records = model.joins(join).where(conditions).where(id: start_id..end_id) - records.in_groups_of(BULK_INSERT_SIZE, false).each do |records| + records.each_batch(of: BULK_INSERT_SIZE) do |records| mentions = [] records.each do |record| mention_record = record.build_mention_values(resource_user_mention_model.resource_foreign_key) mentions << mention_record unless mention_record.blank? end - Gitlab::Database.bulk_insert( # rubocop:disable Gitlab/BulkInsert - resource_user_mention_model.table_name, - mentions, - return_ids: true, - disable_quote: resource_model.no_quote_columns, - on_conflict: :do_nothing - ) + resource_user_mention_model.insert_all(mentions) unless mentions.empty? end end end diff --git a/lib/gitlab/background_migration/user_mentions/models/commit.rb b/lib/gitlab/background_migration/user_mentions/models/commit.rb index 279e93dbf0d..65f4a7a25b6 100644 --- a/lib/gitlab/background_migration/user_mentions/models/commit.rb +++ b/lib/gitlab/background_migration/user_mentions/models/commit.rb @@ -6,6 +6,7 @@ module Gitlab module UserMentions module Models class Commit + include EachBatch include Concerns::IsolatedMentionable include Concerns::MentionableMigrationMethods diff --git a/lib/gitlab/background_migration/user_mentions/models/design_management/design.rb b/lib/gitlab/background_migration/user_mentions/models/design_management/design.rb index 0cdfc6447c7..bdb90b5d2b9 100644 --- a/lib/gitlab/background_migration/user_mentions/models/design_management/design.rb +++ b/lib/gitlab/background_migration/user_mentions/models/design_management/design.rb @@ -7,6 +7,7 @@ module Gitlab module Models module DesignManagement class Design < ActiveRecord::Base + include EachBatch include Concerns::MentionableMigrationMethods def self.user_mention_model diff --git a/lib/gitlab/background_migration/user_mentions/models/epic.rb b/lib/gitlab/background_migration/user_mentions/models/epic.rb index dc2b7819800..61d9244a4c9 100644 --- a/lib/gitlab/background_migration/user_mentions/models/epic.rb +++ b/lib/gitlab/background_migration/user_mentions/models/epic.rb @@ -6,6 +6,7 @@ module Gitlab module UserMentions module Models class Epic < ActiveRecord::Base + include EachBatch include Concerns::IsolatedMentionable include Concerns::MentionableMigrationMethods include CacheMarkdownField diff --git a/lib/gitlab/background_migration/user_mentions/models/merge_request.rb b/lib/gitlab/background_migration/user_mentions/models/merge_request.rb index 655c1db71ae..6b52afea17c 100644 --- a/lib/gitlab/background_migration/user_mentions/models/merge_request.rb +++ b/lib/gitlab/background_migration/user_mentions/models/merge_request.rb @@ -6,6 +6,7 @@ module Gitlab module UserMentions module Models class MergeRequest < ActiveRecord::Base + include EachBatch include Concerns::IsolatedMentionable include CacheMarkdownField include Concerns::MentionableMigrationMethods diff --git a/lib/gitlab/background_migration/user_mentions/models/note.rb b/lib/gitlab/background_migration/user_mentions/models/note.rb index c32292ad704..a3224c8c456 100644 --- a/lib/gitlab/background_migration/user_mentions/models/note.rb +++ b/lib/gitlab/background_migration/user_mentions/models/note.rb @@ -6,6 +6,7 @@ module Gitlab module UserMentions module Models class Note < ActiveRecord::Base + include EachBatch include Concerns::IsolatedMentionable include CacheMarkdownField |