Files
minstrel/internal/db/queries/wanted.sql
T
bvandeusenandClaude Opus 5.5 bf409d5f3f
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m18s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 5m33s
release / android (push) Successful in 5m47s
release / Build signed APK (releases and dev) (push) Successful in 6m4s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
fix: a second copy of a track counts once, joining words don't part two titles, and a newer matcher retries an older one's refusals (M502 #5569)
The second run's refusals were mostly albums holding a track twice (a
two-track single with three files), which no edition can pair, and
release names that differ only in "+" against "/". Tracks are now read
once per disc, number and title, and "and", "&" and "+" count for nothing
when titles are compared; an edition's own title also counts as the
album's name.

library_album_matches records which matcher version decided each album.
Refusals by an older version are matched again, even after the listing is
done, so an improvement reaches them without a migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:44:06 -04:00

342 lines
16 KiB
SQL

-- The wanted list (M502 #5520): release-type profiles, follows and wants.
-- name: ListReleaseTypeProfiles :many
SELECT p.*,
(SELECT COUNT(*) FROM follows f WHERE f.profile_id = p.id)::bigint AS follow_count
FROM release_type_profiles p
ORDER BY p.is_default DESC, p.name;
-- name: GetReleaseTypeProfile :one
SELECT * FROM release_type_profiles WHERE id = $1;
-- name: GetDefaultReleaseTypeProfile :one
SELECT * FROM release_type_profiles WHERE is_default;
-- name: CreateReleaseTypeProfile :one
INSERT INTO release_type_profiles (name, primary_types, secondary_types)
VALUES ($1, $2, $3)
RETURNING *;
-- name: UpdateReleaseTypeProfile :one
UPDATE release_type_profiles
SET name = $2,
primary_types = $3,
secondary_types = $4,
updated_at = now()
WHERE id = $1
RETURNING *;
-- name: ClearDefaultReleaseTypeProfile :exec
UPDATE release_type_profiles SET is_default = false, updated_at = now() WHERE is_default;
-- name: SetDefaultReleaseTypeProfile :execrows
UPDATE release_type_profiles SET is_default = true, updated_at = now() WHERE id = $1;
-- name: DeleteReleaseTypeProfile :execrows
-- The default stays; one in use is refused by the follows foreign key.
DELETE FROM release_type_profiles WHERE id = $1 AND NOT is_default;
-- name: UpsertFollow :one
-- Following an artist already followed changes its profile. want_existing
-- only ever turns on here; the worker turns it off once applied, and turning
-- it on again re-reads the baseline so the existing releases are judged.
INSERT INTO follows (artist_mbid, artist_name, profile_id, followed_by, want_existing)
VALUES (sqlc.arg(artist_mbid), sqlc.arg(artist_name), sqlc.arg(profile_id),
sqlc.narg(followed_by), sqlc.arg(want_existing))
ON CONFLICT (artist_mbid) DO UPDATE
SET artist_name = EXCLUDED.artist_name,
profile_id = EXCLUDED.profile_id,
want_existing = follows.want_existing OR EXCLUDED.want_existing,
baseline_at = CASE WHEN EXCLUDED.want_existing THEN NULL ELSE follows.baseline_at END,
updated_at = now()
RETURNING *;
-- name: GetFollow :one
SELECT f.*, p.name AS profile_name
FROM follows f
JOIN release_type_profiles p ON p.id = f.profile_id
WHERE f.artist_mbid = $1;
-- name: DeleteFollow :execrows
DELETE FROM follows WHERE artist_mbid = $1;
-- name: ListFollows :many
SELECT f.artist_mbid, f.artist_name, f.profile_id, f.followed_at, f.baseline_at, f.checked_at,
p.name AS profile_name,
(SELECT a.id FROM artists a WHERE a.mbid = f.artist_mbid) AS artist_id,
(SELECT COUNT(*) FROM wants w
WHERE w.artist_mbid = f.artist_mbid AND w.fulfilled_at IS NULL)::bigint AS open_wants
FROM follows f
JOIN release_type_profiles p ON p.id = f.profile_id
ORDER BY lower(f.artist_name), f.artist_mbid;
-- name: ListFollowsDue :many
-- Follows whose discography has not been applied since @before, never-read
-- first, so a new follow's baseline is not stuck behind daily re-checks.
SELECT f.*
FROM follows f
WHERE f.baseline_at IS NULL OR f.checked_at IS NULL OR f.checked_at < sqlc.arg(before)
ORDER BY f.baseline_at NULLS FIRST, f.checked_at NULLS FIRST, f.artist_mbid
LIMIT sqlc.arg(max_rows);
-- name: MarkFollowChecked :exec
UPDATE follows
SET checked_at = now(),
baseline_at = COALESCE(baseline_at, now()),
want_existing = false,
updated_at = now()
WHERE artist_mbid = $1;
-- name: ListFollowGroups :many
-- An artist's release groups from the catalogue, each with whether the
-- follow has judged it before.
SELECT g.mbid, g.title, g.primary_type, g.secondary_types, g.first_release_date,
EXISTS (SELECT 1 FROM follow_seen_groups s
WHERE s.artist_mbid = sqlc.arg(artist_mbid) AND s.release_group_mbid = g.mbid) AS seen
FROM mb_artist_release_groups ag
JOIN mb_release_groups g ON g.mbid = ag.release_group_mbid
WHERE ag.artist_mbid = sqlc.arg(artist_mbid)
ORDER BY g.first_release_date, g.title;
-- name: MarkGroupSeen :exec
INSERT INTO follow_seen_groups (artist_mbid, release_group_mbid)
VALUES ($1, $2)
ON CONFLICT DO NOTHING;
-- name: LibraryHoldsReleaseGroup :one
-- Whether the library already has the group: an album tagged with it or
-- with one of its releases, or failing that an album of the artist's with
-- the same title. Only albums with a track on disk count.
SELECT EXISTS (
SELECT 1 FROM albums a
WHERE (a.release_group_mbid = sqlc.arg(group_mbid)::text
OR a.mbid = sqlc.arg(group_mbid)
OR a.mbid IN (SELECT r.mbid FROM mb_releases r WHERE r.release_group_mbid = sqlc.arg(group_mbid))
OR (lower(a.title) = lower(sqlc.arg(title)::text)
AND a.artist_id IN (SELECT ar.id FROM artists ar WHERE ar.mbid = sqlc.arg(artist_mbid)::text)))
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL)
);
-- name: WantReleaseGroup :one
-- Wants a release group, or returns the want already recorded for it. A
-- held want is not reopened here: the library has it.
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, primary_type,
first_release_date, reason, created_by)
VALUES (sqlc.arg(release_group_mbid), sqlc.arg(artist_mbid), sqlc.arg(artist_name), sqlc.arg(title),
sqlc.arg(primary_type), sqlc.arg(first_release_date), sqlc.arg(reason), sqlc.narg(created_by))
ON CONFLICT (release_group_mbid) DO UPDATE SET updated_at = wants.updated_at
RETURNING *;
-- name: GetWantByReleaseGroup :one
SELECT * FROM wants WHERE release_group_mbid = $1;
-- name: ListOpenWants :many
SELECT * FROM wants
WHERE fulfilled_at IS NULL
ORDER BY wanted_at, id
LIMIT $1;
-- name: WantHoldings :many
-- Each library album that may answer a want, with what it has on disk:
-- tracks present, whether one arrived since the want, and whether a track
-- missing before the want is still missing.
SELECT a.id AS album_id,
COUNT(*) FILTER (WHERE t.missing_since IS NULL)::int AS present,
COALESCE(bool_or(t.missing_since IS NULL AND t.added_at > sqlc.arg(since)::timestamptz), false)::bool AS arrived_since,
COALESCE(bool_or(t.missing_since IS NOT NULL AND t.missing_since < sqlc.arg(since)::timestamptz), false)::bool AS missing_before,
-- Missing tracks no track on disk stands in for, by position or
-- title. A missing row whose song is back at a new path (#5245) is
-- covered; a lost bonus track is not.
COUNT(*) FILTER (WHERE t.missing_since IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM tracks p
WHERE p.album_id = a.id AND p.missing_since IS NULL
AND ((p.track_number = t.track_number AND p.disc_number IS NOT DISTINCT FROM t.disc_number)
OR lower(p.title) = lower(t.title))))::int AS uncovered_missing
FROM albums a
JOIN tracks t ON t.album_id = a.id
WHERE a.release_group_mbid = sqlc.arg(group_mbid)::text
OR a.mbid = sqlc.arg(group_mbid)
OR a.id = sqlc.narg(album_id)
OR a.mbid IN (SELECT r.mbid FROM mb_releases r WHERE r.release_group_mbid = sqlc.arg(group_mbid))
GROUP BY a.id
ORDER BY present DESC, a.id;
-- name: FulfillWant :exec
UPDATE wants
SET fulfilled_at = now(),
album_id = sqlc.arg(album_id),
updated_at = now()
WHERE id = sqlc.arg(id) AND fulfilled_at IS NULL;
-- name: RepointWant :exec
-- A want recorded under a release id before #5241 is moved to its group.
UPDATE wants SET release_group_mbid = $2, updated_at = now() WHERE id = $1;
-- name: DeleteWant :execrows
DELETE FROM wants WHERE id = $1;
-- name: ListWants :many
-- The Wanted page: open wants (all but missing ones, or only missing ones),
-- or what was held most recently.
SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reason,
w.album_id, w.wanted_at, w.fulfilled_at,
-- A request names an album before the catalogue has read it: the
-- catalogue's type and date fill in once it has.
COALESCE(NULLIF(w.primary_type, ''), g.primary_type, '')::text AS primary_type,
COALESCE(g.secondary_types, '{}')::text[] AS secondary_types,
COALESCE(NULLIF(w.first_release_date, ''), g.first_release_date, '')::text AS first_release_date,
u.username AS requested_by,
(SELECT ar.id FROM artists ar WHERE ar.mbid = w.artist_mbid) AS library_artist_id,
-- The search schedule's last look (#5521), and the download running.
w.searched_at, w.search_outcome, w.search_considered, w.best_release, w.best_verdict, w.search_error,
-- Its latest download, running or ended, and what the import made of
-- it (#5522).
gr.id AS grab_id, gr.name AS grab_name, gr.grabbed_at, gr.origin AS grab_origin,
gr.quality AS grab_quality, gr.ended_at AS grab_ended_at, gr.end_reason AS grab_end_reason,
gr.progress AS grab_progress, gr.import_state AS grab_import_state,
gr.import_reason AS grab_import_reason, gr.end_detail AS grab_end_detail, gr.imported_at AS grab_imported_at,
gr.imported_files AS grab_imported_files
FROM wants w
LEFT JOIN users u ON u.id = w.created_by
LEFT JOIN mb_release_groups g ON g.mbid = w.release_group_mbid
LEFT JOIN grabs gr ON gr.id = (SELECT x.id FROM grabs x WHERE x.want_id = w.id
ORDER BY x.ended_at IS NULL DESC, x.grabbed_at DESC LIMIT 1)
WHERE CASE sqlc.arg(state)::text
WHEN 'held' THEN w.fulfilled_at IS NOT NULL
WHEN 'missing' THEN w.fulfilled_at IS NULL AND w.reason = 'missing'
ELSE w.fulfilled_at IS NULL AND w.reason <> 'missing'
END
ORDER BY COALESCE(w.fulfilled_at, w.wanted_at) DESC, w.id
LIMIT sqlc.arg(max_rows) OFFSET sqlc.arg(skip);
-- name: CountWants :one
SELECT COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason <> 'missing')::bigint AS wanted,
COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason = 'missing')::bigint AS missing,
COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL)::bigint AS held,
(SELECT COUNT(*) FROM follows)::bigint AS following,
-- The Wanted view's other tabs (#5522): downloads running, those
-- waiting on a person, and releases set aside.
(SELECT COUNT(*) FROM grabs WHERE ended_at IS NULL)::bigint AS activity,
(SELECT COUNT(*) FROM grabs
WHERE ended_at IS NULL AND import_state IN ('review', 'failed'))::bigint AS attention,
(SELECT COUNT(*) FROM release_blocks b
WHERE b.expires_at IS NULL OR b.expires_at > now())::bigint AS blocked
FROM wants;
-- The one-time listing of the library (#5567). ---------------------------
-- name: GetLibraryListing :one
SELECT library_listing_started_at, library_listed_at, library_unlisted
FROM acquisition_settings WHERE id = true;
-- name: StartLibraryListing :one
-- Marks the listing begun, once; its time is the cutoff for which albums
-- count as the library's.
UPDATE acquisition_settings
SET library_listing_started_at = COALESCE(library_listing_started_at, now())
WHERE id = true
RETURNING library_listing_started_at;
-- name: ListLibraryAlbums :execrows
-- Puts library albums on the list as held: each release group once, by the
-- album holding the most tracks. Only albums the library had at the cutoff,
-- and with album_id given only that album. A group already on the list is
-- left as it is. An album with tracks already missing is held from just
-- before the first loss, so re-acquisition still fetches it back.
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, reason, album_id, wanted_at, fulfilled_at)
SELECT DISTINCT ON (a.release_group_mbid)
a.release_group_mbid, ar.mbid, ar.name, a.title, 'library', a.id, now(),
COALESCE((SELECT MIN(t.missing_since) FROM tracks t WHERE t.album_id = a.id) - interval '1 second', now())
FROM albums a
JOIN artists ar ON ar.id = a.artist_id
WHERE a.release_group_mbid IS NOT NULL AND a.release_group_mbid <> ''
AND ar.mbid IS NOT NULL AND ar.mbid <> ''
AND a.created_at <= sqlc.arg(cutoff)::timestamptz
AND (sqlc.narg(album_id)::uuid IS NULL OR a.id = sqlc.narg(album_id)::uuid)
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
ORDER BY a.release_group_mbid,
(SELECT COUNT(*) FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL) DESC, a.id
ON CONFLICT (release_group_mbid) DO NOTHING;
-- name: RecordListedAlbums :exec
-- The albums ListLibraryAlbums takes are decided as listed by their tags,
-- so the matching that follows leaves them alone (#5569).
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid)
SELECT a.id, 'listed', 'tags', a.release_group_mbid
FROM albums a
JOIN artists ar ON ar.id = a.artist_id
WHERE a.release_group_mbid IS NOT NULL AND a.release_group_mbid <> ''
AND ar.mbid IS NOT NULL AND ar.mbid <> ''
AND a.created_at <= sqlc.arg(cutoff)::timestamptz
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
ON CONFLICT (album_id) DO NOTHING;
-- name: ListAlbumsToMatch :many
-- Library albums from before the cutoff that the listing has not decided,
-- or that an older matcher refused: each is matched to MusicBrainz (#5569).
-- An album row with no tracks is not an album in the library.
SELECT a.id, a.title, a.mbid, a.release_group_mbid, ar.name AS artist_name, ar.mbid AS artist_mbid
FROM albums a
JOIN artists ar ON ar.id = a.artist_id
WHERE a.created_at <= sqlc.arg(cutoff)::timestamptz
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
AND NOT EXISTS (SELECT 1 FROM library_album_matches m
WHERE m.album_id = a.id AND (m.outcome = 'listed' OR m.matcher >= sqlc.arg(matcher)::int))
ORDER BY a.created_at, a.id
LIMIT sqlc.arg(max_rows);
-- name: CountAlbumsToRematch :one
-- Refusals by an older matcher, which a finished listing goes back to.
SELECT COUNT(*) FROM library_album_matches
WHERE outcome = 'unmatched' AND matcher < sqlc.arg(matcher)::int;
-- name: ListAlbumTracksToMatch :many
-- What an album's tracks say about themselves, to check a match against.
SELECT title, disc_number, track_number, duration_ms, mbid
FROM tracks
WHERE album_id = $1
ORDER BY disc_number NULLS FIRST, track_number NULLS LAST, title;
-- name: SetAlbumReleaseGroup :exec
-- Names a matched album's release group, filling only what nothing named.
UPDATE albums SET release_group_mbid = sqlc.arg(release_group_mbid), updated_at = now()
WHERE id = sqlc.arg(id) AND (release_group_mbid IS NULL OR release_group_mbid = '');
-- name: InsertLibraryWant :execrows
-- Puts one matched album on the list as held, from just before its first
-- lost track if it has one. A group already on the list is left as it is.
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date,
reason, album_id, wanted_at, fulfilled_at)
VALUES (sqlc.arg(release_group_mbid), sqlc.arg(artist_mbid), sqlc.arg(artist_name), sqlc.arg(title),
sqlc.arg(primary_type), sqlc.arg(first_release_date), 'library', sqlc.arg(album_id)::uuid, now(),
COALESCE((SELECT MIN(t.missing_since) FROM tracks t WHERE t.album_id = sqlc.arg(album_id)::uuid)
- interval '1 second', now()))
ON CONFLICT (release_group_mbid) DO NOTHING;
-- name: RecordAlbumMatch :exec
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid, detail, matcher)
VALUES (sqlc.arg(album_id), sqlc.arg(outcome), sqlc.arg(method), sqlc.arg(release_group_mbid), sqlc.arg(detail),
sqlc.arg(matcher))
ON CONFLICT (album_id) DO UPDATE
SET outcome = EXCLUDED.outcome, method = EXCLUDED.method, release_group_mbid = EXCLUDED.release_group_mbid,
detail = EXCLUDED.detail, matcher = EXCLUDED.matcher, decided_at = now();
-- name: FinishLibraryListing :one
-- The listing is done, or done again after a newer matcher's retries:
-- count the albums no match was found for. The first finish is kept.
UPDATE acquisition_settings
SET library_listed_at = COALESCE(library_listed_at, now()),
library_unlisted = (SELECT COUNT(*) FROM library_album_matches WHERE outcome = 'unmatched')
WHERE id = true
RETURNING library_listed_at, library_unlisted;
-- name: ListUnmatchedLibraryAlbums :many
-- The albums the listing could not match, for the Held tab.
SELECT m.album_id, a.title, ar.name AS artist_name, m.detail
FROM library_album_matches m
JOIN albums a ON a.id = m.album_id
JOIN artists ar ON ar.id = a.artist_id
WHERE m.outcome = 'unmatched'
ORDER BY ar.name, a.title
LIMIT sqlc.arg(max_rows);