search.go 19 KB

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