540 lines
19 KiB
Go
540 lines
19 KiB
Go
// Copyright 2012-2015 Oliver Eilhard. All rights reserved.
|
|
// Use of this source code is governed by a MIT-license.
|
|
// See http://olivere.mit-license.org/license.txt for details.
|
|
|
|
package elastic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"gopkg.in/olivere/elastic.v2/uritemplates"
|
|
)
|
|
|
|
// Search for documents in Elasticsearch.
|
|
type SearchService struct {
|
|
client *Client
|
|
searchSource *SearchSource
|
|
source interface{}
|
|
pretty bool
|
|
searchType string
|
|
indices []string
|
|
queryHint string
|
|
routing string
|
|
preference string
|
|
types []string
|
|
}
|
|
|
|
// NewSearchService creates a new service for searching in Elasticsearch.
|
|
// You typically do not create the service yourself manually, but access
|
|
// it via client.Search().
|
|
func NewSearchService(client *Client) *SearchService {
|
|
builder := &SearchService{
|
|
client: client,
|
|
searchSource: NewSearchSource(),
|
|
}
|
|
return builder
|
|
}
|
|
|
|
// SearchSource sets the search source builder to use with this service.
|
|
func (s *SearchService) SearchSource(searchSource *SearchSource) *SearchService {
|
|
s.searchSource = searchSource
|
|
if s.searchSource == nil {
|
|
s.searchSource = NewSearchSource()
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Source allows the user to set the request body manually without using
|
|
// any of the structs and interfaces in Elastic.
|
|
func (s *SearchService) Source(source interface{}) *SearchService {
|
|
s.source = source
|
|
return s
|
|
}
|
|
|
|
// Index sets the name of the index to use for search.
|
|
func (s *SearchService) Index(index string) *SearchService {
|
|
if s.indices == nil {
|
|
s.indices = make([]string, 0)
|
|
}
|
|
s.indices = append(s.indices, index)
|
|
return s
|
|
}
|
|
|
|
// Indices sets the names of the indices to use for search.
|
|
func (s *SearchService) Indices(indices ...string) *SearchService {
|
|
if s.indices == nil {
|
|
s.indices = make([]string, 0)
|
|
}
|
|
s.indices = append(s.indices, indices...)
|
|
return s
|
|
}
|
|
|
|
// Type restricts the search for the given type.
|
|
func (s *SearchService) Type(typ string) *SearchService {
|
|
if s.types == nil {
|
|
s.types = []string{typ}
|
|
} else {
|
|
s.types = append(s.types, typ)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Types allows to restrict the search to a list of types.
|
|
func (s *SearchService) Types(types ...string) *SearchService {
|
|
if s.types == nil {
|
|
s.types = make([]string, 0)
|
|
}
|
|
s.types = append(s.types, types...)
|
|
return s
|
|
}
|
|
|
|
// Pretty enables the caller to indent the JSON output.
|
|
func (s *SearchService) Pretty(pretty bool) *SearchService {
|
|
s.pretty = pretty
|
|
return s
|
|
}
|
|
|
|
// Timeout sets the timeout to use, e.g. "1s" or "1000ms".
|
|
func (s *SearchService) Timeout(timeout string) *SearchService {
|
|
s.searchSource = s.searchSource.Timeout(timeout)
|
|
return s
|
|
}
|
|
|
|
// TimeoutInMillis sets the timeout in milliseconds.
|
|
func (s *SearchService) TimeoutInMillis(timeoutInMillis int) *SearchService {
|
|
s.searchSource = s.searchSource.TimeoutInMillis(timeoutInMillis)
|
|
return s
|
|
}
|
|
|
|
// SearchType sets the search operation type. Valid values are:
|
|
// "query_then_fetch", "query_and_fetch", "dfs_query_then_fetch",
|
|
// "dfs_query_and_fetch", "count", "scan".
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html#search-request-search-type
|
|
// for details.
|
|
func (s *SearchService) SearchType(searchType string) *SearchService {
|
|
s.searchType = searchType
|
|
return s
|
|
}
|
|
|
|
// Routing allows for (a comma-separated) list of specific routing values.
|
|
func (s *SearchService) Routing(routings ...string) *SearchService {
|
|
s.routing = strings.Join(routings, ",")
|
|
return s
|
|
}
|
|
|
|
// Preference specifies the node or shard the operation should be
|
|
// performed on (default: "random").
|
|
func (s *SearchService) Preference(preference string) *SearchService {
|
|
s.preference = preference
|
|
return s
|
|
}
|
|
|
|
func (s *SearchService) QueryHint(queryHint string) *SearchService {
|
|
s.queryHint = queryHint
|
|
return s
|
|
}
|
|
|
|
// Query sets the query to perform, e.g. MatchAllQuery.
|
|
func (s *SearchService) Query(query Query) *SearchService {
|
|
s.searchSource = s.searchSource.Query(query)
|
|
return s
|
|
}
|
|
|
|
// PostFilter is executed as the last filter. It only affects the
|
|
// search hits but not facets. See
|
|
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-post-filter.html
|
|
// for details.
|
|
func (s *SearchService) PostFilter(postFilter Filter) *SearchService {
|
|
s.searchSource = s.searchSource.PostFilter(postFilter)
|
|
return s
|
|
}
|
|
|
|
// FetchSource indicates whether the response should contain the stored
|
|
// _source for every hit.
|
|
func (s *SearchService) FetchSource(fetchSource bool) *SearchService {
|
|
s.searchSource = s.searchSource.FetchSource(fetchSource)
|
|
return s
|
|
}
|
|
|
|
// FetchSourceContext indicates how the _source should be fetched.
|
|
func (s *SearchService) FetchSourceContext(fetchSourceContext *FetchSourceContext) *SearchService {
|
|
s.searchSource = s.searchSource.FetchSourceContext(fetchSourceContext)
|
|
return s
|
|
}
|
|
|
|
// Highlight sets the highlighting. See
|
|
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html
|
|
// for details.
|
|
func (s *SearchService) Highlight(highlight *Highlight) *SearchService {
|
|
s.searchSource = s.searchSource.Highlight(highlight)
|
|
return s
|
|
}
|
|
|
|
// GlobalSuggestText sets the global text for suggesters. See
|
|
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html#global-suggest
|
|
// for details.
|
|
func (s *SearchService) GlobalSuggestText(globalText string) *SearchService {
|
|
s.searchSource = s.searchSource.GlobalSuggestText(globalText)
|
|
return s
|
|
}
|
|
|
|
// Suggester sets the suggester. See
|
|
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html
|
|
// for details.
|
|
func (s *SearchService) Suggester(suggester Suggester) *SearchService {
|
|
s.searchSource = s.searchSource.Suggester(suggester)
|
|
return s
|
|
}
|
|
|
|
// Facet adds a facet to the search. See
|
|
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets.html
|
|
// to get an overview of Elasticsearch facets.
|
|
func (s *SearchService) Facet(name string, facet Facet) *SearchService {
|
|
s.searchSource = s.searchSource.Facet(name, facet)
|
|
return s
|
|
}
|
|
|
|
// Aggregation adds an aggregation to the search. See
|
|
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations.html
|
|
// for an overview of aggregations in Elasticsearch.
|
|
func (s *SearchService) Aggregation(name string, aggregation Aggregation) *SearchService {
|
|
s.searchSource = s.searchSource.Aggregation(name, aggregation)
|
|
return s
|
|
}
|
|
|
|
// MinScore excludes documents which have a score less than the minimum
|
|
// specified here. See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-min-score.html.
|
|
func (s *SearchService) MinScore(minScore float64) *SearchService {
|
|
s.searchSource = s.searchSource.MinScore(minScore)
|
|
return s
|
|
}
|
|
|
|
// From defines the offset from the first result you want to fetch.
|
|
// Use it in combination with Size to paginate through results.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-from-size.html
|
|
// for details.
|
|
func (s *SearchService) From(from int) *SearchService {
|
|
s.searchSource = s.searchSource.From(from)
|
|
return s
|
|
}
|
|
|
|
// Size defines the maximum number of hits to be returned.
|
|
// Use it in combination with From to paginate through results.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-from-size.html
|
|
// for details.
|
|
func (s *SearchService) Size(size int) *SearchService {
|
|
s.searchSource = s.searchSource.Size(size)
|
|
return s
|
|
}
|
|
|
|
// Explain can be enabled to provide an explanation for each hit and how its
|
|
// score was computed.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-explain.html
|
|
// for details.
|
|
func (s *SearchService) Explain(explain bool) *SearchService {
|
|
s.searchSource = s.searchSource.Explain(explain)
|
|
return s
|
|
}
|
|
|
|
// Version can be set to true to return a version for each search hit.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-version.html.
|
|
func (s *SearchService) Version(version bool) *SearchService {
|
|
s.searchSource = s.searchSource.Version(version)
|
|
return s
|
|
}
|
|
|
|
// Sort the results by the given field, in the given order.
|
|
// Use the alternative SortWithInfo to use a struct to define the sorting.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html
|
|
// for detailed documentation of sorting.
|
|
func (s *SearchService) Sort(field string, ascending bool) *SearchService {
|
|
s.searchSource = s.searchSource.Sort(field, ascending)
|
|
return s
|
|
}
|
|
|
|
// SortWithInfo defines how to sort results.
|
|
// Use the Sort func for a shortcut.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html
|
|
// for detailed documentation of sorting.
|
|
func (s *SearchService) SortWithInfo(info SortInfo) *SearchService {
|
|
s.searchSource = s.searchSource.SortWithInfo(info)
|
|
return s
|
|
}
|
|
|
|
// SortBy defines how to sort results.
|
|
// Use the Sort func for a shortcut.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html
|
|
// for detailed documentation of sorting.
|
|
func (s *SearchService) SortBy(sorter ...Sorter) *SearchService {
|
|
s.searchSource = s.searchSource.SortBy(sorter...)
|
|
return s
|
|
}
|
|
|
|
// Fields tells Elasticsearch to only load specific fields from a search hit.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html.
|
|
func (s *SearchService) Fields(fields ...string) *SearchService {
|
|
s.searchSource = s.searchSource.Fields(fields...)
|
|
return s
|
|
}
|
|
|
|
// Do executes the search and returns a SearchResult.
|
|
func (s *SearchService) Do() (*SearchResult, error) {
|
|
// Build url
|
|
path := "/"
|
|
|
|
// Indices part
|
|
indexPart := make([]string, 0)
|
|
for _, index := range s.indices {
|
|
index, err := uritemplates.Expand("{index}", map[string]string{
|
|
"index": index,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
indexPart = append(indexPart, index)
|
|
}
|
|
path += strings.Join(indexPart, ",")
|
|
|
|
// Types part
|
|
if len(s.types) > 0 {
|
|
typesPart := make([]string, 0)
|
|
for _, typ := range s.types {
|
|
typ, err := uritemplates.Expand("{type}", map[string]string{
|
|
"type": typ,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
typesPart = append(typesPart, typ)
|
|
}
|
|
path += "/"
|
|
path += strings.Join(typesPart, ",")
|
|
}
|
|
|
|
// Search
|
|
path += "/_search"
|
|
|
|
// Parameters
|
|
params := make(url.Values)
|
|
if s.pretty {
|
|
params.Set("pretty", fmt.Sprintf("%v", s.pretty))
|
|
}
|
|
if s.searchType != "" {
|
|
params.Set("search_type", s.searchType)
|
|
}
|
|
if s.routing != "" {
|
|
params.Set("routing", s.routing)
|
|
}
|
|
|
|
// Perform request
|
|
var body interface{}
|
|
if s.source != nil {
|
|
body = s.source
|
|
} else {
|
|
body = s.searchSource.Source()
|
|
}
|
|
res, err := s.client.PerformRequest("POST", path, params, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Return search results
|
|
ret := new(SearchResult)
|
|
if err := json.Unmarshal(res.Body, ret); err != nil {
|
|
return nil, err
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// SearchResult is the result of a search in Elasticsearch.
|
|
type SearchResult struct {
|
|
TookInMillis int64 `json:"took"` // search time in milliseconds
|
|
ScrollId string `json:"_scroll_id"` // only used with Scroll and Scan operations
|
|
Hits *SearchHits `json:"hits"` // the actual search hits
|
|
Suggest SearchSuggest `json:"suggest"` // results from suggesters
|
|
Facets SearchFacets `json:"facets"` // results from facets
|
|
Aggregations Aggregations `json:"aggregations"` // results from aggregations
|
|
TimedOut bool `json:"timed_out"` // true if the search timed out
|
|
Error string `json:"error,omitempty"` // used in MultiSearch only
|
|
}
|
|
|
|
// TotalHits is a convenience function to return the number of hits for
|
|
// a search result.
|
|
func (r *SearchResult) TotalHits() int64 {
|
|
if r.Hits != nil {
|
|
return r.Hits.TotalHits
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Each is a utility function to iterate over all hits. It saves you from
|
|
// checking for nil values. Notice that Each will ignore errors in
|
|
// serializing JSON.
|
|
func (r *SearchResult) Each(typ reflect.Type) []interface{} {
|
|
if r.Hits == nil || r.Hits.Hits == nil || len(r.Hits.Hits) == 0 {
|
|
return nil
|
|
}
|
|
slice := make([]interface{}, 0)
|
|
for _, hit := range r.Hits.Hits {
|
|
v := reflect.New(typ).Elem()
|
|
if err := json.Unmarshal(*hit.Source, v.Addr().Interface()); err == nil {
|
|
slice = append(slice, v.Interface())
|
|
}
|
|
}
|
|
return slice
|
|
}
|
|
|
|
// SearchHits specifies the list of search hits.
|
|
type SearchHits struct {
|
|
TotalHits int64 `json:"total"` // total number of hits found
|
|
MaxScore *float64 `json:"max_score"` // maximum score of all hits
|
|
Hits []*SearchHit `json:"hits"` // the actual hits returned
|
|
}
|
|
|
|
// SearchHit is a single hit.
|
|
type SearchHit struct {
|
|
Score *float64 `json:"_score"` // computed score
|
|
Index string `json:"_index"` // index name
|
|
Id string `json:"_id"` // external or internal
|
|
Type string `json:"_type"` // type
|
|
Version *int64 `json:"_version"` // version number, when Version is set to true in SearchService
|
|
Sort []interface{} `json:"sort"` // sort information
|
|
Highlight SearchHitHighlight `json:"highlight"` // highlighter information
|
|
Source *json.RawMessage `json:"_source"` // stored document source
|
|
Fields map[string]interface{} `json:"fields"` // returned fields
|
|
Explanation *SearchExplanation `json:"_explanation"` // explains how the score was computed
|
|
MatchedQueries []string `json:"matched_queries"` // matched queries
|
|
InnerHits map[string]*SearchHitInnerHits `json:"inner_hits"` // inner hits with ES >= 1.5.0
|
|
|
|
// Shard
|
|
// HighlightFields
|
|
// SortValues
|
|
// MatchedFilters
|
|
}
|
|
|
|
type SearchHitInnerHits struct {
|
|
Hits *SearchHits `json:"hits"`
|
|
}
|
|
|
|
// SearchExplanation explains how the score for a hit was computed.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-explain.html.
|
|
type SearchExplanation struct {
|
|
Value float64 `json:"value"` // e.g. 1.0
|
|
Description string `json:"description"` // e.g. "boost" or "ConstantScore(*:*), product of:"
|
|
Details []SearchExplanation `json:"details,omitempty"` // recursive details
|
|
}
|
|
|
|
// Suggest
|
|
|
|
// SearchSuggest is a map of suggestions.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html.
|
|
type SearchSuggest map[string][]SearchSuggestion
|
|
|
|
// SearchSuggestion is a single search suggestion.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html.
|
|
type SearchSuggestion struct {
|
|
Text string `json:"text"`
|
|
Offset int `json:"offset"`
|
|
Length int `json:"length"`
|
|
Options []SearchSuggestionOption `json:"options"`
|
|
}
|
|
|
|
// SearchSuggestionOption is an option of a SearchSuggestion.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html.
|
|
type SearchSuggestionOption struct {
|
|
Text string `json:"text"`
|
|
Score float32 `json:"score"`
|
|
Freq int `json:"freq"`
|
|
Payload interface{} `json:"payload"`
|
|
}
|
|
|
|
// Facets
|
|
|
|
// SearchFacets is a map of facets.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets.html.
|
|
type SearchFacets map[string]*SearchFacet
|
|
|
|
// SearchFacet is a single facet.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets.html.
|
|
type SearchFacet struct {
|
|
Type string `json:"_type"`
|
|
Missing int `json:"missing"`
|
|
Total int `json:"total"`
|
|
Other int `json:"other"`
|
|
Terms []searchFacetTerm `json:"terms"`
|
|
Ranges []searchFacetRange `json:"ranges"`
|
|
Entries []searchFacetEntry `json:"entries"`
|
|
}
|
|
|
|
// searchFacetTerm is the result of a terms/terms_stats facet.
|
|
// See https://www.elastic.co/guide/en/elasticsearch/reference/1.7/search-facets-terms-facet.html
|
|
// and https://www.elastic.co/guide/en/elasticsearch/reference/1.7/search-facets-terms-stats-facet.html.
|
|
type searchFacetTerm struct {
|
|
Term interface{} `json:"term"`
|
|
Count int `json:"count"`
|
|
|
|
// The following fields are returned for terms_stats facets.
|
|
// See https://www.elastic.co/guide/en/elasticsearch/reference/1.7/search-facets-terms-stats-facet.html.
|
|
|
|
TotalCount int `json:"total_count"`
|
|
Min float64 `json:"min"`
|
|
Max float64 `json:"max"`
|
|
Total float64 `json:"total"`
|
|
Mean float64 `json:"mean"`
|
|
}
|
|
|
|
// searchFacetRange is the result of a range facet.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets-range-facet.html.
|
|
type searchFacetRange struct {
|
|
From *float64 `json:"from"`
|
|
FromStr *string `json:"from_str"`
|
|
To *float64 `json:"to"`
|
|
ToStr *string `json:"to_str"`
|
|
Count int `json:"count"`
|
|
Min *float64 `json:"min"`
|
|
Max *float64 `json:"max"`
|
|
TotalCount int `json:"total_count"`
|
|
Total *float64 `json:"total"`
|
|
Mean *float64 `json:"mean"`
|
|
}
|
|
|
|
// searchFacetEntry is a general facet entry.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets.html
|
|
type searchFacetEntry struct {
|
|
// Key for this facet, e.g. in histograms
|
|
Key interface{} `json:"key"`
|
|
// Date histograms contain the number of milliseconds as date:
|
|
// If e.Time = 1293840000000, then: Time.at(1293840000000/1000) => 2011-01-01
|
|
Time int64 `json:"time"`
|
|
// Number of hits for this facet
|
|
Count int `json:"count"`
|
|
// Min is either a string like "Infinity" or a float64.
|
|
// This is returned with some DateHistogram facets.
|
|
Min interface{} `json:"min,omitempty"`
|
|
// Max is either a string like "-Infinity" or a float64
|
|
// This is returned with some DateHistogram facets.
|
|
Max interface{} `json:"max,omitempty"`
|
|
// Total is the sum of all entries on the recorded Time
|
|
// This is returned with some DateHistogram facets.
|
|
Total float64 `json:"total,omitempty"`
|
|
// TotalCount is the number of entries for Total
|
|
// This is returned with some DateHistogram facets.
|
|
TotalCount int `json:"total_count,omitempty"`
|
|
// Mean is the mean value
|
|
// This is returned with some DateHistogram facets.
|
|
Mean float64 `json:"mean,omitempty"`
|
|
}
|
|
|
|
// Aggregations (see search_aggs.go)
|
|
|
|
// Highlighting
|
|
|
|
// SearchHitHighlight is the highlight information of a search hit.
|
|
// See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html
|
|
// for a general discussion of highlighting.
|
|
type SearchHitHighlight map[string][]string
|