5c386dc73e
Three failures from the prior push: 1. internal/api/library.go and 3 other callers of q.GetArtistByID broke because T2 modified the query to use an explicit column list, making sqlc generate GetArtistByIDRow instead of returning dbq.Artist. dbq.Artist already has every column added by migration 0018 (artist_thumb_path / artist_fanart_path / artist_art_source / artist_art_sources_version), so the explicit column list was unnecessary work. Revert the SELECT to SELECT *, regenerate sqlc, GetArtistByIDRow disappears, all callers compile again. 2. internal/tracks/service_test.go missed the dataDir parameter added to NewService in T9. Add "" as the 4th arg to every test call site (tests don't exercise the artist-art cleanup path; the empty dataDir is fine). 3. integrations.test.ts:356's mock.calls.filter callback had a too-narrow type annotation that svelte-check rejected. Relax to any[] to match what mock.calls actually is.
507 lines
14 KiB
Go
507 lines
14 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: artists.sql
|
|
|
|
package dbq
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const clearArtistArtNone = `-- name: ClearArtistArtNone :exec
|
|
UPDATE artists
|
|
SET artist_art_source = NULL,
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
AND artist_art_source = 'none'
|
|
`
|
|
|
|
// M7 cover-sources: parallel of ClearAlbumCoverNone. Used by the
|
|
// eligibility loop to flip stale 'none' rows back to NULL.
|
|
func (q *Queries) ClearArtistArtNone(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, clearArtistArtNone, id)
|
|
return err
|
|
}
|
|
|
|
const countArtists = `-- name: CountArtists :one
|
|
SELECT COUNT(*) FROM artists
|
|
`
|
|
|
|
func (q *Queries) CountArtists(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countArtists)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const countArtistsMatching = `-- name: CountArtistsMatching :one
|
|
SELECT COUNT(*) FROM artists WHERE name ILIKE '%' || $1::text || '%'
|
|
`
|
|
|
|
func (q *Queries) CountArtistsMatching(ctx context.Context, dollar_1 string) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countArtistsMatching, dollar_1)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const deleteArtistIfEmpty = `-- name: DeleteArtistIfEmpty :one
|
|
DELETE FROM artists a
|
|
WHERE a.id = $1
|
|
AND NOT EXISTS (SELECT 1 FROM albums al WHERE al.artist_id = a.id)
|
|
AND NOT EXISTS (SELECT 1 FROM tracks t WHERE t.artist_id = a.id)
|
|
RETURNING a.id
|
|
`
|
|
|
|
// M7 #372: deletes the artist row only when it has no remaining albums
|
|
// AND no remaining tracks (defensive — tracks can in principle exist
|
|
// without an album, though the scanner doesn't write that shape today).
|
|
func (q *Queries) DeleteArtistIfEmpty(ctx context.Context, id pgtype.UUID) (pgtype.UUID, error) {
|
|
row := q.db.QueryRow(ctx, deleteArtistIfEmpty, id)
|
|
var id_2 pgtype.UUID
|
|
err := row.Scan(&id_2)
|
|
return id_2, err
|
|
}
|
|
|
|
const getArtistByID = `-- name: GetArtistByID :one
|
|
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists WHERE id = $1
|
|
`
|
|
|
|
// Lookup an artist row by its UUID. Returns the full row; consumers
|
|
// pick the columns they need.
|
|
func (q *Queries) GetArtistByID(ctx context.Context, id pgtype.UUID) (Artist, error) {
|
|
row := q.db.QueryRow(ctx, getArtistByID, id)
|
|
var i Artist
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.SortName,
|
|
&i.Mbid,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ArtistThumbPath,
|
|
&i.ArtistFanartPath,
|
|
&i.ArtistArtSource,
|
|
&i.ArtistArtSourcesVersion,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getArtistByName = `-- name: GetArtistByName :one
|
|
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists WHERE name = $1 LIMIT 1
|
|
`
|
|
|
|
func (q *Queries) GetArtistByName(ctx context.Context, name string) (Artist, error) {
|
|
row := q.db.QueryRow(ctx, getArtistByName, name)
|
|
var i Artist
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.SortName,
|
|
&i.Mbid,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ArtistThumbPath,
|
|
&i.ArtistFanartPath,
|
|
&i.ArtistArtSource,
|
|
&i.ArtistArtSourcesVersion,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getArtistsByIDs = `-- name: GetArtistsByIDs :many
|
|
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists WHERE id = ANY($1::uuid[])
|
|
`
|
|
|
|
// Batched lookup used by M5c suggestion attribution to resolve top-3
|
|
// contributing seed UUIDs back to artist names in one round-trip.
|
|
func (q *Queries) GetArtistsByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Artist, error) {
|
|
rows, err := q.db.Query(ctx, getArtistsByIDs, dollar_1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Artist
|
|
for rows.Next() {
|
|
var i Artist
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.SortName,
|
|
&i.Mbid,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ArtistThumbPath,
|
|
&i.ArtistFanartPath,
|
|
&i.ArtistArtSource,
|
|
&i.ArtistArtSourcesVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listArtists = `-- name: ListArtists :many
|
|
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists ORDER BY sort_name
|
|
`
|
|
|
|
func (q *Queries) ListArtists(ctx context.Context) ([]Artist, error) {
|
|
rows, err := q.db.Query(ctx, listArtists)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Artist
|
|
for rows.Next() {
|
|
var i Artist
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.SortName,
|
|
&i.Mbid,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ArtistThumbPath,
|
|
&i.ArtistFanartPath,
|
|
&i.ArtistArtSource,
|
|
&i.ArtistArtSourcesVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listArtistsAlpha = `-- name: ListArtistsAlpha :many
|
|
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists ORDER BY sort_name, name, id LIMIT $1 OFFSET $2
|
|
`
|
|
|
|
type ListArtistsAlphaParams struct {
|
|
Limit int32
|
|
Offset int32
|
|
}
|
|
|
|
func (q *Queries) ListArtistsAlpha(ctx context.Context, arg ListArtistsAlphaParams) ([]Artist, error) {
|
|
rows, err := q.db.Query(ctx, listArtistsAlpha, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Artist
|
|
for rows.Next() {
|
|
var i Artist
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.SortName,
|
|
&i.Mbid,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ArtistThumbPath,
|
|
&i.ArtistFanartPath,
|
|
&i.ArtistArtSource,
|
|
&i.ArtistArtSourcesVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listArtistsAlphaWithCovers = `-- name: ListArtistsAlphaWithCovers :many
|
|
SELECT artists.id, artists.name, artists.sort_name, artists.mbid, artists.created_at, artists.updated_at, artists.artist_thumb_path, artists.artist_fanart_path, artists.artist_art_source, artists.artist_art_sources_version,
|
|
cov.id AS cover_album_id,
|
|
cnt.album_count::bigint AS album_count
|
|
FROM artists
|
|
LEFT JOIN LATERAL (
|
|
SELECT id FROM albums
|
|
WHERE artist_id = artists.id AND cover_art_path IS NOT NULL
|
|
ORDER BY created_at DESC LIMIT 1
|
|
) cov ON true
|
|
LEFT JOIN LATERAL (
|
|
SELECT count(*) AS album_count
|
|
FROM albums WHERE artist_id = artists.id
|
|
) cnt ON true
|
|
ORDER BY artists.sort_name, artists.name, artists.id
|
|
LIMIT $1 OFFSET $2
|
|
`
|
|
|
|
type ListArtistsAlphaWithCoversParams struct {
|
|
Limit int32
|
|
Offset int32
|
|
}
|
|
|
|
type ListArtistsAlphaWithCoversRow struct {
|
|
Artist Artist
|
|
CoverAlbumID pgtype.UUID
|
|
AlbumCount int64
|
|
}
|
|
|
|
// M6a: alpha-sorted artist list with derived cover_album_id (most-recent
|
|
// album whose cover_art_path is set). Replaces ListArtistsAlpha for the
|
|
// /api/artists?sort=alpha branch — the new column is appended, so the
|
|
// alpha branch is purely additive on the wire. album_count is computed
|
|
// in the same query to drop the old N+1 in handleListArtists.
|
|
func (q *Queries) ListArtistsAlphaWithCovers(ctx context.Context, arg ListArtistsAlphaWithCoversParams) ([]ListArtistsAlphaWithCoversRow, error) {
|
|
rows, err := q.db.Query(ctx, listArtistsAlphaWithCovers, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListArtistsAlphaWithCoversRow
|
|
for rows.Next() {
|
|
var i ListArtistsAlphaWithCoversRow
|
|
if err := rows.Scan(
|
|
&i.Artist.ID,
|
|
&i.Artist.Name,
|
|
&i.Artist.SortName,
|
|
&i.Artist.Mbid,
|
|
&i.Artist.CreatedAt,
|
|
&i.Artist.UpdatedAt,
|
|
&i.Artist.ArtistThumbPath,
|
|
&i.Artist.ArtistFanartPath,
|
|
&i.Artist.ArtistArtSource,
|
|
&i.Artist.ArtistArtSourcesVersion,
|
|
&i.CoverAlbumID,
|
|
&i.AlbumCount,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listArtistsMissingArt = `-- name: ListArtistsMissingArt :many
|
|
SELECT a.id
|
|
FROM artists a
|
|
WHERE a.artist_art_source IS NULL
|
|
OR (a.artist_art_source = 'none' AND a.artist_art_sources_version != $1)
|
|
ORDER BY a.created_at
|
|
LIMIT $2
|
|
`
|
|
|
|
type ListArtistsMissingArtParams struct {
|
|
ArtistArtSourcesVersion int32
|
|
Limit int32
|
|
}
|
|
|
|
// M7 cover-sources: returns artist rows the artist-art enricher should
|
|
// attempt. Eligible: artist_art_source IS NULL OR (= 'none' AND stale
|
|
// artist_art_sources_version). MBID-less artists are returned but the
|
|
// enricher skips them at the row.mbid == nil guard inside the chain.
|
|
func (q *Queries) ListArtistsMissingArt(ctx context.Context, arg ListArtistsMissingArtParams) ([]pgtype.UUID, error) {
|
|
rows, err := q.db.Query(ctx, listArtistsMissingArt, arg.ArtistArtSourcesVersion, arg.Limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []pgtype.UUID
|
|
for rows.Next() {
|
|
var id pgtype.UUID
|
|
if err := rows.Scan(&id); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, id)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listArtistsNewest = `-- name: ListArtistsNewest :many
|
|
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists ORDER BY created_at DESC, id DESC LIMIT $1 OFFSET $2
|
|
`
|
|
|
|
type ListArtistsNewestParams struct {
|
|
Limit int32
|
|
Offset int32
|
|
}
|
|
|
|
// Secondary id tiebreaker keeps pagination stable when created_at ties.
|
|
func (q *Queries) ListArtistsNewest(ctx context.Context, arg ListArtistsNewestParams) ([]Artist, error) {
|
|
rows, err := q.db.Query(ctx, listArtistsNewest, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Artist
|
|
for rows.Next() {
|
|
var i Artist
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.SortName,
|
|
&i.Mbid,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ArtistThumbPath,
|
|
&i.ArtistFanartPath,
|
|
&i.ArtistArtSource,
|
|
&i.ArtistArtSourcesVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const searchArtists = `-- name: SearchArtists :many
|
|
SELECT id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version FROM artists
|
|
WHERE name ILIKE '%' || $1 || '%'
|
|
ORDER BY sort_name
|
|
LIMIT $2 OFFSET $3
|
|
`
|
|
|
|
type SearchArtistsParams struct {
|
|
Column1 *string
|
|
Limit int32
|
|
Offset int32
|
|
}
|
|
|
|
func (q *Queries) SearchArtists(ctx context.Context, arg SearchArtistsParams) ([]Artist, error) {
|
|
rows, err := q.db.Query(ctx, searchArtists, arg.Column1, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Artist
|
|
for rows.Next() {
|
|
var i Artist
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.SortName,
|
|
&i.Mbid,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ArtistThumbPath,
|
|
&i.ArtistFanartPath,
|
|
&i.ArtistArtSource,
|
|
&i.ArtistArtSourcesVersion,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const setArtistArtWithVersion = `-- name: SetArtistArtWithVersion :exec
|
|
UPDATE artists
|
|
SET artist_thumb_path = $2,
|
|
artist_fanart_path = $3,
|
|
artist_art_source = $4,
|
|
artist_art_sources_version = $5,
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
`
|
|
|
|
type SetArtistArtWithVersionParams struct {
|
|
ID pgtype.UUID
|
|
ArtistThumbPath *string
|
|
ArtistFanartPath *string
|
|
ArtistArtSource *string
|
|
ArtistArtSourcesVersion int32
|
|
}
|
|
|
|
// M7 cover-sources: records a successful or settled artist-art
|
|
// enrichment with the current sources_version stamp. thumb_path /
|
|
// fanart_path are NULL on a 'none' result, or independently NULL if
|
|
// the provider returned only one of the two image types (e.g.
|
|
// TheAudioDB had a thumb but no fanart for this artist).
|
|
func (q *Queries) SetArtistArtWithVersion(ctx context.Context, arg SetArtistArtWithVersionParams) error {
|
|
_, err := q.db.Exec(ctx, setArtistArtWithVersion,
|
|
arg.ID,
|
|
arg.ArtistThumbPath,
|
|
arg.ArtistFanartPath,
|
|
arg.ArtistArtSource,
|
|
arg.ArtistArtSourcesVersion,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const setArtistMbidIfNull = `-- name: SetArtistMbidIfNull :exec
|
|
UPDATE artists
|
|
SET mbid = $2,
|
|
updated_at = now()
|
|
WHERE id = $1 AND mbid IS NULL
|
|
`
|
|
|
|
type SetArtistMbidIfNullParams struct {
|
|
ID pgtype.UUID
|
|
Mbid *string
|
|
}
|
|
|
|
// M7 #379: heal MBID on existing artist rows during rescan. Idempotent —
|
|
// only updates when the existing mbid is NULL, so concurrent scans don't
|
|
// overwrite an existing value with a stale one.
|
|
func (q *Queries) SetArtistMbidIfNull(ctx context.Context, arg SetArtistMbidIfNullParams) error {
|
|
_, err := q.db.Exec(ctx, setArtistMbidIfNull, arg.ID, arg.Mbid)
|
|
return err
|
|
}
|
|
|
|
const upsertArtist = `-- name: UpsertArtist :one
|
|
INSERT INTO artists (name, sort_name, mbid)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (mbid) WHERE mbid IS NOT NULL
|
|
DO UPDATE SET
|
|
name = EXCLUDED.name,
|
|
sort_name = EXCLUDED.sort_name,
|
|
updated_at = now()
|
|
RETURNING id, name, sort_name, mbid, created_at, updated_at, artist_thumb_path, artist_fanart_path, artist_art_source, artist_art_sources_version
|
|
`
|
|
|
|
type UpsertArtistParams struct {
|
|
Name string
|
|
SortName string
|
|
Mbid *string
|
|
}
|
|
|
|
// Insert or update by mbid when present; otherwise by (name, sort_name).
|
|
// Callers pass the canonical sort_name; scanner is responsible for derivation.
|
|
func (q *Queries) UpsertArtist(ctx context.Context, arg UpsertArtistParams) (Artist, error) {
|
|
row := q.db.QueryRow(ctx, upsertArtist, arg.Name, arg.SortName, arg.Mbid)
|
|
var i Artist
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.SortName,
|
|
&i.Mbid,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ArtistThumbPath,
|
|
&i.ArtistFanartPath,
|
|
&i.ArtistArtSource,
|
|
&i.ArtistArtSourcesVersion,
|
|
)
|
|
return i, err
|
|
}
|