fix(search): preserve value case for non-lowercased bleve fields

The bleve compiler lowercased every query value (except Hidden)
before handing it to the engine. This matched the index tokens
for fields whose analyzer folds case — Name, Tags, Favorites,
Content — but silently broke matching for every other field,
whose default keyword analyzer preserves case. A query like
Title:"Some Title" parsed fine, lowercased to "some title", and
missed the indexed token "Some Title".

Replace the blanket lowercasing with an allowlist of the four
fields whose index mapping actually uses a lowercasing analyzer.
Every other field now passes through unchanged, which keeps
values like "deadmau5" or "Motörhead" intact instead of
normalising them to a case the tag writer didn't choose.
This commit is contained in:
Dominik Schmidt
2026-04-20 16:34:29 +02:00
parent 68b356292b
commit 538c82787c
3 changed files with 28 additions and 5 deletions

View File

@@ -137,6 +137,18 @@ var _ = Describe("Bleve", func() {
assertDocCount(rootResource.ID, "Size:<1000", 0)
assertDocCount(rootResource.ID, "Size:>100000", 0)
})
// FIXME: switch Title to audio.artist once
// https://github.com/opencloud-eu/opencloud/pull/2632 lands
// (KQL grammar then accepts dotted keys).
It("preserves value case for fields not explicitly marked lowercase", func() {
parentResource.Document.Title = "Some Title"
err := eng.Upsert(parentResource.ID, parentResource)
Expect(err).ToNot(HaveOccurred())
assertDocCount(rootResource.ID, `Title:"Some Title"`, 1)
assertDocCount(rootResource.ID, `Title:"some title"`, 0)
})
})
Context("by filename", func() {

View File

@@ -10,6 +10,17 @@ import (
"github.com/opencloud-eu/opencloud/pkg/kql"
)
// lowercaseFields lists the bleve fields whose index mapping uses a lowercasing analyzer.
// Values bound to these fields are pre-lowercased so that non-analyzed query types
// (e.g. wildcard, fuzzy) still match the lowercased terms in the index.
// Keep in sync with services/search/pkg/bleve/index.go NewMapping.
var lowercaseFields = map[string]bool{
"Name": true,
"Tags": true,
"Favorites": true,
"Content": true,
}
var _fields = map[string]string{
"rootid": "RootID",
"path": "Path",
@@ -91,7 +102,7 @@ func walk(offset int, nodes []ast.Node) (bleveQuery.Query, int, error) {
v = bleveEscaper.Replace(n.Value)
}
if k != "Hidden" {
if lowercaseFields[k] {
v = strings.ToLower(v)
}

View File

@@ -227,8 +227,8 @@ func Test_compile(t *testing.T) {
},
},
want: query.NewConjunctionQuery([]query.Query{
query.NewQueryStringQuery(`author:john\ smith`),
query.NewQueryStringQuery(`author:jane`),
query.NewQueryStringQuery(`author:John\ Smith`),
query.NewQueryStringQuery(`author:Jane`),
}),
wantErr: false,
},
@@ -249,8 +249,8 @@ func Test_compile(t *testing.T) {
},
},
want: query.NewConjunctionQuery([]query.Query{
query.NewQueryStringQuery(`author:john\ smith`),
query.NewQueryStringQuery(`author:jane`),
query.NewQueryStringQuery(`author:John\ Smith`),
query.NewQueryStringQuery(`author:Jane`),
query.NewQueryStringQuery(`Tags:bestseller`),
}),
wantErr: false,