2014-07-30 18:30:38 +02:00
|
|
|
// 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.
|
2014-08-29 20:18:36 +02:00
|
|
|
|
2014-07-30 18:30:38 +02:00
|
|
|
package bleve
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
|
2014-08-28 21:38:57 +02:00
|
|
|
"github.com/blevesearch/bleve/registry"
|
2014-07-30 18:30:38 +02:00
|
|
|
)
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// 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 index 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.
|
2014-07-30 18:30:38 +02:00
|
|
|
type DocumentMapping struct {
|
2014-08-14 03:14:47 +02:00
|
|
|
Enabled bool `json:"enabled"`
|
|
|
|
Dynamic bool `json:"dynamic"`
|
2014-08-25 15:08:27 +02:00
|
|
|
Properties map[string]*DocumentMapping `json:"properties,omitempty"`
|
|
|
|
Fields []*FieldMapping `json:"fields,omitempty"`
|
2014-08-14 03:14:47 +02:00
|
|
|
DefaultAnalyzer string `json:"default_analyzer"`
|
|
|
|
}
|
|
|
|
|
2014-08-30 06:13:46 +02:00
|
|
|
func (dm *DocumentMapping) validate(cache *registry.Cache) error {
|
2014-08-14 03:14:47 +02:00
|
|
|
var err error
|
|
|
|
if dm.DefaultAnalyzer != "" {
|
|
|
|
_, err := cache.AnalyzerNamed(dm.DefaultAnalyzer)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, property := range dm.Properties {
|
2014-08-30 06:13:46 +02:00
|
|
|
err = property.validate(cache)
|
2014-08-14 03:14:47 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, field := range dm.Fields {
|
|
|
|
if field.Analyzer != nil {
|
|
|
|
_, err = cache.AnalyzerNamed(*field.Analyzer)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if field.DateFormat != nil {
|
|
|
|
_, err = cache.DateTimeParserNamed(*field.DateFormat)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if field.Type != nil {
|
|
|
|
switch *field.Type {
|
|
|
|
case "text", "datetime", "number":
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unknown field type: '%s'", *field.Type)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
|
2014-08-30 06:13:46 +02:00
|
|
|
func (dm *DocumentMapping) documentMappingForPath(path string) *DocumentMapping {
|
2014-07-30 18:30:38 +02:00
|
|
|
pathElements := decodePath(path)
|
|
|
|
current := dm
|
|
|
|
for _, pathElement := range pathElements {
|
|
|
|
var ok bool
|
|
|
|
current, ok = current.Properties[pathElement]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return current
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// NewDocumentMapping returns a new document mapping
|
|
|
|
// with all the default values.
|
2014-07-30 18:30:38 +02:00
|
|
|
func NewDocumentMapping() *DocumentMapping {
|
|
|
|
return &DocumentMapping{
|
2014-08-14 03:14:47 +02:00
|
|
|
Enabled: true,
|
|
|
|
Dynamic: true,
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// NewDocumentStaticMapping returns a new document
|
|
|
|
// mapping that will not automatically index parts
|
|
|
|
// of a document without an explicit mapping.
|
2014-07-30 18:30:38 +02:00
|
|
|
func NewDocumentStaticMapping() *DocumentMapping {
|
|
|
|
return &DocumentMapping{
|
2014-08-14 03:14:47 +02:00
|
|
|
Enabled: true,
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// NewDocumentDisabledMapping returns a new document
|
|
|
|
// mapping that will not perform any indexing.
|
2014-07-30 18:30:38 +02:00
|
|
|
func NewDocumentDisabledMapping() *DocumentMapping {
|
2014-08-14 03:14:47 +02:00
|
|
|
return &DocumentMapping{}
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// Adds the provided DocumentMapping as a sub-mapping
|
|
|
|
// for the specified named subsection.
|
2014-07-30 18:30:38 +02:00
|
|
|
func (dm *DocumentMapping) AddSubDocumentMapping(property string, sdm *DocumentMapping) *DocumentMapping {
|
|
|
|
if dm.Properties == nil {
|
|
|
|
dm.Properties = make(map[string]*DocumentMapping)
|
|
|
|
}
|
|
|
|
dm.Properties[property] = sdm
|
|
|
|
return dm
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// Adds the provided FieldMapping for this section
|
|
|
|
// of the document.
|
2014-07-30 18:30:38 +02:00
|
|
|
func (dm *DocumentMapping) AddFieldMapping(fm *FieldMapping) *DocumentMapping {
|
|
|
|
if dm.Fields == nil {
|
|
|
|
dm.Fields = make([]*FieldMapping, 0)
|
|
|
|
}
|
|
|
|
dm.Fields = append(dm.Fields, fm)
|
|
|
|
return dm
|
|
|
|
}
|
|
|
|
|
2014-08-31 16:55:22 +02:00
|
|
|
// UnmarshalJSON deserializes a JSON representation
|
|
|
|
// of the DocumentMapping.
|
2014-07-30 18:30:38 +02:00
|
|
|
func (dm *DocumentMapping) UnmarshalJSON(data []byte) error {
|
|
|
|
var tmp struct {
|
|
|
|
Enabled *bool `json:"enabled"`
|
|
|
|
Dynamic *bool `json:"dynamic"`
|
|
|
|
Properties map[string]*DocumentMapping `json:"properties"`
|
|
|
|
Fields []*FieldMapping `json:"fields"`
|
2014-08-14 03:14:47 +02:00
|
|
|
DefaultAnalyzer string `json:"default_analyzer"`
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
err := json.Unmarshal(data, &tmp)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-08-14 03:14:47 +02:00
|
|
|
|
|
|
|
dm.Enabled = true
|
2014-07-30 18:30:38 +02:00
|
|
|
if tmp.Enabled != nil {
|
2014-08-14 03:14:47 +02:00
|
|
|
dm.Enabled = *tmp.Enabled
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
2014-08-14 03:14:47 +02:00
|
|
|
|
|
|
|
dm.Dynamic = true
|
2014-07-30 18:30:38 +02:00
|
|
|
if tmp.Dynamic != nil {
|
2014-08-14 03:14:47 +02:00
|
|
|
dm.Dynamic = *tmp.Dynamic
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
2014-08-14 03:14:47 +02:00
|
|
|
|
|
|
|
dm.DefaultAnalyzer = tmp.DefaultAnalyzer
|
|
|
|
|
2014-07-30 18:30:38 +02:00
|
|
|
if tmp.Properties != nil {
|
|
|
|
dm.Properties = make(map[string]*DocumentMapping, len(tmp.Properties))
|
|
|
|
}
|
|
|
|
for propName, propMapping := range tmp.Properties {
|
|
|
|
dm.Properties[propName] = propMapping
|
|
|
|
}
|
|
|
|
if tmp.Fields != nil {
|
|
|
|
dm.Fields = make([]*FieldMapping, len(tmp.Fields))
|
|
|
|
}
|
|
|
|
for i, field := range tmp.Fields {
|
|
|
|
dm.Fields[i] = field
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-08-14 03:14:47 +02:00
|
|
|
func (dm *DocumentMapping) defaultAnalyzerName(path []string) string {
|
|
|
|
rv := ""
|
2014-07-30 18:30:38 +02:00
|
|
|
current := dm
|
|
|
|
for _, pathElement := range path {
|
|
|
|
var ok bool
|
|
|
|
current, ok = current.Properties[pathElement]
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
2014-08-14 03:14:47 +02:00
|
|
|
if current.DefaultAnalyzer != "" {
|
|
|
|
rv = current.DefaultAnalyzer
|
2014-07-30 18:30:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return rv
|
|
|
|
}
|