Files
minstrel/internal/db/dbq/albums.sql.go
T
bvandeusen 98ec64c658 feat(db/m7-coverage-gauge): GetAlbumCoverageRollup query
FILTER-aggregate count over albums.cover_art_source and albums.mbid
returning total, with_art, pending, settled, pending_no_mbid in a
single round-trip. Sub-millisecond on realistic libraries; no index
needed for v1. pending_no_mbid is a subset of pending — the UI
tooltip uses it to show how many "pending" rows are blocked on
missing MBID and won't be moved by another scan.

Also bumps the Makefile sqlc pin from 1.27.0 to 1.31.1 to match the
version that produced all existing committed dbq/*.go files. The
prior pin was stale; running make generate against 1.27.0 silently
rolled the codegen back (cosmetic version headers on most files
plus a real codegen shape change in DeleteArtistIfEmpty).
2026-05-06 09:40:26 -04:00

674 lines
18 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 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,
)
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 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,
)
return i, err
}
const getAlbumCoverageRollup = `-- name: GetAlbumCoverageRollup :one
SELECT
COUNT(*) AS total,
COUNT(*) FILTER (WHERE cover_art_source IN ('sidecar','embedded','mbcaa')) 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.
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 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, 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.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 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,
); 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, 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.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 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,
); 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 DISTINCT ON (albums.id) 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
FROM albums
JOIN tracks ON tracks.album_id = albums.id
WHERE tracks.genre = $1
ORDER BY albums.id, albums.sort_title
LIMIT $2 OFFSET $3
`
type ListAlbumsByGenreParams struct {
Genre *string
Limit int32
Offset int32
}
// Album "belongs to" a genre if any of its tracks carry that genre.
func (q *Queries) ListAlbumsByGenre(ctx context.Context, arg ListAlbumsByGenreParams) ([]Album, error) {
rows, err := q.db.Query(ctx, listAlbumsByGenre, arg.Genre, 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,
); 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
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.
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 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,
); 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 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,
); 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, 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.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 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,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
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 upsertAlbum = `-- name: UpsertAlbum :one
INSERT INTO albums (title, sort_title, artist_id, release_date, mbid, cover_art_path)
VALUES ($1, $2, $3, $4, $5, $6)
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,
updated_at = now()
RETURNING id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source
`
type UpsertAlbumParams struct {
Title string
SortTitle string
ArtistID pgtype.UUID
ReleaseDate pgtype.Date
Mbid *string
CoverArtPath *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,
)
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,
)
return i, err
}