Files
opencloud/services/search/pkg/query/normalize.go
Dominik Schmidt e1c634e546 feat(search): add shared KQL lowering pass
query.Normalize resolves field names (query.ResolveField, from the derived
index + a small alias overlay) and expands media-type restrictions
(mimetype.Expand) once, between parse and backend compilation.
2026-07-29 17:44:41 +02:00

74 lines
2.1 KiB
Go

package query
import (
"reflect"
"github.com/opencloud-eu/opencloud/pkg/ast"
"github.com/opencloud-eu/opencloud/services/search/pkg/query/mimetype"
)
// Normalize is the shared KQL lowering pass between parse and compile: it
// resolves keys to real field names (via resolve) and expands media-type
// restrictions, so the backends compile a plain field:value AST.
func Normalize(a *ast.Ast, resolve func(string) string) *ast.Ast {
a.Nodes = normalizeNodes(a.Nodes, resolve, "")
return a
}
// normalizeNodes rewrites nodes in place. defaultKey is what a bare restriction
// inherits: its enclosing group's key, or "" at the top level.
func normalizeNodes(nodes []ast.Node, resolve func(string) string, defaultKey string) []ast.Node {
resolveKey := func(key string) string {
if key == "" && defaultKey != "" {
return defaultKey // bare child inherits the group key
}
return resolve(key)
}
out := make([]ast.Node, 0, len(nodes))
for _, n := range nodes {
n = toPointer(n) // ensure a pointer so in-place key rewrites persist
switch node := n.(type) {
case *ast.StringNode:
node.Key = resolveKey(node.Key)
if exp := mimetype.Expand(node.Key, node.Value); exp != nil {
out = append(out, normalizeNodes(exp, resolve, defaultKey)...)
continue
}
out = append(out, node)
case *ast.DateTimeNode:
node.Key = resolveKey(node.Key)
out = append(out, node)
case *ast.BooleanNode:
node.Key = resolveKey(node.Key)
out = append(out, node)
case *ast.GroupNode:
groupKey := defaultKey
if node.Key != "" {
node.Key = resolve(node.Key)
groupKey = node.Key
}
node.Nodes = normalizeNodes(node.Nodes, resolve, groupKey)
out = append(out, node)
default:
out = append(out, n)
}
}
return out
}
// toPointer returns n as a pointer; the parser emits some nodes by value and the
// in-place key rewrites would be lost on those.
func toPointer(n ast.Node) ast.Node {
rv := reflect.ValueOf(n)
if rv.Kind() == reflect.Ptr {
return n
}
ptr := reflect.New(rv.Type())
ptr.Elem().Set(rv)
if pn, ok := ptr.Interface().(ast.Node); ok {
return pn
}
return n
}