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>
1698 lines
52 KiB
Go
1698 lines
52 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 clearHeldUpgrades = `-- name: ClearHeldUpgrades :execrows
|
||
UPDATE wants SET upgrade = '', held_tier = '', held_quality = '', upgrade_checked_at = now()
|
||
WHERE fulfilled_at IS NOT NULL AND upgrade <> ''
|
||
AND NOT EXISTS (SELECT 1 FROM tracks t
|
||
WHERE t.album_id = wants.album_id AND t.missing_since IS NULL AND t.duration_ms > 0)
|
||
`
|
||
|
||
// A held want whose album has no track left to judge is not wanted again.
|
||
func (q *Queries) ClearHeldUpgrades(ctx context.Context) (int64, error) {
|
||
result, err := q.db.Exec(ctx, clearHeldUpgrades)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
const countAlbumsToMatch = `-- name: CountAlbumsToMatch :one
|
||
SELECT COUNT(*)
|
||
FROM albums a
|
||
WHERE (a.created_at <= $1::timestamptz
|
||
OR EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.added_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
|
||
AND (m.outcome IN ('listed', 'dismissed')
|
||
OR (m.matcher >= $2::int
|
||
AND NOT EXISTS (SELECT 1 FROM tracks t
|
||
WHERE t.album_id = a.id AND t.updated_at > m.decided_at))))
|
||
`
|
||
|
||
type CountAlbumsToMatchParams struct {
|
||
Cutoff pgtype.Timestamptz
|
||
Matcher int32
|
||
}
|
||
|
||
// Whether a finished listing has albums to go back to: those
|
||
// ListAlbumsToMatch would hand it.
|
||
func (q *Queries) CountAlbumsToMatch(ctx context.Context, arg CountAlbumsToMatchParams) (int64, error) {
|
||
row := q.db.QueryRow(ctx, countAlbumsToMatch, arg.Cutoff, arg.Matcher)
|
||
var count int64
|
||
err := row.Scan(&count)
|
||
return count, 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,
|
||
-- Held albums wanted for a better copy.
|
||
COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL AND upgrade <> '')::bigint AS upgrades,
|
||
COUNT(DISTINCT artist_mbid)::bigint AS artists,
|
||
-- Library albums waiting to be matched by hand.
|
||
(SELECT COUNT(*) FROM library_album_matches m
|
||
WHERE m.outcome = 'unmatched'
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))::bigint AS unmatched,
|
||
(SELECT COUNT(*) FROM follows)::bigint AS following,
|
||
-- The Wanted view's other tabs: 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
|
||
Upgrades int64
|
||
Artists int64
|
||
Unmatched 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.Upgrades,
|
||
&i.Artists,
|
||
&i.Unmatched,
|
||
&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 = COALESCE(library_listed_at, now()),
|
||
library_unlisted = (SELECT COUNT(*) FROM library_album_matches m
|
||
WHERE m.outcome = 'unmatched'
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))
|
||
WHERE id = true
|
||
RETURNING library_listed_at, library_unlisted
|
||
`
|
||
|
||
type FinishLibraryListingRow struct {
|
||
LibraryListedAt pgtype.Timestamptz
|
||
LibraryUnlisted int32
|
||
}
|
||
|
||
// 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.
|
||
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 getAlbumToMatch = `-- name: GetAlbumToMatch :one
|
||
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
|
||
JOIN library_album_matches m ON m.album_id = a.id AND m.outcome IN ('unmatched', 'dismissed')
|
||
WHERE a.id = $1
|
||
`
|
||
|
||
type GetAlbumToMatchRow struct {
|
||
ID pgtype.UUID
|
||
Title string
|
||
Mbid *string
|
||
ReleaseGroupMbid *string
|
||
ArtistName string
|
||
ArtistMbid *string
|
||
}
|
||
|
||
// One album a person is matching by hand, as the matcher reads albums.
|
||
func (q *Queries) GetAlbumToMatch(ctx context.Context, id pgtype.UUID) (GetAlbumToMatchRow, error) {
|
||
row := q.db.QueryRow(ctx, getAlbumToMatch, id)
|
||
var i GetAlbumToMatchRow
|
||
err := row.Scan(
|
||
&i.ID,
|
||
&i.Title,
|
||
&i.Mbid,
|
||
&i.ReleaseGroupMbid,
|
||
&i.ArtistName,
|
||
&i.ArtistMbid,
|
||
)
|
||
return i, 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. ---------------------------
|
||
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 getWantAlbum = `-- name: GetWantAlbum :one
|
||
SELECT id, release_group_mbid, album_id FROM wants WHERE id = $1
|
||
`
|
||
|
||
type GetWantAlbumRow struct {
|
||
ID pgtype.UUID
|
||
ReleaseGroupMbid string
|
||
AlbumID pgtype.UUID
|
||
}
|
||
|
||
// What the Wanted view's track list needs of a want.
|
||
func (q *Queries) GetWantAlbum(ctx context.Context, id pgtype.UUID) (GetWantAlbumRow, error) {
|
||
row := q.db.QueryRow(ctx, getWantAlbum, id)
|
||
var i GetWantAlbumRow
|
||
err := row.Scan(&i.ID, &i.ReleaseGroupMbid, &i.AlbumID)
|
||
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, upgrade, held_tier, held_quality, upgrade_checked_at 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,
|
||
&i.Upgrade,
|
||
&i.HeldTier,
|
||
&i.HeldQuality,
|
||
&i.UpgradeCheckedAt,
|
||
)
|
||
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
|
||
OR EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.added_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
|
||
AND (m.outcome IN ('listed', 'dismissed')
|
||
OR (m.matcher >= $2::int
|
||
AND NOT EXISTS (SELECT 1 FROM tracks t
|
||
WHERE t.album_id = a.id AND t.updated_at > m.decided_at))))
|
||
ORDER BY a.created_at, a.id
|
||
LIMIT $3
|
||
`
|
||
|
||
type ListAlbumsToMatchParams struct {
|
||
Cutoff pgtype.Timestamptz
|
||
Matcher int32
|
||
MaxRows int32
|
||
}
|
||
|
||
type ListAlbumsToMatchRow struct {
|
||
ID pgtype.UUID
|
||
Title string
|
||
Mbid *string
|
||
ReleaseGroupMbid *string
|
||
ArtistName string
|
||
ArtistMbid *string
|
||
}
|
||
|
||
// Library albums the listing has not decided, or that an older matcher
|
||
// refused, or whose tracks changed since they were refused: each is matched
|
||
// to MusicBrainz. A library album is one from before the cutoff, or
|
||
// one whose tracks were: a re-tag files a track under a new album row but
|
||
// keeps the track. An album row with no tracks is not an album in
|
||
// the library; one a person left off stays off.
|
||
func (q *Queries) ListAlbumsToMatch(ctx context.Context, arg ListAlbumsToMatchParams) ([]ListAlbumsToMatchRow, error) {
|
||
rows, err := q.db.Query(ctx, listAlbumsToMatch, arg.Cutoff, arg.Matcher, 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 listHeldWantFormats = `-- name: ListHeldWantFormats :many
|
||
|
||
SELECT w.id AS want_id, w.upgrade, w.held_tier, w.held_quality,
|
||
lower(t.file_format)::text AS file_format,
|
||
SUM(t.file_size)::bigint AS bytes,
|
||
SUM(t.duration_ms)::bigint AS duration_ms,
|
||
COUNT(*)::int AS tracks,
|
||
COUNT(*) FILTER (WHERE t.source_verdict = 'suspect')::int AS rips
|
||
FROM wants w
|
||
JOIN tracks t ON t.album_id = w.album_id
|
||
WHERE w.fulfilled_at IS NOT NULL
|
||
AND t.missing_since IS NULL AND t.duration_ms > 0
|
||
GROUP BY w.id, lower(t.file_format)
|
||
ORDER BY w.id
|
||
`
|
||
|
||
type ListHeldWantFormatsRow struct {
|
||
WantID pgtype.UUID
|
||
Upgrade string
|
||
HeldTier string
|
||
HeldQuality string
|
||
FileFormat string
|
||
Bytes int64
|
||
DurationMs int64
|
||
Tracks int32
|
||
Rips int32
|
||
}
|
||
|
||
// Upgrades. -------------------------------------------------------
|
||
// What each held album's copy is made of, for judging whether it is worth
|
||
// upgrading: its present tracks grouped by format, with their bytes and
|
||
// length summed, and how many are held back as video rips. A held want whose
|
||
// album has no track with a length yet has no rows.
|
||
func (q *Queries) ListHeldWantFormats(ctx context.Context) ([]ListHeldWantFormatsRow, error) {
|
||
rows, err := q.db.Query(ctx, listHeldWantFormats)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListHeldWantFormatsRow
|
||
for rows.Next() {
|
||
var i ListHeldWantFormatsRow
|
||
if err := rows.Scan(
|
||
&i.WantID,
|
||
&i.Upgrade,
|
||
&i.HeldTier,
|
||
&i.HeldQuality,
|
||
&i.FileFormat,
|
||
&i.Bytes,
|
||
&i.DurationMs,
|
||
&i.Tracks,
|
||
&i.Rips,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listLibraryAlbumTracks = `-- name: ListLibraryAlbumTracks :many
|
||
SELECT title, disc_number, track_number, duration_ms, (missing_since IS NOT NULL)::bool AS missing
|
||
FROM tracks
|
||
WHERE album_id = $1
|
||
ORDER BY disc_number NULLS FIRST, track_number NULLS LAST, title
|
||
`
|
||
|
||
type ListLibraryAlbumTracksRow struct {
|
||
Title string
|
||
DiscNumber *int32
|
||
TrackNumber *int32
|
||
DurationMs int32
|
||
Missing bool
|
||
}
|
||
|
||
// A held or missing album's own tracks, for the Wanted view's track list;
|
||
// missing says its file is gone.
|
||
func (q *Queries) ListLibraryAlbumTracks(ctx context.Context, albumID pgtype.UUID) ([]ListLibraryAlbumTracksRow, error) {
|
||
rows, err := q.db.Query(ctx, listLibraryAlbumTracks, albumID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListLibraryAlbumTracksRow
|
||
for rows.Next() {
|
||
var i ListLibraryAlbumTracksRow
|
||
if err := rows.Scan(
|
||
&i.Title,
|
||
&i.DiscNumber,
|
||
&i.TrackNumber,
|
||
&i.DurationMs,
|
||
&i.Missing,
|
||
); 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, upgrade, held_tier, held_quality, upgrade_checked_at 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,
|
||
&i.Upgrade,
|
||
&i.HeldTier,
|
||
&i.HeldQuality,
|
||
&i.UpgradeCheckedAt,
|
||
); 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: 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, m.outcome, m.decided_at,
|
||
(SELECT COUNT(*) FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL)::int AS tracks
|
||
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 IN ('unmatched', 'dismissed')
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id)
|
||
ORDER BY m.outcome DESC, lower(ar.name), lower(a.title)
|
||
LIMIT $1
|
||
`
|
||
|
||
type ListUnmatchedLibraryAlbumsRow struct {
|
||
AlbumID pgtype.UUID
|
||
Title string
|
||
ArtistName string
|
||
Detail string
|
||
Outcome string
|
||
DecidedAt pgtype.Timestamptz
|
||
Tracks int32
|
||
}
|
||
|
||
// The Collection's Unmatched section: the albums the listing could
|
||
// not match, and below them the ones a person left off. An album whose
|
||
// tracks have all moved to another row is gone, not unmatched.
|
||
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,
|
||
&i.Outcome,
|
||
&i.DecidedAt,
|
||
&i.Tracks,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listWantArtists = `-- name: ListWantArtists :many
|
||
WITH a AS (
|
||
SELECT w.artist_mbid,
|
||
(array_agg(w.artist_name ORDER BY w.wanted_at DESC))[1] AS artist_name,
|
||
COUNT(*) FILTER (WHERE w.fulfilled_at IS NOT NULL)::int AS held,
|
||
COUNT(*) FILTER (WHERE w.fulfilled_at IS NULL AND w.reason <> 'missing')::int AS wanted,
|
||
COUNT(*) FILTER (WHERE w.fulfilled_at IS NULL AND w.reason = 'missing')::int AS missing,
|
||
COUNT(*)::int AS albums,
|
||
bool_or($1::text <> '' AND strpos(lower(w.title), lower($1::text)) > 0) AS title_match
|
||
FROM wants w
|
||
WHERE $4::text = '' OR w.artist_mbid = $4::text
|
||
GROUP BY w.artist_mbid
|
||
)
|
||
SELECT a.artist_mbid, a.artist_name::text AS artist_name, a.held, a.wanted, a.missing, a.albums,
|
||
COALESCE(NULLIF(la.sort_name, ''), a.artist_name)::text AS sort_name,
|
||
la.id AS library_artist_id,
|
||
(f.artist_mbid IS NOT NULL)::bool AS following,
|
||
(SELECT COUNT(*) FROM grabs g JOIN wants x ON x.id = g.want_id
|
||
WHERE x.artist_mbid = a.artist_mbid AND g.ended_at IS NULL)::int AS downloading,
|
||
COUNT(*) OVER () AS total
|
||
FROM a
|
||
LEFT JOIN LATERAL (SELECT ar.id, ar.sort_name FROM artists ar WHERE ar.mbid = a.artist_mbid LIMIT 1) la ON true
|
||
LEFT JOIN follows f ON f.artist_mbid = a.artist_mbid
|
||
WHERE $1::text = ''
|
||
OR strpos(lower(a.artist_name), lower($1::text)) > 0
|
||
OR a.title_match
|
||
ORDER BY lower(COALESCE(NULLIF(la.sort_name, ''), a.artist_name)), a.artist_mbid
|
||
LIMIT $3 OFFSET $2
|
||
`
|
||
|
||
type ListWantArtistsParams struct {
|
||
Q string
|
||
Skip int32
|
||
MaxRows int32
|
||
ArtistMbid string
|
||
}
|
||
|
||
type ListWantArtistsRow struct {
|
||
ArtistMbid string
|
||
ArtistName string
|
||
Held int32
|
||
Wanted int32
|
||
Missing int32
|
||
Albums int32
|
||
SortName string
|
||
LibraryArtistID pgtype.UUID
|
||
Following bool
|
||
Downloading int32
|
||
Total int64
|
||
}
|
||
|
||
// The Wanted view's main page: every artist on the list, A–Z like
|
||
// the library, with how many of their albums are held of how many are on
|
||
// the list. q matches the artist's name or any of their albums' titles;
|
||
// artist_mbid narrows to one artist, for their page's header.
|
||
func (q *Queries) ListWantArtists(ctx context.Context, arg ListWantArtistsParams) ([]ListWantArtistsRow, error) {
|
||
rows, err := q.db.Query(ctx, listWantArtists,
|
||
arg.Q,
|
||
arg.Skip,
|
||
arg.MaxRows,
|
||
arg.ArtistMbid,
|
||
)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListWantArtistsRow
|
||
for rows.Next() {
|
||
var i ListWantArtistsRow
|
||
if err := rows.Scan(
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.Held,
|
||
&i.Wanted,
|
||
&i.Missing,
|
||
&i.Albums,
|
||
&i.SortName,
|
||
&i.LibraryArtistID,
|
||
&i.Following,
|
||
&i.Downloading,
|
||
&i.Total,
|
||
); 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,
|
||
-- Why a held album is wanted again, and the copy it has.
|
||
w.upgrade, w.held_quality,
|
||
-- 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,
|
||
la.id AS library_artist_id,
|
||
COUNT(*) OVER () AS total,
|
||
-- The search schedule's last look, 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.
|
||
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 LATERAL (SELECT ar.id, ar.sort_name FROM artists ar WHERE ar.mbid = w.artist_mbid LIMIT 1) la ON true
|
||
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 'all' THEN true
|
||
WHEN 'held' THEN w.fulfilled_at IS NOT NULL
|
||
WHEN 'upgrade' THEN w.fulfilled_at IS NOT NULL AND w.upgrade <> ''
|
||
WHEN 'missing' THEN w.fulfilled_at IS NULL AND w.reason = 'missing'
|
||
ELSE w.fulfilled_at IS NULL AND w.reason <> 'missing'
|
||
END
|
||
AND ($2::text = '' OR w.artist_mbid = $2::text)
|
||
-- want_ids narrows to given wants: the Missing page's folders.
|
||
AND (COALESCE(cardinality($3::uuid[]), 0) = 0 OR w.id = ANY($3::uuid[]))
|
||
AND ($4::text = ''
|
||
OR strpos(lower(w.artist_name), lower($4::text)) > 0
|
||
OR strpos(lower(w.title), lower($4::text)) > 0)
|
||
ORDER BY lower(COALESCE(NULLIF(la.sort_name, ''), w.artist_name)), w.artist_mbid,
|
||
NULLIF(COALESCE(NULLIF(w.first_release_date, ''), g.first_release_date, ''), '') NULLS LAST,
|
||
lower(w.title), w.id
|
||
LIMIT $6 OFFSET $5
|
||
`
|
||
|
||
type ListWantsParams struct {
|
||
State string
|
||
ArtistMbid string
|
||
WantIds []pgtype.UUID
|
||
Q 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
|
||
Upgrade string
|
||
HeldQuality string
|
||
PrimaryType string
|
||
SecondaryTypes []string
|
||
FirstReleaseDate string
|
||
RequestedBy *string
|
||
LibraryArtistID pgtype.UUID
|
||
Total int64
|
||
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),
|
||
// the held ones, or all of them; artist_mbid narrows to one artist's page,
|
||
// want_ids to given wants. Ordered like the library: artists A–Z by the
|
||
// library's sort name when it holds the artist, each artist's albums by
|
||
// release date, so the page groups them as they arrive. q narrows to an
|
||
// artist or album title; total counts every match, past the page.
|
||
func (q *Queries) ListWants(ctx context.Context, arg ListWantsParams) ([]ListWantsRow, error) {
|
||
rows, err := q.db.Query(ctx, listWants,
|
||
arg.State,
|
||
arg.ArtistMbid,
|
||
arg.WantIds,
|
||
arg.Q,
|
||
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.Upgrade,
|
||
&i.HeldQuality,
|
||
&i.PrimaryType,
|
||
&i.SecondaryTypes,
|
||
&i.FirstReleaseDate,
|
||
&i.RequestedBy,
|
||
&i.LibraryArtistID,
|
||
&i.Total,
|
||
&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, matcher)
|
||
VALUES ($1, $2, $3, $4, $5,
|
||
$6)
|
||
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()
|
||
`
|
||
|
||
type RecordAlbumMatchParams struct {
|
||
AlbumID pgtype.UUID
|
||
Outcome string
|
||
Method string
|
||
ReleaseGroupMbid string
|
||
Detail string
|
||
Matcher int32
|
||
}
|
||
|
||
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,
|
||
arg.Matcher,
|
||
)
|
||
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.
|
||
func (q *Queries) RecordListedAlbums(ctx context.Context, cutoff pgtype.Timestamptz) error {
|
||
_, err := q.db.Exec(ctx, recordListedAlbums, cutoff)
|
||
return err
|
||
}
|
||
|
||
const recountLibraryUnlisted = `-- name: RecountLibraryUnlisted :exec
|
||
UPDATE acquisition_settings
|
||
SET library_unlisted = (SELECT COUNT(*) FROM library_album_matches m
|
||
WHERE m.outcome = 'unmatched'
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))
|
||
WHERE id = true
|
||
`
|
||
|
||
// After a person matches or leaves off an album, the count the catalogue
|
||
// shows.
|
||
func (q *Queries) RecountLibraryUnlisted(ctx context.Context) error {
|
||
_, err := q.db.Exec(ctx, recountLibraryUnlisted)
|
||
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 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 setAlbumMatchOutcome = `-- name: SetAlbumMatchOutcome :execrows
|
||
UPDATE library_album_matches SET outcome = $1, decided_at = now()
|
||
WHERE album_id = $2 AND outcome = $3
|
||
`
|
||
|
||
type SetAlbumMatchOutcomeParams struct {
|
||
Outcome string
|
||
AlbumID pgtype.UUID
|
||
Was string
|
||
}
|
||
|
||
// Leave an unmatched album off the list, or put a left-off one back.
|
||
func (q *Queries) SetAlbumMatchOutcome(ctx context.Context, arg SetAlbumMatchOutcomeParams) (int64, error) {
|
||
result, err := q.db.Exec(ctx, setAlbumMatchOutcome, arg.Outcome, arg.AlbumID, arg.Was)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
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 setWantUpgrades = `-- name: SetWantUpgrades :exec
|
||
UPDATE wants w
|
||
SET upgrade = v.upgrade,
|
||
held_tier = v.held_tier,
|
||
held_quality = v.held_quality,
|
||
upgrade_checked_at = now(),
|
||
updated_at = CASE WHEN w.upgrade <> v.upgrade THEN now() ELSE w.updated_at END
|
||
FROM (SELECT unnest($1::uuid[]) AS id,
|
||
unnest($2::text[]) AS upgrade,
|
||
unnest($3::text[]) AS held_tier,
|
||
unnest($4::text[]) AS held_quality) v
|
||
WHERE w.id = v.id
|
||
`
|
||
|
||
type SetWantUpgradesParams struct {
|
||
Ids []pgtype.UUID
|
||
Upgrades []string
|
||
HeldTiers []string
|
||
HeldQualities []string
|
||
}
|
||
|
||
// Writes the upgrade check's verdicts, one row per want.
|
||
func (q *Queries) SetWantUpgrades(ctx context.Context, arg SetWantUpgradesParams) error {
|
||
_, err := q.db.Exec(ctx, setWantUpgrades,
|
||
arg.Ids,
|
||
arg.Upgrades,
|
||
arg.HeldTiers,
|
||
arg.HeldQualities,
|
||
)
|
||
return err
|
||
}
|
||
|
||
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 is
|
||
-- covered; a lost bonus track is not. A track with no disc number is
|
||
-- on disc 1, so a missing track 6 with no disc beside a present 1/6
|
||
-- does not keep the album wanted.
|
||
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 COALESCE(p.disc_number, 1) = COALESCE(t.disc_number, 1))
|
||
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, upgrade, held_tier, held_quality, upgrade_checked_at
|
||
`
|
||
|
||
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,
|
||
&i.Upgrade,
|
||
&i.HeldTier,
|
||
&i.HeldQuality,
|
||
&i.UpgradeCheckedAt,
|
||
)
|
||
return i, err
|
||
}
|