// 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 (#5524). 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 (#5579). (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 (#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 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 (#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 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 (#5572). 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 (#5569). 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 (#5579). 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 (#5524). ------------------------------------------------------- // 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 (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, 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 (#5579): 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 (#5572): 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 (#5524). 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 (#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 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) AND ($3::text = '' OR strpos(lower(w.artist_name), lower($3::text)) > 0 OR strpos(lower(w.title), lower($3::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 $5 OFFSET $4 ` type ListWantsParams struct { State string ArtistMbid string 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. Ordered like the library (#5572): 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.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 (#5569). 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 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 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 (#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, 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 }