Skip to content

Commit 299cc78

Browse files
committed
Merge branch 'add-multi_terms-support' of git://github.com/lechnertech/elastic into lechnertech-add-multi_terms-support
2 parents 001ee9c + d231f2e commit 299cc78

File tree

2 files changed

+417
-0
lines changed

2 files changed

+417
-0
lines changed

search_aggs_bucket_multi_terms.go

+313
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
// Copyright 2012-present Oliver Eilhard. All rights reserved.
2+
// Use of this source code is governed by a MIT-license.
3+
// See http://olivere.mit-license.org/license.txt for details.
4+
5+
package elastic
6+
7+
import "fmt"
8+
9+
// A multi-bucket value source based aggregation where buckets are dynamically built - one per
10+
// unique set of values. The multi terms aggregation is very similar to the terms aggregation,
11+
// however in most cases it will be slower than the terms aggregation and will consume more
12+
// memory. Therefore, if the same set of fields is constantly used, it would be more efficient to
13+
// index a combined key for this fields as a separate field and use the terms aggregation on this field.
14+
//
15+
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.12/search-aggregations-bucket-multi-terms-aggregation.html
16+
type MultiTermsAggregation struct {
17+
multiTerms []*MultiTerm
18+
subAggregations map[string]Aggregation
19+
meta map[string]interface{}
20+
21+
size *int
22+
shardSize *int
23+
minDocCount *int
24+
shardMinDocCount *int
25+
collectionMode string
26+
showTermDocCountError *bool
27+
order []MultiTermsOrder
28+
}
29+
30+
func NewMultiTermsAggregation() *MultiTermsAggregation {
31+
return &MultiTermsAggregation{
32+
subAggregations: make(map[string]Aggregation),
33+
}
34+
}
35+
36+
func (a *MultiTermsAggregation) Terms(multiTerm ...*MultiTerm) *MultiTermsAggregation {
37+
a.multiTerms = multiTerm
38+
return a
39+
}
40+
41+
func (a *MultiTermsAggregation) SubAggregation(name string, subAggregation Aggregation) *MultiTermsAggregation {
42+
a.subAggregations[name] = subAggregation
43+
return a
44+
}
45+
46+
// Meta sets the meta data to be included in the aggregation response.
47+
func (a *MultiTermsAggregation) Meta(metaData map[string]interface{}) *MultiTermsAggregation {
48+
a.meta = metaData
49+
return a
50+
}
51+
52+
func (a *MultiTermsAggregation) Size(size int) *MultiTermsAggregation {
53+
a.size = &size
54+
return a
55+
}
56+
57+
func (a *MultiTermsAggregation) ShardSize(shardSize int) *MultiTermsAggregation {
58+
a.shardSize = &shardSize
59+
return a
60+
}
61+
62+
func (a *MultiTermsAggregation) MinDocCount(minDocCount int) *MultiTermsAggregation {
63+
a.minDocCount = &minDocCount
64+
return a
65+
}
66+
67+
func (a *MultiTermsAggregation) ShardMinDocCount(shardMinDocCount int) *MultiTermsAggregation {
68+
a.shardMinDocCount = &shardMinDocCount
69+
return a
70+
}
71+
72+
func (a *MultiTermsAggregation) Order(order string, asc bool) *MultiTermsAggregation {
73+
a.order = append(a.order, MultiTermsOrder{Field: order, Ascending: asc})
74+
return a
75+
}
76+
77+
func (a *MultiTermsAggregation) OrderByCount(asc bool) *MultiTermsAggregation {
78+
// "order" : { "_count" : "asc" }
79+
a.order = append(a.order, MultiTermsOrder{Field: "_count", Ascending: asc})
80+
return a
81+
}
82+
83+
func (a *MultiTermsAggregation) OrderByCountAsc() *MultiTermsAggregation {
84+
return a.OrderByCount(true)
85+
}
86+
87+
func (a *MultiTermsAggregation) OrderByCountDesc() *MultiTermsAggregation {
88+
return a.OrderByCount(false)
89+
}
90+
91+
func (a *MultiTermsAggregation) OrderByKey(asc bool) *MultiTermsAggregation {
92+
// "order" : { "_term" : "asc" }
93+
a.order = append(a.order, MultiTermsOrder{Field: "_key", Ascending: asc})
94+
return a
95+
}
96+
97+
func (a *MultiTermsAggregation) OrderByKeyAsc() *MultiTermsAggregation {
98+
return a.OrderByKey(true)
99+
}
100+
101+
func (a *MultiTermsAggregation) OrderByKeyDesc() *MultiTermsAggregation {
102+
return a.OrderByKey(false)
103+
}
104+
105+
// OrderByAggregation creates a bucket ordering strategy which sorts buckets
106+
// based on a single-valued calc get.
107+
func (a *MultiTermsAggregation) OrderByAggregation(aggName string, asc bool) *MultiTermsAggregation {
108+
// {
109+
// "aggs": {
110+
// "genres_and_products": {
111+
// "multi_terms": {
112+
// "terms": [
113+
// {
114+
// "field": "genre"
115+
// },
116+
// {
117+
// "field": "product"
118+
// }
119+
// ],
120+
// "order": {
121+
// "total_quantity": "desc"
122+
// }
123+
// },
124+
// "aggs": {
125+
// "total_quantity": {
126+
// "sum": {
127+
// "field": "quantity"
128+
// }
129+
// }
130+
// }
131+
// }
132+
// }
133+
// }
134+
a.order = append(a.order, MultiTermsOrder{Field: aggName, Ascending: asc})
135+
return a
136+
}
137+
138+
// OrderByAggregationAndMetric creates a bucket ordering strategy which
139+
// sorts buckets based on a multi-valued calc get.
140+
func (a *MultiTermsAggregation) OrderByAggregationAndMetric(aggName, metric string, asc bool) *MultiTermsAggregation {
141+
// {
142+
// "aggs": {
143+
// "genres_and_products": {
144+
// "multi_terms": {
145+
// "terms": [
146+
// {
147+
// "field": "genre"
148+
// },
149+
// {
150+
// "field": "product"
151+
// }
152+
// ],
153+
// "order": {
154+
// "total_quantity": "desc"
155+
// }
156+
// },
157+
// "aggs": {
158+
// "total_quantity": {
159+
// "sum": {
160+
// "field": "quantity"
161+
// }
162+
// }
163+
// }
164+
// }
165+
// }
166+
// }
167+
a.order = append(a.order, MultiTermsOrder{Field: aggName + "." + metric, Ascending: asc})
168+
return a
169+
}
170+
171+
// Collection mode can be depth_first or breadth_first as of 1.4.0.
172+
func (a *MultiTermsAggregation) CollectionMode(collectionMode string) *MultiTermsAggregation {
173+
a.collectionMode = collectionMode
174+
return a
175+
}
176+
177+
func (a *MultiTermsAggregation) ShowTermDocCountError(showTermDocCountError bool) *MultiTermsAggregation {
178+
a.showTermDocCountError = &showTermDocCountError
179+
return a
180+
}
181+
182+
func (a *MultiTermsAggregation) Source() (interface{}, error) {
183+
// Example:
184+
// {
185+
// "aggs": {
186+
// "genres_and_products": {
187+
// "multi_terms": {
188+
// "terms": [
189+
// {
190+
// "field": "genre"
191+
// },
192+
// {
193+
// "field": "product"
194+
// }
195+
// ]
196+
// }
197+
// }
198+
// }
199+
// }
200+
// This method returns only the "multi_terms": { "terms": [ { "field": "genre" }, { "field": "product" } ] } part.
201+
202+
source := make(map[string]interface{})
203+
opts := make(map[string]interface{})
204+
source["multi_terms"] = opts
205+
206+
// ValuesSourceAggregationBuilder
207+
terms := make([]interface{}, len(a.multiTerms))
208+
for i := range a.multiTerms {
209+
if a.multiTerms[i] == nil {
210+
return nil, fmt.Errorf("expected a multiterm but found a nil multiterm")
211+
}
212+
s, err := a.multiTerms[i].Source()
213+
if err != nil {
214+
return nil, err
215+
}
216+
terms[i] = s
217+
}
218+
opts["terms"] = terms
219+
220+
// TermsBuilder
221+
if a.size != nil && *a.size >= 0 {
222+
opts["size"] = *a.size
223+
}
224+
if a.shardSize != nil && *a.shardSize >= 0 {
225+
opts["shard_size"] = *a.shardSize
226+
}
227+
if a.minDocCount != nil && *a.minDocCount >= 0 {
228+
opts["min_doc_count"] = *a.minDocCount
229+
}
230+
if a.shardMinDocCount != nil && *a.shardMinDocCount >= 0 {
231+
opts["shard_min_doc_count"] = *a.shardMinDocCount
232+
}
233+
if a.showTermDocCountError != nil {
234+
opts["show_term_doc_count_error"] = *a.showTermDocCountError
235+
}
236+
if a.collectionMode != "" {
237+
opts["collect_mode"] = a.collectionMode
238+
}
239+
if len(a.order) > 0 {
240+
var orderSlice []interface{}
241+
for _, order := range a.order {
242+
src, err := order.Source()
243+
if err != nil {
244+
return nil, err
245+
}
246+
orderSlice = append(orderSlice, src)
247+
}
248+
opts["order"] = orderSlice
249+
}
250+
251+
// AggregationBuilder (SubAggregations)
252+
if len(a.subAggregations) > 0 {
253+
aggsMap := make(map[string]interface{})
254+
source["aggregations"] = aggsMap
255+
for name, aggregate := range a.subAggregations {
256+
src, err := aggregate.Source()
257+
if err != nil {
258+
return nil, err
259+
}
260+
aggsMap[name] = src
261+
}
262+
}
263+
264+
// Add Meta data if available
265+
if len(a.meta) > 0 {
266+
source["meta"] = a.meta
267+
}
268+
269+
return source, nil
270+
}
271+
272+
// MultiTermsOrder specifies a single order field for a multi terms aggregation.
273+
type MultiTermsOrder struct {
274+
Field string
275+
Ascending bool
276+
}
277+
278+
// Source returns serializable JSON of the MultiTermsOrder.
279+
func (order *MultiTermsOrder) Source() (interface{}, error) {
280+
source := make(map[string]string)
281+
if order.Ascending {
282+
source[order.Field] = "asc"
283+
} else {
284+
source[order.Field] = "desc"
285+
}
286+
return source, nil
287+
}
288+
289+
// MultiTerm specifies a single term field for a multi terms aggregation.
290+
type MultiTerm struct {
291+
field string
292+
missing interface{}
293+
}
294+
295+
// Source returns serializable JSON of the MultiTerm.
296+
func (term *MultiTerm) Source() (interface{}, error) {
297+
source := make(map[string]interface{})
298+
source["field"] = term.field
299+
if term.missing != nil {
300+
source["missing"] = term.missing
301+
}
302+
return source, nil
303+
}
304+
305+
// Missing configures the value to use when document miss a value
306+
func (term *MultiTerm) Missing(missing interface{}) *MultiTerm {
307+
term.missing = missing
308+
return term
309+
}
310+
311+
func NewMultiTerm(field string) *MultiTerm {
312+
return &MultiTerm{field: field}
313+
}

0 commit comments

Comments
 (0)