diff --git a/db/migrations/20260720015443_uniform_canonical_ids.go b/db/migrations/20260720015443_uniform_canonical_ids.go index e15fe4396..a55b0932f 100644 --- a/db/migrations/20260720015443_uniform_canonical_ids.go +++ b/db/migrations/20260720015443_uniform_canonical_ids.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/navidrome/navidrome/conf" "github.com/pressly/goose/v3" ) @@ -64,6 +65,13 @@ func upUniformCanonicalIds(ctx context.Context, tx *sql.Tx) error { if err := rewriteJSONColumn(ctx, tx, "playlist", "rules", canonicalizePlaylistRules); err != nil { return err } + // Legacy PID specs embed old-shaped album/track ids into composite pids; a full rescan + // rewrites every pid with the new encoding so path-based move matching stays consistent. + if strings.Contains(conf.Server.PID.Track, "legacy") || strings.Contains(conf.Server.PID.Album, "legacy") { + if err := forceFullRescan(ctx, tx); err != nil { + return err + } + } _, err := tx.ExecContext(ctx, "DROP TABLE _id_map") return err } diff --git a/db/migrations/uniform_canonical_ids_test.go b/db/migrations/uniform_canonical_ids_test.go index 345579931..58fbc5543 100644 --- a/db/migrations/uniform_canonical_ids_test.go +++ b/db/migrations/uniform_canonical_ids_test.go @@ -6,6 +6,8 @@ import ( "encoding/json" _ "github.com/mattn/go-sqlite3" + "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/consts" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -60,6 +62,7 @@ var _ = Describe("upUniformCanonicalIds", func() { CREATE TABLE album_artists (album_id text, artist_id text); CREATE TABLE library_tag (tag_id text, library_id integer); CREATE TABLE plugin (id text, users text); + CREATE TABLE property (id text primary key, value text); `) Expect(err).ToNot(HaveOccurred()) @@ -86,7 +89,10 @@ var _ = Describe("upUniformCanonicalIds", func() { // malformed JSON in both a plugin list and a playlist rule: must pass through byte-for-byte seed(`INSERT INTO plugin VALUES ('broken', 'not-json')`) seed(`INSERT INTO playlist VALUES (?, ?, '{broken')`, hashID, hashID) + }) + JustBeforeEach(func() { + var err error tx, err = db.Begin() Expect(err).ToNot(HaveOccurred()) Expect(upUniformCanonicalIds(ctx, tx)).To(Succeed()) @@ -175,4 +181,27 @@ var _ = Describe("upUniformCanonicalIds", func() { Expect(get(`SELECT users FROM plugin WHERE id='broken'`)).To(Equal("not-json")) Expect(get(`SELECT rules FROM playlist WHERE id='` + hashID + `'`)).To(Equal("{broken")) }) + + rescanCount := func() int { + var count int + ExpectWithOffset(1, db.QueryRow( + `SELECT count(*) FROM property WHERE id = ?`, consts.FullScanAfterMigrationFlagKey).Scan(&count)).To(Succeed()) + return count + } + + It("does not force a full rescan for the default PID config", func() { + Expect(rescanCount()).To(Equal(0)) + }) + + Context("with a legacy PID configuration", func() { + BeforeEach(func() { + prev := conf.Server.PID.Album + conf.Server.PID.Album = "album_legacy" + DeferCleanup(func() { conf.Server.PID.Album = prev }) + }) + + It("forces a full rescan so composite pids are rewritten", func() { + Expect(rescanCount()).To(Equal(1)) + }) + }) })