fix(database): retire a table whose shape the schema moved past
`applySchema` is CREATE ... IF NOT EXISTS and there is no migration chain, so a *changed* table never migrates: the statement silently no-ops against the old shape. Two plans had already landed on that, and neither showed up in a test because a fresh install is perfectly healthy. - 014 added `total_tracks` to explore_index and to `indexRowFields`, the projection every explore read uses, so every search, browse, artist page and album page failed with "no such column: total_tracks" on any database that already had a catalog. - 013 reshaped audio_files, so applySchema could not run at all and the app did not open. staleshape.go runs before applySchema and drops what disagrees, so the create is a create. It parses sql/schemas/ for the expectation rather than writing the column list down a second time, and it notices a changed *type* as well as a missing column — 013 moved mbid TEXT to BLOB, which no ALTER could express and which SQLite will not coerce, so a query against 16 raw bytes returns no rows rather than an error. Only Authored tables are exempt. Cache is rebuildable by definition, Owned is what a rescan rebuilds (plan 013's stated "delete and rescan"), and a table the schema no longer describes at all goes too -- 013 left seven behind plus schema_migrations. Three things in it are load-bearing, and each was a bug first: - The parser read `UNIQUE(mbid)` as a column, which made a healthy catalog look stale. That would have retired it on every launch and cost every user an artifact download per start. - The drops are one transaction with defer_foreign_keys. Those legacy tables reference each other, so any order fails on whichever goes first; turning foreign keys off instead would suppress playlist_tracks.audio_file_id's ON DELETE SET NULL and leave entries pointing at ids a rescan reissues to *different songs*. Nulled entries are empty; stale ones are wrong, and wrong quietly. - The order is sorted, so a failure reproduces. Map order is random, and the foreign-key bug passed its own regression test on two runs in three until the order was fixed. Verified against a real pre-013 install: it opens, its 22 playlists survive, 1,887 linked playlist entries become 0 rather than dangling, and the legacy tables are swept. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AfVYUVExXsx1nSWrXN8mAh
This commit is contained in:
@@ -53,10 +53,19 @@ func TestRetireLibraryTables(t *testing.T) {
|
||||
CREATE TABLE recordings (id INTEGER PRIMARY KEY, title TEXT);
|
||||
`)
|
||||
|
||||
// The symptom, before the repair: the schema cannot be applied over
|
||||
// a table whose shape has moved on.
|
||||
if _, err := database.NewDB(logger); err == nil {
|
||||
t.Fatal("expected the stale shape to fail to open; it did not")
|
||||
// This used to assert the symptom -- that the schema cannot be
|
||||
// applied over a table whose shape has moved on -- because at the
|
||||
// time nothing repaired it and only this job did. The app-side
|
||||
// repair (backend/database/staleshape.go) now retires a stale
|
||||
// non-authored table before applySchema meets it, so opening
|
||||
// succeeds and the symptom no longer reproduces from here.
|
||||
//
|
||||
// That does not make retireLibraryTables redundant, and the rest of
|
||||
// this test is why: the app-side repair only removes what is *stale*,
|
||||
// while this database wants its library half gone entirely, healthy
|
||||
// or not, because nothing here scans, plays or authors.
|
||||
if _, err := database.NewDB(logger); err != nil {
|
||||
t.Fatalf("the app-side repair should have opened this: %v", err)
|
||||
}
|
||||
|
||||
if err := retireLibraryTables(context.Background(), logger); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user