mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-13 06:09:21 -04:00
Keyword and path fields always index their case-preserved base and, when CaseInsensitive is set, an additional <field>_lowercase sibling used only for matching. The KQL lowering marks a restriction case-insensitive; each backend searches the sibling and lowercases the query value the same way the sibling is precomputed at index time (Go strings.ToLower on both sides, so non-ASCII stays consistent). Search always returns the case-preserved base, so the sibling never has to be read back. In bleve it is indexed but not stored, kept out of _all, and without doc values. In OpenSearch it deliberately stays in _source: excluding it would make every update-by-query script rebuild all siblings from the document via painless toLowerCase, which lowercases differently than Go and would drift from the query side. Keeping it in _source avoids that, and a lowercased copy of a name or path is negligible disk in a cluster. The OpenSearch move script keeps the base and its sibling in sync by swapping the moved prefix in Path_lowercase and setting Name_lowercase from Go-lowercased params, so case-insensitive search still finds a file after it moves (previously the sibling went stale). bleve re-indexes the whole document on move/delete/restore, so its siblings stay fresh for free. This also repairs OpenSearch path search (the query value was no longer folded to lowercase, so path:<Foo> returned nothing) and makes bleve path queries match a folder and its descendants like OpenSearch's path_hierarchy. The Path base stays case-preserved so the move/delete descendant update (an exact TermQuery on Path) matches mixed-case folders.
82 lines
2.3 KiB
Go
82 lines
2.3 KiB
Go
package query
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/opencloud-eu/opencloud/pkg/ast"
|
|
"github.com/opencloud-eu/opencloud/services/search/pkg/query/mimetype"
|
|
)
|
|
|
|
// Normalize is the shared KQL lowering pass between parse and compile: it
|
|
// resolves keys to real field names (via resolve) and expands media-type
|
|
// restrictions, so the backends compile a plain field:value AST.
|
|
func Normalize(a *ast.Ast, resolve func(string) string) *ast.Ast {
|
|
a.Nodes = normalizeNodes(a.Nodes, resolve, "")
|
|
return a
|
|
}
|
|
|
|
// normalizeNodes rewrites nodes in place. defaultKey is what a bare restriction
|
|
// inherits: its enclosing group's key, or "" at the top level.
|
|
func normalizeNodes(nodes []ast.Node, resolve func(string) string, defaultKey string) []ast.Node {
|
|
resolveKey := func(key string) string {
|
|
if key == "" && defaultKey != "" {
|
|
return defaultKey // bare child inherits the group key
|
|
}
|
|
return resolve(key)
|
|
}
|
|
|
|
out := make([]ast.Node, 0, len(nodes))
|
|
for _, n := range nodes {
|
|
n = toPointer(n) // ensure a pointer so in-place key rewrites persist
|
|
switch node := n.(type) {
|
|
case *ast.StringNode:
|
|
node.Key = resolveKey(node.Key)
|
|
if FieldValueIsNormalized(node.Key) {
|
|
node.Value = strings.ToLower(node.Value)
|
|
}
|
|
if exp := mimetype.Expand(node.Key, node.Value); exp != nil {
|
|
out = append(out, normalizeNodes(exp, resolve, defaultKey)...)
|
|
continue
|
|
}
|
|
node.CaseInsensitive = FieldIsCaseInsensitive(node.Key)
|
|
out = append(out, node)
|
|
case *ast.DateTimeNode:
|
|
node.Key = resolveKey(node.Key)
|
|
out = append(out, node)
|
|
case *ast.BooleanNode:
|
|
node.Key = resolveKey(node.Key)
|
|
out = append(out, node)
|
|
case *ast.NumberNode:
|
|
node.Key = resolveKey(node.Key)
|
|
out = append(out, node)
|
|
case *ast.GroupNode:
|
|
groupKey := defaultKey
|
|
if node.Key != "" {
|
|
node.Key = resolve(node.Key)
|
|
groupKey = node.Key
|
|
}
|
|
node.Nodes = normalizeNodes(node.Nodes, resolve, groupKey)
|
|
out = append(out, node)
|
|
default:
|
|
out = append(out, n)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// toPointer returns n as a pointer; the parser emits some nodes by value and the
|
|
// in-place key rewrites would be lost on those.
|
|
func toPointer(n ast.Node) ast.Node {
|
|
rv := reflect.ValueOf(n)
|
|
if rv.Kind() == reflect.Ptr {
|
|
return n
|
|
}
|
|
ptr := reflect.New(rv.Type())
|
|
ptr.Elem().Set(rv)
|
|
if pn, ok := ptr.Interface().(ast.Node); ok {
|
|
return pn
|
|
}
|
|
return n
|
|
}
|