refactor(search): run the bleve backend on the shared lowering pass

The bleve Creator runs query.Normalize before compiling; the compiler consumes a
canonical AST with no field resolution or media-type special-casing.
This commit is contained in:
Dominik Schmidt committed 2026-08-31 13:40:42 +02:00
1 parent bf7d04f7a1
commit 1cba974c1d
3 files changed
+21 -48

No files matched your search

+4
View File
@@ -22,6 +22,10 @@ func (c Creator[T]) Create(qs string) (T, error) {
return t, err
}
// shared KQL lowering pass: resolve field names + expand media-type aliases
// once, so the compiler below sees only canonical field:value nodes.
builderAst = query.Normalize(builderAst, query.ResolveField)
t, err = c.compiler.Compile(builderAst)
if err != nil {
return t, err
+15 -47
View File
@@ -95,9 +95,11 @@ func walk(offset int, nodes []ast.Node) (bleveQuery.Query, int, error) {
for i := offset; i < len(nodes); i++ {
switch n := nodes[i].(type) {
case *ast.StringNode:
k := getField(n.Key)
// keys are resolved and media-type expanded by normalize; MimeType
// values are literal MIME types, so they skip the escaper.
k := n.Key
v := n.Value
if k != "ID" && k != "Size" {
if k != "ID" && k != "Size" && k != "MimeType" {
v = bleveEscaper.Replace(n.Value)
}
@@ -105,46 +107,7 @@ func walk(offset int, nodes []ast.Node) (bleveQuery.Query, int, error) {
v = strings.ToLower(v)
}
if k == "Type" {
v = resourceType(v)
}
var q bleveQuery.Query
var group bool
switch {
case k == "Hidden":
value, err := strconv.ParseBool(v)
if err != nil {
q = bleveQuery.NewMatchNoneQuery()
break
}
bq := bleveQuery.NewBoolFieldQuery(value)
bq.SetField(k)
q = bq
case k == "MimeType":
q, group = mimeType(k, v)
if prev == nil {
isGroup = group
}
case slices.Contains([]string{"Name", "Title"}, k) && strings.ContainsAny(n.Value, "*?"):
patterns := []bleveQuery.Query{bleveQuery.NewQueryStringQuery(k + ".wildcard:" + v)}
if !strings.HasSuffix(v, "*") {
patterns = append(patterns, bleveQuery.NewQueryStringQuery(k+".wildcard:"+v+".*"))
}
q = closed(bleveQuery.NewDisjunctionQuery(patterns))
case n.Exact && !strings.ContainsAny(n.Value, "*?") && slices.Contains([]string{"Name", "Title"}, k):
q = bleveQuery.NewQueryStringQuery(k + ".wildcard:" + v)
case k == "Path" && !strings.ContainsAny(n.Value, "*?"):
q = pathAndBelow(k, n.Value)
case slices.Contains([]string{"Name", "Title", "Content"}, k) && !strings.ContainsAny(n.Value, "*?"):
q = phrase(k, n.Value)
case strings.Contains(n.Value, " ") && !strings.ContainsAny(n.Value, "*?"):
q = phrase(k, n.Value)
default:
q = bleveQuery.NewQueryStringQuery(k + ":" + v)
}
q := bleveQuery.NewQueryStringQuery(k + ":" + v)
if prev == nil {
prev = q
@@ -157,7 +120,7 @@ func walk(offset int, nodes []ast.Node) (bleveQuery.Query, int, error) {
End: bleveQuery.BleveQueryTime{},
InclusiveStart: nil,
InclusiveEnd: nil,
FieldVal: getField(n.Key),
FieldVal: n.Key,
}
if n.Operator == nil {
@@ -187,7 +150,14 @@ func walk(offset int, nodes []ast.Node) (bleveQuery.Query, int, error) {
next = q
}
case *ast.NumberNode:
q := numberRange(getField(n.Key), n.Operator, n.Value)
var q bleveQuery.Query
if field := getField(n.Key); slices.Contains([]string{"Size", "Type"}, field) {
q = numberRange(field, n.Operator, n.Value)
} else {
// same answer as the OpenSearch backend: unknown numeric keys
// match nothing instead of querying an arbitrary field
q = bleveQuery.NewMatchNoneQuery()
}
if q == nil {
continue
}
@@ -206,9 +176,7 @@ func walk(offset int, nodes []ast.Node) (bleveQuery.Query, int, error) {
next = q
}
case *ast.GroupNode:
if n.Key != "" {
n = normalizeGroupingProperty(n)
}
// keys resolved and grouping property propagated in normalize
q, _, err := walk(0, n.Nodes)
if err != nil {
return nil, 0, err
@@ -7,6 +7,7 @@ import (
"github.com/blevesearch/bleve/v2/search/query"
"github.com/opencloud-eu/opencloud/pkg/ast"
searchquery "github.com/opencloud-eu/opencloud/services/search/pkg/query"
tAssert "github.com/stretchr/testify/assert"
)
@@ -590,7 +591,7 @@ func Test_compile(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := compile(tt.args)
got, err := compile(searchquery.Normalize(tt.args, searchquery.ResolveField))
if (err != nil) != tt.wantErr {
t.Errorf("compile() error = %v, wantErr %v", err, tt.wantErr)