diff --git a/pkg/es/search_aggs_metrics_top_hits.go b/pkg/es/search_aggs_metrics_top_hits.go new file mode 100644 index 0000000..010d149 --- /dev/null +++ b/pkg/es/search_aggs_metrics_top_hits.go @@ -0,0 +1,83 @@ +// 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 es + +import "errors" + +// TopMetricsAggregation selects metrics from the document with the largest or smallest "sort" value. +// top_metrics is fairly similar to top_hits in spirit but because it is more limited it is able to do +// its job using less memory and is often faster. +// +// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-top-metrics.html +type TopMetricsAggregation struct { + fields []string + sorter Sorter + size int +} + +func NewTopMetricsAggregation() *TopMetricsAggregation { + return &TopMetricsAggregation{} +} + +// Field adds a field to run aggregation against. +func (a *TopMetricsAggregation) Field(field string) *TopMetricsAggregation { + a.fields = append(a.fields, field) + return a +} + +// Sort adds a sort order. +func (a *TopMetricsAggregation) Sort(field string, ascending bool) *TopMetricsAggregation { + a.sorter = SortInfo{Field: field, Ascending: ascending} + return a +} + +// SortWithInfo adds a sort order. +func (a *TopMetricsAggregation) SortWithInfo(info SortInfo) *TopMetricsAggregation { + a.sorter = info + return a +} + +// SortBy adds a sort order. +func (a *TopMetricsAggregation) SortBy(sorter Sorter) *TopMetricsAggregation { + a.sorter = sorter + return a +} + +// Size sets the number of top documents returned by the aggregation. The default size is 1. +func (a *TopMetricsAggregation) Size(size int) *TopMetricsAggregation { + a.size = size + return a +} + +func (a *TopMetricsAggregation) Source() (interface{}, error) { + params := make(map[string]interface{}) + + if len(a.fields) == 0 { + return nil, errors.New("field list is required for the top metrics aggregation") + } + metrics := make([]interface{}, len(a.fields)) + for idx, field := range a.fields { + metrics[idx] = map[string]string{"field": field} + } + params["metrics"] = metrics + + if a.sorter == nil { + return nil, errors.New("sorter is required for the top metrics aggregation") + } + sortSource, err := a.sorter.Source() + if err != nil { + return nil, err + } + params["sort"] = sortSource + + if a.size > 1 { + params["size"] = a.size + } + + source := map[string]interface{}{ + "top_metrics": params, + } + return source, nil +} diff --git a/pkg/es/sort.go b/pkg/es/sort.go new file mode 100644 index 0000000..0d2e6c0 --- /dev/null +++ b/pkg/es/sort.go @@ -0,0 +1,486 @@ +// 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 es + +import "errors" + +// -- Sorter -- + +// Sorter is an interface for sorting strategies, e.g. ScoreSort or FieldSort. +// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-sort.html. +type Sorter interface { + Source() (interface{}, error) +} + +// -- SortInfo -- + +// SortInfo contains information about sorting a field. +type SortInfo struct { + Sorter + Field string + Ascending bool + Missing interface{} + IgnoreUnmapped *bool + UnmappedType string + SortMode string + NestedFilter Query // deprecated in 6.1 and replaced by Filter + Filter Query + NestedPath string // deprecated in 6.1 and replaced by Path + Path string + NestedSort *NestedSort // deprecated in 6.1 and replaced by Nested + Nested *NestedSort +} + +func (info SortInfo) Source() (interface{}, error) { + prop := make(map[string]interface{}) + if info.Ascending { + prop["order"] = "asc" + } else { + prop["order"] = "desc" + } + if info.Missing != nil { + prop["missing"] = info.Missing + } + if info.IgnoreUnmapped != nil { + prop["ignore_unmapped"] = *info.IgnoreUnmapped + } + if info.UnmappedType != "" { + prop["unmapped_type"] = info.UnmappedType + } + if info.SortMode != "" { + prop["mode"] = info.SortMode + } + if info.Filter != nil { + src, err := info.Filter.Source() + if err != nil { + return nil, err + } + prop["filter"] = src + } else if info.NestedFilter != nil { + src, err := info.NestedFilter.Source() + if err != nil { + return nil, err + } + prop["nested_filter"] = src // deprecated in 6.1 + } + if info.Path != "" { + prop["path"] = info.Path + } else if info.NestedPath != "" { + prop["nested_path"] = info.NestedPath // deprecated in 6.1 + } + if info.Nested != nil { + src, err := info.Nested.Source() + if err != nil { + return nil, err + } + prop["nested"] = src + } else if info.NestedSort != nil { + src, err := info.NestedSort.Source() + if err != nil { + return nil, err + } + prop["nested"] = src + } + source := make(map[string]interface{}) + source[info.Field] = prop + return source, nil +} + +// -- SortByDoc -- + +// SortByDoc sorts by the "_doc" field, as described in +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html. +// +// Example: +// +// ss := elastic.NewSearchSource() +// ss = ss.SortBy(elastic.SortByDoc{}) +type SortByDoc struct { + Sorter +} + +// Source returns the JSON-serializable data. +func (s SortByDoc) Source() (interface{}, error) { + return "_doc", nil +} + +// -- ScoreSort -- + +// ScoreSort sorts by relevancy score. +type ScoreSort struct { + Sorter + ascending bool +} + +// NewScoreSort creates a new ScoreSort. +func NewScoreSort() *ScoreSort { + return &ScoreSort{ascending: false} // Descending by default! +} + +// Order defines whether sorting ascending (default) or descending. +func (s *ScoreSort) Order(ascending bool) *ScoreSort { + s.ascending = ascending + return s +} + +// Asc sets ascending sort order. +func (s *ScoreSort) Asc() *ScoreSort { + s.ascending = true + return s +} + +// Desc sets descending sort order. +func (s *ScoreSort) Desc() *ScoreSort { + s.ascending = false + return s +} + +// Source returns the JSON-serializable data. +func (s *ScoreSort) Source() (interface{}, error) { + source := make(map[string]interface{}) + x := make(map[string]interface{}) + source["_score"] = x + if s.ascending { + x["order"] = "asc" + } else { + x["order"] = "desc" + } + return source, nil +} + +// -- FieldSort -- + +// FieldSort sorts by a given field. +type FieldSort struct { + Sorter + fieldName string + ascending bool + missing interface{} + unmappedType *string + sortMode *string + filter Query + path *string + nested *NestedSort +} + +// NewFieldSort creates a new FieldSort. +func NewFieldSort(fieldName string) *FieldSort { + return &FieldSort{ + fieldName: fieldName, + ascending: true, + } +} + +// FieldName specifies the name of the field to be used for sorting. +func (s *FieldSort) FieldName(fieldName string) *FieldSort { + s.fieldName = fieldName + return s +} + +// Order defines whether sorting ascending (default) or descending. +func (s *FieldSort) Order(ascending bool) *FieldSort { + s.ascending = ascending + return s +} + +// Asc sets ascending sort order. +func (s *FieldSort) Asc() *FieldSort { + s.ascending = true + return s +} + +// Desc sets descending sort order. +func (s *FieldSort) Desc() *FieldSort { + s.ascending = false + return s +} + +// Missing sets the value to be used when a field is missing in a document. +// You can also use "_last" or "_first" to sort missing last or first +// respectively. +func (s *FieldSort) Missing(missing interface{}) *FieldSort { + s.missing = missing + return s +} + +// UnmappedType sets the type to use when the current field is not mapped +// in an index. +func (s *FieldSort) UnmappedType(typ string) *FieldSort { + s.unmappedType = &typ + return s +} + +// SortMode specifies what values to pick in case a document contains +// multiple values for the targeted sort field. Possible values are: +// min, max, sum, and avg. +func (s *FieldSort) SortMode(sortMode string) *FieldSort { + s.sortMode = &sortMode + return s +} + +// NestedFilter sets a filter that nested objects should match with +// in order to be taken into account for sorting. +// Deprecated: Use Filter instead. +func (s *FieldSort) NestedFilter(nestedFilter Query) *FieldSort { + s.filter = nestedFilter + return s +} + +// Filter sets a filter that nested objects should match with +// in order to be taken into account for sorting. +func (s *FieldSort) Filter(filter Query) *FieldSort { + s.filter = filter + return s +} + +// NestedPath is used if sorting occurs on a field that is inside a +// nested object. +// Deprecated: Use Path instead. +func (s *FieldSort) NestedPath(nestedPath string) *FieldSort { + s.path = &nestedPath + return s +} + +// Path is used if sorting occurs on a field that is inside a +// nested object. +func (s *FieldSort) Path(path string) *FieldSort { + s.path = &path + return s +} + +// NestedSort is available starting with 6.1 and will replace NestedFilter +// and NestedPath. +// Deprecated: Use Nested instead. +func (s *FieldSort) NestedSort(nestedSort *NestedSort) *FieldSort { + s.nested = nestedSort + return s +} + +// Nested is available starting with 6.1 and will replace Filter and Path. +func (s *FieldSort) Nested(nested *NestedSort) *FieldSort { + s.nested = nested + return s +} + +// Source returns the JSON-serializable data. +func (s *FieldSort) Source() (interface{}, error) { + source := make(map[string]interface{}) + x := make(map[string]interface{}) + source[s.fieldName] = x + if s.ascending { + x["order"] = "asc" + } else { + x["order"] = "desc" + } + if s.missing != nil { + x["missing"] = s.missing + } + if s.unmappedType != nil { + x["unmapped_type"] = *s.unmappedType + } + if s.sortMode != nil { + x["mode"] = *s.sortMode + } + if s.filter != nil { + src, err := s.filter.Source() + if err != nil { + return nil, err + } + x["filter"] = src + } + if s.path != nil { + x["path"] = *s.path + } + if s.nested != nil { + src, err := s.nested.Source() + if err != nil { + return nil, err + } + x["nested"] = src + } + return source, nil +} + +// -- ScriptSort -- + +// ScriptSort sorts by a custom script. See +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html#modules-scripting +// for details about scripting. +type ScriptSort struct { + Sorter + script *Script + typ string + ascending bool + sortMode *string + nestedFilter Query + nestedPath *string + nestedSort *NestedSort +} + +// NewScriptSort creates and initializes a new ScriptSort. +// You must provide a script and a type, e.g. "string" or "number". +func NewScriptSort(script *Script, typ string) *ScriptSort { + return &ScriptSort{ + script: script, + typ: typ, + ascending: true, + } +} + +// Type sets the script type, which can be either "string" or "number". +func (s *ScriptSort) Type(typ string) *ScriptSort { + s.typ = typ + return s +} + +// Order defines whether sorting ascending (default) or descending. +func (s *ScriptSort) Order(ascending bool) *ScriptSort { + s.ascending = ascending + return s +} + +// Asc sets ascending sort order. +func (s *ScriptSort) Asc() *ScriptSort { + s.ascending = true + return s +} + +// Desc sets descending sort order. +func (s *ScriptSort) Desc() *ScriptSort { + s.ascending = false + return s +} + +// SortMode specifies what values to pick in case a document contains +// multiple values for the targeted sort field. Possible values are: +// min or max. +func (s *ScriptSort) SortMode(sortMode string) *ScriptSort { + s.sortMode = &sortMode + return s +} + +// NestedFilter sets a filter that nested objects should match with +// in order to be taken into account for sorting. +func (s *ScriptSort) NestedFilter(nestedFilter Query) *ScriptSort { + s.nestedFilter = nestedFilter + return s +} + +// NestedPath is used if sorting occurs on a field that is inside a +// nested object. +func (s *ScriptSort) NestedPath(nestedPath string) *ScriptSort { + s.nestedPath = &nestedPath + return s +} + +// NestedSort is available starting with 6.1 and will replace NestedFilter +// and NestedPath. +func (s *ScriptSort) NestedSort(nestedSort *NestedSort) *ScriptSort { + s.nestedSort = nestedSort + return s +} + +// Source returns the JSON-serializable data. +func (s *ScriptSort) Source() (interface{}, error) { + if s.script == nil { + return nil, errors.New("ScriptSort expected a script") + } + source := make(map[string]interface{}) + x := make(map[string]interface{}) + source["_script"] = x + + src, err := s.script.Source() + if err != nil { + return nil, err + } + x["script"] = src + + x["type"] = s.typ + + if s.ascending { + x["order"] = "asc" + } else { + x["order"] = "desc" + } + if s.sortMode != nil { + x["mode"] = *s.sortMode + } + if s.nestedFilter != nil { + src, err := s.nestedFilter.Source() + if err != nil { + return nil, err + } + x["nested_filter"] = src + } + if s.nestedPath != nil { + x["nested_path"] = *s.nestedPath + } + if s.nestedSort != nil { + src, err := s.nestedSort.Source() + if err != nil { + return nil, err + } + x["nested"] = src + } + return source, nil +} + +// -- NestedSort -- + +// NestedSort is used for fields that are inside a nested object. +// It takes a "path" argument and an optional nested filter that the +// nested objects should match with in order to be taken into account +// for sorting. +// +// NestedSort is available from 6.1 and replaces nestedFilter and nestedPath +// in the other sorters. +type NestedSort struct { + Sorter + path string + filter Query + nestedSort *NestedSort +} + +// NewNestedSort creates a new NestedSort. +func NewNestedSort(path string) *NestedSort { + return &NestedSort{path: path} +} + +// Filter sets the filter. +func (s *NestedSort) Filter(filter Query) *NestedSort { + s.filter = filter + return s +} + +// NestedSort embeds another level of nested sorting. +func (s *NestedSort) NestedSort(nestedSort *NestedSort) *NestedSort { + s.nestedSort = nestedSort + return s +} + +// Source returns the JSON-serializable data. +func (s *NestedSort) Source() (interface{}, error) { + source := make(map[string]interface{}) + + if s.path != "" { + source["path"] = s.path + } + if s.filter != nil { + src, err := s.filter.Source() + if err != nil { + return nil, err + } + source["filter"] = src + } + if s.nestedSort != nil { + src, err := s.nestedSort.Source() + if err != nil { + return nil, err + } + source["nested"] = src + } + + return source, nil +}