inner hits: Do not allow inner hits that use _source and have a non nested object field as parent
Closes #25315
This commit is contained in:
parent
c3746b268c
commit
d05aee7eda
|
@ -239,7 +239,7 @@ public final class BitsetFilterCache extends AbstractIndexComponent implements I
|
||||||
hasNested = true;
|
hasNested = true;
|
||||||
for (ObjectMapper objectMapper : docMapper.objectMappers().values()) {
|
for (ObjectMapper objectMapper : docMapper.objectMappers().values()) {
|
||||||
if (objectMapper.nested().isNested()) {
|
if (objectMapper.nested().isNested()) {
|
||||||
ObjectMapper parentObjectMapper = docMapper.findParentObjectMapper(objectMapper);
|
ObjectMapper parentObjectMapper = objectMapper.getParentObjectMapper(mapperService);
|
||||||
if (parentObjectMapper != null && parentObjectMapper.nested().isNested()) {
|
if (parentObjectMapper != null && parentObjectMapper.nested().isNested()) {
|
||||||
warmUp.add(parentObjectMapper.nestedTypeFilter());
|
warmUp.add(parentObjectMapper.nestedTypeFilter());
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,21 +292,6 @@ public class DocumentMapper implements ToXContentFragment {
|
||||||
return nestedObjectMapper;
|
return nestedObjectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the parent {@link ObjectMapper} instance of the specified object mapper or <code>null</code> if there
|
|
||||||
* isn't any.
|
|
||||||
*/
|
|
||||||
// TODO: We should add: ObjectMapper#getParentObjectMapper()
|
|
||||||
public ObjectMapper findParentObjectMapper(ObjectMapper objectMapper) {
|
|
||||||
int indexOfLastDot = objectMapper.fullPath().lastIndexOf('.');
|
|
||||||
if (indexOfLastDot != -1) {
|
|
||||||
String parentNestObjectPath = objectMapper.fullPath().substring(0, indexOfLastDot);
|
|
||||||
return objectMappers().get(parentNestObjectPath);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isParent(String type) {
|
public boolean isParent(String type) {
|
||||||
return mapperService.getParentTypes().contains(type);
|
return mapperService.getParentTypes().contains(type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -396,6 +396,35 @@ public class ObjectMapper extends Mapper implements Cloneable {
|
||||||
return dynamic;
|
return dynamic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parent {@link ObjectMapper} instance of the specified object mapper or <code>null</code> if there
|
||||||
|
* isn't any.
|
||||||
|
*/
|
||||||
|
public ObjectMapper getParentObjectMapper(MapperService mapperService) {
|
||||||
|
int indexOfLastDot = fullPath().lastIndexOf('.');
|
||||||
|
if (indexOfLastDot != -1) {
|
||||||
|
String parentNestObjectPath = fullPath().substring(0, indexOfLastDot);
|
||||||
|
return mapperService.getObjectMapper(parentNestObjectPath);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether all parent objects fields are nested too.
|
||||||
|
*/
|
||||||
|
public boolean parentObjectMapperAreNested(MapperService mapperService) {
|
||||||
|
for (ObjectMapper parent = getParentObjectMapper(mapperService);
|
||||||
|
parent != null;
|
||||||
|
parent = parent.getParentObjectMapper(mapperService)) {
|
||||||
|
|
||||||
|
if (parent.nested().isNested() == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObjectMapper merge(Mapper mergeWith, boolean updateAllTypes) {
|
public ObjectMapper merge(Mapper mergeWith, boolean updateAllTypes) {
|
||||||
if (!(mergeWith instanceof ObjectMapper)) {
|
if (!(mergeWith instanceof ObjectMapper)) {
|
||||||
|
|
|
@ -353,6 +353,11 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
||||||
name, parentSearchContext, parentObjectMapper, nestedObjectMapper
|
name, parentSearchContext, parentObjectMapper, nestedObjectMapper
|
||||||
);
|
);
|
||||||
setupInnerHitsContext(queryShardContext, nestedInnerHits);
|
setupInnerHitsContext(queryShardContext, nestedInnerHits);
|
||||||
|
if ((nestedInnerHits.hasFetchSourceContext() == false || nestedInnerHits.sourceRequested()) &&
|
||||||
|
nestedObjectMapper.parentObjectMapperAreNested(parentSearchContext.mapperService()) == false) {
|
||||||
|
throw new IllegalArgumentException("Cannot execute inner hits. One or more parent object fields of nested field [" +
|
||||||
|
nestedObjectMapper.name() + "] are not nested. All parent fields need to be nested fields too");
|
||||||
|
}
|
||||||
queryShardContext.nestedScope().previousLevel();
|
queryShardContext.nestedScope().previousLevel();
|
||||||
innerHitsContext.addInnerHitDefinition(nestedInnerHits);
|
innerHitsContext.addInnerHitDefinition(nestedInnerHits);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.elasticsearch.index.fieldvisitor.CustomFieldsVisitor;
|
||||||
import org.elasticsearch.index.fieldvisitor.FieldsVisitor;
|
import org.elasticsearch.index.fieldvisitor.FieldsVisitor;
|
||||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||||
|
import org.elasticsearch.index.mapper.MapperService;
|
||||||
import org.elasticsearch.index.mapper.ObjectMapper;
|
import org.elasticsearch.index.mapper.ObjectMapper;
|
||||||
import org.elasticsearch.index.mapper.SourceFieldMapper;
|
import org.elasticsearch.index.mapper.SourceFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.Uid;
|
import org.elasticsearch.index.mapper.Uid;
|
||||||
|
@ -246,7 +247,7 @@ public class FetchPhase implements SearchPhase {
|
||||||
ObjectMapper nestedObjectMapper = documentMapper.findNestedObjectMapper(nestedSubDocId, context, subReaderContext);
|
ObjectMapper nestedObjectMapper = documentMapper.findNestedObjectMapper(nestedSubDocId, context, subReaderContext);
|
||||||
assert nestedObjectMapper != null;
|
assert nestedObjectMapper != null;
|
||||||
SearchHit.NestedIdentity nestedIdentity =
|
SearchHit.NestedIdentity nestedIdentity =
|
||||||
getInternalNestedIdentity(context, nestedSubDocId, subReaderContext, documentMapper, nestedObjectMapper);
|
getInternalNestedIdentity(context, nestedSubDocId, subReaderContext, context.mapperService(), nestedObjectMapper);
|
||||||
|
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
Tuple<XContentType, Map<String, Object>> tuple = XContentHelper.convertToMap(source, true);
|
Tuple<XContentType, Map<String, Object>> tuple = XContentHelper.convertToMap(source, true);
|
||||||
|
@ -311,9 +312,7 @@ public class FetchPhase implements SearchPhase {
|
||||||
return searchFields;
|
return searchFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SearchHit.NestedIdentity getInternalNestedIdentity(SearchContext context, int nestedSubDocId,
|
private SearchHit.NestedIdentity getInternalNestedIdentity(SearchContext context, int nestedSubDocId, LeafReaderContext subReaderContext, MapperService mapperService, ObjectMapper nestedObjectMapper) throws IOException {
|
||||||
LeafReaderContext subReaderContext, DocumentMapper documentMapper,
|
|
||||||
ObjectMapper nestedObjectMapper) throws IOException {
|
|
||||||
int currentParent = nestedSubDocId;
|
int currentParent = nestedSubDocId;
|
||||||
ObjectMapper nestedParentObjectMapper;
|
ObjectMapper nestedParentObjectMapper;
|
||||||
ObjectMapper current = nestedObjectMapper;
|
ObjectMapper current = nestedObjectMapper;
|
||||||
|
@ -321,7 +320,7 @@ public class FetchPhase implements SearchPhase {
|
||||||
SearchHit.NestedIdentity nestedIdentity = null;
|
SearchHit.NestedIdentity nestedIdentity = null;
|
||||||
do {
|
do {
|
||||||
Query parentFilter;
|
Query parentFilter;
|
||||||
nestedParentObjectMapper = documentMapper.findParentObjectMapper(current);
|
nestedParentObjectMapper = current.getParentObjectMapper(mapperService);
|
||||||
if (nestedParentObjectMapper != null) {
|
if (nestedParentObjectMapper != null) {
|
||||||
if (nestedParentObjectMapper.nested().isNested() == false) {
|
if (nestedParentObjectMapper.nested().isNested() == false) {
|
||||||
current = nestedParentObjectMapper;
|
current = nestedParentObjectMapper;
|
||||||
|
|
|
@ -36,6 +36,7 @@ import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
@ -428,4 +429,35 @@ public class NestedObjectMapperTests extends ESSingleNodeTestCase {
|
||||||
createIndex("test5", Settings.builder().put(MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING.getKey(), 0).build())
|
createIndex("test5", Settings.builder().put(MapperService.INDEX_MAPPING_NESTED_FIELDS_LIMIT_SETTING.getKey(), 0).build())
|
||||||
.mapperService().merge("type", new CompressedXContent(mapping.apply("type")), MergeReason.MAPPING_RECOVERY, false);
|
.mapperService().merge("type", new CompressedXContent(mapping.apply("type")), MergeReason.MAPPING_RECOVERY, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testParentObjectMapperAreNested() throws Exception {
|
||||||
|
MapperService mapperService = createIndex("index1", Settings.EMPTY, "doc", jsonBuilder().startObject()
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("comments")
|
||||||
|
.field("type", "nested")
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("messages")
|
||||||
|
.field("type", "nested").endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject()).mapperService();
|
||||||
|
ObjectMapper objectMapper = mapperService.getObjectMapper("comments.messages");
|
||||||
|
assertTrue(objectMapper.parentObjectMapperAreNested(mapperService));
|
||||||
|
|
||||||
|
mapperService = createIndex("index2", Settings.EMPTY, "doc", jsonBuilder().startObject()
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("comments")
|
||||||
|
.field("type", "object")
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("messages")
|
||||||
|
.field("type", "nested").endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject()).mapperService();
|
||||||
|
objectMapper = mapperService.getObjectMapper("comments.messages");
|
||||||
|
assertFalse(objectMapper.parentObjectMapperAreNested(mapperService));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -411,9 +411,26 @@ public class InnerHitsIT extends ESIntegTestCase {
|
||||||
.endObject()));
|
.endObject()));
|
||||||
indexRandom(true, requests);
|
indexRandom(true, requests);
|
||||||
|
|
||||||
|
SearchPhaseExecutionException e = expectThrows(
|
||||||
|
SearchPhaseExecutionException.class,
|
||||||
|
() -> client().prepareSearch("articles").setQuery(nestedQuery("comments.messages",
|
||||||
|
matchQuery("comments.messages.message", "fox"), ScoreMode.Avg).innerHit(new InnerHitBuilder())).get()
|
||||||
|
);
|
||||||
|
assertEquals("Cannot execute inner hits. One or more parent object fields of nested field [comments.messages] are " +
|
||||||
|
"not nested. All parent fields need to be nested fields too", e.shardFailures()[0].getCause().getMessage());
|
||||||
|
|
||||||
|
e = expectThrows(
|
||||||
|
SearchPhaseExecutionException.class,
|
||||||
|
() -> client().prepareSearch("articles").setQuery(nestedQuery("comments.messages",
|
||||||
|
matchQuery("comments.messages.message", "fox"), ScoreMode.Avg).innerHit(new InnerHitBuilder()
|
||||||
|
.setFetchSourceContext(new FetchSourceContext(true)))).get()
|
||||||
|
);
|
||||||
|
assertEquals("Cannot execute inner hits. One or more parent object fields of nested field [comments.messages] are " +
|
||||||
|
"not nested. All parent fields need to be nested fields too", e.shardFailures()[0].getCause().getMessage());
|
||||||
|
|
||||||
SearchResponse response = client().prepareSearch("articles")
|
SearchResponse response = client().prepareSearch("articles")
|
||||||
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox"), ScoreMode.Avg)
|
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox"), ScoreMode.Avg)
|
||||||
.innerHit(new InnerHitBuilder())).get();
|
.innerHit(new InnerHitBuilder().setFetchSourceContext(new FetchSourceContext(false)))).get();
|
||||||
assertNoFailures(response);
|
assertNoFailures(response);
|
||||||
assertHitCount(response, 1);
|
assertHitCount(response, 1);
|
||||||
SearchHit hit = response.getHits().getAt(0);
|
SearchHit hit = response.getHits().getAt(0);
|
||||||
|
@ -427,7 +444,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
||||||
|
|
||||||
response = client().prepareSearch("articles")
|
response = client().prepareSearch("articles")
|
||||||
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "bear"), ScoreMode.Avg)
|
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "bear"), ScoreMode.Avg)
|
||||||
.innerHit(new InnerHitBuilder())).get();
|
.innerHit(new InnerHitBuilder().setFetchSourceContext(new FetchSourceContext(false)))).get();
|
||||||
assertNoFailures(response);
|
assertNoFailures(response);
|
||||||
assertHitCount(response, 1);
|
assertHitCount(response, 1);
|
||||||
hit = response.getHits().getAt(0);
|
hit = response.getHits().getAt(0);
|
||||||
|
@ -448,7 +465,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
||||||
indexRandom(true, requests);
|
indexRandom(true, requests);
|
||||||
response = client().prepareSearch("articles")
|
response = client().prepareSearch("articles")
|
||||||
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox"), ScoreMode.Avg)
|
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox"), ScoreMode.Avg)
|
||||||
.innerHit(new InnerHitBuilder())).get();
|
.innerHit(new InnerHitBuilder().setFetchSourceContext(new FetchSourceContext(false)))).get();
|
||||||
assertNoFailures(response);
|
assertNoFailures(response);
|
||||||
assertHitCount(response, 1);
|
assertHitCount(response, 1);
|
||||||
hit = response.getHits().getAt(0);;
|
hit = response.getHits().getAt(0);;
|
||||||
|
|
Loading…
Reference in New Issue