Files
minstrel/internal/db/dbq/missing_pairs.sql.go
T
bvandeusenandClaude Opus 5.5 1d4ef1a23f docs: query comments say what each query selects and why, not how it came to be (#6277)
Rewrites the internal/db/queries comments to rule 32 and regenerates
dbq, whose doc comments and embedded SQL carry them. The bugs behind the
tiered mixes, the md5 order seed, the score bands and the round-robin
arms are restated as the constraint each one enforces; counts from one
library and stale cross-references are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:31:13 -04:00

159 lines
5.3 KiB
Go

// 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)
AND (m.emptied_at IS DISTINCT FROM m.missing_since OR p.added_at > m.emptied_at)
),
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. A track marked missing whose replacement is already
// on disk under another row — most often a 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 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:
//
// mbid the same recording MBID, within the album group (the album row, or
// albums sharing a release group).
// title the same album row and the same title, ignoring case. 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. Pairs known to be one
// recording often differ 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.
//
// A slot the duplicate resolver emptied (emptied_at, migration 0095) pairs by
// title only with a track added since: the album held no copy of the song as
// long as it is, so one already there under the title is another song.
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
}