mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-14 14:49:31 -04:00
The OpenSearch _lowercase siblings are search-only like bleve's (no doc_values, own map instance instead of aliasing the base), and the dead Path_words field is gone: _words exists for keyword fields only, as SearchSiblings declares.
137 lines
4.2 KiB
Go
137 lines
4.2 KiB
Go
package mapping
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"reflect"
|
|
)
|
|
|
|
// OpenSearchBuildMapping builds the OpenSearch "properties" map (the value
|
|
// of mappings.properties) for type t by walking the struct via reflection.
|
|
// Field names come from json tags; overrides are keyed by those names.
|
|
//
|
|
// The returned map contains plain JSON-friendly values (strings, bools,
|
|
// nested maps) and can be marshalled directly.
|
|
func OpenSearchBuildMapping(t reflect.Type, overrides map[string]FieldOpts) (map[string]any, error) {
|
|
return buildOpenSearchProperties(t, overrides, "")
|
|
}
|
|
|
|
func buildOpenSearchProperties(t reflect.Type, overrides map[string]FieldOpts, prefix string) (map[string]any, error) {
|
|
props := map[string]any{}
|
|
err := walkFields(t, func(fi fieldInfo) error {
|
|
key := fi.Name
|
|
if prefix != "" {
|
|
key = prefix + "." + fi.Name
|
|
}
|
|
opts := overrides[key]
|
|
fieldType := opts.Type
|
|
if fieldType == "" {
|
|
fieldType = inferType(fi.GoField.Type)
|
|
}
|
|
|
|
if fieldType == TypeObject {
|
|
sub := structType(fi.GoField.Type)
|
|
if sub == nil {
|
|
return fmt.Errorf("mapping: object type on non-struct field %q", key)
|
|
}
|
|
subProps, err := buildOpenSearchProperties(sub, overrides, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
props[fi.Name] = map[string]any{"properties": subProps}
|
|
return nil
|
|
}
|
|
|
|
if fieldType == TypeGeopoint {
|
|
// Keep the facet object, add a sibling _geopoint field (see GeopointSuffix).
|
|
sub := structType(fi.GoField.Type)
|
|
if sub == nil {
|
|
return fmt.Errorf("mapping: geopoint type on non-struct field %q", key)
|
|
}
|
|
subProps, err := buildOpenSearchProperties(sub, overrides, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
props[fi.Name] = map[string]any{"properties": subProps}
|
|
props[fi.Name+GeopointSuffix] = map[string]any{"type": "geo_point"}
|
|
return nil
|
|
}
|
|
|
|
if fieldType == TypeKeyword || fieldType == TypePath {
|
|
// path_hierarchy is case-preserving here; casing lives in the value.
|
|
m := map[string]any{"type": "keyword"}
|
|
if fieldType == TypePath {
|
|
m = map[string]any{"type": "text", "analyzer": "path_hierarchy"}
|
|
}
|
|
props[fi.Name] = m
|
|
if opts.caseInsensitive() {
|
|
// search-only, like the bleve sibling: never sorted or
|
|
// aggregated on, so no doc_values
|
|
sibling := maps.Clone(m)
|
|
if sibling["type"] == "keyword" {
|
|
sibling["doc_values"] = false
|
|
}
|
|
props[fi.Name+LowercaseSuffix] = sibling
|
|
}
|
|
if fieldType == TypeKeyword && opts.wordBroken() {
|
|
props[fi.Name+WordsSuffix] = map[string]any{"type": "text", "analyzer": WordsAnalyzer}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
fm, err := openSearchFieldMapping(fieldType, fi.GoField.Type)
|
|
if err != nil {
|
|
return fmt.Errorf("mapping: field %q: %w", key, err)
|
|
}
|
|
props[fi.Name] = fm
|
|
return nil
|
|
})
|
|
return props, err
|
|
}
|
|
|
|
// openSearchFieldMapping handles the non-keyword/path types; keyword and path
|
|
// are emitted (with their cased forms) by buildOpenSearchProperties directly.
|
|
func openSearchFieldMapping(fieldType string, goType reflect.Type) (map[string]any, error) {
|
|
switch fieldType {
|
|
case TypeFulltext:
|
|
return map[string]any{
|
|
"type": "text",
|
|
"term_vector": "with_positions_offsets",
|
|
"analyzer": WordsAnalyzer,
|
|
}, nil
|
|
case TypeWildcard:
|
|
// OpenSearch stores wildcard fields with doc_values=false by
|
|
// default, so emit it explicitly to keep local and remote
|
|
// mappings in sync for the Apply comparison.
|
|
return map[string]any{"type": "wildcard", "doc_values": false}, nil
|
|
case TypeNumeric:
|
|
return map[string]any{"type": openSearchNumericType(goType)}, nil
|
|
case TypeBool:
|
|
return map[string]any{"type": "boolean"}, nil
|
|
case TypeDatetime:
|
|
return map[string]any{"type": "date"}, nil
|
|
case TypeGeopoint:
|
|
return map[string]any{"type": "geo_point"}, nil
|
|
case "":
|
|
return nil, fmt.Errorf("no type inferred and no override")
|
|
}
|
|
return nil, fmt.Errorf("unsupported type %q", fieldType)
|
|
}
|
|
|
|
// openSearchNumericType maps a Go numeric type to an OpenSearch numeric
|
|
// field type.
|
|
func openSearchNumericType(t reflect.Type) string {
|
|
t = deref(t)
|
|
switch t.Kind() {
|
|
case reflect.Float32:
|
|
return "float"
|
|
case reflect.Float64:
|
|
return "double"
|
|
case reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16:
|
|
return "short"
|
|
case reflect.Int32, reflect.Uint32:
|
|
return "integer"
|
|
}
|
|
return "long"
|
|
}
|