search.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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. "context"
  7. "encoding/json"
  8. "fmt"
  9. "net/url"
  10. "reflect"
  11. "strings"
  12. "gopkg.in/olivere/elastic.v5/uritemplates"
  13. )
  14. // Search for documents in Elasticsearch.
  15. type SearchService struct {
  16. client *Client
  17. searchSource *SearchSource
  18. source interface{}
  19. pretty bool
  20. filterPath []string
  21. searchType string
  22. index []string
  23. typ []string
  24. routing string
  25. preference string
  26. requestCache *bool
  27. ignoreUnavailable *bool
  28. allowNoIndices *bool
  29. expandWildcards string
  30. }
  31. // NewSearchService creates a new service for searching in Elasticsearch.
  32. func NewSearchService(client *Client) *SearchService {
  33. builder := &SearchService{
  34. client: client,
  35. searchSource: NewSearchSource(),
  36. }
  37. return builder
  38. }
  39. // SearchSource sets the search source builder to use with this service.
  40. func (s *SearchService) SearchSource(searchSource *SearchSource) *SearchService {
  41. s.searchSource = searchSource
  42. if s.searchSource == nil {
  43. s.searchSource = NewSearchSource()
  44. }
  45. return s
  46. }
  47. // Source allows the user to set the request body manually without using
  48. // any of the structs and interfaces in Elastic.
  49. func (s *SearchService) Source(source interface{}) *SearchService {
  50. s.source = source
  51. return s
  52. }
  53. // FilterPath allows reducing the response, a mechanism known as
  54. // response filtering and described here:
  55. // https://www.elastic.co/guide/en/elasticsearch/reference/5.6/common-options.html#common-options-response-filtering.
  56. func (s *SearchService) FilterPath(filterPath ...string) *SearchService {
  57. s.filterPath = append(s.filterPath, filterPath...)
  58. return s
  59. }
  60. // Index sets the names of the indices to use for search.
  61. func (s *SearchService) Index(index ...string) *SearchService {
  62. s.index = append(s.index, index...)
  63. return s
  64. }
  65. // Types adds search restrictions for a list of types.
  66. func (s *SearchService) Type(typ ...string) *SearchService {
  67. s.typ = append(s.typ, typ...)
  68. return s
  69. }
  70. // Pretty enables the caller to indent the JSON output.
  71. func (s *SearchService) Pretty(pretty bool) *SearchService {
  72. s.pretty = pretty
  73. return s
  74. }
  75. // Timeout sets the timeout to use, e.g. "1s" or "1000ms".
  76. func (s *SearchService) Timeout(timeout string) *SearchService {
  77. s.searchSource = s.searchSource.Timeout(timeout)
  78. return s
  79. }
  80. // Profile sets the Profile API flag on the search source.
  81. // When enabled, a search executed by this service will return query
  82. // profiling data.
  83. func (s *SearchService) Profile(profile bool) *SearchService {
  84. s.searchSource = s.searchSource.Profile(profile)
  85. return s
  86. }
  87. // Collapse adds field collapsing.
  88. func (s *SearchService) Collapse(collapse *CollapseBuilder) *SearchService {
  89. s.searchSource = s.searchSource.Collapse(collapse)
  90. return s
  91. }
  92. // TimeoutInMillis sets the timeout in milliseconds.
  93. func (s *SearchService) TimeoutInMillis(timeoutInMillis int) *SearchService {
  94. s.searchSource = s.searchSource.TimeoutInMillis(timeoutInMillis)
  95. return s
  96. }
  97. // TerminateAfter specifies the maximum number of documents to collect for
  98. // each shard, upon reaching which the query execution will terminate early.
  99. func (s *SearchService) TerminateAfter(terminateAfter int) *SearchService {
  100. s.searchSource = s.searchSource.TerminateAfter(terminateAfter)
  101. return s
  102. }
  103. // SearchType sets the search operation type. Valid values are:
  104. // "dfs_query_then_fetch" and "query_then_fetch".
  105. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-request-search-type.html
  106. // for details.
  107. func (s *SearchService) SearchType(searchType string) *SearchService {
  108. s.searchType = searchType
  109. return s
  110. }
  111. // Routing is a list of specific routing values to control the shards
  112. // the search will be executed on.
  113. func (s *SearchService) Routing(routings ...string) *SearchService {
  114. s.routing = strings.Join(routings, ",")
  115. return s
  116. }
  117. // Preference sets the preference to execute the search. Defaults to
  118. // randomize across shards ("random"). Can be set to "_local" to prefer
  119. // local shards, "_primary" to execute on primary shards only,
  120. // or a custom value which guarantees that the same order will be used
  121. // across different requests.
  122. func (s *SearchService) Preference(preference string) *SearchService {
  123. s.preference = preference
  124. return s
  125. }
  126. // RequestCache indicates whether the cache should be used for this
  127. // request or not, defaults to index level setting.
  128. func (s *SearchService) RequestCache(requestCache bool) *SearchService {
  129. s.requestCache = &requestCache
  130. return s
  131. }
  132. // Query sets the query to perform, e.g. MatchAllQuery.
  133. func (s *SearchService) Query(query Query) *SearchService {
  134. s.searchSource = s.searchSource.Query(query)
  135. return s
  136. }
  137. // PostFilter will be executed after the query has been executed and
  138. // only affects the search hits, not the aggregations.
  139. // This filter is always executed as the last filtering mechanism.
  140. func (s *SearchService) PostFilter(postFilter Query) *SearchService {
  141. s.searchSource = s.searchSource.PostFilter(postFilter)
  142. return s
  143. }
  144. // FetchSource indicates whether the response should contain the stored
  145. // _source for every hit.
  146. func (s *SearchService) FetchSource(fetchSource bool) *SearchService {
  147. s.searchSource = s.searchSource.FetchSource(fetchSource)
  148. return s
  149. }
  150. // FetchSourceContext indicates how the _source should be fetched.
  151. func (s *SearchService) FetchSourceContext(fetchSourceContext *FetchSourceContext) *SearchService {
  152. s.searchSource = s.searchSource.FetchSourceContext(fetchSourceContext)
  153. return s
  154. }
  155. // Highlight adds highlighting to the search.
  156. func (s *SearchService) Highlight(highlight *Highlight) *SearchService {
  157. s.searchSource = s.searchSource.Highlight(highlight)
  158. return s
  159. }
  160. // GlobalSuggestText defines the global text to use with all suggesters.
  161. // This avoids repetition.
  162. func (s *SearchService) GlobalSuggestText(globalText string) *SearchService {
  163. s.searchSource = s.searchSource.GlobalSuggestText(globalText)
  164. return s
  165. }
  166. // Suggester adds a suggester to the search.
  167. func (s *SearchService) Suggester(suggester Suggester) *SearchService {
  168. s.searchSource = s.searchSource.Suggester(suggester)
  169. return s
  170. }
  171. // Aggregation adds an aggreation to perform as part of the search.
  172. func (s *SearchService) Aggregation(name string, aggregation Aggregation) *SearchService {
  173. s.searchSource = s.searchSource.Aggregation(name, aggregation)
  174. return s
  175. }
  176. // MinScore sets the minimum score below which docs will be filtered out.
  177. func (s *SearchService) MinScore(minScore float64) *SearchService {
  178. s.searchSource = s.searchSource.MinScore(minScore)
  179. return s
  180. }
  181. // From index to start the search from. Defaults to 0.
  182. func (s *SearchService) From(from int) *SearchService {
  183. s.searchSource = s.searchSource.From(from)
  184. return s
  185. }
  186. // Size is the number of search hits to return. Defaults to 10.
  187. func (s *SearchService) Size(size int) *SearchService {
  188. s.searchSource = s.searchSource.Size(size)
  189. return s
  190. }
  191. // Explain indicates whether each search hit should be returned with
  192. // an explanation of the hit (ranking).
  193. func (s *SearchService) Explain(explain bool) *SearchService {
  194. s.searchSource = s.searchSource.Explain(explain)
  195. return s
  196. }
  197. // Version indicates whether each search hit should be returned with
  198. // a version associated to it.
  199. func (s *SearchService) Version(version bool) *SearchService {
  200. s.searchSource = s.searchSource.Version(version)
  201. return s
  202. }
  203. // Sort adds a sort order.
  204. func (s *SearchService) Sort(field string, ascending bool) *SearchService {
  205. s.searchSource = s.searchSource.Sort(field, ascending)
  206. return s
  207. }
  208. // SortWithInfo adds a sort order.
  209. func (s *SearchService) SortWithInfo(info SortInfo) *SearchService {
  210. s.searchSource = s.searchSource.SortWithInfo(info)
  211. return s
  212. }
  213. // SortBy adds a sort order.
  214. func (s *SearchService) SortBy(sorter ...Sorter) *SearchService {
  215. s.searchSource = s.searchSource.SortBy(sorter...)
  216. return s
  217. }
  218. // NoStoredFields indicates that no stored fields should be loaded, resulting in only
  219. // id and type to be returned per field.
  220. func (s *SearchService) NoStoredFields() *SearchService {
  221. s.searchSource = s.searchSource.NoStoredFields()
  222. return s
  223. }
  224. // StoredField adds a single field to load and return (note, must be stored) as
  225. // part of the search request. If none are specified, the source of the
  226. // document will be returned.
  227. func (s *SearchService) StoredField(fieldName string) *SearchService {
  228. s.searchSource = s.searchSource.StoredField(fieldName)
  229. return s
  230. }
  231. // StoredFields sets the fields to load and return as part of the search request.
  232. // If none are specified, the source of the document will be returned.
  233. func (s *SearchService) StoredFields(fields ...string) *SearchService {
  234. s.searchSource = s.searchSource.StoredFields(fields...)
  235. return s
  236. }
  237. // TrackScores is applied when sorting and controls if scores will be
  238. // tracked as well. Defaults to false.
  239. func (s *SearchService) TrackScores(trackScores bool) *SearchService {
  240. s.searchSource = s.searchSource.TrackScores(trackScores)
  241. return s
  242. }
  243. // SearchAfter allows a different form of pagination by using a live cursor,
  244. // using the results of the previous page to help the retrieval of the next.
  245. //
  246. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-request-search-after.html
  247. func (s *SearchService) SearchAfter(sortValues ...interface{}) *SearchService {
  248. s.searchSource = s.searchSource.SearchAfter(sortValues...)
  249. return s
  250. }
  251. // IgnoreUnavailable indicates whether the specified concrete indices
  252. // should be ignored when unavailable (missing or closed).
  253. func (s *SearchService) IgnoreUnavailable(ignoreUnavailable bool) *SearchService {
  254. s.ignoreUnavailable = &ignoreUnavailable
  255. return s
  256. }
  257. // AllowNoIndices indicates whether to ignore if a wildcard indices
  258. // expression resolves into no concrete indices. (This includes `_all` string
  259. // or when no indices have been specified).
  260. func (s *SearchService) AllowNoIndices(allowNoIndices bool) *SearchService {
  261. s.allowNoIndices = &allowNoIndices
  262. return s
  263. }
  264. // ExpandWildcards indicates whether to expand wildcard expression to
  265. // concrete indices that are open, closed or both.
  266. func (s *SearchService) ExpandWildcards(expandWildcards string) *SearchService {
  267. s.expandWildcards = expandWildcards
  268. return s
  269. }
  270. // buildURL builds the URL for the operation.
  271. func (s *SearchService) buildURL() (string, url.Values, error) {
  272. var err error
  273. var path string
  274. if len(s.index) > 0 && len(s.typ) > 0 {
  275. path, err = uritemplates.Expand("/{index}/{type}/_search", map[string]string{
  276. "index": strings.Join(s.index, ","),
  277. "type": strings.Join(s.typ, ","),
  278. })
  279. } else if len(s.index) > 0 {
  280. path, err = uritemplates.Expand("/{index}/_search", map[string]string{
  281. "index": strings.Join(s.index, ","),
  282. })
  283. } else if len(s.typ) > 0 {
  284. path, err = uritemplates.Expand("/_all/{type}/_search", map[string]string{
  285. "type": strings.Join(s.typ, ","),
  286. })
  287. } else {
  288. path = "/_search"
  289. }
  290. if err != nil {
  291. return "", url.Values{}, err
  292. }
  293. // Add query string parameters
  294. params := url.Values{}
  295. if s.pretty {
  296. params.Set("pretty", fmt.Sprintf("%v", s.pretty))
  297. }
  298. if s.searchType != "" {
  299. params.Set("search_type", s.searchType)
  300. }
  301. if s.routing != "" {
  302. params.Set("routing", s.routing)
  303. }
  304. if s.preference != "" {
  305. params.Set("preference", s.preference)
  306. }
  307. if s.requestCache != nil {
  308. params.Set("request_cache", fmt.Sprintf("%v", *s.requestCache))
  309. }
  310. if s.allowNoIndices != nil {
  311. params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
  312. }
  313. if s.expandWildcards != "" {
  314. params.Set("expand_wildcards", s.expandWildcards)
  315. }
  316. if s.ignoreUnavailable != nil {
  317. params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
  318. }
  319. if len(s.filterPath) > 0 {
  320. params.Set("filter_path", strings.Join(s.filterPath, ","))
  321. }
  322. return path, params, nil
  323. }
  324. // Validate checks if the operation is valid.
  325. func (s *SearchService) Validate() error {
  326. return nil
  327. }
  328. // Do executes the search and returns a SearchResult.
  329. func (s *SearchService) Do(ctx context.Context) (*SearchResult, error) {
  330. // Check pre-conditions
  331. if err := s.Validate(); err != nil {
  332. return nil, err
  333. }
  334. // Get URL for request
  335. path, params, err := s.buildURL()
  336. if err != nil {
  337. return nil, err
  338. }
  339. // Perform request
  340. var body interface{}
  341. if s.source != nil {
  342. body = s.source
  343. } else {
  344. src, err := s.searchSource.Source()
  345. if err != nil {
  346. return nil, err
  347. }
  348. body = src
  349. }
  350. res, err := s.client.PerformRequest(ctx, "POST", path, params, body)
  351. if err != nil {
  352. return nil, err
  353. }
  354. // Return search results
  355. ret := new(SearchResult)
  356. if err := s.client.decoder.Decode(res.Body, ret); err != nil {
  357. return nil, err
  358. }
  359. return ret, nil
  360. }
  361. // SearchResult is the result of a search in Elasticsearch.
  362. type SearchResult struct {
  363. TookInMillis int64 `json:"took"` // search time in milliseconds
  364. ScrollId string `json:"_scroll_id"` // only used with Scroll operations
  365. Hits *SearchHits `json:"hits"` // the actual search hits
  366. Suggest SearchSuggest `json:"suggest"` // results from suggesters
  367. Aggregations Aggregations `json:"aggregations"` // results from aggregations
  368. TimedOut bool `json:"timed_out"` // true if the search timed out
  369. Error *ErrorDetails `json:"error,omitempty"` // only used in MultiGet
  370. Profile *SearchProfile `json:"profile,omitempty"` // profiling results, if optional Profile API was active for this search
  371. Shards *shardsInfo `json:"_shards,omitempty"` // shard information
  372. }
  373. // TotalHits is a convenience function to return the number of hits for
  374. // a search result.
  375. func (r *SearchResult) TotalHits() int64 {
  376. if r.Hits != nil {
  377. return r.Hits.TotalHits
  378. }
  379. return 0
  380. }
  381. // Each is a utility function to iterate over all hits. It saves you from
  382. // checking for nil values. Notice that Each will ignore errors in
  383. // serializing JSON.
  384. func (r *SearchResult) Each(typ reflect.Type) []interface{} {
  385. if r.Hits == nil || r.Hits.Hits == nil || len(r.Hits.Hits) == 0 {
  386. return nil
  387. }
  388. var slice []interface{}
  389. for _, hit := range r.Hits.Hits {
  390. v := reflect.New(typ).Elem()
  391. if hit.Source == nil {
  392. slice = append(slice, v.Interface())
  393. continue
  394. }
  395. if err := json.Unmarshal(*hit.Source, v.Addr().Interface()); err == nil {
  396. slice = append(slice, v.Interface())
  397. }
  398. }
  399. return slice
  400. }
  401. // SearchHits specifies the list of search hits.
  402. type SearchHits struct {
  403. TotalHits int64 `json:"total"` // total number of hits found
  404. MaxScore *float64 `json:"max_score"` // maximum score of all hits
  405. Hits []*SearchHit `json:"hits"` // the actual hits returned
  406. }
  407. // SearchHit is a single hit.
  408. type SearchHit struct {
  409. Score *float64 `json:"_score"` // computed score
  410. Index string `json:"_index"` // index name
  411. Type string `json:"_type"` // type meta field
  412. Id string `json:"_id"` // external or internal
  413. Uid string `json:"_uid"` // uid meta field (see MapperService.java for all meta fields)
  414. Routing string `json:"_routing"` // routing meta field
  415. Parent string `json:"_parent"` // parent meta field
  416. Version *int64 `json:"_version"` // version number, when Version is set to true in SearchService
  417. Sort []interface{} `json:"sort"` // sort information
  418. Highlight SearchHitHighlight `json:"highlight"` // highlighter information
  419. Source *json.RawMessage `json:"_source"` // stored document source
  420. Fields map[string]interface{} `json:"fields"` // returned (stored) fields
  421. Explanation *SearchExplanation `json:"_explanation"` // explains how the score was computed
  422. MatchedQueries []string `json:"matched_queries"` // matched queries
  423. InnerHits map[string]*SearchHitInnerHits `json:"inner_hits"` // inner hits with ES >= 1.5.0
  424. // Shard
  425. // HighlightFields
  426. // SortValues
  427. // MatchedFilters
  428. }
  429. type SearchHitInnerHits struct {
  430. Hits *SearchHits `json:"hits"`
  431. }
  432. // SearchExplanation explains how the score for a hit was computed.
  433. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-request-explain.html.
  434. type SearchExplanation struct {
  435. Value float64 `json:"value"` // e.g. 1.0
  436. Description string `json:"description"` // e.g. "boost" or "ConstantScore(*:*), product of:"
  437. Details []SearchExplanation `json:"details,omitempty"` // recursive details
  438. }
  439. // Suggest
  440. // SearchSuggest is a map of suggestions.
  441. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-suggesters.html.
  442. type SearchSuggest map[string][]SearchSuggestion
  443. // SearchSuggestion is a single search suggestion.
  444. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-suggesters.html.
  445. type SearchSuggestion struct {
  446. Text string `json:"text"`
  447. Offset int `json:"offset"`
  448. Length int `json:"length"`
  449. Options []SearchSuggestionOption `json:"options"`
  450. }
  451. // SearchSuggestionOption is an option of a SearchSuggestion.
  452. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-suggesters.html.
  453. type SearchSuggestionOption struct {
  454. Text string `json:"text"`
  455. Index string `json:"_index"`
  456. Type string `json:"_type"`
  457. Id string `json:"_id"`
  458. Score float64 `json:"score"`
  459. Highlighted string `json:"highlighted"`
  460. CollateMatch bool `json:"collate_match"`
  461. Freq int `json:"freq"` // from TermSuggestion.Option in Java API
  462. Source *json.RawMessage `json:"_source"`
  463. }
  464. // SearchProfile is a list of shard profiling data collected during
  465. // query execution in the "profile" section of a SearchResult
  466. type SearchProfile struct {
  467. Shards []SearchProfileShardResult `json:"shards"`
  468. }
  469. // SearchProfileShardResult returns the profiling data for a single shard
  470. // accessed during the search query or aggregation.
  471. type SearchProfileShardResult struct {
  472. ID string `json:"id"`
  473. Searches []QueryProfileShardResult `json:"searches"`
  474. Aggregations []ProfileResult `json:"aggregations"`
  475. }
  476. // QueryProfileShardResult is a container class to hold the profile results
  477. // for a single shard in the request. It comtains a list of query profiles,
  478. // a collector tree and a total rewrite tree.
  479. type QueryProfileShardResult struct {
  480. Query []ProfileResult `json:"query,omitempty"`
  481. RewriteTime int64 `json:"rewrite_time,omitempty"`
  482. Collector []interface{} `json:"collector,omitempty"`
  483. }
  484. // CollectorResult holds the profile timings of the collectors used in the
  485. // search. Children's CollectorResults may be embedded inside of a parent
  486. // CollectorResult.
  487. type CollectorResult struct {
  488. Name string `json:"name,omitempty"`
  489. Reason string `json:"reason,omitempty"`
  490. Time string `json:"time,omitempty"`
  491. TimeNanos int64 `json:"time_in_nanos,omitempty"`
  492. Children []CollectorResult `json:"children,omitempty"`
  493. }
  494. // ProfileResult is the internal representation of a profiled query,
  495. // corresponding to a single node in the query tree.
  496. type ProfileResult struct {
  497. Type string `json:"type"`
  498. Description string `json:"description,omitempty"`
  499. NodeTime string `json:"time,omitempty"`
  500. NodeTimeNanos int64 `json:"time_in_nanos,omitempty"`
  501. Breakdown map[string]int64 `json:"breakdown,omitempty"`
  502. Children []ProfileResult `json:"children,omitempty"`
  503. }
  504. // Aggregations (see search_aggs.go)
  505. // Highlighting
  506. // SearchHitHighlight is the highlight information of a search hit.
  507. // See https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-request-highlighting.html
  508. // for a general discussion of highlighting.
  509. type SearchHitHighlight map[string][]string