mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-12 21:58:58 -04:00
fix(search): preserve query value case for case-sensitive fields on OpenSearch
OpenSearch lowercased every KQL query value, so exact-match queries on case-preserved keyword fields (facet values, ids) never matched their stored token. Fold the value only for fields with a lowercasing analyzer, mirroring the bleve backend. The field set is derived once in search.LowercaseValueFields and shared by both backends (bleve's local buildLowercaseFields is dropped).
This commit is contained in:
1 parent
7d55849900
commit
40a2f14600
3 files changed
+37
-22
No files matched your search
@@ -3,13 +3,13 @@ package convert
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/ast"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/search"
|
||||
)
|
||||
|
||||
func ExpandKQL(nodes []ast.Node) ([]ast.Node, error) {
|
||||
@@ -101,11 +101,13 @@ func (_ kqlExpander) remapKey(current string, defaultKey string) string {
|
||||
}
|
||||
|
||||
func (_ kqlExpander) lowerValue(key, value string) string {
|
||||
if slices.Contains([]string{"Name", "Title", "Tags", "Content", "MimeType", "Type", "Hidden"}, key) {
|
||||
return strings.ToLower(value)
|
||||
// only fold the value for fields whose index analyzer lowercases too;
|
||||
// case-preserved (keyword) fields must keep their casing or they never match
|
||||
// their stored token. Shared with bleve via search.LowercaseValueFields.
|
||||
if _, ok := search.LowercaseValueFields()[key]; !ok {
|
||||
return value
|
||||
}
|
||||
|
||||
return value
|
||||
return strings.ToLower(value)
|
||||
}
|
||||
|
||||
func (_ kqlExpander) unfoldValue(key, value string) []ast.Node {
|
||||
|
||||
@@ -12,26 +12,13 @@ import (
|
||||
bleveQuery "github.com/blevesearch/bleve/v2/search/query"
|
||||
"github.com/opencloud-eu/opencloud/pkg/ast"
|
||||
"github.com/opencloud-eu/opencloud/pkg/kql"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/mapping"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/search"
|
||||
)
|
||||
|
||||
// lowercaseFields is derived from Resource.SearchFieldOverrides(): any
|
||||
// field whose override picks a lowercasing analyzer (`lowercaseKeyword`)
|
||||
// or the fulltext type (which uses a lowercasing analyzer under the hood)
|
||||
// gets its query-side value pre-lowercased so compile-time matches the
|
||||
// index-time tokenization. Anything else keeps its original casing.
|
||||
var lowercaseFields = buildLowercaseFields()
|
||||
|
||||
func buildLowercaseFields() map[string]struct{} {
|
||||
out := map[string]struct{}{}
|
||||
for key, opts := range (search.Resource{}).SearchFieldOverrides() {
|
||||
if opts.Analyzer == "lowercaseKeyword" || opts.Type == mapping.TypeFulltext {
|
||||
out[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
// lowercaseFields holds the fields whose query-side value is pre-lowercased so
|
||||
// it matches the index-time lowercasing analyzer. Shared with the OpenSearch
|
||||
// backend via search.LowercaseValueFields; every other field keeps its casing.
|
||||
var lowercaseFields = search.LowercaseValueFields()
|
||||
|
||||
var _fields = map[string]string{
|
||||
"rootid": "RootID",
|
||||
|
||||
@@ -89,6 +89,32 @@ func (Resource) SearchFieldOverrides() map[string]mapping.FieldOpts {
|
||||
return resourceFieldOverrides()
|
||||
}
|
||||
|
||||
// lowercaseValueFields is the set of index field names whose query values must
|
||||
// be lowercased to match their index-time lowercasing analyzer (lowercaseKeyword
|
||||
// or the fulltext type). Built once from the field overrides.
|
||||
var lowercaseValueFields = sync.OnceValue(func() map[string]struct{} {
|
||||
out := map[string]struct{}{}
|
||||
for key, opts := range resourceFieldOverrides() {
|
||||
if opts.Analyzer == "lowercaseKeyword" || opts.Type == mapping.TypeFulltext {
|
||||
out[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
// stored values are normalized lowercase, so query values must fold too
|
||||
// even though the index fields preserve case
|
||||
for _, key := range []string{"MimeType", "Type", "Hidden"} {
|
||||
out[key] = struct{}{}
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
// LowercaseValueFields returns the set of index field names whose query values
|
||||
// must be lowercased so query-side matching lines up with the index-time
|
||||
// analyzer. Both search backends use it, so value casing stays consistent; every
|
||||
// other (case-preserved) field keeps its original case. Read-only, do not mutate.
|
||||
func LowercaseValueFields() map[string]struct{} {
|
||||
return lowercaseValueFields()
|
||||
}
|
||||
|
||||
// ResolveReference makes sure the path is relative to the space root
|
||||
func ResolveReference(ctx context.Context, ref *provider.Reference, ri *provider.ResourceInfo, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) (*provider.Reference, error) {
|
||||
if ref.GetResourceId().GetOpaqueId() == ref.GetResourceId().GetSpaceId() {
|
||||
|
||||
Reference in new issue
Block a user