Skip to content

Commit 29fd56d

Browse files
authored
[8.19] Reject invalid reverse_nested aggs (#137047) (#137460)
* Reject invalid `reverse_nested` aggs (#137047) (cherry picked from commit b46f245)
1 parent 9e3a7d3 commit 29fd56d

File tree

6 files changed

+81
-3
lines changed

6 files changed

+81
-3
lines changed

docs/changelog/137047.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 137047
2+
summary: Reject invalid `reverse_nested` aggs
3+
area: Aggregations
4+
type: bug
5+
issues: []

modules/aggregations/src/yamlRestTest/resources/rest-api-spec/test/aggregations/nested.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,33 @@ setup:
194194
- match: { aggregations.courses.highpass_filter.unnest.department.buckets.0.doc_count: 1 }
195195
- match: { aggregations.courses.highpass_filter.unnest.department.buckets.1.key: math }
196196
- match: { aggregations.courses.highpass_filter.unnest.department.buckets.1.doc_count: 1 }
197+
---
198+
"Illegal reverse nested aggregation to a child nested object":
199+
- requires:
200+
capabilities:
201+
- method: POST
202+
path: /_search
203+
capabilities: [ reject_invalid_reverse_nesting ]
204+
test_runner_features: [ capabilities ]
205+
reason: "search does not yet reject invalid reverse nesting paths"
206+
- do:
207+
catch: /Reverse nested path \[courses.sessions\] is not a parent of the current nested scope \[courses\]/
208+
search:
209+
index: test
210+
body:
211+
{
212+
"aggs": {
213+
"parent_nested": {
214+
"nested": {
215+
"path": "courses"
216+
},
217+
"aggs": {
218+
"invalid_reverse_nested": {
219+
"reverse_nested": {
220+
"path": "courses.sessions"
221+
}
222+
}
223+
}
224+
}
225+
}
226+
}

server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/bucket/ReverseNestedIT.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,15 +473,19 @@ public void testReverseNestedAggWithoutNestedAgg() {
473473
public void testNonExistingNestedField() throws Exception {
474474
assertNoFailuresAndResponse(
475475
prepareSearch("idx2").setQuery(matchAllQuery())
476-
.addAggregation(nested("nested2", "nested1.nested2").subAggregation(reverseNested("incorrect").path("nested3"))),
476+
.addAggregation(nested("nested2", "nested1.nested2"))
477+
.addAggregation(nested("incorrect", "nested1.incorrect")),
477478
response -> {
478479

479480
Nested nested = response.getAggregations().get("nested2");
480481
assertThat(nested, notNullValue());
481482
assertThat(nested.getName(), equalTo("nested2"));
483+
assertThat(nested.getDocCount(), is(27L));
482484

483-
ReverseNested reverseNested = nested.getAggregations().get("incorrect");
484-
assertThat(reverseNested.getDocCount(), is(0L));
485+
Nested incorrect = response.getAggregations().get("incorrect");
486+
assertThat(incorrect, notNullValue());
487+
assertThat(incorrect.getName(), equalTo("incorrect"));
488+
assertThat(incorrect.getDocCount(), is(0L));
485489
}
486490
);
487491

server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ private SearchCapabilities() {}
5050
private static final String EXCLUDE_VECTORS_PARAM = "exclude_vectors_param";
5151
private static final String DENSE_VECTOR_UPDATABLE_BBQ = "dense_vector_updatable_bbq";
5252
private static final String BUCKET_SCRIPT_PARENT_MULTI_BUCKET_ERROR = "bucket_script_parent_multi_bucket_error";
53+
private static final String REJECT_INVALID_REVERSE_NESTING = "reject_invalid_reverse_nesting";
5354

5455
public static final Set<String> CAPABILITIES;
5556
static {
@@ -72,6 +73,7 @@ private SearchCapabilities() {}
7273
capabilities.add(EXCLUDE_VECTORS_PARAM);
7374
capabilities.add(DENSE_VECTOR_UPDATABLE_BBQ);
7475
capabilities.add(BUCKET_SCRIPT_PARENT_MULTI_BUCKET_ERROR);
76+
capabilities.add(REJECT_INVALID_REVERSE_NESTING);
7577
CAPABILITIES = Set.copyOf(capabilities);
7678
}
7779
}

server/src/main/java/org/elasticsearch/search/aggregations/bucket/nested/ReverseNestedAggregationBuilder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@ protected AggregatorFactory doBuild(AggregationContext context, AggregatorFactor
8484
throw new IllegalArgumentException("Reverse nested aggregation [" + name + "] can only be used inside a [nested] aggregation");
8585
}
8686

87+
if (path != null) {
88+
NestedObjectMapper currentParent = context.nestedScope().getObjectMapper();
89+
if (currentParent != null) {
90+
String parentPath = currentParent.fullPath();
91+
if (parentPath.equals(path) == false && parentPath.startsWith(path + ".") == false) {
92+
throw new IllegalArgumentException(
93+
"Reverse nested path [" + path + "] is not a parent of the current nested scope [" + parentPath + "]"
94+
);
95+
}
96+
}
97+
}
98+
8799
NestedObjectMapper nestedMapper = null;
88100
if (path != null) {
89101
nestedMapper = context.nestedLookup().getNestedMappers().get(path);

server/src/test/java/org/elasticsearch/search/aggregations/bucket/nested/ReverseNestedAggregatorTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,29 @@ public void testNestedUnderTerms() throws IOException {
241241
protected List<ObjectMapper> objectMappers() {
242242
return NestedAggregatorTests.MOCK_OBJECT_MAPPERS;
243243
}
244+
245+
public void testErrorOnInvalidReverseNestedWithPath() throws IOException {
246+
AggregationBuilder aggregationBuilder = nested("n1", NESTED_OBJECT).subAggregation(
247+
nested("n2", NESTED_OBJECT + ".child").subAggregation(
248+
reverseNested("r1").path(NESTED_OBJECT).subAggregation(reverseNested("r2").path(NESTED_OBJECT + ".child"))
249+
)
250+
);
251+
252+
try (Directory directory = newDirectory()) {
253+
try (RandomIndexWriter iw = newRandomIndexWriterWithLogDocMergePolicy(directory)) {
254+
// No docs needed
255+
}
256+
try (DirectoryReader indexReader = wrapInMockESDirectoryReader(DirectoryReader.open(directory))) {
257+
IllegalArgumentException e = assertThrows(
258+
IllegalArgumentException.class,
259+
() -> searchAndReduce(indexReader, new AggTestConfig(aggregationBuilder))
260+
);
261+
assertThat(
262+
e.getMessage(),
263+
equalTo("Reverse nested path [nested_object.child] is not a parent of the current nested scope [nested_object]")
264+
);
265+
266+
}
267+
}
268+
}
244269
}

0 commit comments

Comments
 (0)