Commit d9065308 authored by Adam Hegyi's avatar Adam Hegyi

Loose foreign key definition experiment

This change adds loose foreign key definition and triggers to the
following tables:

- chat_names -> ci_pipeline_chat_data
- ci_runners -> clusters_applications_runners

Changelog: added
parent 28e18a4c
# frozen_string_literal: true # frozen_string_literal: true
class ChatName < ApplicationRecord class ChatName < ApplicationRecord
include LooseForeignKey
LAST_USED_AT_INTERVAL = 1.hour LAST_USED_AT_INTERVAL = 1.hour
belongs_to :integration, foreign_key: :service_id belongs_to :integration, foreign_key: :service_id
...@@ -14,6 +16,8 @@ class ChatName < ApplicationRecord ...@@ -14,6 +16,8 @@ class ChatName < ApplicationRecord
validates :user_id, uniqueness: { scope: [:service_id] } validates :user_id, uniqueness: { scope: [:service_id] }
validates :chat_id, uniqueness: { scope: [:service_id, :team_id] } validates :chat_id, uniqueness: { scope: [:service_id, :team_id] }
loose_foreign_key :ci_pipeline_chat_data, :chat_name_id, on_delete: :async_delete
# Updates the "last_used_timestamp" but only if it wasn't already updated # Updates the "last_used_timestamp" but only if it wasn't already updated
# recently. # recently.
# #
......
...@@ -12,6 +12,7 @@ module Ci ...@@ -12,6 +12,7 @@ module Ci
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include TaggableQueries include TaggableQueries
include Presentable include Presentable
include LooseForeignKey
add_authentication_token_field :token, encrypted: :optional add_authentication_token_field :token, encrypted: :optional
...@@ -167,6 +168,8 @@ module Ci ...@@ -167,6 +168,8 @@ module Ci
validates :config, json_schema: { filename: 'ci_runner_config' } validates :config, json_schema: { filename: 'ci_runner_config' }
loose_foreign_key :clusters_applications_runners, :runner_id, on_delete: :async_nullify
# Searches for runners matching the given query. # Searches for runners matching the given query.
# #
# This method uses ILIKE on PostgreSQL for the description field and performs a full match on tokens. # This method uses ILIKE on PostgreSQL for the description field and performs a full match on tokens.
......
# frozen_string_literal: true
class TrackDeletionsInCiRunners < Gitlab::Database::Migration[1.0]
include Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers
enable_lock_retries!
def up
track_record_deletions(:ci_runners)
end
def down
untrack_record_deletions(:ci_runners)
end
end
# frozen_string_literal: true
class TrackDeletionsInChatNames < Gitlab::Database::Migration[1.0]
include Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers
enable_lock_retries!
def up
track_record_deletions(:chat_names)
end
def down
untrack_record_deletions(:chat_names)
end
end
f1b218eaddb9bcc5e4d854a6b43fc5e122b38dc989225327a1c4a899f41e5ac6
\ No newline at end of file
3d7b72684102836d7a7efcab7590b3d14bc63eb3e1bfbc7a95fb5eb5c6a906af
\ No newline at end of file
...@@ -27550,6 +27550,10 @@ ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_p ...@@ -27550,6 +27550,10 @@ ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_p
ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63_pkey; ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63_pkey;
CREATE TRIGGER chat_names_loose_fk_trigger AFTER DELETE ON chat_names REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
CREATE TRIGGER ci_runners_loose_fk_trigger AFTER DELETE ON ci_runners REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
CREATE TRIGGER trigger_91dc388a5fe6 BEFORE INSERT OR UPDATE ON dep_ci_build_trace_sections FOR EACH ROW EXECUTE FUNCTION trigger_91dc388a5fe6(); CREATE TRIGGER trigger_91dc388a5fe6 BEFORE INSERT OR UPDATE ON dep_ci_build_trace_sections FOR EACH ROW EXECUTE FUNCTION trigger_91dc388a5fe6();
CREATE TRIGGER trigger_delete_project_namespace_on_project_delete AFTER DELETE ON projects FOR EACH ROW WHEN ((old.project_namespace_id IS NOT NULL)) EXECUTE FUNCTION delete_associated_project_namespace(); CREATE TRIGGER trigger_delete_project_namespace_on_project_delete AFTER DELETE ON projects FOR EACH ROW WHEN ((old.project_namespace_id IS NOT NULL)) EXECUTE FUNCTION delete_associated_project_namespace();
...@@ -43,4 +43,12 @@ RSpec.describe ChatName do ...@@ -43,4 +43,12 @@ RSpec.describe ChatName do
expect(subject.last_used_at).to eq(time) expect(subject.last_used_at).to eq(time)
end end
end end
it_behaves_like 'it has loose foreign keys' do
let(:factory_name) { :chat_name }
before do
Ci::PipelineChatData # ensure that the referenced model is loaded
end
end
end end
...@@ -5,6 +5,14 @@ require 'spec_helper' ...@@ -5,6 +5,14 @@ require 'spec_helper'
RSpec.describe Ci::Runner do RSpec.describe Ci::Runner do
it_behaves_like 'having unique enum values' it_behaves_like 'having unique enum values'
it_behaves_like 'it has loose foreign keys' do
let(:factory_name) { :ci_runner }
before do
Clusters::Applications::Runner # ensure that the referenced model is loaded
end
end
describe 'groups association' do describe 'groups association' do
# Due to other assoctions such as projects this whole spec is allowed to # Due to other assoctions such as projects this whole spec is allowed to
# generate cross-database queries. So we have this temporary spec to # generate cross-database queries. So we have this temporary spec to
......
# frozen_string_literal: true
RSpec.shared_examples 'it has loose foreign keys' do
let(:factory_name) { nil }
let(:table_name) { described_class.table_name }
let(:connection) { described_class.connection }
it 'includes the LooseForeignKey module' do
expect(described_class.ancestors).to include(LooseForeignKey)
end
it 'responds to #loose_foreign_key_definitions' do
expect(described_class).to respond_to(:loose_foreign_key_definitions)
end
it 'has at least one loose foreign key definition' do
expect(described_class.loose_foreign_key_definitions.size).to be > 0
end
it 'has the deletion trigger present' do
sql = <<-SQL
SELECT trigger_name
FROM information_schema.triggers
WHERE event_object_table = '#{table_name}'
SQL
triggers = connection.execute(sql)
expected_trigger_name = "#{table_name}_loose_fk_trigger"
expect(triggers.pluck('trigger_name')).to include(expected_trigger_name)
end
it 'records record deletions' do
model = create(factory_name) # rubocop: disable Rails/SaveBang
model.destroy!
deleted_record = LooseForeignKeys::DeletedRecord.find_by(fully_qualified_table_name: "#{connection.current_schema}.#{table_name}", primary_key_value: model.id)
expect(deleted_record).not_to be_nil
end
it 'cleans up record deletions' do
model = create(factory_name) # rubocop: disable Rails/SaveBang
expect { model.destroy! }.to change { LooseForeignKeys::DeletedRecord.count }.by(1)
LooseForeignKeys::ProcessDeletedRecordsService.new(connection: connection).execute
expect(LooseForeignKeys::DeletedRecord.status_pending.count).to be(0)
expect(LooseForeignKeys::DeletedRecord.status_processed.count).to be(1)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment