esquery/aggs_metric.go
Ido Perlmuter 8e5dea816b
Add Search() function, two new aggregations (#7)
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.
2020-04-06 12:03:42 +03:00

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,
}
}