diff --git a/docs/reference/search/request/inner-hits.asciidoc b/docs/reference/search/request/inner-hits.asciidoc index 656471406ef..5e38ac024c2 100644 --- a/docs/reference/search/request/inner-hits.asciidoc +++ b/docs/reference/search/request/inner-hits.asciidoc @@ -152,6 +152,28 @@ An important default is that the `_source` returned in hits inside `inner_hits` So in the above example only the comment part is returned per nested hit and not the entire source of the top level document that contained the the comment. +[[hierarchical-nested-inner-hits]] +==== Hierarchical levels of nested object fields and inner hits. + +If a mapping has multiple levels of hierarchical nested object fields each level can be accessed via dot notated path. +For example if there is a `comments` nested field that contains a `votes` nested field and votes should directly be returned +with the the root hits then the following path can be defined: + +[source,js] +-------------------------------------------------- +{ + "query" : { + "nested" : { + "path" : "comments.votes", + "query" : { ... }, + "inner_hits" : {} + } + } +} +-------------------------------------------------- + +This indirect referencing is only supported for nested inner hits. + [[parent-child-inner-hits]] ==== Parent/child inner hits diff --git a/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java b/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java index 0d85a35e034..bdd5e463caa 100644 --- a/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java @@ -30,7 +30,6 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.mapper.object.ObjectMapper; import org.elasticsearch.index.query.support.InnerHitsQueryParserHelper; import org.elasticsearch.index.query.support.NestedInnerQueryParseSupport; import org.elasticsearch.search.fetch.innerhits.InnerHitsContext; @@ -149,8 +148,7 @@ public class NestedQueryParser implements QueryParser { } if (innerHits != null) { - ObjectMapper parentObjectMapper = childDocumentMapper.findParentObjectMapper(nestedObjectMapper); - InnerHitsContext.NestedInnerHits nestedInnerHits = new InnerHitsContext.NestedInnerHits(innerHits.v2(), getInnerQuery(), null, parentObjectMapper, nestedObjectMapper); + InnerHitsContext.NestedInnerHits nestedInnerHits = new InnerHitsContext.NestedInnerHits(innerHits.v2(), getInnerQuery(), null, getParentObjectMapper(), nestedObjectMapper); String name = innerHits.v1() != null ? innerHits.v1() : path; parseContext.addInnerHits(name, nestedInnerHits); } diff --git a/src/main/java/org/elasticsearch/index/query/support/NestedInnerQueryParseSupport.java b/src/main/java/org/elasticsearch/index/query/support/NestedInnerQueryParseSupport.java index 76bfa4c906c..07142e5e1e6 100644 --- a/src/main/java/org/elasticsearch/index/query/support/NestedInnerQueryParseSupport.java +++ b/src/main/java/org/elasticsearch/index/query/support/NestedInnerQueryParseSupport.java @@ -60,6 +60,7 @@ public class NestedInnerQueryParseSupport { protected DocumentMapper childDocumentMapper; protected ObjectMapper nestedObjectMapper; + private ObjectMapper parentObjectMapper; public NestedInnerQueryParseSupport(XContentParser parser, SearchContext searchContext) { parseContext = searchContext.queryParserService().getParseContext(); @@ -187,6 +188,10 @@ public class NestedInnerQueryParseSupport { return filterFound; } + public ObjectMapper getParentObjectMapper() { + return parentObjectMapper; + } + private void setPathLevel() { ObjectMapper objectMapper = parseContext.nestedScope().getObjectMapper(); if (objectMapper == null) { @@ -195,7 +200,7 @@ public class NestedInnerQueryParseSupport { parentFilter = parseContext.bitsetFilter(objectMapper.nestedTypeFilter()); } childFilter = parseContext.bitsetFilter(nestedObjectMapper.nestedTypeFilter()); - parseContext.nestedScope().nextLevel(nestedObjectMapper); + parentObjectMapper = parseContext.nestedScope().nextLevel(nestedObjectMapper); } private void resetPathLevel() { diff --git a/src/main/java/org/elasticsearch/index/query/support/NestedScope.java b/src/main/java/org/elasticsearch/index/query/support/NestedScope.java index e06cb7b5050..8a7383d4cc5 100644 --- a/src/main/java/org/elasticsearch/index/query/support/NestedScope.java +++ b/src/main/java/org/elasticsearch/index/query/support/NestedScope.java @@ -39,17 +39,19 @@ public final class NestedScope { } /** - * Sets the new current nested level and moves old current nested level down + * Sets the new current nested level and pushes old current nested level down the stack returns that level. */ - public void nextLevel(ObjectMapper level) { + public ObjectMapper nextLevel(ObjectMapper level) { + ObjectMapper previous = levelStack.peek(); levelStack.push(level); + return previous; } /** - * Sets the previous nested level as current nested level and removes the current nested level. + * Sets the previous nested level as current nested level and removes and returns the current nested level. */ - public void previousLevel() { - ObjectMapper level = levelStack.pop(); + public ObjectMapper previousLevel() { + return levelStack.pop(); } } diff --git a/src/main/java/org/elasticsearch/search/fetch/innerhits/InnerHitsParseElement.java b/src/main/java/org/elasticsearch/search/fetch/innerhits/InnerHitsParseElement.java index 2d6e4c2520e..f282439b733 100644 --- a/src/main/java/org/elasticsearch/search/fetch/innerhits/InnerHitsParseElement.java +++ b/src/main/java/org/elasticsearch/search/fetch/innerhits/InnerHitsParseElement.java @@ -163,12 +163,10 @@ public class InnerHitsParseElement implements SearchParseElement { if (!childObjectMapper.nested().isNested()) { throw new ElasticsearchIllegalArgumentException("path [" + nestedPath +"] isn't nested"); } - DocumentMapper childDocumentMapper = smartNameObjectMapper.docMapper(); - parseContext.nestedScope().nextLevel(childObjectMapper); + ObjectMapper parentObjectMapper = parseContext.nestedScope().nextLevel(childObjectMapper); ParseResult parseResult = parseSubSearchContext(searchContext, parseContext, parser); parseContext.nestedScope().previousLevel(); - ObjectMapper parentObjectMapper = childDocumentMapper.findParentObjectMapper(childObjectMapper); return new InnerHitsContext.NestedInnerHits(parseResult.context(), parseResult.query(), parseResult.childInnerHits(), parentObjectMapper, childObjectMapper); } diff --git a/src/test/java/org/elasticsearch/search/innerhits/InnerHitsTests.java b/src/test/java/org/elasticsearch/search/innerhits/InnerHitsTests.java index 2a710e9cd37..710481f4134 100644 --- a/src/test/java/org/elasticsearch/search/innerhits/InnerHitsTests.java +++ b/src/test/java/org/elasticsearch/search/innerhits/InnerHitsTests.java @@ -618,13 +618,30 @@ public class InnerHitsTests extends ElasticsearchIntegrationTest { assertThat(innerHits.getAt(0).getNestedIdentity().getChild().getField().string(), equalTo("remarks")); assertThat(innerHits.getAt(0).getNestedIdentity().getChild().getOffset(), equalTo(0)); + // Directly refer to the second level: + response = client().prepareSearch("articles") + .setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad")).innerHit(new QueryInnerHitBuilder())) + .get(); + assertNoFailures(response); + assertHitCount(response, 1); + assertSearchHit(response, 1, hasId("2")); + assertThat(response.getHits().getAt(0).getInnerHits().size(), equalTo(1)); + innerHits = response.getHits().getAt(0).getInnerHits().get("comments.remarks"); + assertThat(innerHits.totalHits(), equalTo(1l)); + assertThat(innerHits.getHits().length, equalTo(1)); + assertThat(innerHits.getAt(0).getId(), equalTo("2")); + assertThat(innerHits.getAt(0).getNestedIdentity().getField().string(), equalTo("comments")); + assertThat(innerHits.getAt(0).getNestedIdentity().getOffset(), equalTo(0)); + assertThat(innerHits.getAt(0).getNestedIdentity().getChild().getField().string(), equalTo("remarks")); + assertThat(innerHits.getAt(0).getNestedIdentity().getChild().getOffset(), equalTo(0)); + response = client().prepareSearch("articles") .setQuery(nestedQuery("comments", nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad")))) .addInnerHit("comment", new InnerHitsBuilder.InnerHit() .setPath("comments") .setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad"))) - .addInnerHit("remark", new InnerHitsBuilder.InnerHit().setPath("comments.remarks").setQuery(matchQuery("comments.remarks.message", "bad"))) - ).get(); + .addInnerHit("remark", new InnerHitsBuilder.InnerHit().setPath("comments.remarks").setQuery(matchQuery("comments.remarks.message", "bad")))) + .get(); assertNoFailures(response); assertHitCount(response, 1); assertSearchHit(response, 1, hasId("2"));