diff --git a/services/search/pkg/bleve/backend_test.go b/services/search/pkg/bleve/backend_test.go index 43a8e5f0d3..16422b809b 100644 --- a/services/search/pkg/bleve/backend_test.go +++ b/services/search/pkg/bleve/backend_test.go @@ -190,8 +190,8 @@ var _ = Describe("Bleve", func() { 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, "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) }) @@ -397,6 +397,19 @@ var _ = Describe("Bleve", func() { assertDocCount(rootResource.ID, "MimeType:image/svg+xml", 1) assertDocCount(rootResource.ID, "MimeType:image/png", 1) }) + + It("combines mediatype:file with another term", func() { + // regression: mediatype:file (a NOT) next to an operator dropped the + // other operand, so mediatype:file AND name:x matched nothing. + parentResource.Document.MimeType = "httpd/unix-directory" // a folder + childResource.Document.MimeType = "image/png" // a file + for _, r := range []search.Resource{parentResource, childResource} { + Expect(eng.Upsert(r.ID, r)).To(Succeed()) + } + assertDocCount(rootResource.ID, "mediatype:file", 1) // only the file + assertDocCount(rootResource.ID, "mediatype:file AND name:child.pdf", 1) // file AND its name + assertDocCount(rootResource.ID, "mediatype:file AND name:nope", 0) + }) }) Context("Highlights", func() { diff --git a/services/search/pkg/opensearch/internal/convert/kql_expand_test.go b/services/search/pkg/opensearch/internal/convert/kql_expand_test.go deleted file mode 100644 index ef1927b48d..0000000000 --- a/services/search/pkg/opensearch/internal/convert/kql_expand_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package convert_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/opencloud-eu/opencloud/pkg/ast" - opensearchtest "github.com/opencloud-eu/opencloud/services/search/internal/opensearchtest" - "github.com/opencloud-eu/opencloud/services/search/pkg/opensearch/internal/convert" -) - -// LowerValues runs after the shared query.Normalize pass, so it operates on -// already-resolved pointer nodes. Field resolution, media-type expansion and -// group-key defaulting are tested once at the query.Normalize level (see -// pkg/query normalize_test), not here. Only lowercase-analyzed fields get their -// value folded; case-preserved fields keep their casing. -func TestLowerValues(t *testing.T) { - tests := []opensearchtest.TableTest[[]ast.Node, []ast.Node]{ - { - Name: "lowercase-analyzed field: value is folded, recursing into groups", - Got: []ast.Node{ - &ast.StringNode{Key: "Name", Value: "StringNode"}, - &ast.GroupNode{Key: "GroupNode", Nodes: []ast.Node{ - &ast.StringNode{Key: "Name", Value: "StringNode"}, - }}, - }, - Want: []ast.Node{ - &ast.StringNode{Key: "Name", Value: "stringnode"}, - &ast.GroupNode{Key: "GroupNode", Nodes: []ast.Node{ - &ast.StringNode{Key: "Name", Value: "stringnode"}, - }}, - }, - }, - { - Name: "case-preserved field: value keeps its casing", - Got: []ast.Node{ - &ast.StringNode{Key: "aBc", Value: "StringNode"}, - &ast.GroupNode{Key: "GroupNode", Nodes: []ast.Node{ - &ast.StringNode{Key: "aBc", Value: "StringNode"}, - }}, - }, - Want: []ast.Node{ - &ast.StringNode{Key: "aBc", Value: "StringNode"}, - &ast.GroupNode{Key: "GroupNode", Nodes: []ast.Node{ - &ast.StringNode{Key: "aBc", Value: "StringNode"}, - }}, - }, - }, - } - - for _, test := range tests { - t.Run(test.Name, func(t *testing.T) { - require.Equal(t, test.Want, convert.LowerValues(test.Got)) - }) - } -} diff --git a/services/search/pkg/query/bleve/compiler_test.go b/services/search/pkg/query/bleve/compiler_test.go index d9a5854a68..23d189f689 100644 --- a/services/search/pkg/query/bleve/compiler_test.go +++ b/services/search/pkg/query/bleve/compiler_test.go @@ -321,8 +321,8 @@ func Test_compile(t *testing.T) { }, want: query.NewConjunctionQuery([]query.Query{ query.NewQueryStringQuery(`Name_lowercase:john\ smith`), - query.NewQueryStringQuery(`Hidden:T`), - query.NewQueryStringQuery(`Hidden:T`), + query.NewQueryStringQuery(`Hidden:t`), + query.NewQueryStringQuery(`Hidden:t`), }), wantErr: false, }, diff --git a/services/search/pkg/query/mimetype/mimetype.go b/services/search/pkg/query/mimetype/mimetype.go index ca61e651ac..06700049e9 100644 --- a/services/search/pkg/query/mimetype/mimetype.go +++ b/services/search/pkg/query/mimetype/mimetype.go @@ -23,9 +23,14 @@ func Expand(key, value string) []ast.Node { value = strings.ToLower(value) switch value { case "file": + // Group the negation so it stays atomic next to an operator: a bare + // `NOT ` sequence spliced inline miscompiles as the left of an AND + // (mediatype:file AND name:x would drop name:x). return []ast.Node{ - &ast.OperatorNode{Value: kql.BoolNOT}, - &ast.StringNode{Key: field, Value: "httpd/unix-directory"}, + &ast.GroupNode{Nodes: []ast.Node{ + &ast.OperatorNode{Value: kql.BoolNOT}, + &ast.StringNode{Key: field, Value: "httpd/unix-directory"}, + }}, } case "folder": return term("httpd/unix-directory") diff --git a/services/search/pkg/query/mimetype/mimetype_test.go b/services/search/pkg/query/mimetype/mimetype_test.go index d4b78671b1..c9d9a7cf6c 100644 --- a/services/search/pkg/query/mimetype/mimetype_test.go +++ b/services/search/pkg/query/mimetype/mimetype_test.go @@ -45,9 +45,12 @@ func TestExpand_literalValuePassesThroughToMimeType(t *testing.T) { } func TestExpand_fileIsNotAFolder(t *testing.T) { + // grouped so the negation stays atomic next to an operator. require.Equal(t, []ast.Node{ - &ast.OperatorNode{Value: "NOT"}, - &ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"}, + &ast.GroupNode{Nodes: []ast.Node{ + &ast.OperatorNode{Value: "NOT"}, + &ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"}, + }}, }, mimetype.Expand("mediatype", "file")) } diff --git a/services/search/pkg/query/normalize_test.go b/services/search/pkg/query/normalize_test.go index df7568f0c0..8af9cf7435 100644 --- a/services/search/pkg/query/normalize_test.go +++ b/services/search/pkg/query/normalize_test.go @@ -57,8 +57,10 @@ func TestNormalize_ResolvesFieldsAndExpandsMediatype(t *testing.T) { &ast.OperatorNode{Value: "AND"}, &ast.StringNode{Key: "photo.cameraMake", Value: "canon"}, &ast.OperatorNode{Value: "AND"}, - &ast.OperatorNode{Value: "NOT"}, - &ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"}, + &ast.GroupNode{Nodes: []ast.Node{ + &ast.OperatorNode{Value: "NOT"}, + &ast.StringNode{Key: "MimeType", Value: "httpd/unix-directory"}, + }}, &ast.OperatorNode{Value: "AND"}, &ast.NumberNode{Key: "Size", Value: 100}, }, got)