mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-13 14:18:38 -04:00
Single-term `content:` built an unanalyzed term query, so once this branch dropped the blanket query-value lowercasing, `content:Foo` missed on OpenSearch (bleve was unaffected, its query analyzes). Fielded full-text queries now use a match query. OpenSearch `Content` also gets a porter stemming analyzer (it used the default standard analyzer and never stemmed), so full-text search matches bleve on both case and stemming.
105 lines
2.9 KiB
Go
105 lines
2.9 KiB
Go
package query
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/opencloud-eu/opencloud/services/search/pkg/mapping"
|
|
"github.com/opencloud-eu/opencloud/services/search/pkg/search"
|
|
)
|
|
|
|
// aliases are KQL spellings the derived index can't produce (fields are plural).
|
|
var aliases = map[string]string{
|
|
"tag": "Tags",
|
|
"favorite": "Favorites",
|
|
}
|
|
|
|
// fieldIndex maps a lowercased KQL key to its canonical field name ("" is the
|
|
// bare-search default).
|
|
var fieldIndex = sync.OnceValue(func() map[string]string {
|
|
idx := mapping.FieldNameIndex(reflect.TypeFor[search.Resource](), search.Resource{}.SearchFieldOverrides())
|
|
for k, v := range aliases {
|
|
idx[k] = v
|
|
}
|
|
idx[""] = idx["name"]
|
|
return idx
|
|
})
|
|
|
|
// caseInsensitiveFields are the fields searched case-insensitively by default,
|
|
// derived from the CaseInsensitive overrides.
|
|
var caseInsensitiveFields = sync.OnceValue(func() map[string]struct{} {
|
|
out := map[string]struct{}{}
|
|
for field, opts := range (search.Resource{}).SearchFieldOverrides() {
|
|
if opts.CaseInsensitive != nil && *opts.CaseInsensitive {
|
|
out[field] = struct{}{}
|
|
}
|
|
}
|
|
return out
|
|
})
|
|
|
|
// pathFields are hierarchical path fields (TypePath), derived from the overrides.
|
|
var pathFields = sync.OnceValue(func() map[string]struct{} {
|
|
out := map[string]struct{}{}
|
|
for field, opts := range (search.Resource{}).SearchFieldOverrides() {
|
|
if opts.Type == mapping.TypePath {
|
|
out[field] = struct{}{}
|
|
}
|
|
}
|
|
return out
|
|
})
|
|
|
|
// fulltextFields are analyzed full-text fields (TypeFulltext), derived from the
|
|
// overrides.
|
|
var fulltextFields = sync.OnceValue(func() map[string]struct{} {
|
|
out := map[string]struct{}{}
|
|
for field, opts := range (search.Resource{}).SearchFieldOverrides() {
|
|
if opts.Type == mapping.TypeFulltext {
|
|
out[field] = struct{}{}
|
|
}
|
|
}
|
|
return out
|
|
})
|
|
|
|
// ResolveField maps a KQL key to its canonical field name; unknown keys pass through.
|
|
func ResolveField(name string) string {
|
|
if v, ok := fieldIndex()[strings.ToLower(name)]; ok {
|
|
return v
|
|
}
|
|
return name
|
|
}
|
|
|
|
// normalizedValueFields have their stored values normalized to lowercase at
|
|
// index time, so query values fold to match even though the fields themselves
|
|
// are case-preserved keywords.
|
|
var normalizedValueFields = map[string]struct{}{
|
|
"MimeType": {},
|
|
"Type": {},
|
|
"Hidden": {},
|
|
}
|
|
|
|
// FieldValueIsNormalized reports whether a field's stored values are
|
|
// normalized lowercase.
|
|
func FieldValueIsNormalized(field string) bool {
|
|
_, ok := normalizedValueFields[field]
|
|
return ok
|
|
}
|
|
|
|
// FieldIsCaseInsensitive reports whether a field's default search is case-insensitive.
|
|
func FieldIsCaseInsensitive(field string) bool {
|
|
_, ok := caseInsensitiveFields()[field]
|
|
return ok
|
|
}
|
|
|
|
// FieldIsPath reports whether a field is a hierarchical path field.
|
|
func FieldIsPath(field string) bool {
|
|
_, ok := pathFields()[field]
|
|
return ok
|
|
}
|
|
|
|
// FieldIsFulltext reports whether a field is an analyzed full-text field.
|
|
func FieldIsFulltext(field string) bool {
|
|
_, ok := fulltextFields()[field]
|
|
return ok
|
|
}
|