More pipeline aggregation cleanup (backport of #54298) (#54890)

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`.
This commit is contained in:
Nik Everett 2020-04-07 10:40:34 -04:00 committed by GitHub
parent 076c199484
commit 915092dc28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 105 additions and 15 deletions

View File

@ -98,3 +98,26 @@ setup:
the_bad_max:
max_bucket:
buckets_path: "the_terms>the_percentiles"
---
"Top level bucket_sort":
- skip:
version: " - 7.7.99"
reason: This validation was changed in 7.8.0
- do:
catch: /bucket_sort aggregation \[the_bad_bucket_sort\] must be declared inside of another aggregation/
search:
body:
aggs:
the_terms:
terms:
field: "int_field"
aggs:
the_max:
max:
field: "int_field"
the_bad_bucket_sort:
bucket_sort:
sort:
- the_terms>the_max

View File

@ -24,8 +24,6 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.search.SearchPhase;
import org.elasticsearch.search.aggregations.bucket.global.GlobalAggregator;
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
import org.elasticsearch.search.aggregations.pipeline.SiblingPipelineAggregator;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.search.profile.query.CollectorResult;
import org.elasticsearch.search.profile.query.InternalProfileCollector;
@ -132,15 +130,6 @@ public class AggregationPhase implements SearchPhase {
throw new AggregationExecutionException("Failed to build aggregation [" + aggregator.name() + "]", e);
}
}
List<PipelineAggregator> pipelineAggregators = context.aggregations().factories().createPipelineAggregators();
for (PipelineAggregator pipelineAggregator : pipelineAggregators) {
if (false == pipelineAggregator instanceof SiblingPipelineAggregator) {
// TODO move this to request validation after #53669
throw new AggregationExecutionException("Invalid pipeline aggregation named [" + pipelineAggregator.name()
+ "] of type [" + pipelineAggregator.getWriteableName() + "]. Only sibling pipeline aggregations are "
+ "allowed at the top level");
}
}
context.queryResult().aggregations(new InternalAggregations(aggregations,
context.request().source().aggregations()::buildPipelineTree));

View File

@ -120,6 +120,11 @@ public abstract class PipelineAggregationBuilder implements NamedWriteable, Base
return siblingPipelineAggregations;
}
@Override
public void validateHasParent(String type, String name) {
addValidationError(type + " aggregation [" + name + "] must be declared inside of another aggregation");
}
@Override
public void validateParentAggSequentiallyOrdered(String type, String name) {
addValidationError(type + " aggregation [" + name
@ -145,6 +150,11 @@ public abstract class PipelineAggregationBuilder implements NamedWriteable, Base
return parent.getPipelineAggregations();
}
@Override
public void validateHasParent(String type, String name) {
// There is a parent inside the tree.
}
@Override
public void validateParentAggSequentiallyOrdered(String type, String name) {
if (parent instanceof HistogramAggregationBuilder) {
@ -195,6 +205,11 @@ public abstract class PipelineAggregationBuilder implements NamedWriteable, Base
addValidationError(PipelineAggregator.Parser.BUCKETS_PATH.getPreferredName() + ' ' + error);
}
/**
* Validates that there <strong>is</strong> a parent aggregation.
*/
public abstract void validateHasParent(String type, String name);
/**
* Validates that the parent is sequentially ordered.
*/

View File

@ -200,7 +200,7 @@ public class BucketScriptPipelineAggregationBuilder extends AbstractPipelineAggr
@Override
protected void validate(ValidationContext context) {
// Nothing to check
context.validateHasParent(NAME, name);
}
@Override

View File

@ -195,7 +195,7 @@ public class BucketSelectorPipelineAggregationBuilder extends AbstractPipelineAg
@Override
protected void validate(ValidationContext context) {
// Nothing to check
context.validateHasParent(NAME, name);
}
@Override

View File

@ -141,6 +141,7 @@ public class BucketSortPipelineAggregationBuilder extends AbstractPipelineAggreg
@Override
protected void validate(ValidationContext context) {
context.validateHasParent(NAME, name);
if (sorts.isEmpty() && size == null && from == 0) {
context.addValidationError("[" + name + "] is configured to perform nothing. Please set either of "
+ Arrays.asList(SearchSourceBuilder.SORT_FIELD.getPreferredName(), SIZE.getPreferredName(), FROM.getPreferredName())

View File

@ -0,0 +1,48 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.search.aggregations.pipeline;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.BasePipelineAggregationTestCase;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static org.hamcrest.CoreMatchers.equalTo;
public class BucketScriptPipelineAggregationBuilderTests extends BasePipelineAggregationTestCase<BucketScriptPipelineAggregationBuilder> {
@Override
protected BucketScriptPipelineAggregationBuilder createTestAggregatorFactory() {
Map<String, String> bucketsPathsMap = new HashMap<>();
int targetBucketsPathsMapSize = randomInt(5);
while (bucketsPathsMap.size() < targetBucketsPathsMapSize) {
bucketsPathsMap.put(randomAlphaOfLength(5), randomAlphaOfLength(5));
}
Script script = new Script(randomAlphaOfLength(4));
return new BucketScriptPipelineAggregationBuilder(randomAlphaOfLength(3), bucketsPathsMap, script);
}
public void testNoParent() {
assertThat(validate(emptyList(), new BucketScriptPipelineAggregationBuilder("foo", emptyMap(), new Script("foo"))),
equalTo("Validation Failed: 1: bucket_script aggregation [foo] must be declared inside of another aggregation;"));
}
}

View File

@ -27,6 +27,10 @@ import org.elasticsearch.search.aggregations.pipeline.BucketHelpers.GapPolicy;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static org.hamcrest.CoreMatchers.equalTo;
public class BucketSelectorTests extends BasePipelineAggregationTestCase<BucketSelectorPipelineAggregationBuilder> {
@Override
@ -56,4 +60,8 @@ public class BucketSelectorTests extends BasePipelineAggregationTestCase<BucketS
return factory;
}
public void testNoParent() {
assertThat(validate(emptyList(), new BucketSelectorPipelineAggregationBuilder("foo", emptyMap(), new Script("foo"))),
equalTo("Validation Failed: 1: bucket_selector aggregation [foo] must be declared inside of another aggregation;"));
}
}

View File

@ -19,8 +19,6 @@
package org.elasticsearch.search.aggregations.pipeline;
import org.elasticsearch.search.aggregations.BasePipelineAggregationTestCase;
import org.elasticsearch.search.aggregations.pipeline.BucketHelpers;
import org.elasticsearch.search.aggregations.pipeline.BucketSortPipelineAggregationBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
@ -28,6 +26,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.equalTo;
public class BucketSortTests extends BasePipelineAggregationTestCase<BucketSortPipelineAggregationBuilder> {
@ -85,4 +85,10 @@ public class BucketSortTests extends BasePipelineAggregationTestCase<BucketSortP
() -> new BucketSortPipelineAggregationBuilder("foo", Collections.emptyList()).gapPolicy(null));
assertThat(e.getMessage(), equalTo("[gap_policy] must not be null: [foo]"));
}
public void testNoParent() {
List<FieldSortBuilder> sorts = singletonList(new FieldSortBuilder("bar"));
assertThat(validate(emptyList(), new BucketSortPipelineAggregationBuilder("foo", sorts)),
equalTo("Validation Failed: 1: bucket_sort aggregation [foo] must be declared inside of another aggregation;"));
}
}