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.
477 lines
14 KiB
Go
477 lines
14 KiB
Go
package esquery
|
|
|
|
import "github.com/fatih/structs"
|
|
|
|
// BaseAgg contains several fields that are common for all aggregation types.
|
|
type BaseAgg struct {
|
|
name string
|
|
apiName string
|
|
*BaseAggParams `structs:",flatten"`
|
|
}
|
|
|
|
// BaseAggParams contains fields that are common to most metric-aggregation
|
|
// types.
|
|
type BaseAggParams struct {
|
|
// Field is the name of the field to aggregate on.
|
|
Field string `structs:"field"`
|
|
// Miss is a value to provide for documents that are missing a value for the
|
|
// field.
|
|
Miss interface{} `structs:"missing,omitempty"`
|
|
}
|
|
|
|
func newBaseAgg(apiName, name, field string) *BaseAgg {
|
|
return &BaseAgg{
|
|
name: name,
|
|
apiName: apiName,
|
|
BaseAggParams: &BaseAggParams{
|
|
Field: field,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Name returns the name of the aggregation, allowing implementation of the
|
|
// Aggregation interface.
|
|
func (agg *BaseAgg) Name() string {
|
|
return agg.name
|
|
}
|
|
|
|
// Map returns a map representation of the aggregation, implementing the
|
|
// Mappable interface.
|
|
func (agg *BaseAgg) Map() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
agg.apiName: structs.Map(agg.BaseAggParams),
|
|
}
|
|
}
|
|
|
|
// AvgAgg represents an aggregation of type "avg", as described in
|
|
// https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-avg-aggregation.html
|
|
type AvgAgg struct {
|
|
*BaseAgg `structs:",flatten"`
|
|
}
|
|
|
|
// Avg creates an aggregation of type "avg", with the provided name and on the
|
|
// provided field.
|
|
func Avg(name, field string) *AvgAgg {
|
|
return &AvgAgg{
|
|
BaseAgg: newBaseAgg("avg", name, field),
|
|
}
|
|
}
|
|
|
|
// Missing sets the value to provide for documents missing a value for the
|
|
// selected field.
|
|
func (agg *AvgAgg) Missing(val interface{}) *AvgAgg {
|
|
agg.Miss = val
|
|
return agg
|
|
}
|
|
|
|
//----------------------------------------------------------------------------//
|
|
|
|
// WeightedAvgAgg represents an aggregation of type "weighted_avg", as described
|
|
// in https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-weight-avg-aggregation.html
|
|
type WeightedAvgAgg struct {
|
|
name string
|
|
apiName string
|
|
|
|
// Val is the value component of the aggregation
|
|
Val *BaseAggParams `structs:"value"`
|
|
|
|
// Weig is the weight component of the aggregation
|
|
Weig *BaseAggParams `structs:"weight"`
|
|
}
|
|
|
|
// WeightedAvg creates a new aggregation of type "weighted_agg" with the
|
|
// provided name.
|
|
func WeightedAvg(name string) *WeightedAvgAgg {
|
|
return &WeightedAvgAgg{
|
|
name: name,
|
|
apiName: "weighted_avg",
|
|
}
|
|
}
|
|
|
|
// Name returns the name of the aggregation.
|
|
func (agg *WeightedAvgAgg) Name() string {
|
|
return agg.name
|
|
}
|
|
|
|
// Value sets the value field and optionally a value to use when records are
|
|
// missing a value for the field.
|
|
func (agg *WeightedAvgAgg) Value(field string, missing ...interface{}) *WeightedAvgAgg {
|
|
agg.Val = new(BaseAggParams)
|
|
agg.Val.Field = field
|
|
if len(missing) > 0 {
|
|
agg.Val.Miss = missing[len(missing)-1]
|
|
}
|
|
return agg
|
|
}
|
|
|
|
// Value sets the weight field and optionally a value to use when records are
|
|
// missing a value for the field.
|
|
func (agg *WeightedAvgAgg) Weight(field string, missing ...interface{}) *WeightedAvgAgg {
|
|
agg.Weig = new(BaseAggParams)
|
|
agg.Weig.Field = field
|
|
if len(missing) > 0 {
|
|
agg.Weig.Miss = missing[len(missing)-1]
|
|
}
|
|
return agg
|
|
}
|
|
|
|
// Map returns a map representation of the aggregation, thus implementing the
|
|
// Mappable interface.
|
|
func (agg *WeightedAvgAgg) Map() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
agg.apiName: structs.Map(agg),
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------//
|
|
|
|
// CardinalityAgg represents an aggregation of type "cardinality", as described
|
|
// in https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-cardinality-aggregation.html
|
|
type CardinalityAgg struct {
|
|
*BaseAgg `structs:",flatten"`
|
|
|
|
// PrecisionThr is the precision threshold of the aggregation
|
|
PrecisionThr uint16 `structs:"precision_threshold,omitempty"`
|
|
}
|
|
|
|
// Cardinality creates a new aggregation of type "cardinality" with the provided
|
|
// name and on the provided field.
|
|
func Cardinality(name, field string) *CardinalityAgg {
|
|
return &CardinalityAgg{
|
|
BaseAgg: newBaseAgg("cardinality", name, field),
|
|
}
|
|
}
|
|
|
|
// Missing sets the value to provide for records that are missing a value for
|
|
// the field.
|
|
func (agg *CardinalityAgg) Missing(val interface{}) *CardinalityAgg {
|
|
agg.Miss = val
|
|
return agg
|
|
}
|
|
|
|
// PrecisionThreshold sets the precision threshold of the aggregation.
|
|
func (agg *CardinalityAgg) PrecisionThreshold(val uint16) *CardinalityAgg {
|
|
agg.PrecisionThr = val
|
|
return agg
|
|
}
|
|
|
|
// Map returns a map representation of the aggregation, thus implementing the
|
|
// Mappable interface
|
|
func (agg *CardinalityAgg) Map() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
agg.apiName: structs.Map(agg),
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------//
|
|
|
|
// MaxAgg represents an aggregation of type "max", as described in:
|
|
// https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-max-aggregation.html
|
|
type MaxAgg struct {
|
|
*BaseAgg `structs:",flatten"`
|
|
}
|
|
|
|
// Max creates a new aggregation of type "max", with the provided name and on
|
|
// the provided field.
|
|
func Max(name, field string) *MaxAgg {
|
|
return &MaxAgg{
|
|
BaseAgg: newBaseAgg("max", name, field),
|
|
}
|
|
}
|
|
|
|
// Missing sets the value to provide for records that are missing a value for
|
|
// the field.
|
|
func (agg *MaxAgg) Missing(val interface{}) *MaxAgg {
|
|
agg.Miss = val
|
|
return agg
|
|
}
|
|
|
|
//----------------------------------------------------------------------------//
|
|
|
|
// MinAgg represents an aggregation of type "min", as described in:
|
|
// https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-min-aggregation.html
|
|
type MinAgg struct {
|
|
*BaseAgg `structs:",flatten"`
|
|
}
|
|
|
|
// Min creates a new aggregation of type "min", with the provided name and on
|
|
// the provided field.
|
|
func Min(name, field string) *MinAgg {
|
|
return &MinAgg{
|
|
BaseAgg: newBaseAgg("min", name, field),
|
|
}
|
|
}
|
|
|
|
// Missing sets the value to provide for records that are missing a value for
|
|
// the field.
|
|
func (agg *MinAgg) Missing(val interface{}) *MinAgg {
|
|
agg.Miss = val
|
|
return agg
|
|
}
|
|
|
|
//----------------------------------------------------------------------------//
|
|
|
|
// SumAgg represents an aggregation of type "sum", as described in:
|
|
// https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-sum-aggregation.html
|
|
type SumAgg struct {
|
|
*BaseAgg `structs:",flatten"`
|
|
}
|
|
|
|
// Sum creates a new aggregation of type "sum", with the provided name and on
|
|
// the provided field.
|
|
func Sum(name, field string) *SumAgg {
|
|
return &SumAgg{
|
|
BaseAgg: newBaseAgg("sum", name, field),
|
|
}
|
|
}
|
|
|
|
// Missing sets the value to provide for records that are missing a value for
|
|
// the field.
|
|
func (agg *SumAgg) Missing(val interface{}) *SumAgg {
|
|
agg.Miss = val
|
|
return agg
|
|
}
|
|
|
|
//----------------------------------------------------------------------------//
|
|
|
|
// ValueCountAgg represents an aggregation of type "value_count", as described
|
|
// in https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-valuecount-aggregation.html
|
|
type ValueCountAgg struct {
|
|
*BaseAgg `structs:",flatten"`
|
|
}
|
|
|
|
// ValueCount creates a new aggregation of type "value_count", with the provided
|
|
// name and on the provided field
|
|
func ValueCount(name, field string) *ValueCountAgg {
|
|
return &ValueCountAgg{
|
|
BaseAgg: newBaseAgg("value_count", name, field),
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------//
|
|
|
|
// PercentilesAgg represents an aggregation of type "percentiles", as described
|
|
// in https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-percentile-aggregation.html
|
|
type PercentilesAgg struct {
|
|
*BaseAgg `structs:",flatten"`
|
|
|
|
// Prcnts is the aggregation's percentages
|
|
Prcnts []float32 `structs:"percents,omitempty"`
|
|
|
|
// Key denotes whether the aggregation is keyed or not
|
|
Key *bool `structs:"keyed,omitempty"`
|
|
|
|
// TDigest includes options for the TDigest algorithm
|
|
TDigest struct {
|
|
// Compression is the compression level to use
|
|
Compression uint16 `structs:"compression,omitempty"`
|
|
} `structs:"tdigest,omitempty"`
|
|
|
|
// HDR includes options for the HDR implementation
|
|
HDR struct {
|
|
// NumHistogramDigits defines the resolution of values for the histogram
|
|
// in number of significant digits
|
|
NumHistogramDigits uint8 `structs:"number_of_significant_value_digits,omitempty"`
|
|
} `structs:"hdr,omitempty"`
|
|
}
|
|
|
|
// Percentiles creates a new aggregation of type "percentiles" with the provided
|
|
// name and on the provided field.
|
|
func Percentiles(name, field string) *PercentilesAgg {
|
|
return &PercentilesAgg{
|
|
BaseAgg: newBaseAgg("percentiles", name, field),
|
|
}
|
|
}
|
|
|
|
// Percents sets the aggregation's percentages
|
|
func (agg *PercentilesAgg) Percents(percents ...float32) *PercentilesAgg {
|
|
agg.Prcnts = percents
|
|
return agg
|
|
}
|
|
|
|
// Missing sets the value to provide for records that are missing a value for
|
|
// the field.
|
|
func (agg *PercentilesAgg) Missing(val interface{}) *PercentilesAgg {
|
|
agg.Miss = val
|
|
return agg
|
|
}
|
|
|
|
// Keyed sets whether the aggregate is keyed or not.
|
|
func (agg *PercentilesAgg) Keyed(b bool) *PercentilesAgg {
|
|
agg.Key = &b
|
|
return agg
|
|
}
|
|
|
|
// Compression sets the compression level for the aggregation.
|
|
func (agg *PercentilesAgg) Compression(val uint16) *PercentilesAgg {
|
|
agg.TDigest.Compression = val
|
|
return agg
|
|
}
|
|
|
|
// NumHistogramDigits specifies the resolution of values for the histogram in
|
|
// number of significant digits.
|
|
func (agg *PercentilesAgg) NumHistogramDigits(val uint8) *PercentilesAgg {
|
|
agg.HDR.NumHistogramDigits = val
|
|
return agg
|
|
}
|
|
|
|
// Map returns a map representation of the aggregation, thus implementing the
|
|
// Mappable interface.
|
|
func (agg *PercentilesAgg) Map() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
agg.apiName: structs.Map(agg),
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------//
|
|
|
|
// StatsAgg represents an aggregation of type "stats", as described in:
|
|
// https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-stats-aggregation.html
|
|
type StatsAgg struct {
|
|
*BaseAgg `structs:",flatten"`
|
|
}
|
|
|
|
// Stats creates a new "stats" aggregation with the provided name and on the
|
|
// provided field.
|
|
func Stats(name, field string) *StatsAgg {
|
|
return &StatsAgg{
|
|
BaseAgg: newBaseAgg("stats", name, field),
|
|
}
|
|
}
|
|
|
|
// Missing sets the value to provide for records missing a value for the field.
|
|
func (agg *StatsAgg) Missing(val interface{}) *StatsAgg {
|
|
agg.Miss = val
|
|
return agg
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------//
|
|
|
|
// StringStatsAgg represents an aggregation of type "string_stats", as described
|
|
// in https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-string-stats-aggregation.html
|
|
type StringStatsAgg struct {
|
|
*BaseAgg `structs:",flatten"`
|
|
|
|
// ShowDist indicates whether to ask ElasticSearch to return a probability
|
|
// distribution for all characters
|
|
ShowDist *bool `structs:"show_distribution,omitempty"`
|
|
}
|
|
|
|
// StringStats creates a new "string_stats" aggregation with the provided name
|
|
// and on the provided field.
|
|
func StringStats(name, field string) *StringStatsAgg {
|
|
return &StringStatsAgg{
|
|
BaseAgg: newBaseAgg("string_stats", name, field),
|
|
}
|
|
}
|
|
|
|
// Missing sets the value to provide for records missing a value for the field.
|
|
func (agg *StringStatsAgg) Missing(val interface{}) *StringStatsAgg {
|
|
agg.Miss = val
|
|
return agg
|
|
}
|
|
|
|
// ShowDistribution sets whether to show the probability distribution for all
|
|
// characters
|
|
func (agg *StringStatsAgg) ShowDistribution(b bool) *StringStatsAgg {
|
|
agg.ShowDist = &b
|
|
return agg
|
|
}
|
|
|
|
// Map returns a map representation of the aggregation, thus implementing the
|
|
// Mappable interface.
|
|
func (agg *StringStatsAgg) Map() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
agg.apiName: structs.Map(agg),
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------//
|
|
|
|
// TopHitsAgg represents an aggregation of type "top_hits", as described
|
|
// in https://www.elastic.co/guide/en/elasticsearch/reference/
|
|
// current/search-aggregations-metrics-top-hits-aggregation.html
|
|
type TopHitsAgg struct {
|
|
name string
|
|
from uint64
|
|
size uint64
|
|
sort []map[string]interface{}
|
|
source Source
|
|
}
|
|
|
|
// TopHits creates an aggregation of type "top_hits".
|
|
func TopHits(name string) *TopHitsAgg {
|
|
return &TopHitsAgg{
|
|
name: name,
|
|
}
|
|
}
|
|
|
|
// Name returns the name of the aggregation.
|
|
func (agg *TopHitsAgg) Name() string {
|
|
return agg.name
|
|
}
|
|
|
|
// From sets an offset from the first result to return.
|
|
func (agg *TopHitsAgg) From(offset uint64) *TopHitsAgg {
|
|
agg.from = offset
|
|
return agg
|
|
}
|
|
|
|
// Size sets the maximum number of top matching hits to return per bucket (the
|
|
// default is 3).
|
|
func (agg *TopHitsAgg) Size(size uint64) *TopHitsAgg {
|
|
agg.size = size
|
|
return agg
|
|
}
|
|
|
|
// Sort sets how the top matching hits should be sorted. By default the hits are
|
|
// sorted by the score of the main query.
|
|
func (agg *TopHitsAgg) Sort(name string, order Order) *TopHitsAgg {
|
|
agg.sort = append(agg.sort, map[string]interface{}{
|
|
name: map[string]interface{}{
|
|
"order": order,
|
|
},
|
|
})
|
|
|
|
return agg
|
|
}
|
|
|
|
// SourceIncludes sets the keys to return from the top matching documents.
|
|
func (agg *TopHitsAgg) SourceIncludes(keys ...string) *TopHitsAgg {
|
|
agg.source.includes = keys
|
|
return agg
|
|
}
|
|
|
|
// Map returns a map representation of the aggregation, thus implementing the
|
|
// Mappable interface.
|
|
func (agg *TopHitsAgg) Map() map[string]interface{} {
|
|
innerMap := make(map[string]interface{})
|
|
|
|
if agg.from > 0 {
|
|
innerMap["from"] = agg.from
|
|
}
|
|
if agg.size > 0 {
|
|
innerMap["size"] = agg.size
|
|
}
|
|
if len(agg.sort) > 0 {
|
|
innerMap["sort"] = agg.sort
|
|
}
|
|
if len(agg.source.includes) > 0 {
|
|
innerMap["_source"] = agg.source.Map()
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"top_hits": innerMap,
|
|
}
|
|
}
|