From 4393939f5edc56a521d0d8f5cb987b7d33a47bd7 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Mon, 12 Jan 2015 13:11:46 +0100 Subject: [PATCH] inner_hits: Nested parent field should be resolved based on the parent inner hit definition, instead of the nested parent field in the mapping. The behaviour is better in the case someone has multiple levels of nested object fields defined in the mapping and like to define a single inner_hits definition that is two or more levels deep. If someone wants inner hits on a nested field that is 2 levels deep the following would need to be defined: ``` { ... "inner_hits" : { "path" : { "level1" : { "inner_hits" : { "path" : { "level2" : { "query" : { .... } } } } } } } } ``` With this change the above can be defined as: ``` { ... "inner_hits" : { "path" : { "level1.level2" : { "query" : { .... } } } } } ``` Closes #9251 --- .../search/request/inner-hits.asciidoc | 22 +++++++++++++++++++ .../index/query/NestedQueryParser.java | 4 +--- .../support/NestedInnerQueryParseSupport.java | 7 +++++- .../index/query/support/NestedScope.java | 12 +++++----- .../innerhits/InnerHitsParseElement.java | 4 +--- .../search/innerhits/InnerHitsTests.java | 21 ++++++++++++++++-- 6 files changed, 56 insertions(+), 14 deletions(-) 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"));