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>
159 lines
5.3 KiB
Go
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
|
|
}
|