// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: missing_pairs.sql package dbq import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const listMissingTrackPairs = `-- name: ListMissingTrackPairs :many 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 ` type ListMissingTrackPairsRow struct { MissingID pgtype.UUID PresentID pgtype.UUID Rule string MissingPath string PresentPath string MissingCandidates int32 PresentCandidates int32 } // 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. // 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. func (q *Queries) ListMissingTrackPairs(ctx context.Context) ([]ListMissingTrackPairsRow, error) { rows, err := q.db.Query(ctx, listMissingTrackPairs) if err != nil { return nil, err } defer rows.Close() var items []ListMissingTrackPairsRow for rows.Next() { var i ListMissingTrackPairsRow if err := rows.Scan( &i.MissingID, &i.PresentID, &i.Rule, &i.MissingPath, &i.PresentPath, &i.MissingCandidates, &i.PresentCandidates, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const lockTracksForPairing = `-- name: LockTracksForPairing :many SELECT id, missing_since FROM tracks WHERE id = ANY($1::uuid[]) ORDER BY id FOR UPDATE ` type LockTracksForPairingRow struct { ID pgtype.UUID MissingSince pgtype.Timestamptz } // 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. func (q *Queries) LockTracksForPairing(ctx context.Context, ids []pgtype.UUID) ([]LockTracksForPairingRow, error) { rows, err := q.db.Query(ctx, lockTracksForPairing, ids) if err != nil { return nil, err } defer rows.Close() var items []LockTracksForPairingRow for rows.Next() { var i LockTracksForPairingRow if err := rows.Scan(&i.ID, &i.MissingSince); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil }