fix(db): normalize timestamps and fix recently added album sorting (#5176)
* fix(db): normalize timestamps and fix recently added album sorting
SQLite stores timestamps as TEXT and uses string comparison for ORDER BY.
Timestamps in RFC3339 T-format ('2024-01-01T10:00:00Z') sort incorrectly
against space-format ('2024-01-01 10:00:00+00:00') because 'T' (ASCII 84)
> ' ' (ASCII 32), causing albums with T-format timestamps to appear as
newer than they are in the "Recently Added" list.
This adds a migration to normalize all T-format timestamps across all
tables to the space-format expected by go-sqlite3, wraps the
recently_added sort with datetime() to make it format-agnostic, and
replaces the plain album timestamp indexes with expression indexes to
maintain query performance.
* fix(test): improve recently_added sort test robustness
Use same-date timestamps (2024-01-15T08:00:00Z vs 2024-01-15 20:00:00)
so the T-vs-space character difference at position 10 actually triggers
the sorting bug. Initialize index variables to -1 and assert both test
albums are found before comparing positions.
* chore(db): update migration timestamp to 2026-03-16
This commit is contained in:
@@ -143,9 +143,9 @@ var albumFilters = sync.OnceValue(func() map[string]filterFunc {
|
||||
|
||||
func recentlyAddedSort() string {
|
||||
if conf.Server.RecentlyAddedByModTime {
|
||||
return "updated_at"
|
||||
return "datetime(album.updated_at)"
|
||||
}
|
||||
return "created_at"
|
||||
return "datetime(album.created_at)"
|
||||
}
|
||||
|
||||
func recentlyPlayedFilter(string, any) Sqlizer {
|
||||
|
||||
@@ -85,6 +85,53 @@ var _ = Describe("AlbumRepository", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("recently_added sort", func() {
|
||||
It("sorts correctly regardless of timestamp format (T-format vs space-format)", func() {
|
||||
// Both timestamps share the same date prefix "2024-01-15" so the T vs space
|
||||
// character at position 10 determines sort order in raw string comparison.
|
||||
// Without normalization, 'T' (ASCII 84) > ' ' (ASCII 32) makes the older
|
||||
// T-format timestamp sort AFTER the newer space-format one.
|
||||
|
||||
// Older album: morning of Jan 15, stored in T-format
|
||||
olderAlbum := &model.Album{LibraryID: 1, ID: "ts-older", Name: "Older Album"}
|
||||
Expect(albumRepo.Put(olderAlbum)).To(Succeed())
|
||||
_, err := albumRepo.executeSQL(squirrel.Update("album").
|
||||
Set("created_at", "2024-01-15T08:00:00Z").
|
||||
Where(squirrel.Eq{"id": "ts-older"}))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Newer album: evening of Jan 15, stored in space-format
|
||||
newerAlbum := &model.Album{LibraryID: 1, ID: "ts-newer", Name: "Newer Album"}
|
||||
Expect(albumRepo.Put(newerAlbum)).To(Succeed())
|
||||
_, err = albumRepo.executeSQL(squirrel.Update("album").
|
||||
Set("created_at", "2024-01-15 20:00:00+00:00").
|
||||
Where(squirrel.Eq{"id": "ts-newer"}))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
albums, err := albumRepo.GetAll(model.QueryOptions{Sort: "recently_added", Order: "desc"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Find positions of our test albums
|
||||
olderIdx, newerIdx := -1, -1
|
||||
for i, a := range albums {
|
||||
switch a.ID {
|
||||
case "ts-older":
|
||||
olderIdx = i
|
||||
case "ts-newer":
|
||||
newerIdx = i
|
||||
}
|
||||
}
|
||||
Expect(olderIdx).To(BeNumerically(">=", 0), "older album not found in results")
|
||||
Expect(newerIdx).To(BeNumerically(">=", 0), "newer album not found in results")
|
||||
// Newer album (evening, space-format) should come before older album (morning, T-format) in desc order
|
||||
Expect(newerIdx).To(BeNumerically("<", olderIdx),
|
||||
"Newer album (20:00 space-format) should sort before older album (08:00 T-format) in desc order")
|
||||
|
||||
// Clean up
|
||||
_, _ = albumRepo.executeSQL(squirrel.Delete("album").Where(squirrel.Eq{"id": []string{"ts-older", "ts-newer"}}))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Filters", func() {
|
||||
var albumWithoutAnnotation model.Album
|
||||
|
||||
|
||||
Reference in New Issue
Block a user