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
This commit is contained in:
2026-03-09 09:47:30 -04:00
parent 2c77368482
commit 02548dd55e
6 changed files with 385 additions and 51 deletions
@@ -127,6 +127,12 @@ SELECT id, file_path, title, artist_name
FROM track_metadata FROM track_metadata
WHERE file_path IN (sqlc.slice('paths')); 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 -- name: DeleteAllAudioFiles :exec
DELETE FROM audio_files; DELETE FROM audio_files;
@@ -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;
+56 -21
View File
@@ -18,13 +18,20 @@ DELETE FROM playlists WHERE id = ?;
SELECT COUNT(*) AS count FROM playlists WHERE name = ?; SELECT COUNT(*) AS count FROM playlists WHERE name = ?;
-- name: AddPlaylistTrack :one -- 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 *; RETURNING *;
-- name: GetPlaylistTracks :many -- 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 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 = ? WHERE pt.playlist_id = ?
ORDER BY pt.position; ORDER BY pt.position;
@@ -40,14 +47,15 @@ SELECT
pt.playlist_id, pt.playlist_id,
pt.audio_file_id, pt.audio_file_id,
pt.position, pt.position,
af.file_path, COALESCE(af.file_path, '') AS file_path,
af.length_milliseconds, COALESCE(af.length_milliseconds, 0) AS length_milliseconds,
COALESCE(r.name, '') AS title, COALESCE(r.name, pt.phantom_title, '') AS title,
COALESCE(ac.text, '') AS artist, COALESCE(ac.text, pt.phantom_artist, '') AS artist,
COALESCE(rg.name, '') AS album, COALESCE(rg.name, pt.phantom_album, '') AS album,
COALESCE(ca.file_path, '') AS cover_art_path 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 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 recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN ( LEFT JOIN (
@@ -66,14 +74,15 @@ SELECT
pt.playlist_id, pt.playlist_id,
pt.audio_file_id, pt.audio_file_id,
pt.position, pt.position,
af.file_path, COALESCE(af.file_path, '') AS file_path,
af.length_milliseconds, COALESCE(af.length_milliseconds, 0) AS length_milliseconds,
COALESCE(r.name, '') AS title, COALESCE(r.name, pt.phantom_title, '') AS title,
COALESCE(ac.text, '') AS artist, COALESCE(ac.text, pt.phantom_artist, '') AS artist,
COALESCE(rg.name, '') AS album, COALESCE(rg.name, pt.phantom_album, '') AS album,
COALESCE(ca.file_path, '') AS cover_art_path 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 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 recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN ( LEFT JOIN (
@@ -93,16 +102,16 @@ SELECT COALESCE(MAX(position), -1) + 1 AS next_position
FROM playlist_tracks WHERE playlist_id = ?; FROM playlist_tracks WHERE playlist_id = ?;
-- name: GetPlaylistTrackFilePaths :many -- name: GetPlaylistTrackFilePaths :many
SELECT af.file_path SELECT COALESCE(af.file_path, '') AS file_path
FROM playlist_tracks pt 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 = ? WHERE pt.playlist_id = ? AND pt.audio_file_id IS NOT NULL
ORDER BY pt.position; ORDER BY pt.position;
-- name: IsTrackInPlaylist :one -- name: IsTrackInPlaylist :one
SELECT EXISTS( SELECT EXISTS(
SELECT 1 FROM playlist_tracks pt 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 = ? WHERE pt.playlist_id = ? AND af.file_path = ?
) AS in_playlist; ) AS in_playlist;
@@ -111,3 +120,29 @@ DELETE FROM playlist_tracks
WHERE playlist_id = ? AND audio_file_id = ( WHERE playlist_id = ? AND audio_file_id = (
SELECT id FROM audio_files WHERE file_path = ? 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 = ?;
@@ -22,6 +22,17 @@ func (q *Queries) CountAudioFiles(ctx context.Context) (int64, error) {
return count, err 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 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 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 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 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 const getAudioFilesByReleaseGroup = `-- name: GetAudioFilesByReleaseGroup :many
SELECT SELECT
af.file_path, af.file_path,
@@ -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
}
+121 -30
View File
@@ -11,18 +11,38 @@ import (
) )
const addPlaylistTrack = `-- name: AddPlaylistTrack :one 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 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 { type AddPlaylistTrackParams struct {
PlaylistID int64 PlaylistID int64
AudioFileID sql.NullInt64 AudioFileID sql.NullInt64
Position int64 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) { 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 var i PlaylistTrack
err := row.Scan( err := row.Scan(
&i.ID, &i.ID,
@@ -100,14 +120,15 @@ SELECT
pt.playlist_id, pt.playlist_id,
pt.audio_file_id, pt.audio_file_id,
pt.position, pt.position,
af.file_path, COALESCE(af.file_path, '') AS file_path,
af.length_milliseconds, COALESCE(af.length_milliseconds, 0) AS length_milliseconds,
COALESCE(r.name, '') AS title, COALESCE(r.name, pt.phantom_title, '') AS title,
COALESCE(ac.text, '') AS artist, COALESCE(ac.text, pt.phantom_artist, '') AS artist,
COALESCE(rg.name, '') AS album, COALESCE(rg.name, pt.phantom_album, '') AS album,
COALESCE(ca.file_path, '') AS cover_art_path 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 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 recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN ( LEFT JOIN (
@@ -131,6 +152,7 @@ type GetAllPlaylistTracksWithMetadataRow struct {
Artist string Artist string
Album string Album string
CoverArtPath string CoverArtPath string
IsPhantom int64
} }
func (q *Queries) GetAllPlaylistTracksWithMetadata(ctx context.Context) ([]GetAllPlaylistTracksWithMetadataRow, error) { func (q *Queries) GetAllPlaylistTracksWithMetadata(ctx context.Context) ([]GetAllPlaylistTracksWithMetadataRow, error) {
@@ -153,6 +175,7 @@ func (q *Queries) GetAllPlaylistTracksWithMetadata(ctx context.Context) ([]GetAl
&i.Artist, &i.Artist,
&i.Album, &i.Album,
&i.CoverArtPath, &i.CoverArtPath,
&i.IsPhantom,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@@ -228,10 +251,10 @@ func (q *Queries) GetPlaylist(ctx context.Context, id int64) (Playlist, error) {
} }
const getPlaylistTrackFilePaths = `-- name: GetPlaylistTrackFilePaths :many const getPlaylistTrackFilePaths = `-- name: GetPlaylistTrackFilePaths :many
SELECT af.file_path SELECT COALESCE(af.file_path, '') AS file_path
FROM playlist_tracks pt 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 = ? WHERE pt.playlist_id = ? AND pt.audio_file_id IS NOT NULL
ORDER BY pt.position ORDER BY pt.position
` `
@@ -259,19 +282,28 @@ func (q *Queries) GetPlaylistTrackFilePaths(ctx context.Context, playlistID int6
} }
const getPlaylistTracks = `-- name: GetPlaylistTracks :many 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 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 = ? WHERE pt.playlist_id = ?
ORDER BY pt.position ORDER BY pt.position
` `
type GetPlaylistTracksRow struct { type GetPlaylistTracksRow struct {
ID int64 ID int64
PlaylistID int64 PlaylistID int64
AudioFileID sql.NullInt64 AudioFileID sql.NullInt64
Position int64 Position int64
FilePath string 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) { 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.AudioFileID,
&i.Position, &i.Position,
&i.FilePath, &i.FilePath,
&i.PhantomTitle,
&i.PhantomArtist,
&i.PhantomAlbum,
&i.PhantomDurationMs,
&i.PhantomGenre,
&i.PhantomCoverArtPath,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@@ -309,14 +347,15 @@ SELECT
pt.playlist_id, pt.playlist_id,
pt.audio_file_id, pt.audio_file_id,
pt.position, pt.position,
af.file_path, COALESCE(af.file_path, '') AS file_path,
af.length_milliseconds, COALESCE(af.length_milliseconds, 0) AS length_milliseconds,
COALESCE(r.name, '') AS title, COALESCE(r.name, pt.phantom_title, '') AS title,
COALESCE(ac.text, '') AS artist, COALESCE(ac.text, pt.phantom_artist, '') AS artist,
COALESCE(rg.name, '') AS album, COALESCE(rg.name, pt.phantom_album, '') AS album,
COALESCE(ca.file_path, '') AS cover_art_path 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 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 recordings r ON af.recording_id = r.id
LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id
LEFT JOIN ( LEFT JOIN (
@@ -341,6 +380,7 @@ type GetPlaylistTracksWithMetadataRow struct {
Artist string Artist string
Album string Album string
CoverArtPath string CoverArtPath string
IsPhantom int64
} }
func (q *Queries) GetPlaylistTracksWithMetadata(ctx context.Context, playlistID int64) ([]GetPlaylistTracksWithMetadataRow, error) { 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.Artist,
&i.Album, &i.Album,
&i.CoverArtPath, &i.CoverArtPath,
&i.IsPhantom,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@@ -377,10 +418,60 @@ func (q *Queries) GetPlaylistTracksWithMetadata(ctx context.Context, playlistID
return items, nil 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 const isTrackInPlaylist = `-- name: IsTrackInPlaylist :one
SELECT EXISTS( SELECT EXISTS(
SELECT 1 FROM playlist_tracks pt 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 = ? WHERE pt.playlist_id = ? AND af.file_path = ?
) AS in_playlist ) AS in_playlist
` `