Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

field.go 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. // Copyright (c) 2014 Couchbase, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package mapping
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "time"
  19. "github.com/blevesearch/bleve/analysis"
  20. "github.com/blevesearch/bleve/document"
  21. "github.com/blevesearch/bleve/geo"
  22. )
  23. // control the default behavior for dynamic fields (those not explicitly mapped)
  24. var (
  25. IndexDynamic = true
  26. StoreDynamic = true
  27. )
  28. // A FieldMapping describes how a specific item
  29. // should be put into the index.
  30. type FieldMapping struct {
  31. Name string `json:"name,omitempty"`
  32. Type string `json:"type,omitempty"`
  33. // Analyzer specifies the name of the analyzer to use for this field. If
  34. // Analyzer is empty, traverse the DocumentMapping tree toward the root and
  35. // pick the first non-empty DefaultAnalyzer found. If there is none, use
  36. // the IndexMapping.DefaultAnalyzer.
  37. Analyzer string `json:"analyzer,omitempty"`
  38. // Store indicates whether to store field values in the index. Stored
  39. // values can be retrieved from search results using SearchRequest.Fields.
  40. Store bool `json:"store,omitempty"`
  41. Index bool `json:"index,omitempty"`
  42. // IncludeTermVectors, if true, makes terms occurrences to be recorded for
  43. // this field. It includes the term position within the terms sequence and
  44. // the term offsets in the source document field. Term vectors are required
  45. // to perform phrase queries or terms highlighting in source documents.
  46. IncludeTermVectors bool `json:"include_term_vectors,omitempty"`
  47. IncludeInAll bool `json:"include_in_all,omitempty"`
  48. DateFormat string `json:"date_format,omitempty"`
  49. }
  50. // NewTextFieldMapping returns a default field mapping for text
  51. func NewTextFieldMapping() *FieldMapping {
  52. return &FieldMapping{
  53. Type: "text",
  54. Store: true,
  55. Index: true,
  56. IncludeTermVectors: true,
  57. IncludeInAll: true,
  58. }
  59. }
  60. func newTextFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
  61. rv := NewTextFieldMapping()
  62. rv.Store = im.StoreDynamic
  63. rv.Index = im.IndexDynamic
  64. return rv
  65. }
  66. // NewNumericFieldMapping returns a default field mapping for numbers
  67. func NewNumericFieldMapping() *FieldMapping {
  68. return &FieldMapping{
  69. Type: "number",
  70. Store: true,
  71. Index: true,
  72. IncludeInAll: true,
  73. }
  74. }
  75. func newNumericFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
  76. rv := NewNumericFieldMapping()
  77. rv.Store = im.StoreDynamic
  78. rv.Index = im.IndexDynamic
  79. return rv
  80. }
  81. // NewDateTimeFieldMapping returns a default field mapping for dates
  82. func NewDateTimeFieldMapping() *FieldMapping {
  83. return &FieldMapping{
  84. Type: "datetime",
  85. Store: true,
  86. Index: true,
  87. IncludeInAll: true,
  88. }
  89. }
  90. func newDateTimeFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
  91. rv := NewDateTimeFieldMapping()
  92. rv.Store = im.StoreDynamic
  93. rv.Index = im.IndexDynamic
  94. return rv
  95. }
  96. // NewBooleanFieldMapping returns a default field mapping for booleans
  97. func NewBooleanFieldMapping() *FieldMapping {
  98. return &FieldMapping{
  99. Type: "boolean",
  100. Store: true,
  101. Index: true,
  102. IncludeInAll: true,
  103. }
  104. }
  105. func newBooleanFieldMappingDynamic(im *IndexMappingImpl) *FieldMapping {
  106. rv := NewBooleanFieldMapping()
  107. rv.Store = im.StoreDynamic
  108. rv.Index = im.IndexDynamic
  109. return rv
  110. }
  111. // NewGeoPointFieldMapping returns a default field mapping for geo points
  112. func NewGeoPointFieldMapping() *FieldMapping {
  113. return &FieldMapping{
  114. Type: "geopoint",
  115. Store: true,
  116. Index: true,
  117. IncludeInAll: true,
  118. }
  119. }
  120. // Options returns the indexing options for this field.
  121. func (fm *FieldMapping) Options() document.IndexingOptions {
  122. var rv document.IndexingOptions
  123. if fm.Store {
  124. rv |= document.StoreField
  125. }
  126. if fm.Index {
  127. rv |= document.IndexField
  128. }
  129. if fm.IncludeTermVectors {
  130. rv |= document.IncludeTermVectors
  131. }
  132. return rv
  133. }
  134. func (fm *FieldMapping) processString(propertyValueString string, pathString string, path []string, indexes []uint64, context *walkContext) {
  135. fieldName := getFieldName(pathString, path, fm)
  136. options := fm.Options()
  137. if fm.Type == "text" {
  138. analyzer := fm.analyzerForField(path, context)
  139. field := document.NewTextFieldCustom(fieldName, indexes, []byte(propertyValueString), options, analyzer)
  140. context.doc.AddField(field)
  141. if !fm.IncludeInAll {
  142. context.excludedFromAll = append(context.excludedFromAll, fieldName)
  143. }
  144. } else if fm.Type == "datetime" {
  145. dateTimeFormat := context.im.DefaultDateTimeParser
  146. if fm.DateFormat != "" {
  147. dateTimeFormat = fm.DateFormat
  148. }
  149. dateTimeParser := context.im.DateTimeParserNamed(dateTimeFormat)
  150. if dateTimeParser != nil {
  151. parsedDateTime, err := dateTimeParser.ParseDateTime(propertyValueString)
  152. if err == nil {
  153. fm.processTime(parsedDateTime, pathString, path, indexes, context)
  154. }
  155. }
  156. }
  157. }
  158. func (fm *FieldMapping) processFloat64(propertyValFloat float64, pathString string, path []string, indexes []uint64, context *walkContext) {
  159. fieldName := getFieldName(pathString, path, fm)
  160. if fm.Type == "number" {
  161. options := fm.Options()
  162. field := document.NewNumericFieldWithIndexingOptions(fieldName, indexes, propertyValFloat, options)
  163. context.doc.AddField(field)
  164. if !fm.IncludeInAll {
  165. context.excludedFromAll = append(context.excludedFromAll, fieldName)
  166. }
  167. }
  168. }
  169. func (fm *FieldMapping) processTime(propertyValueTime time.Time, pathString string, path []string, indexes []uint64, context *walkContext) {
  170. fieldName := getFieldName(pathString, path, fm)
  171. if fm.Type == "datetime" {
  172. options := fm.Options()
  173. field, err := document.NewDateTimeFieldWithIndexingOptions(fieldName, indexes, propertyValueTime, options)
  174. if err == nil {
  175. context.doc.AddField(field)
  176. } else {
  177. logger.Printf("could not build date %v", err)
  178. }
  179. if !fm.IncludeInAll {
  180. context.excludedFromAll = append(context.excludedFromAll, fieldName)
  181. }
  182. }
  183. }
  184. func (fm *FieldMapping) processBoolean(propertyValueBool bool, pathString string, path []string, indexes []uint64, context *walkContext) {
  185. fieldName := getFieldName(pathString, path, fm)
  186. if fm.Type == "boolean" {
  187. options := fm.Options()
  188. field := document.NewBooleanFieldWithIndexingOptions(fieldName, indexes, propertyValueBool, options)
  189. context.doc.AddField(field)
  190. if !fm.IncludeInAll {
  191. context.excludedFromAll = append(context.excludedFromAll, fieldName)
  192. }
  193. }
  194. }
  195. func (fm *FieldMapping) processGeoPoint(propertyMightBeGeoPoint interface{}, pathString string, path []string, indexes []uint64, context *walkContext) {
  196. lon, lat, found := geo.ExtractGeoPoint(propertyMightBeGeoPoint)
  197. if found {
  198. fieldName := getFieldName(pathString, path, fm)
  199. options := fm.Options()
  200. field := document.NewGeoPointFieldWithIndexingOptions(fieldName, indexes, lon, lat, options)
  201. context.doc.AddField(field)
  202. if !fm.IncludeInAll {
  203. context.excludedFromAll = append(context.excludedFromAll, fieldName)
  204. }
  205. }
  206. }
  207. func (fm *FieldMapping) analyzerForField(path []string, context *walkContext) *analysis.Analyzer {
  208. analyzerName := fm.Analyzer
  209. if analyzerName == "" {
  210. analyzerName = context.dm.defaultAnalyzerName(path)
  211. if analyzerName == "" {
  212. analyzerName = context.im.DefaultAnalyzer
  213. }
  214. }
  215. return context.im.AnalyzerNamed(analyzerName)
  216. }
  217. func getFieldName(pathString string, path []string, fieldMapping *FieldMapping) string {
  218. fieldName := pathString
  219. if fieldMapping.Name != "" {
  220. parentName := ""
  221. if len(path) > 1 {
  222. parentName = encodePath(path[:len(path)-1]) + pathSeparator
  223. }
  224. fieldName = parentName + fieldMapping.Name
  225. }
  226. return fieldName
  227. }
  228. // UnmarshalJSON offers custom unmarshaling with optional strict validation
  229. func (fm *FieldMapping) UnmarshalJSON(data []byte) error {
  230. var tmp map[string]json.RawMessage
  231. err := json.Unmarshal(data, &tmp)
  232. if err != nil {
  233. return err
  234. }
  235. var invalidKeys []string
  236. for k, v := range tmp {
  237. switch k {
  238. case "name":
  239. err := json.Unmarshal(v, &fm.Name)
  240. if err != nil {
  241. return err
  242. }
  243. case "type":
  244. err := json.Unmarshal(v, &fm.Type)
  245. if err != nil {
  246. return err
  247. }
  248. case "analyzer":
  249. err := json.Unmarshal(v, &fm.Analyzer)
  250. if err != nil {
  251. return err
  252. }
  253. case "store":
  254. err := json.Unmarshal(v, &fm.Store)
  255. if err != nil {
  256. return err
  257. }
  258. case "index":
  259. err := json.Unmarshal(v, &fm.Index)
  260. if err != nil {
  261. return err
  262. }
  263. case "include_term_vectors":
  264. err := json.Unmarshal(v, &fm.IncludeTermVectors)
  265. if err != nil {
  266. return err
  267. }
  268. case "include_in_all":
  269. err := json.Unmarshal(v, &fm.IncludeInAll)
  270. if err != nil {
  271. return err
  272. }
  273. case "date_format":
  274. err := json.Unmarshal(v, &fm.DateFormat)
  275. if err != nil {
  276. return err
  277. }
  278. default:
  279. invalidKeys = append(invalidKeys, k)
  280. }
  281. }
  282. if MappingJSONStrict && len(invalidKeys) > 0 {
  283. return fmt.Errorf("field mapping contains invalid keys: %v", invalidKeys)
  284. }
  285. return nil
  286. }