Fix pairs disappearing: renumber the auto-cleanup migration to 12
The reverted "missing from master" feature had shipped migration 11, so databases that ran it skipped the new auto_cleanup_empty migration (also 11) and every pairs query failed, showing no libraries. Restore the old 11 as an unused placeholder like 6-10 and add the column as 12; a database that ran it under the old number counts it as applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
faebc5dfb0
commit
c8e7d14677
2 files changed
+60
-3
No files matched your search
+16
-3
@@ -6,6 +6,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
@@ -119,7 +120,7 @@ var migrations = []string{
|
|||||||
// undo must then leave that target alone.
|
// undo must then leave that target alone.
|
||||||
`ALTER TABLE operations ADD COLUMN link_preexisted INTEGER NOT NULL DEFAULT 0;`,
|
`ALTER TABLE operations ADD COLUMN link_preexisted INTEGER NOT NULL DEFAULT 0;`,
|
||||||
|
|
||||||
// 6-10 belong to features that were removed again (tag editing,
|
// 6-11 belong to features that were removed again (tag editing,
|
||||||
// organizing, the step-by-step fixer). They only add columns, which are
|
// organizing, the step-by-step fixer). They only add columns, which are
|
||||||
// unused now, and stay so databases that already ran them keep the same
|
// unused now, and stay so databases that already ran them keep the same
|
||||||
// numbering and future migrations are never skipped.
|
// numbering and future migrations are never skipped.
|
||||||
@@ -141,10 +142,22 @@ var migrations = []string{
|
|||||||
`ALTER TABLE pairs ADD COLUMN library_type TEXT NOT NULL DEFAULT 'music';
|
`ALTER TABLE pairs ADD COLUMN library_type TEXT NOT NULL DEFAULT 'music';
|
||||||
ALTER TABLE pairs ADD COLUMN fixer_state TEXT NOT NULL DEFAULT '';`,
|
ALTER TABLE pairs ADD COLUMN fixer_state TEXT NOT NULL DEFAULT '';`,
|
||||||
|
|
||||||
// 11: opt-in automatic removal of empty slave folders after each scan.
|
// 11: slave-only songs whose album or artist the master has (removed).
|
||||||
|
`ALTER TABLE matches ADD COLUMN missing TEXT NOT NULL DEFAULT '';`,
|
||||||
|
|
||||||
|
// 12: opt-in automatic removal of empty slave folders after each scan.
|
||||||
|
// For a few hours this was numbered 11, so databases that ran it under
|
||||||
|
// that number already have the column; see addsExistingColumn.
|
||||||
`ALTER TABLE pairs ADD COLUMN auto_cleanup_empty INTEGER NOT NULL DEFAULT 0;`,
|
`ALTER TABLE pairs ADD COLUMN auto_cleanup_empty INTEGER NOT NULL DEFAULT 0;`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addsExistingColumn reports whether a migration failed only because the
|
||||||
|
// single column it adds is already there, so it counts as applied.
|
||||||
|
func addsExistingColumn(m string, err error) bool {
|
||||||
|
return strings.Count(strings.TrimSpace(m), ";") == 1 && strings.Contains(m, "ADD COLUMN") &&
|
||||||
|
strings.Contains(err.Error(), "duplicate column name")
|
||||||
|
}
|
||||||
|
|
||||||
// SchemaVersion is the newest database schema this build knows.
|
// SchemaVersion is the newest database schema this build knows.
|
||||||
func SchemaVersion() int { return len(migrations) }
|
func SchemaVersion() int { return len(migrations) }
|
||||||
|
|
||||||
@@ -178,7 +191,7 @@ func (s *Store) migrate() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := tx.Exec(migrations[i]); err != nil {
|
if _, err := tx.Exec(migrations[i]); err != nil && !addsExistingColumn(migrations[i], err) {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return fmt.Errorf("migration %d: %w", i+1, err)
|
return fmt.Errorf("migration %d: %w", i+1, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package store
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -57,3 +59,45 @@ func TestMatchLetters(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Databases reached schema 11 two ways: the removed "missing from master"
|
||||||
|
// column, or (briefly) auto_cleanup_empty numbered 11. Both must open with
|
||||||
|
// every current column, keeping their pairs.
|
||||||
|
func TestMigrateFromEitherSchema11(t *testing.T) {
|
||||||
|
for name, eleven := range map[string]string{
|
||||||
|
"missing": `ALTER TABLE matches ADD COLUMN missing TEXT NOT NULL DEFAULT '';`,
|
||||||
|
"auto cleanup": `ALTER TABLE pairs ADD COLUMN auto_cleanup_empty INTEGER NOT NULL DEFAULT 0;`,
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
db, err := sql.Open("sqlite", "file:"+filepath.Join(dir, "playlink.db"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
old := append(append([]string{}, migrations[:10]...), eleven,
|
||||||
|
`INSERT INTO pairs (name, master_root, slave_root, quarantine_dir, extensions, created_at) VALUES ('Music', '/m', '/s', '/q', 'flac', 1)`)
|
||||||
|
stmts := []string{`CREATE TABLE schema_version (v INTEGER NOT NULL)`, `INSERT INTO schema_version (v) VALUES (11)`}
|
||||||
|
for _, m := range append(stmts, old...) {
|
||||||
|
if _, err := db.Exec(m); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.Close()
|
||||||
|
|
||||||
|
st, err := Open(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer st.DB.Close()
|
||||||
|
ps, err := st.Pairs(context.Background())
|
||||||
|
if err != nil || len(ps) != 1 || ps[0].Name != "Music" {
|
||||||
|
t.Fatalf("pairs = %+v, %v", ps, err)
|
||||||
|
}
|
||||||
|
var v int
|
||||||
|
st.DB.QueryRow(`SELECT MAX(v) FROM schema_version`).Scan(&v)
|
||||||
|
if v != SchemaVersion() {
|
||||||
|
t.Fatalf("schema %d, want %d", v, SchemaVersion())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user