You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

451 lines
12 KiB

  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 bleve
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "time"
  19. "github.com/blevesearch/bleve/analysis"
  20. "github.com/blevesearch/bleve/search"
  21. "github.com/blevesearch/bleve/search/query"
  22. )
  23. type numericRange struct {
  24. Name string `json:"name,omitempty"`
  25. Min *float64 `json:"min,omitempty"`
  26. Max *float64 `json:"max,omitempty"`
  27. }
  28. type dateTimeRange struct {
  29. Name string `json:"name,omitempty"`
  30. Start time.Time `json:"start,omitempty"`
  31. End time.Time `json:"end,omitempty"`
  32. startString *string
  33. endString *string
  34. }
  35. func (dr *dateTimeRange) ParseDates(dateTimeParser analysis.DateTimeParser) (start, end time.Time) {
  36. start = dr.Start
  37. if dr.Start.IsZero() && dr.startString != nil {
  38. s, err := dateTimeParser.ParseDateTime(*dr.startString)
  39. if err == nil {
  40. start = s
  41. }
  42. }
  43. end = dr.End
  44. if dr.End.IsZero() && dr.endString != nil {
  45. e, err := dateTimeParser.ParseDateTime(*dr.endString)
  46. if err == nil {
  47. end = e
  48. }
  49. }
  50. return start, end
  51. }
  52. func (dr *dateTimeRange) UnmarshalJSON(input []byte) error {
  53. var temp struct {
  54. Name string `json:"name,omitempty"`
  55. Start *string `json:"start,omitempty"`
  56. End *string `json:"end,omitempty"`
  57. }
  58. err := json.Unmarshal(input, &temp)
  59. if err != nil {
  60. return err
  61. }
  62. dr.Name = temp.Name
  63. if temp.Start != nil {
  64. dr.startString = temp.Start
  65. }
  66. if temp.End != nil {
  67. dr.endString = temp.End
  68. }
  69. return nil
  70. }
  71. func (dr *dateTimeRange) MarshalJSON() ([]byte, error) {
  72. rv := map[string]interface{}{
  73. "name": dr.Name,
  74. "start": dr.Start,
  75. "end": dr.End,
  76. }
  77. if dr.Start.IsZero() && dr.startString != nil {
  78. rv["start"] = dr.startString
  79. }
  80. if dr.End.IsZero() && dr.endString != nil {
  81. rv["end"] = dr.endString
  82. }
  83. return json.Marshal(rv)
  84. }
  85. // A FacetRequest describes a facet or aggregation
  86. // of the result document set you would like to be
  87. // built.
  88. type FacetRequest struct {
  89. Size int `json:"size"`
  90. Field string `json:"field"`
  91. NumericRanges []*numericRange `json:"numeric_ranges,omitempty"`
  92. DateTimeRanges []*dateTimeRange `json:"date_ranges,omitempty"`
  93. }
  94. func (fr *FacetRequest) Validate() error {
  95. if len(fr.NumericRanges) > 0 && len(fr.DateTimeRanges) > 0 {
  96. return fmt.Errorf("facet can only conain numeric ranges or date ranges, not both")
  97. }
  98. nrNames := map[string]interface{}{}
  99. for _, nr := range fr.NumericRanges {
  100. if _, ok := nrNames[nr.Name]; ok {
  101. return fmt.Errorf("numeric ranges contains duplicate name '%s'", nr.Name)
  102. }
  103. nrNames[nr.Name] = struct{}{}
  104. }
  105. drNames := map[string]interface{}{}
  106. for _, dr := range fr.DateTimeRanges {
  107. if _, ok := drNames[dr.Name]; ok {
  108. return fmt.Errorf("date ranges contains duplicate name '%s'", dr.Name)
  109. }
  110. drNames[dr.Name] = struct{}{}
  111. }
  112. return nil
  113. }
  114. // NewFacetRequest creates a facet on the specified
  115. // field that limits the number of entries to the
  116. // specified size.
  117. func NewFacetRequest(field string, size int) *FacetRequest {
  118. return &FacetRequest{
  119. Field: field,
  120. Size: size,
  121. }
  122. }
  123. // AddDateTimeRange adds a bucket to a field
  124. // containing date values. Documents with a
  125. // date value falling into this range are tabulated
  126. // as part of this bucket/range.
  127. func (fr *FacetRequest) AddDateTimeRange(name string, start, end time.Time) {
  128. if fr.DateTimeRanges == nil {
  129. fr.DateTimeRanges = make([]*dateTimeRange, 0, 1)
  130. }
  131. fr.DateTimeRanges = append(fr.DateTimeRanges, &dateTimeRange{Name: name, Start: start, End: end})
  132. }
  133. // AddNumericRange adds a bucket to a field
  134. // containing numeric values. Documents with a
  135. // numeric value falling into this range are
  136. // tabulated as part of this bucket/range.
  137. func (fr *FacetRequest) AddNumericRange(name string, min, max *float64) {
  138. if fr.NumericRanges == nil {
  139. fr.NumericRanges = make([]*numericRange, 0, 1)
  140. }
  141. fr.NumericRanges = append(fr.NumericRanges, &numericRange{Name: name, Min: min, Max: max})
  142. }
  143. // FacetsRequest groups together all the
  144. // FacetRequest objects for a single query.
  145. type FacetsRequest map[string]*FacetRequest
  146. func (fr FacetsRequest) Validate() error {
  147. for _, v := range fr {
  148. err := v.Validate()
  149. if err != nil {
  150. return err
  151. }
  152. }
  153. return nil
  154. }
  155. // HighlightRequest describes how field matches
  156. // should be highlighted.
  157. type HighlightRequest struct {
  158. Style *string `json:"style"`
  159. Fields []string `json:"fields"`
  160. }
  161. // NewHighlight creates a default
  162. // HighlightRequest.
  163. func NewHighlight() *HighlightRequest {
  164. return &HighlightRequest{}
  165. }
  166. // NewHighlightWithStyle creates a HighlightRequest
  167. // with an alternate style.
  168. func NewHighlightWithStyle(style string) *HighlightRequest {
  169. return &HighlightRequest{
  170. Style: &style,
  171. }
  172. }
  173. func (h *HighlightRequest) AddField(field string) {
  174. if h.Fields == nil {
  175. h.Fields = make([]string, 0, 1)
  176. }
  177. h.Fields = append(h.Fields, field)
  178. }
  179. // A SearchRequest describes all the parameters
  180. // needed to search the index.
  181. // Query is required.
  182. // Size/From describe how much and which part of the
  183. // result set to return.
  184. // Highlight describes optional search result
  185. // highlighting.
  186. // Fields describes a list of field values which
  187. // should be retrieved for result documents, provided they
  188. // were stored while indexing.
  189. // Facets describe the set of facets to be computed.
  190. // Explain triggers inclusion of additional search
  191. // result score explanations.
  192. // Sort describes the desired order for the results to be returned.
  193. //
  194. // A special field named "*" can be used to return all fields.
  195. type SearchRequest struct {
  196. Query query.Query `json:"query"`
  197. Size int `json:"size"`
  198. From int `json:"from"`
  199. Highlight *HighlightRequest `json:"highlight"`
  200. Fields []string `json:"fields"`
  201. Facets FacetsRequest `json:"facets"`
  202. Explain bool `json:"explain"`
  203. Sort search.SortOrder `json:"sort"`
  204. }
  205. func (r *SearchRequest) Validate() error {
  206. if srq, ok := r.Query.(query.ValidatableQuery); ok {
  207. err := srq.Validate()
  208. if err != nil {
  209. return err
  210. }
  211. }
  212. return r.Facets.Validate()
  213. }
  214. // AddFacet adds a FacetRequest to this SearchRequest
  215. func (r *SearchRequest) AddFacet(facetName string, f *FacetRequest) {
  216. if r.Facets == nil {
  217. r.Facets = make(FacetsRequest, 1)
  218. }
  219. r.Facets[facetName] = f
  220. }
  221. // SortBy changes the request to use the requested sort order
  222. // this form uses the simplified syntax with an array of strings
  223. // each string can either be a field name
  224. // or the magic value _id and _score which refer to the doc id and search score
  225. // any of these values can optionally be prefixed with - to reverse the order
  226. func (r *SearchRequest) SortBy(order []string) {
  227. so := search.ParseSortOrderStrings(order)
  228. r.Sort = so
  229. }
  230. // SortByCustom changes the request to use the requested sort order
  231. func (r *SearchRequest) SortByCustom(order search.SortOrder) {
  232. r.Sort = order
  233. }
  234. // UnmarshalJSON deserializes a JSON representation of
  235. // a SearchRequest
  236. func (r *SearchRequest) UnmarshalJSON(input []byte) error {
  237. var temp struct {
  238. Q json.RawMessage `json:"query"`
  239. Size *int `json:"size"`
  240. From int `json:"from"`
  241. Highlight *HighlightRequest `json:"highlight"`
  242. Fields []string `json:"fields"`
  243. Facets FacetsRequest `json:"facets"`
  244. Explain bool `json:"explain"`
  245. Sort []json.RawMessage `json:"sort"`
  246. }
  247. err := json.Unmarshal(input, &temp)
  248. if err != nil {
  249. return err
  250. }
  251. if temp.Size == nil {
  252. r.Size = 10
  253. } else {
  254. r.Size = *temp.Size
  255. }
  256. if temp.Sort == nil {
  257. r.Sort = search.SortOrder{&search.SortScore{Desc: true}}
  258. } else {
  259. r.Sort, err = search.ParseSortOrderJSON(temp.Sort)
  260. if err != nil {
  261. return err
  262. }
  263. }
  264. r.From = temp.From
  265. r.Explain = temp.Explain
  266. r.Highlight = temp.Highlight
  267. r.Fields = temp.Fields
  268. r.Facets = temp.Facets
  269. r.Query, err = query.ParseQuery(temp.Q)
  270. if err != nil {
  271. return err
  272. }
  273. if r.Size < 0 {
  274. r.Size = 10
  275. }
  276. if r.From < 0 {
  277. r.From = 0
  278. }
  279. return nil
  280. }
  281. // NewSearchRequest creates a new SearchRequest
  282. // for the Query, using default values for all
  283. // other search parameters.
  284. func NewSearchRequest(q query.Query) *SearchRequest {
  285. return NewSearchRequestOptions(q, 10, 0, false)
  286. }
  287. // NewSearchRequestOptions creates a new SearchRequest
  288. // for the Query, with the requested size, from
  289. // and explanation search parameters.
  290. // By default results are ordered by score, descending.
  291. func NewSearchRequestOptions(q query.Query, size, from int, explain bool) *SearchRequest {
  292. return &SearchRequest{
  293. Query: q,
  294. Size: size,
  295. From: from,
  296. Explain: explain,
  297. Sort: search.SortOrder{&search.SortScore{Desc: true}},
  298. }
  299. }
  300. // IndexErrMap tracks errors with the name of the index where it occurred
  301. type IndexErrMap map[string]error
  302. // MarshalJSON seralizes the error into a string for JSON consumption
  303. func (iem IndexErrMap) MarshalJSON() ([]byte, error) {
  304. tmp := make(map[string]string, len(iem))
  305. for k, v := range iem {
  306. tmp[k] = v.Error()
  307. }
  308. return json.Marshal(tmp)
  309. }
  310. func (iem IndexErrMap) UnmarshalJSON(data []byte) error {
  311. var tmp map[string]string
  312. err := json.Unmarshal(data, &tmp)
  313. if err != nil {
  314. return err
  315. }
  316. for k, v := range tmp {
  317. iem[k] = fmt.Errorf("%s", v)
  318. }
  319. return nil
  320. }
  321. // SearchStatus is a secion in the SearchResult reporting how many
  322. // underlying indexes were queried, how many were successful/failed
  323. // and a map of any errors that were encountered
  324. type SearchStatus struct {
  325. Total int `json:"total"`
  326. Failed int `json:"failed"`
  327. Successful int `json:"successful"`
  328. Errors IndexErrMap `json:"errors,omitempty"`
  329. }
  330. // Merge will merge together multiple SearchStatuses during a MultiSearch
  331. func (ss *SearchStatus) Merge(other *SearchStatus) {
  332. ss.Total += other.Total
  333. ss.Failed += other.Failed
  334. ss.Successful += other.Successful
  335. if len(other.Errors) > 0 {
  336. if ss.Errors == nil {
  337. ss.Errors = make(map[string]error)
  338. }
  339. for otherIndex, otherError := range other.Errors {
  340. ss.Errors[otherIndex] = otherError
  341. }
  342. }
  343. }
  344. // A SearchResult describes the results of executing
  345. // a SearchRequest.
  346. type SearchResult struct {
  347. Status *SearchStatus `json:"status"`
  348. Request *SearchRequest `json:"request"`
  349. Hits search.DocumentMatchCollection `json:"hits"`
  350. Total uint64 `json:"total_hits"`
  351. MaxScore float64 `json:"max_score"`
  352. Took time.Duration `json:"took"`
  353. Facets search.FacetResults `json:"facets"`
  354. }
  355. func (sr *SearchResult) String() string {
  356. rv := ""
  357. if sr.Total > 0 {
  358. if sr.Request.Size > 0 {
  359. rv = fmt.Sprintf("%d matches, showing %d through %d, took %s\n", sr.Total, sr.Request.From+1, sr.Request.From+len(sr.Hits), sr.Took)
  360. for i, hit := range sr.Hits {
  361. rv += fmt.Sprintf("%5d. %s (%f)\n", i+sr.Request.From+1, hit.ID, hit.Score)
  362. for fragmentField, fragments := range hit.Fragments {
  363. rv += fmt.Sprintf("\t%s\n", fragmentField)
  364. for _, fragment := range fragments {
  365. rv += fmt.Sprintf("\t\t%s\n", fragment)
  366. }
  367. }
  368. for otherFieldName, otherFieldValue := range hit.Fields {
  369. if _, ok := hit.Fragments[otherFieldName]; !ok {
  370. rv += fmt.Sprintf("\t%s\n", otherFieldName)
  371. rv += fmt.Sprintf("\t\t%v\n", otherFieldValue)
  372. }
  373. }
  374. }
  375. } else {
  376. rv = fmt.Sprintf("%d matches, took %s\n", sr.Total, sr.Took)
  377. }
  378. } else {
  379. rv = "No matches"
  380. }
  381. if len(sr.Facets) > 0 {
  382. rv += fmt.Sprintf("Facets:\n")
  383. for fn, f := range sr.Facets {
  384. rv += fmt.Sprintf("%s(%d)\n", fn, f.Total)
  385. for _, t := range f.Terms {
  386. rv += fmt.Sprintf("\t%s(%d)\n", t.Term, t.Count)
  387. }
  388. if f.Other != 0 {
  389. rv += fmt.Sprintf("\tOther(%d)\n", f.Other)
  390. }
  391. }
  392. }
  393. return rv
  394. }
  395. // Merge will merge together multiple SearchResults during a MultiSearch
  396. func (sr *SearchResult) Merge(other *SearchResult) {
  397. sr.Status.Merge(other.Status)
  398. sr.Hits = append(sr.Hits, other.Hits...)
  399. sr.Total += other.Total
  400. if other.MaxScore > sr.MaxScore {
  401. sr.MaxScore = other.MaxScore
  402. }
  403. sr.Facets.Merge(other.Facets)
  404. }