diff --git a/db/post_migrate/20250830041735_fix_misnamed_foreign_keys.rb b/db/post_migrate/20250830041735_fix_misnamed_foreign_keys.rb new file mode 100644 index 0000000000000000000000000000000000000000..23ebbe6561b4404fb962e2f37150c717dd9c960e --- /dev/null +++ b/db/post_migrate/20250830041735_fix_misnamed_foreign_keys.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +class FixMisnamedForeignKeys < Gitlab::Database::Migration[2.3] + include ::Gitlab::Database::PartitioningMigrationHelpers + + disable_ddl_transaction! + milestone '18.4' + + FOREIGN_KEYS = [ + { + table: 'packages_tags', + # Original table name: packages_package_tags + # OpenSSL::Digest::SHA256.hexdigest("packages_package_tags_package_id_fk") + old_name: 'fk_rails_2b18ae9256', + new_name: 'fk_rails_1dfc868911' + }, + { + table: 'pool_repositories', + # Original table name: repositories + # OpenSSL::Digest::SHA256.hexdigest("repositories_shard_id_fk") + old_name: 'fk_rails_95a99c2d56', + new_name: 'fk_rails_af3f8c5d62' + }, + { + table: 'design_management_designs_versions', + old_name: 'fk_03c671965c', + new_name: 'fk_rails_03c671965c' + }, + { + table: 'design_management_designs_versions', + old_name: 'fk_f4d25ba00c', + new_name: 'fk_rails_f4d25ba00c' + }, + { + table: 'todos', + old_name: 'fk_a27c483435', + new_name: 'fk_rails_a27c483435' + }, + { + table: 'boards', + old_name: 'fk_1e9a074a35', + new_name: 'fk_rails_1e9a074a35' + }, + { + table: 'remote_mirrors', + old_name: 'fk_43a9aa4ca8', + new_name: 'fk_rails_43a9aa4ca8' + }, + { + table: 'events', + old_name: 'fk_61fbf6ca48', + new_name: 'fk_rails_61fbf6ca48' + }, + { + table: 'project_mirror_data', + old_name: 'fk_d1aad367d7', + new_name: 'fk_rails_d1aad367d7' + }, + { + table: 'security_policies', + old_name: 'fk_08722e8ac7', + new_name: 'fk_rails_08722e8ac7' + }, + { + table: 'dependency_proxy_group_settings', + old_name: 'fk_616ddd680a', + new_name: 'fk_rails_616ddd680a' + }, + { + table: 'jira_connect_subscriptions', + old_name: 'fk_a3c10bcf7d', + new_name: 'fk_rails_a3c10bcf7d' + }, + { + table: 'dependency_proxy_blobs', + old_name: 'fk_db58bbc5d7', + new_name: 'fk_rails_db58bbc5d7' + }, + { + table: 'approval_policy_rules', + old_name: 'fk_e344cb2d35', + new_name: 'fk_rails_e344cb2d35' + }, + { + table: 'jira_connect_subscriptions', + old_name: 'fk_f1d617343f', + new_name: 'fk_rails_f1d617343f' + } + ].freeze + + def up + FOREIGN_KEYS.each do |data| + table = data[:table] + old_name = data[:old_name] + new_name = data[:new_name] + + next unless foreign_key_exists?(table, name: old_name) + next if foreign_key_exists?(table, name: new_name) + + rename_constraint(table, old_name, new_name) + end + end + + def down; end +end diff --git a/db/schema_migrations/20250830041735 b/db/schema_migrations/20250830041735 new file mode 100644 index 0000000000000000000000000000000000000000..9bd85e79f0fa503e22102a7c8ab5d10ec0601798 --- /dev/null +++ b/db/schema_migrations/20250830041735 @@ -0,0 +1 @@ +97ccea80c9acbf7b31d0ba694fdfba194cb012aa17a3ef80f824637171e32859 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index a21dff1da062d2e95fba851a3ef2d30b85bd22f9..50164547769a435cce2968615ccc68059804178c 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -45744,9 +45744,6 @@ ALTER TABLE ONLY service_desk_settings ALTER TABLE ONLY work_item_type_custom_lifecycles ADD CONSTRAINT fk_03c6229585 FOREIGN KEY (lifecycle_id) REFERENCES work_item_custom_lifecycles(id) ON DELETE CASCADE; -ALTER TABLE ONLY design_management_designs_versions - ADD CONSTRAINT fk_03c671965c FOREIGN KEY (design_id) REFERENCES design_management_designs(id) ON DELETE CASCADE; - ALTER TABLE ONLY work_item_type_custom_lifecycles ADD CONSTRAINT fk_0425cd8e8b FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; @@ -46032,9 +46029,6 @@ ALTER TABLE ONLY design_management_versions ALTER TABLE ONLY sentry_issues ADD CONSTRAINT fk_1df79abe52 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; -ALTER TABLE ONLY boards - ADD CONSTRAINT fk_1e9a074a35 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; - ALTER TABLE ONLY zoekt_enabled_namespaces ADD CONSTRAINT fk_1effa65b25 FOREIGN KEY (root_namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; @@ -46401,9 +46395,6 @@ ALTER TABLE ONLY geo_event_log ALTER TABLE ONLY merge_request_predictions ADD CONSTRAINT fk_42d3b3824f FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; -ALTER TABLE ONLY remote_mirrors - ADD CONSTRAINT fk_43a9aa4ca8 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; - ALTER TABLE ONLY clusters ADD CONSTRAINT fk_43af04cf6d FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; @@ -46656,9 +46647,6 @@ ALTER TABLE ONLY deployment_approvals ALTER TABLE ONLY dast_profile_schedules ADD CONSTRAINT fk_61d52aa0e7 FOREIGN KEY (dast_profile_id) REFERENCES dast_profiles(id) ON DELETE CASCADE; -ALTER TABLE ONLY events - ADD CONSTRAINT fk_61fbf6ca48 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; - ALTER TABLE ONLY vulnerability_reads ADD CONSTRAINT fk_62736f638f FOREIGN KEY (vulnerability_id) REFERENCES vulnerabilities(id) ON DELETE CASCADE; @@ -47670,9 +47658,6 @@ ALTER TABLE ONLY bulk_import_entities ALTER TABLE ONLY subscription_user_add_on_assignments ADD CONSTRAINT fk_d1074a6e16 FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; -ALTER TABLE ONLY project_mirror_data - ADD CONSTRAINT fk_d1aad367d7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; - ALTER TABLE ONLY environments ADD CONSTRAINT fk_d1c8c1da6a FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; @@ -48009,9 +47994,6 @@ ALTER TABLE ONLY approval_policy_merge_request_bypass_events ALTER TABLE ONLY user_group_member_roles ADD CONSTRAINT fk_f3b8fc5e4e FOREIGN KEY (shared_with_group_id) REFERENCES namespaces(id) ON DELETE CASCADE; -ALTER TABLE ONLY design_management_designs_versions - ADD CONSTRAINT fk_f4d25ba00c FOREIGN KEY (version_id) REFERENCES design_management_versions(id) ON DELETE CASCADE; - ALTER TABLE ONLY scan_result_policy_violations ADD CONSTRAINT fk_f53706dbdd FOREIGN KEY (scan_result_policy_id) REFERENCES scan_result_policies(id) ON DELETE CASCADE; @@ -48180,6 +48162,9 @@ ALTER TABLE p_duo_workflows_checkpoints ALTER TABLE ONLY incident_management_oncall_participants ADD CONSTRAINT fk_rails_032b12996a FOREIGN KEY (oncall_rotation_id) REFERENCES incident_management_oncall_rotations(id) ON DELETE CASCADE; +ALTER TABLE ONLY design_management_designs_versions + ADD CONSTRAINT fk_rails_03c671965c FOREIGN KEY (design_id) REFERENCES design_management_designs(id) ON DELETE CASCADE; + ALTER TABLE ONLY events ADD CONSTRAINT fk_rails_0434b48643 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; @@ -48402,6 +48387,9 @@ ALTER TABLE ONLY project_ci_feature_usages ALTER TABLE ONLY packages_tags ADD CONSTRAINT fk_rails_1dfc868911 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE; +ALTER TABLE ONLY boards + ADD CONSTRAINT fk_rails_1e9a074a35 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY approval_policy_merge_request_bypass_events ADD CONSTRAINT fk_rails_1ebbdcc530 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; @@ -48708,6 +48696,9 @@ ALTER TABLE ONLY analytics_cycle_analytics_value_stream_settings ALTER TABLE ONLY merge_request_assignment_events ADD CONSTRAINT fk_rails_4378a2e8d7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; +ALTER TABLE ONLY remote_mirrors + ADD CONSTRAINT fk_rails_43a9aa4ca8 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; + ALTER TABLE ONLY lfs_file_locks ADD CONSTRAINT fk_rails_43df7a0412 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; @@ -48975,6 +48966,9 @@ ALTER TABLE ONLY status_page_published_incidents ALTER TABLE ONLY group_ssh_certificates ADD CONSTRAINT fk_rails_61f9eafcdf FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; +ALTER TABLE ONLY events + ADD CONSTRAINT fk_rails_61fbf6ca48 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY container_repository_states ADD CONSTRAINT fk_rails_63436c99ce FOREIGN KEY (container_repository_id) REFERENCES container_repositories(id) ON DELETE CASCADE; @@ -49866,6 +49860,9 @@ ALTER TABLE ONLY subscriptions ALTER TABLE ONLY operations_strategies ADD CONSTRAINT fk_rails_d183b6e6dd FOREIGN KEY (feature_flag_id) REFERENCES operations_feature_flags(id) ON DELETE CASCADE; +ALTER TABLE ONLY project_mirror_data + ADD CONSTRAINT fk_rails_d1aad367d7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; + ALTER TABLE ONLY cluster_agent_tokens ADD CONSTRAINT fk_rails_d1d26abc25 FOREIGN KEY (agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE; @@ -50163,6 +50160,9 @@ ALTER TABLE ONLY board_group_recent_visits ALTER TABLE ONLY incident_management_issuable_escalation_statuses ADD CONSTRAINT fk_rails_f4c811fd28 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE; +ALTER TABLE ONLY design_management_designs_versions + ADD CONSTRAINT fk_rails_f4d25ba00c FOREIGN KEY (version_id) REFERENCES design_management_versions(id) ON DELETE CASCADE; + ALTER TABLE ONLY vulnerability_export_parts ADD CONSTRAINT fk_rails_f50ca1aabf FOREIGN KEY (vulnerability_export_id) REFERENCES vulnerability_exports(id) ON DELETE CASCADE; diff --git a/spec/migrations/20250830041735_fix_misnamed_foreign_keys_spec.rb b/spec/migrations/20250830041735_fix_misnamed_foreign_keys_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..a124f3e704cecd4602f0c5e70c0791db82e181ea --- /dev/null +++ b/spec/migrations/20250830041735_fix_misnamed_foreign_keys_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe FixMisnamedForeignKeys, feature_category: :database do + let(:migration) { described_class.new } + + describe '#up' do + before do + # Ensure old foreign keys exist for testing by reversing any that might already be renamed + described_class::FOREIGN_KEYS.each do |data| + table_name = data[:table] + old_name = data[:old_name] + new_name = data[:new_name] + + # If the new foreign key exists but old doesn't, rename it back for testing + if migration.foreign_key_exists?(table_name, name: new_name) && + !migration.foreign_key_exists?(table_name, name: old_name) + migration.rename_constraint(table_name, new_name, old_name) + end + end + end + + it 'renames all misnamed foreign keys to their correct names' do + migrate! + + # Verify all foreign keys were renamed correctly + described_class::FOREIGN_KEYS.each do |data| + table_name = data[:table] + old_name = data[:old_name] + new_name = data[:new_name] + + expect(migration.foreign_key_exists?(table_name, name: old_name)) + .to be(false), "Expected old foreign key #{old_name} to not exist on #{table_name} after migration" + expect(migration.foreign_key_exists?(table_name, name: new_name)) + .to be(true), "Expected new foreign key #{new_name} to exist on #{table_name} after migration" + end + end + + it 'is idempotent and can be run multiple times safely' do + migrate! + expect { migrate! }.not_to raise_error + + # Verify foreign keys are still correctly named after second run + described_class::FOREIGN_KEYS.each do |data| + table_name = data[:table] + new_name = data[:new_name] + + expect(migration.foreign_key_exists?(table_name, name: new_name)) + .to be(true), "Expected new foreign key #{new_name} to still exist after second migration run" + end + end + + it 'skips renaming if the new foreign key already exists' do + # Pre-rename one foreign key to simulate already having the correct name + sample_data = described_class::FOREIGN_KEYS.first + sample_table = sample_data[:table] + sample_old_name = sample_data[:old_name] + sample_new_name = sample_data[:new_name] + + if migration.foreign_key_exists?(sample_table, name: sample_old_name) + migration.rename_constraint(sample_table, sample_old_name, sample_new_name) + end + + expect { migrate! }.not_to raise_error + + # Verify the pre-renamed foreign key still exists with correct name + expect(migration.foreign_key_exists?(sample_table, name: sample_new_name)) + .to be(true) + end + + it 'skips renaming if the old foreign key does not exist' do + # Remove one of the old foreign keys to simulate it not existing + sample_data = described_class::FOREIGN_KEYS.first + sample_table = sample_data[:table] + sample_old_name = sample_data[:old_name] + sample_new_name = sample_data[:new_name] + + if migration.foreign_key_exists?(sample_table, name: sample_old_name) + migration.remove_foreign_key(sample_table, name: sample_old_name) + end + + expect { migrate! }.not_to raise_error + + expect(migration.foreign_key_exists?(sample_table, sample_new_name)) + .to be(false) + end + end + + describe '#down' do + it 'is intentionally empty and irreversible' do + expect(described_class.new.method(:down).source_location).to be_present + # The down method should be empty as foreign key names don't affect functionality + expect { described_class.new.down }.not_to raise_error + end + end +end