From 1aa273c0c7df5125bc12169375b456483852105d Mon Sep 17 00:00:00 2001
From: Max Orefice <morefice@gitlab.com>
Date: Tue, 29 Oct 2024 16:09:40 +0000
Subject: [PATCH] Add ci_deleted_objects#project_id not null constraint

This commit adds a new not nul constraint at the database level
to prevent creating records without a project.

Changelog: other
---
 db/docs/ci_deleted_objects.yml                |  3 +-
 ...e_backfill_ci_deleted_object_project_id.rb | 23 ++++++++++++
 ...pdate_invalid_ci_deleted_object_records.rb | 27 ++++++++++++++
 ...d_not_null_ci_deleted_object_project_id.rb | 17 +++++++++
 db/schema_migrations/20241028085040           |  1 +
 db/schema_migrations/20241028085044           |  1 +
 db/schema_migrations/20241028085339           |  1 +
 db/structure.sql                              |  3 +-
 spec/lib/gitlab/database/sharding_key_spec.rb |  1 +
 ..._invalid_ci_deleted_object_records_spec.rb | 36 +++++++++++++++++++
 10 files changed, 111 insertions(+), 2 deletions(-)
 create mode 100644 db/post_migrate/20241028085040_finalize_backfill_ci_deleted_object_project_id.rb
 create mode 100644 db/post_migrate/20241028085044_update_invalid_ci_deleted_object_records.rb
 create mode 100644 db/post_migrate/20241028085339_add_not_null_ci_deleted_object_project_id.rb
 create mode 100644 db/schema_migrations/20241028085040
 create mode 100644 db/schema_migrations/20241028085044
 create mode 100644 db/schema_migrations/20241028085339
 create mode 100644 spec/migrations/20241028085044_update_invalid_ci_deleted_object_records_spec.rb

diff --git a/db/docs/ci_deleted_objects.yml b/db/docs/ci_deleted_objects.yml
index 3bdf58b31850a..f746304b0b45c 100644
--- a/db/docs/ci_deleted_objects.yml
+++ b/db/docs/ci_deleted_objects.yml
@@ -8,4 +8,5 @@ description: Allows efficient batch deletion of data in object storage.
 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/9bf76fe03f8edf4f67023448161af27abb8fb521
 milestone: '13.5'
 gitlab_schema: gitlab_ci
-sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/463245
+sharding_key:
+  project_id: projects
diff --git a/db/post_migrate/20241028085040_finalize_backfill_ci_deleted_object_project_id.rb b/db/post_migrate/20241028085040_finalize_backfill_ci_deleted_object_project_id.rb
new file mode 100644
index 0000000000000..d8860b2809748
--- /dev/null
+++ b/db/post_migrate/20241028085040_finalize_backfill_ci_deleted_object_project_id.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class FinalizeBackfillCiDeletedObjectProjectId < Gitlab::Database::Migration[2.2]
+  milestone '17.6'
+  disable_ddl_transaction!
+  restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+  MIGRATION = 'FixPickUpAtCiDeletedObject'
+
+  def up
+    ensure_batched_background_migration_is_finished(
+      job_class_name: MIGRATION,
+      table_name: :ci_deleted_objects,
+      column_name: :id,
+      job_arguments: [],
+      finalize: true
+    )
+  end
+
+  def down
+    # no-op
+  end
+end
diff --git a/db/post_migrate/20241028085044_update_invalid_ci_deleted_object_records.rb b/db/post_migrate/20241028085044_update_invalid_ci_deleted_object_records.rb
new file mode 100644
index 0000000000000..0c4d9c026c1b5
--- /dev/null
+++ b/db/post_migrate/20241028085044_update_invalid_ci_deleted_object_records.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class UpdateInvalidCiDeletedObjectRecords < Gitlab::Database::Migration[2.2]
+  milestone '17.6'
+  disable_ddl_transaction!
+  restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+  TABLE_NAME = :ci_deleted_objects
+  BATCH_SIZE = 1000
+
+  def up
+    return if Gitlab.com?
+
+    relation = define_batchable_model(TABLE_NAME).where(project_id: nil)
+
+    loop do
+      batch = relation.limit(BATCH_SIZE)
+      update_count = relation.where(id: batch.select(:id)).update_all(project_id: -1)
+
+      break if update_count == 0
+    end
+  end
+
+  def down
+    # no-op
+  end
+end
diff --git a/db/post_migrate/20241028085339_add_not_null_ci_deleted_object_project_id.rb b/db/post_migrate/20241028085339_add_not_null_ci_deleted_object_project_id.rb
new file mode 100644
index 0000000000000..d31b568b78dc5
--- /dev/null
+++ b/db/post_migrate/20241028085339_add_not_null_ci_deleted_object_project_id.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddNotNullCiDeletedObjectProjectId < Gitlab::Database::Migration[2.2]
+  milestone '17.6'
+  disable_ddl_transaction!
+
+  TABLE_NAME = :ci_deleted_objects
+  COLUMN_NAME = :project_id
+
+  def up
+    add_not_null_constraint(TABLE_NAME, COLUMN_NAME)
+  end
+
+  def down
+    remove_not_null_constraint(TABLE_NAME, COLUMN_NAME)
+  end
+end
diff --git a/db/schema_migrations/20241028085040 b/db/schema_migrations/20241028085040
new file mode 100644
index 0000000000000..61928c62057c3
--- /dev/null
+++ b/db/schema_migrations/20241028085040
@@ -0,0 +1 @@
+da93fba39bd21ed82571d63da65bb35b0d0628445f23a5f872dbdce426361773
\ No newline at end of file
diff --git a/db/schema_migrations/20241028085044 b/db/schema_migrations/20241028085044
new file mode 100644
index 0000000000000..ad9faf593b8ea
--- /dev/null
+++ b/db/schema_migrations/20241028085044
@@ -0,0 +1 @@
+d6dd5d6d203730e3e701dd0eb5a312403d6989a6d12128e2975d928eef5a93a3
\ No newline at end of file
diff --git a/db/schema_migrations/20241028085339 b/db/schema_migrations/20241028085339
new file mode 100644
index 0000000000000..a238ddab4721f
--- /dev/null
+++ b/db/schema_migrations/20241028085339
@@ -0,0 +1 @@
+6d36b298741eb05e66c9880e94e3376c484844bd73e11820198b72b92aa29bd7
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index f3aaf7324bcd8..4711290496b16 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -8552,7 +8552,8 @@ CREATE TABLE ci_deleted_objects (
     store_dir text NOT NULL,
     file text NOT NULL,
     project_id bigint,
-    CONSTRAINT check_5e151d6912 CHECK ((char_length(store_dir) <= 1024))
+    CONSTRAINT check_5e151d6912 CHECK ((char_length(store_dir) <= 1024)),
+    CONSTRAINT check_98f90d6c53 CHECK ((project_id IS NOT NULL))
 );
 
 CREATE SEQUENCE ci_deleted_objects_id_seq
diff --git a/spec/lib/gitlab/database/sharding_key_spec.rb b/spec/lib/gitlab/database/sharding_key_spec.rb
index 95ca587b1f5b3..673e7e8c2c2f3 100644
--- a/spec/lib/gitlab/database/sharding_key_spec.rb
+++ b/spec/lib/gitlab/database/sharding_key_spec.rb
@@ -63,6 +63,7 @@
       'ci_job_artifacts.project_id',
       'ci_namespace_monthly_usages.namespace_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/321400
       'ci_builds_metadata.project_id',
+      'ci_deleted_objects.project_id', # LFK already present on p_ci_builds and cascade delete all ci resources
       'p_ci_job_annotations.project_id', # LFK already present on p_ci_builds and cascade delete all ci resources
       'ldap_group_links.group_id',
       'namespace_descendants.namespace_id',
diff --git a/spec/migrations/20241028085044_update_invalid_ci_deleted_object_records_spec.rb b/spec/migrations/20241028085044_update_invalid_ci_deleted_object_records_spec.rb
new file mode 100644
index 0000000000000..829a21ac37d0e
--- /dev/null
+++ b/spec/migrations/20241028085044_update_invalid_ci_deleted_object_records_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe UpdateInvalidCiDeletedObjectRecords, feature_category: :job_artifacts, migration: :gitlab_ci do
+  let!(:ci_deleted_objects) { table(:ci_deleted_objects) }
+
+  describe '#up' do
+    before do
+      ci_deleted_objects.create!(project_id: 1, pick_up_at: Time.current, store_dir: "dir", file: "file1")
+      ci_deleted_objects.create!(project_id: nil, pick_up_at: Time.current, store_dir: "dir", file: "file2")
+      ci_deleted_objects.create!(project_id: nil, pick_up_at: Time.current, store_dir: "dir", file: "file3")
+
+      stub_const("#{described_class}::BATCH_SIZE", 1)
+    end
+
+    it 'sets project_id to -1 for records without a project_id' do
+      migrate!
+
+      expect(ci_deleted_objects.first).to have_attributes(project_id: 1)
+      expect(ci_deleted_objects.second).to have_attributes(project_id: -1)
+      expect(ci_deleted_objects.third).to have_attributes(project_id: -1)
+    end
+
+    it 'does nothing on gitlab.com' do
+      allow(Gitlab).to receive(:com?).and_return(true)
+
+      migrate!
+
+      expect(ci_deleted_objects.first).to have_attributes(project_id: 1)
+      expect(ci_deleted_objects.second).to have_attributes(project_id: nil)
+      expect(ci_deleted_objects.third).to have_attributes(project_id: nil)
+    end
+  end
+end
-- 
GitLab