diff --git a/services/search/pkg/bleve/backend_test.go b/services/search/pkg/bleve/backend_test.go index 2ed8829dc6..d6d2d5cee4 100644 --- a/services/search/pkg/bleve/backend_test.go +++ b/services/search/pkg/bleve/backend_test.go @@ -125,6 +125,17 @@ var _ = Describe("Bleve", func() { assertDocCount(rootResource.ID, "Tags:baz", 0) }) + It("finds files by tags case-insensitively", func() { + // exercises the []string/[]any sibling-lowercasing branch end-to-end. + parentResource.Document.Tags = []string{"Work", "Urgent"} + Expect(eng.Upsert(parentResource.ID, parentResource)).To(Succeed()) + + assertDocCount(rootResource.ID, "tag:work", 1) // stored "Work", queried lower + assertDocCount(rootResource.ID, "tag:WORK", 1) // queried upper + assertDocCount(rootResource.ID, "Tags:Urgent", 1) + assertDocCount(rootResource.ID, "tag:missing", 0) + }) + It("finds files by size", func() { parentResource.Document.Size = 12345 err := eng.Upsert(parentResource.ID, parentResource) @@ -286,6 +297,13 @@ var _ = Describe("Bleve", func() { It("matches case-insensitively", func() { assertDocCount(rootResource.ID, `path:"./PARENT D!R"`, 3) }) + + It("applies an AND filter to the folder itself, not only descendants", func() { + // regression: the folder-itself clause used to match unconditionally + // under an AND, so the parent leaked in despite the name filter. + matches := assertDocCount(rootResource.ID, `path:"./parent d!r" AND name:child.pdf`, 1) + Expect(matches[0].Entity.Name).To(Equal("child.pdf")) + }) }) Context("by content", func() { @@ -296,6 +314,7 @@ var _ = Describe("Bleve", func() { assertDocCount(rootResource.ID, "content:running", 1) assertDocCount(rootResource.ID, "content:RUNNING", 1) // case-insensitive assertDocCount(rootResource.ID, "content:run", 1) // porter stemming + assertDocCount(rootResource.ID, "content:run*", 1) // wildcard over the stemmed term assertDocCount(rootResource.ID, "content:cat", 0) }) }) diff --git a/services/search/pkg/opensearch/internal/convert/kql_transpile.go b/services/search/pkg/opensearch/internal/convert/kql_transpile.go index 67f84606a9..374eefc624 100644 --- a/services/search/pkg/opensearch/internal/convert/kql_transpile.go +++ b/services/search/pkg/opensearch/internal/convert/kql_transpile.go @@ -107,15 +107,15 @@ func (t kqlOpensearchTranspiler) toBuilder(node ast.Node) (osu.Builder, error) { value = strings.ToLower(value) } - if query.FieldIsFulltext(node.Key) { - return osu.NewMatchPhraseQuery(field).Query(value), nil - } - isWildcard := strings.Contains(value, "*") if isWildcard { return osu.NewWildcardQuery(field).Value(value), nil } + if query.FieldIsFulltext(node.Key) { + return osu.NewMatchPhraseQuery(field).Query(value), nil + } + totalTerms := strings.Split(value, " ") isSingleTerm := len(totalTerms) == 1 isMultiTerm := len(totalTerms) >= 1 diff --git a/services/search/pkg/query/bleve/compiler.go b/services/search/pkg/query/bleve/compiler.go index 7f79997755..2d080ba355 100644 --- a/services/search/pkg/query/bleve/compiler.go +++ b/services/search/pkg/query/bleve/compiler.go @@ -85,13 +85,14 @@ func walk(offset int, nodes []ast.Node) (bleveQuery.Query, int, error) { var q bleveQuery.Query = bleveQuery.NewQueryStringQuery(k + ":" + v) if searchQuery.FieldIsPath(n.Key) { - // bleve has no path hierarchy analyzer, unlike OpenSearch: match - // the folder itself and its descendants (`\/*`, a trailing - // wildcard on the value). - q = bleveQuery.NewDisjunctionQuery([]bleveQuery.Query{ - q, - bleveQuery.NewQueryStringQuery(k + ":" + v + `\/*`), - }) + // bleve has no path hierarchy analyzer, unlike OpenSearch: match the + // folder itself and its descendants (`\/*`). A BooleanQuery keeps + // this atomic; a DisjunctionQuery would be redistributed by an + // enclosing AND (mapBinary treats a left disjunction as an OR-chain). + bq := bleve.NewBooleanQuery() + bq.AddShould(q, bleveQuery.NewQueryStringQuery(k+":"+v+`\/*`)) + bq.SetMinShould(1) + q = bq } if prev == nil { diff --git a/services/search/pkg/query/bleve/compiler_test.go b/services/search/pkg/query/bleve/compiler_test.go index c54e59f18b..d9a5854a68 100644 --- a/services/search/pkg/query/bleve/compiler_test.go +++ b/services/search/pkg/query/bleve/compiler_test.go @@ -52,10 +52,16 @@ func Test_compile(t *testing.T) { &ast.StringNode{Key: "path", Value: "/Foo"}, }, }, - want: query.NewDisjunctionQuery([]query.Query{ - query.NewQueryStringQuery(`Path_lowercase:\/foo`), - query.NewQueryStringQuery(`Path_lowercase:\/foo\/*`), - }), + // a BooleanQuery (should: exact OR descendants), not a DisjunctionQuery, + // so an enclosing AND does not redistribute the folder-itself clause. + want: func() query.Query { + bq := query.NewBooleanQuery(nil, []query.Query{ + query.NewQueryStringQuery(`Path_lowercase:\/foo`), + query.NewQueryStringQuery(`Path_lowercase:\/foo\/*`), + }, nil) + bq.SetMinShould(1) + return query.NewConjunctionQuery([]query.Query{bq}) + }(), wantErr: false, }, {