-- 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. -- 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 *; -- name: GetAlbum :one SELECT * FROM albums WHERE id = ? LIMIT 1; -- name: SetAlbumMBID :exec UPDATE albums SET mbid = ? WHERE id = ?; -- name: SetAlbumOriginalYear :exec UPDATE albums SET original_year = ? WHERE id = ?; -- name: SetAlbumCoverArt :exec UPDATE albums SET cover_art_id = ? WHERE id = ?; -- name: SetAlbumPendingReleaseMBID :exec UPDATE albums SET pending_release_mbid = ? WHERE id = ?; -- name: ResolveAlbumPendingReleaseMBID :exec -- Clears the pending marker once the release-group MBID it stood in for -- has been resolved. Guarded so a real MBID is never overwritten. UPDATE albums SET mbid = ?, pending_release_mbid = NULL WHERE id = ? AND (mbid IS NULL OR mbid = ''); -- 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 = '') LIMIT ?; -- name: DeleteAlbum :exec DELETE FROM albums WHERE id = ?; -- name: DeleteAllAlbums :exec DELETE FROM albums; -- name: GetEmptyAlbumIDs :many -- 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. SELECT id FROM albums al WHERE NOT EXISTS ( SELECT 1 FROM audio_files af WHERE af.album_id = al.id ); -- 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(sqlc.arg(library_id) AS INTEGER), 0), af.library_id) ) ORDER BY al.name; -- 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 = sqlc.arg(artist) OR ar.name = sqlc.arg(artist)) AND EXISTS ( SELECT 1 FROM audio_files af WHERE af.album_id = al.id AND af.library_id = COALESCE(NULLIF(CAST(sqlc.arg(library_id) AS INTEGER), 0), af.library_id) ) ORDER BY year, al.name; -- name: GetAlbumCompleteness :one -- "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. 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 = sqlc.arg(album_id) 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 = sqlc.arg(album_id) AND c.total_tracks IS NULL ) AS INTEGER) AS known FROM audio_files a WHERE a.album_id = sqlc.arg(album_id); -- name: GetAlbumsCompleteness :many -- 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. 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 (sqlc.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;