Files
yellowjacket/backend/database/sql/sqlcgen/albums.sql.go
T
logan 4bf59b45b7 feat(library): answer album completeness for a screenful in one query
A card grid has to know how much of an album is here — an album held 2
tracks of 10 wearing the same green tick as one held whole is the
complaint the badge-accuracy work was filed about — and
`GetAlbumCompleteness` is one query per album, which is fifty round
trips for a grid of fifty.

`GetAlbumsCompleteness` is the same question over a slice. It is two
grouping levels rather than the single-album form's correlated
subqueries, because a correlated subquery in the FROM clause is not
something SQLite will reliably do, and because the slice may only be
spelled once or sqlc expands it twice with independently numbered
placeholders.

An album with no files is absent from the result rather than zeroed:
"I have none of this" and "I have no idea" are the third state `Known`
exists to keep apart.

The test that matters is that the two spellings never disagree — they
are genuinely different SQL, so the risk is a drift in meaning (a
disc's total counted once per file, a duplicate counted twice) rather
than a typo.
2026-08-19 00:37:46 -04:00

520 lines
15 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: albums.sql
package sqlcgen
import (
"context"
"database/sql"
"strings"
)
const deleteAlbum = `-- name: DeleteAlbum :exec
DELETE FROM albums WHERE id = ?
`
func (q *Queries) DeleteAlbum(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteAlbum, id)
return err
}
const deleteAllAlbums = `-- name: DeleteAllAlbums :exec
DELETE FROM albums
`
func (q *Queries) DeleteAllAlbums(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteAllAlbums)
return err
}
const getAlbum = `-- name: GetAlbum :one
SELECT id, name, artist_credit, artist_id, mbid, year, original_year, cover_art_id, pending_release_mbid FROM albums WHERE id = ? LIMIT 1
`
func (q *Queries) GetAlbum(ctx context.Context, id int64) (Album, error) {
row := q.db.QueryRowContext(ctx, getAlbum, id)
var i Album
err := row.Scan(
&i.ID,
&i.Name,
&i.ArtistCredit,
&i.ArtistID,
&i.Mbid,
&i.Year,
&i.OriginalYear,
&i.CoverArtID,
&i.PendingReleaseMbid,
)
return i, err
}
const getAlbumCompleteness = `-- name: GetAlbumCompleteness :one
SELECT
-- Distinct (disc, track) pairs: this app detects duplicates, and
-- counting two files of track 3 twice would report a short album as
-- complete. A file with no track number falls back to its own id,
-- because three untagged files are three tracks, not one.
CAST(COUNT(DISTINCT CAST(COALESCE(a.disc_number, 1) AS TEXT) || ':' ||
COALESCE(CAST(a.track_number AS TEXT), 'f' || a.id)
) AS INTEGER) AS owned,
CAST(COALESCE((
SELECT SUM(per_disc.total)
FROM (
SELECT MAX(b.total_tracks) AS total
FROM audio_files b
WHERE b.album_id = ?1 AND b.total_tracks IS NOT NULL
GROUP BY COALESCE(b.disc_number, 1)
) per_disc
), 0) AS INTEGER) AS expected,
CAST((
SELECT COUNT(*) = 0 FROM audio_files c
WHERE c.album_id = ?1 AND c.total_tracks IS NULL
) AS INTEGER) AS known
FROM audio_files a
WHERE a.album_id = ?1
`
type GetAlbumCompletenessRow struct {
Owned int64
Expected int64
Known int64
}
// "Do I have all of this album", answered from the tags on disk.
//
// The expectation is a **sum over discs**, not one number: totals are
// declared per disc ("5/12" on disc 2 means 12 tracks on disc 2), so a
// multi-disc album's expectation is the sum of each disc's declared
// total. A disc whose files declared nothing leaves the whole album
// unknowable rather than being covered by the discs that did -- which is
// what `known` reports.
//
// Owned counts DISTINCT track numbers: this app detects duplicates, and
// counting two files of track 3 twice would report a short album as
// complete.
func (q *Queries) GetAlbumCompleteness(ctx context.Context, albumID sql.NullInt64) (GetAlbumCompletenessRow, error) {
row := q.db.QueryRowContext(ctx, getAlbumCompleteness, albumID)
var i GetAlbumCompletenessRow
err := row.Scan(&i.Owned, &i.Expected, &i.Known)
return i, err
}
const getAlbums = `-- name: GetAlbums :many
SELECT
al.id,
al.name,
COALESCE(al.original_year, al.year) AS year,
COALESCE(al.year, 0) AS release_year,
al.mbid,
al.artist_credit AS artist_name,
CAST(COALESCE(ar.mbid, '') AS TEXT) AS artist_mbid,
COALESCE(ca.file_path, '') AS cover_art_path
FROM albums al
LEFT JOIN artists ar ON ar.id = al.artist_id
LEFT JOIN cover_art ca ON ca.id = al.cover_art_id
WHERE EXISTS (
SELECT 1 FROM audio_files af
WHERE af.album_id = al.id
AND af.library_id = COALESCE(NULLIF(CAST(?1 AS INTEGER), 0), af.library_id)
)
ORDER BY al.name
`
type GetAlbumsRow struct {
ID int64
Name string
Year sql.NullInt64
ReleaseYear int64
Mbid sql.NullString
ArtistName string
ArtistMbid string
CoverArtPath string
}
func (q *Queries) GetAlbums(ctx context.Context, libraryID int64) ([]GetAlbumsRow, error) {
rows, err := q.db.QueryContext(ctx, getAlbums, libraryID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAlbumsRow
for rows.Next() {
var i GetAlbumsRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Year,
&i.ReleaseYear,
&i.Mbid,
&i.ArtistName,
&i.ArtistMbid,
&i.CoverArtPath,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getAlbumsByArtistName = `-- name: GetAlbumsByArtistName :many
SELECT
al.id,
al.name,
COALESCE(al.original_year, al.year) AS year,
COALESCE(al.year, 0) AS release_year,
al.mbid,
al.artist_credit AS artist_name,
CAST(COALESCE(ar.mbid, '') AS TEXT) AS artist_mbid,
COALESCE(ca.file_path, '') AS cover_art_path
FROM albums al
LEFT JOIN artists ar ON ar.id = al.artist_id
LEFT JOIN cover_art ca ON ca.id = al.cover_art_id
WHERE (al.artist_credit = ?1 OR ar.name = ?1)
AND EXISTS (
SELECT 1 FROM audio_files af
WHERE af.album_id = al.id
AND af.library_id = COALESCE(NULLIF(CAST(?2 AS INTEGER), 0), af.library_id)
)
ORDER BY year, al.name
`
type GetAlbumsByArtistNameParams struct {
Artist string
LibraryID int64
}
type GetAlbumsByArtistNameRow struct {
ID int64
Name string
Year sql.NullInt64
ReleaseYear int64
Mbid sql.NullString
ArtistName string
ArtistMbid string
CoverArtPath string
}
func (q *Queries) GetAlbumsByArtistName(ctx context.Context, arg GetAlbumsByArtistNameParams) ([]GetAlbumsByArtistNameRow, error) {
rows, err := q.db.QueryContext(ctx, getAlbumsByArtistName, arg.Artist, arg.LibraryID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAlbumsByArtistNameRow
for rows.Next() {
var i GetAlbumsByArtistNameRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Year,
&i.ReleaseYear,
&i.Mbid,
&i.ArtistName,
&i.ArtistMbid,
&i.CoverArtPath,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getAlbumsCompleteness = `-- name: GetAlbumsCompleteness :many
WITH per_disc AS (
SELECT
album_id AS album_id,
COUNT(DISTINCT COALESCE(CAST(track_number AS TEXT), 'f' || id))
AS owned_on_disc,
MAX(total_tracks) AS disc_total,
SUM(CASE WHEN total_tracks IS NULL THEN 1 ELSE 0 END)
AS discs_without_a_total
FROM audio_files
WHERE album_id IN (/*SLICE:album_ids*/?)
GROUP BY album_id, COALESCE(disc_number, 1)
)
SELECT
CAST(album_id AS INTEGER) AS album_id,
CAST(SUM(owned_on_disc) AS INTEGER) AS owned,
CAST(COALESCE(SUM(disc_total), 0) AS INTEGER) AS expected,
CAST(SUM(discs_without_a_total) = 0 AS INTEGER) AS known
FROM per_disc
GROUP BY album_id
`
type GetAlbumsCompletenessRow struct {
AlbumID int64
Owned int64
Expected int64
Known int64
}
// The same question as GetAlbumCompleteness, asked of a screenful of
// albums at once.
//
// A card grid cannot afford one query per card, and the answer it wants
// is the one thing a badge cannot guess: an album held 9 tracks of 12
// must show the count, never a bare tick. So this is one query for the
// whole grid, asked only of the cards that have a local album id.
//
// It is two grouping levels rather than the single-album form's
// correlated subqueries, because a correlated subquery in the FROM
// clause is not something SQLite will reliably do -- and because the
// slice may only be spelled once, or sqlc expands it twice with
// independently numbered placeholders.
//
// The per-disc level is where the meaning is, and it is the same
// meaning as the single-album query. `owned` counts DISTINCT track
// numbers within a disc (this app detects duplicates, and counting two
// files of track 3 twice would report a short album as complete), with
// a file that declares no track number falling back to its own id
// because three untagged files are three tracks and not one.
// `expected` takes each disc's declared total and sums over discs,
// since a total is declared per disc and a release total written on
// every file of a two-disc album would double its expectation. A disc
// whose files declared nothing contributes a NULL that SUM ignores,
// and `known` is what says the album is therefore unanswerable.
func (q *Queries) GetAlbumsCompleteness(ctx context.Context, albumIds []sql.NullInt64) ([]GetAlbumsCompletenessRow, error) {
query := getAlbumsCompleteness
var queryParams []interface{}
if len(albumIds) > 0 {
for _, v := range albumIds {
queryParams = append(queryParams, v)
}
query = strings.Replace(query, "/*SLICE:album_ids*/?", strings.Repeat(",?", len(albumIds))[1:], 1)
} else {
query = strings.Replace(query, "/*SLICE:album_ids*/?", "NULL", 1)
}
rows, err := q.db.QueryContext(ctx, query, queryParams...)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAlbumsCompletenessRow
for rows.Next() {
var i GetAlbumsCompletenessRow
if err := rows.Scan(
&i.AlbumID,
&i.Owned,
&i.Expected,
&i.Known,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getAlbumsWithPendingReleaseMBID = `-- name: GetAlbumsWithPendingReleaseMBID :many
SELECT id, pending_release_mbid FROM albums
WHERE pending_release_mbid IS NOT NULL AND pending_release_mbid != ''
AND (mbid IS NULL OR mbid = '')
`
type GetAlbumsWithPendingReleaseMBIDRow struct {
ID int64
PendingReleaseMbid sql.NullString
}
func (q *Queries) GetAlbumsWithPendingReleaseMBID(ctx context.Context) ([]GetAlbumsWithPendingReleaseMBIDRow, error) {
rows, err := q.db.QueryContext(ctx, getAlbumsWithPendingReleaseMBID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAlbumsWithPendingReleaseMBIDRow
for rows.Next() {
var i GetAlbumsWithPendingReleaseMBIDRow
if err := rows.Scan(&i.ID, &i.PendingReleaseMbid); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getEmptyAlbumIDs = `-- name: GetEmptyAlbumIDs :many
SELECT id FROM albums al
WHERE NOT EXISTS (
SELECT 1 FROM audio_files af WHERE af.album_id = al.id
)
`
// Albums with no file left behind them. Under the old schema this was
// one of three orphan sweeps that had to run by hand and did not;
// audio_files is the only thing that can leave an album empty now, so
// this is the whole of it.
func (q *Queries) GetEmptyAlbumIDs(ctx context.Context) ([]int64, error) {
rows, err := q.db.QueryContext(ctx, getEmptyAlbumIDs)
if err != nil {
return nil, err
}
defer rows.Close()
var items []int64
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
return nil, err
}
items = append(items, id)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const resolveAlbumPendingReleaseMBID = `-- name: ResolveAlbumPendingReleaseMBID :exec
UPDATE albums
SET mbid = ?, pending_release_mbid = NULL
WHERE id = ? AND (mbid IS NULL OR mbid = '')
`
type ResolveAlbumPendingReleaseMBIDParams struct {
Mbid sql.NullString
ID int64
}
// Clears the pending marker once the release-group MBID it stood in for
// has been resolved. Guarded so a real MBID is never overwritten.
func (q *Queries) ResolveAlbumPendingReleaseMBID(ctx context.Context, arg ResolveAlbumPendingReleaseMBIDParams) error {
_, err := q.db.ExecContext(ctx, resolveAlbumPendingReleaseMBID, arg.Mbid, arg.ID)
return err
}
const setAlbumCoverArt = `-- name: SetAlbumCoverArt :exec
UPDATE albums SET cover_art_id = ? WHERE id = ?
`
type SetAlbumCoverArtParams struct {
CoverArtID sql.NullInt64
ID int64
}
func (q *Queries) SetAlbumCoverArt(ctx context.Context, arg SetAlbumCoverArtParams) error {
_, err := q.db.ExecContext(ctx, setAlbumCoverArt, arg.CoverArtID, arg.ID)
return err
}
const setAlbumMBID = `-- name: SetAlbumMBID :exec
UPDATE albums SET mbid = ? WHERE id = ?
`
type SetAlbumMBIDParams struct {
Mbid sql.NullString
ID int64
}
func (q *Queries) SetAlbumMBID(ctx context.Context, arg SetAlbumMBIDParams) error {
_, err := q.db.ExecContext(ctx, setAlbumMBID, arg.Mbid, arg.ID)
return err
}
const setAlbumOriginalYear = `-- name: SetAlbumOriginalYear :exec
UPDATE albums SET original_year = ? WHERE id = ?
`
type SetAlbumOriginalYearParams struct {
OriginalYear sql.NullInt64
ID int64
}
func (q *Queries) SetAlbumOriginalYear(ctx context.Context, arg SetAlbumOriginalYearParams) error {
_, err := q.db.ExecContext(ctx, setAlbumOriginalYear, arg.OriginalYear, arg.ID)
return err
}
const setAlbumPendingReleaseMBID = `-- name: SetAlbumPendingReleaseMBID :exec
UPDATE albums SET pending_release_mbid = ? WHERE id = ?
`
type SetAlbumPendingReleaseMBIDParams struct {
PendingReleaseMbid sql.NullString
ID int64
}
func (q *Queries) SetAlbumPendingReleaseMBID(ctx context.Context, arg SetAlbumPendingReleaseMBIDParams) error {
_, err := q.db.ExecContext(ctx, setAlbumPendingReleaseMBID, arg.PendingReleaseMbid, arg.ID)
return err
}
const upsertAlbum = `-- name: UpsertAlbum :one
INSERT INTO albums (name, artist_credit, artist_id, year, cover_art_id)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(name, artist_credit) DO UPDATE SET
artist_id = COALESCE(excluded.artist_id, albums.artist_id),
year = COALESCE(excluded.year, albums.year),
cover_art_id = COALESCE(excluded.cover_art_id, albums.cover_art_id)
RETURNING id, name, artist_credit, artist_id, mbid, year, original_year, cover_art_id, pending_release_mbid
`
type UpsertAlbumParams struct {
Name string
ArtistCredit string
ArtistID sql.NullInt64
Year sql.NullInt64
CoverArtID sql.NullInt64
}
// Queries over albums (formerly release_groups).
//
// The two-copy pattern is gone here too: one query answers both the
// whole-library and the single-library case. The `fallback_ac`
// subquery every album read used to carry -- "if the album has no album
// artist credit, borrow one from any of its recordings" -- is gone with
// it, because the album carries its own credit text now.
func (q *Queries) UpsertAlbum(ctx context.Context, arg UpsertAlbumParams) (Album, error) {
row := q.db.QueryRowContext(ctx, upsertAlbum,
arg.Name,
arg.ArtistCredit,
arg.ArtistID,
arg.Year,
arg.CoverArtID,
)
var i Album
err := row.Scan(
&i.ID,
&i.Name,
&i.ArtistCredit,
&i.ArtistID,
&i.Mbid,
&i.Year,
&i.OriginalYear,
&i.CoverArtID,
&i.PendingReleaseMbid,
)
return i, err
}