Files
minstrel/internal/db/dbq/albums.sql.go
T
bvandeusenandClaude Opus 5.5 e509d7d5a9
release / go (push) Successful in 2m33s
release / web (push) Successful in 1m41s
release / govulncheck (push) Successful in 22s
release / integration (push) Successful in 6m0s
release / android (push) Successful in 6m7s
release / Build signed APK (releases and dev) (push) Successful in 6m8s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m43s
release / Verify release artifacts (tag releases only) (push) Skipped
feat(lidarr): ask Lidarr for release groups; repair stored release-id requests (M483 #5244)
Lidarr's metadata is keyed by MusicBrainz release group, but re-acquisition
requested albums by their release id, so every add came back "not found".

- Sweeper requests an album by its tag-supplied release group, else the one
  MusicBrainz names (cached onto the album). An album MusicBrainz cannot name
  is skipped and counted, with no attempt spent.
- Reconciler: an add refused as not found re-reads the request's album id as
  a release (library first, then MusicBrainz), rewrites the request to the
  group and adds again. This repairs the requests already stored.
- Completion matches an album by release id or release group, and only once a
  track of it is on disk, so a re-acquisition request no longer completes
  against the row of the album it is trying to bring back.

Closes #5241.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:38:45 -04:00

942 lines
28 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: albums.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const countAlbums = `-- name: CountAlbums :one
SELECT COUNT(*) FROM albums
`
// M6a: total album count for the /api/library/albums envelope.
func (q *Queries) CountAlbums(ctx context.Context) (int64, error) {
row := q.db.QueryRow(ctx, countAlbums)
var count int64
err := row.Scan(&count)
return count, err
}
const countAlbumsByArtist = `-- name: CountAlbumsByArtist :one
SELECT COUNT(*) FROM albums WHERE artist_id = $1
`
// Used by request-progress reporting to count how many albums have been
// ingested for a matched artist while a request is still in flight.
func (q *Queries) CountAlbumsByArtist(ctx context.Context, artistID pgtype.UUID) (int64, error) {
row := q.db.QueryRow(ctx, countAlbumsByArtist, artistID)
var count int64
err := row.Scan(&count)
return count, err
}
const countAlbumsMatching = `-- name: CountAlbumsMatching :one
SELECT COUNT(*) FROM albums WHERE title ILIKE '%' || $1::text || '%'
`
func (q *Queries) CountAlbumsMatching(ctx context.Context, dollar_1 string) (int64, error) {
row := q.db.QueryRow(ctx, countAlbumsMatching, dollar_1)
var count int64
err := row.Scan(&count)
return count, err
}
const deleteAlbumIfEmpty = `-- name: DeleteAlbumIfEmpty :one
DELETE FROM albums a
WHERE a.id = $1
AND NOT EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
RETURNING a.id, a.artist_id
`
type DeleteAlbumIfEmptyRow struct {
ID pgtype.UUID
ArtistID pgtype.UUID
}
// M7 #372: deletes the album row only when it has no remaining tracks.
// Used after a track delete to tidy up orphaned albums. RETURNING gives
// us the artist_id so the service can chain into the artist-empty check.
// Returns no rows when the album still has tracks; the service treats
// pgx.ErrNoRows as "album not orphaned, skip cascade".
func (q *Queries) DeleteAlbumIfEmpty(ctx context.Context, id pgtype.UUID) (DeleteAlbumIfEmptyRow, error) {
row := q.db.QueryRow(ctx, deleteAlbumIfEmpty, id)
var i DeleteAlbumIfEmptyRow
err := row.Scan(&i.ID, &i.ArtistID)
return i, err
}
const getAlbumByArtistAndTitle = `-- name: GetAlbumByArtistAndTitle :one
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version, release_group_mbid FROM albums WHERE artist_id = $1 AND title = $2 LIMIT 1
`
type GetAlbumByArtistAndTitleParams struct {
ArtistID pgtype.UUID
Title string
}
// Scanner uses this for the no-mbid dedupe path: resolve-or-create.
func (q *Queries) GetAlbumByArtistAndTitle(ctx context.Context, arg GetAlbumByArtistAndTitleParams) (Album, error) {
row := q.db.QueryRow(ctx, getAlbumByArtistAndTitle, arg.ArtistID, arg.Title)
var i Album
err := row.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
)
return i, err
}
const getAlbumByID = `-- name: GetAlbumByID :one
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version, release_group_mbid FROM albums WHERE id = $1
`
func (q *Queries) GetAlbumByID(ctx context.Context, id pgtype.UUID) (Album, error) {
row := q.db.QueryRow(ctx, getAlbumByID, id)
var i Album
err := row.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
)
return i, err
}
const getAlbumCoverageRollup = `-- name: GetAlbumCoverageRollup :one
SELECT
COUNT(*) AS total,
COUNT(*) FILTER (WHERE cover_art_source IN
('sidecar','embedded','mbcaa','theaudiodb','deezer','lastfm')) AS with_art,
COUNT(*) FILTER (WHERE cover_art_source IS NULL) AS pending,
COUNT(*) FILTER (WHERE cover_art_source = 'none') AS settled,
COUNT(*) FILTER (WHERE cover_art_source IS NULL AND mbid IS NULL) AS pending_no_mbid
FROM albums
`
type GetAlbumCoverageRollupRow struct {
Total int64
WithArt int64
Pending int64
Settled int64
PendingNoMbid int64
}
// M7 coverage-gauge: library-wide cover-art coverage rollup for the
// admin gauge. Single sequential scan with FILTER aggregates —
// sub-millisecond on realistic libraries. pending_no_mbid is a SUBSET
// of pending; the UI surfaces it separately so operators can see how
// many "pending" albums are blocked on missing MBID and won't be
// helped by another scan.
//
// Invariant: with_art + pending + settled = total. (pending_no_mbid is
// not part of the sum — it's a subset of pending, surfaced separately.)
//
// The IN list below must stay in sync with the cover_art_source CHECK
// constraint — currently relaxed by migration 0020 to include 'deezer'
// and 'lastfm', and migration 0030 further relaxed it to any non-empty
// string. If a new source value is added without updating this query,
// with_art will silently undercount. Drift #556 caught the deezer +
// lastfm omission introduced by migration 0020.
func (q *Queries) GetAlbumCoverageRollup(ctx context.Context) (GetAlbumCoverageRollupRow, error) {
row := q.db.QueryRow(ctx, getAlbumCoverageRollup)
var i GetAlbumCoverageRollupRow
err := row.Scan(
&i.Total,
&i.WithArt,
&i.Pending,
&i.Settled,
&i.PendingNoMbid,
)
return i, err
}
const getAlbumWithArtist = `-- name: GetAlbumWithArtist :one
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, albums.release_group_mbid, artists.name AS artist_name
FROM albums
JOIN artists ON artists.id = albums.artist_id
WHERE albums.id = $1
`
type GetAlbumWithArtistRow struct {
Album Album
ArtistName string
}
// Combined fetch for /api/albums/{id}: returns the album row + the
// joined artist name in a single round trip. Replaces a sequential
// GetAlbumByID + GetArtistByID pair on the hot detail-page path.
func (q *Queries) GetAlbumWithArtist(ctx context.Context, id pgtype.UUID) (GetAlbumWithArtistRow, error) {
row := q.db.QueryRow(ctx, getAlbumWithArtist, id)
var i GetAlbumWithArtistRow
err := row.Scan(
&i.Album.ID,
&i.Album.Title,
&i.Album.SortTitle,
&i.Album.ArtistID,
&i.Album.ReleaseDate,
&i.Album.Mbid,
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.Album.CoverArtSourcesVersion,
&i.Album.ReleaseGroupMbid,
&i.ArtistName,
)
return i, err
}
const getAlbumsByIDs = `-- name: GetAlbumsByIDs :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version, release_group_mbid FROM albums WHERE id = ANY($1::uuid[])
`
// Batched lookup used by /api/library/sync to hydrate upsert payloads
// (#357). Mirror of GetArtistsByIDs.
func (q *Queries) GetAlbumsByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Album, error) {
rows, err := q.db.Query(ctx, getAlbumsByIDs, dollar_1)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Album
for rows.Next() {
var i Album
if err := rows.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getReleaseGroupForReleaseMbid = `-- name: GetReleaseGroupForReleaseMbid :one
SELECT release_group_mbid::text
FROM albums
WHERE mbid = $1 AND release_group_mbid IS NOT NULL
LIMIT 1
`
// #5241: the release group the library already knows for a release id, so a
// request carrying a release id can be repaired without asking MusicBrainz.
func (q *Queries) GetReleaseGroupForReleaseMbid(ctx context.Context, mbid *string) (string, error) {
row := q.db.QueryRow(ctx, getReleaseGroupForReleaseMbid, mbid)
var release_group_mbid string
err := row.Scan(&release_group_mbid)
return release_group_mbid, err
}
const listAlbumsAlphaByArtist = `-- name: ListAlbumsAlphaByArtist :many
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, albums.release_group_mbid, artists.sort_name AS artist_sort_name
FROM albums
JOIN artists ON artists.id = albums.artist_id
ORDER BY artists.sort_name, albums.sort_title
LIMIT $1 OFFSET $2
`
type ListAlbumsAlphaByArtistParams struct {
Limit int32
Offset int32
}
type ListAlbumsAlphaByArtistRow struct {
Album Album
ArtistSortName string
}
// Sorted by the owning artist's sort_name. Needed by Subsonic's
// alphabeticalByArtist album list type.
func (q *Queries) ListAlbumsAlphaByArtist(ctx context.Context, arg ListAlbumsAlphaByArtistParams) ([]ListAlbumsAlphaByArtistRow, error) {
rows, err := q.db.Query(ctx, listAlbumsAlphaByArtist, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAlbumsAlphaByArtistRow
for rows.Next() {
var i ListAlbumsAlphaByArtistRow
if err := rows.Scan(
&i.Album.ID,
&i.Album.Title,
&i.Album.SortTitle,
&i.Album.ArtistID,
&i.Album.ReleaseDate,
&i.Album.Mbid,
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.Album.CoverArtSourcesVersion,
&i.Album.ReleaseGroupMbid,
&i.ArtistSortName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsAlphaByName = `-- name: ListAlbumsAlphaByName :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version, release_group_mbid FROM albums ORDER BY sort_title LIMIT $1 OFFSET $2
`
type ListAlbumsAlphaByNameParams struct {
Limit int32
Offset int32
}
func (q *Queries) ListAlbumsAlphaByName(ctx context.Context, arg ListAlbumsAlphaByNameParams) ([]Album, error) {
rows, err := q.db.Query(ctx, listAlbumsAlphaByName, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Album
for rows.Next() {
var i Album
if err := rows.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsAlphaWithArtist = `-- name: ListAlbumsAlphaWithArtist :many
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, albums.release_group_mbid, artists.name AS artist_name
FROM albums
JOIN artists ON artists.id = albums.artist_id
ORDER BY albums.sort_title, albums.id
LIMIT $1 OFFSET $2
`
type ListAlbumsAlphaWithArtistParams struct {
Limit int32
Offset int32
}
type ListAlbumsAlphaWithArtistRow struct {
Album Album
ArtistName string
}
// M6a: alpha-sorted album list joined with artist_name. Used by
// /api/library/albums for the wrapping-grid page. Stable id-tiebreak.
func (q *Queries) ListAlbumsAlphaWithArtist(ctx context.Context, arg ListAlbumsAlphaWithArtistParams) ([]ListAlbumsAlphaWithArtistRow, error) {
rows, err := q.db.Query(ctx, listAlbumsAlphaWithArtist, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAlbumsAlphaWithArtistRow
for rows.Next() {
var i ListAlbumsAlphaWithArtistRow
if err := rows.Scan(
&i.Album.ID,
&i.Album.Title,
&i.Album.SortTitle,
&i.Album.ArtistID,
&i.Album.ReleaseDate,
&i.Album.Mbid,
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.Album.CoverArtSourcesVersion,
&i.Album.ReleaseGroupMbid,
&i.ArtistName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsByArtist = `-- name: ListAlbumsByArtist :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version, release_group_mbid FROM albums WHERE artist_id = $1 ORDER BY release_date NULLS LAST, sort_title
`
func (q *Queries) ListAlbumsByArtist(ctx context.Context, artistID pgtype.UUID) ([]Album, error) {
rows, err := q.db.Query(ctx, listAlbumsByArtist, artistID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Album
for rows.Next() {
var i Album
if err := rows.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsByArtistWithTrackCount = `-- name: ListAlbumsByArtistWithTrackCount :many
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, albums.release_group_mbid,
(SELECT count(*) FROM tracks t WHERE t.album_id = albums.id)::bigint
AS track_count
FROM albums
WHERE albums.artist_id = $1
ORDER BY release_date NULLS LAST, sort_title
`
type ListAlbumsByArtistWithTrackCountRow struct {
Album Album
TrackCount int64
}
// Replaces the N+1 pattern in handleGetArtist (1 + N CountTracksByAlbum
// per album). Returns each album joined with its track count via a
// correlated subquery — single round trip regardless of album count.
func (q *Queries) ListAlbumsByArtistWithTrackCount(ctx context.Context, artistID pgtype.UUID) ([]ListAlbumsByArtistWithTrackCountRow, error) {
rows, err := q.db.Query(ctx, listAlbumsByArtistWithTrackCount, artistID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAlbumsByArtistWithTrackCountRow
for rows.Next() {
var i ListAlbumsByArtistWithTrackCountRow
if err := rows.Scan(
&i.Album.ID,
&i.Album.Title,
&i.Album.SortTitle,
&i.Album.ArtistID,
&i.Album.ReleaseDate,
&i.Album.Mbid,
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.Album.CoverArtSourcesVersion,
&i.Album.ReleaseGroupMbid,
&i.TrackCount,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsByGenre = `-- name: ListAlbumsByGenre :many
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, albums.release_group_mbid
FROM albums
WHERE EXISTS (
SELECT 1
FROM tracks
JOIN LATERAL regexp_split_to_table(coalesce(tracks.genre, ''), '[;,]') AS g(genre) ON true
WHERE tracks.album_id = albums.id
AND trim(g.genre) = trim($1::text)
)
ORDER BY albums.sort_title, albums.id
LIMIT $3 OFFSET $2
`
type ListAlbumsByGenreParams struct {
Genre string
Off int32
Lim int32
}
// Album "belongs to" a genre if any of its tracks carry that genre.
// Serves Subsonic getAlbumList?type=byGenre.
//
// Splits tracks.genre on [;,] as of #367. It previously compared the whole
// column verbatim, so a track tagged "Rock;Pop" was unreachable from EITHER
// "Rock" or "Pop" — a Subsonic client asking for a genre silently missed
// every multi-genre track. This also aligns the endpoint with
// recommendation.sql / discover.sql, which have always split, and with the
// genre browse index that #367 adds.
//
// EXISTS rather than JOIN + DISTINCT ON: the lateral split emits one row per
// (track, genre-fragment), so a join would multiply rows per album and lean
// on DISTINCT to undo it. EXISTS asks the question directly.
func (q *Queries) ListAlbumsByGenre(ctx context.Context, arg ListAlbumsByGenreParams) ([]Album, error) {
rows, err := q.db.Query(ctx, listAlbumsByGenre, arg.Genre, arg.Off, arg.Lim)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Album
for rows.Next() {
var i Album
if err := rows.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsMissingMbidWithTrack = `-- name: ListAlbumsMissingMbidWithTrack :many
SELECT a.id AS album_id,
a.artist_id AS artist_id,
a.title AS title,
t.file_path AS track_file_path
FROM albums a
JOIN LATERAL (
SELECT file_path
FROM tracks
WHERE album_id = a.id
AND missing_since IS NULL
ORDER BY disc_number NULLS LAST, track_number NULLS LAST, id
LIMIT 1
) t ON true
WHERE a.mbid IS NULL
ORDER BY a.created_at ASC
LIMIT $1
`
type ListAlbumsMissingMbidWithTrackRow struct {
AlbumID pgtype.UUID
ArtistID pgtype.UUID
Title string
TrackFilePath string
}
// One-shot MBID backfill: returns each album where mbid IS NULL alongside
// one of its tracks' file_path so the worker can re-read tags. LIMIT
// supplied by caller for batching/progress purposes. The track read is
// one still on disk; an album with none left is skipped until a scan
// finds its files again (#5139).
func (q *Queries) ListAlbumsMissingMbidWithTrack(ctx context.Context, limit int32) ([]ListAlbumsMissingMbidWithTrackRow, error) {
rows, err := q.db.Query(ctx, listAlbumsMissingMbidWithTrack, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAlbumsMissingMbidWithTrackRow
for rows.Next() {
var i ListAlbumsMissingMbidWithTrackRow
if err := rows.Scan(
&i.AlbumID,
&i.ArtistID,
&i.Title,
&i.TrackFilePath,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsNewest = `-- name: ListAlbumsNewest :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version, release_group_mbid FROM albums ORDER BY created_at DESC LIMIT $1 OFFSET $2
`
type ListAlbumsNewestParams struct {
Limit int32
Offset int32
}
func (q *Queries) ListAlbumsNewest(ctx context.Context, arg ListAlbumsNewestParams) ([]Album, error) {
rows, err := q.db.Query(ctx, listAlbumsNewest, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Album
for rows.Next() {
var i Album
if err := rows.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsRandom = `-- name: ListAlbumsRandom :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version, release_group_mbid FROM albums ORDER BY random() LIMIT $1
`
func (q *Queries) ListAlbumsRandom(ctx context.Context, limit int32) ([]Album, error) {
rows, err := q.db.Query(ctx, listAlbumsRandom, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Album
for rows.Next() {
var i Album
if err := rows.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listRecentlyAddedAlbumsWithArtist = `-- name: ListRecentlyAddedAlbumsWithArtist :many
SELECT albums.id, albums.title, albums.sort_title, albums.artist_id, albums.release_date, albums.mbid, albums.cover_art_path, albums.created_at, albums.updated_at, albums.cover_art_source, albums.cover_art_sources_version, albums.release_group_mbid, artists.name AS artist_name
FROM albums
JOIN artists ON artists.id = albums.artist_id
ORDER BY albums.created_at DESC, albums.id
LIMIT $1
`
type ListRecentlyAddedAlbumsWithArtistRow struct {
Album Album
ArtistName string
}
// M6a: recently-added albums joined with artist_name + artist_id for the
// home-page section. created_at is the row-insert timestamp from the
// scanner — the closest proxy to "added to library". Stable secondary
// ordering by id keeps pagination tie-break deterministic.
func (q *Queries) ListRecentlyAddedAlbumsWithArtist(ctx context.Context, limit int32) ([]ListRecentlyAddedAlbumsWithArtistRow, error) {
rows, err := q.db.Query(ctx, listRecentlyAddedAlbumsWithArtist, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListRecentlyAddedAlbumsWithArtistRow
for rows.Next() {
var i ListRecentlyAddedAlbumsWithArtistRow
if err := rows.Scan(
&i.Album.ID,
&i.Album.Title,
&i.Album.SortTitle,
&i.Album.ArtistID,
&i.Album.ReleaseDate,
&i.Album.Mbid,
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.Album.CoverArtSourcesVersion,
&i.Album.ReleaseGroupMbid,
&i.ArtistName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const searchAlbums = `-- name: SearchAlbums :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version, release_group_mbid FROM albums
WHERE title ILIKE '%' || $1 || '%'
ORDER BY sort_title
LIMIT $2 OFFSET $3
`
type SearchAlbumsParams struct {
Column1 *string
Limit int32
Offset int32
}
func (q *Queries) SearchAlbums(ctx context.Context, arg SearchAlbumsParams) ([]Album, error) {
rows, err := q.db.Query(ctx, searchAlbums, arg.Column1, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Album
for rows.Next() {
var i Album
if err := rows.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const setAlbumCoverWithVersion = `-- name: SetAlbumCoverWithVersion :exec
UPDATE albums
SET cover_art_path = $2,
cover_art_source = $3,
cover_art_sources_version = $4,
updated_at = now()
WHERE id = $1
`
type SetAlbumCoverWithVersionParams struct {
ID pgtype.UUID
CoverArtPath *string
CoverArtSource *string
CoverArtSourcesVersion int32
}
// M7 cover-sources: records a successful or settled enrichment with the
// current sources_version stamp. cover_art_path is NULL when source is
// 'none' (no file written). Atomic; called per album per pass.
func (q *Queries) SetAlbumCoverWithVersion(ctx context.Context, arg SetAlbumCoverWithVersionParams) error {
_, err := q.db.Exec(ctx, setAlbumCoverWithVersion,
arg.ID,
arg.CoverArtPath,
arg.CoverArtSource,
arg.CoverArtSourcesVersion,
)
return err
}
const setAlbumMbidIfNull = `-- name: SetAlbumMbidIfNull :exec
UPDATE albums
SET mbid = $2,
updated_at = now()
WHERE id = $1 AND mbid IS NULL
`
type SetAlbumMbidIfNullParams struct {
ID pgtype.UUID
Mbid *string
}
// M7 #379: heal MBID on existing album rows during rescan. Idempotent —
// only updates when the existing mbid is NULL.
func (q *Queries) SetAlbumMbidIfNull(ctx context.Context, arg SetAlbumMbidIfNullParams) error {
_, err := q.db.Exec(ctx, setAlbumMbidIfNull, arg.ID, arg.Mbid)
return err
}
const setAlbumReleaseGroupMbidIfNull = `-- name: SetAlbumReleaseGroupMbidIfNull :exec
UPDATE albums
SET release_group_mbid = $2,
updated_at = now()
WHERE id = $1 AND release_group_mbid IS NULL
`
type SetAlbumReleaseGroupMbidIfNullParams struct {
ID pgtype.UUID
ReleaseGroupMbid *string
}
// #5241: heal the release-group id on an existing album row, from a rescan's
// tags or from the re-acquisition sweeper's MusicBrainz lookup. Only fills a
// NULL, so a tag never gets overwritten by a later guess or vice versa.
func (q *Queries) SetAlbumReleaseGroupMbidIfNull(ctx context.Context, arg SetAlbumReleaseGroupMbidIfNullParams) error {
_, err := q.db.Exec(ctx, setAlbumReleaseGroupMbidIfNull, arg.ID, arg.ReleaseGroupMbid)
return err
}
const setReleaseGroupForReleaseMbidIfNull = `-- name: SetReleaseGroupForReleaseMbidIfNull :exec
UPDATE albums
SET release_group_mbid = $1::text,
updated_at = now()
WHERE mbid = $2::text AND release_group_mbid IS NULL
`
type SetReleaseGroupForReleaseMbidIfNullParams struct {
ReleaseGroupMbid string
ReleaseMbid string
}
// #5241: cache a MusicBrainz-resolved release group onto the album row that
// holds the release, filling only a NULL.
func (q *Queries) SetReleaseGroupForReleaseMbidIfNull(ctx context.Context, arg SetReleaseGroupForReleaseMbidIfNullParams) error {
_, err := q.db.Exec(ctx, setReleaseGroupForReleaseMbidIfNull, arg.ReleaseGroupMbid, arg.ReleaseMbid)
return err
}
const upsertAlbum = `-- name: UpsertAlbum :one
INSERT INTO albums (title, sort_title, artist_id, release_date, mbid, cover_art_path, release_group_mbid)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (mbid) WHERE mbid IS NOT NULL
DO UPDATE SET
title = EXCLUDED.title,
sort_title = EXCLUDED.sort_title,
artist_id = EXCLUDED.artist_id,
release_date = EXCLUDED.release_date,
cover_art_path = EXCLUDED.cover_art_path,
release_group_mbid = COALESCE(EXCLUDED.release_group_mbid, albums.release_group_mbid),
updated_at = now()
RETURNING id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source, cover_art_sources_version, release_group_mbid
`
type UpsertAlbumParams struct {
Title string
SortTitle string
ArtistID pgtype.UUID
ReleaseDate pgtype.Date
Mbid *string
CoverArtPath *string
ReleaseGroupMbid *string
}
func (q *Queries) UpsertAlbum(ctx context.Context, arg UpsertAlbumParams) (Album, error) {
row := q.db.QueryRow(ctx, upsertAlbum,
arg.Title,
arg.SortTitle,
arg.ArtistID,
arg.ReleaseDate,
arg.Mbid,
arg.CoverArtPath,
arg.ReleaseGroupMbid,
)
var i Album
err := row.Scan(
&i.ID,
&i.Title,
&i.SortTitle,
&i.ArtistID,
&i.ReleaseDate,
&i.Mbid,
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
&i.CoverArtSourcesVersion,
&i.ReleaseGroupMbid,
)
return i, err
}