catalog-backend: combine PG DROP+ADD into single ALTER TABLE, fix MySQL comment

- 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 <noreply@anthropic.com>
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
This commit is contained in:
Fredrik Adelöw
2026-03-11 14:43:10 +01:00
parent 89a1c313d0
commit 09c7e49f82
@@ -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