diff --git a/db/post_migrate/20230811145848_ensure_commit_user_mentions_note_id_bigint_backfill_is_finished_for_self_managed.rb b/db/post_migrate/20230811145848_ensure_commit_user_mentions_note_id_bigint_backfill_is_finished_for_self_managed.rb new file mode 100644 index 0000000000000000000000000000000000000000..9510e2673dcff5e0dac76b350fedecd94c94e0cb --- /dev/null +++ b/db/post_migrate/20230811145848_ensure_commit_user_mentions_note_id_bigint_backfill_is_finished_for_self_managed.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class EnsureCommitUserMentionsNoteIdBigintBackfillIsFinishedForSelfManaged < Gitlab::Database::Migration[2.1] + include Gitlab::Database::MigrationHelpers::ConvertToBigint + + restrict_gitlab_migration gitlab_schema: :gitlab_main + disable_ddl_transaction! + + def up + return if com_or_dev_or_test_but_not_jh? + + ensure_batched_background_migration_is_finished( + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: 'commit_user_mentions', + column_name: 'id', + job_arguments: [['note_id'], ['note_id_convert_to_bigint']] + ) + end + + def down + # no-op + end +end diff --git a/db/post_migrate/20230811150636_swap_commit_user_mentions_note_id_to_bigint_for_self_managed.rb b/db/post_migrate/20230811150636_swap_commit_user_mentions_note_id_to_bigint_for_self_managed.rb new file mode 100644 index 0000000000000000000000000000000000000000..e98cf61db989c4d8632f58129e3b4fb36c98d670 --- /dev/null +++ b/db/post_migrate/20230811150636_swap_commit_user_mentions_note_id_to_bigint_for_self_managed.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +class SwapCommitUserMentionsNoteIdToBigintForSelfManaged < Gitlab::Database::Migration[2.1] + include Gitlab::Database::MigrationHelpers::ConvertToBigint + + disable_ddl_transaction! + + TABLE_NAME = 'commit_user_mentions' + + def up + return if com_or_dev_or_test_but_not_jh? + return if temp_column_removed?(TABLE_NAME, :note_id) + return if columns_swapped?(TABLE_NAME, :note_id) + + swap + end + + def down + return if com_or_dev_or_test_but_not_jh? + return if temp_column_removed?(TABLE_NAME, :note_id) + return unless columns_swapped?(TABLE_NAME, :note_id) + + swap + end + + # Same as db/post_migrate/20230321003252_swap_commit_user_mentions_note_id_to_bigint_for_gitlab_dot_com.rb + def swap + # This will replace the existing commit_user_mentions_on_commit_id_and_note_id_unique_index + add_concurrent_index TABLE_NAME, [:commit_id, :note_id_convert_to_bigint], unique: true, + name: 'commit_user_mentions_on_commit_id_and_note_id_convert_to_bigint' + + # This will replace the existing index_commit_user_mentions_on_note_id + add_concurrent_index TABLE_NAME, :note_id_convert_to_bigint, unique: true, + name: 'index_commit_user_mentions_on_note_id_convert_to_bigint' + + # This will replace the existing fk_rails_a6760813e0 + add_concurrent_foreign_key TABLE_NAME, :notes, column: :note_id_convert_to_bigint, + name: 'fk_commit_user_mentions_note_id_convert_to_bigint', + on_delete: :cascade + + with_lock_retries(raise_on_exhaustion: true) do + execute "LOCK TABLE notes, #{TABLE_NAME} IN ACCESS EXCLUSIVE MODE" + + execute "ALTER TABLE #{TABLE_NAME} RENAME COLUMN note_id TO note_id_tmp" + execute "ALTER TABLE #{TABLE_NAME} RENAME COLUMN note_id_convert_to_bigint TO note_id" + execute "ALTER TABLE #{TABLE_NAME} RENAME COLUMN note_id_tmp TO note_id_convert_to_bigint" + + function_name = Gitlab::Database::UnidirectionalCopyTrigger + .on_table(TABLE_NAME, connection: connection) + .name(:note_id, :note_id_convert_to_bigint) + execute "ALTER FUNCTION #{quote_table_name(function_name)} RESET ALL" + + # Swap defaults + change_column_default TABLE_NAME, :note_id, nil + change_column_default TABLE_NAME, :note_id_convert_to_bigint, 0 + + execute 'DROP INDEX IF EXISTS commit_user_mentions_on_commit_id_and_note_id_unique_index' + rename_index TABLE_NAME, 'commit_user_mentions_on_commit_id_and_note_id_convert_to_bigint', + 'commit_user_mentions_on_commit_id_and_note_id_unique_index' + + execute 'DROP INDEX IF EXISTS index_commit_user_mentions_on_note_id' + rename_index TABLE_NAME, 'index_commit_user_mentions_on_note_id_convert_to_bigint', + 'index_commit_user_mentions_on_note_id' + + execute "ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT IF EXISTS fk_rails_a6760813e0" + rename_constraint(TABLE_NAME, 'fk_commit_user_mentions_note_id_convert_to_bigint', 'fk_rails_a6760813e0') + end + end +end diff --git a/db/schema_migrations/20230811145848 b/db/schema_migrations/20230811145848 new file mode 100644 index 0000000000000000000000000000000000000000..8aac90ce7229645d5c3f0317d5acf372ef73d27c --- /dev/null +++ b/db/schema_migrations/20230811145848 @@ -0,0 +1 @@ +d97af224c290fa6a16b6c855e51e7f65a64301c70ec7e222e1db76a6501ca341 \ No newline at end of file diff --git a/db/schema_migrations/20230811150636 b/db/schema_migrations/20230811150636 new file mode 100644 index 0000000000000000000000000000000000000000..d0e166030aa00456bc326ea181d9c7f7124e1418 --- /dev/null +++ b/db/schema_migrations/20230811150636 @@ -0,0 +1 @@ +311166d35fd7d55c27146309637e8ac8dafc1ad2988a6da7f6d1105f0368248d \ No newline at end of file diff --git a/spec/migrations/ensure_commit_user_mentions_note_id_bigint_backfill_is_finished_for_self_managed_spec.rb b/spec/migrations/ensure_commit_user_mentions_note_id_bigint_backfill_is_finished_for_self_managed_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b281403204bf53882ebd41855f2d189d546d76fa --- /dev/null +++ b/spec/migrations/ensure_commit_user_mentions_note_id_bigint_backfill_is_finished_for_self_managed_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe EnsureCommitUserMentionsNoteIdBigintBackfillIsFinishedForSelfManaged, feature_category: :database do + describe '#up' do + let(:migration_arguments) do + { + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: 'commit_user_mentions', + column_name: 'id', + job_arguments: [['note_id'], ['note_id_convert_to_bigint']] + } + end + + it 'ensures the migration is completed for self-managed instances' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + expect(instance).to receive(:ensure_batched_background_migration_is_finished).with(migration_arguments) + end + + migrate! + end + + it 'skips the check for GitLab.com, dev, or test' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + expect(instance).not_to receive(:ensure_batched_background_migration_is_finished) + end + + migrate! + end + end +end diff --git a/spec/migrations/swap_commit_user_mentions_note_id_to_bigint_for_self_managed_spec.rb b/spec/migrations/swap_commit_user_mentions_note_id_to_bigint_for_self_managed_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b35da2f78db445244775c24f67e9a4cc34ac3053 --- /dev/null +++ b/spec/migrations/swap_commit_user_mentions_note_id_to_bigint_for_self_managed_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapCommitUserMentionsNoteIdToBigintForSelfManaged, feature_category: :database do + let(:connection) { described_class.new.connection } + let(:commit_user_mentions) { table(:commit_user_mentions) } + + shared_examples 'column `note_id_convert_to_bigint` is already dropped' do + before do + connection.execute('ALTER TABLE commit_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute('ALTER TABLE commit_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + commit_user_mentions.reset_column_information + + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + + migration.after -> { + commit_user_mentions.reset_column_information + + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }).to be_nil + } + end + end + end + end + + describe '#up' do + before do + # rubocop:disable RSpec/AnyInstanceOf + allow_any_instance_of(described_class).to( + receive(:com_or_dev_or_test_but_not_jh?).and_return(com_or_dev_or_test_but_not_jh?) + ) + # rubocop:enable RSpec/AnyInstanceOf + end + + context 'when GitLab.com, dev, or test' do + let(:com_or_dev_or_test_but_not_jh?) { true } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance with the `note_id_convert_to_bigint` column already dropped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + it_behaves_like 'column `note_id_convert_to_bigint` is already dropped' + end + + context 'when self-managed instance columns already swapped' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE commit_user_mentions ALTER COLUMN note_id TYPE bigint') + connection.execute( + 'ALTER TABLE commit_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint integer' + ) + + disable_migrations_output { migrate! } + end + + after do + connection.execute('ALTER TABLE commit_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'does not swaps the columns' do + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('integer') + ) + end + end + + context 'when self-managed instance' do + let(:com_or_dev_or_test_but_not_jh?) { false } + + before do + connection.execute('ALTER TABLE commit_user_mentions ALTER COLUMN note_id TYPE integer') + connection.execute('ALTER TABLE commit_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint bigint') + connection.execute('ALTER TABLE commit_user_mentions ALTER COLUMN note_id_convert_to_bigint TYPE bigint') + connection.execute('DROP INDEX IF EXISTS index_commit_user_mentions_on_note_id_convert_to_bigint CASCADE') + connection.execute('CREATE OR REPLACE FUNCTION trigger_17c3a95ee58a() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN NEW."note_id_convert_to_bigint" := NEW."note_id"; RETURN NEW; END; $$;') + end + + after do + connection.execute('ALTER TABLE commit_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint') + end + + it 'swaps the columns' do + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + commit_user_mentions.reset_column_information + + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('bigint') + ) + } + + migration.after -> { + commit_user_mentions.reset_column_information + + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(commit_user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to( + eq('integer') + ) + } + end + end + end + end + end +end