Files
minstrel/internal/db/dbq/wanted.sql.go
T
bvandeusenandClaude Opus 5.5 2c677b3184
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m44s
release / integration (push) Successful in 5m19s
release / android (push) Successful in 5m31s
release / Build signed APK (releases and dev) (push) Successful in 5m35s
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
feat: the library's albums go on the wanted list as held, once; after that the list is the truth (M502 #5567)
A one-time job in the wanted worker lists every album in the library as a
held want, one per release group, with a record that it happened
(acquisition_settings.library_listed_at). Albums whose tags name only a
release are named on MusicBrainz first, 120 a pass; what nothing names is
counted and shown on the Held tab.

Re-acquisition now wants back only albums on the list. Taking a held album
off the list leaves its files, and it is never fetched back; albums put in
the library folder by hand afterwards are not added.

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

1075 lines
32 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 s
SET library_listed_at = now(),
library_unlisted = (
SELECT COUNT(*) FROM albums a
WHERE a.created_at <= s.library_listing_started_at
AND NOT EXISTS (SELECT 1 FROM wants w
WHERE w.album_id = a.id
OR (a.release_group_mbid <> '' AND w.release_group_mbid = a.release_group_mbid)))
WHERE s.id = true AND s.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 from before the cutoff it could not
// list, because nothing names them to MusicBrainz.
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 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 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 listLibraryAlbumsToName = `-- name: ListLibraryAlbumsToName :many
SELECT a.id, a.title, a.mbid FROM albums a
WHERE a.release_group_mbid IS NULL AND a.mbid IS NOT NULL AND a.mbid <> ''
AND a.created_at <= $1::timestamptz
ORDER BY a.created_at, a.id
LIMIT $2
`
type ListLibraryAlbumsToNameParams struct {
Cutoff pgtype.Timestamptz
MaxRows int32
}
type ListLibraryAlbumsToNameRow struct {
ID pgtype.UUID
Title string
Mbid *string
}
// Library albums from before the cutoff that carry a release id but no
// release group yet: the listing asks MusicBrainz for each. ” marks one
// MusicBrainz could not name, so it is asked once.
func (q *Queries) ListLibraryAlbumsToName(ctx context.Context, arg ListLibraryAlbumsToNameParams) ([]ListLibraryAlbumsToNameRow, error) {
rows, err := q.db.Query(ctx, listLibraryAlbumsToName, arg.Cutoff, arg.MaxRows)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListLibraryAlbumsToNameRow
for rows.Next() {
var i ListLibraryAlbumsToNameRow
if err := rows.Scan(&i.ID, &i.Title, &i.Mbid); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, 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 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 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
WHERE id = $2 AND release_group_mbid IS NULL
`
type SetAlbumReleaseGroupParams struct {
ReleaseGroupMbid *string
ID pgtype.UUID
}
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
}