From 09c7e49f82e8d7a9d32ec8009db4fdcf63a4e6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 11 Mar 2026 14:43:10 +0100 Subject: [PATCH] catalog-backend: combine PG DROP+ADD into single ALTER TABLE, fix MySQL comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Combine DROP CONSTRAINT and ADD CONSTRAINT into a single ALTER TABLE statement for PostgreSQL, eliminating the brief window where no FK exists - Reword MySQL transaction comment to clarify that ALTER TABLE causes implicit commits in InnoDB, so the wrapper doesn't provide full atomicity Co-Authored-By: Claude Opus 4.6 Signed-off-by: Fredrik Adelöw --- ...20260214000000_search_fk_final_entities.js | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/plugins/catalog-backend/migrations/20260214000000_search_fk_final_entities.js b/plugins/catalog-backend/migrations/20260214000000_search_fk_final_entities.js index 7048f2da75..71caeb600d 100644 --- a/plugins/catalog-backend/migrations/20260214000000_search_fk_final_entities.js +++ b/plugins/catalog-backend/migrations/20260214000000_search_fk_final_entities.js @@ -82,14 +82,13 @@ exports.up = async function up(knex) { const client = knex.client.config.client; if (client.includes('pg')) { - // Drop old FK and immediately add the new one as NOT VALID. This - // prevents new orphan rows from being inserted while we clean up - // existing ones, closing the race window between cleanup and FK add. - await knex.raw( - `ALTER TABLE "search" DROP CONSTRAINT IF EXISTS "search_entity_id_foreign"`, - ); + // Drop old FK and immediately add the new one as NOT VALID in a single + // ALTER TABLE statement. This prevents new orphan rows from being + // inserted while we clean up existing ones, and eliminates any window + // where no FK exists at all. await knex.raw(` ALTER TABLE "search" + DROP CONSTRAINT IF EXISTS "search_entity_id_foreign", ADD CONSTRAINT "search_entity_id_foreign" FOREIGN KEY ("entity_id") REFERENCES "final_entities"("entity_id") ON DELETE CASCADE @@ -110,10 +109,10 @@ exports.up = async function up(knex) { // Batch-delete orphaned rows before DDL to reduce lock time. await batchDeleteOrphansMysql(knex, 'final_entities'); - // Drop old FK and add new one inside an explicit transaction, since the - // global transaction wrapper is disabled for this migration. MySQL does - // not support NOT VALID, but the table is already clean so validation - // is fast. + // Perform the FK changes. Note that in MySQL/InnoDB, ALTER TABLE + // statements cause implicit commits, so wrapping in a transaction does + // not provide full atomicity. However the table is already cleaned of + // orphans and validation remains fast. await knex.transaction(async trx => { await trx.schema.alterTable('search', table => { table.dropForeign(['entity_id']); @@ -156,11 +155,9 @@ exports.down = async function down(knex) { const client = knex.client.config.client; if (client.includes('pg')) { - await knex.raw( - `ALTER TABLE "search" DROP CONSTRAINT IF EXISTS "search_entity_id_foreign"`, - ); await knex.raw(` ALTER TABLE "search" + DROP CONSTRAINT IF EXISTS "search_entity_id_foreign", ADD CONSTRAINT "search_entity_id_foreign" FOREIGN KEY ("entity_id") REFERENCES "refresh_state"("entity_id") ON DELETE CASCADE