From 1cba974c1db2cd557e4c1970cee95e13ebd6af78 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Wed, 29 Jul 2026 17:39:23 +0200 Subject: [PATCH] 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. --- services/search/pkg/query/bleve/bleve.go | 4 ++ services/search/pkg/query/bleve/compiler.go | 62 +++++-------------- .../search/pkg/query/bleve/compiler_test.go | 3 +- 3 files changed, 21 insertions(+), 48 deletions(-) diff --git a/services/search/pkg/query/bleve/bleve.go b/services/search/pkg/query/bleve/bleve.go index 0acbc80095..41e260b81c 100644 --- a/services/search/pkg/query/bleve/bleve.go +++ b/services/search/pkg/query/bleve/bleve.go @@ -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 diff --git a/services/search/pkg/query/bleve/compiler.go b/services/search/pkg/query/bleve/compiler.go index 9722f45512..c5b32835b0 100644 --- a/services/search/pkg/query/bleve/compiler.go +++ b/services/search/pkg/query/bleve/compiler.go @@ -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 diff --git a/services/search/pkg/query/bleve/compiler_test.go b/services/search/pkg/query/bleve/compiler_test.go index 30c283f694..f3f1f3ab53 100644 --- a/services/search/pkg/query/bleve/compiler_test.go +++ b/services/search/pkg/query/bleve/compiler_test.go @@ -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)