Files
minstrel/internal/db/dbq/wanted.sql.go
T
bvandeusenandClaude Opus 5.5 b887ab759b
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m19s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m28s
release / android (push) Successful in 5m44s
release / Build signed APK (releases and dev) (push) Successful in 5m58s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
fix: every library album is matched to MusicBrainz before it is listed, by its tags, its artist's discography or a search, and checked against its tracks (M502 #5569)
The one-time listing took only albums whose tags already named their
release group and artist, and left 340 off the list. The rest are now
matched: the release their tags name, else the artist's release groups
called what the album is called, else a MusicBrainz search by artist and
title. A group found by name is taken only when its tracks agree with the
album's (by disc and number, then title; length and title, or recording),
so a partial copy still matches and a same-titled other album does not.

library_album_matches records each album the listing decided, listed or
unmatched with why, so an album taken off the list is never re-listed. The
albums no match was found for are named on the Held tab. Album rows with
no tracks are no longer counted.

The length and title checks move from the importer to release, shared by
both.

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

1252 lines
37 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: wanted.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const clearDefaultReleaseTypeProfile = `-- name: ClearDefaultReleaseTypeProfile :exec
UPDATE release_type_profiles SET is_default = false, updated_at = now() WHERE is_default
`
func (q *Queries) ClearDefaultReleaseTypeProfile(ctx context.Context) error {
_, err := q.db.Exec(ctx, clearDefaultReleaseTypeProfile)
return err
}
const countWants = `-- 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
`
type CountWantsRow struct {
Wanted int64
Missing int64
Held int64
Following int64
Activity int64
Attention int64
Blocked int64
}
func (q *Queries) CountWants(ctx context.Context) (CountWantsRow, error) {
row := q.db.QueryRow(ctx, countWants)
var i CountWantsRow
err := row.Scan(
&i.Wanted,
&i.Missing,
&i.Held,
&i.Following,
&i.Activity,
&i.Attention,
&i.Blocked,
)
return i, err
}
const createReleaseTypeProfile = `-- name: CreateReleaseTypeProfile :one
INSERT INTO release_type_profiles (name, primary_types, secondary_types)
VALUES ($1, $2, $3)
RETURNING id, name, primary_types, secondary_types, is_default, created_at, updated_at
`
type CreateReleaseTypeProfileParams struct {
Name string
PrimaryTypes []string
SecondaryTypes []string
}
func (q *Queries) CreateReleaseTypeProfile(ctx context.Context, arg CreateReleaseTypeProfileParams) (ReleaseTypeProfile, error) {
row := q.db.QueryRow(ctx, createReleaseTypeProfile, arg.Name, arg.PrimaryTypes, arg.SecondaryTypes)
var i ReleaseTypeProfile
err := row.Scan(
&i.ID,
&i.Name,
&i.PrimaryTypes,
&i.SecondaryTypes,
&i.IsDefault,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteFollow = `-- name: DeleteFollow :execrows
DELETE FROM follows WHERE artist_mbid = $1
`
func (q *Queries) DeleteFollow(ctx context.Context, artistMbid string) (int64, error) {
result, err := q.db.Exec(ctx, deleteFollow, artistMbid)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const deleteReleaseTypeProfile = `-- name: DeleteReleaseTypeProfile :execrows
DELETE FROM release_type_profiles WHERE id = $1 AND NOT is_default
`
// The default stays; one in use is refused by the follows foreign key.
func (q *Queries) DeleteReleaseTypeProfile(ctx context.Context, id pgtype.UUID) (int64, error) {
result, err := q.db.Exec(ctx, deleteReleaseTypeProfile, id)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const deleteWant = `-- name: DeleteWant :execrows
DELETE FROM wants WHERE id = $1
`
func (q *Queries) DeleteWant(ctx context.Context, id pgtype.UUID) (int64, error) {
result, err := q.db.Exec(ctx, deleteWant, id)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const finishLibraryListing = `-- name: FinishLibraryListing :one
UPDATE acquisition_settings
SET library_listed_at = now(),
library_unlisted = (SELECT COUNT(*) FROM library_album_matches WHERE outcome = 'unmatched')
WHERE id = true AND library_listed_at IS NULL
RETURNING library_listed_at, library_unlisted
`
type FinishLibraryListingRow struct {
LibraryListedAt pgtype.Timestamptz
LibraryUnlisted int32
}
// The listing is done: count the albums no match was found for.
func (q *Queries) FinishLibraryListing(ctx context.Context) (FinishLibraryListingRow, error) {
row := q.db.QueryRow(ctx, finishLibraryListing)
var i FinishLibraryListingRow
err := row.Scan(&i.LibraryListedAt, &i.LibraryUnlisted)
return i, err
}
const fulfillWant = `-- name: FulfillWant :exec
UPDATE wants
SET fulfilled_at = now(),
album_id = $1,
updated_at = now()
WHERE id = $2 AND fulfilled_at IS NULL
`
type FulfillWantParams struct {
AlbumID pgtype.UUID
ID pgtype.UUID
}
func (q *Queries) FulfillWant(ctx context.Context, arg FulfillWantParams) error {
_, err := q.db.Exec(ctx, fulfillWant, arg.AlbumID, arg.ID)
return err
}
const getDefaultReleaseTypeProfile = `-- name: GetDefaultReleaseTypeProfile :one
SELECT id, name, primary_types, secondary_types, is_default, created_at, updated_at FROM release_type_profiles WHERE is_default
`
func (q *Queries) GetDefaultReleaseTypeProfile(ctx context.Context) (ReleaseTypeProfile, error) {
row := q.db.QueryRow(ctx, getDefaultReleaseTypeProfile)
var i ReleaseTypeProfile
err := row.Scan(
&i.ID,
&i.Name,
&i.PrimaryTypes,
&i.SecondaryTypes,
&i.IsDefault,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getFollow = `-- name: GetFollow :one
SELECT f.artist_mbid, f.artist_name, f.profile_id, f.followed_by, f.followed_at, f.baseline_at, f.want_existing, f.checked_at, f.updated_at, p.name AS profile_name
FROM follows f
JOIN release_type_profiles p ON p.id = f.profile_id
WHERE f.artist_mbid = $1
`
type GetFollowRow struct {
ArtistMbid string
ArtistName string
ProfileID pgtype.UUID
FollowedBy pgtype.UUID
FollowedAt pgtype.Timestamptz
BaselineAt pgtype.Timestamptz
WantExisting bool
CheckedAt pgtype.Timestamptz
UpdatedAt pgtype.Timestamptz
ProfileName string
}
func (q *Queries) GetFollow(ctx context.Context, artistMbid string) (GetFollowRow, error) {
row := q.db.QueryRow(ctx, getFollow, artistMbid)
var i GetFollowRow
err := row.Scan(
&i.ArtistMbid,
&i.ArtistName,
&i.ProfileID,
&i.FollowedBy,
&i.FollowedAt,
&i.BaselineAt,
&i.WantExisting,
&i.CheckedAt,
&i.UpdatedAt,
&i.ProfileName,
)
return i, err
}
const getLibraryListing = `-- name: GetLibraryListing :one
SELECT library_listing_started_at, library_listed_at, library_unlisted
FROM acquisition_settings WHERE id = true
`
type GetLibraryListingRow struct {
LibraryListingStartedAt pgtype.Timestamptz
LibraryListedAt pgtype.Timestamptz
LibraryUnlisted int32
}
// The one-time listing of the library (#5567). ---------------------------
func (q *Queries) GetLibraryListing(ctx context.Context) (GetLibraryListingRow, error) {
row := q.db.QueryRow(ctx, getLibraryListing)
var i GetLibraryListingRow
err := row.Scan(&i.LibraryListingStartedAt, &i.LibraryListedAt, &i.LibraryUnlisted)
return i, err
}
const getReleaseTypeProfile = `-- name: GetReleaseTypeProfile :one
SELECT id, name, primary_types, secondary_types, is_default, created_at, updated_at FROM release_type_profiles WHERE id = $1
`
func (q *Queries) GetReleaseTypeProfile(ctx context.Context, id pgtype.UUID) (ReleaseTypeProfile, error) {
row := q.db.QueryRow(ctx, getReleaseTypeProfile, id)
var i ReleaseTypeProfile
err := row.Scan(
&i.ID,
&i.Name,
&i.PrimaryTypes,
&i.SecondaryTypes,
&i.IsDefault,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getWantByReleaseGroup = `-- name: GetWantByReleaseGroup :one
SELECT id, release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date, reason, album_id, created_by, wanted_at, fulfilled_at, created_at, updated_at, searched_at, search_outcome, search_considered, best_release, best_verdict, search_error FROM wants WHERE release_group_mbid = $1
`
func (q *Queries) GetWantByReleaseGroup(ctx context.Context, releaseGroupMbid string) (Want, error) {
row := q.db.QueryRow(ctx, getWantByReleaseGroup, releaseGroupMbid)
var i Want
err := row.Scan(
&i.ID,
&i.ReleaseGroupMbid,
&i.ArtistMbid,
&i.ArtistName,
&i.Title,
&i.PrimaryType,
&i.FirstReleaseDate,
&i.Reason,
&i.AlbumID,
&i.CreatedBy,
&i.WantedAt,
&i.FulfilledAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.SearchedAt,
&i.SearchOutcome,
&i.SearchConsidered,
&i.BestRelease,
&i.BestVerdict,
&i.SearchError,
)
return i, err
}
const insertLibraryWant = `-- name: InsertLibraryWant :execrows
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date,
reason, album_id, wanted_at, fulfilled_at)
VALUES ($1, $2, $3, $4,
$5, $6, 'library', $7::uuid, now(),
COALESCE((SELECT MIN(t.missing_since) FROM tracks t WHERE t.album_id = $7::uuid)
- interval '1 second', now()))
ON CONFLICT (release_group_mbid) DO NOTHING
`
type InsertLibraryWantParams struct {
ReleaseGroupMbid string
ArtistMbid string
ArtistName string
Title string
PrimaryType string
FirstReleaseDate string
AlbumID pgtype.UUID
}
// 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.
func (q *Queries) InsertLibraryWant(ctx context.Context, arg InsertLibraryWantParams) (int64, error) {
result, err := q.db.Exec(ctx, insertLibraryWant,
arg.ReleaseGroupMbid,
arg.ArtistMbid,
arg.ArtistName,
arg.Title,
arg.PrimaryType,
arg.FirstReleaseDate,
arg.AlbumID,
)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const libraryHoldsReleaseGroup = `-- name: LibraryHoldsReleaseGroup :one
SELECT EXISTS (
SELECT 1 FROM albums a
WHERE (a.release_group_mbid = $1::text
OR a.mbid = $1
OR a.mbid IN (SELECT r.mbid FROM mb_releases r WHERE r.release_group_mbid = $1)
OR (lower(a.title) = lower($2::text)
AND a.artist_id IN (SELECT ar.id FROM artists ar WHERE ar.mbid = $3::text)))
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL)
)
`
type LibraryHoldsReleaseGroupParams struct {
GroupMbid string
Title string
ArtistMbid string
}
// 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.
func (q *Queries) LibraryHoldsReleaseGroup(ctx context.Context, arg LibraryHoldsReleaseGroupParams) (bool, error) {
row := q.db.QueryRow(ctx, libraryHoldsReleaseGroup, arg.GroupMbid, arg.Title, arg.ArtistMbid)
var exists bool
err := row.Scan(&exists)
return exists, err
}
const listAlbumTracksToMatch = `-- name: ListAlbumTracksToMatch :many
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
`
type ListAlbumTracksToMatchRow struct {
Title string
DiscNumber *int32
TrackNumber *int32
DurationMs int32
Mbid *string
}
// What an album's tracks say about themselves, to check a match against.
func (q *Queries) ListAlbumTracksToMatch(ctx context.Context, albumID pgtype.UUID) ([]ListAlbumTracksToMatchRow, error) {
rows, err := q.db.Query(ctx, listAlbumTracksToMatch, albumID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAlbumTracksToMatchRow
for rows.Next() {
var i ListAlbumTracksToMatchRow
if err := rows.Scan(
&i.Title,
&i.DiscNumber,
&i.TrackNumber,
&i.DurationMs,
&i.Mbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsToMatch = `-- name: ListAlbumsToMatch :many
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 <= $1::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)
ORDER BY a.created_at, a.id
LIMIT $2
`
type ListAlbumsToMatchParams struct {
Cutoff pgtype.Timestamptz
MaxRows int32
}
type ListAlbumsToMatchRow struct {
ID pgtype.UUID
Title string
Mbid *string
ReleaseGroupMbid *string
ArtistName string
ArtistMbid *string
}
// Library albums from before the cutoff that the listing has not decided:
// each is matched to MusicBrainz (#5569). An album row with no tracks is
// not an album in the library.
func (q *Queries) ListAlbumsToMatch(ctx context.Context, arg ListAlbumsToMatchParams) ([]ListAlbumsToMatchRow, error) {
rows, err := q.db.Query(ctx, listAlbumsToMatch, arg.Cutoff, arg.MaxRows)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAlbumsToMatchRow
for rows.Next() {
var i ListAlbumsToMatchRow
if err := rows.Scan(
&i.ID,
&i.Title,
&i.Mbid,
&i.ReleaseGroupMbid,
&i.ArtistName,
&i.ArtistMbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listFollowGroups = `-- name: ListFollowGroups :many
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 = $1 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 = $1
ORDER BY g.first_release_date, g.title
`
type ListFollowGroupsRow struct {
Mbid string
Title string
PrimaryType string
SecondaryTypes []string
FirstReleaseDate string
Seen bool
}
// An artist's release groups from the catalogue, each with whether the
// follow has judged it before.
func (q *Queries) ListFollowGroups(ctx context.Context, artistMbid string) ([]ListFollowGroupsRow, error) {
rows, err := q.db.Query(ctx, listFollowGroups, artistMbid)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListFollowGroupsRow
for rows.Next() {
var i ListFollowGroupsRow
if err := rows.Scan(
&i.Mbid,
&i.Title,
&i.PrimaryType,
&i.SecondaryTypes,
&i.FirstReleaseDate,
&i.Seen,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listFollows = `-- 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
`
type ListFollowsRow struct {
ArtistMbid string
ArtistName string
ProfileID pgtype.UUID
FollowedAt pgtype.Timestamptz
BaselineAt pgtype.Timestamptz
CheckedAt pgtype.Timestamptz
ProfileName string
ArtistID pgtype.UUID
OpenWants int64
}
func (q *Queries) ListFollows(ctx context.Context) ([]ListFollowsRow, error) {
rows, err := q.db.Query(ctx, listFollows)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListFollowsRow
for rows.Next() {
var i ListFollowsRow
if err := rows.Scan(
&i.ArtistMbid,
&i.ArtistName,
&i.ProfileID,
&i.FollowedAt,
&i.BaselineAt,
&i.CheckedAt,
&i.ProfileName,
&i.ArtistID,
&i.OpenWants,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listFollowsDue = `-- name: ListFollowsDue :many
SELECT f.artist_mbid, f.artist_name, f.profile_id, f.followed_by, f.followed_at, f.baseline_at, f.want_existing, f.checked_at, f.updated_at
FROM follows f
WHERE f.baseline_at IS NULL OR f.checked_at IS NULL OR f.checked_at < $1
ORDER BY f.baseline_at NULLS FIRST, f.checked_at NULLS FIRST, f.artist_mbid
LIMIT $2
`
type ListFollowsDueParams struct {
Before pgtype.Timestamptz
MaxRows int32
}
// 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.
func (q *Queries) ListFollowsDue(ctx context.Context, arg ListFollowsDueParams) ([]Follow, error) {
rows, err := q.db.Query(ctx, listFollowsDue, arg.Before, arg.MaxRows)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Follow
for rows.Next() {
var i Follow
if err := rows.Scan(
&i.ArtistMbid,
&i.ArtistName,
&i.ProfileID,
&i.FollowedBy,
&i.FollowedAt,
&i.BaselineAt,
&i.WantExisting,
&i.CheckedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listLibraryAlbums = `-- name: ListLibraryAlbums :execrows
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 <= $1::timestamptz
AND ($2::uuid IS NULL OR a.id = $2::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
`
type ListLibraryAlbumsParams struct {
Cutoff pgtype.Timestamptz
AlbumID pgtype.UUID
}
// 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.
func (q *Queries) ListLibraryAlbums(ctx context.Context, arg ListLibraryAlbumsParams) (int64, error) {
result, err := q.db.Exec(ctx, listLibraryAlbums, arg.Cutoff, arg.AlbumID)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const listOpenWants = `-- name: ListOpenWants :many
SELECT id, release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date, reason, album_id, created_by, wanted_at, fulfilled_at, created_at, updated_at, searched_at, search_outcome, search_considered, best_release, best_verdict, search_error FROM wants
WHERE fulfilled_at IS NULL
ORDER BY wanted_at, id
LIMIT $1
`
func (q *Queries) ListOpenWants(ctx context.Context, limit int32) ([]Want, error) {
rows, err := q.db.Query(ctx, listOpenWants, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Want
for rows.Next() {
var i Want
if err := rows.Scan(
&i.ID,
&i.ReleaseGroupMbid,
&i.ArtistMbid,
&i.ArtistName,
&i.Title,
&i.PrimaryType,
&i.FirstReleaseDate,
&i.Reason,
&i.AlbumID,
&i.CreatedBy,
&i.WantedAt,
&i.FulfilledAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.SearchedAt,
&i.SearchOutcome,
&i.SearchConsidered,
&i.BestRelease,
&i.BestVerdict,
&i.SearchError,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listReleaseTypeProfiles = `-- name: ListReleaseTypeProfiles :many
SELECT p.id, p.name, p.primary_types, p.secondary_types, p.is_default, p.created_at, p.updated_at,
(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
`
type ListReleaseTypeProfilesRow struct {
ID pgtype.UUID
Name string
PrimaryTypes []string
SecondaryTypes []string
IsDefault bool
CreatedAt pgtype.Timestamptz
UpdatedAt pgtype.Timestamptz
FollowCount int64
}
// The wanted list (M502 #5520): release-type profiles, follows and wants.
func (q *Queries) ListReleaseTypeProfiles(ctx context.Context) ([]ListReleaseTypeProfilesRow, error) {
rows, err := q.db.Query(ctx, listReleaseTypeProfiles)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListReleaseTypeProfilesRow
for rows.Next() {
var i ListReleaseTypeProfilesRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.PrimaryTypes,
&i.SecondaryTypes,
&i.IsDefault,
&i.CreatedAt,
&i.UpdatedAt,
&i.FollowCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listUnmatchedLibraryAlbums = `-- name: ListUnmatchedLibraryAlbums :many
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 $1
`
type ListUnmatchedLibraryAlbumsRow struct {
AlbumID pgtype.UUID
Title string
ArtistName string
Detail string
}
// The albums the listing could not match, for the Held tab.
func (q *Queries) ListUnmatchedLibraryAlbums(ctx context.Context, maxRows int32) ([]ListUnmatchedLibraryAlbumsRow, error) {
rows, err := q.db.Query(ctx, listUnmatchedLibraryAlbums, maxRows)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListUnmatchedLibraryAlbumsRow
for rows.Next() {
var i ListUnmatchedLibraryAlbumsRow
if err := rows.Scan(
&i.AlbumID,
&i.Title,
&i.ArtistName,
&i.Detail,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listWants = `-- name: ListWants :many
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 $1::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 $3 OFFSET $2
`
type ListWantsParams struct {
State string
Skip int32
MaxRows int32
}
type ListWantsRow struct {
ID pgtype.UUID
ReleaseGroupMbid string
ArtistMbid string
ArtistName string
Title string
Reason string
AlbumID pgtype.UUID
WantedAt pgtype.Timestamptz
FulfilledAt pgtype.Timestamptz
PrimaryType string
SecondaryTypes []string
FirstReleaseDate string
RequestedBy *string
LibraryArtistID pgtype.UUID
SearchedAt pgtype.Timestamptz
SearchOutcome string
SearchConsidered int32
BestRelease string
BestVerdict string
SearchError string
GrabID pgtype.UUID
GrabName *string
GrabbedAt pgtype.Timestamptz
GrabOrigin *string
GrabQuality *string
GrabEndedAt pgtype.Timestamptz
GrabEndReason *string
GrabProgress *float32
GrabImportState *string
GrabImportReason *string
GrabEndDetail *string
GrabImportedAt pgtype.Timestamptz
GrabImportedFiles *int32
}
// The Wanted page: open wants (all but missing ones, or only missing ones),
// or what was held most recently.
func (q *Queries) ListWants(ctx context.Context, arg ListWantsParams) ([]ListWantsRow, error) {
rows, err := q.db.Query(ctx, listWants, arg.State, arg.Skip, arg.MaxRows)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListWantsRow
for rows.Next() {
var i ListWantsRow
if err := rows.Scan(
&i.ID,
&i.ReleaseGroupMbid,
&i.ArtistMbid,
&i.ArtistName,
&i.Title,
&i.Reason,
&i.AlbumID,
&i.WantedAt,
&i.FulfilledAt,
&i.PrimaryType,
&i.SecondaryTypes,
&i.FirstReleaseDate,
&i.RequestedBy,
&i.LibraryArtistID,
&i.SearchedAt,
&i.SearchOutcome,
&i.SearchConsidered,
&i.BestRelease,
&i.BestVerdict,
&i.SearchError,
&i.GrabID,
&i.GrabName,
&i.GrabbedAt,
&i.GrabOrigin,
&i.GrabQuality,
&i.GrabEndedAt,
&i.GrabEndReason,
&i.GrabProgress,
&i.GrabImportState,
&i.GrabImportReason,
&i.GrabEndDetail,
&i.GrabImportedAt,
&i.GrabImportedFiles,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const markFollowChecked = `-- 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
`
func (q *Queries) MarkFollowChecked(ctx context.Context, artistMbid string) error {
_, err := q.db.Exec(ctx, markFollowChecked, artistMbid)
return err
}
const markGroupSeen = `-- name: MarkGroupSeen :exec
INSERT INTO follow_seen_groups (artist_mbid, release_group_mbid)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
`
type MarkGroupSeenParams struct {
ArtistMbid string
ReleaseGroupMbid string
}
func (q *Queries) MarkGroupSeen(ctx context.Context, arg MarkGroupSeenParams) error {
_, err := q.db.Exec(ctx, markGroupSeen, arg.ArtistMbid, arg.ReleaseGroupMbid)
return err
}
const recordAlbumMatch = `-- name: RecordAlbumMatch :exec
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid, detail)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (album_id) DO UPDATE
SET outcome = EXCLUDED.outcome, method = EXCLUDED.method,
release_group_mbid = EXCLUDED.release_group_mbid, detail = EXCLUDED.detail, decided_at = now()
`
type RecordAlbumMatchParams struct {
AlbumID pgtype.UUID
Outcome string
Method string
ReleaseGroupMbid string
Detail string
}
func (q *Queries) RecordAlbumMatch(ctx context.Context, arg RecordAlbumMatchParams) error {
_, err := q.db.Exec(ctx, recordAlbumMatch,
arg.AlbumID,
arg.Outcome,
arg.Method,
arg.ReleaseGroupMbid,
arg.Detail,
)
return err
}
const recordListedAlbums = `-- name: RecordListedAlbums :exec
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 <= $1::timestamptz
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
ON CONFLICT (album_id) DO NOTHING
`
// The albums ListLibraryAlbums takes are decided as listed by their tags,
// so the matching that follows leaves them alone (#5569).
func (q *Queries) RecordListedAlbums(ctx context.Context, cutoff pgtype.Timestamptz) error {
_, err := q.db.Exec(ctx, recordListedAlbums, cutoff)
return err
}
const repointWant = `-- name: RepointWant :exec
UPDATE wants SET release_group_mbid = $2, updated_at = now() WHERE id = $1
`
type RepointWantParams struct {
ID pgtype.UUID
ReleaseGroupMbid string
}
// A want recorded under a release id before #5241 is moved to its group.
func (q *Queries) RepointWant(ctx context.Context, arg RepointWantParams) error {
_, err := q.db.Exec(ctx, repointWant, arg.ID, arg.ReleaseGroupMbid)
return err
}
const setAlbumReleaseGroup = `-- name: SetAlbumReleaseGroup :exec
UPDATE albums SET release_group_mbid = $1, updated_at = now()
WHERE id = $2 AND (release_group_mbid IS NULL OR release_group_mbid = '')
`
type SetAlbumReleaseGroupParams struct {
ReleaseGroupMbid *string
ID pgtype.UUID
}
// Names a matched album's release group, filling only what nothing named.
func (q *Queries) SetAlbumReleaseGroup(ctx context.Context, arg SetAlbumReleaseGroupParams) error {
_, err := q.db.Exec(ctx, setAlbumReleaseGroup, arg.ReleaseGroupMbid, arg.ID)
return err
}
const setDefaultReleaseTypeProfile = `-- name: SetDefaultReleaseTypeProfile :execrows
UPDATE release_type_profiles SET is_default = true, updated_at = now() WHERE id = $1
`
func (q *Queries) SetDefaultReleaseTypeProfile(ctx context.Context, id pgtype.UUID) (int64, error) {
result, err := q.db.Exec(ctx, setDefaultReleaseTypeProfile, id)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const startLibraryListing = `-- name: StartLibraryListing :one
UPDATE acquisition_settings
SET library_listing_started_at = COALESCE(library_listing_started_at, now())
WHERE id = true
RETURNING library_listing_started_at
`
// Marks the listing begun, once; its time is the cutoff for which albums
// count as the library's.
func (q *Queries) StartLibraryListing(ctx context.Context) (pgtype.Timestamptz, error) {
row := q.db.QueryRow(ctx, startLibraryListing)
var library_listing_started_at pgtype.Timestamptz
err := row.Scan(&library_listing_started_at)
return library_listing_started_at, err
}
const updateReleaseTypeProfile = `-- name: UpdateReleaseTypeProfile :one
UPDATE release_type_profiles
SET name = $2,
primary_types = $3,
secondary_types = $4,
updated_at = now()
WHERE id = $1
RETURNING id, name, primary_types, secondary_types, is_default, created_at, updated_at
`
type UpdateReleaseTypeProfileParams struct {
ID pgtype.UUID
Name string
PrimaryTypes []string
SecondaryTypes []string
}
func (q *Queries) UpdateReleaseTypeProfile(ctx context.Context, arg UpdateReleaseTypeProfileParams) (ReleaseTypeProfile, error) {
row := q.db.QueryRow(ctx, updateReleaseTypeProfile,
arg.ID,
arg.Name,
arg.PrimaryTypes,
arg.SecondaryTypes,
)
var i ReleaseTypeProfile
err := row.Scan(
&i.ID,
&i.Name,
&i.PrimaryTypes,
&i.SecondaryTypes,
&i.IsDefault,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const upsertFollow = `-- name: UpsertFollow :one
INSERT INTO follows (artist_mbid, artist_name, profile_id, followed_by, want_existing)
VALUES ($1, $2, $3,
$4, $5)
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 artist_mbid, artist_name, profile_id, followed_by, followed_at, baseline_at, want_existing, checked_at, updated_at
`
type UpsertFollowParams struct {
ArtistMbid string
ArtistName string
ProfileID pgtype.UUID
FollowedBy pgtype.UUID
WantExisting bool
}
// 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.
func (q *Queries) UpsertFollow(ctx context.Context, arg UpsertFollowParams) (Follow, error) {
row := q.db.QueryRow(ctx, upsertFollow,
arg.ArtistMbid,
arg.ArtistName,
arg.ProfileID,
arg.FollowedBy,
arg.WantExisting,
)
var i Follow
err := row.Scan(
&i.ArtistMbid,
&i.ArtistName,
&i.ProfileID,
&i.FollowedBy,
&i.FollowedAt,
&i.BaselineAt,
&i.WantExisting,
&i.CheckedAt,
&i.UpdatedAt,
)
return i, err
}
const wantHoldings = `-- name: WantHoldings :many
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 > $1::timestamptz), false)::bool AS arrived_since,
COALESCE(bool_or(t.missing_since IS NOT NULL AND t.missing_since < $1::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 = $2::text
OR a.mbid = $2
OR a.id = $3
OR a.mbid IN (SELECT r.mbid FROM mb_releases r WHERE r.release_group_mbid = $2)
GROUP BY a.id
ORDER BY present DESC, a.id
`
type WantHoldingsParams struct {
Since pgtype.Timestamptz
GroupMbid string
AlbumID pgtype.UUID
}
type WantHoldingsRow struct {
AlbumID pgtype.UUID
Present int32
ArrivedSince bool
MissingBefore bool
UncoveredMissing int32
}
// 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.
func (q *Queries) WantHoldings(ctx context.Context, arg WantHoldingsParams) ([]WantHoldingsRow, error) {
rows, err := q.db.Query(ctx, wantHoldings, arg.Since, arg.GroupMbid, arg.AlbumID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []WantHoldingsRow
for rows.Next() {
var i WantHoldingsRow
if err := rows.Scan(
&i.AlbumID,
&i.Present,
&i.ArrivedSince,
&i.MissingBefore,
&i.UncoveredMissing,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const wantReleaseGroup = `-- name: WantReleaseGroup :one
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, primary_type,
first_release_date, reason, created_by)
VALUES ($1, $2, $3, $4,
$5, $6, $7, $8)
ON CONFLICT (release_group_mbid) DO UPDATE SET updated_at = wants.updated_at
RETURNING id, release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date, reason, album_id, created_by, wanted_at, fulfilled_at, created_at, updated_at, searched_at, search_outcome, search_considered, best_release, best_verdict, search_error
`
type WantReleaseGroupParams struct {
ReleaseGroupMbid string
ArtistMbid string
ArtistName string
Title string
PrimaryType string
FirstReleaseDate string
Reason string
CreatedBy pgtype.UUID
}
// Wants a release group, or returns the want already recorded for it. A
// held want is not reopened here: the library has it.
func (q *Queries) WantReleaseGroup(ctx context.Context, arg WantReleaseGroupParams) (Want, error) {
row := q.db.QueryRow(ctx, wantReleaseGroup,
arg.ReleaseGroupMbid,
arg.ArtistMbid,
arg.ArtistName,
arg.Title,
arg.PrimaryType,
arg.FirstReleaseDate,
arg.Reason,
arg.CreatedBy,
)
var i Want
err := row.Scan(
&i.ID,
&i.ReleaseGroupMbid,
&i.ArtistMbid,
&i.ArtistName,
&i.Title,
&i.PrimaryType,
&i.FirstReleaseDate,
&i.Reason,
&i.AlbumID,
&i.CreatedBy,
&i.WantedAt,
&i.FulfilledAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.SearchedAt,
&i.SearchOutcome,
&i.SearchConsidered,
&i.BestRelease,
&i.BestVerdict,
&i.SearchError,
)
return i, err
}