From 929e7193b4c11f01cab6aa7527431f6b44234fe3 Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 17 Feb 2026 09:00:23 -0500 Subject: [PATCH] refactor: use natural.Compare directly instead of wrapper --- db/db.go | 4 +- utils/str/natural_sort.go | 16 ------ utils/str/natural_sort_test.go | 101 --------------------------------- 3 files changed, 2 insertions(+), 119 deletions(-) delete mode 100644 utils/str/natural_sort.go delete mode 100644 utils/str/natural_sort_test.go diff --git a/db/db.go b/db/db.go index e69661697..ff852c740 100644 --- a/db/db.go +++ b/db/db.go @@ -7,13 +7,13 @@ import ( "fmt" "runtime" + "github.com/maruel/natural" "github.com/mattn/go-sqlite3" "github.com/navidrome/navidrome/conf" _ "github.com/navidrome/navidrome/db/migrations" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/utils/hasher" "github.com/navidrome/navidrome/utils/singleton" - "github.com/navidrome/navidrome/utils/str" "github.com/pressly/goose/v3" ) @@ -35,7 +35,7 @@ func Db() *sql.DB { if err := conn.RegisterFunc("SEEDEDRAND", hasher.HashFunc(), false); err != nil { return err } - return conn.RegisterCollation("NATURALSORT", str.NaturalSortCompare) + return conn.RegisterCollation("NATURALSORT", natural.Compare) }, }) Path = conf.Server.DbPath diff --git a/utils/str/natural_sort.go b/utils/str/natural_sort.go deleted file mode 100644 index 281114e1f..000000000 --- a/utils/str/natural_sort.go +++ /dev/null @@ -1,16 +0,0 @@ -package str - -import ( - "strings" - - "github.com/maruel/natural" -) - -// NaturalSortCompare compares two strings using natural sort ordering, -// where embedded numeric sequences are compared as numbers rather than -// lexicographically. For example, "track2" < "track10" (unlike lexicographic -// ordering which gives "track10" < "track2"). The comparison is also -// case-insensitive. -func NaturalSortCompare(a, b string) int { - return natural.Compare(strings.ToLower(a), strings.ToLower(b)) -} diff --git a/utils/str/natural_sort_test.go b/utils/str/natural_sort_test.go deleted file mode 100644 index 514c79b69..000000000 --- a/utils/str/natural_sort_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package str_test - -import ( - "testing" - - "github.com/navidrome/navidrome/utils/str" -) - -func TestNaturalSortCompare(t *testing.T) { - tests := []struct { - name string - a, b string - want int // -1, 0, or 1 - }{ - // Basic string comparison - {"equal strings", "abc", "abc", 0}, - {"less than", "abc", "abd", -1}, - {"greater than", "abd", "abc", 1}, - - // Case insensitive - {"case insensitive equal", "ABC", "abc", 0}, - {"case insensitive less", "ABC", "abd", -1}, - {"case insensitive mixed", "aBc", "AbC", 0}, - - // Numeric ordering - {"single digit order", "track1", "track2", -1}, - {"natural sort 2 vs 10", "track2", "track10", -1}, - {"natural sort 9 vs 10", "track9", "track10", -1}, - {"natural sort 10 vs 2", "track10", "track2", 1}, - {"natural sort 1 vs 100", "track1", "track100", -1}, - - // Album numbering (main use case from issue) - {"album numbers 1 vs 2", "Bravo Hits 1", "Bravo Hits 2", -1}, - {"album numbers 2 vs 10", "Bravo Hits 2", "Bravo Hits 10", -1}, - {"album numbers 10 vs 100", "Bravo Hits 10", "Bravo Hits 100", -1}, - {"album numbers 9 vs 10", "Bravo Hits 9", "Bravo Hits 10", -1}, - {"album numbers equal", "Bravo Hits 10", "Bravo Hits 10", 0}, - {"album numbers 99 vs 100", "Bravo Hits 99", "Bravo Hits 100", -1}, - {"album numbers 100 vs 101", "Bravo Hits 100", "Bravo Hits 101", -1}, - - // Pure numeric strings - {"pure number 1 vs 2", "1", "2", -1}, - {"pure number 2 vs 10", "2", "10", -1}, - {"pure number 10 vs 9", "10", "9", 1}, - {"pure number equal", "42", "42", 0}, - - // Multiple numeric sequences - {"multi number 1.2 vs 1.10", "file1.2", "file1.10", -1}, - {"multi number 2.1 vs 10.1", "file2.1", "file10.1", -1}, - - // Empty strings - {"both empty", "", "", 0}, - {"empty vs non-empty", "", "a", -1}, - {"non-empty vs empty", "a", "", 1}, - - // Edge cases - {"same prefix different length", "abc", "abcd", -1}, - {"numbers at beginning", "10abc", "9abc", 1}, - {"numbers at end", "abc10", "abc9", 1}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := str.NaturalSortCompare(tt.a, tt.b) - // Normalize to -1, 0, 1 for comparison - gotNorm := normalize(got) - if gotNorm != tt.want { - t.Errorf("NaturalSortCompare(%q, %q) = %d (normalized: %d), want %d", tt.a, tt.b, got, gotNorm, tt.want) - } - }) - } -} - -func TestNaturalSortCompare_Symmetry(t *testing.T) { - pairs := [][2]string{ - {"track2", "track10"}, - {"abc", "abd"}, - {"Bravo Hits 9", "Bravo Hits 10"}, - {"1", "2"}, - {"file1.2", "file1.10"}, - } - - for _, pair := range pairs { - a, b := pair[0], pair[1] - ab := str.NaturalSortCompare(a, b) - ba := str.NaturalSortCompare(b, a) - if normalize(ab) != -normalize(ba) { - t.Errorf("Symmetry violated: Compare(%q,%q)=%d but Compare(%q,%q)=%d", a, b, ab, b, a, ba) - } - } -} - -func normalize(v int) int { - if v < 0 { - return -1 - } - if v > 0 { - return 1 - } - return 0 -}