Skip to content

Commit 20b1690

Browse files
centic9jimczi
authored andcommitted
Add parent-aggregation to parent-join module (#34210)
Add `parent` aggregation, a special single bucket aggregation that joins children documents to their parent.
1 parent dc5dfc5 commit 20b1690

19 files changed

+1496
-112
lines changed

client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ public void testDefaultNamedXContents() {
625625

626626
public void testProvidedNamedXContents() {
627627
List<NamedXContentRegistry.Entry> namedXContents = RestHighLevelClient.getProvidedNamedXContents();
628-
assertEquals(16, namedXContents.size());
628+
assertEquals(17, namedXContents.size());
629629
Map<Class<?>, Integer> categories = new HashMap<>();
630630
List<String> names = new ArrayList<>();
631631
for (NamedXContentRegistry.Entry namedXContent : namedXContents) {
@@ -635,8 +635,8 @@ public void testProvidedNamedXContents() {
635635
categories.put(namedXContent.categoryClass, counter + 1);
636636
}
637637
}
638-
assertEquals(4, categories.size());
639-
assertEquals(Integer.valueOf(2), categories.get(Aggregation.class));
638+
assertEquals("Had: " + categories, 4, categories.size());
639+
assertEquals(Integer.valueOf(3), categories.get(Aggregation.class));
640640
assertTrue(names.contains(ChildrenAggregationBuilder.NAME));
641641
assertTrue(names.contains(MatrixStatsAggregationBuilder.NAME));
642642
assertEquals(Integer.valueOf(4), categories.get(EvaluationMetric.class));

docs/reference/aggregations/bucket.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ include::bucket/missing-aggregation.asciidoc[]
4949

5050
include::bucket/nested-aggregation.asciidoc[]
5151

52+
include::bucket/parent-aggregation.asciidoc[]
53+
5254
include::bucket/range-aggregation.asciidoc[]
5355

5456
include::bucket/reverse-nested-aggregation.asciidoc[]
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
[[search-aggregations-bucket-parent-aggregation]]
2+
=== Parent Aggregation
3+
4+
A special single bucket aggregation that selects parent documents that have the specified type, as defined in a <<parent-join,`join` field>>.
5+
6+
This aggregation has a single option:
7+
8+
* `type` - The child type that should be selected.
9+
10+
For example, let's say we have an index of questions and answers. The answer type has the following `join` field in the mapping:
11+
12+
[source,js]
13+
--------------------------------------------------
14+
PUT parent_example
15+
{
16+
"mappings": {
17+
"_doc": {
18+
"properties": {
19+
"join": {
20+
"type": "join",
21+
"relations": {
22+
"question": "answer"
23+
}
24+
}
25+
}
26+
}
27+
}
28+
}
29+
--------------------------------------------------
30+
// CONSOLE
31+
32+
The `question` document contain a tag field and the `answer` documents contain an owner field. With the `parent`
33+
aggregation the owner buckets can be mapped to the tag buckets in a single request even though the two fields exist in
34+
two different kinds of documents.
35+
36+
An example of a question document:
37+
38+
[source,js]
39+
--------------------------------------------------
40+
PUT parent_example/_doc/1
41+
{
42+
"join": {
43+
"name": "question"
44+
},
45+
"body": "<p>I have Windows 2003 server and i bought a new Windows 2008 server...",
46+
"title": "Whats the best way to file transfer my site from server to a newer one?",
47+
"tags": [
48+
"windows-server-2003",
49+
"windows-server-2008",
50+
"file-transfer"
51+
]
52+
}
53+
--------------------------------------------------
54+
// CONSOLE
55+
// TEST[continued]
56+
57+
Examples of `answer` documents:
58+
59+
[source,js]
60+
--------------------------------------------------
61+
PUT parent_example/_doc/2?routing=1
62+
{
63+
"join": {
64+
"name": "answer",
65+
"parent": "1"
66+
},
67+
"owner": {
68+
"location": "Norfolk, United Kingdom",
69+
"display_name": "Sam",
70+
"id": 48
71+
},
72+
"body": "<p>Unfortunately you're pretty much limited to FTP...",
73+
"creation_date": "2009-05-04T13:45:37.030"
74+
}
75+
76+
PUT parent_example/_doc/3?routing=1&refresh
77+
{
78+
"join": {
79+
"name": "answer",
80+
"parent": "1"
81+
},
82+
"owner": {
83+
"location": "Norfolk, United Kingdom",
84+
"display_name": "Troll",
85+
"id": 49
86+
},
87+
"body": "<p>Use Linux...",
88+
"creation_date": "2009-05-05T13:45:37.030"
89+
}
90+
--------------------------------------------------
91+
// CONSOLE
92+
// TEST[continued]
93+
94+
The following request can be built that connects the two together:
95+
96+
[source,js]
97+
--------------------------------------------------
98+
POST parent_example/_search?size=0
99+
{
100+
"aggs": {
101+
"top-names": {
102+
"terms": {
103+
"field": "owner.display_name.keyword",
104+
"size": 10
105+
},
106+
"aggs": {
107+
"to-questions": {
108+
"parent": {
109+
"type" : "answer"
110+
},
111+
"aggs": {
112+
"top-tags": {
113+
"terms": {
114+
"field": "tags.keyword",
115+
"size": 10
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
}
123+
}
124+
--------------------------------------------------
125+
// CONSOLE
126+
// TEST[continued]
127+
128+
<1> The `type` points to type / mapping with the name `answer`.
129+
130+
The above example returns the top answer owners and per owner the top question tags.
131+
132+
Possible response:
133+
134+
[source,js]
135+
--------------------------------------------------
136+
{
137+
"took": 9,
138+
"timed_out": false,
139+
"_shards": {
140+
"total": 1,
141+
"successful": 1,
142+
"skipped": 0,
143+
"failed": 0
144+
},
145+
"hits": {
146+
"total": 3,
147+
"max_score": null,
148+
"hits": []
149+
},
150+
"aggregations": {
151+
"top-names": {
152+
"doc_count_error_upper_bound": 0,
153+
"sum_other_doc_count": 0,
154+
"buckets": [
155+
{
156+
"key": "Sam",
157+
"doc_count": 1, <1>
158+
"to-questions": {
159+
"doc_count": 1, <2>
160+
"top-tags": {
161+
"doc_count_error_upper_bound": 0,
162+
"sum_other_doc_count": 0,
163+
"buckets": [
164+
{
165+
"key": "file-transfer",
166+
"doc_count": 1
167+
},
168+
{
169+
"key": "windows-server-2003",
170+
"doc_count": 1
171+
},
172+
{
173+
"key": "windows-server-2008",
174+
"doc_count": 1
175+
}
176+
]
177+
}
178+
}
179+
},
180+
{
181+
"key": "Troll",
182+
"doc_count": 1, <1>
183+
"to-questions": {
184+
"doc_count": 1, <2>
185+
"top-tags": {
186+
"doc_count_error_upper_bound": 0,
187+
"sum_other_doc_count": 0,
188+
"buckets": [
189+
{
190+
"key": "file-transfer",
191+
"doc_count": 1
192+
},
193+
{
194+
"key": "windows-server-2003",
195+
"doc_count": 1
196+
},
197+
{
198+
"key": "windows-server-2008",
199+
"doc_count": 1
200+
}
201+
]
202+
}
203+
}
204+
}
205+
]
206+
}
207+
}
208+
}
209+
--------------------------------------------------
210+
// TESTRESPONSE[s/"took": 9/"took": $body.took/]
211+
212+
<1> The number of answer documents with the tag `Sam`, `Troll`, etc.
213+
<2> The number of question documents that are related to answer documents with the tag `Sam`, `Troll`, etc.

modules/parent-join/src/main/java/org/elasticsearch/join/ParentJoinPlugin.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.elasticsearch.index.mapper.Mapper;
2323
import org.elasticsearch.join.aggregations.ChildrenAggregationBuilder;
2424
import org.elasticsearch.join.aggregations.InternalChildren;
25+
import org.elasticsearch.join.aggregations.InternalParent;
26+
import org.elasticsearch.join.aggregations.ParentAggregationBuilder;
2527
import org.elasticsearch.join.mapper.ParentJoinFieldMapper;
2628
import org.elasticsearch.join.query.HasChildQueryBuilder;
2729
import org.elasticsearch.join.query.HasParentQueryBuilder;
@@ -51,9 +53,11 @@ public List<QuerySpec<?>> getQueries() {
5153

5254
@Override
5355
public List<AggregationSpec> getAggregations() {
54-
return Collections.singletonList(
55-
new AggregationSpec(ChildrenAggregationBuilder.NAME, ChildrenAggregationBuilder::new, ChildrenAggregationBuilder::parse)
56-
.addResultReader(InternalChildren::new)
56+
return Arrays.asList(
57+
new AggregationSpec(ChildrenAggregationBuilder.NAME, ChildrenAggregationBuilder::new, ChildrenAggregationBuilder::parse)
58+
.addResultReader(InternalChildren::new),
59+
new AggregationSpec(ParentAggregationBuilder.NAME, ParentAggregationBuilder::new, ParentAggregationBuilder::parse)
60+
.addResultReader(InternalParent::new)
5761
);
5862
}
5963

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.join.aggregations;
20+
21+
import org.apache.lucene.search.Query;
22+
import org.elasticsearch.common.ParseField;
23+
import org.elasticsearch.search.aggregations.Aggregator;
24+
import org.elasticsearch.search.aggregations.AggregatorFactories;
25+
import org.elasticsearch.search.aggregations.InternalAggregation;
26+
import org.elasticsearch.search.aggregations.bucket.BucketsAggregator;
27+
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
28+
import org.elasticsearch.search.aggregations.support.ValuesSource;
29+
import org.elasticsearch.search.internal.SearchContext;
30+
31+
import java.io.IOException;
32+
import java.util.List;
33+
import java.util.Map;
34+
35+
/**
36+
* A {@link BucketsAggregator} which resolves to the matching parent documents.
37+
*/
38+
public class ChildrenToParentAggregator extends ParentJoinAggregator {
39+
40+
static final ParseField TYPE_FIELD = new ParseField("type");
41+
42+
public ChildrenToParentAggregator(String name, AggregatorFactories factories,
43+
SearchContext context, Aggregator parent, Query childFilter,
44+
Query parentFilter, ValuesSource.Bytes.WithOrdinals valuesSource,
45+
long maxOrd, List<PipelineAggregator> pipelineAggregators, Map<String, Object> metaData) throws IOException {
46+
super(name, factories, context, parent, childFilter, parentFilter, valuesSource, maxOrd, pipelineAggregators, metaData);
47+
}
48+
49+
@Override
50+
public InternalAggregation buildAggregation(long owningBucketOrdinal) throws IOException {
51+
return new InternalParent(name, bucketDocCount(owningBucketOrdinal),
52+
bucketAggregations(owningBucketOrdinal), pipelineAggregators(), metaData());
53+
}
54+
55+
@Override
56+
public InternalAggregation buildEmptyAggregation() {
57+
return new InternalParent(name, 0, buildEmptySubAggregations(), pipelineAggregators(),
58+
metaData());
59+
}
60+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.join.aggregations;
21+
22+
import org.elasticsearch.common.io.stream.StreamInput;
23+
import org.elasticsearch.search.aggregations.InternalAggregations;
24+
import org.elasticsearch.search.aggregations.bucket.InternalSingleBucketAggregation;
25+
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
26+
27+
import java.io.IOException;
28+
import java.util.List;
29+
import java.util.Map;
30+
31+
/**
32+
* Results of the {@link ChildrenToParentAggregator}.
33+
*/
34+
public class InternalParent extends InternalSingleBucketAggregation implements Parent {
35+
public InternalParent(String name, long docCount, InternalAggregations aggregations, List<PipelineAggregator> pipelineAggregators,
36+
Map<String, Object> metaData) {
37+
super(name, docCount, aggregations, pipelineAggregators, metaData);
38+
}
39+
40+
/**
41+
* Read from a stream.
42+
*/
43+
public InternalParent(StreamInput in) throws IOException {
44+
super(in);
45+
}
46+
47+
@Override
48+
public String getWriteableName() {
49+
return ParentAggregationBuilder.NAME;
50+
}
51+
52+
@Override
53+
protected InternalSingleBucketAggregation newAggregation(String name, long docCount, InternalAggregations subAggregations) {
54+
return new InternalParent(name, docCount, subAggregations, pipelineAggregators(), getMetaData());
55+
}
56+
}

modules/parent-join/src/main/java/org/elasticsearch/join/aggregations/JoinAggregationBuilders.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,11 @@ public abstract class JoinAggregationBuilders {
2626
public static ChildrenAggregationBuilder children(String name, String childType) {
2727
return new ChildrenAggregationBuilder(name, childType);
2828
}
29+
30+
/**
31+
* Create a new {@link Parent} aggregation with the given name.
32+
*/
33+
public static ParentAggregationBuilder parent(String name, String childType) {
34+
return new ParentAggregationBuilder(name, childType);
35+
}
2936
}

0 commit comments

Comments
 (0)