Skip to content

Commit

Permalink
Merge branch 'add-multi_terms-support' of git://github.com/lechnertec…
Browse files Browse the repository at this point in the history
…h/elastic into lechnertech-add-multi_terms-support
  • Loading branch information
olivere committed Jun 16, 2021
2 parents 001ee9c + d231f2e commit 299cc78
Show file tree
Hide file tree
Showing 2 changed files with 417 additions and 0 deletions.
313 changes: 313 additions & 0 deletions search_aggs_bucket_multi_terms.go
@@ -0,0 +1,313 @@
// 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"

// A multi-bucket value source based aggregation where buckets are dynamically built - one per
// unique set of values. The multi terms aggregation is very similar to the terms aggregation,
// however in most cases it will be slower than the terms aggregation and will consume more
// memory. Therefore, if the same set of fields is constantly used, it would be more efficient to
// index a combined key for this fields as a separate field and use the terms aggregation on this field.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.12/search-aggregations-bucket-multi-terms-aggregation.html
type MultiTermsAggregation struct {
multiTerms []*MultiTerm
subAggregations map[string]Aggregation
meta map[string]interface{}

size *int
shardSize *int
minDocCount *int
shardMinDocCount *int
collectionMode string
showTermDocCountError *bool
order []MultiTermsOrder
}

func NewMultiTermsAggregation() *MultiTermsAggregation {
return &MultiTermsAggregation{
subAggregations: make(map[string]Aggregation),
}
}

func (a *MultiTermsAggregation) Terms(multiTerm ...*MultiTerm) *MultiTermsAggregation {
a.multiTerms = multiTerm
return a
}

func (a *MultiTermsAggregation) SubAggregation(name string, subAggregation Aggregation) *MultiTermsAggregation {
a.subAggregations[name] = subAggregation
return a
}

// Meta sets the meta data to be included in the aggregation response.
func (a *MultiTermsAggregation) Meta(metaData map[string]interface{}) *MultiTermsAggregation {
a.meta = metaData
return a
}

func (a *MultiTermsAggregation) Size(size int) *MultiTermsAggregation {
a.size = &size
return a
}

func (a *MultiTermsAggregation) ShardSize(shardSize int) *MultiTermsAggregation {
a.shardSize = &shardSize
return a
}

func (a *MultiTermsAggregation) MinDocCount(minDocCount int) *MultiTermsAggregation {
a.minDocCount = &minDocCount
return a
}

func (a *MultiTermsAggregation) ShardMinDocCount(shardMinDocCount int) *MultiTermsAggregation {
a.shardMinDocCount = &shardMinDocCount
return a
}

func (a *MultiTermsAggregation) Order(order string, asc bool) *MultiTermsAggregation {
a.order = append(a.order, MultiTermsOrder{Field: order, Ascending: asc})
return a
}

func (a *MultiTermsAggregation) OrderByCount(asc bool) *MultiTermsAggregation {
// "order" : { "_count" : "asc" }
a.order = append(a.order, MultiTermsOrder{Field: "_count", Ascending: asc})
return a
}

func (a *MultiTermsAggregation) OrderByCountAsc() *MultiTermsAggregation {
return a.OrderByCount(true)
}

func (a *MultiTermsAggregation) OrderByCountDesc() *MultiTermsAggregation {
return a.OrderByCount(false)
}

func (a *MultiTermsAggregation) OrderByKey(asc bool) *MultiTermsAggregation {
// "order" : { "_term" : "asc" }
a.order = append(a.order, MultiTermsOrder{Field: "_key", Ascending: asc})
return a
}

func (a *MultiTermsAggregation) OrderByKeyAsc() *MultiTermsAggregation {
return a.OrderByKey(true)
}

func (a *MultiTermsAggregation) OrderByKeyDesc() *MultiTermsAggregation {
return a.OrderByKey(false)
}

// OrderByAggregation creates a bucket ordering strategy which sorts buckets
// based on a single-valued calc get.
func (a *MultiTermsAggregation) OrderByAggregation(aggName string, asc bool) *MultiTermsAggregation {
// {
// "aggs": {
// "genres_and_products": {
// "multi_terms": {
// "terms": [
// {
// "field": "genre"
// },
// {
// "field": "product"
// }
// ],
// "order": {
// "total_quantity": "desc"
// }
// },
// "aggs": {
// "total_quantity": {
// "sum": {
// "field": "quantity"
// }
// }
// }
// }
// }
// }
a.order = append(a.order, MultiTermsOrder{Field: aggName, Ascending: asc})
return a
}

// OrderByAggregationAndMetric creates a bucket ordering strategy which
// sorts buckets based on a multi-valued calc get.
func (a *MultiTermsAggregation) OrderByAggregationAndMetric(aggName, metric string, asc bool) *MultiTermsAggregation {
// {
// "aggs": {
// "genres_and_products": {
// "multi_terms": {
// "terms": [
// {
// "field": "genre"
// },
// {
// "field": "product"
// }
// ],
// "order": {
// "total_quantity": "desc"
// }
// },
// "aggs": {
// "total_quantity": {
// "sum": {
// "field": "quantity"
// }
// }
// }
// }
// }
// }
a.order = append(a.order, MultiTermsOrder{Field: aggName + "." + metric, Ascending: asc})
return a
}

// Collection mode can be depth_first or breadth_first as of 1.4.0.
func (a *MultiTermsAggregation) CollectionMode(collectionMode string) *MultiTermsAggregation {
a.collectionMode = collectionMode
return a
}

func (a *MultiTermsAggregation) ShowTermDocCountError(showTermDocCountError bool) *MultiTermsAggregation {
a.showTermDocCountError = &showTermDocCountError
return a
}

func (a *MultiTermsAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs": {
// "genres_and_products": {
// "multi_terms": {
// "terms": [
// {
// "field": "genre"
// },
// {
// "field": "product"
// }
// ]
// }
// }
// }
// }
// This method returns only the "multi_terms": { "terms": [ { "field": "genre" }, { "field": "product" } ] } part.

source := make(map[string]interface{})
opts := make(map[string]interface{})
source["multi_terms"] = opts

// ValuesSourceAggregationBuilder
terms := make([]interface{}, len(a.multiTerms))
for i := range a.multiTerms {
if a.multiTerms[i] == nil {
return nil, fmt.Errorf("expected a multiterm but found a nil multiterm")
}
s, err := a.multiTerms[i].Source()
if err != nil {
return nil, err
}
terms[i] = s
}
opts["terms"] = terms

// TermsBuilder
if a.size != nil && *a.size >= 0 {
opts["size"] = *a.size
}
if a.shardSize != nil && *a.shardSize >= 0 {
opts["shard_size"] = *a.shardSize
}
if a.minDocCount != nil && *a.minDocCount >= 0 {
opts["min_doc_count"] = *a.minDocCount
}
if a.shardMinDocCount != nil && *a.shardMinDocCount >= 0 {
opts["shard_min_doc_count"] = *a.shardMinDocCount
}
if a.showTermDocCountError != nil {
opts["show_term_doc_count_error"] = *a.showTermDocCountError
}
if a.collectionMode != "" {
opts["collect_mode"] = a.collectionMode
}
if len(a.order) > 0 {
var orderSlice []interface{}
for _, order := range a.order {
src, err := order.Source()
if err != nil {
return nil, err
}
orderSlice = append(orderSlice, src)
}
opts["order"] = orderSlice
}

// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}

// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}

return source, nil
}

// MultiTermsOrder specifies a single order field for a multi terms aggregation.
type MultiTermsOrder struct {
Field string
Ascending bool
}

// Source returns serializable JSON of the MultiTermsOrder.
func (order *MultiTermsOrder) Source() (interface{}, error) {
source := make(map[string]string)
if order.Ascending {
source[order.Field] = "asc"
} else {
source[order.Field] = "desc"
}
return source, nil
}

// MultiTerm specifies a single term field for a multi terms aggregation.
type MultiTerm struct {
field string
missing interface{}
}

// Source returns serializable JSON of the MultiTerm.
func (term *MultiTerm) Source() (interface{}, error) {
source := make(map[string]interface{})
source["field"] = term.field
if term.missing != nil {
source["missing"] = term.missing
}
return source, nil
}

// Missing configures the value to use when document miss a value
func (term *MultiTerm) Missing(missing interface{}) *MultiTerm {
term.missing = missing
return term
}

func NewMultiTerm(field string) *MultiTerm {
return &MultiTerm{field: field}
}

0 comments on commit 299cc78

Please sign in to comment.