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
}
+113 -22
View File
@@ -11,7 +11,11 @@ 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
` `
@@ -19,10 +23,26 @@ 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,9 +282,12 @@ 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
` `
@@ -272,6 +298,12 @@ type GetPlaylistTracksRow struct {
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
` `