feat(06-01): create track_metadata VIEW schema and migration 4

- Add track_metadata_view.sql for sqlc VIEW awareness
- Add migration4TrackMetadataView for existing databases
- sqlc generate produces TrackMetadatum model from VIEW
- migration2 inline JOIN preserved (runs before VIEW exists)
This commit is contained in:
2026-03-04 19:21:58 -05:00
parent e8cca7c942
commit 9c7e5a9634
3 changed files with 135 additions and 0 deletions
+79
View File
@@ -276,6 +276,15 @@ func runMigrations(
logger.Info("migration 3 complete")
}
// Migration 4: create track_metadata VIEW.
if version < 4 {
if err := migration4TrackMetadataView(
ctx, db, logger,
); err != nil {
return err
}
}
return nil
}
@@ -379,6 +388,76 @@ func migration2BasenameAndFTS(
return nil
}
// migration4TrackMetadataView creates the track_metadata VIEW that
// consolidates the 5-table JOIN used by FTS5 search queries.
// Fresh databases get the VIEW from the embedded schema file;
// this migration covers databases created before the VIEW existed.
func migration4TrackMetadataView(
ctx context.Context,
db *sql.DB,
logger *slog.Logger,
) error {
logger.Info(
"applying migration 4: track_metadata VIEW",
)
if _, err := db.ExecContext(ctx, `
CREATE VIEW IF NOT EXISTS track_metadata AS
SELECT
af.id,
af.file_path,
af.length_milliseconds,
COALESCE(r.name, '') AS title,
COALESCE(ac.text, '') AS artist_name,
r.track_number,
r.disc_number,
COALESCE(rg.name, '') AS album,
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(r.year, 0) AS year,
COALESCE(r.composer, '') AS composer,
COALESCE(ft.extension, '') AS file_type,
af.sample_rate,
af.bit_depth,
af.channels,
af.bitrate,
af.file_size
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 file_types ft ON af.file_type_id = ft.id
`); err != nil {
return fmt.Errorf(
"migration 4: could not create track_metadata VIEW: %w",
err,
)
}
if _, err := db.ExecContext(
ctx, "PRAGMA user_version = 4",
); err != nil {
return fmt.Errorf(
"could not set user_version to 4: %w", err,
)
}
logger.Info("migration 4 complete")
return nil
}
// isDuplicateColumnErr returns true when the error is SQLite's
// "duplicate column name" error from an ALTER TABLE ADD COLUMN
// on a column that already exists.
@@ -0,0 +1,36 @@
CREATE VIEW IF NOT EXISTS track_metadata AS
SELECT
af.id,
af.file_path,
af.length_milliseconds,
COALESCE(r.name, '') AS title,
COALESCE(ac.text, '') AS artist_name,
r.track_number,
r.disc_number,
COALESCE(rg.name, '') AS album,
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(r.year, 0) AS year,
COALESCE(r.composer, '') AS composer,
COALESCE(ft.extension, '') AS file_type,
af.sample_rate,
af.bit_depth,
af.channels,
af.bitrate,
af.file_size
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 file_types ft ON af.file_type_id = ft.id;
+20
View File
@@ -136,3 +136,23 @@ type SearchIndex struct {
Artist string
Album string
}
type TrackMetadatum struct {
ID int64
FilePath string
LengthMilliseconds int64
Title string
ArtistName string
TrackNumber sql.NullInt64
DiscNumber sql.NullInt64
Album string
Genre string
Year int64
Composer string
FileType string
SampleRate int64
BitDepth int64
Channels int64
Bitrate int64
FileSize int64
}