fix: add NULL safety and idempotent DROP CONSTRAINT to search FK migration

Prevent orphan cleanup queries from incorrectly matching rows with NULL
entity_id via LEFT JOIN, and use DROP CONSTRAINT IF EXISTS for safer
partial re-runs given transaction: false.

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 11:53:28 +01:00
parent 42a42d5625
commit 65364716da
@@ -41,6 +41,7 @@ exports.up = async function up(knex) {
SELECT s.ctid FROM "search" s
LEFT JOIN "final_entities" fe ON s."entity_id" = fe."entity_id"
WHERE fe."entity_id" IS NULL
AND s."entity_id" IS NOT NULL
LIMIT 10000
)
`);
@@ -52,7 +53,7 @@ exports.up = async function up(knex) {
// Drop old FK and add new one with NOT VALID (minimal lock time).
// NOT VALID skips the full table scan — we already cleaned up orphans above.
await knex.raw(
`ALTER TABLE "search" DROP CONSTRAINT "search_entity_id_foreign"`,
`ALTER TABLE "search" DROP CONSTRAINT IF EXISTS "search_entity_id_foreign"`,
);
await knex.raw(`
ALTER TABLE "search"
@@ -77,6 +78,7 @@ exports.up = async function up(knex) {
SELECT DISTINCT s.\`entity_id\` FROM \`search\` s
LEFT JOIN \`final_entities\` fe ON s.\`entity_id\` = fe.\`entity_id\`
WHERE fe.\`entity_id\` IS NULL
AND s.\`entity_id\` IS NOT NULL
LIMIT 10000
`);
if (orphanIds.length === 0) {
@@ -134,6 +136,7 @@ exports.down = async function down(knex) {
SELECT s.ctid FROM "search" s
LEFT JOIN "refresh_state" rs ON s."entity_id" = rs."entity_id"
WHERE rs."entity_id" IS NULL
AND s."entity_id" IS NOT NULL
LIMIT 10000
)
`);
@@ -143,7 +146,7 @@ exports.down = async function down(knex) {
}
await knex.raw(
`ALTER TABLE "search" DROP CONSTRAINT "search_entity_id_foreign"`,
`ALTER TABLE "search" DROP CONSTRAINT IF EXISTS "search_entity_id_foreign"`,
);
await knex.raw(`
ALTER TABLE "search"
@@ -162,6 +165,7 @@ exports.down = async function down(knex) {
SELECT DISTINCT s.\`entity_id\` FROM \`search\` s
LEFT JOIN \`refresh_state\` rs ON s.\`entity_id\` = rs.\`entity_id\`
WHERE rs.\`entity_id\` IS NULL
AND s.\`entity_id\` IS NOT NULL
LIMIT 10000
`);
if (orphanIds.length === 0) {