fix(server): fix numeric comparisons for float custom tags in smart playlists (#4116)

* Fix numeric comparisons for custom float tags

* feat(criteria): cast numeric tags for sorting and comparisons

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan Quintão
2025-05-25 17:52:27 -04:00
committed by GitHub
parent fac9275c27
commit 5e2db2c673
6 changed files with 69 additions and 6 deletions
+18
View File
@@ -13,6 +13,7 @@ import (
var _ = BeforeSuite(func() {
AddRoles([]string{"artist", "composer"})
AddTagNames([]string{"genre"})
AddNumericTags([]string{"rate"})
})
var _ = Describe("Operators", func() {
@@ -68,6 +69,15 @@ var _ = Describe("Operators", func() {
Entry("role endsWith [string]", EndsWith{"composer": "Lennon"}, "exists (select 1 from json_tree(participants, '$.composer') where key='name' and value LIKE ?)", "%Lennon"),
)
// TODO Validate operators that are not valid for each field type.
XDescribeTable("ToSQL - Invalid Operators",
func(op Expression, expectedError string) {
_, _, err := op.ToSql()
gomega.Expect(err).To(gomega.MatchError(expectedError))
},
Entry("numeric tag contains", Contains{"rate": 5}, "numeric tag 'rate' cannot be used with Contains operator"),
)
Describe("Custom Tags", func() {
It("generates valid SQL", func() {
AddTagNames([]string{"mood"})
@@ -77,6 +87,14 @@ var _ = Describe("Operators", func() {
gomega.Expect(sql).To(gomega.Equal("exists (select 1 from json_tree(tags, '$.mood') where key='value' and value LIKE ?)"))
gomega.Expect(args).To(gomega.HaveExactElements("%Soft"))
})
It("casts numeric comparisons", func() {
AddNumericTags([]string{"rate"})
op := Lt{"rate": 6}
sql, args, err := op.ToSql()
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Expect(sql).To(gomega.Equal("exists (select 1 from json_tree(tags, '$.rate') where key='value' and CAST(value AS REAL) < ?)"))
gomega.Expect(args).To(gomega.HaveExactElements(6))
})
It("skips unknown tag names", func() {
op := EndsWith{"unknown": "value"}
sql, args, _ := op.ToSql()