Skip to content

Commit 2bc7635

Browse files
authored
Add multi terms aggregation (#5372)
* Implement multi terms aggregation * Add skip version
1 parent 27ec801 commit 2bc7635

File tree

13 files changed

+519
-4
lines changed

13 files changed

+519
-4
lines changed

docs/aggregations.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ In addition to the buckets themselves, the bucket aggregations also compute and
168168

169169
* <<missing-aggregation-usage,Missing Aggregation Usage>>
170170

171+
* <<multi-terms-aggregation-usage,Multi Terms Aggregation Usage>>
172+
171173
* <<nested-aggregation-usage,Nested Aggregation Usage>>
172174

173175
* <<parent-aggregation-usage,Parent Aggregation Usage>>
@@ -231,6 +233,8 @@ include::aggregations/bucket/ip-range/ip-range-aggregation-usage.asciidoc[]
231233

232234
include::aggregations/bucket/missing/missing-aggregation-usage.asciidoc[]
233235

236+
include::aggregations/bucket/multi-terms/multi-terms-aggregation-usage.asciidoc[]
237+
234238
include::aggregations/bucket/nested/nested-aggregation-usage.asciidoc[]
235239

236240
include::aggregations/bucket/parent/parent-aggregation-usage.asciidoc[]
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/7.x
2+
3+
:github: https://github.com/elastic/elasticsearch-net
4+
5+
:nuget: https://www.nuget.org/packages
6+
7+
////
8+
IMPORTANT NOTE
9+
==============
10+
This file has been generated from https://github.com/elastic/elasticsearch-net/tree/7.x/src/Tests/Tests/Aggregations/Bucket/MultiTerms/MultiTermsAggregationUsageTests.cs.
11+
If you wish to submit a PR for any spelling mistakes, typos or grammatical errors for this file,
12+
please modify the original csharp file found at the link and submit the PR with that change. Thanks!
13+
////
14+
15+
[[multi-terms-aggregation-usage]]
16+
=== Multi Terms Aggregation Usage
17+
18+
A multi-bucket value source based aggregation where buckets are dynamically built - one per unique set of values.
19+
20+
See the Elasticsearch documentation on {ref_current}//search-aggregations-bucket-multi-terms-aggregation.html[multi terms aggregation] for more detail.
21+
22+
==== Fluent DSL example
23+
24+
[source,csharp]
25+
----
26+
a => a
27+
.MultiTerms("states", st => st
28+
.CollectMode(TermsAggregationCollectMode.BreadthFirst)
29+
.Terms(t => t.Field(f => f.Name), t => t.Field(f => f.NumberOfCommits).Missing(0))
30+
.MinimumDocumentCount(1)
31+
.Size(5)
32+
.ShardSize(100)
33+
.ShardMinimumDocumentCount(1)
34+
.ShowTermDocCountError(true)
35+
.Order(o => o
36+
.KeyAscending()
37+
.CountDescending()
38+
)
39+
.Meta(m => m
40+
.Add("foo", "bar")
41+
)
42+
)
43+
----
44+
45+
==== Object Initializer syntax example
46+
47+
[source,csharp]
48+
----
49+
new MultiTermsAggregation("states")
50+
{
51+
CollectMode = TermsAggregationCollectMode.BreadthFirst,
52+
Terms = new List<Term>
53+
{
54+
new() {Field = Field<Project>(f => f.Name) },
55+
new() {Field = Field<Project>(f => f.NumberOfCommits), Missing = 0 }
56+
},
57+
MinimumDocumentCount = 1,
58+
Size = 5,
59+
ShardSize = 100,
60+
ShardMinimumDocumentCount = 1,
61+
ShowTermDocCountError = true,
62+
Order = new List<TermsOrder>
63+
{
64+
TermsOrder.KeyAscending,
65+
TermsOrder.CountDescending
66+
},
67+
Meta = new Dictionary<string, object>
68+
{
69+
{ "foo", "bar" }
70+
}
71+
}
72+
----
73+
74+
[source,javascript]
75+
.Example json output
76+
----
77+
{
78+
"states": {
79+
"meta": {
80+
"foo": "bar"
81+
},
82+
"multi_terms": {
83+
"collect_mode": "breadth_first",
84+
"terms": [
85+
{
86+
"field": "name"
87+
},
88+
{
89+
"field": "numberOfCommits",
90+
"missing": 0
91+
}
92+
],
93+
"min_doc_count": 1,
94+
"shard_min_doc_count": 1,
95+
"size": 5,
96+
"shard_size": 100,
97+
"show_term_doc_count_error": true,
98+
"order": [
99+
{
100+
"_key": "asc"
101+
},
102+
{
103+
"_count": "desc"
104+
}
105+
]
106+
}
107+
}
108+
}
109+
----
110+
111+
==== Handling Responses
112+
113+
[source,csharp]
114+
----
115+
response.ShouldBeValid();
116+
var states = response.Aggregations.MultiTerms("states");
117+
states.Should().NotBeNull();
118+
states.DocCountErrorUpperBound.Should().HaveValue();
119+
states.SumOtherDocCount.Should().HaveValue();
120+
states.Buckets.Should().NotBeNull();
121+
states.Buckets.Count.Should().BeGreaterThan(0);
122+
foreach (var item in states.Buckets)
123+
{
124+
item.Key.Should().NotBeNullOrEmpty();
125+
item.DocCount.Should().BeGreaterOrEqualTo(1);
126+
item.KeyAsString.Should().NotBeNullOrEmpty();
127+
}
128+
states.Meta.Should().NotBeNull().And.HaveCount(1);
129+
states.Meta["foo"].Should().Be("bar");
130+
----
131+

src/Nest/Aggregations/AggregateDictionary.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,22 @@ public MultiBucketAggregate<RareTermsBucket<TKey>> RareTerms<TKey>(string key)
222222

223223
public MultiBucketAggregate<DateHistogramBucket> DateHistogram(string key) => GetMultiBucketAggregate<DateHistogramBucket>(key);
224224

225+
public MultiTermsAggregate<string> MultiTerms(string key) => MultiTerms<string>(key);
226+
227+
public MultiTermsAggregate<TKey> MultiTerms<TKey>(string key)
228+
{
229+
var bucket = TryGet<BucketAggregate>(key);
230+
return bucket == null
231+
? null
232+
: new MultiTermsAggregate<TKey>
233+
{
234+
DocCountErrorUpperBound = bucket.DocCountErrorUpperBound,
235+
SumOtherDocCount = bucket.SumOtherDocCount,
236+
Buckets = GetMultiTermsBuckets<TKey>(bucket.Items).ToList(),
237+
Meta = bucket.Meta
238+
};
239+
}
240+
225241
public AutoDateHistogramAggregate AutoDateHistogram(string key)
226242
{
227243
var bucket = TryGet<BucketAggregate>(key);
@@ -324,6 +340,28 @@ private IEnumerable<RareTermsBucket<TKey>> GetRareTermsBuckets<TKey>(IEnumerable
324340
};
325341
}
326342

343+
private static IEnumerable<MultiTermsBucket<TKey>> GetMultiTermsBuckets<TKey>(IEnumerable<IBucket> items)
344+
{
345+
var buckets = items.Cast<KeyedBucket<object>>();
346+
347+
foreach (var bucket in buckets)
348+
{
349+
var aggregates = new MultiTermsBucket<TKey>(bucket.BackingDictionary)
350+
{
351+
DocCount = bucket.DocCount,
352+
DocCountErrorUpperBound = bucket.DocCountErrorUpperBound,
353+
KeyAsString = bucket.KeyAsString
354+
};
355+
356+
if (bucket.Key is List<object> allKeys)
357+
aggregates.Key = allKeys.Select(GetKeyFromBucketKey<TKey>).ToList();
358+
else
359+
aggregates.Key = new[] { GetKeyFromBucketKey<TKey>(bucket.Key) };
360+
361+
yield return aggregates;
362+
}
363+
}
364+
327365
private static TKey GetKeyFromBucketKey<TKey>(object key) =>
328366
typeof(TKey).IsEnum
329367
? (TKey)Enum.Parse(typeof(TKey), key.ToString(), true)

src/Nest/Aggregations/AggregateFormatter.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -939,9 +939,7 @@ private IBucket GetDateHistogramBucket(ref JsonReader reader, IJsonFormatterReso
939939

940940
return dateHistogram;
941941
}
942-
943-
944-
942+
945943
private IBucket GetKeyedBucket(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
946944
{
947945
var token = reader.GetCurrentJsonToken();
@@ -951,6 +949,32 @@ private IBucket GetKeyedBucket(ref JsonReader reader, IJsonFormatterResolver for
951949
object key;
952950
if (token == JsonToken.String)
953951
key = reader.ReadString();
952+
else if (token == JsonToken.BeginArray) // we're in a multi-terms bucket
953+
{
954+
var keys = new List<object>();
955+
var count = 0;
956+
957+
while(reader.ReadIsInArray(ref count))
958+
{
959+
object keyItem;
960+
961+
var keyToken = reader.GetCurrentJsonToken();
962+
963+
if (keyToken == JsonToken.String)
964+
keyItem = reader.ReadString();
965+
else
966+
{
967+
var numberKey = reader.ReadNumberSegment();
968+
if (numberKey.IsLong())
969+
keyItem = NumberConverter.ReadInt64(numberKey.Array, numberKey.Offset, out _);
970+
else
971+
keyItem = NumberConverter.ReadDouble(numberKey.Array, numberKey.Offset, out _);
972+
}
973+
974+
keys.Add(keyItem);
975+
}
976+
key = keys;
977+
}
954978
else
955979
{
956980
var numberSegment = reader.ReadNumberSegment();

src/Nest/Aggregations/AggregationContainer.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ public interface IAggregationContainer
293293
[DataMember(Name = "top_metrics")]
294294
ITopMetricsAggregation TopMetrics { get; set; }
295295

296+
[DataMember(Name = "multi_terms")]
297+
IMultiTermsAggregation MultiTerms { get; set; }
298+
296299
void Accept(IAggregationVisitor visitor);
297300
}
298301

@@ -441,6 +444,8 @@ public class AggregationContainer : IAggregationContainer
441444

442445
public ITopMetricsAggregation TopMetrics { get; set; }
443446

447+
public IMultiTermsAggregation MultiTerms { get; set; }
448+
444449
public void Accept(IAggregationVisitor visitor)
445450
{
446451
if (visitor.Scope == AggregationVisitorScope.Unknown) visitor.Scope = AggregationVisitorScope.Aggregation;
@@ -554,6 +559,8 @@ public class AggregationContainerDescriptor<T> : DescriptorBase<AggregationConta
554559

555560
IMovingPercentilesAggregation IAggregationContainer.MovingPercentiles { get; set; }
556561

562+
IMultiTermsAggregation IAggregationContainer.MultiTerms { get; set; }
563+
557564
INestedAggregation IAggregationContainer.Nested { get; set; }
558565

559566
INormalizeAggregation IAggregationContainer.Normalize { get; set; }
@@ -718,6 +725,11 @@ Func<MissingAggregationDescriptor<T>, IMissingAggregation> selector
718725
) =>
719726
_SetInnerAggregation(name, selector, (a, d) => a.Missing = d);
720727

728+
public AggregationContainerDescriptor<T> MultiTerms(string name,
729+
Func<MultiTermsAggregationDescriptor<T>, IMultiTermsAggregation> selector
730+
) =>
731+
_SetInnerAggregation(name, selector, (a, d) => a.MultiTerms = d);
732+
721733
public AggregationContainerDescriptor<T> Nested(string name,
722734
Func<NestedAggregationDescriptor<T>, INestedAggregation> selector
723735
) =>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
namespace Nest
6+
{
7+
public class MultiTermsAggregate<TKey> : MultiBucketAggregate<MultiTermsBucket<TKey>>
8+
{
9+
public long? DocCountErrorUpperBound { get; set; }
10+
public long? SumOtherDocCount { get; set; }
11+
}
12+
}

0 commit comments

Comments
 (0)