search_source.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. // Copyright 2012-present Oliver Eilhard. All rights reserved.
  2. // Use of this source code is governed by a MIT-license.
  3. // See http://olivere.mit-license.org/license.txt for details.
  4. package elastic
  5. import (
  6. "fmt"
  7. )
  8. // SearchSource enables users to build the search source.
  9. // It resembles the SearchSourceBuilder in Elasticsearch.
  10. type SearchSource struct {
  11. query Query
  12. postQuery Query
  13. sliceQuery Query
  14. from int
  15. size int
  16. explain *bool
  17. version *bool
  18. sorters []Sorter
  19. trackScores bool
  20. searchAfterSortValues []interface{}
  21. minScore *float64
  22. timeout string
  23. terminateAfter *int
  24. storedFieldNames []string
  25. docvalueFields []string
  26. scriptFields []*ScriptField
  27. fetchSourceContext *FetchSourceContext
  28. aggregations map[string]Aggregation
  29. highlight *Highlight
  30. globalSuggestText string
  31. suggesters []Suggester
  32. rescores []*Rescore
  33. defaultRescoreWindowSize *int
  34. indexBoosts map[string]float64
  35. stats []string
  36. innerHits map[string]*InnerHit
  37. collapse *CollapseBuilder
  38. profile bool
  39. }
  40. // NewSearchSource initializes a new SearchSource.
  41. func NewSearchSource() *SearchSource {
  42. return &SearchSource{
  43. from: -1,
  44. size: -1,
  45. trackScores: false,
  46. aggregations: make(map[string]Aggregation),
  47. indexBoosts: make(map[string]float64),
  48. innerHits: make(map[string]*InnerHit),
  49. }
  50. }
  51. // Query sets the query to use with this search source.
  52. func (s *SearchSource) Query(query Query) *SearchSource {
  53. s.query = query
  54. return s
  55. }
  56. // Profile specifies that this search source should activate the
  57. // Profile API for queries made on it.
  58. func (s *SearchSource) Profile(profile bool) *SearchSource {
  59. s.profile = profile
  60. return s
  61. }
  62. // PostFilter will be executed after the query has been executed and
  63. // only affects the search hits, not the aggregations.
  64. // This filter is always executed as the last filtering mechanism.
  65. func (s *SearchSource) PostFilter(postFilter Query) *SearchSource {
  66. s.postQuery = postFilter
  67. return s
  68. }
  69. // Slice allows partitioning the documents in multiple slices.
  70. // It is e.g. used to slice a scroll operation, supported in
  71. // Elasticsearch 5.0 or later.
  72. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.2/search-request-scroll.html#sliced-scroll
  73. // for details.
  74. func (s *SearchSource) Slice(sliceQuery Query) *SearchSource {
  75. s.sliceQuery = sliceQuery
  76. return s
  77. }
  78. // From index to start the search from. Defaults to 0.
  79. func (s *SearchSource) From(from int) *SearchSource {
  80. s.from = from
  81. return s
  82. }
  83. // Size is the number of search hits to return. Defaults to 10.
  84. func (s *SearchSource) Size(size int) *SearchSource {
  85. s.size = size
  86. return s
  87. }
  88. // MinScore sets the minimum score below which docs will be filtered out.
  89. func (s *SearchSource) MinScore(minScore float64) *SearchSource {
  90. s.minScore = &minScore
  91. return s
  92. }
  93. // Explain indicates whether each search hit should be returned with
  94. // an explanation of the hit (ranking).
  95. func (s *SearchSource) Explain(explain bool) *SearchSource {
  96. s.explain = &explain
  97. return s
  98. }
  99. // Version indicates whether each search hit should be returned with
  100. // a version associated to it.
  101. func (s *SearchSource) Version(version bool) *SearchSource {
  102. s.version = &version
  103. return s
  104. }
  105. // Timeout controls how long a search is allowed to take, e.g. "1s" or "500ms".
  106. func (s *SearchSource) Timeout(timeout string) *SearchSource {
  107. s.timeout = timeout
  108. return s
  109. }
  110. // TimeoutInMillis controls how many milliseconds a search is allowed
  111. // to take before it is canceled.
  112. func (s *SearchSource) TimeoutInMillis(timeoutInMillis int) *SearchSource {
  113. s.timeout = fmt.Sprintf("%dms", timeoutInMillis)
  114. return s
  115. }
  116. // TerminateAfter specifies the maximum number of documents to collect for
  117. // each shard, upon reaching which the query execution will terminate early.
  118. func (s *SearchSource) TerminateAfter(terminateAfter int) *SearchSource {
  119. s.terminateAfter = &terminateAfter
  120. return s
  121. }
  122. // Sort adds a sort order.
  123. func (s *SearchSource) Sort(field string, ascending bool) *SearchSource {
  124. s.sorters = append(s.sorters, SortInfo{Field: field, Ascending: ascending})
  125. return s
  126. }
  127. // SortWithInfo adds a sort order.
  128. func (s *SearchSource) SortWithInfo(info SortInfo) *SearchSource {
  129. s.sorters = append(s.sorters, info)
  130. return s
  131. }
  132. // SortBy adds a sort order.
  133. func (s *SearchSource) SortBy(sorter ...Sorter) *SearchSource {
  134. s.sorters = append(s.sorters, sorter...)
  135. return s
  136. }
  137. func (s *SearchSource) hasSort() bool {
  138. return len(s.sorters) > 0
  139. }
  140. // TrackScores is applied when sorting and controls if scores will be
  141. // tracked as well. Defaults to false.
  142. func (s *SearchSource) TrackScores(trackScores bool) *SearchSource {
  143. s.trackScores = trackScores
  144. return s
  145. }
  146. // SearchAfter allows a different form of pagination by using a live cursor,
  147. // using the results of the previous page to help the retrieval of the next.
  148. //
  149. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.2/search-request-search-after.html
  150. func (s *SearchSource) SearchAfter(sortValues ...interface{}) *SearchSource {
  151. s.searchAfterSortValues = append(s.searchAfterSortValues, sortValues...)
  152. return s
  153. }
  154. // Aggregation adds an aggreation to perform as part of the search.
  155. func (s *SearchSource) Aggregation(name string, aggregation Aggregation) *SearchSource {
  156. s.aggregations[name] = aggregation
  157. return s
  158. }
  159. // DefaultRescoreWindowSize sets the rescore window size for rescores
  160. // that don't specify their window.
  161. func (s *SearchSource) DefaultRescoreWindowSize(defaultRescoreWindowSize int) *SearchSource {
  162. s.defaultRescoreWindowSize = &defaultRescoreWindowSize
  163. return s
  164. }
  165. // Highlight adds highlighting to the search.
  166. func (s *SearchSource) Highlight(highlight *Highlight) *SearchSource {
  167. s.highlight = highlight
  168. return s
  169. }
  170. // Highlighter returns the highlighter.
  171. func (s *SearchSource) Highlighter() *Highlight {
  172. if s.highlight == nil {
  173. s.highlight = NewHighlight()
  174. }
  175. return s.highlight
  176. }
  177. // GlobalSuggestText defines the global text to use with all suggesters.
  178. // This avoids repetition.
  179. func (s *SearchSource) GlobalSuggestText(text string) *SearchSource {
  180. s.globalSuggestText = text
  181. return s
  182. }
  183. // Suggester adds a suggester to the search.
  184. func (s *SearchSource) Suggester(suggester Suggester) *SearchSource {
  185. s.suggesters = append(s.suggesters, suggester)
  186. return s
  187. }
  188. // Rescorer adds a rescorer to the search.
  189. func (s *SearchSource) Rescorer(rescore *Rescore) *SearchSource {
  190. s.rescores = append(s.rescores, rescore)
  191. return s
  192. }
  193. // ClearRescorers removes all rescorers from the search.
  194. func (s *SearchSource) ClearRescorers() *SearchSource {
  195. s.rescores = make([]*Rescore, 0)
  196. return s
  197. }
  198. // FetchSource indicates whether the response should contain the stored
  199. // _source for every hit.
  200. func (s *SearchSource) FetchSource(fetchSource bool) *SearchSource {
  201. if s.fetchSourceContext == nil {
  202. s.fetchSourceContext = NewFetchSourceContext(fetchSource)
  203. } else {
  204. s.fetchSourceContext.SetFetchSource(fetchSource)
  205. }
  206. return s
  207. }
  208. // FetchSourceContext indicates how the _source should be fetched.
  209. func (s *SearchSource) FetchSourceContext(fetchSourceContext *FetchSourceContext) *SearchSource {
  210. s.fetchSourceContext = fetchSourceContext
  211. return s
  212. }
  213. // NoStoredFields indicates that no fields should be loaded, resulting in only
  214. // id and type to be returned per field.
  215. func (s *SearchSource) NoStoredFields() *SearchSource {
  216. s.storedFieldNames = nil
  217. return s
  218. }
  219. // StoredField adds a single field to load and return (note, must be stored) as
  220. // part of the search request. If none are specified, the source of the
  221. // document will be returned.
  222. func (s *SearchSource) StoredField(storedFieldName string) *SearchSource {
  223. s.storedFieldNames = append(s.storedFieldNames, storedFieldName)
  224. return s
  225. }
  226. // StoredFields sets the fields to load and return as part of the search request.
  227. // If none are specified, the source of the document will be returned.
  228. func (s *SearchSource) StoredFields(storedFieldNames ...string) *SearchSource {
  229. s.storedFieldNames = append(s.storedFieldNames, storedFieldNames...)
  230. return s
  231. }
  232. // DocvalueField adds a single field to load from the field data cache
  233. // and return as part of the search request.
  234. func (s *SearchSource) DocvalueField(fieldDataField string) *SearchSource {
  235. s.docvalueFields = append(s.docvalueFields, fieldDataField)
  236. return s
  237. }
  238. // DocvalueFields adds one or more fields to load from the field data cache
  239. // and return as part of the search request.
  240. func (s *SearchSource) DocvalueFields(docvalueFields ...string) *SearchSource {
  241. s.docvalueFields = append(s.docvalueFields, docvalueFields...)
  242. return s
  243. }
  244. // ScriptField adds a single script field with the provided script.
  245. func (s *SearchSource) ScriptField(scriptField *ScriptField) *SearchSource {
  246. s.scriptFields = append(s.scriptFields, scriptField)
  247. return s
  248. }
  249. // ScriptFields adds one or more script fields with the provided scripts.
  250. func (s *SearchSource) ScriptFields(scriptFields ...*ScriptField) *SearchSource {
  251. s.scriptFields = append(s.scriptFields, scriptFields...)
  252. return s
  253. }
  254. // IndexBoost sets the boost that a specific index will receive when the
  255. // query is executed against it.
  256. func (s *SearchSource) IndexBoost(index string, boost float64) *SearchSource {
  257. s.indexBoosts[index] = boost
  258. return s
  259. }
  260. // Stats group this request will be aggregated under.
  261. func (s *SearchSource) Stats(statsGroup ...string) *SearchSource {
  262. s.stats = append(s.stats, statsGroup...)
  263. return s
  264. }
  265. // InnerHit adds an inner hit to return with the result.
  266. func (s *SearchSource) InnerHit(name string, innerHit *InnerHit) *SearchSource {
  267. s.innerHits[name] = innerHit
  268. return s
  269. }
  270. // Collapse adds field collapsing.
  271. func (s *SearchSource) Collapse(collapse *CollapseBuilder) *SearchSource {
  272. s.collapse = collapse
  273. return s
  274. }
  275. // Source returns the serializable JSON for the source builder.
  276. func (s *SearchSource) Source() (interface{}, error) {
  277. source := make(map[string]interface{})
  278. if s.from != -1 {
  279. source["from"] = s.from
  280. }
  281. if s.size != -1 {
  282. source["size"] = s.size
  283. }
  284. if s.timeout != "" {
  285. source["timeout"] = s.timeout
  286. }
  287. if s.terminateAfter != nil {
  288. source["terminate_after"] = *s.terminateAfter
  289. }
  290. if s.query != nil {
  291. src, err := s.query.Source()
  292. if err != nil {
  293. return nil, err
  294. }
  295. source["query"] = src
  296. }
  297. if s.postQuery != nil {
  298. src, err := s.postQuery.Source()
  299. if err != nil {
  300. return nil, err
  301. }
  302. source["post_filter"] = src
  303. }
  304. if s.sliceQuery != nil {
  305. src, err := s.sliceQuery.Source()
  306. if err != nil {
  307. return nil, err
  308. }
  309. source["slice"] = src
  310. }
  311. if s.minScore != nil {
  312. source["min_score"] = *s.minScore
  313. }
  314. if s.version != nil {
  315. source["version"] = *s.version
  316. }
  317. if s.explain != nil {
  318. source["explain"] = *s.explain
  319. }
  320. if s.profile {
  321. source["profile"] = s.profile
  322. }
  323. if s.collapse != nil {
  324. src, err := s.collapse.Source()
  325. if err != nil {
  326. return nil, err
  327. }
  328. source["collapse"] = src
  329. }
  330. if s.fetchSourceContext != nil {
  331. src, err := s.fetchSourceContext.Source()
  332. if err != nil {
  333. return nil, err
  334. }
  335. source["_source"] = src
  336. }
  337. if s.storedFieldNames != nil {
  338. switch len(s.storedFieldNames) {
  339. case 1:
  340. source["stored_fields"] = s.storedFieldNames[0]
  341. default:
  342. source["stored_fields"] = s.storedFieldNames
  343. }
  344. }
  345. if len(s.docvalueFields) > 0 {
  346. source["docvalue_fields"] = s.docvalueFields
  347. }
  348. if len(s.scriptFields) > 0 {
  349. sfmap := make(map[string]interface{})
  350. for _, scriptField := range s.scriptFields {
  351. src, err := scriptField.Source()
  352. if err != nil {
  353. return nil, err
  354. }
  355. sfmap[scriptField.FieldName] = src
  356. }
  357. source["script_fields"] = sfmap
  358. }
  359. if len(s.sorters) > 0 {
  360. var sortarr []interface{}
  361. for _, sorter := range s.sorters {
  362. src, err := sorter.Source()
  363. if err != nil {
  364. return nil, err
  365. }
  366. sortarr = append(sortarr, src)
  367. }
  368. source["sort"] = sortarr
  369. }
  370. if s.trackScores {
  371. source["track_scores"] = s.trackScores
  372. }
  373. if len(s.searchAfterSortValues) > 0 {
  374. source["search_after"] = s.searchAfterSortValues
  375. }
  376. if len(s.indexBoosts) > 0 {
  377. source["indices_boost"] = s.indexBoosts
  378. }
  379. if len(s.aggregations) > 0 {
  380. aggsMap := make(map[string]interface{})
  381. for name, aggregate := range s.aggregations {
  382. src, err := aggregate.Source()
  383. if err != nil {
  384. return nil, err
  385. }
  386. aggsMap[name] = src
  387. }
  388. source["aggregations"] = aggsMap
  389. }
  390. if s.highlight != nil {
  391. src, err := s.highlight.Source()
  392. if err != nil {
  393. return nil, err
  394. }
  395. source["highlight"] = src
  396. }
  397. if len(s.suggesters) > 0 {
  398. suggesters := make(map[string]interface{})
  399. for _, s := range s.suggesters {
  400. src, err := s.Source(false)
  401. if err != nil {
  402. return nil, err
  403. }
  404. suggesters[s.Name()] = src
  405. }
  406. if s.globalSuggestText != "" {
  407. suggesters["text"] = s.globalSuggestText
  408. }
  409. source["suggest"] = suggesters
  410. }
  411. if len(s.rescores) > 0 {
  412. // Strip empty rescores from request
  413. var rescores []*Rescore
  414. for _, r := range s.rescores {
  415. if !r.IsEmpty() {
  416. rescores = append(rescores, r)
  417. }
  418. }
  419. if len(rescores) == 1 {
  420. rescores[0].defaultRescoreWindowSize = s.defaultRescoreWindowSize
  421. src, err := rescores[0].Source()
  422. if err != nil {
  423. return nil, err
  424. }
  425. source["rescore"] = src
  426. } else {
  427. var slice []interface{}
  428. for _, r := range rescores {
  429. r.defaultRescoreWindowSize = s.defaultRescoreWindowSize
  430. src, err := r.Source()
  431. if err != nil {
  432. return nil, err
  433. }
  434. slice = append(slice, src)
  435. }
  436. source["rescore"] = slice
  437. }
  438. }
  439. if len(s.stats) > 0 {
  440. source["stats"] = s.stats
  441. }
  442. if len(s.innerHits) > 0 {
  443. // Top-level inner hits
  444. // See http://www.elastic.co/guide/en/elasticsearch/reference/1.5/search-request-inner-hits.html#top-level-inner-hits
  445. // "inner_hits": {
  446. // "<inner_hits_name>": {
  447. // "<path|type>": {
  448. // "<path-to-nested-object-field|child-or-parent-type>": {
  449. // <inner_hits_body>,
  450. // [,"inner_hits" : { [<sub_inner_hits>]+ } ]?
  451. // }
  452. // }
  453. // },
  454. // [,"<inner_hits_name_2>" : { ... } ]*
  455. // }
  456. m := make(map[string]interface{})
  457. for name, hit := range s.innerHits {
  458. if hit.path != "" {
  459. src, err := hit.Source()
  460. if err != nil {
  461. return nil, err
  462. }
  463. path := make(map[string]interface{})
  464. path[hit.path] = src
  465. m[name] = map[string]interface{}{
  466. "path": path,
  467. }
  468. } else if hit.typ != "" {
  469. src, err := hit.Source()
  470. if err != nil {
  471. return nil, err
  472. }
  473. typ := make(map[string]interface{})
  474. typ[hit.typ] = src
  475. m[name] = map[string]interface{}{
  476. "type": typ,
  477. }
  478. } else {
  479. // TODO the Java client throws here, because either path or typ must be specified
  480. _ = m
  481. }
  482. }
  483. source["inner_hits"] = m
  484. }
  485. return source, nil
  486. }