* refactor: move criteria SQL generation to persistence Keep model/criteria as a domain DSL with JSON parsing, field metadata, expression traversal, and child playlist extraction only. Move smart playlist SQL translation, sort SQL, and join planning into persistence behind smartPlaylistCriteria so repository code uses a small query-building API. * refactor: simplify criteria translator metadata Use generic helper functions for criteria operator maps so the SQL translator can pass named criteria map types directly. Remove unused pseudo-field metadata from the criteria field API while preserving special field name lookup. * test: add coverage check for criteria-to-SQL field mappings Add a test that iterates all fields registered in the criteria package and verifies that every non-tag/non-role field has a corresponding entry in the persistence layer's smartPlaylistFields map. This prevents silent drift between the domain field registry and the SQL translation layer. Also adds an AllFieldNames() function to the criteria package to support field enumeration from outside the package.
72 lines
1.5 KiB
Go
72 lines
1.5 KiB
Go
package criteria
|
|
|
|
import "fmt"
|
|
|
|
type Visitor func(Expression) error
|
|
|
|
func Walk(expr Expression, visit Visitor) error {
|
|
if expr == nil {
|
|
return nil
|
|
}
|
|
if err := visit(expr); err != nil {
|
|
return err
|
|
}
|
|
switch e := expr.(type) {
|
|
case All:
|
|
for _, child := range e {
|
|
if err := Walk(child, visit); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case Any:
|
|
for _, child := range e {
|
|
if err := Walk(child, visit); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case Is, IsNot, Gt, Lt, Before, After, Contains, NotContains, StartsWith, EndsWith, InTheRange, InTheLast, NotInTheLast, InPlaylist, NotInPlaylist:
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unknown criteria expression type %T", expr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Fields returns field values for leaf expressions only.
|
|
// Use Walk to traverse All and Any expressions before calling Fields.
|
|
func Fields(expr Expression) map[string]any {
|
|
switch e := expr.(type) {
|
|
case Is:
|
|
return map[string]any(e)
|
|
case IsNot:
|
|
return map[string]any(e)
|
|
case Gt:
|
|
return map[string]any(e)
|
|
case Lt:
|
|
return map[string]any(e)
|
|
case Before:
|
|
return map[string]any(e)
|
|
case After:
|
|
return map[string]any(Gt(e))
|
|
case Contains:
|
|
return map[string]any(e)
|
|
case NotContains:
|
|
return map[string]any(e)
|
|
case StartsWith:
|
|
return map[string]any(e)
|
|
case EndsWith:
|
|
return map[string]any(e)
|
|
case InTheRange:
|
|
return map[string]any(e)
|
|
case InTheLast:
|
|
return map[string]any(e)
|
|
case NotInTheLast:
|
|
return map[string]any(e)
|
|
case InPlaylist:
|
|
return map[string]any(e)
|
|
case NotInPlaylist:
|
|
return map[string]any(e)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|