feat(server): add percentage-based limits to smart playlists (#5144)
* feat(playlists): add percentage-based limits to smart playlists Add a new `limitPercent` JSON field to Criteria that allows smart playlist limits to be expressed as a percentage of matching tracks rather than a fixed number. For example, a playlist matching 450 songs with a 10% limit returns 45 songs, scaling dynamically as the library grows. When `limitPercent` is set, refreshSmartPlaylist runs a COUNT query first to determine the total matching tracks, then resolves the percentage to an absolute LIMIT before executing the main query. The fixed `limit` field takes precedence when both are set. Values are clamped to [0, 100] during JSON unmarshaling. No database migration is needed since rules are stored as a JSON string. * fix(criteria): validate percentage limit range in IsPercentageLimit method Signed-off-by: Deluan <deluan@navidrome.org> * fix(criteria): ensure idempotency of ToSql method for expressions Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
+11
-14
@@ -122,27 +122,24 @@ func mapExpr(expr squirrel.Sqlizer, negate bool, exprFunc func(string, squirrel.
|
||||
log.Fatal(fmt.Sprintf("expr is not a map-based operator: %T", expr))
|
||||
}
|
||||
|
||||
// Extract into a generic map
|
||||
// Extract the field name and value, then build a new map keyed by "value"
|
||||
// for the inner condition. The original map is left untouched so that
|
||||
// ToSql can be called multiple times without corruption.
|
||||
var k string
|
||||
m := make(map[string]any, rv.Len())
|
||||
var v any
|
||||
for _, key := range rv.MapKeys() {
|
||||
// Save the key to build the expression, and use the provided keyName as the key
|
||||
k = key.String()
|
||||
m["value"] = rv.MapIndex(key).Interface()
|
||||
v = rv.MapIndex(key).Interface()
|
||||
break // only one key is expected (and supported)
|
||||
}
|
||||
|
||||
// Clear the original map
|
||||
for _, key := range rv.MapKeys() {
|
||||
rv.SetMapIndex(key, reflect.Value{})
|
||||
}
|
||||
// Create a new map-based expression with "value" as the key, matching the
|
||||
// column name inside json_tree subqueries.
|
||||
newMap := reflect.MakeMap(rv.Type())
|
||||
newMap.SetMapIndex(reflect.ValueOf("value"), reflect.ValueOf(v))
|
||||
newExpr := newMap.Interface().(squirrel.Sqlizer)
|
||||
|
||||
// Write the updated map back into the original variable
|
||||
for key, val := range m {
|
||||
rv.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(val))
|
||||
}
|
||||
|
||||
return exprFunc(k, expr, negate)
|
||||
return exprFunc(k, newExpr, negate)
|
||||
}
|
||||
|
||||
// mapTagExpr maps a normal field expression to a tag expression.
|
||||
|
||||
Reference in New Issue
Block a user