diff --git a/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java b/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java index 9fe79497713..956cee1020b 100644 --- a/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java +++ b/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java @@ -19,6 +19,7 @@ package org.elasticsearch.search.fetch; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.ReaderUtil; @@ -27,6 +28,7 @@ import org.apache.lucene.search.Filter; import org.apache.lucene.util.BitDocIdSet; import org.apache.lucene.util.BitSet; import org.elasticsearch.ElasticsearchIllegalArgumentException; +import org.elasticsearch.ElasticsearchIllegalStateException; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Tuple; @@ -293,7 +295,16 @@ public class FetchPhase implements SearchPhase { List> nestedParsedSource; SearchHit.NestedIdentity nested = nestedIdentity; do { - nestedParsedSource = (List>) XContentMapValues.extractValue(nested.getField().string(), sourceAsMap); + Object extractedValue = XContentMapValues.extractValue(nested.getField().string(), sourceAsMap); + if (extractedValue instanceof List) { + // nested field has an array value in the _source + nestedParsedSource = (List>) extractedValue; + } else if (extractedValue instanceof Map) { + // nested field has an object value in the _source. This just means the nested field has just one inner object, which is valid, but uncommon. + nestedParsedSource = ImmutableList.of((Map < String, Object >) extractedValue); + } else { + throw new ElasticsearchIllegalStateException("extracted source isn't an object or an array"); + } sourceAsMap = nestedParsedSource.get(nested.getOffset()); nested = nested.getChild(); } while (nested != null); diff --git a/src/test/java/org/elasticsearch/search/innerhits/InnerHitsTests.java b/src/test/java/org/elasticsearch/search/innerhits/InnerHitsTests.java index fd210adb499..2a710e9cd37 100644 --- a/src/test/java/org/elasticsearch/search/innerhits/InnerHitsTests.java +++ b/src/test/java/org/elasticsearch/search/innerhits/InnerHitsTests.java @@ -645,4 +645,29 @@ public class InnerHitsTests extends ElasticsearchIntegrationTest { assertThat(innerHits.getAt(0).getNestedIdentity().getChild().getOffset(), equalTo(0)); } + @Test + // https://github.com/elasticsearch/elasticsearch/issues/9723 + public void testNestedDefinedAsObject() throws Exception { + assertAcked(prepareCreate("articles").addMapping("article", "comments", "type=nested", "title", "type=string")); + + List requests = new ArrayList<>(); + requests.add(client().prepareIndex("articles", "article", "1").setSource(jsonBuilder().startObject() + .field("title", "quick brown fox") + .startObject("comments").field("message", "fox eat quick").endObject() + .endObject())); + indexRandom(true, requests); + + SearchResponse response = client().prepareSearch("articles") + .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox")).innerHit(new QueryInnerHitBuilder())) + .get(); + assertNoFailures(response); + assertHitCount(response, 1); + assertThat(response.getHits().getAt(0).id(), equalTo("1")); + assertThat(response.getHits().getAt(0).getInnerHits().get("comments").getTotalHits(), equalTo(1l)); + assertThat(response.getHits().getAt(0).getInnerHits().get("comments").getAt(0).id(), equalTo("1")); + assertThat(response.getHits().getAt(0).getInnerHits().get("comments").getAt(0).getNestedIdentity().getField().string(), equalTo("comments")); + assertThat(response.getHits().getAt(0).getInnerHits().get("comments").getAt(0).getNestedIdentity().getOffset(), equalTo(0)); + assertThat(response.getHits().getAt(0).getInnerHits().get("comments").getAt(0).getNestedIdentity().getChild(), nullValue()); + } + }