123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- // Copyright 2012-present 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 (
- "fmt"
- )
- // SearchSource enables users to build the search source.
- // It resembles the SearchSourceBuilder in Elasticsearch.
- type SearchSource struct {
- query Query
- postQuery Query
- sliceQuery Query
- from int
- size int
- explain *bool
- version *bool
- sorters []Sorter
- trackScores bool
- searchAfterSortValues []interface{}
- minScore *float64
- timeout string
- terminateAfter *int
- storedFieldNames []string
- docvalueFields []string
- scriptFields []*ScriptField
- fetchSourceContext *FetchSourceContext
- aggregations map[string]Aggregation
- highlight *Highlight
- globalSuggestText string
- suggesters []Suggester
- rescores []*Rescore
- defaultRescoreWindowSize *int
- indexBoosts map[string]float64
- stats []string
- innerHits map[string]*InnerHit
- collapse *CollapseBuilder
- profile bool
- }
- // NewSearchSource initializes a new SearchSource.
- func NewSearchSource() *SearchSource {
- return &SearchSource{
- from: -1,
- size: -1,
- trackScores: false,
- aggregations: make(map[string]Aggregation),
- indexBoosts: make(map[string]float64),
- innerHits: make(map[string]*InnerHit),
- }
- }
- // Query sets the query to use with this search source.
- func (s *SearchSource) Query(query Query) *SearchSource {
- s.query = query
- return s
- }
- // Profile specifies that this search source should activate the
- // Profile API for queries made on it.
- func (s *SearchSource) Profile(profile bool) *SearchSource {
- s.profile = profile
- return s
- }
- // PostFilter will be executed after the query has been executed and
- // only affects the search hits, not the aggregations.
- // This filter is always executed as the last filtering mechanism.
- func (s *SearchSource) PostFilter(postFilter Query) *SearchSource {
- s.postQuery = postFilter
- return s
- }
- // Slice allows partitioning the documents in multiple slices.
- // It is e.g. used to slice a scroll operation, supported in
- // Elasticsearch 5.0 or later.
- // See https://www.elastic.co/guide/en/elasticsearch/reference/5.2/search-request-scroll.html#sliced-scroll
- // for details.
- func (s *SearchSource) Slice(sliceQuery Query) *SearchSource {
- s.sliceQuery = sliceQuery
- return s
- }
- // From index to start the search from. Defaults to 0.
- func (s *SearchSource) From(from int) *SearchSource {
- s.from = from
- return s
- }
- // Size is the number of search hits to return. Defaults to 10.
- func (s *SearchSource) Size(size int) *SearchSource {
- s.size = size
- return s
- }
- // MinScore sets the minimum score below which docs will be filtered out.
- func (s *SearchSource) MinScore(minScore float64) *SearchSource {
- s.minScore = &minScore
- return s
- }
- // Explain indicates whether each search hit should be returned with
- // an explanation of the hit (ranking).
- func (s *SearchSource) Explain(explain bool) *SearchSource {
- s.explain = &explain
- return s
- }
- // Version indicates whether each search hit should be returned with
- // a version associated to it.
- func (s *SearchSource) Version(version bool) *SearchSource {
- s.version = &version
- return s
- }
- // Timeout controls how long a search is allowed to take, e.g. "1s" or "500ms".
- func (s *SearchSource) Timeout(timeout string) *SearchSource {
- s.timeout = timeout
- return s
- }
- // TimeoutInMillis controls how many milliseconds a search is allowed
- // to take before it is canceled.
- func (s *SearchSource) TimeoutInMillis(timeoutInMillis int) *SearchSource {
- s.timeout = fmt.Sprintf("%dms", timeoutInMillis)
- return s
- }
- // TerminateAfter specifies the maximum number of documents to collect for
- // each shard, upon reaching which the query execution will terminate early.
- func (s *SearchSource) TerminateAfter(terminateAfter int) *SearchSource {
- s.terminateAfter = &terminateAfter
- return s
- }
- // Sort adds a sort order.
- func (s *SearchSource) Sort(field string, ascending bool) *SearchSource {
- s.sorters = append(s.sorters, SortInfo{Field: field, Ascending: ascending})
- return s
- }
- // SortWithInfo adds a sort order.
- func (s *SearchSource) SortWithInfo(info SortInfo) *SearchSource {
- s.sorters = append(s.sorters, info)
- return s
- }
- // SortBy adds a sort order.
- func (s *SearchSource) SortBy(sorter ...Sorter) *SearchSource {
- s.sorters = append(s.sorters, sorter...)
- return s
- }
- func (s *SearchSource) hasSort() bool {
- return len(s.sorters) > 0
- }
- // TrackScores is applied when sorting and controls if scores will be
- // tracked as well. Defaults to false.
- func (s *SearchSource) TrackScores(trackScores bool) *SearchSource {
- s.trackScores = trackScores
- return s
- }
- // SearchAfter allows a different form of pagination by using a live cursor,
- // using the results of the previous page to help the retrieval of the next.
- //
- // See https://www.elastic.co/guide/en/elasticsearch/reference/5.2/search-request-search-after.html
- func (s *SearchSource) SearchAfter(sortValues ...interface{}) *SearchSource {
- s.searchAfterSortValues = append(s.searchAfterSortValues, sortValues...)
- return s
- }
- // Aggregation adds an aggreation to perform as part of the search.
- func (s *SearchSource) Aggregation(name string, aggregation Aggregation) *SearchSource {
- s.aggregations[name] = aggregation
- return s
- }
- // DefaultRescoreWindowSize sets the rescore window size for rescores
- // that don't specify their window.
- func (s *SearchSource) DefaultRescoreWindowSize(defaultRescoreWindowSize int) *SearchSource {
- s.defaultRescoreWindowSize = &defaultRescoreWindowSize
- return s
- }
- // Highlight adds highlighting to the search.
- func (s *SearchSource) Highlight(highlight *Highlight) *SearchSource {
- s.highlight = highlight
- return s
- }
- // Highlighter returns the highlighter.
- func (s *SearchSource) Highlighter() *Highlight {
- if s.highlight == nil {
- s.highlight = NewHighlight()
- }
- return s.highlight
- }
- // GlobalSuggestText defines the global text to use with all suggesters.
- // This avoids repetition.
- func (s *SearchSource) GlobalSuggestText(text string) *SearchSource {
- s.globalSuggestText = text
- return s
- }
- // Suggester adds a suggester to the search.
- func (s *SearchSource) Suggester(suggester Suggester) *SearchSource {
- s.suggesters = append(s.suggesters, suggester)
- return s
- }
- // Rescorer adds a rescorer to the search.
- func (s *SearchSource) Rescorer(rescore *Rescore) *SearchSource {
- s.rescores = append(s.rescores, rescore)
- return s
- }
- // ClearRescorers removes all rescorers from the search.
- func (s *SearchSource) ClearRescorers() *SearchSource {
- s.rescores = make([]*Rescore, 0)
- return s
- }
- // FetchSource indicates whether the response should contain the stored
- // _source for every hit.
- func (s *SearchSource) FetchSource(fetchSource bool) *SearchSource {
- if s.fetchSourceContext == nil {
- s.fetchSourceContext = NewFetchSourceContext(fetchSource)
- } else {
- s.fetchSourceContext.SetFetchSource(fetchSource)
- }
- return s
- }
- // FetchSourceContext indicates how the _source should be fetched.
- func (s *SearchSource) FetchSourceContext(fetchSourceContext *FetchSourceContext) *SearchSource {
- s.fetchSourceContext = fetchSourceContext
- return s
- }
- // NoStoredFields indicates that no fields should be loaded, resulting in only
- // id and type to be returned per field.
- func (s *SearchSource) NoStoredFields() *SearchSource {
- s.storedFieldNames = nil
- return s
- }
- // StoredField adds a single field to load and return (note, must be stored) as
- // part of the search request. If none are specified, the source of the
- // document will be returned.
- func (s *SearchSource) StoredField(storedFieldName string) *SearchSource {
- s.storedFieldNames = append(s.storedFieldNames, storedFieldName)
- return s
- }
- // StoredFields sets the fields to load and return as part of the search request.
- // If none are specified, the source of the document will be returned.
- func (s *SearchSource) StoredFields(storedFieldNames ...string) *SearchSource {
- s.storedFieldNames = append(s.storedFieldNames, storedFieldNames...)
- return s
- }
- // DocvalueField adds a single field to load from the field data cache
- // and return as part of the search request.
- func (s *SearchSource) DocvalueField(fieldDataField string) *SearchSource {
- s.docvalueFields = append(s.docvalueFields, fieldDataField)
- return s
- }
- // DocvalueFields adds one or more fields to load from the field data cache
- // and return as part of the search request.
- func (s *SearchSource) DocvalueFields(docvalueFields ...string) *SearchSource {
- s.docvalueFields = append(s.docvalueFields, docvalueFields...)
- return s
- }
- // ScriptField adds a single script field with the provided script.
- func (s *SearchSource) ScriptField(scriptField *ScriptField) *SearchSource {
- s.scriptFields = append(s.scriptFields, scriptField)
- return s
- }
- // ScriptFields adds one or more script fields with the provided scripts.
- func (s *SearchSource) ScriptFields(scriptFields ...*ScriptField) *SearchSource {
- s.scriptFields = append(s.scriptFields, scriptFields...)
- return s
- }
- // IndexBoost sets the boost that a specific index will receive when the
- // query is executed against it.
- func (s *SearchSource) IndexBoost(index string, boost float64) *SearchSource {
- s.indexBoosts[index] = boost
- return s
- }
- // Stats group this request will be aggregated under.
- func (s *SearchSource) Stats(statsGroup ...string) *SearchSource {
- s.stats = append(s.stats, statsGroup...)
- return s
- }
- // InnerHit adds an inner hit to return with the result.
- func (s *SearchSource) InnerHit(name string, innerHit *InnerHit) *SearchSource {
- s.innerHits[name] = innerHit
- return s
- }
- // Collapse adds field collapsing.
- func (s *SearchSource) Collapse(collapse *CollapseBuilder) *SearchSource {
- s.collapse = collapse
- return s
- }
- // Source returns the serializable JSON for the source builder.
- func (s *SearchSource) Source() (interface{}, error) {
- source := make(map[string]interface{})
- if s.from != -1 {
- source["from"] = s.from
- }
- if s.size != -1 {
- source["size"] = s.size
- }
- if s.timeout != "" {
- source["timeout"] = s.timeout
- }
- if s.terminateAfter != nil {
- source["terminate_after"] = *s.terminateAfter
- }
- if s.query != nil {
- src, err := s.query.Source()
- if err != nil {
- return nil, err
- }
- source["query"] = src
- }
- if s.postQuery != nil {
- src, err := s.postQuery.Source()
- if err != nil {
- return nil, err
- }
- source["post_filter"] = src
- }
- if s.sliceQuery != nil {
- src, err := s.sliceQuery.Source()
- if err != nil {
- return nil, err
- }
- source["slice"] = src
- }
- if s.minScore != nil {
- source["min_score"] = *s.minScore
- }
- if s.version != nil {
- source["version"] = *s.version
- }
- if s.explain != nil {
- source["explain"] = *s.explain
- }
- if s.profile {
- source["profile"] = s.profile
- }
- if s.collapse != nil {
- src, err := s.collapse.Source()
- if err != nil {
- return nil, err
- }
- source["collapse"] = src
- }
- if s.fetchSourceContext != nil {
- src, err := s.fetchSourceContext.Source()
- if err != nil {
- return nil, err
- }
- source["_source"] = src
- }
- if s.storedFieldNames != nil {
- switch len(s.storedFieldNames) {
- case 1:
- source["stored_fields"] = s.storedFieldNames[0]
- default:
- source["stored_fields"] = s.storedFieldNames
- }
- }
- if len(s.docvalueFields) > 0 {
- source["docvalue_fields"] = s.docvalueFields
- }
- if len(s.scriptFields) > 0 {
- sfmap := make(map[string]interface{})
- for _, scriptField := range s.scriptFields {
- src, err := scriptField.Source()
- if err != nil {
- return nil, err
- }
- sfmap[scriptField.FieldName] = src
- }
- source["script_fields"] = sfmap
- }
- if len(s.sorters) > 0 {
- var sortarr []interface{}
- for _, sorter := range s.sorters {
- src, err := sorter.Source()
- if err != nil {
- return nil, err
- }
- sortarr = append(sortarr, src)
- }
- source["sort"] = sortarr
- }
- if s.trackScores {
- source["track_scores"] = s.trackScores
- }
- if len(s.searchAfterSortValues) > 0 {
- source["search_after"] = s.searchAfterSortValues
- }
- if len(s.indexBoosts) > 0 {
- source["indices_boost"] = s.indexBoosts
- }
- if len(s.aggregations) > 0 {
- aggsMap := make(map[string]interface{})
- for name, aggregate := range s.aggregations {
- src, err := aggregate.Source()
- if err != nil {
- return nil, err
- }
- aggsMap[name] = src
- }
- source["aggregations"] = aggsMap
- }
- if s.highlight != nil {
- src, err := s.highlight.Source()
- if err != nil {
- return nil, err
- }
- source["highlight"] = src
- }
- if len(s.suggesters) > 0 {
- suggesters := make(map[string]interface{})
- for _, s := range s.suggesters {
- src, err := s.Source(false)
- if err != nil {
- return nil, err
- }
- suggesters[s.Name()] = src
- }
- if s.globalSuggestText != "" {
- suggesters["text"] = s.globalSuggestText
- }
- source["suggest"] = suggesters
- }
- if len(s.rescores) > 0 {
- // Strip empty rescores from request
- var rescores []*Rescore
- for _, r := range s.rescores {
- if !r.IsEmpty() {
- rescores = append(rescores, r)
- }
- }
- if len(rescores) == 1 {
- rescores[0].defaultRescoreWindowSize = s.defaultRescoreWindowSize
- src, err := rescores[0].Source()
- if err != nil {
- return nil, err
- }
- source["rescore"] = src
- } else {
- var slice []interface{}
- for _, r := range rescores {
- r.defaultRescoreWindowSize = s.defaultRescoreWindowSize
- src, err := r.Source()
- if err != nil {
- return nil, err
- }
- slice = append(slice, src)
- }
- source["rescore"] = slice
- }
- }
- if len(s.stats) > 0 {
- source["stats"] = s.stats
- }
- if len(s.innerHits) > 0 {
- // Top-level inner hits
- // See http://www.elastic.co/guide/en/elasticsearch/reference/1.5/search-request-inner-hits.html#top-level-inner-hits
- // "inner_hits": {
- // "<inner_hits_name>": {
- // "<path|type>": {
- // "<path-to-nested-object-field|child-or-parent-type>": {
- // <inner_hits_body>,
- // [,"inner_hits" : { [<sub_inner_hits>]+ } ]?
- // }
- // }
- // },
- // [,"<inner_hits_name_2>" : { ... } ]*
- // }
- m := make(map[string]interface{})
- for name, hit := range s.innerHits {
- if hit.path != "" {
- src, err := hit.Source()
- if err != nil {
- return nil, err
- }
- path := make(map[string]interface{})
- path[hit.path] = src
- m[name] = map[string]interface{}{
- "path": path,
- }
- } else if hit.typ != "" {
- src, err := hit.Source()
- if err != nil {
- return nil, err
- }
- typ := make(map[string]interface{})
- typ[hit.typ] = src
- m[name] = map[string]interface{}{
- "type": typ,
- }
- } else {
- // TODO the Java client throws here, because either path or typ must be specified
- _ = m
- }
- }
- source["inner_hits"] = m
- }
- return source, nil
- }
|