diff --git a/persistence/album_repository.go b/persistence/album_repository.go index 0047dcacb..2ee94973b 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -75,7 +75,7 @@ func NewAlbumRepository(ctx context.Context, db dbx.Builder) model.AlbumReposito "artist": "compilation asc, COALESCE(NULLIF(sort_album_artist_name,''),order_album_artist_name) asc, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc", "album_artist": "compilation asc, COALESCE(NULLIF(sort_album_artist_name,''),order_album_artist_name) asc, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc", "max_year": "coalesce(nullif(original_date,''), cast(max_year as text)), release_date, name, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc", - "random": r.seededRandomSort(), + "random": "random", "recently_added": recentlyAddedSort(), "starred_at": "starred, starred_at", } @@ -85,7 +85,7 @@ func NewAlbumRepository(ctx context.Context, db dbx.Builder) model.AlbumReposito "artist": "compilation asc, order_album_artist_name asc, order_album_name asc", "album_artist": "compilation asc, order_album_artist_name asc, order_album_name asc", "max_year": "coalesce(nullif(original_date,''), cast(max_year as text)), release_date, name, order_album_name asc", - "random": r.seededRandomSort(), + "random": "random", "recently_added": recentlyAddedSort(), "starred_at": "starred, starred_at", } diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index a63094cec..39fa4a34f 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -36,7 +36,7 @@ func NewMediaFileRepository(ctx context.Context, db dbx.Builder) *mediaFileRepos "title": "COALESCE(NULLIF(sort_title,''),title)", "artist": "COALESCE(NULLIF(sort_artist_name,''),order_artist_name) asc, COALESCE(NULLIF(sort_album_name,''),order_album_name) asc, release_date asc, disc_number asc, track_number asc", "album": "COALESCE(NULLIF(sort_album_name,''),order_album_name) asc, release_date asc, disc_number asc, track_number asc, COALESCE(NULLIF(sort_artist_name,''),order_artist_name) asc, COALESCE(NULLIF(sort_title,''),title) asc", - "random": r.seededRandomSort(), + "random": "random", "created_at": "media_file.created_at", "track_number": "album, release_date, disc_number, track_number", "starred_at": "starred, starred_at", @@ -46,7 +46,7 @@ func NewMediaFileRepository(ctx context.Context, db dbx.Builder) *mediaFileRepos "title": "order_title", "artist": "order_artist_name asc, order_album_name asc, release_date asc, disc_number asc, track_number asc", "album": "order_album_name asc, release_date asc, disc_number asc, track_number asc, order_artist_name asc, title asc", - "random": r.seededRandomSort(), + "random": "random", "created_at": "media_file.created_at", "track_number": "album, release_date, disc_number, track_number", "starred_at": "starred, starred_at", diff --git a/persistence/sql_base_repository.go b/persistence/sql_base_repository.go index d43375a58..f5c33978b 100644 --- a/persistence/sql_base_repository.go +++ b/persistence/sql_base_repository.go @@ -167,20 +167,15 @@ func (r sqlRepository) seedKey() string { return r.tableName + userId(r.ctx) } -func (r sqlRepository) seededRandomSort() string { - return fmt.Sprintf("SEEDEDRAND('%s', %s.id)", r.seedKey(), r.tableName) -} - func (r sqlRepository) resetSeededRandom(options []model.QueryOptions) { - if len(options) == 0 || !strings.HasPrefix(options[0].Sort, "SEEDEDRAND(") { + if len(options) == 0 || options[0].Sort != "random" { return } - + options[0].Sort = fmt.Sprintf("SEEDEDRAND('%s', %s.id)", r.seedKey(), r.tableName) if options[0].Seed != "" { hasher.SetSeed(r.seedKey(), options[0].Seed) return } - if options[0].Offset == 0 { hasher.Reseed(r.seedKey()) } diff --git a/persistence/sql_base_repository_test.go b/persistence/sql_base_repository_test.go index 35d802d98..4b380e298 100644 --- a/persistence/sql_base_repository_test.go +++ b/persistence/sql_base_repository_test.go @@ -6,16 +6,25 @@ import ( "github.com/Masterminds/squirrel" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" + "github.com/navidrome/navidrome/utils/hasher" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("sqlRepository", func() { - r := sqlRepository{} + var r sqlRepository + BeforeEach(func() { + r.ctx = request.WithUser(context.Background(), model.User{ID: "user-id"}) + r.tableName = "table" + }) + Describe("applyOptions", func() { var sq squirrel.SelectBuilder BeforeEach(func() { sq = squirrel.Select("*").From("test") + r.sortMappings = map[string]string{ + "name": "title", + } }) It("does not add any clauses when options is empty", func() { sq = r.applyOptions(sq, model.QueryOptions{}) @@ -30,17 +39,11 @@ var _ = Describe("sqlRepository", func() { Offset: 2, }) sql, _, _ := sq.ToSql() - Expect(sql).To(Equal("SELECT * FROM test ORDER BY name desc LIMIT 1 OFFSET 2")) + Expect(sql).To(Equal("SELECT * FROM test ORDER BY title desc LIMIT 1 OFFSET 2")) }) }) Describe("toSQL", func() { - var r sqlRepository - - BeforeEach(func() { - r = sqlRepository{} - }) - It("returns error for invalid SQL", func() { sq := squirrel.Select("*").From("test").Where(1) _, _, err := r.toSQL(sq) @@ -175,13 +178,37 @@ var _ = Describe("sqlRepository", func() { Expect(sql).To(Equal("name asc, coalesce(nullif(release_date, ''), nullif(original_date, '')) desc, status desc")) }) }) - Context("seededRandomSort", func() { - It("returns a random sort order function call", func() { - r.ctx = request.WithUser(context.Background(), model.User{ID: "2"}) - r.tableName = "media_file" - sql := r.seededRandomSort() - Expect(sql).To(ContainSubstring("SEEDEDRAND('media_file2', media_file.id)")) - }) + }) + + Describe("resetSeededRandom", func() { + var id string + BeforeEach(func() { + id = r.seedKey() + hasher.SetSeed(id, "") + }) + It("does not reset seed if sort is not random", func() { + var options []model.QueryOptions + r.resetSeededRandom(options) + Expect(hasher.CurrentSeed(id)).To(BeEmpty()) + }) + It("resets seed if sort is random", func() { + options := []model.QueryOptions{{Sort: "random"}} + r.resetSeededRandom(options) + Expect(hasher.CurrentSeed(id)).NotTo(BeEmpty()) + }) + It("resets seed if sort is random and seed is provided", func() { + options := []model.QueryOptions{{Sort: "random", Seed: "seed"}} + r.resetSeededRandom(options) + Expect(hasher.CurrentSeed(id)).To(Equal("seed")) + }) + It("keeps seed when paginating", func() { + options := []model.QueryOptions{{Sort: "random", Seed: "seed", Offset: 0}} + r.resetSeededRandom(options) + Expect(hasher.CurrentSeed(id)).To(Equal("seed")) + + options = []model.QueryOptions{{Sort: "random", Offset: 1}} + r.resetSeededRandom(options) + Expect(hasher.CurrentSeed(id)).To(Equal("seed")) }) }) }) diff --git a/utils/hasher/hasher.go b/utils/hasher/hasher.go index b07c17c31..279cbe444 100644 --- a/utils/hasher/hasher.go +++ b/utils/hasher/hasher.go @@ -18,6 +18,12 @@ func SetSeed(id string, seed string) { instance.SetSeed(id, seed) } +func CurrentSeed(id string) string { + instance.mutex.RLock() + defer instance.mutex.RUnlock() + return instance.seeds[id] +} + func HashFunc() func(id, str string) uint64 { return instance.HashFunc() }