feat(db/m7-353): sqlc queries for album cover enrichment

This commit is contained in:
2026-05-04 14:30:55 -04:00
parent 458a183099
commit 15170ed7c6
7 changed files with 300 additions and 25 deletions
+24 -12
View File
@@ -72,7 +72,7 @@ func (q *Queries) DeleteAlbumIfEmpty(ctx context.Context, id pgtype.UUID) (Delet
}
const getAlbumByArtistAndTitle = `-- name: GetAlbumByArtistAndTitle :one
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at FROM albums WHERE artist_id = $1 AND title = $2 LIMIT 1
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 {
@@ -94,12 +94,13 @@ func (q *Queries) GetAlbumByArtistAndTitle(ctx context.Context, arg GetAlbumByAr
&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 FROM albums WHERE id = $1
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) {
@@ -115,12 +116,13 @@ func (q *Queries) GetAlbumByID(ctx context.Context, id pgtype.UUID) (Album, erro
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
)
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, artists.sort_name AS artist_sort_name
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
@@ -158,6 +160,7 @@ func (q *Queries) ListAlbumsAlphaByArtist(ctx context.Context, arg ListAlbumsAlp
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.ArtistSortName,
); err != nil {
return nil, err
@@ -171,7 +174,7 @@ func (q *Queries) ListAlbumsAlphaByArtist(ctx context.Context, arg ListAlbumsAlp
}
const listAlbumsAlphaByName = `-- name: ListAlbumsAlphaByName :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at FROM albums ORDER BY sort_title LIMIT $1 OFFSET $2
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 {
@@ -198,6 +201,7 @@ func (q *Queries) ListAlbumsAlphaByName(ctx context.Context, arg ListAlbumsAlpha
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
); err != nil {
return nil, err
}
@@ -210,7 +214,7 @@ func (q *Queries) ListAlbumsAlphaByName(ctx context.Context, arg ListAlbumsAlpha
}
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, artists.name AS artist_name
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
@@ -248,6 +252,7 @@ func (q *Queries) ListAlbumsAlphaWithArtist(ctx context.Context, arg ListAlbumsA
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.ArtistName,
); err != nil {
return nil, err
@@ -261,7 +266,7 @@ func (q *Queries) ListAlbumsAlphaWithArtist(ctx context.Context, arg ListAlbumsA
}
const listAlbumsByArtist = `-- name: ListAlbumsByArtist :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at FROM albums WHERE artist_id = $1 ORDER BY release_date NULLS LAST, sort_title
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) {
@@ -283,6 +288,7 @@ func (q *Queries) ListAlbumsByArtist(ctx context.Context, artistID pgtype.UUID)
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
); err != nil {
return nil, err
}
@@ -295,7 +301,7 @@ func (q *Queries) ListAlbumsByArtist(ctx context.Context, artistID pgtype.UUID)
}
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
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
@@ -329,6 +335,7 @@ func (q *Queries) ListAlbumsByGenre(ctx context.Context, arg ListAlbumsByGenrePa
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
); err != nil {
return nil, err
}
@@ -341,7 +348,7 @@ func (q *Queries) ListAlbumsByGenre(ctx context.Context, arg ListAlbumsByGenrePa
}
const listAlbumsNewest = `-- name: ListAlbumsNewest :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at FROM albums ORDER BY created_at DESC LIMIT $1 OFFSET $2
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 {
@@ -368,6 +375,7 @@ func (q *Queries) ListAlbumsNewest(ctx context.Context, arg ListAlbumsNewestPara
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
); err != nil {
return nil, err
}
@@ -380,7 +388,7 @@ func (q *Queries) ListAlbumsNewest(ctx context.Context, arg ListAlbumsNewestPara
}
const listAlbumsRandom = `-- name: ListAlbumsRandom :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at FROM albums ORDER BY random() LIMIT $1
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) {
@@ -402,6 +410,7 @@ func (q *Queries) ListAlbumsRandom(ctx context.Context, limit int32) ([]Album, e
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
); err != nil {
return nil, err
}
@@ -414,7 +423,7 @@ func (q *Queries) ListAlbumsRandom(ctx context.Context, limit int32) ([]Album, e
}
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, artists.name AS artist_name
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
@@ -449,6 +458,7 @@ func (q *Queries) ListRecentlyAddedAlbumsWithArtist(ctx context.Context, limit i
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.ArtistName,
); err != nil {
return nil, err
@@ -462,7 +472,7 @@ func (q *Queries) ListRecentlyAddedAlbumsWithArtist(ctx context.Context, limit i
}
const searchAlbums = `-- name: SearchAlbums :many
SELECT id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at FROM albums
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
@@ -493,6 +503,7 @@ func (q *Queries) SearchAlbums(ctx context.Context, arg SearchAlbumsParams) ([]A
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
); err != nil {
return nil, err
}
@@ -515,7 +526,7 @@ DO UPDATE SET
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
RETURNING id, title, sort_title, artist_id, release_date, mbid, cover_art_path, created_at, updated_at, cover_art_source
`
type UpsertAlbumParams struct {
@@ -547,6 +558,7 @@ func (q *Queries) UpsertAlbum(ctx context.Context, arg UpsertAlbumParams) (Album
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
)
return i, err
}
+199
View File
@@ -0,0 +1,199 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: covers.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const clearAlbumCover = `-- name: ClearAlbumCover :exec
UPDATE albums
SET cover_art_path = NULL,
cover_art_source = NULL
WHERE id = $1
`
// Used by the admin retry endpoint to wipe state before re-running the
// enricher. Sets both fields to NULL.
func (q *Queries) ClearAlbumCover(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, clearAlbumCover, id)
return err
}
const countAlbumCoverSources = `-- name: CountAlbumCoverSources :one
SELECT
COUNT(*) FILTER (WHERE cover_art_source IS NULL) ::bigint AS untried,
COUNT(*) FILTER (WHERE cover_art_source = 'sidecar') ::bigint AS sidecar,
COUNT(*) FILTER (WHERE cover_art_source = 'embedded')::bigint AS embedded,
COUNT(*) FILTER (WHERE cover_art_source = 'mbcaa') ::bigint AS mbcaa,
COUNT(*) FILTER (WHERE cover_art_source = 'none') ::bigint AS none
FROM albums
`
type CountAlbumCoverSourcesRow struct {
Untried int64
Sidecar int64
Embedded int64
Mbcaa int64
None int64
}
// Diagnostic for the admin overview: counts by source, including NULL.
// Returns one row with named tallies.
func (q *Queries) CountAlbumCoverSources(ctx context.Context) (CountAlbumCoverSourcesRow, error) {
row := q.db.QueryRow(ctx, countAlbumCoverSources)
var i CountAlbumCoverSourcesRow
err := row.Scan(
&i.Untried,
&i.Sidecar,
&i.Embedded,
&i.Mbcaa,
&i.None,
)
return i, err
}
const getAlbumWithFirstTrackPath = `-- name: GetAlbumWithFirstTrackPath :one
SELECT a.id, a.mbid, a.title, a.cover_art_path, a.cover_art_source,
t.file_path AS track_file_path
FROM albums a
LEFT JOIN tracks t ON t.album_id = a.id
WHERE a.id = $1
ORDER BY t.disc_number NULLS LAST, t.track_number NULLS LAST, t.id
LIMIT 1
`
type GetAlbumWithFirstTrackPathRow struct {
ID pgtype.UUID
Mbid *string
Title string
CoverArtPath *string
CoverArtSource *string
TrackFilePath *string
}
// Returns the album row and one of its track file paths (used to derive
// the album directory). Pick lowest position track for determinism.
func (q *Queries) GetAlbumWithFirstTrackPath(ctx context.Context, id pgtype.UUID) (GetAlbumWithFirstTrackPathRow, error) {
row := q.db.QueryRow(ctx, getAlbumWithFirstTrackPath, id)
var i GetAlbumWithFirstTrackPathRow
err := row.Scan(
&i.ID,
&i.Mbid,
&i.Title,
&i.CoverArtPath,
&i.CoverArtSource,
&i.TrackFilePath,
)
return i, err
}
const listAlbumsMissingCover = `-- name: ListAlbumsMissingCover :many
SELECT a.id, a.mbid, a.title, a.artist_id
FROM albums a
WHERE a.cover_art_source IS NULL
ORDER BY a.created_at ASC
LIMIT $1
`
type ListAlbumsMissingCoverRow struct {
ID pgtype.UUID
Mbid *string
Title string
ArtistID pgtype.UUID
}
// M7 #353: album cover enrichment queries.
// Albums where cover_art_source has never been set. Used by the boot
// backfill and post-scan enrichment passes. LIMIT supplied by caller.
func (q *Queries) ListAlbumsMissingCover(ctx context.Context, limit int32) ([]ListAlbumsMissingCoverRow, error) {
rows, err := q.db.Query(ctx, listAlbumsMissingCover, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAlbumsMissingCoverRow
for rows.Next() {
var i ListAlbumsMissingCoverRow
if err := rows.Scan(
&i.ID,
&i.Mbid,
&i.Title,
&i.ArtistID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsRetryMissing = `-- name: ListAlbumsRetryMissing :many
SELECT a.id, a.mbid, a.title, a.artist_id
FROM albums a
WHERE a.cover_art_source IS NULL OR a.cover_art_source = 'none'
ORDER BY a.created_at ASC
LIMIT $1
`
type ListAlbumsRetryMissingRow struct {
ID pgtype.UUID
Mbid *string
Title string
ArtistID pgtype.UUID
}
// Bulk-retry candidates: NULL (never tried) plus 'none' (tried, failed).
// Used by the admin "refetch missing covers" endpoint. LIMIT supplied.
func (q *Queries) ListAlbumsRetryMissing(ctx context.Context, limit int32) ([]ListAlbumsRetryMissingRow, error) {
rows, err := q.db.Query(ctx, listAlbumsRetryMissing, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAlbumsRetryMissingRow
for rows.Next() {
var i ListAlbumsRetryMissingRow
if err := rows.Scan(
&i.ID,
&i.Mbid,
&i.Title,
&i.ArtistID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const setAlbumCover = `-- name: SetAlbumCover :exec
UPDATE albums
SET cover_art_path = NULLIF($2::text, ''),
cover_art_source = $3
WHERE id = $1
`
type SetAlbumCoverParams struct {
ID pgtype.UUID
Column2 string
CoverArtSource *string
}
// Persist enrichment result. Pass cover_art_path = ” to clear (turns into NULL).
func (q *Queries) SetAlbumCover(ctx context.Context, arg SetAlbumCoverParams) error {
_, err := q.db.Exec(ctx, setAlbumCover, arg.ID, arg.Column2, arg.CoverArtSource)
return err
}
+2 -1
View File
@@ -120,7 +120,7 @@ func (q *Queries) ListLikedAlbumIDs(ctx context.Context, userID pgtype.UUID) ([]
}
const listLikedAlbumRows = `-- name: ListLikedAlbumRows :many
SELECT a.id, a.title, a.sort_title, a.artist_id, a.release_date, a.mbid, a.cover_art_path, a.created_at, a.updated_at FROM albums a
SELECT a.id, a.title, a.sort_title, a.artist_id, a.release_date, a.mbid, a.cover_art_path, a.created_at, a.updated_at, a.cover_art_source FROM albums a
JOIN general_likes_albums l ON l.album_id = a.id
WHERE l.user_id = $1
ORDER BY l.liked_at DESC
@@ -152,6 +152,7 @@ func (q *Queries) ListLikedAlbumRows(ctx context.Context, arg ListLikedAlbumRows
&i.CoverArtPath,
&i.CreatedAt,
&i.UpdatedAt,
&i.CoverArtSource,
); err != nil {
return nil, err
}
+10 -9
View File
@@ -188,15 +188,16 @@ func (ns NullLidarrRequestStatus) Value() (driver.Value, error) {
}
type Album struct {
ID pgtype.UUID
Title string
SortTitle string
ArtistID pgtype.UUID
ReleaseDate pgtype.Date
Mbid *string
CoverArtPath *string
CreatedAt pgtype.Timestamptz
UpdatedAt pgtype.Timestamptz
ID pgtype.UUID
Title string
SortTitle string
ArtistID pgtype.UUID
ReleaseDate pgtype.Date
Mbid *string
CoverArtPath *string
CreatedAt pgtype.Timestamptz
UpdatedAt pgtype.Timestamptz
CoverArtSource *string
}
type Artist struct {
+4 -2
View File
@@ -157,7 +157,7 @@ func (q *Queries) ListMostPlayedTracksForUser(ctx context.Context, arg ListMostP
}
const listRediscoverAlbumsFallbackForUser = `-- name: ListRediscoverAlbumsFallbackForUser :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, artists.name AS artist_name
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 general_likes_albums gla
JOIN albums ON albums.id = gla.album_id
JOIN artists ON artists.id = albums.artist_id
@@ -198,6 +198,7 @@ func (q *Queries) ListRediscoverAlbumsFallbackForUser(ctx context.Context, arg L
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.ArtistName,
); err != nil {
return nil, err
@@ -225,7 +226,7 @@ WITH eligible AS (
HAVING COALESCE(max(pe.started_at), '1970-01-01'::timestamptz)
< now() - interval '14 days'
)
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, artists.name AS artist_name
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 eligible e
JOIN albums ON albums.id = e.album_id
JOIN artists ON artists.id = albums.artist_id
@@ -266,6 +267,7 @@ func (q *Queries) ListRediscoverAlbumsForUser(ctx context.Context, arg ListRedis
&i.Album.CoverArtPath,
&i.Album.CreatedAt,
&i.Album.UpdatedAt,
&i.Album.CoverArtSource,
&i.ArtistName,
); err != nil {
return nil, err
+5 -1
View File
@@ -201,7 +201,11 @@ type ListPlaylistsByUserAndKindRow struct {
UpdatedAt pgtype.Timestamptz
}
// Powers GET /api/me/playlists?kind=. Sorted by updated_at DESC.
// Used in integration tests; production filter applied in Go
// (api.handleListPlaylists fetches via Service.List and filters by Kind in
// memory so it can also surface other users' public playlists alongside
// the caller's own kind-filtered ones).
// Sorted by updated_at DESC.
func (q *Queries) ListPlaylistsByUserAndKind(ctx context.Context, arg ListPlaylistsByUserAndKindParams) ([]ListPlaylistsByUserAndKindRow, error) {
rows, err := q.db.Query(ctx, listPlaylistsByUserAndKind, arg.UserID, arg.Column2)
if err != nil {
+56
View File
@@ -0,0 +1,56 @@
-- M7 #353: album cover enrichment queries.
-- name: ListAlbumsMissingCover :many
-- Albums where cover_art_source has never been set. Used by the boot
-- backfill and post-scan enrichment passes. LIMIT supplied by caller.
SELECT a.id, a.mbid, a.title, a.artist_id
FROM albums a
WHERE a.cover_art_source IS NULL
ORDER BY a.created_at ASC
LIMIT $1;
-- name: ListAlbumsRetryMissing :many
-- Bulk-retry candidates: NULL (never tried) plus 'none' (tried, failed).
-- Used by the admin "refetch missing covers" endpoint. LIMIT supplied.
SELECT a.id, a.mbid, a.title, a.artist_id
FROM albums a
WHERE a.cover_art_source IS NULL OR a.cover_art_source = 'none'
ORDER BY a.created_at ASC
LIMIT $1;
-- name: GetAlbumWithFirstTrackPath :one
-- Returns the album row and one of its track file paths (used to derive
-- the album directory). Pick lowest position track for determinism.
SELECT a.id, a.mbid, a.title, a.cover_art_path, a.cover_art_source,
t.file_path AS track_file_path
FROM albums a
LEFT JOIN tracks t ON t.album_id = a.id
WHERE a.id = $1
ORDER BY t.disc_number NULLS LAST, t.track_number NULLS LAST, t.id
LIMIT 1;
-- name: SetAlbumCover :exec
-- Persist enrichment result. Pass cover_art_path = '' to clear (turns into NULL).
UPDATE albums
SET cover_art_path = NULLIF($2::text, ''),
cover_art_source = $3
WHERE id = $1;
-- name: ClearAlbumCover :exec
-- Used by the admin retry endpoint to wipe state before re-running the
-- enricher. Sets both fields to NULL.
UPDATE albums
SET cover_art_path = NULL,
cover_art_source = NULL
WHERE id = $1;
-- name: CountAlbumCoverSources :one
-- Diagnostic for the admin overview: counts by source, including NULL.
-- Returns one row with named tallies.
SELECT
COUNT(*) FILTER (WHERE cover_art_source IS NULL) ::bigint AS untried,
COUNT(*) FILTER (WHERE cover_art_source = 'sidecar') ::bigint AS sidecar,
COUNT(*) FILTER (WHERE cover_art_source = 'embedded')::bigint AS embedded,
COUNT(*) FILTER (WHERE cover_art_source = 'mbcaa') ::bigint AS mbcaa,
COUNT(*) FILTER (WHERE cover_art_source = 'none') ::bigint AS none
FROM albums;