diff --git a/db/migrate/20251007102142_add_sharding_key_to_suggestions.rb b/db/migrate/20251007102142_add_sharding_key_to_suggestions.rb new file mode 100644 index 0000000000000000000000000000000000000000..5b2b1ae28ae81aef403b73f24b59a9639b480b2c --- /dev/null +++ b/db/migrate/20251007102142_add_sharding_key_to_suggestions.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddShardingKeyToSuggestions < Gitlab::Database::Migration[2.3] + milestone '18.6' + + def change + add_column :suggestions, :namespace_id, :bigint + end +end diff --git a/db/migrate/20251007102313_add_sharding_key_trigger_on_suggestions.rb b/db/migrate/20251007102313_add_sharding_key_trigger_on_suggestions.rb new file mode 100644 index 0000000000000000000000000000000000000000..4330af3d74750764541238770ce98f01866f7e97 --- /dev/null +++ b/db/migrate/20251007102313_add_sharding_key_trigger_on_suggestions.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class AddShardingKeyTriggerOnSuggestions < Gitlab::Database::Migration[2.3] + include Gitlab::Database::SchemaHelpers + + FUNCTION_NAME = 'sync_sharding_key_with_notes_table' + TRIGGER_NAME = 'set_sharding_key_for_suggestions_on_insert_and_update' + + milestone '18.6' + + def up + execute(<<~SQL) + CREATE OR REPLACE FUNCTION #{FUNCTION_NAME}() + RETURNS TRIGGER AS + $$ + DECLARE + note_project_id BIGINT; + note_namespace_id BIGINT; + BEGIN + IF NEW."note_id" IS NULL OR NEW."namespace_id" IS NOT NULL THEN + RETURN NEW; + END IF; + + SELECT "project_id", "namespace_id" + INTO note_project_id, note_namespace_id + FROM "notes" + WHERE "id" = NEW."note_id"; + + IF note_project_id IS NOT NULL THEN + SELECT "project_namespace_id" FROM "projects" + INTO NEW."namespace_id" WHERE "projects"."id" = note_project_id; + ELSE + NEW."namespace_id" := note_namespace_id; + END IF; + + RETURN NEW; + END + $$ LANGUAGE PLPGSQL; + SQL + + create_trigger(:suggestions, TRIGGER_NAME, FUNCTION_NAME, fires: 'BEFORE INSERT OR UPDATE') + end + + def down + drop_trigger(:suggestions, TRIGGER_NAME) + + drop_function(FUNCTION_NAME) + end +end diff --git a/db/post_migrate/20251007111103_add_not_null_constraint_on_suggestions_sharding_key.rb b/db/post_migrate/20251007111103_add_not_null_constraint_on_suggestions_sharding_key.rb new file mode 100644 index 0000000000000000000000000000000000000000..c139dd036d53c14379d3cf992497b2f6e00a7cba --- /dev/null +++ b/db/post_migrate/20251007111103_add_not_null_constraint_on_suggestions_sharding_key.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class AddNotNullConstraintOnSuggestionsShardingKey < Gitlab::Database::Migration[2.3] + disable_ddl_transaction! + milestone '18.6' + + def up + add_not_null_constraint :suggestions, :namespace_id, validate: false + end + + def down + remove_not_null_constraint :suggestions, :namespace_id + end +end diff --git a/db/post_migrate/20251007111146_add_sharding_key_index_on_suggestions.rb b/db/post_migrate/20251007111146_add_sharding_key_index_on_suggestions.rb new file mode 100644 index 0000000000000000000000000000000000000000..15cb031e7cf9ed3c5feb97a40ceab7dde11742d0 --- /dev/null +++ b/db/post_migrate/20251007111146_add_sharding_key_index_on_suggestions.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddShardingKeyIndexOnSuggestions < Gitlab::Database::Migration[2.3] + INDEX_NAME = 'index_suggestions_on_namespace_id' + + milestone '18.6' + disable_ddl_transaction! + + def up + add_concurrent_index :suggestions, :namespace_id, name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :suggestions, INDEX_NAME + end +end diff --git a/db/post_migrate/20251013085124_add_suggestions_namespace_id_foreign_key.rb b/db/post_migrate/20251013085124_add_suggestions_namespace_id_foreign_key.rb new file mode 100644 index 0000000000000000000000000000000000000000..56cccbf895b598a52788056e080e1435e7b55849 --- /dev/null +++ b/db/post_migrate/20251013085124_add_suggestions_namespace_id_foreign_key.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddSuggestionsNamespaceIdForeignKey < Gitlab::Database::Migration[2.3] + disable_ddl_transaction! + milestone '18.6' + + def up + add_concurrent_foreign_key :suggestions, + :namespaces, + column: :namespace_id, + reverse_lock_order: true, + validate: false + end + + def down + with_lock_retries do + remove_foreign_key_if_exists(:suggestions, column: :namespace_id) + end + end +end diff --git a/db/schema_migrations/20251007102142 b/db/schema_migrations/20251007102142 new file mode 100644 index 0000000000000000000000000000000000000000..675bfd9267588dade407332cd707ba44558361fb --- /dev/null +++ b/db/schema_migrations/20251007102142 @@ -0,0 +1 @@ +411080c716f68f0ff85dd6eebf107ec03f826cb518a8632ab998530c806d07b5 \ No newline at end of file diff --git a/db/schema_migrations/20251007102313 b/db/schema_migrations/20251007102313 new file mode 100644 index 0000000000000000000000000000000000000000..1ea6c3997b536514b8781a8523adbb39b42db160 --- /dev/null +++ b/db/schema_migrations/20251007102313 @@ -0,0 +1 @@ +c1ee5c4aced2c5e2c05c142582a0217d1b34a0d6ecad235953d59cc9c103003f \ No newline at end of file diff --git a/db/schema_migrations/20251007111103 b/db/schema_migrations/20251007111103 new file mode 100644 index 0000000000000000000000000000000000000000..b5b5a362e0ca3c167a7ffe6a392bb5d9befc10a8 --- /dev/null +++ b/db/schema_migrations/20251007111103 @@ -0,0 +1 @@ +66eaa55375e15abe2666bb07d5dbba834072c90ca0feaaeccc14a46aa934ba01 \ No newline at end of file diff --git a/db/schema_migrations/20251007111146 b/db/schema_migrations/20251007111146 new file mode 100644 index 0000000000000000000000000000000000000000..66dcea3c60df584127de6bfaa6e5bd750cccdb19 --- /dev/null +++ b/db/schema_migrations/20251007111146 @@ -0,0 +1 @@ +7a2ff0a3347c2b404ccf84fc3462eaf2301d92c073f668c22bedc4f48dfafd8a \ No newline at end of file diff --git a/db/schema_migrations/20251013085124 b/db/schema_migrations/20251013085124 new file mode 100644 index 0000000000000000000000000000000000000000..dd6d68b91b697bb49ea1236f666eb368fcf7050a --- /dev/null +++ b/db/schema_migrations/20251013085124 @@ -0,0 +1 @@ +f5d223d252b3be6fb5c72ad1281f928a08525e997697de690c82ac746c57255a \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index b9946ff29f5399f6e334beb906c49a2765e8384d..75927edb60ba2093ea964b23c96037345ff65d98 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1113,6 +1113,33 @@ RETURN NEW; END $$; +CREATE FUNCTION sync_sharding_key_with_notes_table() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + note_project_id BIGINT; + note_namespace_id BIGINT; +BEGIN + IF NEW."note_id" IS NULL OR NEW."namespace_id" IS NOT NULL THEN + RETURN NEW; + END IF; + + SELECT "project_id", "namespace_id" + INTO note_project_id, note_namespace_id + FROM "notes" + WHERE "id" = NEW."note_id"; + + IF note_project_id IS NOT NULL THEN + SELECT "project_namespace_id" FROM "projects" + INTO NEW."namespace_id" WHERE "projects"."id" = note_project_id; + ELSE + NEW."namespace_id" := note_namespace_id; + END IF; + + RETURN NEW; +END +$$; + CREATE FUNCTION sync_to_p_sent_notifications_table() RETURNS trigger LANGUAGE plpgsql AS $$ @@ -26564,7 +26591,8 @@ CREATE TABLE suggestions ( lines_above integer DEFAULT 0 NOT NULL, lines_below integer DEFAULT 0 NOT NULL, outdated boolean DEFAULT false NOT NULL, - note_id bigint NOT NULL + note_id bigint NOT NULL, + namespace_id bigint ); CREATE SEQUENCE suggestions_id_seq @@ -33205,6 +33233,9 @@ ALTER TABLE packages_packages ALTER TABLE sprints ADD CONSTRAINT check_df3816aed7 CHECK ((due_date IS NOT NULL)) NOT VALID; +ALTER TABLE suggestions + ADD CONSTRAINT check_e69372e45f CHECK ((namespace_id IS NOT NULL)) NOT VALID; + ALTER TABLE redirect_routes ADD CONSTRAINT check_e82ff70482 CHECK ((namespace_id IS NOT NULL)) NOT VALID; @@ -42463,6 +42494,8 @@ CREATE INDEX index_subscriptions_on_user ON subscriptions USING btree (user_id); CREATE INDEX index_successful_authentication_events_for_metrics ON authentication_events USING btree (user_id, provider, created_at) WHERE (result = 1); +CREATE INDEX index_suggestions_on_namespace_id ON suggestions USING btree (namespace_id); + CREATE UNIQUE INDEX index_suggestions_on_note_id_and_relative_order ON suggestions USING btree (note_id, relative_order); CREATE UNIQUE INDEX index_system_access_group_microsoft_applications_on_group_id ON system_access_group_microsoft_applications USING btree (group_id); @@ -46851,6 +46884,8 @@ CREATE TRIGGER projects_loose_fk_trigger AFTER DELETE ON projects REFERENCING OL CREATE TRIGGER push_rules_loose_fk_trigger AFTER DELETE ON push_rules REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); +CREATE TRIGGER set_sharding_key_for_suggestions_on_insert_and_update BEFORE INSERT OR UPDATE ON suggestions FOR EACH ROW EXECUTE FUNCTION sync_sharding_key_with_notes_table(); + CREATE TRIGGER set_sharding_key_for_system_note_metadata_on_insert BEFORE INSERT ON system_note_metadata FOR EACH ROW EXECUTE FUNCTION get_sharding_key_from_notes_table(); CREATE TRIGGER sync_project_authorizations_to_migration AFTER INSERT OR DELETE OR UPDATE ON project_authorizations FOR EACH ROW EXECUTE FUNCTION sync_project_authorizations_to_migration_table(); @@ -47960,6 +47995,9 @@ ALTER TABLE ONLY namespaces ALTER TABLE ONLY saml_providers ADD CONSTRAINT fk_351dde3a84 FOREIGN KEY (member_role_id) REFERENCES member_roles(id) ON DELETE SET NULL; +ALTER TABLE ONLY suggestions + ADD CONSTRAINT fk_35c950f0d6 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE NOT VALID; + ALTER TABLE ONLY approval_merge_request_rules_users ADD CONSTRAINT fk_35e88790f5 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; diff --git a/spec/lib/gitlab/database/sharding_key_spec.rb b/spec/lib/gitlab/database/sharding_key_spec.rb index 3db66e3b039e86d60c6f416697b2afe926386923..0194ea9f513cabd056ee2742a5ab593c0d8a25ec 100644 --- a/spec/lib/gitlab/database/sharding_key_spec.rb +++ b/spec/lib/gitlab/database/sharding_key_spec.rb @@ -328,7 +328,8 @@ "notes_archived" => "https://gitlab.com/gitlab-org/gitlab/-/issues/569521", "system_note_metadata" => "https://gitlab.com/gitlab-org/gitlab/-/issues/571215", "note_diff_files" => "https://gitlab.com/gitlab-org/gitlab/-/issues/550694", - "keys" => "https://gitlab.com/gitlab-org/gitlab/-/issues/553463" + "keys" => "https://gitlab.com/gitlab-org/gitlab/-/issues/553463", + "suggestions" => "https://gitlab.com/gitlab-org/gitlab/-/issues/550696" } has_lfk = ->(lfks) { lfks.any? { |k| k.options[:column] == 'organization_id' && k.to_table == 'organizations' } } diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb index 4f91908264fc00241a38d0c9fbb2854036eab5db..364a240821b370510bd622cbf2adc86a574de962 100644 --- a/spec/models/suggestion_spec.rb +++ b/spec/models/suggestion_spec.rb @@ -182,4 +182,19 @@ it { is_expected.to eq(false) } end end + + describe 'ensure sharding key trigger' do + let_it_be(:diff_note) { create(:diff_note_on_merge_request) } + + it 'syncs `namespace_id` with note on create' do + expect(create(:suggestion, note: diff_note).reload.namespace_id).to eq(diff_note.project.project_namespace_id) + end + + it 'syncs `namespace_id` with note on update' do + existing_suggestion = create(:suggestion, note: diff_note) + existing_suggestion.update!(namespace_id: nil) + + expect(existing_suggestion.reload.namespace_id).to eq(diff_note.project.project_namespace_id) + end + end end