feat(database): shape the library like files, and shrink the catalog
CI / check (push) Successful in 3m7s
CI / e2e (push) Canceled after 1m45s

Plans 013 and 014, the album page that prompted them, and the smaller
fixes they turned up. Changelog, largest first.

## The local library is shaped like files, not like MusicBrainz

`audio_files` carries its own tags and points at `albums` and
`artists`; `file_genres` is the one real many-to-many. `recordings`,
`release_group_recordings`, `artist_credit`, `artist_credit_artist`,
`recording_genres`, `release_groups` and `release_to_rg` are gone from
the local side, and with them a six-way join in every read, a
`MIN(release_group_id)` subquery in eleven queries and a
first-credited-artist subquery in nine. Measured on a real 25,966-file
library, every many-to-many that model expressed was 1:1 in the data.

- Ownership is a file. `GetFilePathsByRecordingMBIDs`,
  `LibraryMBIDIndex.CheckMBIDs`, `collectLibraryEntities` and
  `pruneStaleLocalCrossReferences` all join `audio_files`, so the 812
  orphaned recordings, 216 release groups and 260 artists that library
  carried are now structurally impossible.
- One projection: every track query selects from the `track_metadata`
  view, one row type, one mapper. Nine hand-rolled copies had drifted
  far enough to report different years on different screens.
- `library_id = 0` means every library, so each list query exists once
  instead of scoped and unscoped with a branch at every call site.
- No migration chain. `sql/schemas/` is the one description of the
  shape; `sql/migrations/`, `applyMigrations` and `schema_migrations`
  are squashed away, along with the drift between them that had sqlc
  generating against a stale schema.
- `database.InsertTestTrack` is the one test seeder; twenty test files
  had been assembling the old FK chain each in its own order.

## The catalog stores its ids as bytes

`explore_index`'s three 36-char MBID columns and its entity-type text
are 16 raw bytes and a small integer. The table and its six indexes go
780 MB to 405 MB on a real 2,052,200-row catalog, which is why a fresh
install is ~0.6 GB rather than ~1.0 GB.

- `backend/explore/mbid.go` is the only place the encoding is known;
  everything above it speaks dashed strings.
- `CHECK(length(mbid) = 16)` makes a stringly write fail at the insert
  rather than silently returning no rows, since SQLite does not coerce
  between TEXT and BLOB.
- The importer asks the artifact what encoding it carries and converts
  on the way in, so the artifact already published keeps working and no
  format bump is needed.
- `indexRowColumns`/`scanIndexRow` replace four copies of a 22-column
  list, and `TestStoredEncodingRoundTrips` sweeps every read path.

## An album page that says how much of the album is yours

- One question, asked once: is there a file. `filePaths` is filled by a
  single batched lookup when the tracklist settles, and the badge, the
  Play count, the dimmed rows and every menu item read it — replacing
  four claims of decreasing confidence that could show a green tick on
  an album whose every action did nothing.
- Play, Play 7 of 12, or no play button at all.
- `total_tracks` on `explore_index` (~2 bytes over 400,677 release
  groups) and on `audio_files` from tags that have always carried it:
  a complete MBID-matched album now makes no catalog call at all, where
  it used to spend the most expensive request the app makes.
- A merged cluster shows the running order the most releases agree on,
  and the version list marks the release you own rather than standing a
  synthetic entry in for it.
- `AlbumReleasesFailed`: a slow fetch is no longer reported as a failed
  one by a 12-second timer.
- Rows not in the library are dimmed in place (with `aria-disabled`)
  instead of the owned ones wearing a green tick and a legend.

## Caches and cover art get ceilings

- Only the three tiers of a cover are stored; the full-resolution copy
  nothing rendered was 1,134 MB of a 1.4 GB covers directory.
- One artist portrait is downloaded and the rest are remembered as
  URLs — 4.1 GB of a 5.3 GB cache was candidates no code path reads.
- `browsedArtBudget` and `httpCacheBudget` bound what an age cannot:
  the same install held art for 5,770 artists in a 1,301-artist
  library.
- `OrphanedArtistImagesJob` joined a bare MBID onto a sharded
  directory, so it deleted the rows that were the only record of the
  files it left behind. `explore.ArtistImageDir` is that layout's one
  definition now.

## The autotag queue asks whether there is work

`tagging_items` was a row per album folder, not a queue, and no query
read the `tag_status` column that held the answer. The four queue
queries ask the files, which matters most where it is least visible:
`startPrefetch` was scoring every album in a tagged library against
MusicBrainz.

## Phantom playlist tracks resolve in place

An M3U8 imported before its files leaves phantom rows; they now match
by path and fall back to position, keep their place in the playlist
when resolved, and pair best-first so two phantoms cannot claim the
same file.

## Playing a track plays the list it is in

Double-click, and Play on a single row's menu, queue the list as
displayed with `startIndex` on that row — the album page and the track
list used to queue one track and discard the album around it. A
multi-row selection still plays exactly itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AfVYUVExXsx1nSWrXN8mAh
This commit is contained in:
2026-08-16 13:58:15 -04:00
co-authored by Claude Opus 5
parent 1128881e8d
commit e7748f1fd5
208 changed files with 10944 additions and 12104 deletions
+426
View File
@@ -0,0 +1,426 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: albums.sql
package sqlcgen
import (
"context"
"database/sql"
)
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 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
}
@@ -1,140 +0,0 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: artist_credit.sql
package sqlcgen
import (
"context"
)
const countArtistCreditReferences = `-- name: CountArtistCreditReferences :one
SELECT
(SELECT COUNT(*) FROM recordings WHERE artist_credit_id = ?1) +
(SELECT COUNT(*) FROM release_groups WHERE album_artist_credit_id = ?1)
AS total
`
func (q *Queries) CountArtistCreditReferences(ctx context.Context, artistCreditID int64) (int64, error) {
row := q.db.QueryRowContext(ctx, countArtistCreditReferences, artistCreditID)
var total int64
err := row.Scan(&total)
return total, err
}
const createArtistCredit = `-- name: CreateArtistCredit :one
INSERT INTO artist_credit (text) VALUES (?)
RETURNING id, text
`
func (q *Queries) CreateArtistCredit(ctx context.Context, text string) (ArtistCredit, error) {
row := q.db.QueryRowContext(ctx, createArtistCredit, text)
var i ArtistCredit
err := row.Scan(&i.ID, &i.Text)
return i, err
}
const deleteAllArtistCredits = `-- name: DeleteAllArtistCredits :exec
DELETE FROM artist_credit
`
func (q *Queries) DeleteAllArtistCredits(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteAllArtistCredits)
return err
}
const deleteArtistCredit = `-- name: DeleteArtistCredit :exec
DELETE FROM artist_credit
WHERE id = ?
`
func (q *Queries) DeleteArtistCredit(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteArtistCredit, id)
return err
}
const getArtistCredit = `-- name: GetArtistCredit :one
SELECT id, text FROM artist_credit
WHERE id = ? LIMIT 1
`
func (q *Queries) GetArtistCredit(ctx context.Context, id int64) (ArtistCredit, error) {
row := q.db.QueryRowContext(ctx, getArtistCredit, id)
var i ArtistCredit
err := row.Scan(&i.ID, &i.Text)
return i, err
}
const getArtistCreditByText = `-- name: GetArtistCreditByText :one
SELECT id, text FROM artist_credit
WHERE text = ? LIMIT 1
`
func (q *Queries) GetArtistCreditByText(ctx context.Context, text string) (ArtistCredit, error) {
row := q.db.QueryRowContext(ctx, getArtistCreditByText, text)
var i ArtistCredit
err := row.Scan(&i.ID, &i.Text)
return i, err
}
const getOrphanedArtistCreditIDs = `-- name: GetOrphanedArtistCreditIDs :many
SELECT ac.id FROM artist_credit ac
WHERE NOT EXISTS (SELECT 1 FROM recordings r WHERE r.artist_credit_id = ac.id)
AND NOT EXISTS (SELECT 1 FROM release_groups rg WHERE rg.album_artist_credit_id = ac.id)
`
// Artist credits no longer used by any recording or release group - run
// after orphaned recordings/release groups are deleted, so a credit
// that only existed for now-removed tracks is cleaned up too.
func (q *Queries) GetOrphanedArtistCreditIDs(ctx context.Context) ([]int64, error) {
rows, err := q.db.QueryContext(ctx, getOrphanedArtistCreditIDs)
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 updateArtistCredit = `-- name: UpdateArtistCredit :exec
UPDATE artist_credit
SET text = ?
WHERE id = ?
`
type UpdateArtistCreditParams struct {
Text string
ID int64
}
func (q *Queries) UpdateArtistCredit(ctx context.Context, arg UpdateArtistCreditParams) error {
_, err := q.db.ExecContext(ctx, updateArtistCredit, arg.Text, arg.ID)
return err
}
const upsertArtistCredit = `-- name: UpsertArtistCredit :one
INSERT INTO artist_credit (text) VALUES (?)
ON CONFLICT(text) DO UPDATE SET text = excluded.text
RETURNING id, text
`
func (q *Queries) UpsertArtistCredit(ctx context.Context, text string) (ArtistCredit, error) {
row := q.db.QueryRowContext(ctx, upsertArtistCredit, text)
var i ArtistCredit
err := row.Scan(&i.ID, &i.Text)
return i, err
}
@@ -1,85 +0,0 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: artist_credit_artists.sql
package sqlcgen
import (
"context"
)
const createArtistCreditArtist = `-- name: CreateArtistCreditArtist :one
INSERT INTO artist_credit_artist (artist_id, credit_id) VALUES (?, ?)
RETURNING id, artist_id, credit_id
`
type CreateArtistCreditArtistParams struct {
ArtistID int64
CreditID int64
}
func (q *Queries) CreateArtistCreditArtist(ctx context.Context, arg CreateArtistCreditArtistParams) (ArtistCreditArtist, error) {
row := q.db.QueryRowContext(ctx, createArtistCreditArtist, arg.ArtistID, arg.CreditID)
var i ArtistCreditArtist
err := row.Scan(&i.ID, &i.ArtistID, &i.CreditID)
return i, err
}
const deleteAllArtistCreditArtists = `-- name: DeleteAllArtistCreditArtists :exec
DELETE FROM artist_credit_artist
`
func (q *Queries) DeleteAllArtistCreditArtists(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteAllArtistCreditArtists)
return err
}
const deleteArtistCreditArtist = `-- name: DeleteArtistCreditArtist :exec
DELETE FROM artist_credit_artist
WHERE id =?
`
func (q *Queries) DeleteArtistCreditArtist(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteArtistCreditArtist, id)
return err
}
const deleteArtistCreditArtistByCredit = `-- name: DeleteArtistCreditArtistByCredit :exec
DELETE FROM artist_credit_artist
WHERE credit_id = ?
`
func (q *Queries) DeleteArtistCreditArtistByCredit(ctx context.Context, creditID int64) error {
_, err := q.db.ExecContext(ctx, deleteArtistCreditArtistByCredit, creditID)
return err
}
const getArtistCreditArtist = `-- name: GetArtistCreditArtist :one
SELECT id, artist_id, credit_id FROM artist_credit_artist
WHERE id = ? LIMIT 1
`
func (q *Queries) GetArtistCreditArtist(ctx context.Context, id int64) (ArtistCreditArtist, error) {
row := q.db.QueryRowContext(ctx, getArtistCreditArtist, id)
var i ArtistCreditArtist
err := row.Scan(&i.ID, &i.ArtistID, &i.CreditID)
return i, err
}
const updateArtistCreditArtist = `-- name: UpdateArtistCreditArtist :exec
UPDATE artist_credit_artist
SET artist_id = ?, credit_id = ?
WHERE id =?
`
type UpdateArtistCreditArtistParams struct {
ArtistID int64
CreditID int64
ID int64
}
func (q *Queries) UpdateArtistCreditArtist(ctx context.Context, arg UpdateArtistCreditArtistParams) error {
_, err := q.db.ExecContext(ctx, updateArtistCreditArtist, arg.ArtistID, arg.CreditID, arg.ID)
return err
}
+63 -88
View File
@@ -7,20 +7,9 @@ package sqlcgen
import (
"context"
"database/sql"
)
const createArtist = `-- name: CreateArtist :one
INSERT INTO artists (name) VALUES (?)
RETURNING id, name, mbid
`
func (q *Queries) CreateArtist(ctx context.Context, name string) (Artist, error) {
row := q.db.QueryRowContext(ctx, createArtist, name)
var i Artist
err := row.Scan(&i.ID, &i.Name, &i.Mbid)
return i, err
}
const deleteAllArtists = `-- name: DeleteAllArtists :exec
DELETE FROM artists
`
@@ -31,8 +20,7 @@ func (q *Queries) DeleteAllArtists(ctx context.Context) error {
}
const deleteArtist = `-- name: DeleteArtist :exec
DELETE FROM artists
WHERE id = ?
DELETE FROM artists WHERE id = ?
`
func (q *Queries) DeleteArtist(ctx context.Context, id int64) error {
@@ -43,56 +31,17 @@ func (q *Queries) DeleteArtist(ctx context.Context, id int64) error {
const getAlbumArtists = `-- name: GetAlbumArtists :many
SELECT DISTINCT a.id, a.name, a.mbid
FROM artists a
JOIN artist_credit_artist aca ON aca.artist_id = a.id
JOIN artist_credit ac ON ac.id = aca.credit_id
JOIN release_groups rg ON rg.album_artist_credit_id = ac.id
ORDER BY a.name
`
func (q *Queries) GetAlbumArtists(ctx context.Context) ([]Artist, error) {
rows, err := q.db.QueryContext(ctx, getAlbumArtists)
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.Mbid); 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 getAlbumArtistsByLibrary = `-- name: GetAlbumArtistsByLibrary :many
SELECT DISTINCT a.id, a.name, a.mbid
FROM artists a
JOIN artist_credit_artist aca ON aca.artist_id = a.id
JOIN artist_credit ac ON ac.id = aca.credit_id
JOIN release_groups rg ON rg.album_artist_credit_id = ac.id
WHERE a.id IN (
SELECT DISTINCT aca2.artist_id
FROM artist_credit_artist aca2
JOIN artist_credit ac2 ON ac2.id = aca2.credit_id
JOIN release_groups rg2 ON rg2.album_artist_credit_id = ac2.id
JOIN release_group_recordings rgr2 ON rgr2.release_group_id = rg2.id
JOIN recordings r2 ON r2.id = rgr2.recording_id
JOIN audio_files af2 ON af2.recording_id = r2.id
WHERE af2.library_id = ?
JOIN albums al ON al.artist_id = a.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 a.name
`
func (q *Queries) GetAlbumArtistsByLibrary(ctx context.Context, libraryID int64) ([]Artist, error) {
rows, err := q.db.QueryContext(ctx, getAlbumArtistsByLibrary, libraryID)
func (q *Queries) GetAlbumArtists(ctx context.Context, libraryID int64) ([]Artist, error) {
rows, err := q.db.QueryContext(ctx, getAlbumArtists, libraryID)
if err != nil {
return nil, err
}
@@ -115,8 +64,7 @@ func (q *Queries) GetAlbumArtistsByLibrary(ctx context.Context, libraryID int64)
}
const getAllArtists = `-- name: GetAllArtists :many
SELECT id, name, mbid FROM artists
ORDER BY name
SELECT id, name, mbid FROM artists ORDER BY name
`
func (q *Queries) GetAllArtists(ctx context.Context) ([]Artist, error) {
@@ -143,8 +91,7 @@ func (q *Queries) GetAllArtists(ctx context.Context) ([]Artist, error) {
}
const getArtist = `-- name: GetArtist :one
SELECT id, name, mbid FROM artists
WHERE id = ? LIMIT 1
SELECT id, name, mbid FROM artists WHERE id = ? LIMIT 1
`
func (q *Queries) GetArtist(ctx context.Context, id int64) (Artist, error) {
@@ -154,9 +101,28 @@ func (q *Queries) GetArtist(ctx context.Context, id int64) (Artist, error) {
return i, err
}
const getArtistByFilePath = `-- name: GetArtistByFilePath :one
SELECT COALESCE(a.name, '') AS artist_name, COALESCE(a.mbid, '') AS artist_mbid
FROM audio_files af
LEFT JOIN artists a ON a.id = af.artist_id
WHERE af.file_path = ?
LIMIT 1
`
type GetArtistByFilePathRow struct {
ArtistName string
ArtistMbid string
}
func (q *Queries) GetArtistByFilePath(ctx context.Context, filePath string) (GetArtistByFilePathRow, error) {
row := q.db.QueryRowContext(ctx, getArtistByFilePath, filePath)
var i GetArtistByFilePathRow
err := row.Scan(&i.ArtistName, &i.ArtistMbid)
return i, err
}
const getArtistByName = `-- name: GetArtistByName :one
SELECT id, name, mbid FROM artists
WHERE name = ? LIMIT 1
SELECT id, name, mbid FROM artists WHERE name = ? LIMIT 1
`
func (q *Queries) GetArtistByName(ctx context.Context, name string) (Artist, error) {
@@ -166,18 +132,15 @@ func (q *Queries) GetArtistByName(ctx context.Context, name string) (Artist, err
return i, err
}
const getOrphanedArtistIDs = `-- name: GetOrphanedArtistIDs :many
SELECT a.id FROM artists a
WHERE NOT EXISTS (
SELECT 1 FROM artist_credit_artist aca WHERE aca.artist_id = a.id
)
const getUnreferencedArtistIDs = `-- name: GetUnreferencedArtistIDs :many
SELECT id FROM artists a
WHERE NOT EXISTS (SELECT 1 FROM audio_files af WHERE af.artist_id = a.id)
AND NOT EXISTS (SELECT 1 FROM albums al WHERE al.artist_id = a.id)
`
// Artists no longer credited on any recording or release group - left
// behind when a scan's orphan cleanup removes the audio_files that used
// to justify them, since deleting an audio_files row doesn't cascade.
func (q *Queries) GetOrphanedArtistIDs(ctx context.Context) ([]int64, error) {
rows, err := q.db.QueryContext(ctx, getOrphanedArtistIDs)
// Artists no file and no album points at any more.
func (q *Queries) GetUnreferencedArtistIDs(ctx context.Context) ([]int64, error) {
rows, err := q.db.QueryContext(ctx, getUnreferencedArtistIDs)
if err != nil {
return nil, err
}
@@ -199,30 +162,42 @@ func (q *Queries) GetOrphanedArtistIDs(ctx context.Context) ([]int64, error) {
return items, nil
}
const updateArtist = `-- name: UpdateArtist :exec
UPDATE artists
SET name = ?
WHERE id = ?
const setArtistMBID = `-- name: SetArtistMBID :exec
UPDATE artists SET mbid = ? WHERE id = ?
`
type UpdateArtistParams struct {
Name string
type SetArtistMBIDParams struct {
Mbid sql.NullString
ID int64
}
func (q *Queries) UpdateArtist(ctx context.Context, arg UpdateArtistParams) error {
_, err := q.db.ExecContext(ctx, updateArtist, arg.Name, arg.ID)
func (q *Queries) SetArtistMBID(ctx context.Context, arg SetArtistMBIDParams) error {
_, err := q.db.ExecContext(ctx, setArtistMBID, arg.Mbid, arg.ID)
return err
}
const upsertArtist = `-- name: UpsertArtist :one
INSERT INTO artists (name) VALUES (?)
ON CONFLICT(name) DO UPDATE SET name = excluded.name
INSERT INTO artists (name, mbid) VALUES (?, ?)
ON CONFLICT(name) DO UPDATE SET
mbid = COALESCE(excluded.mbid, artists.mbid)
RETURNING id, name, mbid
`
func (q *Queries) UpsertArtist(ctx context.Context, name string) (Artist, error) {
row := q.db.QueryRowContext(ctx, upsertArtist, name)
type UpsertArtistParams struct {
Name string
Mbid sql.NullString
}
// Queries over artists.
//
// An artist row is reachable two ways: as a file's primary artist
// (audio_files.artist_id) and as an album's artist (albums.artist_id).
// Both used to route through artist_credit + artist_credit_artist,
// which is how "which album artists are in library 2" came to be a
// five-join subquery inside a three-join query.
func (q *Queries) UpsertArtist(ctx context.Context, arg UpsertArtistParams) (Artist, error) {
row := q.db.QueryRowContext(ctx, upsertArtist, arg.Name, arg.Mbid)
var i Artist
err := row.Scan(&i.ID, &i.Name, &i.Mbid)
return i, err
File diff suppressed because it is too large Load Diff
+65 -355
View File
@@ -7,36 +7,9 @@ package sqlcgen
import (
"context"
"database/sql"
"strings"
)
const countGenreReferences = `-- name: CountGenreReferences :one
SELECT COUNT(*) FROM recording_genres WHERE genre_id = ?
`
func (q *Queries) CountGenreReferences(ctx context.Context, genreID int64) (int64, error) {
row := q.db.QueryRowContext(ctx, countGenreReferences, genreID)
var count int64
err := row.Scan(&count)
return count, err
}
const createRecordingGenre = `-- name: CreateRecordingGenre :exec
INSERT OR IGNORE INTO recording_genres (recording_id, genre_id)
VALUES (?, ?)
`
type CreateRecordingGenreParams struct {
RecordingID int64
GenreID int64
}
func (q *Queries) CreateRecordingGenre(ctx context.Context, arg CreateRecordingGenreParams) error {
_, err := q.db.ExecContext(ctx, createRecordingGenre, arg.RecordingID, arg.GenreID)
return err
}
const deleteAllGenres = `-- name: DeleteAllGenres :exec
DELETE FROM genres
`
@@ -46,12 +19,12 @@ func (q *Queries) DeleteAllGenres(ctx context.Context) error {
return err
}
const deleteAllRecordingGenres = `-- name: DeleteAllRecordingGenres :exec
DELETE FROM recording_genres
const deleteFileGenres = `-- name: DeleteFileGenres :exec
DELETE FROM file_genres WHERE audio_file_id = ?
`
func (q *Queries) DeleteAllRecordingGenres(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteAllRecordingGenres)
func (q *Queries) DeleteFileGenres(ctx context.Context, audioFileID int64) error {
_, err := q.db.ExecContext(ctx, deleteFileGenres, audioFileID)
return err
}
@@ -64,20 +37,12 @@ func (q *Queries) DeleteGenre(ctx context.Context, id int64) error {
return err
}
const deleteRecordingGenres = `-- name: DeleteRecordingGenres :exec
DELETE FROM recording_genres
WHERE recording_id = ?
`
func (q *Queries) DeleteRecordingGenres(ctx context.Context, recordingID int64) error {
_, err := q.db.ExecContext(ctx, deleteRecordingGenres, recordingID)
return err
}
const getAllGenresWithCounts = `-- name: GetAllGenresWithCounts :many
SELECT g.name, COUNT(rg.recording_id) AS track_count
SELECT g.name, COUNT(fg.audio_file_id) AS track_count
FROM genres g
JOIN recording_genres rg ON g.id = rg.genre_id
JOIN file_genres fg ON fg.genre_id = g.id
JOIN audio_files af ON af.id = fg.audio_file_id
WHERE af.library_id = COALESCE(NULLIF(CAST(?1 AS INTEGER), 0), af.library_id)
GROUP BY g.id, g.name
ORDER BY g.name
`
@@ -87,8 +52,8 @@ type GetAllGenresWithCountsRow struct {
TrackCount int64
}
func (q *Queries) GetAllGenresWithCounts(ctx context.Context) ([]GetAllGenresWithCountsRow, error) {
rows, err := q.db.QueryContext(ctx, getAllGenresWithCounts)
func (q *Queries) GetAllGenresWithCounts(ctx context.Context, libraryID int64) ([]GetAllGenresWithCountsRow, error) {
rows, err := q.db.QueryContext(ctx, getAllGenresWithCounts, libraryID)
if err != nil {
return nil, err
}
@@ -110,35 +75,25 @@ func (q *Queries) GetAllGenresWithCounts(ctx context.Context) ([]GetAllGenresWit
return items, nil
}
const getAllGenresWithCountsByLibrary = `-- name: GetAllGenresWithCountsByLibrary :many
SELECT g.name, COUNT(rg.recording_id) AS track_count
FROM genres g
JOIN recording_genres rg ON g.id = rg.genre_id
JOIN recordings r ON rg.recording_id = r.id
JOIN audio_files af ON af.recording_id = r.id
WHERE af.library_id = ?
GROUP BY g.id, g.name
ORDER BY g.name
const getGenreNamesByFile = `-- name: GetGenreNamesByFile :many
SELECT g.name FROM genres g
JOIN file_genres fg ON fg.genre_id = g.id
WHERE fg.audio_file_id = ?
`
type GetAllGenresWithCountsByLibraryRow struct {
Name string
TrackCount int64
}
func (q *Queries) GetAllGenresWithCountsByLibrary(ctx context.Context, libraryID int64) ([]GetAllGenresWithCountsByLibraryRow, error) {
rows, err := q.db.QueryContext(ctx, getAllGenresWithCountsByLibrary, libraryID)
func (q *Queries) GetGenreNamesByFile(ctx context.Context, audioFileID int64) ([]string, error) {
rows, err := q.db.QueryContext(ctx, getGenreNamesByFile, audioFileID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAllGenresWithCountsByLibraryRow
var items []string
for rows.Next() {
var i GetAllGenresWithCountsByLibraryRow
if err := rows.Scan(&i.Name, &i.TrackCount); err != nil {
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
items = append(items, i)
items = append(items, name)
}
if err := rows.Close(); err != nil {
return nil, err
@@ -149,45 +104,42 @@ func (q *Queries) GetAllGenresWithCountsByLibrary(ctx context.Context, libraryID
return items, nil
}
const getFilePathsByGenres = `-- name: GetFilePathsByGenres :many
SELECT g.name AS genre_name, af.file_path
FROM genres g
JOIN recording_genres rg ON g.id = rg.genre_id
JOIN recordings r ON rg.recording_id = r.id
JOIN audio_files af ON af.recording_id = r.id
WHERE g.name IN (/*SLICE:genre_names*/?)
ORDER BY r.name
const getGenreNamesByFilePaths = `-- name: GetGenreNamesByFilePaths :many
SELECT af.file_path, g.name
FROM audio_files af
JOIN file_genres fg ON fg.audio_file_id = af.id
JOIN genres g ON g.id = fg.genre_id
WHERE af.file_path IN (/*SLICE:paths*/?)
`
type GetFilePathsByGenresRow struct {
GenreName string
FilePath string
type GetGenreNamesByFilePathsRow struct {
FilePath string
Name string
}
// Same as GetFilePathsByReleaseGroups, for "play these genres" (perf.m2):
// one query instead of one per genre, and file paths instead of whole
// track rows, which was 6 MB over the IPC for five genres.
func (q *Queries) GetFilePathsByGenres(ctx context.Context, genreNames []string) ([]GetFilePathsByGenresRow, error) {
query := getFilePathsByGenres
// Genres for many files at once. The mix builder asked this one file
// at a time, inside two nested loops -- twelve thousand single-row
// queries to assemble one mix.
func (q *Queries) GetGenreNamesByFilePaths(ctx context.Context, paths []string) ([]GetGenreNamesByFilePathsRow, error) {
query := getGenreNamesByFilePaths
var queryParams []interface{}
if len(genreNames) > 0 {
for _, v := range genreNames {
if len(paths) > 0 {
for _, v := range paths {
queryParams = append(queryParams, v)
}
query = strings.Replace(query, "/*SLICE:genre_names*/?", strings.Repeat(",?", len(genreNames))[1:], 1)
query = strings.Replace(query, "/*SLICE:paths*/?", strings.Repeat(",?", len(paths))[1:], 1)
} else {
query = strings.Replace(query, "/*SLICE:genre_names*/?", "NULL", 1)
query = strings.Replace(query, "/*SLICE:paths*/?", "NULL", 1)
}
rows, err := q.db.QueryContext(ctx, query, queryParams...)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetFilePathsByGenresRow
var items []GetGenreNamesByFilePathsRow
for rows.Next() {
var i GetFilePathsByGenresRow
if err := rows.Scan(&i.GenreName, &i.FilePath); err != nil {
var i GetGenreNamesByFilePathsRow
if err := rows.Scan(&i.FilePath, &i.Name); err != nil {
return nil, err
}
items = append(items, i)
@@ -201,51 +153,24 @@ func (q *Queries) GetFilePathsByGenres(ctx context.Context, genreNames []string)
return items, nil
}
const getFilePathsByGenresByLibrary = `-- name: GetFilePathsByGenresByLibrary :many
SELECT g.name AS genre_name, af.file_path
FROM genres g
JOIN recording_genres rg ON g.id = rg.genre_id
JOIN recordings r ON rg.recording_id = r.id
JOIN audio_files af ON af.recording_id = r.id
WHERE g.name IN (/*SLICE:genre_names*/?)
AND af.library_id = ?
ORDER BY r.name
const getUnusedGenreIDs = `-- name: GetUnusedGenreIDs :many
SELECT id FROM genres g
WHERE NOT EXISTS (SELECT 1 FROM file_genres fg WHERE fg.genre_id = g.id)
`
type GetFilePathsByGenresByLibraryParams struct {
GenreNames []string
LibraryID int64
}
type GetFilePathsByGenresByLibraryRow struct {
GenreName string
FilePath string
}
func (q *Queries) GetFilePathsByGenresByLibrary(ctx context.Context, arg GetFilePathsByGenresByLibraryParams) ([]GetFilePathsByGenresByLibraryRow, error) {
query := getFilePathsByGenresByLibrary
var queryParams []interface{}
if len(arg.GenreNames) > 0 {
for _, v := range arg.GenreNames {
queryParams = append(queryParams, v)
}
query = strings.Replace(query, "/*SLICE:genre_names*/?", strings.Repeat(",?", len(arg.GenreNames))[1:], 1)
} else {
query = strings.Replace(query, "/*SLICE:genre_names*/?", "NULL", 1)
}
queryParams = append(queryParams, arg.LibraryID)
rows, err := q.db.QueryContext(ctx, query, queryParams...)
func (q *Queries) GetUnusedGenreIDs(ctx context.Context) ([]int64, error) {
rows, err := q.db.QueryContext(ctx, getUnusedGenreIDs)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetFilePathsByGenresByLibraryRow
var items []int64
for rows.Next() {
var i GetFilePathsByGenresByLibraryRow
if err := rows.Scan(&i.GenreName, &i.FilePath); err != nil {
var id int64
if err := rows.Scan(&id); err != nil {
return nil, err
}
items = append(items, i)
items = append(items, id)
}
if err := rows.Close(); err != nil {
return nil, err
@@ -256,247 +181,32 @@ func (q *Queries) GetFilePathsByGenresByLibrary(ctx context.Context, arg GetFile
return items, nil
}
const getGenresByRecordingID = `-- name: GetGenresByRecordingID :many
SELECT g.id, g.name
FROM genres g
JOIN recording_genres rg ON g.id = rg.genre_id
WHERE rg.recording_id = ?
const linkFileGenre = `-- name: LinkFileGenre :exec
INSERT OR IGNORE INTO file_genres (audio_file_id, genre_id) VALUES (?, ?)
`
func (q *Queries) GetGenresByRecordingID(ctx context.Context, recordingID int64) ([]Genre, error) {
rows, err := q.db.QueryContext(ctx, getGenresByRecordingID, recordingID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Genre
for rows.Next() {
var i Genre
if err := rows.Scan(&i.ID, &i.Name); 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
type LinkFileGenreParams struct {
AudioFileID int64
GenreID int64
}
const getTracksByGenre = `-- name: GetTracksByGenre :many
SELECT
af.file_path,
af.length_milliseconds,
COALESCE(r.name, '') AS title,
COALESCE(ac.text, '') AS artist_name,
r.track_number,
r.disc_number,
COALESCE(rlg.name, '') AS album,
CAST(COALESCE(
(SELECT GROUP_CONCAT(g2.name, '||')
FROM recording_genres rg2
JOIN genres g2 ON rg2.genre_id = g2.id
WHERE rg2.recording_id = r.id),
''
) AS TEXT) AS genre,
COALESCE(r.year, 0) AS year,
COALESCE(r.composer, '') AS composer,
COALESCE(ft.extension, '') AS file_type,
af.sample_rate,
af.bit_depth,
af.channels,
af.bitrate,
af.file_size
FROM genres g
JOIN recording_genres rg ON g.id = rg.genre_id
JOIN recordings r ON rg.recording_id = r.id
JOIN audio_files af ON af.recording_id = r.id
JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN (
SELECT recording_id,
MIN(release_group_id) AS release_group_id
FROM release_group_recordings
GROUP BY recording_id
) rgr ON r.id = rgr.recording_id
LEFT JOIN release_groups rlg ON rgr.release_group_id = rlg.id
LEFT JOIN file_types ft ON af.file_type_id = ft.id
WHERE g.name = ?
ORDER BY r.name
`
type GetTracksByGenreRow struct {
FilePath string
LengthMilliseconds int64
Title string
ArtistName string
TrackNumber sql.NullInt64
DiscNumber sql.NullInt64
Album string
Genre string
Year int64
Composer string
FileType string
SampleRate int64
BitDepth int64
Channels int64
Bitrate int64
FileSize int64
}
func (q *Queries) GetTracksByGenre(ctx context.Context, name string) ([]GetTracksByGenreRow, error) {
rows, err := q.db.QueryContext(ctx, getTracksByGenre, name)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetTracksByGenreRow
for rows.Next() {
var i GetTracksByGenreRow
if err := rows.Scan(
&i.FilePath,
&i.LengthMilliseconds,
&i.Title,
&i.ArtistName,
&i.TrackNumber,
&i.DiscNumber,
&i.Album,
&i.Genre,
&i.Year,
&i.Composer,
&i.FileType,
&i.SampleRate,
&i.BitDepth,
&i.Channels,
&i.Bitrate,
&i.FileSize,
); 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 getTracksByGenreByLibrary = `-- name: GetTracksByGenreByLibrary :many
SELECT
af.file_path,
af.length_milliseconds,
COALESCE(r.name, '') AS title,
COALESCE(ac.text, '') AS artist_name,
r.track_number,
r.disc_number,
COALESCE(rlg.name, '') AS album,
CAST(COALESCE(
(SELECT GROUP_CONCAT(g2.name, '||')
FROM recording_genres rg2
JOIN genres g2 ON rg2.genre_id = g2.id
WHERE rg2.recording_id = r.id),
''
) AS TEXT) AS genre,
COALESCE(r.year, 0) AS year,
COALESCE(r.composer, '') AS composer,
COALESCE(ft.extension, '') AS file_type,
af.sample_rate,
af.bit_depth,
af.channels,
af.bitrate,
af.file_size
FROM genres g
JOIN recording_genres rg ON g.id = rg.genre_id
JOIN recordings r ON rg.recording_id = r.id
JOIN audio_files af ON af.recording_id = r.id
JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN (
SELECT recording_id,
MIN(release_group_id) AS release_group_id
FROM release_group_recordings
GROUP BY recording_id
) rgr ON r.id = rgr.recording_id
LEFT JOIN release_groups rlg ON rgr.release_group_id = rlg.id
LEFT JOIN file_types ft ON af.file_type_id = ft.id
WHERE g.name = ? AND af.library_id = ?
ORDER BY r.name
`
type GetTracksByGenreByLibraryParams struct {
Name string
LibraryID int64
}
type GetTracksByGenreByLibraryRow struct {
FilePath string
LengthMilliseconds int64
Title string
ArtistName string
TrackNumber sql.NullInt64
DiscNumber sql.NullInt64
Album string
Genre string
Year int64
Composer string
FileType string
SampleRate int64
BitDepth int64
Channels int64
Bitrate int64
FileSize int64
}
func (q *Queries) GetTracksByGenreByLibrary(ctx context.Context, arg GetTracksByGenreByLibraryParams) ([]GetTracksByGenreByLibraryRow, error) {
rows, err := q.db.QueryContext(ctx, getTracksByGenreByLibrary, arg.Name, arg.LibraryID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetTracksByGenreByLibraryRow
for rows.Next() {
var i GetTracksByGenreByLibraryRow
if err := rows.Scan(
&i.FilePath,
&i.LengthMilliseconds,
&i.Title,
&i.ArtistName,
&i.TrackNumber,
&i.DiscNumber,
&i.Album,
&i.Genre,
&i.Year,
&i.Composer,
&i.FileType,
&i.SampleRate,
&i.BitDepth,
&i.Channels,
&i.Bitrate,
&i.FileSize,
); 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
func (q *Queries) LinkFileGenre(ctx context.Context, arg LinkFileGenreParams) error {
_, err := q.db.ExecContext(ctx, linkFileGenre, arg.AudioFileID, arg.GenreID)
return err
}
const upsertGenre = `-- name: UpsertGenre :one
INSERT INTO genres (name) VALUES (?)
ON CONFLICT(name) DO UPDATE SET name = name
ON CONFLICT(name) DO UPDATE SET name = excluded.name
RETURNING id, name
`
// Queries over genres and file_genres.
//
// The track-returning ones live in audio_files.sql with the rest of the
// track_metadata reads; what is left here is the genre list itself and
// the link table's writes.
func (q *Queries) UpsertGenre(ctx context.Context, name string) (Genre, error) {
row := q.db.QueryRowContext(ctx, upsertGenre, name)
var i Genre
+25 -33
View File
@@ -12,10 +12,10 @@ import (
const homeAlbumsByGenre = `-- name: HomeAlbumsByGenre :many
SELECT rg.id AS album_id
FROM release_groups rg
JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
JOIN recording_genres rgen ON rgen.recording_id = rgr.recording_id
JOIN genres g ON g.id = rgen.genre_id
FROM albums rg
JOIN audio_files af ON af.album_id = rg.id
JOIN file_genres fg ON fg.audio_file_id = af.id
JOIN genres g ON g.id = fg.genre_id
WHERE g.name = ?
GROUP BY rg.id
ORDER BY RANDOM()
@@ -54,9 +54,8 @@ func (q *Queries) HomeAlbumsByGenre(ctx context.Context, arg HomeAlbumsByGenrePa
const homeMostPlayedAlbums = `-- name: HomeMostPlayedAlbums :many
SELECT rg.id AS album_id
FROM release_groups rg
JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
JOIN audio_files af ON af.recording_id = rgr.recording_id
FROM albums rg
JOIN audio_files af ON af.album_id = rg.id
GROUP BY rg.id
HAVING SUM(af.play_count) > 0
ORDER BY SUM(af.play_count) DESC
@@ -89,9 +88,8 @@ func (q *Queries) HomeMostPlayedAlbums(ctx context.Context, limit int64) ([]int6
const homeRandomAlbums = `-- name: HomeRandomAlbums :many
SELECT rg.id AS album_id
FROM release_groups rg
JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
JOIN audio_files af ON af.recording_id = rgr.recording_id
FROM albums rg
JOIN audio_files af ON af.album_id = rg.id
GROUP BY rg.id
ORDER BY RANDOM()
LIMIT ?
@@ -122,9 +120,8 @@ func (q *Queries) HomeRandomAlbums(ctx context.Context, limit int64) ([]int64, e
const homeRecentlyAddedAlbums = `-- name: HomeRecentlyAddedAlbums :many
SELECT rg.id AS album_id
FROM release_groups rg
JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
JOIN audio_files af ON af.recording_id = rgr.recording_id
FROM albums rg
JOIN audio_files af ON af.album_id = rg.id
GROUP BY rg.id
ORDER BY MAX(af.id) DESC
LIMIT ?
@@ -159,9 +156,8 @@ func (q *Queries) HomeRecentlyAddedAlbums(ctx context.Context, limit int64) ([]i
const homeRecentlyPlayedAlbums = `-- name: HomeRecentlyPlayedAlbums :many
SELECT rg.id AS album_id
FROM release_groups rg
JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
JOIN audio_files af ON af.recording_id = rgr.recording_id
FROM albums rg
JOIN audio_files af ON af.album_id = rg.id
WHERE af.last_played IS NOT NULL
GROUP BY rg.id
ORDER BY MAX(af.last_played) DESC
@@ -172,7 +168,7 @@ LIMIT ?
//
// Every one of these returns album ids and nothing else. The display
// columns (cover art, artist credit, year) already have exactly one
// correct expression of them, in GetAllAlbumsWithDetails, and a second
// correct expression of them, in GetAlbums, and a second
// copy per shelf would be six more places for that to drift. The home
// service joins the ids back to that one album list in Go.
// Albums with the most recent play, newest first.
@@ -201,9 +197,8 @@ func (q *Queries) HomeRecentlyPlayedAlbums(ctx context.Context, limit int64) ([]
const homeStaleAlbums = `-- name: HomeStaleAlbums :many
SELECT rg.id AS album_id
FROM release_groups rg
JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
JOIN audio_files af ON af.recording_id = rgr.recording_id
FROM albums rg
JOIN audio_files af ON af.album_id = rg.id
WHERE af.last_played IS NOT NULL
GROUP BY rg.id
HAVING MAX(af.last_played) < datetime('now', ?)
@@ -242,14 +237,12 @@ func (q *Queries) HomeStaleAlbums(ctx context.Context, arg HomeStaleAlbumsParams
const homeTopArtists = `-- name: HomeTopArtists :many
SELECT
COALESCE(ac.text, '') AS artist_name,
rg.artist_credit AS artist_name,
SUM(af.play_count) AS plays
FROM release_groups rg
JOIN artist_credit ac ON ac.id = rg.album_artist_credit_id
JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
JOIN audio_files af ON af.recording_id = rgr.recording_id
WHERE ac.text <> ''
GROUP BY ac.text
FROM albums rg
JOIN audio_files af ON af.album_id = rg.id
WHERE rg.artist_credit <> ''
GROUP BY rg.artist_credit
HAVING plays > 0
ORDER BY plays DESC
LIMIT ?
@@ -288,10 +281,10 @@ func (q *Queries) HomeTopArtists(ctx context.Context, limit int64) ([]HomeTopArt
const homeTopGenres = `-- name: HomeTopGenres :many
SELECT
g.name AS genre,
COUNT(DISTINCT rgr.release_group_id) AS album_count
COUNT(DISTINCT af.album_id) AS album_count
FROM genres g
JOIN recording_genres rgen ON rgen.genre_id = g.id
JOIN release_group_recordings rgr ON rgr.recording_id = rgen.recording_id
JOIN file_genres fg ON fg.genre_id = g.id
JOIN audio_files af ON af.id = fg.audio_file_id
GROUP BY g.id
HAVING album_count >= 3
ORDER BY album_count DESC
@@ -331,9 +324,8 @@ func (q *Queries) HomeTopGenres(ctx context.Context, limit int64) ([]HomeTopGenr
const homeUnplayedAlbums = `-- name: HomeUnplayedAlbums :many
SELECT rg.id AS album_id
FROM release_groups rg
JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
JOIN audio_files af ON af.recording_id = rgr.recording_id
FROM albums rg
JOIN audio_files af ON af.album_id = rg.id
GROUP BY rg.id
HAVING SUM(af.play_count) = 0
ORDER BY RANDOM()
-103
View File
@@ -1,103 +0,0 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: mix.sql
package sqlcgen
import (
"context"
"database/sql"
)
const getArtistByFilePath = `-- name: GetArtistByFilePath :one
SELECT COALESCE(a.name, '') AS artist_name, COALESCE(a.mbid, '') AS artist_mbid
FROM audio_files af
JOIN recordings r ON af.recording_id = r.id
JOIN artist_credit ac ON r.artist_credit_id = ac.id
JOIN artist_credit_artist aca ON aca.credit_id = ac.id
JOIN artists a ON a.id = aca.artist_id
WHERE af.file_path = ?
LIMIT 1
`
type GetArtistByFilePathRow struct {
ArtistName string
ArtistMbid string
}
func (q *Queries) GetArtistByFilePath(ctx context.Context, filePath string) (GetArtistByFilePathRow, error) {
row := q.db.QueryRowContext(ctx, getArtistByFilePath, filePath)
var i GetArtistByFilePathRow
err := row.Scan(&i.ArtistName, &i.ArtistMbid)
return i, err
}
const getFilePathsByArtistMBID = `-- name: GetFilePathsByArtistMBID :many
SELECT DISTINCT af.file_path
FROM audio_files af
JOIN recordings r ON af.recording_id = r.id
JOIN artist_credit ac ON r.artist_credit_id = ac.id
JOIN artist_credit_artist aca ON aca.credit_id = ac.id
JOIN artists a ON a.id = aca.artist_id
WHERE a.mbid = ?
`
// Queries backing the dynamic-mix queue fallback (backend/explore/mix.go):
// expanding a seed selection into a candidate pool by artist similarity
// and genre overlap, restricted to what is actually in the library.
func (q *Queries) GetFilePathsByArtistMBID(ctx context.Context, mbid sql.NullString) ([]string, error) {
rows, err := q.db.QueryContext(ctx, getFilePathsByArtistMBID, mbid)
if err != nil {
return nil, err
}
defer rows.Close()
var items []string
for rows.Next() {
var file_path string
if err := rows.Scan(&file_path); err != nil {
return nil, err
}
items = append(items, file_path)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getGenreNamesByFilePath = `-- name: GetGenreNamesByFilePath :many
SELECT DISTINCT g.name
FROM genres g
JOIN recording_genres rg ON g.id = rg.genre_id
JOIN recordings r ON rg.recording_id = r.id
JOIN audio_files af ON af.recording_id = r.id
WHERE af.file_path = ?
`
func (q *Queries) GetGenreNamesByFilePath(ctx context.Context, filePath string) ([]string, error) {
rows, err := q.db.QueryContext(ctx, getGenreNamesByFilePath, filePath)
if err != nil {
return nil, err
}
defer rows.Close()
var items []string
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return nil, err
}
items = append(items, name)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
+47 -62
View File
@@ -9,23 +9,24 @@ import (
"time"
)
type Album struct {
ID int64
Name string
ArtistCredit string
ArtistID sql.NullInt64
Mbid sql.NullString
Year sql.NullInt64
OriginalYear sql.NullInt64
CoverArtID sql.NullInt64
PendingReleaseMbid sql.NullString
}
type Artist struct {
ID int64
Name string
Mbid sql.NullString
}
type ArtistCredit struct {
ID int64
Text string
}
type ArtistCreditArtist struct {
ID int64
ArtistID int64
CreditID int64
}
type ArtistEnrichment struct {
ArtistMbid string
BrowsedAt sql.NullTime
@@ -56,21 +57,31 @@ type ArtistMetadatum struct {
type AudioFile struct {
ID int64
FilePath string
LengthMilliseconds int64
LibraryID int64
FileTypeID int64
RecordingID int64
LengthMilliseconds int64
SampleRate int64
BitDepth int64
Channels int64
Bitrate int64
FileSize int64
Title string
ArtistCredit string
ArtistID sql.NullInt64
AlbumID sql.NullInt64
TrackNumber sql.NullInt64
DiscNumber sql.NullInt64
TotalTracks sql.NullInt64
Year sql.NullInt64
Composer string
Comment string
RecordingMbid sql.NullString
Basename string
LibraryID int64
GroupKey string
ModifiedAt int64
PlayCount int64
LastPlayed sql.NullTime
TagStatus string
GroupKey string
ModifiedAt int64
}
type CoverArt struct {
@@ -160,20 +171,21 @@ type ExploreChampionFt struct {
type ExploreIndex struct {
ID int64
EntityType string
Mbid string
EntityType int64
Mbid []byte
Title string
ArtistName string
ArtistMbid string
ArtistMbid []byte
Aliases string
Popularity int64
ListenerCount int64
Duration int64
CaaReleaseMbid string
CaaReleaseMbid []byte
ReleaseName string
PrimaryType string
SecondaryTypes string
ReleaseDate string
TotalTracks int64
ArtistType string
Country string
Disambiguation string
@@ -197,6 +209,11 @@ type ExploreIndexMetum struct {
Value string
}
type FileGenre struct {
AudioFileID int64
GenreID int64
}
type FileType struct {
ID int64
Extension string
@@ -231,6 +248,14 @@ type Library struct {
AutotagWarningAcked int64
}
type Lyric struct {
AudioFileID int64
Text string
Source string
RecordingMbid sql.NullString
FetchedAt time.Time
}
type LyricsIndex struct {
Lyrics string
}
@@ -291,48 +316,6 @@ type QueueTrack struct {
Position int64
}
type Recording struct {
ID int64
Name string
ArtistCreditID int64
TrackNumber sql.NullInt64
DiscNumber sql.NullInt64
Year sql.NullInt64
Genre sql.NullString
Composer sql.NullString
Lyrics sql.NullString
Comment sql.NullString
Mbid sql.NullString
}
type RecordingGenre struct {
ID int64
RecordingID int64
GenreID int64
}
type ReleaseGroup struct {
ID int64
Name string
CoverArtID sql.NullInt64
AlbumArtistCreditID sql.NullInt64
Year sql.NullInt64
TotalTracks sql.NullInt64
TotalDiscs sql.NullInt64
Mbid sql.NullString
OriginalYear sql.NullInt64
PendingReleaseMbid sql.NullString
}
type ReleaseGroupRecording struct {
ID int64
ReleaseGroupID int64
RecordingID int64
TrackNumber sql.NullInt64
DiscNumber sql.NullInt64
TotalTracks sql.NullInt64
}
type ReleaseToRg struct {
ReleaseMbid string
RgMbid string
@@ -410,4 +393,6 @@ type TrackMetadatum struct {
ArtistMbid string
ReleaseGroupMbid string
RecordingMbid string
AlbumID sql.NullInt64
ArtistID sql.NullInt64
}
+25 -66
View File
@@ -124,29 +124,18 @@ SELECT
pt.playlist_id,
pt.audio_file_id,
pt.position,
COALESCE(af.file_path, '') AS file_path,
COALESCE(af.length_milliseconds, 0) AS length_milliseconds,
COALESCE(r.name, pt.phantom_title, '') AS title,
COALESCE(ac.text, pt.phantom_artist, '') AS artist,
COALESCE(rg.name, pt.phantom_album, '') AS album,
COALESCE(ca.file_path, pt.phantom_cover_art_path, '') AS cover_art_path,
COALESCE(tm.file_path, '') AS file_path,
COALESCE(tm.length_milliseconds, 0) AS length_milliseconds,
COALESCE(tm.title, pt.phantom_title, '') AS title,
COALESCE(tm.artist_name, pt.phantom_artist, '') AS artist,
COALESCE(tm.album, pt.phantom_album, '') AS album,
COALESCE(NULLIF(tm.cover_art_path, ''), pt.phantom_cover_art_path, '') AS cover_art_path,
CASE WHEN pt.audio_file_id IS NULL THEN 1 ELSE 0 END AS is_phantom,
COALESCE(a.mbid, '') AS artist_mbid,
COALESCE(rg.mbid, '') AS release_group_mbid,
COALESCE(r.mbid, '') AS recording_mbid
CAST(COALESCE(tm.artist_mbid, '') AS TEXT) AS artist_mbid,
COALESCE(tm.release_group_mbid, '') AS release_group_mbid,
COALESCE(tm.recording_mbid, '') AS recording_mbid
FROM playlist_tracks pt
LEFT JOIN audio_files af ON pt.audio_file_id = af.id
LEFT JOIN recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN artist_credit_artist aca ON aca.credit_id = ac.id
LEFT JOIN artists a ON a.id = aca.artist_id
LEFT JOIN (
SELECT recording_id, MIN(release_group_id) AS release_group_id
FROM release_group_recordings
GROUP BY recording_id
) rgr ON r.id = rgr.recording_id
LEFT JOIN release_groups rg ON rgr.release_group_id = rg.id
LEFT JOIN cover_art ca ON rg.cover_art_id = ca.id
LEFT JOIN track_metadata tm ON tm.id = pt.audio_file_id
ORDER BY pt.playlist_id, pt.position
`
@@ -368,29 +357,18 @@ SELECT
pt.playlist_id,
pt.audio_file_id,
pt.position,
COALESCE(af.file_path, '') AS file_path,
COALESCE(af.length_milliseconds, 0) AS length_milliseconds,
COALESCE(r.name, pt.phantom_title, '') AS title,
COALESCE(ac.text, pt.phantom_artist, '') AS artist,
COALESCE(rg.name, pt.phantom_album, '') AS album,
COALESCE(ca.file_path, pt.phantom_cover_art_path, '') AS cover_art_path,
COALESCE(tm.file_path, '') AS file_path,
COALESCE(tm.length_milliseconds, 0) AS length_milliseconds,
COALESCE(tm.title, pt.phantom_title, '') AS title,
COALESCE(tm.artist_name, pt.phantom_artist, '') AS artist,
COALESCE(tm.album, pt.phantom_album, '') AS album,
COALESCE(NULLIF(tm.cover_art_path, ''), pt.phantom_cover_art_path, '') AS cover_art_path,
CASE WHEN pt.audio_file_id IS NULL THEN 1 ELSE 0 END AS is_phantom,
COALESCE(a.mbid, '') AS artist_mbid,
COALESCE(rg.mbid, '') AS release_group_mbid,
COALESCE(r.mbid, '') AS recording_mbid
CAST(COALESCE(tm.artist_mbid, '') AS TEXT) AS artist_mbid,
COALESCE(tm.release_group_mbid, '') AS release_group_mbid,
COALESCE(tm.recording_mbid, '') AS recording_mbid
FROM playlist_tracks pt
LEFT JOIN audio_files af ON pt.audio_file_id = af.id
LEFT JOIN recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN artist_credit_artist aca ON aca.credit_id = ac.id
LEFT JOIN artists a ON a.id = aca.artist_id
LEFT JOIN (
SELECT recording_id, MIN(release_group_id) AS release_group_id
FROM release_group_recordings
GROUP BY recording_id
) rgr ON r.id = rgr.recording_id
LEFT JOIN release_groups rg ON rgr.release_group_id = rg.id
LEFT JOIN cover_art ca ON rg.cover_art_id = ca.id
LEFT JOIN track_metadata tm ON tm.id = pt.audio_file_id
WHERE pt.playlist_id = ?
ORDER BY pt.position
`
@@ -451,30 +429,10 @@ func (q *Queries) GetPlaylistTracksWithMetadata(ctx context.Context, playlistID
}
const getTrackPhantomMetadata = `-- name: GetTrackPhantomMetadata :one
SELECT
COALESCE(r.name, '') AS title,
COALESCE(ac.text, '') AS artist,
COALESCE(rg.name, '') AS album,
af.length_milliseconds AS duration_ms,
CAST(COALESCE(
(SELECT GROUP_CONCAT(g.name, '||')
FROM recording_genres rg_sub
JOIN genres g ON rg_sub.genre_id = g.id
WHERE rg_sub.recording_id = r.id),
''
) AS TEXT) AS genre,
COALESCE(ca.file_path, '') AS cover_art_path
FROM audio_files af
LEFT JOIN recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN (
SELECT recording_id, MIN(release_group_id) AS release_group_id
FROM release_group_recordings
GROUP BY recording_id
) rgr ON r.id = rgr.recording_id
LEFT JOIN release_groups rg ON rgr.release_group_id = rg.id
LEFT JOIN cover_art ca ON rg.cover_art_id = ca.id
WHERE af.id = ?
SELECT title, artist_name AS artist, album,
length_milliseconds AS duration_ms, genre, cover_art_path
FROM track_metadata
WHERE id = ?
`
type GetTrackPhantomMetadataRow struct {
@@ -486,6 +444,7 @@ type GetTrackPhantomMetadataRow struct {
CoverArtPath string
}
// The display fields a playlist row keeps after its file goes away.
func (q *Queries) GetTrackPhantomMetadata(ctx context.Context, id int64) (GetTrackPhantomMetadataRow, error) {
row := q.db.QueryRowContext(ctx, getTrackPhantomMetadata, id)
var i GetTrackPhantomMetadataRow
+5 -20
View File
@@ -61,27 +61,11 @@ func (q *Queries) GetQueueTrackCount(ctx context.Context) (int64, error) {
}
const getQueueTracks = `-- name: GetQueueTracks :many
SELECT qt.id, qt.audio_file_id, qt.position, af.file_path,
COALESCE(r.name, '') AS title,
COALESCE(ac.text, '') AS artist,
COALESCE(rg.name, '') AS album,
COALESCE(ca.file_path, '') AS cover_art_path,
COALESCE(a.mbid, '') AS artist_mbid,
COALESCE(rg.mbid, '') AS release_group_mbid,
COALESCE(r.mbid, '') AS recording_mbid
SELECT qt.id, qt.audio_file_id, qt.position, tm.file_path,
tm.title, tm.artist_name AS artist, tm.album, tm.cover_art_path,
tm.artist_mbid, tm.release_group_mbid, tm.recording_mbid
FROM queue_tracks qt
JOIN audio_files af ON qt.audio_file_id = af.id
LEFT JOIN recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN artist_credit_artist aca ON aca.credit_id = ac.id
LEFT JOIN artists a ON a.id = aca.artist_id
LEFT JOIN (
SELECT recording_id, MIN(release_group_id) AS release_group_id
FROM release_group_recordings
GROUP BY recording_id
) rgr ON r.id = rgr.recording_id
LEFT JOIN release_groups rg ON rgr.release_group_id = rg.id
LEFT JOIN cover_art ca ON rg.cover_art_id = ca.id
JOIN track_metadata tm ON tm.id = qt.audio_file_id
ORDER BY qt.position
`
@@ -99,6 +83,7 @@ type GetQueueTracksRow struct {
RecordingMbid string
}
// The queue's rows, joined to the one track projection.
func (q *Queries) GetQueueTracks(ctx context.Context) ([]GetQueueTracksRow, error) {
rows, err := q.db.QueryContext(ctx, getQueueTracks)
if err != nil {
@@ -1,268 +0,0 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: recordings.sql
package sqlcgen
import (
"context"
"database/sql"
)
const countRecordingsByArtistCredit = `-- name: CountRecordingsByArtistCredit :one
SELECT COUNT(*) FROM recordings WHERE artist_credit_id = ?
`
func (q *Queries) CountRecordingsByArtistCredit(ctx context.Context, artistCreditID int64) (int64, error) {
row := q.db.QueryRowContext(ctx, countRecordingsByArtistCredit, artistCreditID)
var count int64
err := row.Scan(&count)
return count, err
}
const createRecording = `-- name: CreateRecording :one
INSERT INTO recordings (name, artist_credit_id) VALUES (?, ?)
RETURNING id, name, artist_credit_id, track_number, disc_number, year, genre, composer, lyrics, comment, mbid
`
type CreateRecordingParams struct {
Name string
ArtistCreditID int64
}
func (q *Queries) CreateRecording(ctx context.Context, arg CreateRecordingParams) (Recording, error) {
row := q.db.QueryRowContext(ctx, createRecording, arg.Name, arg.ArtistCreditID)
var i Recording
err := row.Scan(
&i.ID,
&i.Name,
&i.ArtistCreditID,
&i.TrackNumber,
&i.DiscNumber,
&i.Year,
&i.Genre,
&i.Composer,
&i.Lyrics,
&i.Comment,
&i.Mbid,
)
return i, err
}
const createRecordingFull = `-- name: CreateRecordingFull :one
INSERT INTO recordings (
name, artist_credit_id, track_number, disc_number,
year, genre, composer, lyrics, comment
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id, name, artist_credit_id, track_number, disc_number, year, genre, composer, lyrics, comment, mbid
`
type CreateRecordingFullParams struct {
Name string
ArtistCreditID int64
TrackNumber sql.NullInt64
DiscNumber sql.NullInt64
Year sql.NullInt64
Genre sql.NullString
Composer sql.NullString
Lyrics sql.NullString
Comment sql.NullString
}
func (q *Queries) CreateRecordingFull(ctx context.Context, arg CreateRecordingFullParams) (Recording, error) {
row := q.db.QueryRowContext(ctx, createRecordingFull,
arg.Name,
arg.ArtistCreditID,
arg.TrackNumber,
arg.DiscNumber,
arg.Year,
arg.Genre,
arg.Composer,
arg.Lyrics,
arg.Comment,
)
var i Recording
err := row.Scan(
&i.ID,
&i.Name,
&i.ArtistCreditID,
&i.TrackNumber,
&i.DiscNumber,
&i.Year,
&i.Genre,
&i.Composer,
&i.Lyrics,
&i.Comment,
&i.Mbid,
)
return i, err
}
const deleteAllRecordings = `-- name: DeleteAllRecordings :exec
DELETE FROM recordings
`
func (q *Queries) DeleteAllRecordings(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteAllRecordings)
return err
}
const deleteRecording = `-- name: DeleteRecording :exec
DELETE FROM recordings
WHERE id = ?
`
func (q *Queries) DeleteRecording(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteRecording, id)
return err
}
const getAllRecordings = `-- name: GetAllRecordings :many
SELECT id, name, artist_credit_id, track_number, disc_number, year, genre, composer, lyrics, comment, mbid FROM recordings
ORDER BY name
`
func (q *Queries) GetAllRecordings(ctx context.Context) ([]Recording, error) {
rows, err := q.db.QueryContext(ctx, getAllRecordings)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Recording
for rows.Next() {
var i Recording
if err := rows.Scan(
&i.ID,
&i.Name,
&i.ArtistCreditID,
&i.TrackNumber,
&i.DiscNumber,
&i.Year,
&i.Genre,
&i.Composer,
&i.Lyrics,
&i.Comment,
&i.Mbid,
); 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 getOrphanedRecordingIDs = `-- name: GetOrphanedRecordingIDs :many
SELECT r.id FROM recordings r
LEFT JOIN audio_files af ON af.recording_id = r.id
WHERE af.id IS NULL
`
// Recordings no longer backed by any audio_files row - left behind
// when a scan's orphan cleanup deletes the file that used to own them,
// since deleting audio_files doesn't cascade to recordings.
func (q *Queries) GetOrphanedRecordingIDs(ctx context.Context) ([]int64, error) {
rows, err := q.db.QueryContext(ctx, getOrphanedRecordingIDs)
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 getRecording = `-- name: GetRecording :one
SELECT id, name, artist_credit_id, track_number, disc_number, year, genre, composer, lyrics, comment, mbid FROM recordings
WHERE id = ? LIMIT 1
`
func (q *Queries) GetRecording(ctx context.Context, id int64) (Recording, error) {
row := q.db.QueryRowContext(ctx, getRecording, id)
var i Recording
err := row.Scan(
&i.ID,
&i.Name,
&i.ArtistCreditID,
&i.TrackNumber,
&i.DiscNumber,
&i.Year,
&i.Genre,
&i.Composer,
&i.Lyrics,
&i.Comment,
&i.Mbid,
)
return i, err
}
const updateRecording = `-- name: UpdateRecording :exec
UPDATE recordings
SET name = ?, artist_credit_id = ?
WHERE id = ?
`
type UpdateRecordingParams struct {
Name string
ArtistCreditID int64
ID int64
}
func (q *Queries) UpdateRecording(ctx context.Context, arg UpdateRecordingParams) error {
_, err := q.db.ExecContext(ctx, updateRecording, arg.Name, arg.ArtistCreditID, arg.ID)
return err
}
const updateRecordingFull = `-- name: UpdateRecordingFull :exec
UPDATE recordings
SET name = ?, artist_credit_id = ?, track_number = ?, disc_number = ?,
year = ?, genre = ?, composer = ?, lyrics = ?, comment = ?
WHERE id = ?
`
type UpdateRecordingFullParams struct {
Name string
ArtistCreditID int64
TrackNumber sql.NullInt64
DiscNumber sql.NullInt64
Year sql.NullInt64
Genre sql.NullString
Composer sql.NullString
Lyrics sql.NullString
Comment sql.NullString
ID int64
}
func (q *Queries) UpdateRecordingFull(ctx context.Context, arg UpdateRecordingFullParams) error {
_, err := q.db.ExecContext(ctx, updateRecordingFull,
arg.Name,
arg.ArtistCreditID,
arg.TrackNumber,
arg.DiscNumber,
arg.Year,
arg.Genre,
arg.Composer,
arg.Lyrics,
arg.Comment,
arg.ID,
)
return err
}
@@ -1,211 +0,0 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: release_group_recordings.sql
package sqlcgen
import (
"context"
"database/sql"
)
const createReleaseGroupRecording = `-- name: CreateReleaseGroupRecording :one
INSERT INTO release_group_recordings (
release_group_id, recording_id, track_number, disc_number, total_tracks
)
VALUES (?, ?, ?, ?, ?)
RETURNING id, release_group_id, recording_id, track_number, disc_number, total_tracks
`
type CreateReleaseGroupRecordingParams struct {
ReleaseGroupID int64
RecordingID int64
TrackNumber sql.NullInt64
DiscNumber sql.NullInt64
TotalTracks sql.NullInt64
}
func (q *Queries) CreateReleaseGroupRecording(ctx context.Context, arg CreateReleaseGroupRecordingParams) (ReleaseGroupRecording, error) {
row := q.db.QueryRowContext(ctx, createReleaseGroupRecording,
arg.ReleaseGroupID,
arg.RecordingID,
arg.TrackNumber,
arg.DiscNumber,
arg.TotalTracks,
)
var i ReleaseGroupRecording
err := row.Scan(
&i.ID,
&i.ReleaseGroupID,
&i.RecordingID,
&i.TrackNumber,
&i.DiscNumber,
&i.TotalTracks,
)
return i, err
}
const deleteAllReleaseGroupRecordings = `-- name: DeleteAllReleaseGroupRecordings :exec
DELETE FROM release_group_recordings
`
func (q *Queries) DeleteAllReleaseGroupRecordings(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteAllReleaseGroupRecordings)
return err
}
const deleteReleaseGroupRecording = `-- name: DeleteReleaseGroupRecording :exec
DELETE FROM release_group_recordings
WHERE id = ?
`
func (q *Queries) DeleteReleaseGroupRecording(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteReleaseGroupRecording, id)
return err
}
const deleteReleaseGroupRecordingByFK = `-- name: DeleteReleaseGroupRecordingByFK :exec
DELETE FROM release_group_recordings
WHERE release_group_id = ? AND recording_id = ?
`
type DeleteReleaseGroupRecordingByFKParams struct {
ReleaseGroupID int64
RecordingID int64
}
func (q *Queries) DeleteReleaseGroupRecordingByFK(ctx context.Context, arg DeleteReleaseGroupRecordingByFKParams) error {
_, err := q.db.ExecContext(ctx, deleteReleaseGroupRecordingByFK, arg.ReleaseGroupID, arg.RecordingID)
return err
}
const deleteReleaseGroupRecordingsByRecording = `-- name: DeleteReleaseGroupRecordingsByRecording :exec
DELETE FROM release_group_recordings
WHERE recording_id = ?
`
func (q *Queries) DeleteReleaseGroupRecordingsByRecording(ctx context.Context, recordingID int64) error {
_, err := q.db.ExecContext(ctx, deleteReleaseGroupRecordingsByRecording, recordingID)
return err
}
const getAlbumCompleteness = `-- name: GetAlbumCompleteness :one
WITH discs AS (
SELECT
COALESCE(rgr.disc_number, 1) AS disc,
MAX(COALESCE(rgr.total_tracks, 0)) AS declared,
COUNT(DISTINCT COALESCE(rgr.track_number, -rgr.recording_id)) AS owned
FROM release_group_recordings rgr
WHERE rgr.release_group_id = ?
GROUP BY COALESCE(rgr.disc_number, 1)
)
SELECT
CAST(COALESCE(SUM(owned), 0) AS INTEGER) AS owned,
CAST(COALESCE(SUM(declared), 0) AS INTEGER) AS expected,
CAST(COALESCE(SUM(CASE WHEN declared = 0 THEN 1 ELSE 0 END), 0) AS INTEGER) AS discs_untotalled
FROM discs
`
type GetAlbumCompletenessRow struct {
Owned int64
Expected int64
DiscsUntotalled int64
}
func (q *Queries) GetAlbumCompleteness(ctx context.Context, releaseGroupID int64) (GetAlbumCompletenessRow, error) {
row := q.db.QueryRowContext(ctx, getAlbumCompleteness, releaseGroupID)
var i GetAlbumCompletenessRow
err := row.Scan(&i.Owned, &i.Expected, &i.DiscsUntotalled)
return i, err
}
const getRecordingReleaseGroups = `-- name: GetRecordingReleaseGroups :many
SELECT id, release_group_id, recording_id, track_number, disc_number, total_tracks FROM release_group_recordings
WHERE recording_id = ?
`
func (q *Queries) GetRecordingReleaseGroups(ctx context.Context, recordingID int64) ([]ReleaseGroupRecording, error) {
rows, err := q.db.QueryContext(ctx, getRecordingReleaseGroups, recordingID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ReleaseGroupRecording
for rows.Next() {
var i ReleaseGroupRecording
if err := rows.Scan(
&i.ID,
&i.ReleaseGroupID,
&i.RecordingID,
&i.TrackNumber,
&i.DiscNumber,
&i.TotalTracks,
); 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 getReleaseGroupRecording = `-- name: GetReleaseGroupRecording :one
SELECT id, release_group_id, recording_id, track_number, disc_number, total_tracks FROM release_group_recordings
WHERE id = ? LIMIT 1
`
func (q *Queries) GetReleaseGroupRecording(ctx context.Context, id int64) (ReleaseGroupRecording, error) {
row := q.db.QueryRowContext(ctx, getReleaseGroupRecording, id)
var i ReleaseGroupRecording
err := row.Scan(
&i.ID,
&i.ReleaseGroupID,
&i.RecordingID,
&i.TrackNumber,
&i.DiscNumber,
&i.TotalTracks,
)
return i, err
}
const getReleaseGroupRecordings = `-- name: GetReleaseGroupRecordings :many
SELECT id, release_group_id, recording_id, track_number, disc_number, total_tracks FROM release_group_recordings
WHERE release_group_id = ?
ORDER BY disc_number, track_number
`
func (q *Queries) GetReleaseGroupRecordings(ctx context.Context, releaseGroupID int64) ([]ReleaseGroupRecording, error) {
rows, err := q.db.QueryContext(ctx, getReleaseGroupRecordings, releaseGroupID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ReleaseGroupRecording
for rows.Next() {
var i ReleaseGroupRecording
if err := rows.Scan(
&i.ID,
&i.ReleaseGroupID,
&i.RecordingID,
&i.TrackNumber,
&i.DiscNumber,
&i.TotalTracks,
); 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
}
@@ -1,639 +0,0 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: release_groups.sql
package sqlcgen
import (
"context"
"database/sql"
)
const countReleaseGroupRecordings = `-- name: CountReleaseGroupRecordings :one
SELECT COUNT(*) FROM release_group_recordings WHERE release_group_id = ?
`
func (q *Queries) CountReleaseGroupRecordings(ctx context.Context, releaseGroupID int64) (int64, error) {
row := q.db.QueryRowContext(ctx, countReleaseGroupRecordings, releaseGroupID)
var count int64
err := row.Scan(&count)
return count, err
}
const createReleaseGroup = `-- name: CreateReleaseGroup :one
INSERT INTO release_groups (name) VALUES (?)
RETURNING id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year, pending_release_mbid
`
func (q *Queries) CreateReleaseGroup(ctx context.Context, name string) (ReleaseGroup, error) {
row := q.db.QueryRowContext(ctx, createReleaseGroup, name)
var i ReleaseGroup
err := row.Scan(
&i.ID,
&i.Name,
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
&i.PendingReleaseMbid,
)
return i, err
}
const createReleaseGroupFull = `-- name: CreateReleaseGroupFull :one
INSERT INTO release_groups (
name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs
) VALUES (?, ?, ?, ?, ?, ?)
RETURNING id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year, pending_release_mbid
`
type CreateReleaseGroupFullParams struct {
Name string
CoverArtID sql.NullInt64
AlbumArtistCreditID sql.NullInt64
Year sql.NullInt64
TotalTracks sql.NullInt64
TotalDiscs sql.NullInt64
}
func (q *Queries) CreateReleaseGroupFull(ctx context.Context, arg CreateReleaseGroupFullParams) (ReleaseGroup, error) {
row := q.db.QueryRowContext(ctx, createReleaseGroupFull,
arg.Name,
arg.CoverArtID,
arg.AlbumArtistCreditID,
arg.Year,
arg.TotalTracks,
arg.TotalDiscs,
)
var i ReleaseGroup
err := row.Scan(
&i.ID,
&i.Name,
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
&i.PendingReleaseMbid,
)
return i, err
}
const deleteAllReleaseGroups = `-- name: DeleteAllReleaseGroups :exec
DELETE FROM release_groups
`
func (q *Queries) DeleteAllReleaseGroups(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteAllReleaseGroups)
return err
}
const deleteReleaseGroup = `-- name: DeleteReleaseGroup :exec
DELETE FROM release_groups
WHERE id = ?
`
func (q *Queries) DeleteReleaseGroup(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteReleaseGroup, id)
return err
}
const getAlbumsByArtist = `-- name: GetAlbumsByArtist :many
SELECT
rg.id,
rg.name,
COALESCE(rg.original_year, rg.year) AS year,
COALESCE(rg.year, 0) AS release_year,
COALESCE(ac.text, fallback_ac.text, '') as artist_name,
-- primary (first-credited) album artist's MBID, for linking the
-- artist name to its detail page. Empty when the album has no
-- MB-tagged album-artist credit.
CAST(COALESCE((
SELECT a.mbid
FROM artist_credit_artist aca_p
JOIN artists a ON a.id = aca_p.artist_id
WHERE aca_p.credit_id = rg.album_artist_credit_id
ORDER BY aca_p.id
LIMIT 1
), '') AS TEXT) as artist_mbid,
COALESCE(ca.file_path, '') as cover_art_path
FROM release_groups rg
JOIN artist_credit ac ON rg.album_artist_credit_id = ac.id
JOIN artist_credit_artist aca ON aca.credit_id = ac.id
LEFT JOIN cover_art ca ON rg.cover_art_id = ca.id
LEFT JOIN (
SELECT rgr.release_group_id, ac2.text
FROM release_group_recordings rgr
JOIN recordings rec ON rec.id = rgr.recording_id
JOIN artist_credit ac2 ON ac2.id = rec.artist_credit_id
GROUP BY rgr.release_group_id
) fallback_ac ON fallback_ac.release_group_id = rg.id
WHERE aca.artist_id = ?
ORDER BY rg.name
`
type GetAlbumsByArtistRow struct {
ID int64
Name string
Year sql.NullInt64
ReleaseYear int64
ArtistName string
ArtistMbid string
CoverArtPath string
}
func (q *Queries) GetAlbumsByArtist(ctx context.Context, artistID int64) ([]GetAlbumsByArtistRow, error) {
rows, err := q.db.QueryContext(ctx, getAlbumsByArtist, artistID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAlbumsByArtistRow
for rows.Next() {
var i GetAlbumsByArtistRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Year,
&i.ReleaseYear,
&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 getAlbumsByArtistByLibrary = `-- name: GetAlbumsByArtistByLibrary :many
SELECT
rg.id,
rg.name,
COALESCE(rg.original_year, rg.year) AS year,
COALESCE(rg.year, 0) AS release_year,
COALESCE(ac.text, fallback_ac.text, '') as artist_name,
-- primary (first-credited) album artist's MBID, for linking the
-- artist name to its detail page. Empty when the album has no
-- MB-tagged album-artist credit.
CAST(COALESCE((
SELECT a.mbid
FROM artist_credit_artist aca_p
JOIN artists a ON a.id = aca_p.artist_id
WHERE aca_p.credit_id = rg.album_artist_credit_id
ORDER BY aca_p.id
LIMIT 1
), '') AS TEXT) as artist_mbid,
COALESCE(ca.file_path, '') as cover_art_path
FROM release_groups rg
JOIN artist_credit ac ON rg.album_artist_credit_id = ac.id
JOIN artist_credit_artist aca ON aca.credit_id = ac.id
LEFT JOIN cover_art ca ON rg.cover_art_id = ca.id
LEFT JOIN (
SELECT rgr.release_group_id, ac2.text
FROM release_group_recordings rgr
JOIN recordings rec ON rec.id = rgr.recording_id
JOIN artist_credit ac2 ON ac2.id = rec.artist_credit_id
GROUP BY rgr.release_group_id
) fallback_ac ON fallback_ac.release_group_id = rg.id
WHERE aca.artist_id = ?
AND rg.id IN (
SELECT DISTINCT rgr2.release_group_id
FROM release_group_recordings rgr2
JOIN recordings r2 ON r2.id = rgr2.recording_id
JOIN audio_files af2 ON af2.recording_id = r2.id
WHERE af2.library_id = ?
)
ORDER BY rg.name
`
type GetAlbumsByArtistByLibraryParams struct {
ArtistID int64
LibraryID int64
}
type GetAlbumsByArtistByLibraryRow struct {
ID int64
Name string
Year sql.NullInt64
ReleaseYear int64
ArtistName string
ArtistMbid string
CoverArtPath string
}
func (q *Queries) GetAlbumsByArtistByLibrary(ctx context.Context, arg GetAlbumsByArtistByLibraryParams) ([]GetAlbumsByArtistByLibraryRow, error) {
rows, err := q.db.QueryContext(ctx, getAlbumsByArtistByLibrary, arg.ArtistID, arg.LibraryID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAlbumsByArtistByLibraryRow
for rows.Next() {
var i GetAlbumsByArtistByLibraryRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Year,
&i.ReleaseYear,
&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 getAllAlbumsWithDetails = `-- name: GetAllAlbumsWithDetails :many
SELECT
rg.id,
rg.name,
-- year prefers original release year (MB first-release-date)
-- over the file-tag year so the UI surfaces the album's
-- original year by default. release_year keeps the file-tag
-- year accessible.
COALESCE(rg.original_year, rg.year) AS year,
COALESCE(rg.year, 0) AS release_year,
rg.mbid,
COALESCE(ac.text, fallback_ac.text, '') as artist_name,
-- primary (first-credited) album artist's MBID, for linking the
-- artist name to its detail page. Empty when the album has no
-- MB-tagged album-artist credit.
CAST(COALESCE((
SELECT a.mbid
FROM artist_credit_artist aca_p
JOIN artists a ON a.id = aca_p.artist_id
WHERE aca_p.credit_id = rg.album_artist_credit_id
ORDER BY aca_p.id
LIMIT 1
), '') AS TEXT) as artist_mbid,
COALESCE(ca.file_path, '') as cover_art_path
FROM release_groups rg
LEFT JOIN artist_credit ac ON rg.album_artist_credit_id = ac.id
LEFT JOIN cover_art ca ON rg.cover_art_id = ca.id
LEFT JOIN (
SELECT rgr.release_group_id, ac2.text
FROM release_group_recordings rgr
JOIN recordings rec ON rec.id = rgr.recording_id
JOIN artist_credit ac2 ON ac2.id = rec.artist_credit_id
GROUP BY rgr.release_group_id
) fallback_ac ON fallback_ac.release_group_id = rg.id
ORDER BY rg.name
`
type GetAllAlbumsWithDetailsRow struct {
ID int64
Name string
Year sql.NullInt64
ReleaseYear int64
Mbid sql.NullString
ArtistName string
ArtistMbid string
CoverArtPath string
}
func (q *Queries) GetAllAlbumsWithDetails(ctx context.Context) ([]GetAllAlbumsWithDetailsRow, error) {
rows, err := q.db.QueryContext(ctx, getAllAlbumsWithDetails)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAllAlbumsWithDetailsRow
for rows.Next() {
var i GetAllAlbumsWithDetailsRow
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 getAllAlbumsWithDetailsByLibrary = `-- name: GetAllAlbumsWithDetailsByLibrary :many
SELECT
rg.id,
rg.name,
-- year prefers original release year (MB first-release-date)
-- over the file-tag year so the UI surfaces the album's
-- original year by default. release_year keeps the file-tag
-- year accessible.
COALESCE(rg.original_year, rg.year) AS year,
COALESCE(rg.year, 0) AS release_year,
rg.mbid,
COALESCE(ac.text, fallback_ac.text, '') as artist_name,
-- primary (first-credited) album artist's MBID, for linking the
-- artist name to its detail page. Empty when the album has no
-- MB-tagged album-artist credit.
CAST(COALESCE((
SELECT a.mbid
FROM artist_credit_artist aca_p
JOIN artists a ON a.id = aca_p.artist_id
WHERE aca_p.credit_id = rg.album_artist_credit_id
ORDER BY aca_p.id
LIMIT 1
), '') AS TEXT) as artist_mbid,
COALESCE(ca.file_path, '') as cover_art_path
FROM release_groups rg
LEFT JOIN artist_credit ac ON rg.album_artist_credit_id = ac.id
LEFT JOIN cover_art ca ON rg.cover_art_id = ca.id
LEFT JOIN (
SELECT rgr.release_group_id, ac2.text
FROM release_group_recordings rgr
JOIN recordings rec ON rec.id = rgr.recording_id
JOIN artist_credit ac2 ON ac2.id = rec.artist_credit_id
GROUP BY rgr.release_group_id
) fallback_ac ON fallback_ac.release_group_id = rg.id
WHERE rg.id IN (
SELECT DISTINCT rgr2.release_group_id
FROM release_group_recordings rgr2
JOIN recordings r2 ON r2.id = rgr2.recording_id
JOIN audio_files af2 ON af2.recording_id = r2.id
WHERE af2.library_id = ?
)
ORDER BY rg.name
`
type GetAllAlbumsWithDetailsByLibraryRow struct {
ID int64
Name string
Year sql.NullInt64
ReleaseYear int64
Mbid sql.NullString
ArtistName string
ArtistMbid string
CoverArtPath string
}
func (q *Queries) GetAllAlbumsWithDetailsByLibrary(ctx context.Context, libraryID int64) ([]GetAllAlbumsWithDetailsByLibraryRow, error) {
rows, err := q.db.QueryContext(ctx, getAllAlbumsWithDetailsByLibrary, libraryID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAllAlbumsWithDetailsByLibraryRow
for rows.Next() {
var i GetAllAlbumsWithDetailsByLibraryRow
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 getAllReleaseGroups = `-- name: GetAllReleaseGroups :many
SELECT id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year, pending_release_mbid FROM release_groups
ORDER BY name
`
func (q *Queries) GetAllReleaseGroups(ctx context.Context) ([]ReleaseGroup, error) {
rows, err := q.db.QueryContext(ctx, getAllReleaseGroups)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ReleaseGroup
for rows.Next() {
var i ReleaseGroup
if err := rows.Scan(
&i.ID,
&i.Name,
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
&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 getOrphanedReleaseGroupIDs = `-- name: GetOrphanedReleaseGroupIDs :many
SELECT rg.id FROM release_groups rg
LEFT JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
WHERE rgr.id IS NULL
`
// Release groups with no recordings left in them - run after orphaned
// recordings (and their release_group_recordings rows) are deleted, so
// a release group whose last owned track was removed is cleaned up too.
func (q *Queries) GetOrphanedReleaseGroupIDs(ctx context.Context) ([]int64, error) {
rows, err := q.db.QueryContext(ctx, getOrphanedReleaseGroupIDs)
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 getReleaseGroup = `-- name: GetReleaseGroup :one
SELECT id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year, pending_release_mbid FROM release_groups
WHERE id = ? LIMIT 1
`
func (q *Queries) GetReleaseGroup(ctx context.Context, id int64) (ReleaseGroup, error) {
row := q.db.QueryRowContext(ctx, getReleaseGroup, id)
var i ReleaseGroup
err := row.Scan(
&i.ID,
&i.Name,
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
&i.PendingReleaseMbid,
)
return i, err
}
const getReleaseGroupByNameAndArtist = `-- name: GetReleaseGroupByNameAndArtist :one
SELECT id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year, pending_release_mbid FROM release_groups
WHERE name = ? AND album_artist_credit_id = ? LIMIT 1
`
type GetReleaseGroupByNameAndArtistParams struct {
Name string
AlbumArtistCreditID sql.NullInt64
}
func (q *Queries) GetReleaseGroupByNameAndArtist(ctx context.Context, arg GetReleaseGroupByNameAndArtistParams) (ReleaseGroup, error) {
row := q.db.QueryRowContext(ctx, getReleaseGroupByNameAndArtist, arg.Name, arg.AlbumArtistCreditID)
var i ReleaseGroup
err := row.Scan(
&i.ID,
&i.Name,
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
&i.PendingReleaseMbid,
)
return i, err
}
const setReleaseGroupOriginalYear = `-- name: SetReleaseGroupOriginalYear :exec
UPDATE release_groups SET original_year = ? WHERE id = ?
`
type SetReleaseGroupOriginalYearParams struct {
OriginalYear sql.NullInt64
ID int64
}
// Set the release group's original-release-year (release-group's
// first-release-date from MusicBrainz). Called from autotag apply
// when the user confirms a candidate; the file-tag year stays in
// the year column.
func (q *Queries) SetReleaseGroupOriginalYear(ctx context.Context, arg SetReleaseGroupOriginalYearParams) error {
_, err := q.db.ExecContext(ctx, setReleaseGroupOriginalYear, arg.OriginalYear, arg.ID)
return err
}
const updateReleaseGroup = `-- name: UpdateReleaseGroup :exec
UPDATE release_groups
SET name = ?
WHERE id = ?
`
type UpdateReleaseGroupParams struct {
Name string
ID int64
}
func (q *Queries) UpdateReleaseGroup(ctx context.Context, arg UpdateReleaseGroupParams) error {
_, err := q.db.ExecContext(ctx, updateReleaseGroup, arg.Name, arg.ID)
return err
}
const updateReleaseGroupCoverArt = `-- name: UpdateReleaseGroupCoverArt :exec
UPDATE release_groups
SET cover_art_id = ?
WHERE id = ?
`
type UpdateReleaseGroupCoverArtParams struct {
CoverArtID sql.NullInt64
ID int64
}
func (q *Queries) UpdateReleaseGroupCoverArt(ctx context.Context, arg UpdateReleaseGroupCoverArtParams) error {
_, err := q.db.ExecContext(ctx, updateReleaseGroupCoverArt, arg.CoverArtID, arg.ID)
return err
}
const upsertReleaseGroup = `-- name: UpsertReleaseGroup :one
INSERT INTO release_groups (name, album_artist_credit_id, year)
VALUES (?, ?, ?)
ON CONFLICT(name, album_artist_credit_id) DO UPDATE SET
album_artist_credit_id = COALESCE(excluded.album_artist_credit_id, release_groups.album_artist_credit_id),
year = COALESCE(excluded.year, release_groups.year)
RETURNING id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year, pending_release_mbid
`
type UpsertReleaseGroupParams struct {
Name string
AlbumArtistCreditID sql.NullInt64
Year sql.NullInt64
}
func (q *Queries) UpsertReleaseGroup(ctx context.Context, arg UpsertReleaseGroupParams) (ReleaseGroup, error) {
row := q.db.QueryRowContext(ctx, upsertReleaseGroup, arg.Name, arg.AlbumArtistCreditID, arg.Year)
var i ReleaseGroup
err := row.Scan(
&i.ID,
&i.Name,
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
&i.PendingReleaseMbid,
)
return i, err
}
+104 -87
View File
@@ -25,11 +25,21 @@ func (q *Queries) ClearCompletedTaggingItems(ctx context.Context, libraryID int6
}
const countPendingTaggingItems = `-- name: CountPendingTaggingItems :one
SELECT COUNT(*) FROM tagging_items
WHERE status = 'pending'
AND (CAST(?1 AS INTEGER) = 0 OR library_id = ?1)
SELECT COUNT(*) FROM tagging_items ti
WHERE ti.status = 'pending'
AND (CAST(?1 AS INTEGER) = 0 OR ti.library_id = ?1)
AND EXISTS (
SELECT 1 FROM audio_files af
WHERE af.group_key = ti.group_key AND af.tag_status = 'untagged'
)
`
// "Needs tagging" is a question about the files, not about the row:
// every scanned folder gets a tagging_items row (see
// UpsertTaggingItemOnTrackAdd), including one whose files all arrived
// carrying a recording MBID. Without the EXISTS a fully MB-tagged
// library reports its entire album count as pending work. See the
// same predicate on the three list queries below.
func (q *Queries) CountPendingTaggingItems(ctx context.Context, libraryID int64) (int64, error) {
row := q.db.QueryRowContext(ctx, countPendingTaggingItems, libraryID)
var count int64
@@ -77,6 +87,13 @@ LEFT JOIN libraries lb ON lb.id = ti.library_id
WHERE ti.status = 'pending'
AND (CAST(?1 AS INTEGER) = 0 OR ti.library_id = ?1)
AND ti.group_key > ?2
-- See CountPendingTaggingItems: the cursor must not stop on a
-- folder the list query no longer shows, or "next" walks folders
-- that are not in the sidebar.
AND EXISTS (
SELECT 1 FROM audio_files af
WHERE af.group_key = ti.group_key AND af.tag_status = 'untagged'
)
ORDER BY ti.group_key
LIMIT 1
`
@@ -185,20 +202,6 @@ func (q *Queries) GetPendingFolderDetail(ctx context.Context, groupKey string) (
return i, err
}
const getRecordingReleaseGroupID = `-- name: GetRecordingReleaseGroupID :one
SELECT COALESCE(rgr.release_group_id, 0) AS release_group_id
FROM release_group_recordings rgr
WHERE rgr.recording_id = ?
LIMIT 1
`
func (q *Queries) GetRecordingReleaseGroupID(ctx context.Context, recordingID int64) (int64, error) {
row := q.db.QueryRowContext(ctx, getRecordingReleaseGroupID, recordingID)
var release_group_id int64
err := row.Scan(&release_group_id)
return release_group_id, err
}
const getTaggingItem = `-- name: GetTaggingItem :one
SELECT group_key, library_id, track_count, album_name, album_artist, disc_number, best_match_release_mbid, score, last_checked_at, status, cleared_at, created_at, synthetic, parent_group_key, album_artist_conflict FROM tagging_items
WHERE group_key = ?
@@ -235,22 +238,18 @@ SELECT
af.basename,
af.length_milliseconds,
af.tag_status,
COALESCE(r.track_number, 0) AS track_number,
COALESCE(r.disc_number, 0) AS disc_number,
COALESCE(r.name, '') AS title,
COALESCE(ac.text, '') AS artist_name,
COALESCE(r.mbid, '') AS recording_mbid,
COALESCE(rg.name, '') AS album_name,
COALESCE(rgac.text, '') AS album_artist
COALESCE(af.track_number, 0) AS track_number,
COALESCE(af.disc_number, 0) AS disc_number,
af.title,
af.artist_credit AS artist_name,
COALESCE(af.recording_mbid, '') AS recording_mbid,
COALESCE(al.name, '') AS album_name,
COALESCE(al.artist_credit, '') AS album_artist
FROM audio_files af
LEFT JOIN recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN release_group_recordings rgr ON rgr.recording_id = r.id
LEFT JOIN release_groups rg ON rg.id = rgr.release_group_id
LEFT JOIN artist_credit rgac ON rg.album_artist_credit_id = rgac.id
LEFT JOIN albums al ON al.id = af.album_id
WHERE af.group_key = ?
ORDER BY COALESCE(r.disc_number, 0),
COALESCE(r.track_number, 0),
ORDER BY COALESCE(af.disc_number, 0),
COALESCE(af.track_number, 0),
af.file_path
`
@@ -269,10 +268,10 @@ type ListAudioFilesInTaggingGroupRow struct {
AlbumArtist string
}
// album_name/album_artist are the PER-TRACK tags (via each track's
// own release_group link), not the folder-level tagging_items
// values. SplitMixedFolder clusters on these to find sub-albums
// hiding inside a folder full of unrelated tracks.
// album_name/album_artist are the PER-TRACK tags (each file's own
// album link), not the folder-level tagging_items values.
// SplitMixedFolder clusters on these to find sub-albums hiding inside
// a folder full of unrelated tracks.
func (q *Queries) ListAudioFilesInTaggingGroup(ctx context.Context, groupKey string) ([]ListAudioFilesInTaggingGroupRow, error) {
rows, err := q.db.QueryContext(ctx, listAudioFilesInTaggingGroup, groupKey)
if err != nil {
@@ -313,10 +312,7 @@ const listLikelyMixedBagGroupKeys = `-- name: ListLikelyMixedBagGroupKeys :many
SELECT ti.group_key
FROM tagging_items ti
JOIN audio_files af ON af.group_key = ti.group_key
LEFT JOIN recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN release_group_recordings rgr ON rgr.recording_id = r.id
LEFT JOIN release_groups rg ON rg.id = rgr.release_group_id
LEFT JOIN albums rg ON rg.id = af.album_id
WHERE ti.synthetic = 0
AND ti.track_count >= 4
AND (
@@ -324,7 +320,7 @@ WHERE ti.synthetic = 0
OR LOWER(TRIM(ti.album_artist)) IN ('various artists', 'various', 'va', 'v.a.', 'v a', 'unknown')
)
GROUP BY ti.group_key
HAVING COUNT(DISTINCT CASE WHEN ac.text != '' THEN LOWER(TRIM(ac.text)) END) > 1
HAVING COUNT(DISTINCT CASE WHEN af.artist_credit != '' THEN LOWER(TRIM(af.artist_credit)) END) > 1
AND COUNT(DISTINCT CASE WHEN rg.name != '' THEN LOWER(TRIM(rg.name)) END) > 1
`
@@ -359,34 +355,31 @@ func (q *Queries) ListLikelyMixedBagGroupKeys(ctx context.Context) ([]string, er
return items, nil
}
const listLocalReleaseGroupCandidates = `-- name: ListLocalReleaseGroupCandidates :many
const listLocalAlbumCandidates = `-- name: ListLocalAlbumCandidates :many
SELECT
rg.id AS release_group_id,
rg.mbid AS release_group_mbid,
rg.name AS album_name,
COALESCE(rg.year, 0) AS year,
COALESCE(ac.text, '') AS artist_credit,
COALESCE(rgr.track_number, 0) AS track_number,
COALESCE(rgr.disc_number, 0) AS disc_number,
COALESCE(r.name, '') AS track_title,
COALESCE(r.mbid, '') AS recording_mbid,
COALESCE(local_af.length_milliseconds, 0) AS length_milliseconds
FROM release_groups rg
JOIN release_group_recordings rgr ON rgr.release_group_id = rg.id
JOIN recordings r ON r.id = rgr.recording_id
LEFT JOIN artist_credit ac ON rg.album_artist_credit_id = ac.id
LEFT JOIN audio_files local_af ON local_af.recording_id = r.id
WHERE rg.mbid IS NOT NULL
AND rg.mbid != ''
AND r.mbid IS NOT NULL
AND r.mbid != ''
AND rg.name = ? COLLATE NOCASE
ORDER BY rg.id, rgr.disc_number, rgr.track_number
al.id AS album_id,
al.mbid AS album_mbid,
al.name AS album_name,
COALESCE(al.year, 0) AS year,
al.artist_credit,
COALESCE(af.track_number, 0) AS track_number,
COALESCE(af.disc_number, 0) AS disc_number,
af.title AS track_title,
COALESCE(af.recording_mbid, '') AS recording_mbid,
af.length_milliseconds
FROM albums al
JOIN audio_files af ON af.album_id = al.id
WHERE al.mbid IS NOT NULL
AND al.mbid != ''
AND af.recording_mbid IS NOT NULL
AND af.recording_mbid != ''
AND al.name = ? COLLATE NOCASE
ORDER BY al.id, af.disc_number, af.track_number
`
type ListLocalReleaseGroupCandidatesRow struct {
ReleaseGroupID int64
ReleaseGroupMbid sql.NullString
type ListLocalAlbumCandidatesRow struct {
AlbumID int64
AlbumMbid sql.NullString
AlbumName string
Year int64
ArtistCredit string
@@ -397,22 +390,21 @@ type ListLocalReleaseGroupCandidatesRow struct {
LengthMilliseconds int64
}
// Returns one row per (release_group, track) combination for any
// local release_group that has an MBID. Callers group these in Go
// and filter by normalized album-name match. Joined case-insensitive
// on name to pre-filter cheaply; Go does the real normalization.
func (q *Queries) ListLocalReleaseGroupCandidates(ctx context.Context, name string) ([]ListLocalReleaseGroupCandidatesRow, error) {
rows, err := q.db.QueryContext(ctx, listLocalReleaseGroupCandidates, name)
// One row per (album, track) for any local album carrying an MBID.
// Callers group these in Go and filter by normalized album-name match;
// the join is case-insensitive on name to pre-filter cheaply.
func (q *Queries) ListLocalAlbumCandidates(ctx context.Context, name string) ([]ListLocalAlbumCandidatesRow, error) {
rows, err := q.db.QueryContext(ctx, listLocalAlbumCandidates, name)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListLocalReleaseGroupCandidatesRow
var items []ListLocalAlbumCandidatesRow
for rows.Next() {
var i ListLocalReleaseGroupCandidatesRow
var i ListLocalAlbumCandidatesRow
if err := rows.Scan(
&i.ReleaseGroupID,
&i.ReleaseGroupMbid,
&i.AlbumID,
&i.AlbumMbid,
&i.AlbumName,
&i.Year,
&i.ArtistCredit,
@@ -454,6 +446,18 @@ LEFT JOIN libraries lb ON lb.id = ti.library_id
WHERE (CAST(?1 AS INTEGER) = 0 OR ti.library_id = ?1)
AND (CAST(?2 AS TEXT) = 'all' OR ti.status = ?2)
AND ti.cleared_at IS NULL
-- Actionable rows must have something to act on: see
-- CountPendingTaggingItems. Reviewed rows (confirmed/skipped) are
-- exempt because they are history, not work -- an applied folder is
-- fully tagged by definition and would otherwise vanish from the
-- sidebar's Completed section the instant it succeeded.
AND (
ti.status IN ('confirmed', 'skipped')
OR EXISTS (
SELECT 1 FROM audio_files af
WHERE af.group_key = ti.group_key AND af.tag_status = 'untagged'
)
)
ORDER BY LOWER(ti.album_artist), LOWER(ti.album_name), ti.disc_number
LIMIT ?4 OFFSET ?3
`
@@ -628,6 +632,14 @@ LEFT JOIN libraries lb ON lb.id = ti.library_id
WHERE (CAST(?1 AS INTEGER) = 0 OR ti.library_id = ?1)
AND (CAST(?2 AS TEXT) = 'all' OR ti.status = ?2)
AND ti.cleared_at IS NULL
-- See ListPendingTaggingItemsAlphabetical.
AND (
ti.status IN ('confirmed', 'skipped')
OR EXISTS (
SELECT 1 FROM audio_files af
WHERE af.group_key = ti.group_key AND af.tag_status = 'untagged'
)
)
ORDER BY ti.score IS NULL, ti.score DESC, LOWER(ti.album_artist), LOWER(ti.album_name)
LIMIT ?4 OFFSET ?3
`
@@ -758,31 +770,36 @@ func (q *Queries) SetAudioFileTagStatus(ctx context.Context, arg SetAudioFileTag
return err
}
const setRecordingMBID = `-- name: SetRecordingMBID :exec
UPDATE recordings SET mbid = ? WHERE id = ?
const setFileAlbumMBID = `-- name: SetFileAlbumMBID :exec
UPDATE albums SET mbid = ?
WHERE albums.id = (SELECT af.album_id FROM audio_files af WHERE af.id = ?)
`
type SetRecordingMBIDParams struct {
type SetFileAlbumMBIDParams struct {
Mbid sql.NullString
ID int64
}
func (q *Queries) SetRecordingMBID(ctx context.Context, arg SetRecordingMBIDParams) error {
_, err := q.db.ExecContext(ctx, setRecordingMBID, arg.Mbid, arg.ID)
// The album MBID for the album a file belongs to. Keyed by file
// because that is what the autotag apply path holds; under the old
// schema it had to look the release group up through two join tables
// first (GetRecordingReleaseGroupID), which is gone.
func (q *Queries) SetFileAlbumMBID(ctx context.Context, arg SetFileAlbumMBIDParams) error {
_, err := q.db.ExecContext(ctx, setFileAlbumMBID, arg.Mbid, arg.ID)
return err
}
const setReleaseGroupMBID = `-- name: SetReleaseGroupMBID :exec
UPDATE release_groups SET mbid = ? WHERE id = ?
const setFileRecordingMBID = `-- name: SetFileRecordingMBID :exec
UPDATE audio_files SET recording_mbid = ? WHERE id = ?
`
type SetReleaseGroupMBIDParams struct {
Mbid sql.NullString
ID int64
type SetFileRecordingMBIDParams struct {
RecordingMbid sql.NullString
ID int64
}
func (q *Queries) SetReleaseGroupMBID(ctx context.Context, arg SetReleaseGroupMBIDParams) error {
_, err := q.db.ExecContext(ctx, setReleaseGroupMBID, arg.Mbid, arg.ID)
func (q *Queries) SetFileRecordingMBID(ctx context.Context, arg SetFileRecordingMBIDParams) error {
_, err := q.db.ExecContext(ctx, setFileRecordingMBID, arg.RecordingMbid, arg.ID)
return err
}