From d5d5f2a966a32878eeeff9b888f8d86022be1e54 Mon Sep 17 00:00:00 2001 From: Alper Akgun Date: Mon, 6 Oct 2025 18:52:19 +0300 Subject: [PATCH 1/2] Address organization_id foreign key to ai_settings --- db/docs/ai_settings.yml | 4 ++- ...072100_clean_singleton_from_ai_settings.rb | 16 +++++++++++ ...2125_add_organization_id_to_ai_settings.rb | 16 +++++++++++ ...backfill_organization_id_on_ai_settings.rb | 16 +++++++++++ ...null_for_organization_id_on_ai_settings.rb | 13 +++++++++ db/schema_migrations/20251006072100 | 1 + db/schema_migrations/20251006112125 | 1 + db/schema_migrations/20251006112925 | 1 + db/schema_migrations/20251006112937 | 1 + db/structure.sql | 9 ++++-- ee/app/models/ai/setting.rb | 12 ++++++-- ee/spec/factories/ai/ai_settings.rb | 1 + ee/spec/models/ai/setting_spec.rb | 28 +------------------ 13 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 db/migrate/20251006072100_clean_singleton_from_ai_settings.rb create mode 100644 db/migrate/20251006112125_add_organization_id_to_ai_settings.rb create mode 100644 db/migrate/20251006112925_backfill_organization_id_on_ai_settings.rb create mode 100644 db/migrate/20251006112937_change_not_null_for_organization_id_on_ai_settings.rb create mode 100644 db/schema_migrations/20251006072100 create mode 100644 db/schema_migrations/20251006112125 create mode 100644 db/schema_migrations/20251006112925 create mode 100644 db/schema_migrations/20251006112937 diff --git a/db/docs/ai_settings.yml b/db/docs/ai_settings.yml index 180811d146bc55..c6482ae757b069 100644 --- a/db/docs/ai_settings.yml +++ b/db/docs/ai_settings.yml @@ -7,6 +7,8 @@ feature_categories: description: Stores instance-wide AI-related settings introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172007 milestone: '17.6' -gitlab_schema: gitlab_main +gitlab_schema: gitlab_main_cell table_size: small sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/557293 +sharding_key: + organization_id: organizations diff --git a/db/migrate/20251006072100_clean_singleton_from_ai_settings.rb b/db/migrate/20251006072100_clean_singleton_from_ai_settings.rb new file mode 100644 index 00000000000000..2661879652ae16 --- /dev/null +++ b/db/migrate/20251006072100_clean_singleton_from_ai_settings.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class CleanSingletonFromAiSettings < Gitlab::Database::Migration[2.3] + milestone '18.5' + disable_ddl_transaction! + + def up + remove_check_constraint :ai_settings, :check_singleton + remove_concurrent_index_by_name :ai_settings, :index_ai_settings_on_singleton + end + + def down + add_concurrent_index :ai_settings, :singleton, unique: true, name: :index_ai_settings_on_singleton + add_check_constraint :ai_settings, "(singleton IS TRUE)", 'check_singleton' + end +end diff --git a/db/migrate/20251006112125_add_organization_id_to_ai_settings.rb b/db/migrate/20251006112125_add_organization_id_to_ai_settings.rb new file mode 100644 index 00000000000000..272521016a6ccd --- /dev/null +++ b/db/migrate/20251006112125_add_organization_id_to_ai_settings.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddOrganizationIdToAiSettings < Gitlab::Database::Migration[2.3] + milestone '18.5' + disable_ddl_transaction! + + def up + add_column :ai_settings, :organization_id, :bigint, if_not_exists: true + add_concurrent_index :ai_settings, :organization_id, unique: true, name: :index_ai_settings_on_organization_id + add_concurrent_foreign_key :ai_settings, :organizations, column: :organization_id, on_delete: :cascade + end + + def down + remove_column :ai_settings, :organization_id + end +end diff --git a/db/migrate/20251006112925_backfill_organization_id_on_ai_settings.rb b/db/migrate/20251006112925_backfill_organization_id_on_ai_settings.rb new file mode 100644 index 00000000000000..822874bb938d0d --- /dev/null +++ b/db/migrate/20251006112925_backfill_organization_id_on_ai_settings.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class BackfillOrganizationIdOnAiSettings < Gitlab::Database::Migration[2.3] + milestone '18.5' + disable_ddl_transaction! + DEFAULT_ORGANIZATION_ID = 1 + restrict_gitlab_migration gitlab_schema: :gitlab_main + + def up + execute "UPDATE ai_settings SET organization_id = #{DEFAULT_ORGANIZATION_ID}" + end + + def down + # No need to restore + end +end diff --git a/db/migrate/20251006112937_change_not_null_for_organization_id_on_ai_settings.rb b/db/migrate/20251006112937_change_not_null_for_organization_id_on_ai_settings.rb new file mode 100644 index 00000000000000..fbca3f9edefefc --- /dev/null +++ b/db/migrate/20251006112937_change_not_null_for_organization_id_on_ai_settings.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class ChangeNotNullForOrganizationIdOnAiSettings < Gitlab::Database::Migration[2.3] + milestone '18.5' + + def up + change_column_null :ai_settings, :organization_id, false + end + + def down + change_column_null :ai_settings, :organization_id, true + end +end diff --git a/db/schema_migrations/20251006072100 b/db/schema_migrations/20251006072100 new file mode 100644 index 00000000000000..f659622d46322d --- /dev/null +++ b/db/schema_migrations/20251006072100 @@ -0,0 +1 @@ +3fed57137a6542dae482d1e56a4acc715e50b49debbecb6748009847cb761bd5 \ No newline at end of file diff --git a/db/schema_migrations/20251006112125 b/db/schema_migrations/20251006112125 new file mode 100644 index 00000000000000..2a40404a097ea0 --- /dev/null +++ b/db/schema_migrations/20251006112125 @@ -0,0 +1 @@ +dfbf6201e425476593941d9e4d17670d34d4fd1140b07a3268cf2d926de515de \ No newline at end of file diff --git a/db/schema_migrations/20251006112925 b/db/schema_migrations/20251006112925 new file mode 100644 index 00000000000000..cb501e00517604 --- /dev/null +++ b/db/schema_migrations/20251006112925 @@ -0,0 +1 @@ +09cbc48c7fc381d6af2a253c129ccf4ceb067318372edaaa64048ad56848ae3d \ No newline at end of file diff --git a/db/schema_migrations/20251006112937 b/db/schema_migrations/20251006112937 new file mode 100644 index 00000000000000..62143a07d337b9 --- /dev/null +++ b/db/schema_migrations/20251006112937 @@ -0,0 +1 @@ +4df3c2ed703521b94aaa41583a955b1c3fda918e138f3be93ae86cc0f0ca9bb0 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 080cfa1218a0f7..aa4715077eccd1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -10315,10 +10315,10 @@ CREATE TABLE ai_settings ( duo_core_features_enabled boolean, duo_agent_platform_service_url text, duo_agent_platform_request_count integer DEFAULT 0 NOT NULL, + organization_id bigint NOT NULL, CONSTRAINT check_3cf9826589 CHECK ((char_length(ai_gateway_url) <= 2048)), CONSTRAINT check_900d7a89b3 CHECK ((char_length(duo_agent_platform_service_url) <= 2048)), - CONSTRAINT check_a02bd8868c CHECK ((char_length(amazon_q_role_arn) <= 2048)), - CONSTRAINT check_singleton CHECK ((singleton IS TRUE)) + CONSTRAINT check_a02bd8868c CHECK ((char_length(amazon_q_role_arn) <= 2048)) ); COMMENT ON COLUMN ai_settings.singleton IS 'Always true, used for singleton enforcement'; @@ -38377,7 +38377,7 @@ CREATE INDEX index_ai_settings_on_duo_workflow_oauth_application_id ON ai_settin CREATE INDEX index_ai_settings_on_duo_workflow_service_account_user_id ON ai_settings USING btree (duo_workflow_service_account_user_id); -CREATE UNIQUE INDEX index_ai_settings_on_singleton ON ai_settings USING btree (singleton); +CREATE UNIQUE INDEX index_ai_settings_on_organization_id ON ai_settings USING btree (organization_id); CREATE INDEX index_ai_troubleshoot_job_events_on_job_id ON ONLY ai_troubleshoot_job_events USING btree (job_id); @@ -47462,6 +47462,9 @@ ALTER TABLE ONLY board_user_preferences ALTER TABLE ONLY approval_policy_rule_project_links ADD CONSTRAINT fk_1c78796d52 FOREIGN KEY (approval_policy_rule_id) REFERENCES approval_policy_rules(id) ON DELETE CASCADE; +ALTER TABLE ONLY ai_settings + ADD CONSTRAINT fk_1ca0081d1a FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; + ALTER TABLE ONLY issue_links ADD CONSTRAINT fk_1cce06b868 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; diff --git a/ee/app/models/ai/setting.rb b/ee/app/models/ai/setting.rb index a0a99eae190c89..19f73034b13d45 100644 --- a/ee/app/models/ai/setting.rb +++ b/ee/app/models/ai/setting.rb @@ -4,8 +4,6 @@ module Ai class Setting < ApplicationRecord self.table_name = "ai_settings" - include SingletonRecord - ignore_column :duo_nano_features_enabled, remove_with: '18.3', remove_after: '2025-07-15' validates :ai_gateway_url, :duo_agent_platform_service_url, length: { maximum: 2048 }, allow_nil: true @@ -23,6 +21,7 @@ class Setting < ApplicationRecord belongs_to :duo_workflow_oauth_application, class_name: 'Authn::OauthApplication', optional: true belongs_to :duo_workflow_service_account_user, class_name: 'User', optional: true + belongs_to :organization, class_name: 'Organizations::Organization', optional: false after_commit :trigger_todo_creation, on: :update, if: :saved_change_to_duo_core_features_enabled? @@ -33,6 +32,15 @@ def self.defaults } end + def self.instance + # rubocop:disable Performance/ActiveRecordSubtransactionMethods -- only + # uses a subtransaction if creating a record, which should only happen once per instance + safe_find_or_create_by(organization_id: Organizations::Organization::DEFAULT_ORGANIZATION_ID) do |setting| + setting.assign_attributes(defaults) + end + # rubocop:enable Performance/ActiveRecordSubtransactionMethods + end + def self.self_hosted? ::Ai::SelfHostedModel.any? end diff --git a/ee/spec/factories/ai/ai_settings.rb b/ee/spec/factories/ai/ai_settings.rb index f9fec9be5982d1..ecc59c5194f11d 100644 --- a/ee/spec/factories/ai/ai_settings.rb +++ b/ee/spec/factories/ai/ai_settings.rb @@ -4,5 +4,6 @@ factory :ai_settings, class: '::Ai::Setting' do ai_gateway_url { "http://0.0.0.0:5052" } duo_agent_platform_service_url { "0.0.0.0:50052" } + organization { association(:organization, :default) } end end diff --git a/ee/spec/models/ai/setting_spec.rb b/ee/spec/models/ai/setting_spec.rb index a2583b4d8385dd..812d8f6051390f 100644 --- a/ee/spec/models/ai/setting_spec.rb +++ b/ee/spec/models/ai/setting_spec.rb @@ -4,6 +4,7 @@ RSpec.describe Ai::Setting, feature_category: :ai_abstraction_layer do using RSpec::Parameterized::TableSyntax + let_it_be(:organization) { create(:organization, id: Organizations::Organization::DEFAULT_ORGANIZATION_ID) } describe 'associations', :aggregate_failures do it 'has expected associations' do @@ -131,33 +132,6 @@ it_behaves_like 'a URL field', :ai_gateway_url end - it_behaves_like 'singleton record validation' do - it 'allows updating the existing record' do - setting = described_class.create! - - setting.ai_gateway_url = 'https://new-url.example.com' - - expect(setting).to be_valid - end - - it 'does not override existing record attributes' do - original_url = 'http://example.com' - new_url = 'http://new.example.com' - stub_env('AI_GATEWAY_URL', original_url) - - # on create, uses default value from AI_GATEWAY_URL - described_class.instance - expect(described_class.first.ai_gateway_url).to eq original_url - - # update to non-default value - described_class.first.update!(ai_gateway_url: new_url) - - # on update, attributes are persisted rather than overridden by defaults - described_class.instance - expect(described_class.first.reload.ai_gateway_url).to eq new_url - end - end - context 'when validating the duo_core_features_enabled value' do describe 'new record' do it 'returns nil as the default value' do -- GitLab From 958dbd9570452cd058239f604a3d8b96578a703a Mon Sep 17 00:00:00 2001 From: Alper Akgun Date: Tue, 7 Oct 2025 09:51:44 +0300 Subject: [PATCH 2/2] Remove sharding url & away from exempted tables --- db/docs/ai_settings.yml | 1 - .../database/no_new_tables_with_gitlab_main_schema_spec.rb | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/db/docs/ai_settings.yml b/db/docs/ai_settings.yml index c6482ae757b069..20a7c7d78b25e6 100644 --- a/db/docs/ai_settings.yml +++ b/db/docs/ai_settings.yml @@ -9,6 +9,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172007 milestone: '17.6' gitlab_schema: gitlab_main_cell table_size: small -sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/557293 sharding_key: organization_id: organizations diff --git a/spec/lib/gitlab/database/no_new_tables_with_gitlab_main_schema_spec.rb b/spec/lib/gitlab/database/no_new_tables_with_gitlab_main_schema_spec.rb index 00be3d4deaa2ef..db1a840e4ec9d7 100644 --- a/spec/lib/gitlab/database/no_new_tables_with_gitlab_main_schema_spec.rb +++ b/spec/lib/gitlab/database/no_new_tables_with_gitlab_main_schema_spec.rb @@ -24,8 +24,7 @@ 'authentication_event_archived_records', 'instance_integrations', # gitlab_main_clusterwide now deprecated 'ldap_admin_role_links', # gitlab_main_clusterwide now deprecated - 'user_permission_export_upload_uploads', # gitlab_main_clusterwide now deprecated - 'ai_settings' # awaiting schema decision: https://gitlab.com/gitlab-org/gitlab/-/issues/531356 + 'user_permission_export_upload_uploads' # gitlab_main_clusterwide now deprecated ] end -- GitLab