diff --git a/backend/database/sql/queries/audio_files.sql b/backend/database/sql/queries/audio_files.sql index 25e7b32..db72601 100644 --- a/backend/database/sql/queries/audio_files.sql +++ b/backend/database/sql/queries/audio_files.sql @@ -58,6 +58,7 @@ JOIN artist_credit ac ON r.artist_credit_id = ac.id; -- name: GetTrackMetadataByPath :one SELECT af.file_path, + af.length_milliseconds, COALESCE(r.name, '') AS title, COALESCE(ac.text, '') AS artist, COALESCE(rg.name, '') AS album, @@ -121,6 +122,11 @@ LEFT JOIN release_groups rg ON rgr.release_group_id = rg.id WHERE af.basename = ? LIMIT ?; +-- name: LookupTrackMetaByPaths :many +SELECT id, file_path, title, artist_name +FROM track_metadata +WHERE file_path IN (sqlc.slice('paths')); + -- name: DeleteAllAudioFiles :exec DELETE FROM audio_files; diff --git a/backend/database/sql/sqlcgen/audio_files.sql.go b/backend/database/sql/sqlcgen/audio_files.sql.go index 9aa2750..f3bdb46 100644 --- a/backend/database/sql/sqlcgen/audio_files.sql.go +++ b/backend/database/sql/sqlcgen/audio_files.sql.go @@ -8,6 +8,7 @@ package sqlcgen import ( "context" "database/sql" + "strings" ) const countAudioFiles = `-- name: CountAudioFiles :one @@ -464,6 +465,7 @@ func (q *Queries) GetRandomAudioFilePath(ctx context.Context) (string, error) { const getTrackMetadataByPath = `-- name: GetTrackMetadataByPath :one SELECT af.file_path, + af.length_milliseconds, COALESCE(r.name, '') AS title, COALESCE(ac.text, '') AS artist, COALESCE(rg.name, '') AS album, @@ -479,11 +481,12 @@ LIMIT 1 ` type GetTrackMetadataByPathRow struct { - FilePath string - Title string - Artist string - Album string - CoverArtPath string + FilePath string + LengthMilliseconds int64 + Title string + Artist string + Album string + CoverArtPath string } func (q *Queries) GetTrackMetadataByPath(ctx context.Context, filePath string) (GetTrackMetadataByPathRow, error) { @@ -491,6 +494,7 @@ func (q *Queries) GetTrackMetadataByPath(ctx context.Context, filePath string) ( var i GetTrackMetadataByPathRow err := row.Scan( &i.FilePath, + &i.LengthMilliseconds, &i.Title, &i.Artist, &i.Album, @@ -499,6 +503,57 @@ func (q *Queries) GetTrackMetadataByPath(ctx context.Context, filePath string) ( return i, err } +const lookupTrackMetaByPaths = `-- name: LookupTrackMetaByPaths :many +SELECT id, file_path, title, artist_name +FROM track_metadata +WHERE file_path IN (/*SLICE:paths*/?) +` + +type LookupTrackMetaByPathsRow struct { + ID int64 + FilePath string + Title string + ArtistName string +} + +func (q *Queries) LookupTrackMetaByPaths(ctx context.Context, paths []string) ([]LookupTrackMetaByPathsRow, error) { + query := lookupTrackMetaByPaths + var queryParams []interface{} + if len(paths) > 0 { + for _, v := range paths { + queryParams = append(queryParams, v) + } + query = strings.Replace(query, "/*SLICE:paths*/?", strings.Repeat(",?", len(paths))[1:], 1) + } else { + query = strings.Replace(query, "/*SLICE:paths*/?", "NULL", 1) + } + rows, err := q.db.QueryContext(ctx, query, queryParams...) + if err != nil { + return nil, err + } + defer rows.Close() + var items []LookupTrackMetaByPathsRow + for rows.Next() { + var i LookupTrackMetaByPathsRow + if err := rows.Scan( + &i.ID, + &i.FilePath, + &i.Title, + &i.ArtistName, + ); 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 searchAudioFilesByBasename = `-- name: SearchAudioFilesByBasename :many SELECT af.file_path, diff --git a/backend/queue/persistence.go b/backend/queue/persistence.go index 3657a68..42e3dc3 100644 --- a/backend/queue/persistence.go +++ b/backend/queue/persistence.go @@ -44,7 +44,9 @@ func (q *Queue) lookupTrackMetaBatch( return result } -// lookupChunk executes a single batch query for a chunk of file paths. +// lookupChunk executes a single batch query for a chunk of file paths +// using the sqlc-generated LookupTrackMetaByPaths query against the +// track_metadata VIEW. func (q *Queue) lookupChunk( paths []string, result map[string]trackMeta, @@ -53,63 +55,20 @@ func (q *Queue) lookupChunk( return } - placeholders := make([]string, len(paths)) - args := make([]any, len(paths)) - - for i, fp := range paths { - placeholders[i] = "?" - args[i] = fp - } - - query := fmt.Sprintf( - `SELECT af.id, af.file_path, - COALESCE(r.name, '') AS title, - COALESCE(ac.text, '') AS artist - 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 - WHERE af.file_path IN (%s)`, - strings.Join(placeholders, ","), - ) - - rows, err := q.db.QueryContext(query, args...) + rows, err := q.db.Queries.LookupTrackMetaByPaths(q.db.Ctx, paths) if err != nil { q.logger.Error("Batch metadata lookup failed", "err", err) return } - defer func() { - if closeErr := rows.Close(); closeErr != nil { - q.logger.Error( - "Failed to close rows", - "err", closeErr, - ) + for _, row := range rows { + result[row.FilePath] = trackMeta{ + AudioFileID: row.ID, + FilePath: row.FilePath, + Title: row.Title, + Artist: row.ArtistName, } - }() - - for rows.Next() { - var m trackMeta - - if scanErr := rows.Scan( - &m.AudioFileID, &m.FilePath, &m.Title, &m.Artist, - ); scanErr != nil { - q.logger.Error( - "Failed to scan batch metadata row", - "err", scanErr, - ) - - continue - } - - result[m.FilePath] = m - } - - if rowsErr := rows.Err(); rowsErr != nil { - q.logger.Error( - "Error iterating batch metadata rows", - "err", rowsErr, - ) } }