Commit 9e273eae authored by Andreas Brandl's avatar Andreas Brandl

Add migration to create missing project features

Relates to: https://gitlab.com/gitlab-org/gitlab/issues/34367
parent 4505da2c
---
title: Ensure all Project records have a corresponding ProjectFeature record
merge_request: 23766
author:
type: fixed
# frozen_string_literal: true
class FixProjectsWithoutProjectFeature < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
BATCH_SIZE = 100_000
disable_ddl_transaction!
class Project < ActiveRecord::Base
include EachBatch
end
def up
queue_background_migration_jobs_by_range_at_intervals(Project, 'FixProjectsWithoutProjectFeature', 2.minutes, batch_size: BATCH_SIZE)
end
def down
# no-op
end
end
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Project model for this migration
class Project < ActiveRecord::Base
include EachBatch
end
ENABLED = 20
INSERT_BATCH_SIZE = 10_000
# This migration creates missing project_features records
# for the projects within the given range of ids
class FixProjectsWithoutProjectFeature
def perform(from_id, to_id)
Project.transaction do
projects = Project.where(<<~SQL, from_id, to_id)
projects.id BETWEEN ? AND ?
AND NOT EXISTS (
SELECT 1 FROM project_features
WHERE project_features.project_id = projects.id
)
SQL
projects.each_batch(of: INSERT_BATCH_SIZE) do |batch|
insert_missing_records(batch)
end
end
end
private
def insert_missing_records(projects)
features = projects.map do |project|
record = {
project_id: project.id,
merge_requests_access_level: ENABLED,
issues_access_level: ENABLED,
wiki_access_level: ENABLED,
snippets_access_level: ENABLED,
builds_access_level: ENABLED,
repository_access_level: ENABLED,
pages_access_level: ENABLED,
forking_access_level: ENABLED
}
record['created_at'] = record['updated_at'] = Time.now.to_s(:db)
record
end
Gitlab::Database.bulk_insert(:project_features, features, on_conflict: :do_nothing)
logger.info(message: "FixProjectsWithoutProjectFeature: created missing project_features for Projects #{projects.map(&:id).join(', ')}")
end
def logger
@logger ||= Gitlab::BackgroundMigration::Logger.build
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, :migration, schema: 2020_01_27_111840 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:project_features) { table(:project_features) }
let(:namespace) { namespaces.create(name: 'foo', path: 'foo') }
let!(:project) { projects.create!(namespace_id: namespace.id) }
let!(:projects_without_feature) { [projects.create!(namespace_id: namespace.id), projects.create!(namespace_id: namespace.id)] }
before do
project_features.create({ project_id: project.id, pages_access_level: 20 })
end
subject { described_class.new.perform(*project_range) }
def project_feature_records
ActiveRecord::Base.connection.select_all('SELECT project_id FROM project_features ORDER BY project_id').map { |e| e['project_id'] }
end
def project_range
ActiveRecord::Base.connection.select_one('SELECT MIN(id), MAX(id) FROM projects').values
end
it 'creates a default ProjectFeature for projects without it' do
expect { subject }.to change { project_feature_records }.from([project.id]).to([project.id, *projects_without_feature.map(&:id)])
end
it 'sets created_at/updated_at timestamps' do
subject
offenders = ActiveRecord::Base.connection.select_all('SELECT 1 FROM project_features WHERE created_at IS NULL or updated_at IS NULL')
expect(offenders).to be_empty
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment