From 02548dd55e59b28f3d6c8d9614f209140c979250 Mon Sep 17 00:00:00 2001 From: Caleb Allen Date: Mon, 9 Mar 2026 09:47:30 -0400 Subject: [PATCH] feat(10-02): add sqlc queries for libraries and update playlist queries for phantom support - Create libraries.sql with 7 CRUD queries (create, get, get-by-path, list, update, delete, count) - Update playlists.sql: AddPlaylistTrack now accepts 9 params including phantom metadata - Update playlist queries to use LEFT JOIN for nullable audio_file_id - Add GetTrackPhantomMetadata helper query for eager phantom population - Add is_phantom computed column to metadata queries - Add GetAudioFilesByLibrary and CountAudioFilesByLibrary queries - Regenerate all sqlc code --- backend/database/sql/queries/audio_files.sql | 6 + backend/database/sql/queries/libraries.sql | 21 +++ backend/database/sql/queries/playlists.sql | 77 ++++++--- .../database/sql/sqlcgen/audio_files.sql.go | 51 ++++++ backend/database/sql/sqlcgen/libraries.sql.go | 130 +++++++++++++++ backend/database/sql/sqlcgen/playlists.sql.go | 151 ++++++++++++++---- 6 files changed, 385 insertions(+), 51 deletions(-) create mode 100644 backend/database/sql/queries/libraries.sql create mode 100644 backend/database/sql/sqlcgen/libraries.sql.go diff --git a/backend/database/sql/queries/audio_files.sql b/backend/database/sql/queries/audio_files.sql index f7f73c9..cbd7f12 100644 --- a/backend/database/sql/queries/audio_files.sql +++ b/backend/database/sql/queries/audio_files.sql @@ -127,6 +127,12 @@ SELECT id, file_path, title, artist_name FROM track_metadata WHERE file_path IN (sqlc.slice('paths')); +-- name: GetAudioFilesByLibrary :many +SELECT * FROM audio_files WHERE library_id = ?; + +-- name: CountAudioFilesByLibrary :one +SELECT COUNT(*) AS count FROM audio_files WHERE library_id = ?; + -- name: DeleteAllAudioFiles :exec DELETE FROM audio_files; diff --git a/backend/database/sql/queries/libraries.sql b/backend/database/sql/queries/libraries.sql new file mode 100644 index 0000000..b2280fd --- /dev/null +++ b/backend/database/sql/queries/libraries.sql @@ -0,0 +1,21 @@ +-- name: CreateLibrary :one +INSERT INTO libraries (name, path) VALUES (?, ?) +RETURNING *; + +-- name: GetLibrary :one +SELECT * FROM libraries WHERE id = ? LIMIT 1; + +-- name: GetLibraryByPath :one +SELECT * FROM libraries WHERE path = ? LIMIT 1; + +-- name: GetAllLibraries :many +SELECT * FROM libraries ORDER BY name; + +-- name: UpdateLibraryName :exec +UPDATE libraries SET name = ? WHERE id = ?; + +-- name: DeleteLibrary :exec +DELETE FROM libraries WHERE id = ?; + +-- name: CountLibraries :one +SELECT COUNT(*) AS count FROM libraries; diff --git a/backend/database/sql/queries/playlists.sql b/backend/database/sql/queries/playlists.sql index 2f94a7f..4107f7a 100644 --- a/backend/database/sql/queries/playlists.sql +++ b/backend/database/sql/queries/playlists.sql @@ -18,13 +18,20 @@ DELETE FROM playlists WHERE id = ?; SELECT COUNT(*) AS count FROM playlists WHERE name = ?; -- name: AddPlaylistTrack :one -INSERT INTO playlist_tracks (playlist_id, audio_file_id, position) VALUES (?, ?, ?) +INSERT INTO playlist_tracks ( + playlist_id, audio_file_id, position, + phantom_title, phantom_artist, phantom_album, + phantom_duration_ms, phantom_genre, phantom_cover_art_path +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *; -- name: GetPlaylistTracks :many -SELECT pt.id, pt.playlist_id, pt.audio_file_id, pt.position, af.file_path +SELECT pt.id, pt.playlist_id, pt.audio_file_id, pt.position, + COALESCE(af.file_path, '') AS file_path, + pt.phantom_title, pt.phantom_artist, pt.phantom_album, + pt.phantom_duration_ms, pt.phantom_genre, pt.phantom_cover_art_path FROM playlist_tracks pt -JOIN audio_files af ON pt.audio_file_id = af.id +LEFT JOIN audio_files af ON pt.audio_file_id = af.id WHERE pt.playlist_id = ? ORDER BY pt.position; @@ -40,14 +47,15 @@ SELECT pt.playlist_id, pt.audio_file_id, pt.position, - af.file_path, - af.length_milliseconds, - COALESCE(r.name, '') AS title, - COALESCE(ac.text, '') AS artist, - COALESCE(rg.name, '') AS album, - COALESCE(ca.file_path, '') AS cover_art_path + 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, + CASE WHEN pt.audio_file_id IS NULL THEN 1 ELSE 0 END AS is_phantom FROM playlist_tracks pt -JOIN audio_files af ON pt.audio_file_id = af.id +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 ( @@ -66,14 +74,15 @@ SELECT pt.playlist_id, pt.audio_file_id, pt.position, - af.file_path, - af.length_milliseconds, - COALESCE(r.name, '') AS title, - COALESCE(ac.text, '') AS artist, - COALESCE(rg.name, '') AS album, - COALESCE(ca.file_path, '') AS cover_art_path + 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, + CASE WHEN pt.audio_file_id IS NULL THEN 1 ELSE 0 END AS is_phantom FROM playlist_tracks pt -JOIN audio_files af ON pt.audio_file_id = af.id +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 ( @@ -93,16 +102,16 @@ SELECT COALESCE(MAX(position), -1) + 1 AS next_position FROM playlist_tracks WHERE playlist_id = ?; -- name: GetPlaylistTrackFilePaths :many -SELECT af.file_path +SELECT COALESCE(af.file_path, '') AS file_path FROM playlist_tracks pt -JOIN audio_files af ON pt.audio_file_id = af.id -WHERE pt.playlist_id = ? +LEFT JOIN audio_files af ON pt.audio_file_id = af.id +WHERE pt.playlist_id = ? AND pt.audio_file_id IS NOT NULL ORDER BY pt.position; -- name: IsTrackInPlaylist :one SELECT EXISTS( SELECT 1 FROM playlist_tracks pt - JOIN audio_files af ON pt.audio_file_id = af.id + LEFT JOIN audio_files af ON pt.audio_file_id = af.id WHERE pt.playlist_id = ? AND af.file_path = ? ) AS in_playlist; @@ -111,3 +120,29 @@ DELETE FROM playlist_tracks WHERE playlist_id = ? AND audio_file_id = ( SELECT id FROM audio_files WHERE file_path = ? ); + +-- 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 = ?; diff --git a/backend/database/sql/sqlcgen/audio_files.sql.go b/backend/database/sql/sqlcgen/audio_files.sql.go index 2057579..509b7b4 100644 --- a/backend/database/sql/sqlcgen/audio_files.sql.go +++ b/backend/database/sql/sqlcgen/audio_files.sql.go @@ -22,6 +22,17 @@ func (q *Queries) CountAudioFiles(ctx context.Context) (int64, error) { return count, err } +const countAudioFilesByLibrary = `-- name: CountAudioFilesByLibrary :one +SELECT COUNT(*) AS count FROM audio_files WHERE library_id = ? +` + +func (q *Queries) CountAudioFilesByLibrary(ctx context.Context, libraryID int64) (int64, error) { + row := q.db.QueryRowContext(ctx, countAudioFilesByLibrary, libraryID) + var count int64 + err := row.Scan(&count) + return count, err +} + const createAudioFile = `-- name: CreateAudioFile :one INSERT INTO audio_files (file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id @@ -358,6 +369,46 @@ func (q *Queries) GetAudioFileByPath(ctx context.Context, filePath string) (Audi return i, err } +const getAudioFilesByLibrary = `-- name: GetAudioFilesByLibrary :many +SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id FROM audio_files WHERE library_id = ? +` + +func (q *Queries) GetAudioFilesByLibrary(ctx context.Context, libraryID int64) ([]AudioFile, error) { + rows, err := q.db.QueryContext(ctx, getAudioFilesByLibrary, libraryID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AudioFile + for rows.Next() { + var i AudioFile + if err := rows.Scan( + &i.ID, + &i.FilePath, + &i.LengthMilliseconds, + &i.FileTypeID, + &i.RecordingID, + &i.SampleRate, + &i.BitDepth, + &i.Channels, + &i.Bitrate, + &i.FileSize, + &i.Basename, + &i.LibraryID, + ); 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 getAudioFilesByReleaseGroup = `-- name: GetAudioFilesByReleaseGroup :many SELECT af.file_path, diff --git a/backend/database/sql/sqlcgen/libraries.sql.go b/backend/database/sql/sqlcgen/libraries.sql.go new file mode 100644 index 0000000..c8a69a5 --- /dev/null +++ b/backend/database/sql/sqlcgen/libraries.sql.go @@ -0,0 +1,130 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: libraries.sql + +package sqlcgen + +import ( + "context" +) + +const countLibraries = `-- name: CountLibraries :one +SELECT COUNT(*) AS count FROM libraries +` + +func (q *Queries) CountLibraries(ctx context.Context) (int64, error) { + row := q.db.QueryRowContext(ctx, countLibraries) + var count int64 + err := row.Scan(&count) + return count, err +} + +const createLibrary = `-- name: CreateLibrary :one +INSERT INTO libraries (name, path) VALUES (?, ?) +RETURNING id, name, path, created_at +` + +type CreateLibraryParams struct { + Name string + Path string +} + +func (q *Queries) CreateLibrary(ctx context.Context, arg CreateLibraryParams) (Library, error) { + row := q.db.QueryRowContext(ctx, createLibrary, arg.Name, arg.Path) + var i Library + err := row.Scan( + &i.ID, + &i.Name, + &i.Path, + &i.CreatedAt, + ) + return i, err +} + +const deleteLibrary = `-- name: DeleteLibrary :exec +DELETE FROM libraries WHERE id = ? +` + +func (q *Queries) DeleteLibrary(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, deleteLibrary, id) + return err +} + +const getAllLibraries = `-- name: GetAllLibraries :many +SELECT id, name, path, created_at FROM libraries ORDER BY name +` + +func (q *Queries) GetAllLibraries(ctx context.Context) ([]Library, error) { + rows, err := q.db.QueryContext(ctx, getAllLibraries) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Library + for rows.Next() { + var i Library + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Path, + &i.CreatedAt, + ); 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 getLibrary = `-- name: GetLibrary :one +SELECT id, name, path, created_at FROM libraries WHERE id = ? LIMIT 1 +` + +func (q *Queries) GetLibrary(ctx context.Context, id int64) (Library, error) { + row := q.db.QueryRowContext(ctx, getLibrary, id) + var i Library + err := row.Scan( + &i.ID, + &i.Name, + &i.Path, + &i.CreatedAt, + ) + return i, err +} + +const getLibraryByPath = `-- name: GetLibraryByPath :one +SELECT id, name, path, created_at FROM libraries WHERE path = ? LIMIT 1 +` + +func (q *Queries) GetLibraryByPath(ctx context.Context, path string) (Library, error) { + row := q.db.QueryRowContext(ctx, getLibraryByPath, path) + var i Library + err := row.Scan( + &i.ID, + &i.Name, + &i.Path, + &i.CreatedAt, + ) + return i, err +} + +const updateLibraryName = `-- name: UpdateLibraryName :exec +UPDATE libraries SET name = ? WHERE id = ? +` + +type UpdateLibraryNameParams struct { + Name string + ID int64 +} + +func (q *Queries) UpdateLibraryName(ctx context.Context, arg UpdateLibraryNameParams) error { + _, err := q.db.ExecContext(ctx, updateLibraryName, arg.Name, arg.ID) + return err +} diff --git a/backend/database/sql/sqlcgen/playlists.sql.go b/backend/database/sql/sqlcgen/playlists.sql.go index e54660a..9c13fe2 100644 --- a/backend/database/sql/sqlcgen/playlists.sql.go +++ b/backend/database/sql/sqlcgen/playlists.sql.go @@ -11,18 +11,38 @@ import ( ) const addPlaylistTrack = `-- name: AddPlaylistTrack :one -INSERT INTO playlist_tracks (playlist_id, audio_file_id, position) VALUES (?, ?, ?) +INSERT INTO playlist_tracks ( + playlist_id, audio_file_id, position, + phantom_title, phantom_artist, phantom_album, + phantom_duration_ms, phantom_genre, phantom_cover_art_path +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id, playlist_id, audio_file_id, position, phantom_title, phantom_artist, phantom_album, phantom_duration_ms, phantom_genre, phantom_cover_art_path ` type AddPlaylistTrackParams struct { - PlaylistID int64 - AudioFileID sql.NullInt64 - Position int64 + PlaylistID int64 + AudioFileID sql.NullInt64 + Position int64 + PhantomTitle sql.NullString + PhantomArtist sql.NullString + PhantomAlbum sql.NullString + PhantomDurationMs sql.NullInt64 + PhantomGenre sql.NullString + PhantomCoverArtPath sql.NullString } func (q *Queries) AddPlaylistTrack(ctx context.Context, arg AddPlaylistTrackParams) (PlaylistTrack, error) { - row := q.db.QueryRowContext(ctx, addPlaylistTrack, arg.PlaylistID, arg.AudioFileID, arg.Position) + row := q.db.QueryRowContext(ctx, addPlaylistTrack, + arg.PlaylistID, + arg.AudioFileID, + arg.Position, + arg.PhantomTitle, + arg.PhantomArtist, + arg.PhantomAlbum, + arg.PhantomDurationMs, + arg.PhantomGenre, + arg.PhantomCoverArtPath, + ) var i PlaylistTrack err := row.Scan( &i.ID, @@ -100,14 +120,15 @@ SELECT pt.playlist_id, pt.audio_file_id, pt.position, - af.file_path, - af.length_milliseconds, - COALESCE(r.name, '') AS title, - COALESCE(ac.text, '') AS artist, - COALESCE(rg.name, '') AS album, - COALESCE(ca.file_path, '') AS cover_art_path + 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, + CASE WHEN pt.audio_file_id IS NULL THEN 1 ELSE 0 END AS is_phantom FROM playlist_tracks pt -JOIN audio_files af ON pt.audio_file_id = af.id +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 ( @@ -131,6 +152,7 @@ type GetAllPlaylistTracksWithMetadataRow struct { Artist string Album string CoverArtPath string + IsPhantom int64 } func (q *Queries) GetAllPlaylistTracksWithMetadata(ctx context.Context) ([]GetAllPlaylistTracksWithMetadataRow, error) { @@ -153,6 +175,7 @@ func (q *Queries) GetAllPlaylistTracksWithMetadata(ctx context.Context) ([]GetAl &i.Artist, &i.Album, &i.CoverArtPath, + &i.IsPhantom, ); err != nil { return nil, err } @@ -228,10 +251,10 @@ func (q *Queries) GetPlaylist(ctx context.Context, id int64) (Playlist, error) { } const getPlaylistTrackFilePaths = `-- name: GetPlaylistTrackFilePaths :many -SELECT af.file_path +SELECT COALESCE(af.file_path, '') AS file_path FROM playlist_tracks pt -JOIN audio_files af ON pt.audio_file_id = af.id -WHERE pt.playlist_id = ? +LEFT JOIN audio_files af ON pt.audio_file_id = af.id +WHERE pt.playlist_id = ? AND pt.audio_file_id IS NOT NULL ORDER BY pt.position ` @@ -259,19 +282,28 @@ func (q *Queries) GetPlaylistTrackFilePaths(ctx context.Context, playlistID int6 } const getPlaylistTracks = `-- name: GetPlaylistTracks :many -SELECT pt.id, pt.playlist_id, pt.audio_file_id, pt.position, af.file_path +SELECT pt.id, pt.playlist_id, pt.audio_file_id, pt.position, + COALESCE(af.file_path, '') AS file_path, + pt.phantom_title, pt.phantom_artist, pt.phantom_album, + pt.phantom_duration_ms, pt.phantom_genre, pt.phantom_cover_art_path FROM playlist_tracks pt -JOIN audio_files af ON pt.audio_file_id = af.id +LEFT JOIN audio_files af ON pt.audio_file_id = af.id WHERE pt.playlist_id = ? ORDER BY pt.position ` type GetPlaylistTracksRow struct { - ID int64 - PlaylistID int64 - AudioFileID sql.NullInt64 - Position int64 - FilePath string + ID int64 + PlaylistID int64 + AudioFileID sql.NullInt64 + Position int64 + FilePath string + PhantomTitle sql.NullString + PhantomArtist sql.NullString + PhantomAlbum sql.NullString + PhantomDurationMs sql.NullInt64 + PhantomGenre sql.NullString + PhantomCoverArtPath sql.NullString } func (q *Queries) GetPlaylistTracks(ctx context.Context, playlistID int64) ([]GetPlaylistTracksRow, error) { @@ -289,6 +321,12 @@ func (q *Queries) GetPlaylistTracks(ctx context.Context, playlistID int64) ([]Ge &i.AudioFileID, &i.Position, &i.FilePath, + &i.PhantomTitle, + &i.PhantomArtist, + &i.PhantomAlbum, + &i.PhantomDurationMs, + &i.PhantomGenre, + &i.PhantomCoverArtPath, ); err != nil { return nil, err } @@ -309,14 +347,15 @@ SELECT pt.playlist_id, pt.audio_file_id, pt.position, - af.file_path, - af.length_milliseconds, - COALESCE(r.name, '') AS title, - COALESCE(ac.text, '') AS artist, - COALESCE(rg.name, '') AS album, - COALESCE(ca.file_path, '') AS cover_art_path + 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, + CASE WHEN pt.audio_file_id IS NULL THEN 1 ELSE 0 END AS is_phantom FROM playlist_tracks pt -JOIN audio_files af ON pt.audio_file_id = af.id +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 ( @@ -341,6 +380,7 @@ type GetPlaylistTracksWithMetadataRow struct { Artist string Album string CoverArtPath string + IsPhantom int64 } func (q *Queries) GetPlaylistTracksWithMetadata(ctx context.Context, playlistID int64) ([]GetPlaylistTracksWithMetadataRow, error) { @@ -363,6 +403,7 @@ func (q *Queries) GetPlaylistTracksWithMetadata(ctx context.Context, playlistID &i.Artist, &i.Album, &i.CoverArtPath, + &i.IsPhantom, ); err != nil { return nil, err } @@ -377,10 +418,60 @@ func (q *Queries) GetPlaylistTracksWithMetadata(ctx context.Context, playlistID return items, nil } +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 = ? +` + +type GetTrackPhantomMetadataRow struct { + Title string + Artist string + Album string + DurationMs int64 + Genre string + CoverArtPath string +} + +func (q *Queries) GetTrackPhantomMetadata(ctx context.Context, id int64) (GetTrackPhantomMetadataRow, error) { + row := q.db.QueryRowContext(ctx, getTrackPhantomMetadata, id) + var i GetTrackPhantomMetadataRow + err := row.Scan( + &i.Title, + &i.Artist, + &i.Album, + &i.DurationMs, + &i.Genre, + &i.CoverArtPath, + ) + return i, err +} + const isTrackInPlaylist = `-- name: IsTrackInPlaylist :one SELECT EXISTS( SELECT 1 FROM playlist_tracks pt - JOIN audio_files af ON pt.audio_file_id = af.id + LEFT JOIN audio_files af ON pt.audio_file_id = af.id WHERE pt.playlist_id = ? AND af.file_path = ? ) AS in_playlist `