diff --git a/db/migrations/20220625_delete_pacticipants_with_no_name.rb b/db/migrations/20220625_delete_pacticipants_with_no_name.rb new file mode 100644 index 000000000..83c9b8aa8 --- /dev/null +++ b/db/migrations/20220625_delete_pacticipants_with_no_name.rb @@ -0,0 +1,62 @@ +# There was a bug that caused a duplicate pacticipant to be created +# with a null name when updating the pacticipant in a certain way. +# It was fixed in be24a8ad650f0ed49993283b22ba1d1a744fb3e8. +# This migration deletes any pacticipants with null names that are not referenced by any other rows, +# or assigns a name if the pacticipant is referenced so that it can be deleted through the API +# (I can't think of a reason why a pacticipant with a null name should be referenced, but better to be safe than sorry). +# The "find tables that reference pacticipants" logic is done dynamically because Pactflow has extra tables not in the OSS. + +# Query each table that references the pacticipants table to determine +# if there are any rows in it that reference the pacticipant with the specified ID. +# @return [Boolean] +def pacticipant_is_unreferenced(pacticipant_id, table_foreign_keys) + table_foreign_keys.any? do | (table_name, foreign_keys) | + criteria = foreign_keys.flat_map do | fk | + fk[:columns].collect do | column_name | + { column_name => pacticipant_id } + end + end + + # SELECT 'one' FROM xxx where consumer_id = x or provider_id = x LIMIT 1 + !from(table_name).where(Sequel.|(*criteria)).empty? + end +end + +# Return a structure describing the tables and columns that reference the pacticipants table. +# @return [Array] eg. [ [:integrations, [ { columns: [:consumer_id] }, { columns: [:provider_id] } ] ] ] +def get_table_foreign_keys + table_foreign_keys = tables.sort.collect do | table_name | + key_list = foreign_key_list(table_name).select{ |fk| fk[:table] == :pacticipants } + if key_list.any? + [ table_name, key_list] + end + end.compact + + # move the integrations table check first because that's the most likely candidate to have a referencing row + table_foreign_keys.select{ |(table_name, _)| table_name == :integrations } | table_foreign_keys +end + +# Deletes the pacticipant if it is unreferenced, or populates the name so it can +# be deleted through the API. +def delete_pacticipant_or_populate_name(row, table_foreign_keys) + if pacticipant_is_unreferenced(row[:id], table_foreign_keys) + from(:pacticipants).where(id: row[:id]).delete + else + from(:pacticipants).where(id: row[:id]).update(name: "Delete me #{row[:id]}") + end +end + +Sequel.migration do + up do + + table_foreign_keys = get_table_foreign_keys + + from(:pacticipants).where(name: nil).all.each do | row | + delete_pacticipant_or_populate_name(row, table_foreign_keys) + end + end + + down do + + end +end diff --git a/spec/migrations/20220625_delete_pacticipants_with_no_name_spec.rb b/spec/migrations/20220625_delete_pacticipants_with_no_name_spec.rb new file mode 100644 index 000000000..f8219f952 --- /dev/null +++ b/spec/migrations/20220625_delete_pacticipants_with_no_name_spec.rb @@ -0,0 +1,20 @@ +describe "deleting unreferenced pacticipants with no name", migration: true do + before do + PactBroker::Database.migrate(20220622) + end + + let(:now) { DateTime.new(2017, 1, 1) } + let!(:has_name) { create(:pacticipants, { name: "aaa", created_at: now, updated_at: now }) } + let!(:no_name_1) { create(:pacticipants, { created_at: now, updated_at: now }) } + let!(:no_name_2) { create(:pacticipants, { created_at: now, updated_at: now }) } + + let!(:integration) { create(:integrations, { consumer_id: has_name[:id], provider_id: no_name_1[:id], created_at: now }) } + + it "deletes the pacticipant with no name that has no other rows referencing it" do + expect(database[:pacticipants].count).to eq 3 + expect(database[:pacticipants].where(name: nil).count).to eq 2 + PactBroker::Database.migrate(20220625) + expect(database[:pacticipants].where(name: nil).count).to eq 0 + expect(database[:pacticipants].count).to eq 2 + end +end