diff --git a/db/post_migrate/20230717144729_drop_ci_job_artifacts_partition_id_default_v2.rb b/db/post_migrate/20230717144729_drop_ci_job_artifacts_partition_id_default_v2.rb new file mode 100644 index 0000000000000000000000000000000000000000..a3b2cd324fb8be6eaeb476ff72f91da5863b5b56 --- /dev/null +++ b/db/post_migrate/20230717144729_drop_ci_job_artifacts_partition_id_default_v2.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class DropCiJobArtifactsPartitionIdDefaultV2 < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + TABLE_NAME = :ci_job_artifacts + COLUMN_NAME = :partition_id + + def up + remove_column_default(TABLE_NAME, COLUMN_NAME) + end + + def down + change_column_default(TABLE_NAME, COLUMN_NAME, from: nil, to: 100) + end +end diff --git a/db/post_migrate/20230717144744_drop_ci_stages_partition_id_default_v2.rb b/db/post_migrate/20230717144744_drop_ci_stages_partition_id_default_v2.rb new file mode 100644 index 0000000000000000000000000000000000000000..d32b871828161a1ea79394720a99bd8259b045fc --- /dev/null +++ b/db/post_migrate/20230717144744_drop_ci_stages_partition_id_default_v2.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class DropCiStagesPartitionIdDefaultV2 < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + TABLE_NAME = :ci_stages + COLUMN_NAME = :partition_id + + def up + remove_column_default(TABLE_NAME, COLUMN_NAME) + end + + def down + change_column_default(TABLE_NAME, COLUMN_NAME, from: nil, to: 100) + end +end diff --git a/db/post_migrate/20230717144802_drop_ci_build_trace_metadata_partition_id_default_v2.rb b/db/post_migrate/20230717144802_drop_ci_build_trace_metadata_partition_id_default_v2.rb new file mode 100644 index 0000000000000000000000000000000000000000..e189801c79b27af1f8ef40112a744f1f00ad5d0d --- /dev/null +++ b/db/post_migrate/20230717144802_drop_ci_build_trace_metadata_partition_id_default_v2.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class DropCiBuildTraceMetadataPartitionIdDefaultV2 < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + TABLE_NAME = :ci_build_trace_metadata + COLUMN_NAME = :partition_id + + def up + remove_column_default(TABLE_NAME, COLUMN_NAME) + end + + def down + change_column_default(TABLE_NAME, COLUMN_NAME, from: nil, to: 100) + end +end diff --git a/db/post_migrate/20230717144817_drop_ci_pipeline_variable_partition_id_default_v2.rb b/db/post_migrate/20230717144817_drop_ci_pipeline_variable_partition_id_default_v2.rb new file mode 100644 index 0000000000000000000000000000000000000000..c75c0226b15d0b173ffeb46c30fb0e0dbd77b261 --- /dev/null +++ b/db/post_migrate/20230717144817_drop_ci_pipeline_variable_partition_id_default_v2.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class DropCiPipelineVariablePartitionIdDefaultV2 < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + TABLE_NAME = :ci_pipeline_variables + COLUMN_NAME = :partition_id + + def up + remove_column_default(TABLE_NAME, COLUMN_NAME) + end + + def down + change_column_default(TABLE_NAME, COLUMN_NAME, from: nil, to: 100) + end +end diff --git a/db/schema_migrations/20230717144729 b/db/schema_migrations/20230717144729 new file mode 100644 index 0000000000000000000000000000000000000000..786c5fe295beb1cd2b5a2b128629439ac0197248 --- /dev/null +++ b/db/schema_migrations/20230717144729 @@ -0,0 +1 @@ +a9264babc5e84597c2f8fb2fb2a3df4dceb0f6fe83877d18df03e679c77afa1b \ No newline at end of file diff --git a/db/schema_migrations/20230717144744 b/db/schema_migrations/20230717144744 new file mode 100644 index 0000000000000000000000000000000000000000..c643d8d90dcf4dc67f62745ce6497f7556fadb71 --- /dev/null +++ b/db/schema_migrations/20230717144744 @@ -0,0 +1 @@ +2ed2825eb5a1adaf1456096c84c0bc4016374e5525defa31b8c1393a8d864007 \ No newline at end of file diff --git a/db/schema_migrations/20230717144802 b/db/schema_migrations/20230717144802 new file mode 100644 index 0000000000000000000000000000000000000000..12494fd21043706fcdb482d2d49b03b8d1326e4e --- /dev/null +++ b/db/schema_migrations/20230717144802 @@ -0,0 +1 @@ +eed3845b0321cdb69c7dfe8e54dda940362f1bc99405727eb252744cda254522 \ No newline at end of file diff --git a/db/schema_migrations/20230717144817 b/db/schema_migrations/20230717144817 new file mode 100644 index 0000000000000000000000000000000000000000..d16daa2fad6d4b0f1ce28a12b1fe412b635fb8e5 --- /dev/null +++ b/db/schema_migrations/20230717144817 @@ -0,0 +1 @@ +18f72f73afe0fb027df28f6bc915f8afc1460258b08038055ee9c48a6d2bfbeb \ No newline at end of file diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 256c524e989d5c6457debea662a2c6740e7615dc..60cec12b4b5aa720384918d93aab07b523b88d5f 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -1191,6 +1191,19 @@ def table_partitioned?(table_name) .present? end + # While it is safe to call `change_column_default` on a column without + # default it would still require access exclusive lock on the table + # and for tables with high autovacuum(wraparound prevention) it will + # fail if their executions overlap. + # + def remove_column_default(table_name, column_name) + column = connection.columns(table_name).find { |col| col.name == column_name.to_s } + + if column.default || column.default_function + change_column_default(table_name, column_name, to: nil) + end + end + private def multiple_columns(columns, separator: ', ') diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index b1e8301d69f610501775b9434ff5e6a8bec90de1..f3c181db3aacbb83ffe527ad0f79113c4969e96c 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -2867,4 +2867,43 @@ def setup it { is_expected.to be_falsey } end end + + describe '#remove_column_default' do + let(:test_table) { :_test_defaults_table } + let(:drop_default_statement) do + /ALTER TABLE "#{test_table}" ALTER COLUMN "#{column_name}" SET DEFAULT NULL/ + end + + subject(:recorder) do + ActiveRecord::QueryRecorder.new do + model.remove_column_default(test_table, column_name) + end + end + + before do + model.create_table(test_table) do |t| + t.integer :int_with_default, default: 100 + t.integer :int_with_default_function, default: -> { 'ceil(random () * 100)::int' } + t.integer :int_without_default + end + end + + context 'with default values' do + let(:column_name) { :int_with_default } + + it { expect(recorder.log).to include(drop_default_statement) } + end + + context 'with default functions' do + let(:column_name) { :int_with_default_function } + + it { expect(recorder.log).to include(drop_default_statement) } + end + + context 'without any defaults' do + let(:column_name) { :int_without_default } + + it { expect(recorder.log).to be_empty } + end + end end