-- Missing-pair pass (M485). A track marked missing whose replacement is already -- on disk under another row — most often a Lidarr quality upgrade, mp3 → flac at -- a new path — is folded into that replacement, so its history moves across and -- re-acquisition stops chasing music the library already has. -- -- Move adoption (#2528) cannot catch these: it links a new file to a missing row -- at the moment the file is first inserted, and the replacement arrives as a -- re-encode with no recording MBID, so it has nothing to match on. AcoustID may -- give it an MBID later; by then both rows exist and only a fold can join them. -- name: ListMissingTrackPairs :many -- Every candidate (missing, present) pair, with how many candidates each side -- has. Two rules, measured on the operator's library on 2026-10-07 (#5274): -- -- mbid the same recording MBID, within the album group (the album row, or -- albums sharing a release group). 142 of 359 pairs. -- title the same album row and the same title, ignoring case. 213 more. The -- replacements are often not the original file — another edition, -- re-encoded, untagged — so the metadata is what is left to go on. -- -- Deliberately NOT gated on duration or track position. On the 142 pairs known -- to be one recording, 18% differed by over 2s (old mp3 durations are poor -- estimates), and the poorly tagged set is exactly where numbering is broken. -- -- A pair whose MBIDs are both set and differ is not a title match: the tags say -- they are different recordings. The caller folds a pair only when each side has -- exactly one candidate; anything else is ambiguous and left alone. WITH grp AS ( SELECT id AS album_id, COALESCE(release_group_mbid, id::text) AS g FROM albums ), cand AS ( SELECT m.id AS missing_id, p.id AS present_id, 'mbid'::text AS rule FROM tracks m JOIN grp gm ON gm.album_id = m.album_id JOIN grp gp ON gp.g = gm.g JOIN tracks p ON p.album_id = gp.album_id AND p.missing_since IS NULL AND p.mbid = m.mbid WHERE m.missing_since IS NOT NULL AND m.mbid IS NOT NULL UNION ALL SELECT m.id, p.id, 'title'::text FROM tracks m JOIN tracks p ON p.album_id = m.album_id AND p.missing_since IS NULL AND lower(p.title) = lower(m.title) WHERE m.missing_since IS NOT NULL AND (m.mbid IS NULL OR p.mbid IS NULL OR m.mbid = p.mbid) ), pairs AS ( -- A pair both rules found is one candidate, credited to the stronger rule. SELECT missing_id, present_id, min(rule)::text AS rule FROM cand GROUP BY missing_id, present_id ) SELECT pr.missing_id, pr.present_id, pr.rule, m.file_path AS missing_path, p.file_path AS present_path, (count(*) OVER (PARTITION BY pr.missing_id))::int AS missing_candidates, (count(*) OVER (PARTITION BY pr.present_id))::int AS present_candidates FROM pairs pr JOIN tracks m ON m.id = pr.missing_id JOIN tracks p ON p.id = pr.present_id ORDER BY m.file_path, p.file_path; -- name: LockTracksForPairing :many -- Locks both rows of a pair for the fold's transaction, in id order so two -- passes cannot deadlock, and returns what the fold re-checks: the missing row -- must still be missing and the present one still present. A scan may have -- restored or adopted the first, or marked the second, since the pairs were listed. SELECT id, missing_since FROM tracks WHERE id = ANY(sqlc.arg(ids)::uuid[]) ORDER BY id FOR UPDATE;