fix: make the genre dedup migration collision-proof (0038)
Review caught a boot-crash risk: 0038's repoint-UPDATE could set two non-canonical variant links of one manga to the same canonical id in a single statement (NOT EXISTS sees the pre-statement snapshot) -> manga_genres PK violation -> migration rollback -> startup failure. Replace with INSERT ... SELECT DISTINCT ... ON CONFLICT DO NOTHING (collision-proof) then delete variants. Amended in place (0038 is unpushed). Tested with the exact collision scenario. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,32 +9,31 @@
|
||||
|
||||
-- Heal any pre-existing case-variant duplicates before adding the index, so
|
||||
-- `sqlx::migrate!` can't crash on a dirty table (mirrors 0031's pre-dedup).
|
||||
-- Keep the lowest id per lower(name) as canonical and repoint manga_genres.
|
||||
UPDATE manga_genres mg
|
||||
SET genre_id = d.keep_id
|
||||
FROM (
|
||||
SELECT g.id AS dup_id, k.keep_id
|
||||
FROM genres g
|
||||
JOIN (SELECT lower(name) AS lname, (array_agg(id ORDER BY id))[1] AS keep_id
|
||||
FROM genres GROUP BY lower(name)) k
|
||||
ON lower(g.name) = k.lname AND g.id <> k.keep_id
|
||||
) d
|
||||
WHERE mg.genre_id = d.dup_id
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM manga_genres x
|
||||
WHERE x.manga_id = mg.manga_id AND x.genre_id = d.keep_id
|
||||
);
|
||||
-- Canonical = lowest id per lower(name).
|
||||
|
||||
-- Drop links that would have collided with the canonical link (already present).
|
||||
-- 1. Ensure every manga linked to any case-variant also has the canonical link.
|
||||
-- INSERT ... ON CONFLICT DO NOTHING is collision-proof: SELECT DISTINCT
|
||||
-- collapses multiple variants of one manga to a single canonical row, and the
|
||||
-- ON CONFLICT absorbs a canonical link that already exists. (A prior
|
||||
-- UPDATE-repoint could set two non-canonical rows of the SAME manga to the
|
||||
-- same canonical id in a single statement and violate the manga_genres PK,
|
||||
-- rolling the migration back and wedging startup.)
|
||||
INSERT INTO manga_genres (manga_id, genre_id)
|
||||
SELECT DISTINCT mg.manga_id, k.keep_id
|
||||
FROM manga_genres mg
|
||||
JOIN genres g ON mg.genre_id = g.id
|
||||
JOIN (SELECT lower(name) AS lname, (array_agg(id ORDER BY id))[1] AS keep_id
|
||||
FROM genres GROUP BY lower(name)) k
|
||||
ON lower(g.name) = k.lname
|
||||
WHERE g.id <> k.keep_id
|
||||
ON CONFLICT (manga_id, genre_id) DO NOTHING;
|
||||
|
||||
-- 2. Every non-canonical link now has a canonical sibling — drop the variants.
|
||||
DELETE FROM manga_genres mg
|
||||
USING (
|
||||
SELECT g.id AS dup_id
|
||||
FROM genres g
|
||||
JOIN (SELECT lower(name) AS lname, (array_agg(id ORDER BY id))[1] AS keep_id
|
||||
FROM genres GROUP BY lower(name)) k
|
||||
ON lower(g.name) = k.lname AND g.id <> k.keep_id
|
||||
) d
|
||||
WHERE mg.genre_id = d.dup_id;
|
||||
USING genres g,
|
||||
(SELECT lower(name) AS lname, (array_agg(id ORDER BY id))[1] AS keep_id
|
||||
FROM genres GROUP BY lower(name)) k
|
||||
WHERE mg.genre_id = g.id AND lower(g.name) = k.lname AND g.id <> k.keep_id;
|
||||
|
||||
-- Remove the now-orphaned duplicate genre rows.
|
||||
DELETE FROM genres g
|
||||
|
||||
Reference in New Issue
Block a user