Skip to content

Commit 56843c7

Browse files
committed
Add Parent Aggregation (#3609)
Relates: elastic/elasticsearch#34210 This commit adds Parent Aggregation to the high level client.
1 parent 7ad67ec commit 56843c7

File tree

8 files changed

+297
-0
lines changed

8 files changed

+297
-0
lines changed

docs/aggregations.asciidoc

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

139139
* <<nested-aggregation-usage,Nested Aggregation Usage>>
140140

141+
* <<parent-aggregation-usage,Parent Aggregation Usage>>
142+
141143
* <<range-aggregation-usage,Range Aggregation Usage>>
142144

143145
* <<reverse-nested-aggregation-usage,Reverse Nested Aggregation Usage>>
@@ -193,6 +195,8 @@ include::aggregations/bucket/missing/missing-aggregation-usage.asciidoc[]
193195

194196
include::aggregations/bucket/nested/nested-aggregation-usage.asciidoc[]
195197

198+
include::aggregations/bucket/parent/parent-aggregation-usage.asciidoc[]
199+
196200
include::aggregations/bucket/range/range-aggregation-usage.asciidoc[]
197201

198202
include::aggregations/bucket/reverse-nested/reverse-nested-aggregation-usage.asciidoc[]
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/6.5
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/6.x/src/Tests/Tests/Aggregations/Bucket/Parent/ParentAggregationUsageTests.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+
[[parent-aggregation-usage]]
16+
=== Parent Aggregation Usage
17+
18+
A special single bucket aggregation that selects parent documents that have the specified type, as defined in a `join` field.
19+
20+
Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-parent-aggregation.html[Parent Aggregation].
21+
22+
==== Fluent DSL example
23+
24+
[source,csharp]
25+
----
26+
a => a
27+
.Parent<Project>("name_of_parent_agg", parent => parent <1>
28+
.Aggregations(parentAggs => parentAggs
29+
.Average("average_commits", avg => avg.Field(p => p.NumberOfCommits))
30+
.Max("max_commits", avg => avg.Field(p => p.NumberOfCommits))
31+
.Min("min_commits", avg => avg.Field(p => p.NumberOfCommits))
32+
)
33+
)
34+
----
35+
<1> sub-aggregations are on the type determined from the generic type parameter. In this example, the search is against `CommitActivity` type and `Project` is a parent of `CommitActivity`
36+
37+
==== Object Initializer syntax example
38+
39+
[source,csharp]
40+
----
41+
new ParentAggregation("name_of_parent_agg", typeof(CommitActivity)) <1>
42+
{
43+
Aggregations =
44+
new AverageAggregation("average_commits", Field<Project>(f => f.NumberOfCommits)) <2>
45+
&& new MaxAggregation("max_commits", Field<Project>(f => f.NumberOfCommits))
46+
&& new MinAggregation("min_commits", Field<Project>(f => f.NumberOfCommits))
47+
}
48+
----
49+
<1> `join` field is determined from the _child_ type. In this example, it is `CommitActivity`
50+
51+
<2> sub-aggregations are on the type determined from the `join` field. In this example, a `Project` is a parent of `CommitActivity`
52+
53+
[source,javascript]
54+
.Example json output
55+
----
56+
{
57+
"size": 0,
58+
"aggs": {
59+
"name_of_parent_agg": {
60+
"parent": {
61+
"type": "commits"
62+
},
63+
"aggs": {
64+
"average_commits": {
65+
"avg": {
66+
"field": "numberOfCommits"
67+
}
68+
},
69+
"max_commits": {
70+
"max": {
71+
"field": "numberOfCommits"
72+
}
73+
},
74+
"min_commits": {
75+
"min": {
76+
"field": "numberOfCommits"
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}
83+
----
84+
85+
==== Handling Responses
86+
87+
[source,csharp]
88+
----
89+
response.ShouldBeValid();
90+
91+
var parentAgg = response.Aggregations.Parent("name_of_parent_agg");
92+
parentAgg.Should().NotBeNull();
93+
parentAgg.DocCount.Should().BeGreaterThan(0);
94+
parentAgg.Min("average_commits").Should().NotBeNull();
95+
parentAgg.Min("min_commits").Should().NotBeNull();
96+
parentAgg.Max("max_commits").Should().NotBeNull();
97+
----
98+

src/Nest/Aggregations/AggregateDictionary.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ public FiltersAggregate Filters(string key)
113113

114114
public SingleBucketAggregate Children(string key) => TryGet<SingleBucketAggregate>(key);
115115

116+
public SingleBucketAggregate Parent(string key) => TryGet<SingleBucketAggregate>(key);
117+
116118
public SingleBucketAggregate Sampler(string key) => TryGet<SingleBucketAggregate>(key);
117119

118120
public GeoCentroidAggregate GeoCentroid(string key) => TryGet<GeoCentroidAggregate>(key);

src/Nest/Aggregations/AggregationContainer.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ public interface IAggregationContainer
177177
[JsonProperty("nested")]
178178
INestedAggregation Nested { get; set; }
179179

180+
[JsonProperty("parent")]
181+
IParentAggregation Parent { get; set; }
182+
180183
[JsonProperty("percentile_ranks")]
181184
IPercentileRanksAggregation PercentileRanks { get; set; }
182185

@@ -304,6 +307,8 @@ public class AggregationContainer : IAggregationContainer
304307

305308
public INestedAggregation Nested { get; set; }
306309

310+
public IParentAggregation Parent { get; set; }
311+
307312
public IPercentileRanksAggregation PercentileRanks { get; set; }
308313

309314
public IPercentilesAggregation Percentiles { get; set; }
@@ -440,6 +445,8 @@ public class AggregationContainerDescriptor<T> : DescriptorBase<AggregationConta
440445

441446
INestedAggregation IAggregationContainer.Nested { get; set; }
442447

448+
IParentAggregation IAggregationContainer.Parent { get; set; }
449+
443450
IPercentileRanksAggregation IAggregationContainer.PercentileRanks { get; set; }
444451

445452
IPercentilesAggregation IAggregationContainer.Percentiles { get; set; }
@@ -582,6 +589,11 @@ Func<NestedAggregationDescriptor<T>, INestedAggregation> selector
582589
) =>
583590
_SetInnerAggregation(name, selector, (a, d) => a.Nested = d);
584591

592+
public AggregationContainerDescriptor<T> Parent<TParent>(string name,
593+
Func<ParentAggregationDescriptor<T, TParent>, IParentAggregation> selector
594+
) where TParent : class =>
595+
_SetInnerAggregation(name, selector, (a, d) => a.Parent = d);
596+
585597
public AggregationContainerDescriptor<T> ReverseNested(string name,
586598
Func<ReverseNestedAggregationDescriptor<T>, IReverseNestedAggregation> selector
587599
) =>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Nest
4+
{
5+
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
6+
[ContractJsonConverter(typeof(AggregationJsonConverter<ParentAggregation>))]
7+
public interface IParentAggregation : IBucketAggregation
8+
{
9+
/// <summary>
10+
/// The type for the child in the parent/child relationship
11+
/// </summary>
12+
[JsonProperty("type")]
13+
RelationName Type { get; set; }
14+
}
15+
16+
public class ParentAggregation : BucketAggregationBase, IParentAggregation
17+
{
18+
internal ParentAggregation() { }
19+
20+
public ParentAggregation(string name, RelationName type) : base(name) => Type = type;
21+
22+
public RelationName Type { get; set; }
23+
24+
internal override void WrapInContainer(AggregationContainer c) => c.Parent = this;
25+
}
26+
27+
public class ParentAggregationDescriptor<T, TParent>
28+
: BucketAggregationDescriptorBase<ParentAggregationDescriptor<T, TParent>, IParentAggregation, TParent>, IParentAggregation
29+
where T : class
30+
where TParent : class
31+
{
32+
RelationName IParentAggregation.Type { get; set; } = typeof(T);
33+
34+
public ParentAggregationDescriptor<T, TParent> Type(RelationName type) =>
35+
Assign(a => a.Type = type);
36+
37+
public ParentAggregationDescriptor<T, TParent> Type<TOtherParent>() =>
38+
Assign(a => a.Type = typeof(TOtherParent));
39+
}
40+
}

src/Nest/Aggregations/Visitor/AggregationVisitor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public interface IAggregationVisitor
6666

6767
void Visit(INestedAggregation aggregation);
6868

69+
void Visit(IParentAggregation aggregation);
70+
6971
void Visit(IReverseNestedAggregation aggregation);
7072

7173
void Visit(IRangeAggregation aggregation);
@@ -149,6 +151,7 @@ public virtual void Visit(IReverseNestedAggregation aggregation) { }
149151

150152
public virtual void Visit(ITermsAggregation aggregation) { }
151153

154+
// TODO: make virtual
152155
public void Visit(ISignificantTextAggregation aggregation) { }
153156

154157
public virtual void Visit(IPercentileRanksAggregation aggregation) { }
@@ -195,6 +198,8 @@ public virtual void Visit(IRangeAggregation aggregation) { }
195198

196199
public virtual void Visit(INestedAggregation aggregation) { }
197200

201+
public virtual void Visit(IParentAggregation aggregation) { }
202+
198203
public virtual void Visit(ICardinalityAggregation aggregation) { }
199204

200205
public virtual void Visit(IGlobalAggregation aggregation) { }

src/Nest/Aggregations/Visitor/AggregationWalker.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ public void Walk(IAggregationContainer aggregation, IAggregationVisitor visitor)
111111
v.Visit(d);
112112
Accept(v, d.Aggregations);
113113
});
114+
AcceptAggregation(aggregation.Parent, visitor, (v, d) =>
115+
{
116+
v.Visit(d);
117+
Accept(v, d.Aggregations);
118+
});
114119
AcceptAggregation(aggregation.PercentileRanks, visitor, (v, d) => v.Visit(d));
115120
AcceptAggregation(aggregation.Percentiles, visitor, (v, d) => v.Visit(d));
116121
AcceptAggregation(aggregation.Range, visitor, (v, d) =>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Elastic.Xunit.XunitPlumbing;
4+
using Elasticsearch.Net;
5+
using FluentAssertions;
6+
using Nest;
7+
using Tests.Core.Client;
8+
using Tests.Core.Extensions;
9+
using Tests.Core.ManagedElasticsearch.Clusters;
10+
using Tests.Core.ManagedElasticsearch.NodeSeeders;
11+
using Tests.Domain;
12+
using Tests.Framework;
13+
using Tests.Framework.Integration;
14+
using static Nest.Infer;
15+
16+
namespace Tests.Aggregations.Bucket.Parent
17+
{
18+
/**
19+
* A special single bucket aggregation that selects parent documents that have the specified type, as defined in a `join` field.
20+
*
21+
* Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-parent-aggregation.html[Parent Aggregation].
22+
*/
23+
public class ParentAggregationUsageTests : ApiIntegrationTestBase<ReadOnlyCluster, ISearchResponse<CommitActivity>, ISearchRequest, SearchDescriptor<CommitActivity>, SearchRequest<CommitActivity>>
24+
{
25+
public ParentAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }
26+
27+
protected override bool ExpectIsValid => true;
28+
29+
protected sealed override object ExpectJson => new
30+
{
31+
size = 0,
32+
aggs = new
33+
{
34+
name_of_parent_agg = new
35+
{
36+
parent = new { type = "commits" },
37+
aggs = new
38+
{
39+
average_commits = new
40+
{
41+
avg = new { field = "numberOfCommits" }
42+
},
43+
max_commits = new
44+
{
45+
max = new { field = "numberOfCommits" }
46+
},
47+
min_commits = new
48+
{
49+
min = new { field = "numberOfCommits" }
50+
}
51+
}
52+
}
53+
}
54+
};
55+
56+
protected override int ExpectStatusCode => 200;
57+
58+
// hide
59+
protected override Func<SearchDescriptor<CommitActivity>, ISearchRequest> Fluent => s => s
60+
.Size(0)
61+
.Index(DefaultSeeder.CommitsAliasFilter)
62+
.Type<CommitActivity>()
63+
.TypedKeys(TestClient.Configuration.Random.TypedKeys)
64+
.Aggregations(FluentAggs);
65+
66+
protected override HttpMethod HttpMethod => HttpMethod.POST;
67+
68+
// hide
69+
protected override SearchRequest<CommitActivity> Initializer =>
70+
new SearchRequest<CommitActivity>(DefaultSeeder.CommitsAliasFilter, Type<CommitActivity>())
71+
{
72+
Size = 0,
73+
TypedKeys = TestClient.Configuration.Random.TypedKeys,
74+
Aggregations = InitializerAggs
75+
};
76+
77+
protected override string UrlPath => $"/commits-only/doc/_search";
78+
79+
// https://youtrack.jetbrains.com/issue/RIDER-19912
80+
[U] protected override Task HitsTheCorrectUrl() => base.HitsTheCorrectUrl();
81+
82+
[U] protected override Task UsesCorrectHttpMethod() => base.UsesCorrectHttpMethod();
83+
84+
[U] protected override void SerializesInitializer() => base.SerializesInitializer();
85+
86+
[U] protected override void SerializesFluent() => base.SerializesFluent();
87+
88+
[I] public override Task ReturnsExpectedStatusCode() => base.ReturnsExpectedResponse();
89+
90+
[I] public override Task ReturnsExpectedIsValid() => base.ReturnsExpectedIsValid();
91+
92+
[I] public override Task ReturnsExpectedResponse() => base.ReturnsExpectedResponse();
93+
94+
protected override LazyResponses ClientUsage() => Calls(
95+
(client, f) => client.Search<CommitActivity>(f),
96+
(client, f) => client.SearchAsync<CommitActivity>(f),
97+
(client, r) => client.Search<CommitActivity>(r),
98+
(client, r) => client.SearchAsync<CommitActivity>(r)
99+
);
100+
101+
protected Func<AggregationContainerDescriptor<CommitActivity>, IAggregationContainer> FluentAggs => a => a
102+
.Parent<Project>("name_of_parent_agg", parent => parent // <1> sub-aggregations are on the type determined from the generic type parameter. In this example, the search is against `CommitActivity` type and `Project` is a parent of `CommitActivity`
103+
.Aggregations(parentAggs => parentAggs
104+
.Average("average_commits", avg => avg.Field(p => p.NumberOfCommits))
105+
.Max("max_commits", avg => avg.Field(p => p.NumberOfCommits))
106+
.Min("min_commits", avg => avg.Field(p => p.NumberOfCommits))
107+
)
108+
);
109+
110+
protected AggregationDictionary InitializerAggs =>
111+
new ParentAggregation("name_of_parent_agg", typeof(CommitActivity)) // <1> `join` field is determined from the _child_ type. In this example, it is `CommitActivity`
112+
{
113+
Aggregations =
114+
new AverageAggregation("average_commits", Field<Project>(f => f.NumberOfCommits)) // <2> sub-aggregations are on the type determined from the `join` field. In this example, a `Project` is a parent of `CommitActivity`
115+
&& new MaxAggregation("max_commits", Field<Project>(f => f.NumberOfCommits))
116+
&& new MinAggregation("min_commits", Field<Project>(f => f.NumberOfCommits))
117+
};
118+
119+
protected override void ExpectResponse(ISearchResponse<CommitActivity> response)
120+
{
121+
response.ShouldBeValid();
122+
123+
var parentAgg = response.Aggregations.Parent("name_of_parent_agg");
124+
parentAgg.Should().NotBeNull();
125+
parentAgg.DocCount.Should().BeGreaterThan(0);
126+
parentAgg.Min("average_commits").Should().NotBeNull();
127+
parentAgg.Min("min_commits").Should().NotBeNull();
128+
parentAgg.Max("max_commits").Should().NotBeNull();
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)