This commit implements a Search() function, which allow for running search requests with both a query and aggregations. This function is meant to more accurately implement the structure of search requests accepted by ElasticSearch's Search API. The Query() and Aggregate() functions are still included by the library, but now simply call Search() internally, making them simple shortcuts. Two new aggregations are also added: "terms" and "top_hits". These are implemented a bit differently than previously implemented ones. The structs and methods for ElasticSearch queries and aggregations will eventually be auto-generated from a specification file, and will look more like the new implementations of these new aggregations.
148 lines
4.2 KiB
Go
148 lines
4.2 KiB
Go
package esquery
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/elastic/go-elasticsearch/v7"
|
|
"github.com/elastic/go-elasticsearch/v7/esapi"
|
|
)
|
|
|
|
// SearchRequest represents a request to ElasticSearch's Search API, described
|
|
// in https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html.
|
|
// Not all features of the search API are currently supported, but a request can
|
|
// currently include a query, aggregations, and more.
|
|
type SearchRequest struct {
|
|
query Mappable
|
|
aggs []Aggregation
|
|
from *uint64
|
|
size *uint64
|
|
explain *bool
|
|
timeout *time.Duration
|
|
}
|
|
|
|
// Search creates a new SearchRequest object, to be filled via method chaining.
|
|
func Search() *SearchRequest {
|
|
return &SearchRequest{}
|
|
}
|
|
|
|
// Query sets a query for the request.
|
|
func (req *SearchRequest) Query(q Mappable) *SearchRequest {
|
|
req.query = q
|
|
return req
|
|
}
|
|
|
|
// Aggs sets one or more aggregations for the request.
|
|
func (req *SearchRequest) Aggs(aggs ...Aggregation) *SearchRequest {
|
|
req.aggs = aggs
|
|
return req
|
|
}
|
|
|
|
// From sets a document offset to start from.
|
|
func (req *SearchRequest) From(offset uint64) *SearchRequest {
|
|
req.from = &offset
|
|
return req
|
|
}
|
|
|
|
// Size sets the number of hits to return. The default - according to the ES
|
|
// documentation - is 10.
|
|
func (req *SearchRequest) Size(size uint64) *SearchRequest {
|
|
req.size = &size
|
|
return req
|
|
}
|
|
|
|
// Explain sets whether the ElasticSearch API should return an explanation for
|
|
// how each hit's score was calculated.
|
|
func (req *SearchRequest) Explain(b bool) *SearchRequest {
|
|
req.explain = &b
|
|
return req
|
|
}
|
|
|
|
// Timeout sets a timeout for the request.
|
|
func (req *SearchRequest) Timeout(dur time.Duration) *SearchRequest {
|
|
req.timeout = &dur
|
|
return req
|
|
}
|
|
|
|
// Map implements the Mappable interface. It converts the request to into a
|
|
// nested map[string]interface{}, as expected by the go-elasticsearch library.
|
|
func (req *SearchRequest) Map() map[string]interface{} {
|
|
m := make(map[string]interface{})
|
|
if req.query != nil {
|
|
m["query"] = req.query.Map()
|
|
}
|
|
if len(req.aggs) > 0 {
|
|
aggs := make(map[string]interface{})
|
|
for _, agg := range req.aggs {
|
|
aggs[agg.Name()] = agg.Map()
|
|
}
|
|
|
|
m["aggs"] = aggs
|
|
}
|
|
if req.size != nil {
|
|
m["size"] = *req.size
|
|
}
|
|
if req.from != nil {
|
|
m["from"] = *req.from
|
|
}
|
|
if req.explain != nil {
|
|
m["explain"] = *req.explain
|
|
}
|
|
if req.timeout != nil {
|
|
m["timeout"] = fmt.Sprintf("%.0fs", req.timeout.Seconds())
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// MarshalJSON implements the json.Marshaler interface. It returns a JSON
|
|
// representation of the map generated by the SearchRequest's Map method.
|
|
func (req *SearchRequest) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(req.Map())
|
|
}
|
|
|
|
// Run executes the request using the provided ElasticSearch client. Zero or
|
|
// more search options can be provided as well. It returns the standard Response
|
|
// type of the official Go client.
|
|
func (req *SearchRequest) Run(
|
|
api *elasticsearch.Client,
|
|
o ...func(*esapi.SearchRequest),
|
|
) (res *esapi.Response, err error) {
|
|
return req.RunSearch(api.Search, o...)
|
|
}
|
|
|
|
// RunSearch is the same as the Run method, except that it accepts a value of
|
|
// type esapi.Search (usually this is the Search field of an elasticsearch.Client
|
|
// object). Since the ElasticSearch client does not provide an interface type
|
|
// for its API (which would allow implementation of mock clients), this provides
|
|
// a workaround. The Search function in the ES client is actually a field of a
|
|
// function type.
|
|
func (req *SearchRequest) RunSearch(
|
|
search esapi.Search,
|
|
o ...func(*esapi.SearchRequest),
|
|
) (res *esapi.Response, err error) {
|
|
var b bytes.Buffer
|
|
err = json.NewEncoder(&b).Encode(req.Map())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
opts := append([]func(*esapi.SearchRequest){search.WithBody(&b)}, o...)
|
|
|
|
return search(opts...)
|
|
}
|
|
|
|
// Query is a shortcut for creating a SearchRequest with only a query. It is
|
|
// mostly included to maintain the API provided by esquery in early releases.
|
|
func Query(q Mappable) *SearchRequest {
|
|
return Search().Query(q)
|
|
}
|
|
|
|
// Aggregate is a shortcut for creating a SearchRequest with aggregations. It is
|
|
// mostly included to maintain the API provided by esquery in early releases.
|
|
func Aggregate(aggs ...Aggregation) *SearchRequest {
|
|
return Search().Aggs(aggs...)
|
|
}
|