Skip to content

Commit f942655

Browse files
authored
More pipeline aggregation cleanup (#54298)
This replaces the last bit of validation that pipeline aggregations performed on the data nodes with explicit checks in a few `PipelineAggregationBuilders`. We were *already* catching these validation errors for pipeline aggregations that require that their parent be squentially ordered. This just adds validation for pipelines that require *any* parent like `bucket_selector` and `bucket_sort`.
1 parent 3f5b2e8 commit f942655

File tree

9 files changed

+105
-15
lines changed

9 files changed

+105
-15
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/300_pipeline.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,26 @@ setup:
9898
the_bad_max:
9999
max_bucket:
100100
buckets_path: "the_terms>the_percentiles"
101+
102+
---
103+
"Top level bucket_sort":
104+
- skip:
105+
version: " - 7.99.99"
106+
reason: This validation was changed in 8.0.0 to be backported to 7.8.0
107+
108+
- do:
109+
catch: /bucket_sort aggregation \[the_bad_bucket_sort\] must be declared inside of another aggregation/
110+
search:
111+
body:
112+
aggs:
113+
the_terms:
114+
terms:
115+
field: "int_field"
116+
aggs:
117+
the_max:
118+
max:
119+
field: "int_field"
120+
the_bad_bucket_sort:
121+
bucket_sort:
122+
sort:
123+
- the_terms>the_max

server/src/main/java/org/elasticsearch/search/aggregations/AggregationPhase.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import org.elasticsearch.common.lucene.search.Queries;
2525
import org.elasticsearch.search.SearchPhase;
2626
import org.elasticsearch.search.aggregations.bucket.global.GlobalAggregator;
27-
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
28-
import org.elasticsearch.search.aggregations.pipeline.SiblingPipelineAggregator;
2927
import org.elasticsearch.search.internal.SearchContext;
3028
import org.elasticsearch.search.profile.query.CollectorResult;
3129
import org.elasticsearch.search.profile.query.InternalProfileCollector;
@@ -132,15 +130,6 @@ public void execute(SearchContext context) {
132130
throw new AggregationExecutionException("Failed to build aggregation [" + aggregator.name() + "]", e);
133131
}
134132
}
135-
List<PipelineAggregator> pipelineAggregators = context.aggregations().factories().createPipelineAggregators();
136-
for (PipelineAggregator pipelineAggregator : pipelineAggregators) {
137-
if (false == pipelineAggregator instanceof SiblingPipelineAggregator) {
138-
// TODO move this to request validation after #53669
139-
throw new AggregationExecutionException("Invalid pipeline aggregation named [" + pipelineAggregator.name()
140-
+ "] of type [" + pipelineAggregator.getWriteableName() + "]. Only sibling pipeline aggregations are "
141-
+ "allowed at the top level");
142-
}
143-
}
144133
context.queryResult().aggregations(new InternalAggregations(aggregations,
145134
context.request().source().aggregations()::buildPipelineTree));
146135

server/src/main/java/org/elasticsearch/search/aggregations/PipelineAggregationBuilder.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ public Collection<PipelineAggregationBuilder> getSiblingPipelineAggregations() {
120120
return siblingPipelineAggregations;
121121
}
122122

123+
@Override
124+
public void validateHasParent(String type, String name) {
125+
addValidationError(type + " aggregation [" + name + "] must be declared inside of another aggregation");
126+
}
127+
123128
@Override
124129
public void validateParentAggSequentiallyOrdered(String type, String name) {
125130
addValidationError(type + " aggregation [" + name
@@ -145,6 +150,11 @@ public Collection<PipelineAggregationBuilder> getSiblingPipelineAggregations() {
145150
return parent.getPipelineAggregations();
146151
}
147152

153+
@Override
154+
public void validateHasParent(String type, String name) {
155+
// There is a parent inside the tree.
156+
}
157+
148158
@Override
149159
public void validateParentAggSequentiallyOrdered(String type, String name) {
150160
if (parent instanceof HistogramAggregationBuilder) {
@@ -195,6 +205,11 @@ public void addBucketPathValidationError(String error) {
195205
addValidationError(PipelineAggregator.Parser.BUCKETS_PATH.getPreferredName() + ' ' + error);
196206
}
197207

208+
/**
209+
* Validates that there <strong>is</strong> a parent aggregation.
210+
*/
211+
public abstract void validateHasParent(String type, String name);
212+
198213
/**
199214
* Validates that the parent is sequentially ordered.
200215
*/

server/src/main/java/org/elasticsearch/search/aggregations/pipeline/BucketScriptPipelineAggregationBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ protected XContentBuilder internalXContent(XContentBuilder builder, Params param
200200

201201
@Override
202202
protected void validate(ValidationContext context) {
203-
// Nothing to check
203+
context.validateHasParent(NAME, name);
204204
}
205205

206206
@Override

server/src/main/java/org/elasticsearch/search/aggregations/pipeline/BucketSelectorPipelineAggregationBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ public static BucketSelectorPipelineAggregationBuilder parse(String reducerName,
195195

196196
@Override
197197
protected void validate(ValidationContext context) {
198-
// Nothing to check
198+
context.validateHasParent(NAME, name);
199199
}
200200

201201
@Override

server/src/main/java/org/elasticsearch/search/aggregations/pipeline/BucketSortPipelineAggregationBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ protected PipelineAggregator createInternal(Map<String, Object> metadata) {
141141

142142
@Override
143143
protected void validate(ValidationContext context) {
144+
context.validateHasParent(NAME, name);
144145
if (sorts.isEmpty() && size == null && from == 0) {
145146
context.addValidationError("[" + name + "] is configured to perform nothing. Please set either of "
146147
+ Arrays.asList(SearchSourceBuilder.SORT_FIELD.getPreferredName(), SIZE.getPreferredName(), FROM.getPreferredName())
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.search.aggregations.pipeline;
21+
22+
import org.elasticsearch.script.Script;
23+
import org.elasticsearch.search.aggregations.BasePipelineAggregationTestCase;
24+
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
28+
import static java.util.Collections.emptyList;
29+
import static java.util.Collections.emptyMap;
30+
import static org.hamcrest.CoreMatchers.equalTo;
31+
32+
public class BucketScriptPipelineAggregationBuilderTests extends BasePipelineAggregationTestCase<BucketScriptPipelineAggregationBuilder> {
33+
@Override
34+
protected BucketScriptPipelineAggregationBuilder createTestAggregatorFactory() {
35+
Map<String, String> bucketsPathsMap = new HashMap<>();
36+
int targetBucketsPathsMapSize = randomInt(5);
37+
while (bucketsPathsMap.size() < targetBucketsPathsMapSize) {
38+
bucketsPathsMap.put(randomAlphaOfLength(5), randomAlphaOfLength(5));
39+
}
40+
Script script = new Script(randomAlphaOfLength(4));
41+
return new BucketScriptPipelineAggregationBuilder(randomAlphaOfLength(3), bucketsPathsMap, script);
42+
}
43+
44+
public void testNoParent() {
45+
assertThat(validate(emptyList(), new BucketScriptPipelineAggregationBuilder("foo", emptyMap(), new Script("foo"))),
46+
equalTo("Validation Failed: 1: bucket_script aggregation [foo] must be declared inside of another aggregation;"));
47+
}
48+
}

server/src/test/java/org/elasticsearch/search/aggregations/pipeline/BucketSelectorTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
import java.util.HashMap;
2828
import java.util.Map;
2929

30+
import static java.util.Collections.emptyList;
31+
import static java.util.Collections.emptyMap;
32+
import static org.hamcrest.CoreMatchers.equalTo;
33+
3034
public class BucketSelectorTests extends BasePipelineAggregationTestCase<BucketSelectorPipelineAggregationBuilder> {
3135

3236
@Override
@@ -56,4 +60,8 @@ protected BucketSelectorPipelineAggregationBuilder createTestAggregatorFactory()
5660
return factory;
5761
}
5862

63+
public void testNoParent() {
64+
assertThat(validate(emptyList(), new BucketSelectorPipelineAggregationBuilder("foo", emptyMap(), new Script("foo"))),
65+
equalTo("Validation Failed: 1: bucket_selector aggregation [foo] must be declared inside of another aggregation;"));
66+
}
5967
}

server/src/test/java/org/elasticsearch/search/aggregations/pipeline/BucketSortTests.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919
package org.elasticsearch.search.aggregations.pipeline;
2020

2121
import org.elasticsearch.search.aggregations.BasePipelineAggregationTestCase;
22-
import org.elasticsearch.search.aggregations.pipeline.BucketHelpers;
23-
import org.elasticsearch.search.aggregations.pipeline.BucketSortPipelineAggregationBuilder;
2422
import org.elasticsearch.search.sort.FieldSortBuilder;
2523
import org.elasticsearch.search.sort.SortOrder;
2624

2725
import java.util.ArrayList;
2826
import java.util.Collections;
2927
import java.util.List;
3028

29+
import static java.util.Collections.emptyList;
30+
import static java.util.Collections.singletonList;
3131
import static org.hamcrest.Matchers.equalTo;
3232

3333
public class BucketSortTests extends BasePipelineAggregationTestCase<BucketSortPipelineAggregationBuilder> {
@@ -85,4 +85,10 @@ public void testNullGapPolicy() {
8585
() -> new BucketSortPipelineAggregationBuilder("foo", Collections.emptyList()).gapPolicy(null));
8686
assertThat(e.getMessage(), equalTo("[gap_policy] must not be null: [foo]"));
8787
}
88+
89+
public void testNoParent() {
90+
List<FieldSortBuilder> sorts = singletonList(new FieldSortBuilder("bar"));
91+
assertThat(validate(emptyList(), new BucketSortPipelineAggregationBuilder("foo", sorts)),
92+
equalTo("Validation Failed: 1: bucket_sort aggregation [foo] must be declared inside of another aggregation;"));
93+
}
8894
}

0 commit comments

Comments
 (0)