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:
@@ -276,6 +276,15 @@ func runMigrations(
|
|||||||
logger.Info("migration 3 complete")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,6 +388,76 @@ func migration2BasenameAndFTS(
|
|||||||
return nil
|
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
|
// isDuplicateColumnErr returns true when the error is SQLite's
|
||||||
// "duplicate column name" error from an ALTER TABLE ADD COLUMN
|
// "duplicate column name" error from an ALTER TABLE ADD COLUMN
|
||||||
// on a column that already exists.
|
// 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;
|
||||||
@@ -136,3 +136,23 @@ type SearchIndex struct {
|
|||||||
Artist string
|
Artist string
|
||||||
Album 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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user