Track details from the queue, a playlist or a smart playlist looked the track up in libraryStore's whole-library array, and returned silently when it had not loaded, so the menu item did nothing. That dependency is also why every track was fetched eagerly at startup (20.5 MB at 26k tracks). GetTracksByPaths answers for the paths in hand, and trackCache holds the answers: bounded, coalesced into one call per task, and forgetting what the retag, removal and scan events name. Every opener, and track-details' own refresh after a save, go through it. A source sweep pins the whole-library array to the store and the Tracks view. Closes #279
213 lines
8.1 KiB
SQL
213 lines
8.1 KiB
SQL
-- Queries over audio_files and the track_metadata view above it.
|
|
--
|
|
-- Every query that returns "a track" selects from `track_metadata`,
|
|
-- which is the one place the projection is defined. The scoped and
|
|
-- unscoped variants that used to be written twice are one query now:
|
|
-- library_id 0 means "every library", and `(:id = 0 OR library_id = :id)`
|
|
-- costs nothing measurable (23 ms vs 21 ms over 26k rows) because these
|
|
-- queries scan either way.
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Writes
|
|
-- ---------------------------------------------------------------------
|
|
|
|
-- name: CreateAudioFile :one
|
|
INSERT INTO audio_files (
|
|
file_path, library_id, file_type_id,
|
|
length_milliseconds, sample_rate, bit_depth, channels, bitrate, file_size,
|
|
title, artist_credit, artist_id, album_id,
|
|
track_number, disc_number, total_tracks, year, composer, comment,
|
|
recording_mbid, basename, group_key, modified_at, tag_status
|
|
) VALUES (
|
|
?, ?, ?,
|
|
?, ?, ?, ?, ?, ?,
|
|
?, ?, ?, ?,
|
|
?, ?, ?, ?, ?, ?,
|
|
?, ?, ?, ?, ?
|
|
)
|
|
RETURNING *;
|
|
|
|
-- name: UpdateAudioFileTags :exec
|
|
-- A rescan of a file whose mtime moved: the tags are re-read and
|
|
-- written over the same row. Under the old schema this created a
|
|
-- *new* recording and repointed the file at it, abandoning the old one
|
|
-- -- which is where 812 orphaned rows and every phantom "you own this"
|
|
-- came from. There is nothing to orphan now.
|
|
UPDATE audio_files
|
|
SET title = ?, artist_credit = ?, artist_id = ?, album_id = ?,
|
|
track_number = ?, disc_number = ?, total_tracks = ?, year = ?,
|
|
composer = ?, comment = ?, recording_mbid = ?,
|
|
sample_rate = ?, bit_depth = ?, channels = ?, bitrate = ?,
|
|
file_size = ?, length_milliseconds = ?, modified_at = ?
|
|
WHERE id = ?;
|
|
|
|
-- name: SetAudioFileGroupKey :exec
|
|
UPDATE audio_files SET group_key = ? WHERE id = ?;
|
|
|
|
-- name: PromoteAudioFileTagStatusIfUntagged :exec
|
|
-- A rescan re-reads the tags of a file whose mtime moved, so a file
|
|
-- another tagger stamped with MBIDs since import arrives here still
|
|
-- carrying the 'untagged' status it was created with (only the insert
|
|
-- path sets it). Promote it the same way saveAudioFile does.
|
|
-- Guarded on 'untagged' so it cannot overwrite a deliberate
|
|
-- 'user_skipped_permanent', and so a file losing its MBIDs is left
|
|
-- alone -- demotion is the scan's judgement, not this statement's.
|
|
UPDATE audio_files
|
|
SET tag_status = 'user_confirmed'
|
|
WHERE id = ? AND tag_status = 'untagged';
|
|
|
|
-- name: UpdateAudioFileStat :exec
|
|
-- Records the on-disk mtime/size without re-reading tags. Used to
|
|
-- backfill the staleness baseline for files the scan skipped, and to
|
|
-- re-baseline after YellowJacket's own tag writer rewrites a file.
|
|
UPDATE audio_files
|
|
SET modified_at = ?, file_size = ?
|
|
WHERE id = ?;
|
|
|
|
-- name: SetAudioFileRecordingMBID :exec
|
|
UPDATE audio_files SET recording_mbid = ? WHERE id = ?;
|
|
|
|
-- name: DeleteAudioFile :exec
|
|
DELETE FROM audio_files WHERE id = ?;
|
|
|
|
-- name: DeleteAllAudioFiles :exec
|
|
DELETE FROM audio_files;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Reads: the file row itself
|
|
-- ---------------------------------------------------------------------
|
|
|
|
-- name: GetAudioFile :one
|
|
SELECT * FROM audio_files WHERE id = ? LIMIT 1;
|
|
|
|
-- name: GetAudioFileByPath :one
|
|
SELECT * FROM audio_files WHERE file_path = ? LIMIT 1;
|
|
|
|
-- name: GetAudioFileGroupKey :one
|
|
SELECT group_key FROM audio_files WHERE id = ? LIMIT 1;
|
|
|
|
-- name: GetAllAudioFilePaths :many
|
|
SELECT id, file_path FROM audio_files;
|
|
|
|
-- name: GetAudioFilesByPaths :many
|
|
SELECT id, library_id, file_path, group_key FROM audio_files
|
|
WHERE file_path IN (sqlc.slice('paths'));
|
|
|
|
-- name: GetRandomAudioFilePath :one
|
|
SELECT file_path FROM audio_files ORDER BY RANDOM() LIMIT 1;
|
|
|
|
-- name: CountAudioFiles :one
|
|
SELECT COUNT(*) AS count FROM audio_files
|
|
WHERE library_id = COALESCE(NULLIF(CAST(sqlc.arg(library_id) AS INTEGER), 0), library_id);
|
|
|
|
-- name: GetLibraryMaxModifiedAt :one
|
|
-- Newest recorded mtime in a library, for the startup soft scan. 0 when
|
|
-- the library is empty or no row has a baseline yet.
|
|
SELECT CAST(COALESCE(MAX(modified_at), 0) AS INTEGER) FROM audio_files
|
|
WHERE library_id = ?;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Reads: tracks
|
|
-- ---------------------------------------------------------------------
|
|
|
|
-- name: GetTracks :many
|
|
SELECT * FROM track_metadata
|
|
WHERE library_id = COALESCE(NULLIF(CAST(sqlc.arg(library_id) AS INTEGER), 0), library_id);
|
|
|
|
-- name: GetTrackByPath :one
|
|
SELECT * FROM track_metadata WHERE file_path = ? LIMIT 1;
|
|
|
|
-- name: GetTracksByPaths :many
|
|
SELECT * FROM track_metadata WHERE file_path IN (sqlc.slice('paths'));
|
|
|
|
-- name: GetTracksByAlbum :many
|
|
SELECT * FROM track_metadata
|
|
WHERE album_id = sqlc.arg(album_id)
|
|
AND library_id = COALESCE(NULLIF(CAST(sqlc.arg(library_id) AS INTEGER), 0), library_id)
|
|
ORDER BY disc_number, track_number;
|
|
|
|
-- name: GetTracksByGenre :many
|
|
SELECT tm.* FROM track_metadata tm
|
|
JOIN file_genres fg ON fg.audio_file_id = tm.id
|
|
JOIN genres g ON g.id = fg.genre_id
|
|
WHERE g.name = sqlc.arg(genre)
|
|
AND tm.library_id = COALESCE(NULLIF(CAST(sqlc.arg(library_id) AS INTEGER), 0), tm.library_id);
|
|
|
|
-- name: LookupTrackMetaByPaths :many
|
|
SELECT id, file_path, title, artist_name, album, cover_art_path,
|
|
artist_mbid, release_group_mbid, recording_mbid
|
|
FROM track_metadata
|
|
WHERE file_path IN (sqlc.slice('paths'));
|
|
|
|
-- name: SearchTracksByBasename :many
|
|
SELECT id, file_path, length_milliseconds, title, artist_name, album
|
|
FROM track_metadata
|
|
WHERE file_path IN (
|
|
SELECT file_path FROM audio_files WHERE basename = sqlc.arg(basename)
|
|
)
|
|
LIMIT sqlc.arg(lim);
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Reads: file paths, grouped by whatever the caller asked about
|
|
-- ---------------------------------------------------------------------
|
|
-- These answer "what can I play" and they all ask audio_files, because
|
|
-- that is the only table whose rows are files. Grouped rather than
|
|
-- flattened because the caller owns the order.
|
|
|
|
-- name: GetFilePathsByAlbums :many
|
|
-- The library filter is applied in Go rather than here: sqlc numbers a
|
|
-- named parameter (?2) but expands a slice into N placeholders, so the
|
|
-- two together bind the wrong values - GetFilePathsByAlbums([1,2], 0)
|
|
-- read album id 2 as the library id. Returning library_id and
|
|
-- filtering the (small) result is the version that cannot be wrong.
|
|
SELECT album_id, library_id, file_path FROM audio_files
|
|
WHERE album_id IN (sqlc.slice('album_ids'))
|
|
ORDER BY disc_number, track_number;
|
|
|
|
-- name: GetFilePathsByRecordingMBIDs :many
|
|
-- The ownership question in its only honest form: which of these
|
|
-- catalog recordings has a *file* behind it. Asked of audio_files, so
|
|
-- a metadata row with no file cannot answer yes.
|
|
SELECT recording_mbid, library_id, file_path FROM audio_files
|
|
WHERE recording_mbid IN (sqlc.slice('mbids'))
|
|
ORDER BY file_path;
|
|
|
|
-- name: GetFilePathsByGenres :many
|
|
SELECT g.name AS genre, af.library_id, af.file_path
|
|
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 g.name IN (sqlc.slice('genres'))
|
|
ORDER BY af.disc_number, af.track_number;
|
|
|
|
-- name: GetFilePathsByArtistMBID :many
|
|
SELECT DISTINCT af.file_path
|
|
FROM audio_files af
|
|
JOIN artists a ON a.id = af.artist_id
|
|
WHERE a.mbid = ?;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Ownership, asked in bulk
|
|
-- ---------------------------------------------------------------------
|
|
|
|
-- name: OwnedRecordingMBIDs :many
|
|
-- Which of these recording MBIDs are actually in the library. This is
|
|
-- what marks a catalog tracklist owned; it used to be
|
|
-- `SELECT mbid FROM recordings`, which answered yes for 129 tracks in a
|
|
-- real library that had no file at all.
|
|
SELECT DISTINCT recording_mbid FROM audio_files
|
|
WHERE recording_mbid IN (sqlc.slice('mbids'));
|
|
|
|
-- name: OwnedAlbumMBIDs :many
|
|
SELECT DISTINCT al.mbid FROM albums al
|
|
JOIN audio_files af ON af.album_id = al.id
|
|
WHERE al.mbid IN (sqlc.slice('mbids'));
|
|
|
|
-- name: OwnedArtistMBIDs :many
|
|
SELECT DISTINCT a.mbid FROM artists a
|
|
JOIN audio_files af ON af.artist_id = a.id
|
|
WHERE a.mbid IN (sqlc.slice('mbids'));
|
|
|
|
-- name: GetAudioFilesInLibrary :many
|
|
SELECT * FROM audio_files WHERE library_id = ?;
|