Skip to content

Commit d05aee7

Browse files
committed
inner hits: Do not allow inner hits that use _source and have a non nested object field as parent
Closes #25315
1 parent c3746b2 commit d05aee7

File tree

7 files changed

+91
-24
lines changed

7 files changed

+91
-24
lines changed

core/src/main/java/org/elasticsearch/index/cache/bitset/BitsetFilterCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ public IndexWarmer.TerminationHandle warmReader(final IndexShard indexShard, fin
239239
hasNested = true;
240240
for (ObjectMapper objectMapper : docMapper.objectMappers().values()) {
241241
if (objectMapper.nested().isNested()) {
242-
ObjectMapper parentObjectMapper = docMapper.findParentObjectMapper(objectMapper);
242+
ObjectMapper parentObjectMapper = objectMapper.getParentObjectMapper(mapperService);
243243
if (parentObjectMapper != null && parentObjectMapper.nested().isNested()) {
244244
warmUp.add(parentObjectMapper.nestedTypeFilter());
245245
}

core/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -292,21 +292,6 @@ public ObjectMapper findNestedObjectMapper(int nestedDocId, SearchContext sc, Le
292292
return nestedObjectMapper;
293293
}
294294

295-
/**
296-
* Returns the parent {@link ObjectMapper} instance of the specified object mapper or <code>null</code> if there
297-
* isn't any.
298-
*/
299-
// TODO: We should add: ObjectMapper#getParentObjectMapper()
300-
public ObjectMapper findParentObjectMapper(ObjectMapper objectMapper) {
301-
int indexOfLastDot = objectMapper.fullPath().lastIndexOf('.');
302-
if (indexOfLastDot != -1) {
303-
String parentNestObjectPath = objectMapper.fullPath().substring(0, indexOfLastDot);
304-
return objectMappers().get(parentNestObjectPath);
305-
} else {
306-
return null;
307-
}
308-
}
309-
310295
public boolean isParent(String type) {
311296
return mapperService.getParentTypes().contains(type);
312297
}

core/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,35 @@ public final Dynamic dynamic() {
396396
return dynamic;
397397
}
398398

399+
/**
400+
* Returns the parent {@link ObjectMapper} instance of the specified object mapper or <code>null</code> if there
401+
* isn't any.
402+
*/
403+
public ObjectMapper getParentObjectMapper(MapperService mapperService) {
404+
int indexOfLastDot = fullPath().lastIndexOf('.');
405+
if (indexOfLastDot != -1) {
406+
String parentNestObjectPath = fullPath().substring(0, indexOfLastDot);
407+
return mapperService.getObjectMapper(parentNestObjectPath);
408+
} else {
409+
return null;
410+
}
411+
}
412+
413+
/**
414+
* Returns whether all parent objects fields are nested too.
415+
*/
416+
public boolean parentObjectMapperAreNested(MapperService mapperService) {
417+
for (ObjectMapper parent = getParentObjectMapper(mapperService);
418+
parent != null;
419+
parent = parent.getParentObjectMapper(mapperService)) {
420+
421+
if (parent.nested().isNested() == false) {
422+
return false;
423+
}
424+
}
425+
return true;
426+
}
427+
399428
@Override
400429
public ObjectMapper merge(Mapper mergeWith, boolean updateAllTypes) {
401430
if (!(mergeWith instanceof ObjectMapper)) {

core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,11 @@ protected void doBuild(SearchContext parentSearchContext,
353353
name, parentSearchContext, parentObjectMapper, nestedObjectMapper
354354
);
355355
setupInnerHitsContext(queryShardContext, nestedInnerHits);
356+
if ((nestedInnerHits.hasFetchSourceContext() == false || nestedInnerHits.sourceRequested()) &&
357+
nestedObjectMapper.parentObjectMapperAreNested(parentSearchContext.mapperService()) == false) {
358+
throw new IllegalArgumentException("Cannot execute inner hits. One or more parent object fields of nested field [" +
359+
nestedObjectMapper.name() + "] are not nested. All parent fields need to be nested fields too");
360+
}
356361
queryShardContext.nestedScope().previousLevel();
357362
innerHitsContext.addInnerHitDefinition(nestedInnerHits);
358363
}

core/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.elasticsearch.index.fieldvisitor.FieldsVisitor;
4141
import org.elasticsearch.index.mapper.DocumentMapper;
4242
import org.elasticsearch.index.mapper.MappedFieldType;
43+
import org.elasticsearch.index.mapper.MapperService;
4344
import org.elasticsearch.index.mapper.ObjectMapper;
4445
import org.elasticsearch.index.mapper.SourceFieldMapper;
4546
import org.elasticsearch.index.mapper.Uid;
@@ -246,7 +247,7 @@ private SearchHit createNestedSearchHit(SearchContext context, int nestedTopDocI
246247
ObjectMapper nestedObjectMapper = documentMapper.findNestedObjectMapper(nestedSubDocId, context, subReaderContext);
247248
assert nestedObjectMapper != null;
248249
SearchHit.NestedIdentity nestedIdentity =
249-
getInternalNestedIdentity(context, nestedSubDocId, subReaderContext, documentMapper, nestedObjectMapper);
250+
getInternalNestedIdentity(context, nestedSubDocId, subReaderContext, context.mapperService(), nestedObjectMapper);
250251

251252
if (source != null) {
252253
Tuple<XContentType, Map<String, Object>> tuple = XContentHelper.convertToMap(source, true);
@@ -311,17 +312,15 @@ private Map<String, DocumentField> getSearchFields(SearchContext context, int ne
311312
return searchFields;
312313
}
313314

314-
private SearchHit.NestedIdentity getInternalNestedIdentity(SearchContext context, int nestedSubDocId,
315-
LeafReaderContext subReaderContext, DocumentMapper documentMapper,
316-
ObjectMapper nestedObjectMapper) throws IOException {
315+
private SearchHit.NestedIdentity getInternalNestedIdentity(SearchContext context, int nestedSubDocId, LeafReaderContext subReaderContext, MapperService mapperService, ObjectMapper nestedObjectMapper) throws IOException {
317316
int currentParent = nestedSubDocId;
318317
ObjectMapper nestedParentObjectMapper;
319318
ObjectMapper current = nestedObjectMapper;
320319
String originalName = nestedObjectMapper.name();
321320
SearchHit.NestedIdentity nestedIdentity = null;
322321
do {
323322
Query parentFilter;
324-
nestedParentObjectMapper = documentMapper.findParentObjectMapper(current);
323+
nestedParentObjectMapper = current.getParentObjectMapper(mapperService);
325324
if (nestedParentObjectMapper != null) {
326325
if (nestedParentObjectMapper.nested().isNested() == false) {
327326
current = nestedParentObjectMapper;

core/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.Collections;
3737
import java.util.function.Function;
3838

39+
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
3940
import static org.hamcrest.Matchers.containsString;
4041
import static org.hamcrest.Matchers.equalTo;
4142
import static org.hamcrest.Matchers.nullValue;
@@ -428,4 +429,35 @@ public void testLimitOfNestedFieldsWithMultiTypePerIndex() throws Exception {
428429
createIndex("test5", Settings.builder().put(MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING.getKey(), 0).build())
429430
.mapperService().merge("type", new CompressedXContent(mapping.apply("type")), MergeReason.MAPPING_RECOVERY, false);
430431
}
432+
433+
public void testParentObjectMapperAreNested() throws Exception {
434+
MapperService mapperService = createIndex("index1", Settings.EMPTY, "doc", jsonBuilder().startObject()
435+
.startObject("properties")
436+
.startObject("comments")
437+
.field("type", "nested")
438+
.startObject("properties")
439+
.startObject("messages")
440+
.field("type", "nested").endObject()
441+
.endObject()
442+
.endObject()
443+
.endObject()
444+
.endObject()).mapperService();
445+
ObjectMapper objectMapper = mapperService.getObjectMapper("comments.messages");
446+
assertTrue(objectMapper.parentObjectMapperAreNested(mapperService));
447+
448+
mapperService = createIndex("index2", Settings.EMPTY, "doc", jsonBuilder().startObject()
449+
.startObject("properties")
450+
.startObject("comments")
451+
.field("type", "object")
452+
.startObject("properties")
453+
.startObject("messages")
454+
.field("type", "nested").endObject()
455+
.endObject()
456+
.endObject()
457+
.endObject()
458+
.endObject()).mapperService();
459+
objectMapper = mapperService.getObjectMapper("comments.messages");
460+
assertFalse(objectMapper.parentObjectMapperAreNested(mapperService));
461+
}
462+
431463
}

core/src/test/java/org/elasticsearch/search/fetch/subphase/InnerHitsIT.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,26 @@ public void testInnerHitsWithObjectFieldThatHasANestedField() throws Exception {
411411
.endObject()));
412412
indexRandom(true, requests);
413413

414+
SearchPhaseExecutionException e = expectThrows(
415+
SearchPhaseExecutionException.class,
416+
() -> client().prepareSearch("articles").setQuery(nestedQuery("comments.messages",
417+
matchQuery("comments.messages.message", "fox"), ScoreMode.Avg).innerHit(new InnerHitBuilder())).get()
418+
);
419+
assertEquals("Cannot execute inner hits. One or more parent object fields of nested field [comments.messages] are " +
420+
"not nested. All parent fields need to be nested fields too", e.shardFailures()[0].getCause().getMessage());
421+
422+
e = expectThrows(
423+
SearchPhaseExecutionException.class,
424+
() -> client().prepareSearch("articles").setQuery(nestedQuery("comments.messages",
425+
matchQuery("comments.messages.message", "fox"), ScoreMode.Avg).innerHit(new InnerHitBuilder()
426+
.setFetchSourceContext(new FetchSourceContext(true)))).get()
427+
);
428+
assertEquals("Cannot execute inner hits. One or more parent object fields of nested field [comments.messages] are " +
429+
"not nested. All parent fields need to be nested fields too", e.shardFailures()[0].getCause().getMessage());
430+
414431
SearchResponse response = client().prepareSearch("articles")
415432
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox"), ScoreMode.Avg)
416-
.innerHit(new InnerHitBuilder())).get();
433+
.innerHit(new InnerHitBuilder().setFetchSourceContext(new FetchSourceContext(false)))).get();
417434
assertNoFailures(response);
418435
assertHitCount(response, 1);
419436
SearchHit hit = response.getHits().getAt(0);
@@ -427,7 +444,7 @@ public void testInnerHitsWithObjectFieldThatHasANestedField() throws Exception {
427444

428445
response = client().prepareSearch("articles")
429446
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "bear"), ScoreMode.Avg)
430-
.innerHit(new InnerHitBuilder())).get();
447+
.innerHit(new InnerHitBuilder().setFetchSourceContext(new FetchSourceContext(false)))).get();
431448
assertNoFailures(response);
432449
assertHitCount(response, 1);
433450
hit = response.getHits().getAt(0);
@@ -448,7 +465,7 @@ public void testInnerHitsWithObjectFieldThatHasANestedField() throws Exception {
448465
indexRandom(true, requests);
449466
response = client().prepareSearch("articles")
450467
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox"), ScoreMode.Avg)
451-
.innerHit(new InnerHitBuilder())).get();
468+
.innerHit(new InnerHitBuilder().setFetchSourceContext(new FetchSourceContext(false)))).get();
452469
assertNoFailures(response);
453470
assertHitCount(response, 1);
454471
hit = response.getHits().getAt(0);;

0 commit comments

Comments
 (0)