perf(library): resolve album and genre file paths in one query

"Play this artist" awaited `GetAlbumTracks` inside a for loop — 13
sequential round trips for a 12-album artist — and every one of the
four sites doing that asked for whole track rows to read `FilePath`
off them. Five genres cost 6 MB across the IPC.

`GetFilePathsByAlbums(ids, libraryID)` and `GetFilePathsByGenres(names,
libraryID)` answer once and carry only the paths. Measured at 50 000
tracks: an artist 13 calls / 74.2 kB -> 2 / 19.2 kB, twenty albums
20 / 117.5 kB / 7.8 ms -> 1 / 26.0 kB / 1.7 ms, five genres
5 / 6 014 kB / 213 ms -> 1 / 1 291 kB / 32.6 ms, with the returned path
lists identical.

They return the paths grouped by album id or genre name rather than
flattened, because the caller owns the order — an album list is sorted
by name, not by id, and a flattened result would silently reorder a
queue — and because the album drag cache stores them per album. A
libraryID of 0 means "every library", matching an unset filter.
This commit is contained in:
2026-08-12 01:18:17 -04:00
parent 0cf710cf47
commit 9e0e4d5bb8
8 changed files with 625 additions and 0 deletions
@@ -907,6 +907,113 @@ func (q *Queries) GetAudioFilesNeedingMetadata(ctx context.Context) ([]AudioFile
return items, nil
}
const getFilePathsByReleaseGroups = `-- name: GetFilePathsByReleaseGroups :many
SELECT rgr.release_group_id, af.file_path
FROM release_group_recordings rgr
JOIN recordings r ON rgr.recording_id = r.id
JOIN audio_files af ON af.recording_id = r.id
WHERE rgr.release_group_id IN (/*SLICE:release_group_ids*/?)
ORDER BY rgr.disc_number, rgr.track_number
`
type GetFilePathsByReleaseGroupsRow struct {
ReleaseGroupID int64
FilePath string
}
// "Play this artist" and "play these albums" wanted file paths and asked
// for whole track rows to get them, one round trip per album (perf.m2).
// These answer the same question in one query and carry only what the
// caller uses; the release group id comes back so the caller can keep
// its own album ordering.
func (q *Queries) GetFilePathsByReleaseGroups(ctx context.Context, releaseGroupIds []int64) ([]GetFilePathsByReleaseGroupsRow, error) {
query := getFilePathsByReleaseGroups
var queryParams []interface{}
if len(releaseGroupIds) > 0 {
for _, v := range releaseGroupIds {
queryParams = append(queryParams, v)
}
query = strings.Replace(query, "/*SLICE:release_group_ids*/?", strings.Repeat(",?", len(releaseGroupIds))[1:], 1)
} else {
query = strings.Replace(query, "/*SLICE:release_group_ids*/?", "NULL", 1)
}
rows, err := q.db.QueryContext(ctx, query, queryParams...)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetFilePathsByReleaseGroupsRow
for rows.Next() {
var i GetFilePathsByReleaseGroupsRow
if err := rows.Scan(&i.ReleaseGroupID, &i.FilePath); 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 getFilePathsByReleaseGroupsByLibrary = `-- name: GetFilePathsByReleaseGroupsByLibrary :many
SELECT rgr.release_group_id, af.file_path
FROM release_group_recordings rgr
JOIN recordings r ON rgr.recording_id = r.id
JOIN audio_files af ON af.recording_id = r.id
WHERE rgr.release_group_id IN (/*SLICE:release_group_ids*/?)
AND af.library_id = ?
ORDER BY rgr.disc_number, rgr.track_number
`
type GetFilePathsByReleaseGroupsByLibraryParams struct {
ReleaseGroupIds []int64
LibraryID int64
}
type GetFilePathsByReleaseGroupsByLibraryRow struct {
ReleaseGroupID int64
FilePath string
}
func (q *Queries) GetFilePathsByReleaseGroupsByLibrary(ctx context.Context, arg GetFilePathsByReleaseGroupsByLibraryParams) ([]GetFilePathsByReleaseGroupsByLibraryRow, error) {
query := getFilePathsByReleaseGroupsByLibrary
var queryParams []interface{}
if len(arg.ReleaseGroupIds) > 0 {
for _, v := range arg.ReleaseGroupIds {
queryParams = append(queryParams, v)
}
query = strings.Replace(query, "/*SLICE:release_group_ids*/?", strings.Repeat(",?", len(arg.ReleaseGroupIds))[1:], 1)
} else {
query = strings.Replace(query, "/*SLICE:release_group_ids*/?", "NULL", 1)
}
queryParams = append(queryParams, arg.LibraryID)
rows, err := q.db.QueryContext(ctx, query, queryParams...)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetFilePathsByReleaseGroupsByLibraryRow
for rows.Next() {
var i GetFilePathsByReleaseGroupsByLibraryRow
if err := rows.Scan(&i.ReleaseGroupID, &i.FilePath); 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 getLibraryMaxModifiedAt = `-- name: GetLibraryMaxModifiedAt :one
SELECT CAST(COALESCE(MAX(modified_at), 0) AS INTEGER) FROM audio_files
WHERE library_id = ?