forked from forgejo/forgejo
Upgrade blevesearch dependency to v2.0.1 (#14346)
* Upgrade blevesearch dependency to v2.0.1 * Update rupture to v1.0.0 * Fix test
This commit is contained in:
parent
3aa53dc6bc
commit
f5abe2f563
459 changed files with 7518 additions and 4211 deletions
99
vendor/github.com/blevesearch/bleve/v2/mapping/analysis.go
generated
vendored
Normal file
99
vendor/github.com/blevesearch/bleve/v2/mapping/analysis.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapping
|
||||
|
||||
type customAnalysis struct {
|
||||
CharFilters map[string]map[string]interface{} `json:"char_filters,omitempty"`
|
||||
Tokenizers map[string]map[string]interface{} `json:"tokenizers,omitempty"`
|
||||
TokenMaps map[string]map[string]interface{} `json:"token_maps,omitempty"`
|
||||
TokenFilters map[string]map[string]interface{} `json:"token_filters,omitempty"`
|
||||
Analyzers map[string]map[string]interface{} `json:"analyzers,omitempty"`
|
||||
DateTimeParsers map[string]map[string]interface{} `json:"date_time_parsers,omitempty"`
|
||||
}
|
||||
|
||||
func (c *customAnalysis) registerAll(i *IndexMappingImpl) error {
|
||||
for name, config := range c.CharFilters {
|
||||
_, err := i.cache.DefineCharFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Tokenizers) > 0 {
|
||||
// put all the names in map tracking work to do
|
||||
todo := map[string]struct{}{}
|
||||
for name := range c.Tokenizers {
|
||||
todo[name] = struct{}{}
|
||||
}
|
||||
registered := 1
|
||||
errs := []error{}
|
||||
// as long as we keep making progress, keep going
|
||||
for len(todo) > 0 && registered > 0 {
|
||||
registered = 0
|
||||
errs = []error{}
|
||||
for name := range todo {
|
||||
config := c.Tokenizers[name]
|
||||
_, err := i.cache.DefineTokenizer(name, config)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
delete(todo, name)
|
||||
registered++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs[0]
|
||||
}
|
||||
}
|
||||
for name, config := range c.TokenMaps {
|
||||
_, err := i.cache.DefineTokenMap(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, config := range c.TokenFilters {
|
||||
_, err := i.cache.DefineTokenFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, config := range c.Analyzers {
|
||||
_, err := i.cache.DefineAnalyzer(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, config := range c.DateTimeParsers {
|
||||
_, err := i.cache.DefineDateTimeParser(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCustomAnalysis() *customAnalysis {
|
||||
rv := customAnalysis{
|
||||
CharFilters: make(map[string]map[string]interface{}),
|
||||
Tokenizers: make(map[string]map[string]interface{}),
|
||||
TokenMaps: make(map[string]map[string]interface{}),
|
||||
TokenFilters: make(map[string]map[string]interface{}),
|
||||
Analyzers: make(map[string]map[string]interface{}),
|
||||
DateTimeParsers: make(map[string]map[string]interface{}),
|
||||
}
|
||||
return &rv
|
||||
}
|
558
vendor/github.com/blevesearch/bleve/v2/mapping/document.go
generated
vendored
Normal file
558
vendor/github.com/blevesearch/bleve/v2/mapping/document.go
generated
vendored
Normal file
|
@ -0,0 +1,558 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/registry"
|
||||
)
|
||||
|
||||
// A DocumentMapping describes how a type of document
|
||||
// should be indexed.
|
||||
// As documents can be hierarchical, named sub-sections
|
||||
// of documents are mapped using the same structure in
|
||||
// the Properties field.
|
||||
// Each value inside a document can be indexed 0 or more
|
||||
// ways. These index entries are called fields and
|
||||
// are stored in the Fields field.
|
||||
// Entire sections of a document can be ignored or
|
||||
// excluded by setting Enabled to false.
|
||||
// If not explicitly mapped, default mapping operations
|
||||
// are used. To disable this automatic handling, set
|
||||
// Dynamic to false.
|
||||
type DocumentMapping struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Dynamic bool `json:"dynamic"`
|
||||
Properties map[string]*DocumentMapping `json:"properties,omitempty"`
|
||||
Fields []*FieldMapping `json:"fields,omitempty"`
|
||||
DefaultAnalyzer string `json:"default_analyzer,omitempty"`
|
||||
|
||||
// StructTagKey overrides "json" when looking for field names in struct tags
|
||||
StructTagKey string `json:"struct_tag_key,omitempty"`
|
||||
}
|
||||
|
||||
func (dm *DocumentMapping) Validate(cache *registry.Cache) error {
|
||||
var err error
|
||||
if dm.DefaultAnalyzer != "" {
|
||||
_, err := cache.AnalyzerNamed(dm.DefaultAnalyzer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, property := range dm.Properties {
|
||||
err = property.Validate(cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, field := range dm.Fields {
|
||||
if field.Analyzer != "" {
|
||||
_, err = cache.AnalyzerNamed(field.Analyzer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if field.DateFormat != "" {
|
||||
_, err = cache.DateTimeParserNamed(field.DateFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch field.Type {
|
||||
case "text", "datetime", "number", "boolean", "geopoint":
|
||||
default:
|
||||
return fmt.Errorf("unknown field type: '%s'", field.Type)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// analyzerNameForPath attempts to first find the field
|
||||
// described by this path, then returns the analyzer
|
||||
// configured for that field
|
||||
func (dm *DocumentMapping) analyzerNameForPath(path string) string {
|
||||
field := dm.fieldDescribedByPath(path)
|
||||
if field != nil {
|
||||
return field.Analyzer
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (dm *DocumentMapping) fieldDescribedByPath(path string) *FieldMapping {
|
||||
pathElements := decodePath(path)
|
||||
if len(pathElements) > 1 {
|
||||
// easy case, there is more than 1 path element remaining
|
||||
// the next path element must match a property name
|
||||
// at this level
|
||||
for propName, subDocMapping := range dm.Properties {
|
||||
if propName == pathElements[0] {
|
||||
return subDocMapping.fieldDescribedByPath(encodePath(pathElements[1:]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// either the path just had one element
|
||||
// or it had multiple, but no match for the first element at this level
|
||||
// look for match with full path
|
||||
|
||||
// first look for property name with empty field
|
||||
for propName, subDocMapping := range dm.Properties {
|
||||
if propName == path {
|
||||
// found property name match, now look at its fields
|
||||
for _, field := range subDocMapping.Fields {
|
||||
if field.Name == "" || field.Name == path {
|
||||
// match
|
||||
return field
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// next, walk the properties again, looking for field overriding the name
|
||||
for propName, subDocMapping := range dm.Properties {
|
||||
if propName != path {
|
||||
// property name isn't a match, but field name could override it
|
||||
for _, field := range subDocMapping.Fields {
|
||||
if field.Name == path {
|
||||
return field
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// documentMappingForPath only returns EXACT matches for a sub document
|
||||
// or for an explicitly mapped field, if you want to find the
|
||||
// closest document mapping to a field not explicitly mapped
|
||||
// use closestDocMapping
|
||||
func (dm *DocumentMapping) documentMappingForPath(path string) *DocumentMapping {
|
||||
pathElements := decodePath(path)
|
||||
current := dm
|
||||
OUTER:
|
||||
for i, pathElement := range pathElements {
|
||||
for name, subDocMapping := range current.Properties {
|
||||
if name == pathElement {
|
||||
current = subDocMapping
|
||||
continue OUTER
|
||||
}
|
||||
}
|
||||
// no subDocMapping matches this pathElement
|
||||
// only if this is the last element check for field name
|
||||
if i == len(pathElements)-1 {
|
||||
for _, field := range current.Fields {
|
||||
if field.Name == pathElement {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
// closestDocMapping findest the most specific document mapping that matches
|
||||
// part of the provided path
|
||||
func (dm *DocumentMapping) closestDocMapping(path string) *DocumentMapping {
|
||||
pathElements := decodePath(path)
|
||||
current := dm
|
||||
OUTER:
|
||||
for _, pathElement := range pathElements {
|
||||
for name, subDocMapping := range current.Properties {
|
||||
if name == pathElement {
|
||||
current = subDocMapping
|
||||
continue OUTER
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
// NewDocumentMapping returns a new document mapping
|
||||
// with all the default values.
|
||||
func NewDocumentMapping() *DocumentMapping {
|
||||
return &DocumentMapping{
|
||||
Enabled: true,
|
||||
Dynamic: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDocumentStaticMapping returns a new document
|
||||
// mapping that will not automatically index parts
|
||||
// of a document without an explicit mapping.
|
||||
func NewDocumentStaticMapping() *DocumentMapping {
|
||||
return &DocumentMapping{
|
||||
Enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDocumentDisabledMapping returns a new document
|
||||
// mapping that will not perform any indexing.
|
||||
func NewDocumentDisabledMapping() *DocumentMapping {
|
||||
return &DocumentMapping{}
|
||||
}
|
||||
|
||||
// AddSubDocumentMapping adds the provided DocumentMapping as a sub-mapping
|
||||
// for the specified named subsection.
|
||||
func (dm *DocumentMapping) AddSubDocumentMapping(property string, sdm *DocumentMapping) {
|
||||
if dm.Properties == nil {
|
||||
dm.Properties = make(map[string]*DocumentMapping)
|
||||
}
|
||||
dm.Properties[property] = sdm
|
||||
}
|
||||
|
||||
// AddFieldMappingsAt adds one or more FieldMappings
|
||||
// at the named sub-document. If the named sub-document
|
||||
// doesn't yet exist it is created for you.
|
||||
// This is a convenience function to make most common
|
||||
// mappings more concise.
|
||||
// Otherwise, you would:
|
||||
// subMapping := NewDocumentMapping()
|
||||
// subMapping.AddFieldMapping(fieldMapping)
|
||||
// parentMapping.AddSubDocumentMapping(property, subMapping)
|
||||
func (dm *DocumentMapping) AddFieldMappingsAt(property string, fms ...*FieldMapping) {
|
||||
if dm.Properties == nil {
|
||||
dm.Properties = make(map[string]*DocumentMapping)
|
||||
}
|
||||
sdm, ok := dm.Properties[property]
|
||||
if !ok {
|
||||
sdm = NewDocumentMapping()
|
||||
}
|
||||
for _, fm := range fms {
|
||||
sdm.AddFieldMapping(fm)
|
||||
}
|
||||
dm.Properties[property] = sdm
|
||||
}
|
||||
|
||||
// AddFieldMapping adds the provided FieldMapping for this section
|
||||
// of the document.
|
||||
func (dm *DocumentMapping) AddFieldMapping(fm *FieldMapping) {
|
||||
if dm.Fields == nil {
|
||||
dm.Fields = make([]*FieldMapping, 0)
|
||||
}
|
||||
dm.Fields = append(dm.Fields, fm)
|
||||
}
|
||||
|
||||
// UnmarshalJSON offers custom unmarshaling with optional strict validation
|
||||
func (dm *DocumentMapping) UnmarshalJSON(data []byte) error {
|
||||
var tmp map[string]json.RawMessage
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set defaults for fields which might have been omitted
|
||||
dm.Enabled = true
|
||||
dm.Dynamic = true
|
||||
|
||||
var invalidKeys []string
|
||||
for k, v := range tmp {
|
||||
switch k {
|
||||
case "enabled":
|
||||
err := json.Unmarshal(v, &dm.Enabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "dynamic":
|
||||
err := json.Unmarshal(v, &dm.Dynamic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "default_analyzer":
|
||||
err := json.Unmarshal(v, &dm.DefaultAnalyzer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "properties":
|
||||
err := json.Unmarshal(v, &dm.Properties)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "fields":
|
||||
err := json.Unmarshal(v, &dm.Fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "struct_tag_key":
|
||||
err := json.Unmarshal(v, &dm.StructTagKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
invalidKeys = append(invalidKeys, k)
|
||||
}
|
||||
}
|
||||
|
||||
if MappingJSONStrict && len(invalidKeys) > 0 {
|
||||
return fmt.Errorf("document mapping contains invalid keys: %v", invalidKeys)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dm *DocumentMapping) defaultAnalyzerName(path []string) string {
|
||||
current := dm
|
||||
rv := current.DefaultAnalyzer
|
||||
for _, pathElement := range path {
|
||||
var ok bool
|
||||
current, ok = current.Properties[pathElement]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if current.DefaultAnalyzer != "" {
|
||||
rv = current.DefaultAnalyzer
|
||||
}
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (dm *DocumentMapping) walkDocument(data interface{}, path []string, indexes []uint64, context *walkContext) {
|
||||
// allow default "json" tag to be overridden
|
||||
structTagKey := dm.StructTagKey
|
||||
if structTagKey == "" {
|
||||
structTagKey = "json"
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(data)
|
||||
if !val.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
switch typ.Kind() {
|
||||
case reflect.Map:
|
||||
// FIXME can add support for other map keys in the future
|
||||
if typ.Key().Kind() == reflect.String {
|
||||
for _, key := range val.MapKeys() {
|
||||
fieldName := key.String()
|
||||
fieldVal := val.MapIndex(key).Interface()
|
||||
dm.processProperty(fieldVal, append(path, fieldName), indexes, context)
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
fieldName := field.Name
|
||||
// anonymous fields of type struct can elide the type name
|
||||
if field.Anonymous && field.Type.Kind() == reflect.Struct {
|
||||
fieldName = ""
|
||||
}
|
||||
|
||||
// if the field has a name under the specified tag, prefer that
|
||||
tag := field.Tag.Get(structTagKey)
|
||||
tagFieldName := parseTagName(tag)
|
||||
if tagFieldName == "-" {
|
||||
continue
|
||||
}
|
||||
// allow tag to set field name to empty, only if anonymous
|
||||
if field.Tag != "" && (tagFieldName != "" || field.Anonymous) {
|
||||
fieldName = tagFieldName
|
||||
}
|
||||
|
||||
if val.Field(i).CanInterface() {
|
||||
fieldVal := val.Field(i).Interface()
|
||||
newpath := path
|
||||
if fieldName != "" {
|
||||
newpath = append(path, fieldName)
|
||||
}
|
||||
dm.processProperty(fieldVal, newpath, indexes, context)
|
||||
}
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
if val.Index(i).CanInterface() {
|
||||
fieldVal := val.Index(i).Interface()
|
||||
dm.processProperty(fieldVal, path, append(indexes, uint64(i)), context)
|
||||
}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
ptrElem := val.Elem()
|
||||
if ptrElem.IsValid() && ptrElem.CanInterface() {
|
||||
dm.processProperty(ptrElem.Interface(), path, indexes, context)
|
||||
}
|
||||
case reflect.String:
|
||||
dm.processProperty(val.String(), path, indexes, context)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
dm.processProperty(float64(val.Int()), path, indexes, context)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
dm.processProperty(float64(val.Uint()), path, indexes, context)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
dm.processProperty(float64(val.Float()), path, indexes, context)
|
||||
case reflect.Bool:
|
||||
dm.processProperty(val.Bool(), path, indexes, context)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (dm *DocumentMapping) processProperty(property interface{}, path []string, indexes []uint64, context *walkContext) {
|
||||
pathString := encodePath(path)
|
||||
// look to see if there is a mapping for this field
|
||||
subDocMapping := dm.documentMappingForPath(pathString)
|
||||
closestDocMapping := dm.closestDocMapping(pathString)
|
||||
|
||||
// check to see if we even need to do further processing
|
||||
if subDocMapping != nil && !subDocMapping.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
propertyValue := reflect.ValueOf(property)
|
||||
if !propertyValue.IsValid() {
|
||||
// cannot do anything with the zero value
|
||||
return
|
||||
}
|
||||
propertyType := propertyValue.Type()
|
||||
switch propertyType.Kind() {
|
||||
case reflect.String:
|
||||
propertyValueString := propertyValue.String()
|
||||
if subDocMapping != nil {
|
||||
// index by explicit mapping
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
if fieldMapping.Type == "geopoint" {
|
||||
fieldMapping.processGeoPoint(property, pathString, path, indexes, context)
|
||||
} else {
|
||||
fieldMapping.processString(propertyValueString, pathString, path, indexes, context)
|
||||
}
|
||||
}
|
||||
} else if closestDocMapping.Dynamic {
|
||||
// automatic indexing behavior
|
||||
|
||||
// first see if it can be parsed by the default date parser
|
||||
dateTimeParser := context.im.DateTimeParserNamed(context.im.DefaultDateTimeParser)
|
||||
if dateTimeParser != nil {
|
||||
parsedDateTime, err := dateTimeParser.ParseDateTime(propertyValueString)
|
||||
if err != nil {
|
||||
// index as text
|
||||
fieldMapping := newTextFieldMappingDynamic(context.im)
|
||||
fieldMapping.processString(propertyValueString, pathString, path, indexes, context)
|
||||
} else {
|
||||
// index as datetime
|
||||
fieldMapping := newDateTimeFieldMappingDynamic(context.im)
|
||||
fieldMapping.processTime(parsedDateTime, pathString, path, indexes, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
dm.processProperty(float64(propertyValue.Int()), path, indexes, context)
|
||||
return
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
dm.processProperty(float64(propertyValue.Uint()), path, indexes, context)
|
||||
return
|
||||
case reflect.Float64, reflect.Float32:
|
||||
propertyValFloat := propertyValue.Float()
|
||||
if subDocMapping != nil {
|
||||
// index by explicit mapping
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
fieldMapping.processFloat64(propertyValFloat, pathString, path, indexes, context)
|
||||
}
|
||||
} else if closestDocMapping.Dynamic {
|
||||
// automatic indexing behavior
|
||||
fieldMapping := newNumericFieldMappingDynamic(context.im)
|
||||
fieldMapping.processFloat64(propertyValFloat, pathString, path, indexes, context)
|
||||
}
|
||||
case reflect.Bool:
|
||||
propertyValBool := propertyValue.Bool()
|
||||
if subDocMapping != nil {
|
||||
// index by explicit mapping
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context)
|
||||
}
|
||||
} else if closestDocMapping.Dynamic {
|
||||
// automatic indexing behavior
|
||||
fieldMapping := newBooleanFieldMappingDynamic(context.im)
|
||||
fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context)
|
||||
}
|
||||
case reflect.Struct:
|
||||
switch property := property.(type) {
|
||||
case time.Time:
|
||||
// don't descend into the time struct
|
||||
if subDocMapping != nil {
|
||||
// index by explicit mapping
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
fieldMapping.processTime(property, pathString, path, indexes, context)
|
||||
}
|
||||
} else if closestDocMapping.Dynamic {
|
||||
fieldMapping := newDateTimeFieldMappingDynamic(context.im)
|
||||
fieldMapping.processTime(property, pathString, path, indexes, context)
|
||||
}
|
||||
case encoding.TextMarshaler:
|
||||
txt, err := property.MarshalText()
|
||||
if err == nil && subDocMapping != nil {
|
||||
// index by explicit mapping
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
if fieldMapping.Type == "text" {
|
||||
fieldMapping.processString(string(txt), pathString, path, indexes, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
dm.walkDocument(property, path, indexes, context)
|
||||
default:
|
||||
if subDocMapping != nil {
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
if fieldMapping.Type == "geopoint" {
|
||||
fieldMapping.processGeoPoint(property, pathString, path, indexes, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
dm.walkDocument(property, path, indexes, context)
|
||||
}
|
||||
case reflect.Map, reflect.Slice:
|
||||
if subDocMapping != nil {
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
if fieldMapping.Type == "geopoint" {
|
||||
fieldMapping.processGeoPoint(property, pathString, path, indexes, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
dm.walkDocument(property, path, indexes, context)
|
||||
case reflect.Ptr:
|
||||
if !propertyValue.IsNil() {
|
||||
switch property := property.(type) {
|
||||
case encoding.TextMarshaler:
|
||||
// ONLY process TextMarshaler if there is an explicit mapping
|
||||
// AND all of the fiels are of type text
|
||||
// OTHERWISE process field without TextMarshaler
|
||||
if subDocMapping != nil {
|
||||
allFieldsText := true
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
if fieldMapping.Type != "text" {
|
||||
allFieldsText = false
|
||||
break
|
||||
}
|
||||
}
|
||||
txt, err := property.MarshalText()
|
||||
if err == nil && allFieldsText {
|
||||
txtStr := string(txt)
|
||||
for _, fieldMapping := range subDocMapping.Fields {
|
||||
fieldMapping.processString(txtStr, pathString, path, indexes, context)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
dm.walkDocument(property, path, indexes, context)
|
||||
default:
|
||||
dm.walkDocument(property, path, indexes, context)
|
||||
}
|
||||
}
|
||||
default:
|
||||
dm.walkDocument(property, path, indexes, context)
|
||||
}
|
||||
}
|
359
vendor/github.com/blevesearch/bleve/v2/mapping/field.go
generated
vendored
Normal file
359
vendor/github.com/blevesearch/bleve/v2/mapping/field.go
generated
vendored
Normal file
|
@ -0,0 +1,359 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
index "github.com/blevesearch/bleve_index_api"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/analysis"
|
||||
"github.com/blevesearch/bleve/v2/document"
|
||||
"github.com/blevesearch/bleve/v2/geo"
|
||||
)
|
||||
|
||||
// control the default behavior for dynamic fields (those not explicitly mapped)
|
||||
var (
|
||||
IndexDynamic = true
|
||||
StoreDynamic = true
|
||||
DocValuesDynamic = true // TODO revisit default?
|
||||
)
|
||||
|
||||
// A FieldMapping describes how a specific item
|
||||
// should be put into the index.
|
||||
type FieldMapping struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
// Analyzer specifies the name of the analyzer to use for this field. If
|
||||
// Analyzer is empty, traverse the DocumentMapping tree toward the root and
|
||||
// pick the first non-empty DefaultAnalyzer found. If there is none, use
|
||||
// the IndexMapping.DefaultAnalyzer.
|
||||
Analyzer string `json:"analyzer,omitempty"`
|
||||
|
||||
// Store indicates whether to store field values in the index. Stored
|
||||
// values can be retrieved from search results using SearchRequest.Fields.
|
||||
Store bool `json:"store,omitempty"`
|
||||
Index bool `json:"index,omitempty"`
|
||||
|
||||
// IncludeTermVectors, if true, makes terms occurrences to be recorded for
|
||||
// this field. It includes the term position within the terms sequence and
|
||||
// the term offsets in the source document field. Term vectors are required
|
||||
// to perform phrase queries or terms highlighting in source documents.
|
||||
IncludeTermVectors bool `json:"include_term_vectors,omitempty"`
|
||||
IncludeInAll bool `json:"include_in_all,omitempty"`
|
||||
DateFormat string `json:"date_format,omitempty"`
|
||||
|
||||
// DocValues, if true makes the index uninverting possible for this field
|
||||
// It is useful for faceting and sorting queries.
|
||||
DocValues bool `json:"docvalues,omitempty"`
|
||||
|
||||
// SkipFreqNorm, if true, avoids the indexing of frequency and norm values
|
||||
// of the tokens for this field. This option would be useful for saving
|
||||
// the processing of freq/norm details when the default score based relevancy
|
||||
// isn't needed.
|
||||
SkipFreqNorm bool `json:"skip_freq_norm,omitempty"`
|
||||
}
|
||||
|
||||
// NewTextFieldMapping returns a default field mapping for text
|
||||
func NewTextFieldMapping() *FieldMapping {
|
||||
return &FieldMapping{
|
||||
Type: "text",
|
||||
Store: true,
|
||||
Index: true,
|
||||
IncludeTermVectors: true,
|
||||
IncludeInAll: true,
|
||||
DocValues: true,
|
||||
}
|
||||
}
|
||||
|
||||
func newTextFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
|
||||
rv := NewTextFieldMapping()
|
||||
rv.Store = im.StoreDynamic
|
||||
rv.Index = im.IndexDynamic
|
||||
rv.DocValues = im.DocValuesDynamic
|
||||
return rv
|
||||
}
|
||||
|
||||
// NewNumericFieldMapping returns a default field mapping for numbers
|
||||
func NewNumericFieldMapping() *FieldMapping {
|
||||
return &FieldMapping{
|
||||
Type: "number",
|
||||
Store: true,
|
||||
Index: true,
|
||||
IncludeInAll: true,
|
||||
DocValues: true,
|
||||
}
|
||||
}
|
||||
|
||||
func newNumericFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
|
||||
rv := NewNumericFieldMapping()
|
||||
rv.Store = im.StoreDynamic
|
||||
rv.Index = im.IndexDynamic
|
||||
rv.DocValues = im.DocValuesDynamic
|
||||
return rv
|
||||
}
|
||||
|
||||
// NewDateTimeFieldMapping returns a default field mapping for dates
|
||||
func NewDateTimeFieldMapping() *FieldMapping {
|
||||
return &FieldMapping{
|
||||
Type: "datetime",
|
||||
Store: true,
|
||||
Index: true,
|
||||
IncludeInAll: true,
|
||||
DocValues: true,
|
||||
}
|
||||
}
|
||||
|
||||
func newDateTimeFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
|
||||
rv := NewDateTimeFieldMapping()
|
||||
rv.Store = im.StoreDynamic
|
||||
rv.Index = im.IndexDynamic
|
||||
rv.DocValues = im.DocValuesDynamic
|
||||
return rv
|
||||
}
|
||||
|
||||
// NewBooleanFieldMapping returns a default field mapping for booleans
|
||||
func NewBooleanFieldMapping() *FieldMapping {
|
||||
return &FieldMapping{
|
||||
Type: "boolean",
|
||||
Store: true,
|
||||
Index: true,
|
||||
IncludeInAll: true,
|
||||
DocValues: true,
|
||||
}
|
||||
}
|
||||
|
||||
func newBooleanFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
|
||||
rv := NewBooleanFieldMapping()
|
||||
rv.Store = im.StoreDynamic
|
||||
rv.Index = im.IndexDynamic
|
||||
rv.DocValues = im.DocValuesDynamic
|
||||
return rv
|
||||
}
|
||||
|
||||
// NewGeoPointFieldMapping returns a default field mapping for geo points
|
||||
func NewGeoPointFieldMapping() *FieldMapping {
|
||||
return &FieldMapping{
|
||||
Type: "geopoint",
|
||||
Store: true,
|
||||
Index: true,
|
||||
IncludeInAll: true,
|
||||
DocValues: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Options returns the indexing options for this field.
|
||||
func (fm *FieldMapping) Options() index.FieldIndexingOptions {
|
||||
var rv index.FieldIndexingOptions
|
||||
if fm.Store {
|
||||
rv |= index.StoreField
|
||||
}
|
||||
if fm.Index {
|
||||
rv |= index.IndexField
|
||||
}
|
||||
if fm.IncludeTermVectors {
|
||||
rv |= index.IncludeTermVectors
|
||||
}
|
||||
if fm.DocValues {
|
||||
rv |= index.DocValues
|
||||
}
|
||||
if fm.SkipFreqNorm {
|
||||
rv |= index.SkipFreqNorm
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (fm *FieldMapping) processString(propertyValueString string, pathString string, path []string, indexes []uint64, context *walkContext) {
|
||||
fieldName := getFieldName(pathString, path, fm)
|
||||
options := fm.Options()
|
||||
if fm.Type == "text" {
|
||||
analyzer := fm.analyzerForField(path, context)
|
||||
field := document.NewTextFieldCustom(fieldName, indexes, []byte(propertyValueString), options, analyzer)
|
||||
context.doc.AddField(field)
|
||||
|
||||
if !fm.IncludeInAll {
|
||||
context.excludedFromAll = append(context.excludedFromAll, fieldName)
|
||||
}
|
||||
} else if fm.Type == "datetime" {
|
||||
dateTimeFormat := context.im.DefaultDateTimeParser
|
||||
if fm.DateFormat != "" {
|
||||
dateTimeFormat = fm.DateFormat
|
||||
}
|
||||
dateTimeParser := context.im.DateTimeParserNamed(dateTimeFormat)
|
||||
if dateTimeParser != nil {
|
||||
parsedDateTime, err := dateTimeParser.ParseDateTime(propertyValueString)
|
||||
if err == nil {
|
||||
fm.processTime(parsedDateTime, pathString, path, indexes, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FieldMapping) processFloat64(propertyValFloat float64, pathString string, path []string, indexes []uint64, context *walkContext) {
|
||||
fieldName := getFieldName(pathString, path, fm)
|
||||
if fm.Type == "number" {
|
||||
options := fm.Options()
|
||||
field := document.NewNumericFieldWithIndexingOptions(fieldName, indexes, propertyValFloat, options)
|
||||
context.doc.AddField(field)
|
||||
|
||||
if !fm.IncludeInAll {
|
||||
context.excludedFromAll = append(context.excludedFromAll, fieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FieldMapping) processTime(propertyValueTime time.Time, pathString string, path []string, indexes []uint64, context *walkContext) {
|
||||
fieldName := getFieldName(pathString, path, fm)
|
||||
if fm.Type == "datetime" {
|
||||
options := fm.Options()
|
||||
field, err := document.NewDateTimeFieldWithIndexingOptions(fieldName, indexes, propertyValueTime, options)
|
||||
if err == nil {
|
||||
context.doc.AddField(field)
|
||||
} else {
|
||||
logger.Printf("could not build date %v", err)
|
||||
}
|
||||
|
||||
if !fm.IncludeInAll {
|
||||
context.excludedFromAll = append(context.excludedFromAll, fieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FieldMapping) processBoolean(propertyValueBool bool, pathString string, path []string, indexes []uint64, context *walkContext) {
|
||||
fieldName := getFieldName(pathString, path, fm)
|
||||
if fm.Type == "boolean" {
|
||||
options := fm.Options()
|
||||
field := document.NewBooleanFieldWithIndexingOptions(fieldName, indexes, propertyValueBool, options)
|
||||
context.doc.AddField(field)
|
||||
|
||||
if !fm.IncludeInAll {
|
||||
context.excludedFromAll = append(context.excludedFromAll, fieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FieldMapping) processGeoPoint(propertyMightBeGeoPoint interface{}, pathString string, path []string, indexes []uint64, context *walkContext) {
|
||||
lon, lat, found := geo.ExtractGeoPoint(propertyMightBeGeoPoint)
|
||||
if found {
|
||||
fieldName := getFieldName(pathString, path, fm)
|
||||
options := fm.Options()
|
||||
field := document.NewGeoPointFieldWithIndexingOptions(fieldName, indexes, lon, lat, options)
|
||||
context.doc.AddField(field)
|
||||
|
||||
if !fm.IncludeInAll {
|
||||
context.excludedFromAll = append(context.excludedFromAll, fieldName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FieldMapping) analyzerForField(path []string, context *walkContext) *analysis.Analyzer {
|
||||
analyzerName := fm.Analyzer
|
||||
if analyzerName == "" {
|
||||
analyzerName = context.dm.defaultAnalyzerName(path)
|
||||
if analyzerName == "" {
|
||||
analyzerName = context.im.DefaultAnalyzer
|
||||
}
|
||||
}
|
||||
return context.im.AnalyzerNamed(analyzerName)
|
||||
}
|
||||
|
||||
func getFieldName(pathString string, path []string, fieldMapping *FieldMapping) string {
|
||||
fieldName := pathString
|
||||
if fieldMapping.Name != "" {
|
||||
parentName := ""
|
||||
if len(path) > 1 {
|
||||
parentName = encodePath(path[:len(path)-1]) + pathSeparator
|
||||
}
|
||||
fieldName = parentName + fieldMapping.Name
|
||||
}
|
||||
return fieldName
|
||||
}
|
||||
|
||||
// UnmarshalJSON offers custom unmarshaling with optional strict validation
|
||||
func (fm *FieldMapping) UnmarshalJSON(data []byte) error {
|
||||
|
||||
var tmp map[string]json.RawMessage
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var invalidKeys []string
|
||||
for k, v := range tmp {
|
||||
switch k {
|
||||
case "name":
|
||||
err := json.Unmarshal(v, &fm.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "type":
|
||||
err := json.Unmarshal(v, &fm.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "analyzer":
|
||||
err := json.Unmarshal(v, &fm.Analyzer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "store":
|
||||
err := json.Unmarshal(v, &fm.Store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "index":
|
||||
err := json.Unmarshal(v, &fm.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "include_term_vectors":
|
||||
err := json.Unmarshal(v, &fm.IncludeTermVectors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "include_in_all":
|
||||
err := json.Unmarshal(v, &fm.IncludeInAll)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "date_format":
|
||||
err := json.Unmarshal(v, &fm.DateFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "docvalues":
|
||||
err := json.Unmarshal(v, &fm.DocValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "skip_freq_norm":
|
||||
err := json.Unmarshal(v, &fm.SkipFreqNorm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
invalidKeys = append(invalidKeys, k)
|
||||
}
|
||||
}
|
||||
|
||||
if MappingJSONStrict && len(invalidKeys) > 0 {
|
||||
return fmt.Errorf("field mapping contains invalid keys: %v", invalidKeys)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
444
vendor/github.com/blevesearch/bleve/v2/mapping/index.go
generated
vendored
Normal file
444
vendor/github.com/blevesearch/bleve/v2/mapping/index.go
generated
vendored
Normal file
|
@ -0,0 +1,444 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
index "github.com/blevesearch/bleve_index_api"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/analysis"
|
||||
"github.com/blevesearch/bleve/v2/analysis/analyzer/standard"
|
||||
"github.com/blevesearch/bleve/v2/analysis/datetime/optional"
|
||||
"github.com/blevesearch/bleve/v2/document"
|
||||
"github.com/blevesearch/bleve/v2/registry"
|
||||
)
|
||||
|
||||
var MappingJSONStrict = false
|
||||
|
||||
const defaultTypeField = "_type"
|
||||
const defaultType = "_default"
|
||||
const defaultField = "_all"
|
||||
const defaultAnalyzer = standard.Name
|
||||
const defaultDateTimeParser = optional.Name
|
||||
|
||||
// An IndexMappingImpl controls how objects are placed
|
||||
// into an index.
|
||||
// First the type of the object is determined.
|
||||
// Once the type is know, the appropriate
|
||||
// DocumentMapping is selected by the type.
|
||||
// If no mapping was determined for that type,
|
||||
// a DefaultMapping will be used.
|
||||
type IndexMappingImpl struct {
|
||||
TypeMapping map[string]*DocumentMapping `json:"types,omitempty"`
|
||||
DefaultMapping *DocumentMapping `json:"default_mapping"`
|
||||
TypeField string `json:"type_field"`
|
||||
DefaultType string `json:"default_type"`
|
||||
DefaultAnalyzer string `json:"default_analyzer"`
|
||||
DefaultDateTimeParser string `json:"default_datetime_parser"`
|
||||
DefaultField string `json:"default_field"`
|
||||
StoreDynamic bool `json:"store_dynamic"`
|
||||
IndexDynamic bool `json:"index_dynamic"`
|
||||
DocValuesDynamic bool `json:"docvalues_dynamic"`
|
||||
CustomAnalysis *customAnalysis `json:"analysis,omitempty"`
|
||||
cache *registry.Cache
|
||||
}
|
||||
|
||||
// AddCustomCharFilter defines a custom char filter for use in this mapping
|
||||
func (im *IndexMappingImpl) AddCustomCharFilter(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineCharFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
im.CustomAnalysis.CharFilters[name] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCustomTokenizer defines a custom tokenizer for use in this mapping
|
||||
func (im *IndexMappingImpl) AddCustomTokenizer(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineTokenizer(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
im.CustomAnalysis.Tokenizers[name] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCustomTokenMap defines a custom token map for use in this mapping
|
||||
func (im *IndexMappingImpl) AddCustomTokenMap(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineTokenMap(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
im.CustomAnalysis.TokenMaps[name] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCustomTokenFilter defines a custom token filter for use in this mapping
|
||||
func (im *IndexMappingImpl) AddCustomTokenFilter(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineTokenFilter(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
im.CustomAnalysis.TokenFilters[name] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCustomAnalyzer defines a custom analyzer for use in this mapping. The
|
||||
// config map must have a "type" string entry to resolve the analyzer
|
||||
// constructor. The constructor is invoked with the remaining entries and
|
||||
// returned analyzer is registered in the IndexMapping.
|
||||
//
|
||||
// bleve comes with predefined analyzers, like
|
||||
// github.com/blevesearch/bleve/analysis/analyzer/custom. They are
|
||||
// available only if their package is imported by client code. To achieve this,
|
||||
// use their metadata to fill configuration entries:
|
||||
//
|
||||
// import (
|
||||
// "github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
|
||||
// "github.com/blevesearch/bleve/v2/analysis/char/html"
|
||||
// "github.com/blevesearch/bleve/v2/analysis/token/lowercase"
|
||||
// "github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
||||
// )
|
||||
//
|
||||
// m := bleve.NewIndexMapping()
|
||||
// err := m.AddCustomAnalyzer("html", map[string]interface{}{
|
||||
// "type": custom.Name,
|
||||
// "char_filters": []string{
|
||||
// html.Name,
|
||||
// },
|
||||
// "tokenizer": unicode.Name,
|
||||
// "token_filters": []string{
|
||||
// lowercase.Name,
|
||||
// ...
|
||||
// },
|
||||
// })
|
||||
func (im *IndexMappingImpl) AddCustomAnalyzer(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineAnalyzer(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
im.CustomAnalysis.Analyzers[name] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCustomDateTimeParser defines a custom date time parser for use in this mapping
|
||||
func (im *IndexMappingImpl) AddCustomDateTimeParser(name string, config map[string]interface{}) error {
|
||||
_, err := im.cache.DefineDateTimeParser(name, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
im.CustomAnalysis.DateTimeParsers[name] = config
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewIndexMapping creates a new IndexMapping that will use all the default indexing rules
|
||||
func NewIndexMapping() *IndexMappingImpl {
|
||||
return &IndexMappingImpl{
|
||||
TypeMapping: make(map[string]*DocumentMapping),
|
||||
DefaultMapping: NewDocumentMapping(),
|
||||
TypeField: defaultTypeField,
|
||||
DefaultType: defaultType,
|
||||
DefaultAnalyzer: defaultAnalyzer,
|
||||
DefaultDateTimeParser: defaultDateTimeParser,
|
||||
DefaultField: defaultField,
|
||||
IndexDynamic: IndexDynamic,
|
||||
StoreDynamic: StoreDynamic,
|
||||
DocValuesDynamic: DocValuesDynamic,
|
||||
CustomAnalysis: newCustomAnalysis(),
|
||||
cache: registry.NewCache(),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate will walk the entire structure ensuring the following
|
||||
// explicitly named and default analyzers can be built
|
||||
func (im *IndexMappingImpl) Validate() error {
|
||||
_, err := im.cache.AnalyzerNamed(im.DefaultAnalyzer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = im.cache.DateTimeParserNamed(im.DefaultDateTimeParser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = im.DefaultMapping.Validate(im.cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, docMapping := range im.TypeMapping {
|
||||
err = docMapping.Validate(im.cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddDocumentMapping sets a custom document mapping for the specified type
|
||||
func (im *IndexMappingImpl) AddDocumentMapping(doctype string, dm *DocumentMapping) {
|
||||
im.TypeMapping[doctype] = dm
|
||||
}
|
||||
|
||||
func (im *IndexMappingImpl) mappingForType(docType string) *DocumentMapping {
|
||||
docMapping := im.TypeMapping[docType]
|
||||
if docMapping == nil {
|
||||
docMapping = im.DefaultMapping
|
||||
}
|
||||
return docMapping
|
||||
}
|
||||
|
||||
// UnmarshalJSON offers custom unmarshaling with optional strict validation
|
||||
func (im *IndexMappingImpl) UnmarshalJSON(data []byte) error {
|
||||
|
||||
var tmp map[string]json.RawMessage
|
||||
err := json.Unmarshal(data, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set defaults for fields which might have been omitted
|
||||
im.cache = registry.NewCache()
|
||||
im.CustomAnalysis = newCustomAnalysis()
|
||||
im.TypeField = defaultTypeField
|
||||
im.DefaultType = defaultType
|
||||
im.DefaultAnalyzer = defaultAnalyzer
|
||||
im.DefaultDateTimeParser = defaultDateTimeParser
|
||||
im.DefaultField = defaultField
|
||||
im.DefaultMapping = NewDocumentMapping()
|
||||
im.TypeMapping = make(map[string]*DocumentMapping)
|
||||
im.StoreDynamic = StoreDynamic
|
||||
im.IndexDynamic = IndexDynamic
|
||||
im.DocValuesDynamic = DocValuesDynamic
|
||||
|
||||
var invalidKeys []string
|
||||
for k, v := range tmp {
|
||||
switch k {
|
||||
case "analysis":
|
||||
err := json.Unmarshal(v, &im.CustomAnalysis)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "type_field":
|
||||
err := json.Unmarshal(v, &im.TypeField)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "default_type":
|
||||
err := json.Unmarshal(v, &im.DefaultType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "default_analyzer":
|
||||
err := json.Unmarshal(v, &im.DefaultAnalyzer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "default_datetime_parser":
|
||||
err := json.Unmarshal(v, &im.DefaultDateTimeParser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "default_field":
|
||||
err := json.Unmarshal(v, &im.DefaultField)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "default_mapping":
|
||||
err := json.Unmarshal(v, &im.DefaultMapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "types":
|
||||
err := json.Unmarshal(v, &im.TypeMapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "store_dynamic":
|
||||
err := json.Unmarshal(v, &im.StoreDynamic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "index_dynamic":
|
||||
err := json.Unmarshal(v, &im.IndexDynamic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "docvalues_dynamic":
|
||||
err := json.Unmarshal(v, &im.DocValuesDynamic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
invalidKeys = append(invalidKeys, k)
|
||||
}
|
||||
}
|
||||
|
||||
if MappingJSONStrict && len(invalidKeys) > 0 {
|
||||
return fmt.Errorf("index mapping contains invalid keys: %v", invalidKeys)
|
||||
}
|
||||
|
||||
err = im.CustomAnalysis.registerAll(im)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im *IndexMappingImpl) determineType(data interface{}) string {
|
||||
// first see if the object implements bleveClassifier
|
||||
bleveClassifier, ok := data.(bleveClassifier)
|
||||
if ok {
|
||||
return bleveClassifier.BleveType()
|
||||
}
|
||||
// next see if the object implements Classifier
|
||||
classifier, ok := data.(Classifier)
|
||||
if ok {
|
||||
return classifier.Type()
|
||||
}
|
||||
|
||||
// now see if we can find a type using the mapping
|
||||
typ, ok := mustString(lookupPropertyPath(data, im.TypeField))
|
||||
if ok {
|
||||
return typ
|
||||
}
|
||||
|
||||
return im.DefaultType
|
||||
}
|
||||
|
||||
func (im *IndexMappingImpl) MapDocument(doc *document.Document, data interface{}) error {
|
||||
docType := im.determineType(data)
|
||||
docMapping := im.mappingForType(docType)
|
||||
if docMapping.Enabled {
|
||||
walkContext := im.newWalkContext(doc, docMapping)
|
||||
docMapping.walkDocument(data, []string{}, []uint64{}, walkContext)
|
||||
|
||||
// see if the _all field was disabled
|
||||
allMapping := docMapping.documentMappingForPath("_all")
|
||||
if allMapping == nil || allMapping.Enabled {
|
||||
field := document.NewCompositeFieldWithIndexingOptions("_all", true, []string{}, walkContext.excludedFromAll, index.IndexField|index.IncludeTermVectors)
|
||||
doc.AddField(field)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type walkContext struct {
|
||||
doc *document.Document
|
||||
im *IndexMappingImpl
|
||||
dm *DocumentMapping
|
||||
excludedFromAll []string
|
||||
}
|
||||
|
||||
func (im *IndexMappingImpl) newWalkContext(doc *document.Document, dm *DocumentMapping) *walkContext {
|
||||
return &walkContext{
|
||||
doc: doc,
|
||||
im: im,
|
||||
dm: dm,
|
||||
excludedFromAll: []string{"_id"},
|
||||
}
|
||||
}
|
||||
|
||||
// AnalyzerNameForPath attempts to find the best analyzer to use with only a
|
||||
// field name will walk all the document types, look for field mappings at the
|
||||
// provided path, if one exists and it has an explicit analyzer that is
|
||||
// returned.
|
||||
func (im *IndexMappingImpl) AnalyzerNameForPath(path string) string {
|
||||
// first we look for explicit mapping on the field
|
||||
for _, docMapping := range im.TypeMapping {
|
||||
analyzerName := docMapping.analyzerNameForPath(path)
|
||||
if analyzerName != "" {
|
||||
return analyzerName
|
||||
}
|
||||
}
|
||||
// now try the default mapping
|
||||
pathMapping := im.DefaultMapping.documentMappingForPath(path)
|
||||
if pathMapping != nil {
|
||||
if len(pathMapping.Fields) > 0 {
|
||||
if pathMapping.Fields[0].Analyzer != "" {
|
||||
return pathMapping.Fields[0].Analyzer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// next we will try default analyzers for the path
|
||||
pathDecoded := decodePath(path)
|
||||
for _, docMapping := range im.TypeMapping {
|
||||
rv := docMapping.defaultAnalyzerName(pathDecoded)
|
||||
if rv != "" {
|
||||
return rv
|
||||
}
|
||||
}
|
||||
|
||||
return im.DefaultAnalyzer
|
||||
}
|
||||
|
||||
func (im *IndexMappingImpl) AnalyzerNamed(name string) *analysis.Analyzer {
|
||||
analyzer, err := im.cache.AnalyzerNamed(name)
|
||||
if err != nil {
|
||||
logger.Printf("error using analyzer named: %s", name)
|
||||
return nil
|
||||
}
|
||||
return analyzer
|
||||
}
|
||||
|
||||
func (im *IndexMappingImpl) DateTimeParserNamed(name string) analysis.DateTimeParser {
|
||||
if name == "" {
|
||||
name = im.DefaultDateTimeParser
|
||||
}
|
||||
dateTimeParser, err := im.cache.DateTimeParserNamed(name)
|
||||
if err != nil {
|
||||
logger.Printf("error using datetime parser named: %s", name)
|
||||
return nil
|
||||
}
|
||||
return dateTimeParser
|
||||
}
|
||||
|
||||
func (im *IndexMappingImpl) datetimeParserNameForPath(path string) string {
|
||||
|
||||
// first we look for explicit mapping on the field
|
||||
for _, docMapping := range im.TypeMapping {
|
||||
pathMapping := docMapping.documentMappingForPath(path)
|
||||
if pathMapping != nil {
|
||||
if len(pathMapping.Fields) > 0 {
|
||||
if pathMapping.Fields[0].Analyzer != "" {
|
||||
return pathMapping.Fields[0].Analyzer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return im.DefaultDateTimeParser
|
||||
}
|
||||
|
||||
func (im *IndexMappingImpl) AnalyzeText(analyzerName string, text []byte) (analysis.TokenStream, error) {
|
||||
analyzer, err := im.cache.AnalyzerNamed(analyzerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return analyzer.Analyze(text), nil
|
||||
}
|
||||
|
||||
// FieldAnalyzer returns the name of the analyzer used on a field.
|
||||
func (im *IndexMappingImpl) FieldAnalyzer(field string) string {
|
||||
return im.AnalyzerNameForPath(field)
|
||||
}
|
||||
|
||||
// wrapper to satisfy new interface
|
||||
|
||||
func (im *IndexMappingImpl) DefaultSearchField() string {
|
||||
return im.DefaultField
|
||||
}
|
58
vendor/github.com/blevesearch/bleve/v2/mapping/mapping.go
generated
vendored
Normal file
58
vendor/github.com/blevesearch/bleve/v2/mapping/mapping.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/analysis"
|
||||
"github.com/blevesearch/bleve/v2/document"
|
||||
)
|
||||
|
||||
// A Classifier is an interface describing any object which knows how to
|
||||
// identify its own type. Alternatively, if a struct already has a Type
|
||||
// field or method in conflict, one can use BleveType instead.
|
||||
type Classifier interface {
|
||||
Type() string
|
||||
}
|
||||
|
||||
// A bleveClassifier is an interface describing any object which knows how
|
||||
// to identify its own type. This is introduced as an alternative to the
|
||||
// Classifier interface which often has naming conflicts with existing
|
||||
// structures.
|
||||
type bleveClassifier interface {
|
||||
BleveType() string
|
||||
}
|
||||
|
||||
var logger = log.New(ioutil.Discard, "bleve mapping ", log.LstdFlags)
|
||||
|
||||
// SetLog sets the logger used for logging
|
||||
// by default log messages are sent to ioutil.Discard
|
||||
func SetLog(l *log.Logger) {
|
||||
logger = l
|
||||
}
|
||||
|
||||
type IndexMapping interface {
|
||||
MapDocument(doc *document.Document, data interface{}) error
|
||||
Validate() error
|
||||
|
||||
DateTimeParserNamed(name string) analysis.DateTimeParser
|
||||
|
||||
DefaultSearchField() string
|
||||
|
||||
AnalyzerNameForPath(path string) string
|
||||
AnalyzerNamed(name string) *analysis.Analyzer
|
||||
}
|
92
vendor/github.com/blevesearch/bleve/v2/mapping/reflect.go
generated
vendored
Normal file
92
vendor/github.com/blevesearch/bleve/v2/mapping/reflect.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) 2014 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func lookupPropertyPath(data interface{}, path string) interface{} {
|
||||
pathParts := decodePath(path)
|
||||
|
||||
current := data
|
||||
for _, part := range pathParts {
|
||||
current = lookupPropertyPathPart(current, part)
|
||||
if current == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
func lookupPropertyPathPart(data interface{}, part string) interface{} {
|
||||
val := reflect.ValueOf(data)
|
||||
if !val.IsValid() {
|
||||
return nil
|
||||
}
|
||||
typ := val.Type()
|
||||
switch typ.Kind() {
|
||||
case reflect.Map:
|
||||
// FIXME can add support for other map keys in the future
|
||||
if typ.Key().Kind() == reflect.String {
|
||||
key := reflect.ValueOf(part)
|
||||
entry := val.MapIndex(key)
|
||||
if entry.IsValid() {
|
||||
return entry.Interface()
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
field := val.FieldByName(part)
|
||||
if field.IsValid() && field.CanInterface() {
|
||||
return field.Interface()
|
||||
}
|
||||
case reflect.Ptr:
|
||||
ptrElem := val.Elem()
|
||||
if ptrElem.IsValid() && ptrElem.CanInterface() {
|
||||
return lookupPropertyPathPart(ptrElem.Interface(), part)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const pathSeparator = "."
|
||||
|
||||
func decodePath(path string) []string {
|
||||
return strings.Split(path, pathSeparator)
|
||||
}
|
||||
|
||||
func encodePath(pathElements []string) string {
|
||||
return strings.Join(pathElements, pathSeparator)
|
||||
}
|
||||
|
||||
func mustString(data interface{}) (string, bool) {
|
||||
if data != nil {
|
||||
str, ok := data.(string)
|
||||
if ok {
|
||||
return str, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// parseTagName extracts the field name from a struct tag
|
||||
func parseTagName(tag string) string {
|
||||
if idx := strings.Index(tag, ","); idx != -1 {
|
||||
return tag[:idx]
|
||||
}
|
||||
return tag
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue