Skip to content

Commit fcd824a

Browse files
committed
Add Parent Aggregation (#3609)
Relates: elastic/elasticsearch#34210 This commit adds Parent Aggregation to the high level client. (cherry picked from commit 1051a57)
1 parent 6645893 commit fcd824a

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
@@ -136,6 +136,8 @@ In addition to the buckets themselves, the bucket aggregations also compute and
136136

137137
* <<nested-aggregation-usage,Nested Aggregation Usage>>
138138

139+
* <<parent-aggregation-usage,Parent Aggregation Usage>>
140+
139141
* <<range-aggregation-usage,Range Aggregation Usage>>
140142

141143
* <<reverse-nested-aggregation-usage,Reverse Nested Aggregation Usage>>
@@ -189,6 +191,8 @@ include::aggregations/bucket/missing/missing-aggregation-usage.asciidoc[]
189191

190192
include::aggregations/bucket/nested/nested-aggregation-usage.asciidoc[]
191193

194+
include::aggregations/bucket/parent/parent-aggregation-usage.asciidoc[]
195+
192196
include::aggregations/bucket/range/range-aggregation-usage.asciidoc[]
193197

194198
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
@@ -115,6 +115,8 @@ public FiltersAggregate Filters(string key)
115115

116116
public SingleBucketAggregate Children(string key) => TryGet<SingleBucketAggregate>(key);
117117

118+
public SingleBucketAggregate Parent(string key) => TryGet<SingleBucketAggregate>(key);
119+
118120
public SingleBucketAggregate Sampler(string key) => TryGet<SingleBucketAggregate>(key);
119121

120122
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
@@ -189,6 +189,9 @@ public interface IAggregationContainer
189189
[DataMember(Name = "nested")]
190190
INestedAggregation Nested { get; set; }
191191

192+
[DataMember(Name = "parent")]
193+
IParentAggregation Parent { get; set; }
194+
192195
[DataMember(Name = "percentile_ranks")]
193196
IPercentileRanksAggregation PercentileRanks { get; set; }
194197

@@ -316,6 +319,8 @@ public class AggregationContainer : IAggregationContainer
316319

317320
public INestedAggregation Nested { get; set; }
318321

322+
public IParentAggregation Parent { get; set; }
323+
319324
public IPercentileRanksAggregation PercentileRanks { get; set; }
320325

321326
public IPercentilesAggregation Percentiles { get; set; }
@@ -452,6 +457,8 @@ public class AggregationContainerDescriptor<T> : DescriptorBase<AggregationConta
452457

453458
INestedAggregation IAggregationContainer.Nested { get; set; }
454459

460+
IParentAggregation IAggregationContainer.Parent { get; set; }
461+
455462
IPercentileRanksAggregation IAggregationContainer.PercentileRanks { get; set; }
456463

457464
IPercentilesAggregation IAggregationContainer.Percentiles { get; set; }
@@ -594,6 +601,11 @@ Func<NestedAggregationDescriptor<T>, INestedAggregation> selector
594601
) =>
595602
_SetInnerAggregation(name, selector, (a, d) => a.Nested = d);
596603

604+
public AggregationContainerDescriptor<T> Parent<TParent>(string name,
605+
Func<ParentAggregationDescriptor<T, TParent>, IParentAggregation> selector
606+
) where TParent : class =>
607+
_SetInnerAggregation(name, selector, (a, d) => a.Parent = d);
608+
597609
public AggregationContainerDescriptor<T> ReverseNested(string name,
598610
Func<ReverseNestedAggregationDescriptor<T>, IReverseNestedAggregation> selector
599611
) =>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Runtime.Serialization;
2+
using Elasticsearch.Net;
3+
4+
namespace Nest
5+
{
6+
[InterfaceDataContract]
7+
[ReadAs(typeof(ParentAggregation))]
8+
public interface IParentAggregation : IBucketAggregation
9+
{
10+
/// <summary>
11+
/// The type for the child in the parent/child relationship
12+
/// </summary>
13+
[DataMember(Name = "type")]
14+
RelationName Type { get; set; }
15+
}
16+
17+
public class ParentAggregation : BucketAggregationBase, IParentAggregation
18+
{
19+
internal ParentAggregation() { }
20+
21+
public ParentAggregation(string name, RelationName type) : base(name) => Type = type;
22+
23+
public RelationName Type { get; set; }
24+
25+
internal override void WrapInContainer(AggregationContainer c) => c.Parent = this;
26+
}
27+
28+
public class ParentAggregationDescriptor<T, TParent>
29+
: BucketAggregationDescriptorBase<ParentAggregationDescriptor<T, TParent>, IParentAggregation, TParent>, IParentAggregation
30+
where T : class
31+
where TParent : class
32+
{
33+
RelationName IParentAggregation.Type { get; set; } = typeof(T);
34+
35+
public ParentAggregationDescriptor<T, TParent> Type(RelationName type) =>
36+
Assign(a => a.Type = type);
37+
38+
public ParentAggregationDescriptor<T, TParent> Type<TOtherParent>() =>
39+
Assign(a => a.Type = typeof(TOtherParent));
40+
}
41+
}

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: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
.TypedKeys(TestClient.Configuration.Random.TypedKeys)
63+
.Aggregations(FluentAggs);
64+
65+
protected override HttpMethod HttpMethod => HttpMethod.POST;
66+
67+
// hide
68+
protected override SearchRequest<CommitActivity> Initializer =>
69+
new SearchRequest<CommitActivity>(DefaultSeeder.CommitsAliasFilter)
70+
{
71+
Size = 0,
72+
TypedKeys = TestClient.Configuration.Random.TypedKeys,
73+
Aggregations = InitializerAggs
74+
};
75+
76+
protected override string UrlPath => $"/commits-only/_search";
77+
78+
// https://youtrack.jetbrains.com/issue/RIDER-19912
79+
[U] protected override Task HitsTheCorrectUrl() => base.HitsTheCorrectUrl();
80+
81+
[U] protected override Task UsesCorrectHttpMethod() => base.UsesCorrectHttpMethod();
82+
83+
[U] protected override void SerializesInitializer() => base.SerializesInitializer();
84+
85+
[U] protected override void SerializesFluent() => base.SerializesFluent();
86+
87+
[I] public override Task ReturnsExpectedStatusCode() => base.ReturnsExpectedResponse();
88+
89+
[I] public override Task ReturnsExpectedIsValid() => base.ReturnsExpectedIsValid();
90+
91+
[I] public override Task ReturnsExpectedResponse() => base.ReturnsExpectedResponse();
92+
93+
protected override LazyResponses ClientUsage() => Calls(
94+
(client, f) => client.Search<CommitActivity>(f),
95+
(client, f) => client.SearchAsync<CommitActivity>(f),
96+
(client, r) => client.Search<CommitActivity>(r),
97+
(client, r) => client.SearchAsync<CommitActivity>(r)
98+
);
99+
100+
protected Func<AggregationContainerDescriptor<CommitActivity>, IAggregationContainer> FluentAggs => a => a
101+
.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`
102+
.Aggregations(parentAggs => parentAggs
103+
.Average("average_commits", avg => avg.Field(p => p.NumberOfCommits))
104+
.Max("max_commits", avg => avg.Field(p => p.NumberOfCommits))
105+
.Min("min_commits", avg => avg.Field(p => p.NumberOfCommits))
106+
)
107+
);
108+
109+
protected AggregationDictionary InitializerAggs =>
110+
new ParentAggregation("name_of_parent_agg", typeof(CommitActivity)) // <1> `join` field is determined from the _child_ type. In this example, it is `CommitActivity`
111+
{
112+
Aggregations =
113+
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`
114+
&& new MaxAggregation("max_commits", Field<Project>(f => f.NumberOfCommits))
115+
&& new MinAggregation("min_commits", Field<Project>(f => f.NumberOfCommits))
116+
};
117+
118+
protected override void ExpectResponse(ISearchResponse<CommitActivity> response)
119+
{
120+
response.ShouldBeValid();
121+
122+
var parentAgg = response.Aggregations.Parent("name_of_parent_agg");
123+
parentAgg.Should().NotBeNull();
124+
parentAgg.DocCount.Should().BeGreaterThan(0);
125+
parentAgg.Min("average_commits").Should().NotBeNull();
126+
parentAgg.Min("min_commits").Should().NotBeNull();
127+
parentAgg.Max("max_commits").Should().NotBeNull();
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)