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:
@@ -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 = ?
|
||||
|
||||
Reference in New Issue
Block a user