mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-12 21:58:58 -04:00
fix(search): review fixes for path AND-term and content wildcard
bleve compiled a path restriction to a DisjunctionQuery, which mapBinary redistributes as an OR-chain, so `path:/Foo AND name:bar` matched the folder itself unconditionally. It is now a BooleanQuery (should: folder OR descendants), which mapBinary keeps atomic under an enclosing AND. The OpenSearch full-text branch ran before the wildcard check, so `content:foo*` degraded to a phrase match and diverged from bleve; the wildcard check now comes first. Adds the missing coverage the review flagged: path AND term, content wildcard, case-insensitive tags (the array sibling branch), and a spaced path with descendants on OpenSearch.
This commit is contained in:
1 parent
e3c45dd1d0
commit
727ca92afe
4 files changed
+41
-15
No files matched your search
@@ -185,6 +185,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)
|
||||
@@ -346,6 +357,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() {
|
||||
@@ -356,6 +374,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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -106,15 +106,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
|
||||
|
||||
@@ -88,13 +88,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 {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user