org.elasticsearch.search.innerhits.InnerHitsIT.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.search.innerhits.InnerHitsIT.java

Source

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.elasticsearch.search.innerhits;

import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.ArrayUtil;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.collect.HppcMaps;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.support.QueryInnerHitBuilder;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.MockScriptEngine;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.test.ESIntegTestCase;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
import static org.hamcrest.Matchers.*;

/**
 */
public class InnerHitsIT extends ESIntegTestCase {

    @Override
    protected Collection<Class<? extends Plugin>> nodePlugins() {
        return pluginList(MockScriptEngine.TestPlugin.class);
    }

    @Test
    public void testSimpleNested() throws Exception {
        assertAcked(prepareCreate("articles").addMapping("article",
                jsonBuilder().startObject().startObject("article").startObject("properties").startObject("comments")
                        .field("type", "nested").startObject("properties").startObject("message")
                        .field("type", "string").endObject().endObject().endObject().startObject("title")
                        .field("type", "string").endObject().endObject().endObject().endObject()));

        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("articles", "article", "1")
                .setSource(jsonBuilder().startObject().field("title", "quick brown fox").startArray("comments")
                        .startObject().field("message", "fox eat quick").endObject().startObject()
                        .field("message", "fox ate rabbit x y z").endObject().startObject()
                        .field("message", "rabbit got away").endObject().endArray().endObject()));
        requests.add(client().prepareIndex("articles", "article", "2")
                .setSource(jsonBuilder().startObject().field("title", "big gray elephant").startArray("comments")
                        .startObject().field("message", "elephant captured").endObject().startObject()
                        .field("message", "mice squashed by elephant x").endObject().startObject()
                        .field("message", "elephant scared by mice x y").endObject().endArray().endObject()));
        indexRandom(true, requests);

        // Inner hits can be defined in two ways: 1) with the query 2) as seperate inner_hit definition
        SearchRequest[] searchRequests = new SearchRequest[] {
                client().prepareSearch("articles")
                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"))
                                .innerHit(new QueryInnerHitBuilder().setName("comment")))
                        .request(),
                client().prepareSearch("articles")
                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox")))
                        .addInnerHit("comment", new InnerHitsBuilder.InnerHit().setPath("comments")
                                .setQuery(matchQuery("comments.message", "fox")))
                        .request() };
        for (SearchRequest searchRequest : searchRequests) {
            SearchResponse response = client().search(searchRequest).actionGet();
            assertNoFailures(response);
            assertHitCount(response, 1);
            assertSearchHit(response, 1, hasId("1"));
            assertThat(response.getHits().getAt(0).getInnerHits().size(), equalTo(1));
            SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("comment");
            assertThat(innerHits.totalHits(), equalTo(2l));
            assertThat(innerHits.getHits().length, equalTo(2));
            assertThat(innerHits.getAt(0).getId(), equalTo("1"));
            assertThat(innerHits.getAt(0).getNestedIdentity().getField().string(), equalTo("comments"));
            assertThat(innerHits.getAt(0).getNestedIdentity().getOffset(), equalTo(0));
            assertThat(innerHits.getAt(1).getId(), equalTo("1"));
            assertThat(innerHits.getAt(1).getNestedIdentity().getField().string(), equalTo("comments"));
            assertThat(innerHits.getAt(1).getNestedIdentity().getOffset(), equalTo(1));
        }

        searchRequests = new SearchRequest[] {
                client().prepareSearch("articles")
                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant")))
                        .addInnerHit("comment",
                                new InnerHitsBuilder.InnerHit().setPath("comments")
                                        .setQuery(matchQuery("comments.message", "elephant")))
                        .request(),
                client().prepareSearch("articles")
                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant"))
                                .innerHit(new QueryInnerHitBuilder().setName("comment")))
                        .request(),
                client().prepareSearch("articles")
                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant")).innerHit(
                                new QueryInnerHitBuilder().setName("comment").addSort("_doc", SortOrder.DESC)))
                        .request() };
        for (SearchRequest searchRequest : searchRequests) {
            SearchResponse response = client().search(searchRequest).actionGet();
            assertNoFailures(response);
            assertHitCount(response, 1);
            assertSearchHit(response, 1, hasId("2"));
            assertThat(response.getHits().getAt(0).getShard(), notNullValue());
            assertThat(response.getHits().getAt(0).getInnerHits().size(), equalTo(1));
            SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("comment");
            assertThat(innerHits.totalHits(), equalTo(3l));
            assertThat(innerHits.getHits().length, equalTo(3));
            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(1).getId(), equalTo("2"));
            assertThat(innerHits.getAt(1).getNestedIdentity().getField().string(), equalTo("comments"));
            assertThat(innerHits.getAt(1).getNestedIdentity().getOffset(), equalTo(1));
            assertThat(innerHits.getAt(2).getId(), equalTo("2"));
            assertThat(innerHits.getAt(2).getNestedIdentity().getField().string(), equalTo("comments"));
            assertThat(innerHits.getAt(2).getNestedIdentity().getOffset(), equalTo(2));
        }
        InnerHitsBuilder.InnerHit innerHit = new InnerHitsBuilder.InnerHit();
        innerHit.highlightBuilder().field("comments.message");
        innerHit.setExplain(true);
        innerHit.addFieldDataField("comments.message");
        innerHit.addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE, MockScriptEngine.NAME,
                Collections.<String, Object>emptyMap()));
        innerHit.setSize(1);
        searchRequests = new SearchRequest[] {
                client().prepareSearch("articles")
                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox")))
                        .addInnerHit("comments",
                                new InnerHitsBuilder.InnerHit().setPath("comments")
                                        .setQuery(matchQuery("comments.message", "fox"))
                                        .addHighlightedField("comments.message").setExplain(true)
                                        .addFieldDataField("comments.message")
                                        .addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE,
                                                MockScriptEngine.NAME, Collections.<String, Object>emptyMap()))
                                        .setSize(1))
                        .request(),
                client().prepareSearch("articles")
                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"))
                                .innerHit(new QueryInnerHitBuilder().addHighlightedField("comments.message")
                                        .setExplain(true).addFieldDataField("comments.message")
                                        .addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE,
                                                MockScriptEngine.NAME, Collections.<String, Object>emptyMap()))
                                        .setSize(1)))
                        .request() };

        for (SearchRequest searchRequest : searchRequests) {
            SearchResponse response = client().search(searchRequest).actionGet();
            assertNoFailures(response);
            SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("comments");
            assertThat(innerHits.getTotalHits(), equalTo(2l));
            assertThat(innerHits.getHits().length, equalTo(1));
            assertThat(innerHits.getAt(0).getHighlightFields().get("comments.message").getFragments()[0].string(),
                    equalTo("<em>fox</em> eat quick"));
            assertThat(innerHits.getAt(0).explanation().toString(),
                    containsString("weight(comments.message:fox in"));
            assertThat(innerHits.getAt(0).getFields().get("comments.message").getValue().toString(),
                    equalTo("eat"));
            assertThat(innerHits.getAt(0).getFields().get("script").getValue().toString(), equalTo("5"));
        }
    }

    @Test
    public void testRandomNested() throws Exception {
        assertAcked(prepareCreate("idx").addMapping("type", "field1", "type=nested", "field2", "type=nested"));
        int numDocs = scaledRandomIntBetween(25, 100);
        List<IndexRequestBuilder> requestBuilders = new ArrayList<>();

        int[] field1InnerObjects = new int[numDocs];
        int[] field2InnerObjects = new int[numDocs];
        for (int i = 0; i < numDocs; i++) {
            int numInnerObjects = field1InnerObjects[i] = scaledRandomIntBetween(1, numDocs);
            XContentBuilder source = jsonBuilder().startObject().startArray("field1");
            for (int j = 0; j < numInnerObjects; j++) {
                source.startObject().field("x", "y").endObject();
            }
            numInnerObjects = field2InnerObjects[i] = scaledRandomIntBetween(1, numDocs);
            source.endArray().startArray("field2");
            for (int j = 0; j < numInnerObjects; j++) {
                source.startObject().field("x", "y").endObject();
            }
            source.endArray().endObject();
            requestBuilders.add(client().prepareIndex("idx", "type", String.format(Locale.ENGLISH, "%03d", i))
                    .setSource(source));
        }
        indexRandom(true, requestBuilders);

        int size = randomIntBetween(0, numDocs);
        SearchResponse searchResponse;
        if (randomBoolean()) {
            searchResponse = client().prepareSearch("idx").setSize(numDocs).addSort("_uid", SortOrder.ASC)
                    .addInnerHit("a",
                            new InnerHitsBuilder.InnerHit().setPath("field1").addSort("_doc", SortOrder.DESC)
                                    .setSize(size)) // Sort order is DESC, because we reverse the inner objects during indexing!
                    .addInnerHit("b", new InnerHitsBuilder.InnerHit().setPath("field2")
                            .addSort("_doc", SortOrder.DESC).setSize(size))
                    .get();
        } else {
            BoolQueryBuilder boolQuery = new BoolQueryBuilder();
            if (randomBoolean()) {
                boolQuery.should(nestedQuery("field1", matchAllQuery()).innerHit(
                        new QueryInnerHitBuilder().setName("a").addSort("_doc", SortOrder.DESC).setSize(size)));
                boolQuery.should(nestedQuery("field2", matchAllQuery()).innerHit(
                        new QueryInnerHitBuilder().setName("b").addSort("_doc", SortOrder.DESC).setSize(size)));
            } else {
                boolQuery.should(constantScoreQuery(nestedQuery("field1", matchAllQuery()).innerHit(
                        new QueryInnerHitBuilder().setName("a").addSort("_doc", SortOrder.DESC).setSize(size))));
                boolQuery.should(constantScoreQuery(nestedQuery("field2", matchAllQuery()).innerHit(
                        new QueryInnerHitBuilder().setName("b").addSort("_doc", SortOrder.DESC).setSize(size))));
            }
            searchResponse = client().prepareSearch("idx").setQuery(boolQuery).setSize(numDocs)
                    .addSort("_uid", SortOrder.ASC).get();
        }

        assertNoFailures(searchResponse);
        assertHitCount(searchResponse, numDocs);
        assertThat(searchResponse.getHits().getHits().length, equalTo(numDocs));
        for (int i = 0; i < numDocs; i++) {
            SearchHit searchHit = searchResponse.getHits().getAt(i);
            assertThat(searchHit.getShard(), notNullValue());
            SearchHits inner = searchHit.getInnerHits().get("a");
            assertThat(inner.totalHits(), equalTo((long) field1InnerObjects[i]));
            for (int j = 0; j < field1InnerObjects[i] && j < size; j++) {
                SearchHit innerHit = inner.getAt(j);
                assertThat(innerHit.getNestedIdentity().getField().string(), equalTo("field1"));
                assertThat(innerHit.getNestedIdentity().getOffset(), equalTo(j));
                assertThat(innerHit.getNestedIdentity().getChild(), nullValue());
            }

            inner = searchHit.getInnerHits().get("b");
            assertThat(inner.totalHits(), equalTo((long) field2InnerObjects[i]));
            for (int j = 0; j < field2InnerObjects[i] && j < size; j++) {
                SearchHit innerHit = inner.getAt(j);
                assertThat(innerHit.getNestedIdentity().getField().string(), equalTo("field2"));
                assertThat(innerHit.getNestedIdentity().getOffset(), equalTo(j));
                assertThat(innerHit.getNestedIdentity().getChild(), nullValue());
            }
        }
    }

    @Test
    public void testSimpleParentChild() throws Exception {
        assertAcked(prepareCreate("articles").addMapping("article", "title", "type=string").addMapping("comment",
                "_parent", "type=article", "message", "type=string"));

        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("articles", "article", "1").setSource("title", "quick brown fox"));
        requests.add(client().prepareIndex("articles", "comment", "1").setParent("1").setSource("message",
                "fox eat quick"));
        requests.add(client().prepareIndex("articles", "comment", "2").setParent("1").setSource("message",
                "fox ate rabbit x y z"));
        requests.add(client().prepareIndex("articles", "comment", "3").setParent("1").setSource("message",
                "rabbit got away"));
        requests.add(client().prepareIndex("articles", "article", "2").setSource("title", "big gray elephant"));
        requests.add(client().prepareIndex("articles", "comment", "4").setParent("2").setSource("message",
                "elephant captured"));
        requests.add(client().prepareIndex("articles", "comment", "5").setParent("2").setSource("message",
                "mice squashed by elephant x"));
        requests.add(client().prepareIndex("articles", "comment", "6").setParent("2").setSource("message",
                "elephant scared by mice x y"));
        indexRandom(true, requests);

        SearchRequest[] searchRequests = new SearchRequest[] {
                client().prepareSearch("articles").setQuery(hasChildQuery("comment", matchQuery("message", "fox")))
                        .addInnerHit("comment",
                                new InnerHitsBuilder.InnerHit().setType("comment")
                                        .setQuery(matchQuery("message", "fox")))
                        .request(),
                client().prepareSearch("articles").setQuery(hasChildQuery("comment", matchQuery("message", "fox"))
                        .innerHit(new QueryInnerHitBuilder().setName("comment"))).request() };
        for (SearchRequest searchRequest : searchRequests) {
            SearchResponse response = client().search(searchRequest).actionGet();
            assertNoFailures(response);
            assertHitCount(response, 1);
            assertSearchHit(response, 1, hasId("1"));
            assertThat(response.getHits().getAt(0).getShard(), notNullValue());

            assertThat(response.getHits().getAt(0).getInnerHits().size(), equalTo(1));
            SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("comment");
            assertThat(innerHits.totalHits(), equalTo(2l));

            assertThat(innerHits.getAt(0).getId(), equalTo("1"));
            assertThat(innerHits.getAt(0).type(), equalTo("comment"));
            assertThat(innerHits.getAt(1).getId(), equalTo("2"));
            assertThat(innerHits.getAt(1).type(), equalTo("comment"));
        }

        searchRequests = new SearchRequest[] {
                client().prepareSearch("articles")
                        .setQuery(hasChildQuery("comment", matchQuery("message", "elephant")))
                        .addInnerHit("comment",
                                new InnerHitsBuilder.InnerHit().setType("comment")
                                        .setQuery(matchQuery("message", "elephant")))
                        .request(),
                client().prepareSearch("articles")
                        .setQuery(hasChildQuery("comment", matchQuery("message", "elephant"))
                                .innerHit(new QueryInnerHitBuilder()))
                        .request() };
        for (SearchRequest searchRequest : searchRequests) {
            SearchResponse response = client().search(searchRequest).actionGet();
            assertNoFailures(response);
            assertHitCount(response, 1);
            assertSearchHit(response, 1, hasId("2"));

            assertThat(response.getHits().getAt(0).getInnerHits().size(), equalTo(1));
            SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("comment");
            assertThat(innerHits.totalHits(), equalTo(3l));

            assertThat(innerHits.getAt(0).getId(), equalTo("4"));
            assertThat(innerHits.getAt(0).type(), equalTo("comment"));
            assertThat(innerHits.getAt(1).getId(), equalTo("5"));
            assertThat(innerHits.getAt(1).type(), equalTo("comment"));
            assertThat(innerHits.getAt(2).getId(), equalTo("6"));
            assertThat(innerHits.getAt(2).type(), equalTo("comment"));
        }
        InnerHitsBuilder.InnerHit innerHit = new InnerHitsBuilder.InnerHit();
        innerHit.highlightBuilder().field("message");
        innerHit.setExplain(true);
        innerHit.addFieldDataField("message");
        innerHit.addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE, MockScriptEngine.NAME,
                Collections.<String, Object>emptyMap()));
        innerHit.setSize(1);

        searchRequests = new SearchRequest[] {
                client().prepareSearch("articles").setQuery(hasChildQuery("comment", matchQuery("message", "fox")))
                        .addInnerHit("comment",
                                new InnerHitsBuilder.InnerHit().setType("comment")
                                        .setQuery(matchQuery("message", "fox")).addHighlightedField("message")
                                        .setExplain(true).addFieldDataField("message")
                                        .addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE,
                                                MockScriptEngine.NAME, Collections.<String, Object>emptyMap()))
                                        .setSize(1))
                        .request(),
                client().prepareSearch("articles").setQuery(
                        hasChildQuery("comment", matchQuery("message", "fox")).innerHit(new QueryInnerHitBuilder()
                                .addHighlightedField("message").setExplain(true).addFieldDataField("message")
                                .addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE,
                                        MockScriptEngine.NAME, Collections.<String, Object>emptyMap()))
                                .setSize(1)))
                        .request() };

        for (SearchRequest searchRequest : searchRequests) {
            SearchResponse response = client().search(searchRequest).actionGet();
            assertNoFailures(response);
            SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("comment");
            assertThat(innerHits.getHits().length, equalTo(1));
            assertThat(innerHits.getAt(0).getHighlightFields().get("message").getFragments()[0].string(),
                    equalTo("<em>fox</em> eat quick"));
            assertThat(innerHits.getAt(0).explanation().toString(), containsString("weight(message:fox"));
            assertThat(innerHits.getAt(0).getFields().get("message").getValue().toString(), equalTo("eat"));
            assertThat(innerHits.getAt(0).getFields().get("script").getValue().toString(), equalTo("5"));
        }
    }

    @Test
    public void testRandomParentChild() throws Exception {
        assertAcked(prepareCreate("idx").addMapping("parent").addMapping("child1", "_parent", "type=parent")
                .addMapping("child2", "_parent", "type=parent"));
        int numDocs = scaledRandomIntBetween(5, 50);
        List<IndexRequestBuilder> requestBuilders = new ArrayList<>();

        int child1 = 0;
        int child2 = 0;
        int[] child1InnerObjects = new int[numDocs];
        int[] child2InnerObjects = new int[numDocs];
        for (int parent = 0; parent < numDocs; parent++) {
            String parentId = String.format(Locale.ENGLISH, "%03d", parent);
            requestBuilders.add(client().prepareIndex("idx", "parent", parentId).setSource("{}"));

            int numChildDocs = child1InnerObjects[parent] = scaledRandomIntBetween(1, numDocs);
            int limit = child1 + numChildDocs;
            for (; child1 < limit; child1++) {
                requestBuilders
                        .add(client().prepareIndex("idx", "child1", String.format(Locale.ENGLISH, "%04d", child1))
                                .setParent(parentId).setSource("{}"));
            }
            numChildDocs = child2InnerObjects[parent] = scaledRandomIntBetween(1, numDocs);
            limit = child2 + numChildDocs;
            for (; child2 < limit; child2++) {
                requestBuilders
                        .add(client().prepareIndex("idx", "child2", String.format(Locale.ENGLISH, "%04d", child2))
                                .setParent(parentId).setSource("{}"));
            }
        }
        indexRandom(true, requestBuilders);

        int size = randomIntBetween(0, numDocs);
        SearchResponse searchResponse;
        if (randomBoolean()) {
            searchResponse = client().prepareSearch("idx").setSize(numDocs).setTypes("parent")
                    .addSort("_uid", SortOrder.ASC)
                    .addInnerHit("a",
                            new InnerHitsBuilder.InnerHit().setType("child1").addSort("_uid", SortOrder.ASC)
                                    .setSize(size))
                    .addInnerHit("b", new InnerHitsBuilder.InnerHit().setType("child2")
                            .addSort("_uid", SortOrder.ASC).setSize(size))
                    .get();
        } else {
            BoolQueryBuilder boolQuery = new BoolQueryBuilder();
            if (randomBoolean()) {
                boolQuery.should(hasChildQuery("child1", matchAllQuery()).innerHit(
                        new QueryInnerHitBuilder().setName("a").addSort("_uid", SortOrder.ASC).setSize(size)));
                boolQuery.should(hasChildQuery("child2", matchAllQuery()).innerHit(
                        new QueryInnerHitBuilder().setName("b").addSort("_uid", SortOrder.ASC).setSize(size)));
            } else {
                boolQuery.should(constantScoreQuery(hasChildQuery("child1", matchAllQuery()).innerHit(
                        new QueryInnerHitBuilder().setName("a").addSort("_uid", SortOrder.ASC).setSize(size))));
                boolQuery.should(constantScoreQuery(hasChildQuery("child2", matchAllQuery()).innerHit(
                        new QueryInnerHitBuilder().setName("b").addSort("_uid", SortOrder.ASC).setSize(size))));
            }
            searchResponse = client().prepareSearch("idx").setSize(numDocs).setTypes("parent")
                    .addSort("_uid", SortOrder.ASC).setQuery(boolQuery).get();
        }

        assertNoFailures(searchResponse);
        assertHitCount(searchResponse, numDocs);
        assertThat(searchResponse.getHits().getHits().length, equalTo(numDocs));

        int offset1 = 0;
        int offset2 = 0;
        for (int parent = 0; parent < numDocs; parent++) {
            SearchHit searchHit = searchResponse.getHits().getAt(parent);
            assertThat(searchHit.getType(), equalTo("parent"));
            assertThat(searchHit.getId(), equalTo(String.format(Locale.ENGLISH, "%03d", parent)));
            assertThat(searchHit.getShard(), notNullValue());

            SearchHits inner = searchHit.getInnerHits().get("a");
            assertThat(inner.totalHits(), equalTo((long) child1InnerObjects[parent]));
            for (int child = 0; child < child1InnerObjects[parent] && child < size; child++) {
                SearchHit innerHit = inner.getAt(child);
                assertThat(innerHit.getType(), equalTo("child1"));
                String childId = String.format(Locale.ENGLISH, "%04d", offset1 + child);
                assertThat(innerHit.getId(), equalTo(childId));
                assertThat(innerHit.getNestedIdentity(), nullValue());
            }
            offset1 += child1InnerObjects[parent];

            inner = searchHit.getInnerHits().get("b");
            assertThat(inner.totalHits(), equalTo((long) child2InnerObjects[parent]));
            for (int child = 0; child < child2InnerObjects[parent] && child < size; child++) {
                SearchHit innerHit = inner.getAt(child);
                assertThat(innerHit.getType(), equalTo("child2"));
                String childId = String.format(Locale.ENGLISH, "%04d", offset2 + child);
                assertThat(innerHit.getId(), equalTo(childId));
                assertThat(innerHit.getNestedIdentity(), nullValue());
            }
            offset2 += child2InnerObjects[parent];
        }
    }

    @Test
    public void testPathOrTypeMustBeDefined() {
        createIndex("articles");
        ensureGreen("articles");
        try {
            client().prepareSearch("articles").addInnerHit("comment", new InnerHitsBuilder.InnerHit()).get();
        } catch (Exception e) {
            assertThat(e.getMessage(), containsString("Failed to build"));
        }

    }

    @Test
    public void testInnerHitsOnHasParent() throws Exception {
        assertAcked(prepareCreate("stack").addMapping("question", "body", "type=string").addMapping("answer",
                "_parent", "type=question", "body", "type=string"));
        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("stack", "question", "1").setSource("body",
                "I'm using HTTPS + Basic authentication to protect a resource. How can I throttle authentication attempts to protect against brute force attacks?"));
        requests.add(client().prepareIndex("stack", "answer", "1").setParent("1").setSource("body",
                "install fail2ban and enable rules for apache"));
        requests.add(client().prepareIndex("stack", "question", "2").setSource("body",
                "I have firewall rules set up and also denyhosts installed.\\ndo I also need to install fail2ban?"));
        requests.add(client().prepareIndex("stack", "answer", "2").setParent("2").setSource("body",
                "Denyhosts protects only ssh; Fail2Ban protects all daemons."));
        indexRandom(true, requests);

        SearchResponse response = client().prepareSearch("stack").setTypes("answer").addSort("_uid", SortOrder.ASC)
                .setQuery(boolQuery().must(matchQuery("body", "fail2ban"))
                        .must(hasParentQuery("question", matchAllQuery()).innerHit(new QueryInnerHitBuilder())))
                .get();
        assertNoFailures(response);
        assertHitCount(response, 2);

        SearchHit searchHit = response.getHits().getAt(0);
        assertThat(searchHit.getId(), equalTo("1"));
        assertThat(searchHit.getType(), equalTo("answer"));
        assertThat(searchHit.getInnerHits().get("question").getTotalHits(), equalTo(1l));
        assertThat(searchHit.getInnerHits().get("question").getAt(0).getType(), equalTo("question"));
        assertThat(searchHit.getInnerHits().get("question").getAt(0).id(), equalTo("1"));

        searchHit = response.getHits().getAt(1);
        assertThat(searchHit.getId(), equalTo("2"));
        assertThat(searchHit.getType(), equalTo("answer"));
        assertThat(searchHit.getInnerHits().get("question").getTotalHits(), equalTo(1l));
        assertThat(searchHit.getInnerHits().get("question").getAt(0).getType(), equalTo("question"));
        assertThat(searchHit.getInnerHits().get("question").getAt(0).id(), equalTo("2"));
    }

    @Test
    public void testParentChildMultipleLayers() throws Exception {
        assertAcked(prepareCreate("articles").addMapping("article", "title", "type=string")
                .addMapping("comment", "_parent", "type=article", "message", "type=string")
                .addMapping("remark", "_parent", "type=comment", "message", "type=string"));

        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("articles", "article", "1").setSource("title", "quick brown fox"));
        requests.add(client().prepareIndex("articles", "comment", "1").setParent("1").setSource("message",
                "fox eat quick"));
        requests.add(client().prepareIndex("articles", "remark", "1").setParent("1").setRouting("1")
                .setSource("message", "good"));
        requests.add(client().prepareIndex("articles", "article", "2").setSource("title", "big gray elephant"));
        requests.add(client().prepareIndex("articles", "comment", "2").setParent("2").setSource("message",
                "elephant captured"));
        requests.add(client().prepareIndex("articles", "remark", "2").setParent("2").setRouting("2")
                .setSource("message", "bad"));
        indexRandom(true, requests);

        SearchResponse response = client().prepareSearch("articles")
                .setQuery(hasChildQuery("comment", hasChildQuery("remark", matchQuery("message", "good"))))
                .addInnerHit("comment",
                        new InnerHitsBuilder.InnerHit().setType("comment")
                                .setQuery(hasChildQuery("remark", matchQuery("message", "good")))
                                .addInnerHit("remark", new InnerHitsBuilder.InnerHit().setType("remark")
                                        .setQuery(matchQuery("message", "good"))))
                .get();

        assertNoFailures(response);
        assertHitCount(response, 1);
        assertSearchHit(response, 1, hasId("1"));

        assertThat(response.getHits().getAt(0).getInnerHits().size(), equalTo(1));
        SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("comment");
        assertThat(innerHits.totalHits(), equalTo(1l));
        assertThat(innerHits.getAt(0).getId(), equalTo("1"));
        assertThat(innerHits.getAt(0).type(), equalTo("comment"));

        innerHits = innerHits.getAt(0).getInnerHits().get("remark");
        assertThat(innerHits.totalHits(), equalTo(1l));
        assertThat(innerHits.getAt(0).getId(), equalTo("1"));
        assertThat(innerHits.getAt(0).type(), equalTo("remark"));

        response = client().prepareSearch("articles")
                .setQuery(hasChildQuery("comment", hasChildQuery("remark", matchQuery("message", "bad"))))
                .addInnerHit("comment",
                        new InnerHitsBuilder.InnerHit().setType("comment")
                                .setQuery(hasChildQuery("remark", matchQuery("message", "bad")))
                                .addInnerHit("remark", new InnerHitsBuilder.InnerHit().setType("remark")
                                        .setQuery(matchQuery("message", "bad"))))
                .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("comment");
        assertThat(innerHits.totalHits(), equalTo(1l));
        assertThat(innerHits.getAt(0).getId(), equalTo("2"));
        assertThat(innerHits.getAt(0).type(), equalTo("comment"));

        innerHits = innerHits.getAt(0).getInnerHits().get("remark");
        assertThat(innerHits.totalHits(), equalTo(1l));
        assertThat(innerHits.getAt(0).getId(), equalTo("2"));
        assertThat(innerHits.getAt(0).type(), equalTo("remark"));
    }

    @Test
    public void testNestedMultipleLayers() throws Exception {
        assertAcked(prepareCreate("articles").addMapping("article",
                jsonBuilder().startObject().startObject("article").startObject("properties").startObject("comments")
                        .field("type", "nested").startObject("properties").startObject("message")
                        .field("type", "string").endObject().startObject("remarks").field("type", "nested")
                        .startObject("properties").startObject("message").field("type", "string").endObject()
                        .endObject().endObject().endObject().endObject().startObject("title")
                        .field("type", "string").endObject().endObject().endObject().endObject()));

        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("articles", "article", "1")
                .setSource(jsonBuilder().startObject().field("title", "quick brown fox").startArray("comments")
                        .startObject().field("message", "fox eat quick").startArray("remarks").startObject()
                        .field("message", "good").endObject().endArray().endObject().endArray().endObject()));
        requests.add(client().prepareIndex("articles", "article", "2")
                .setSource(jsonBuilder().startObject().field("title", "big gray elephant").startArray("comments")
                        .startObject().field("message", "elephant captured").startArray("remarks").startObject()
                        .field("message", "bad").endObject().endArray().endObject().endArray().endObject()));
        indexRandom(true, requests);

        SearchResponse response = client().prepareSearch("articles")
                .setQuery(nestedQuery("comments",
                        nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "good"))))
                .addInnerHit("comment",
                        new InnerHitsBuilder.InnerHit().setPath("comments")
                                .setQuery(nestedQuery("comments.remarks",
                                        matchQuery("comments.remarks.message", "good")))
                                .addInnerHit("remark", new InnerHitsBuilder.InnerHit().setPath("comments.remarks")
                                        .setQuery(matchQuery("comments.remarks.message", "good"))))
                .get();
        assertNoFailures(response);
        assertHitCount(response, 1);
        assertSearchHit(response, 1, hasId("1"));
        assertThat(response.getHits().getAt(0).getInnerHits().size(), equalTo(1));
        SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("comment");
        assertThat(innerHits.totalHits(), equalTo(1l));
        assertThat(innerHits.getHits().length, equalTo(1));
        assertThat(innerHits.getAt(0).getId(), equalTo("1"));
        assertThat(innerHits.getAt(0).getNestedIdentity().getField().string(), equalTo("comments"));
        assertThat(innerHits.getAt(0).getNestedIdentity().getOffset(), equalTo(0));
        innerHits = innerHits.getAt(0).getInnerHits().get("remark");
        assertThat(innerHits.totalHits(), equalTo(1l));
        assertThat(innerHits.getHits().length, equalTo(1));
        assertThat(innerHits.getAt(0).getId(), equalTo("1"));
        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));

        // 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();
        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("comment");
        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));
        innerHits = innerHits.getAt(0).getInnerHits().get("remark");
        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));
    }

    @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<IndexRequestBuilder> 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());
    }

    @Test
    public void testNestedInnerHitsWithStoredFieldsAndNoSourceBackcompat() throws Exception {
        assertAcked(prepareCreate("articles").setSettings(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2.id)
                .addMapping("article", jsonBuilder().startObject().startObject("_source").field("enabled", false)
                        .endObject().startObject("properties").startObject("comments").field("type", "nested")
                        .startObject("properties").startObject("message").field("type", "string")
                        .field("store", "yes").endObject().endObject().endObject().endObject().endObject()));

        List<IndexRequestBuilder> 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().field("comments.message")))
                .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());
        assertThat(String.valueOf(response.getHits().getAt(0).getInnerHits().get("comments").getAt(0).fields()
                .get("comments.message").getValue()), equalTo("fox eat quick"));
    }

    @Test
    public void testNestedInnerHitsWithHighlightOnStoredFieldBackcompat() throws Exception {
        assertAcked(prepareCreate("articles").setSettings(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2.id)
                .addMapping("article", jsonBuilder().startObject().startObject("_source").field("enabled", false)
                        .endObject().startObject("properties").startObject("comments").field("type", "nested")
                        .startObject("properties").startObject("message").field("type", "string")
                        .field("store", "yes").endObject().endObject().endObject().endObject().endObject()));

        List<IndexRequestBuilder> 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().addHighlightedField("comments.message")))
                .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());
        assertThat(String.valueOf(response.getHits().getAt(0).getInnerHits().get("comments").getAt(0)
                .highlightFields().get("comments.message").getFragments()[0]), equalTo("<em>fox</em> eat quick"));
    }

    @Test
    public void testNestedInnerHitsWithExcludeSourceBackcompat() throws Exception {
        assertAcked(prepareCreate("articles").setSettings(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2.id)
                .addMapping("article", jsonBuilder().startObject().startObject("_source")
                        .field("excludes", new String[] { "comments" }).endObject().startObject("properties")
                        .startObject("comments").field("type", "nested").startObject("properties")
                        .startObject("message").field("type", "string").field("store", "yes").endObject()
                        .endObject().endObject().endObject().endObject()));

        List<IndexRequestBuilder> 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().field("comments.message").setFetchSource(true)))
                .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());
        assertThat(String.valueOf(response.getHits().getAt(0).getInnerHits().get("comments").getAt(0).fields()
                .get("comments.message").getValue()), equalTo("fox eat quick"));
    }

    @Test
    public void testNestedInnerHitsHiglightWithExcludeSourceBackcompat() throws Exception {
        assertAcked(prepareCreate("articles").setSettings(IndexMetaData.SETTING_VERSION_CREATED, Version.V_1_4_2.id)
                .addMapping("article", jsonBuilder().startObject().startObject("_source")
                        .field("excludes", new String[] { "comments" }).endObject().startObject("properties")
                        .startObject("comments").field("type", "nested").startObject("properties")
                        .startObject("message").field("type", "string").field("store", "yes").endObject()
                        .endObject().endObject().endObject().endObject()));

        List<IndexRequestBuilder> 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().addHighlightedField("comments.message")))
                .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());
        assertThat(String.valueOf(response.getHits().getAt(0).getInnerHits().get("comments").getAt(0)
                .highlightFields().get("comments.message").getFragments()[0]), equalTo("<em>fox</em> eat quick"));
    }

    @Test
    public void testInnerHitsWithObjectFieldThatHasANestedField() throws Exception {
        assertAcked(prepareCreate("articles").addMapping("article",
                jsonBuilder().startObject().startObject("properties").startObject("comments")
                        .field("type", "object").startObject("properties").startObject("messages")
                        .field("type", "nested").endObject().endObject().endObject().endObject().endObject()));

        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("articles", "article", "1")
                .setSource(jsonBuilder().startObject().field("title", "quick brown fox").startObject("comments")
                        .startArray("messages").startObject().field("message", "fox eat quick").endObject()
                        .startObject().field("message", "bear eat quick").endObject().endArray().endObject()
                        .endObject()));
        indexRandom(true, requests);

        SearchResponse response = client().prepareSearch("articles")
                .setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.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.messages").getTotalHits(), equalTo(1l));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).id(), equalTo("1"));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity()
                .getField().string(), equalTo("comments.messages"));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity()
                .getOffset(), equalTo(0));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity()
                .getChild(), nullValue());

        response = client().prepareSearch("articles")
                .setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "bear"))
                        .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.messages").getTotalHits(), equalTo(1l));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).id(), equalTo("1"));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity()
                .getField().string(), equalTo("comments.messages"));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity()
                .getOffset(), equalTo(1));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity()
                .getChild(), nullValue());

        // index the message in an object form instead of an array
        requests = new ArrayList<>();
        requests.add(client().prepareIndex("articles", "article", "1")
                .setSource(jsonBuilder().startObject().field("title", "quick brown fox").startObject("comments")
                        .startObject("messages").field("message", "fox eat quick").endObject().endObject()
                        .endObject()));
        indexRandom(true, requests);
        response = client().prepareSearch("articles")
                .setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.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.messages").getTotalHits(), equalTo(1l));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).id(), equalTo("1"));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity()
                .getField().string(), equalTo("comments.messages"));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity()
                .getOffset(), equalTo(0));
        assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity()
                .getChild(), nullValue());
    }

    @Test
    public void testRoyals() throws Exception {
        assertAcked(prepareCreate("royals").addMapping("king").addMapping("prince", "_parent", "type=king")
                .addMapping("duke", "_parent", "type=prince").addMapping("earl", "_parent", "type=duke")
                .addMapping("baron", "_parent", "type=earl"));

        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("royals", "king", "king").setSource("{}"));
        requests.add(client().prepareIndex("royals", "prince", "prince").setParent("king").setSource("{}"));
        requests.add(client().prepareIndex("royals", "duke", "duke").setParent("prince").setRouting("king")
                .setSource("{}"));
        requests.add(client().prepareIndex("royals", "earl", "earl1").setParent("duke").setRouting("king")
                .setSource("{}"));
        requests.add(client().prepareIndex("royals", "earl", "earl2").setParent("duke").setRouting("king")
                .setSource("{}"));
        requests.add(client().prepareIndex("royals", "earl", "earl3").setParent("duke").setRouting("king")
                .setSource("{}"));
        requests.add(client().prepareIndex("royals", "earl", "earl4").setParent("duke").setRouting("king")
                .setSource("{}"));
        requests.add(client().prepareIndex("royals", "baron", "baron1").setParent("earl1").setRouting("king")
                .setSource("{}"));
        requests.add(client().prepareIndex("royals", "baron", "baron2").setParent("earl2").setRouting("king")
                .setSource("{}"));
        requests.add(client().prepareIndex("royals", "baron", "baron3").setParent("earl3").setRouting("king")
                .setSource("{}"));
        requests.add(client().prepareIndex("royals", "baron", "baron4").setParent("earl4").setRouting("king")
                .setSource("{}"));
        indexRandom(true, requests);

        SearchResponse response = client().prepareSearch("royals").setTypes("duke")
                .addInnerHit("earls",
                        new InnerHitsBuilder.InnerHit().setType("earl")
                                .addSort(SortBuilders.fieldSort("_uid").order(SortOrder.ASC)).setSize(4)
                                .addInnerHit("barons", new InnerHitsBuilder.InnerHit().setType("baron")))
                .addInnerHit("princes", new InnerHitsBuilder.InnerHit().setType("prince").addInnerHit("kings",
                        new InnerHitsBuilder.InnerHit().setType("king")))
                .get();
        assertHitCount(response, 1);
        assertThat(response.getHits().getAt(0).getId(), equalTo("duke"));

        SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("earls");
        assertThat(innerHits.getTotalHits(), equalTo(4l));
        assertThat(innerHits.getAt(0).getId(), equalTo("earl1"));
        assertThat(innerHits.getAt(1).getId(), equalTo("earl2"));
        assertThat(innerHits.getAt(2).getId(), equalTo("earl3"));
        assertThat(innerHits.getAt(3).getId(), equalTo("earl4"));

        SearchHits innerInnerHits = innerHits.getAt(0).getInnerHits().get("barons");
        assertThat(innerInnerHits.totalHits(), equalTo(1l));
        assertThat(innerInnerHits.getAt(0).getId(), equalTo("baron1"));

        innerInnerHits = innerHits.getAt(1).getInnerHits().get("barons");
        assertThat(innerInnerHits.totalHits(), equalTo(1l));
        assertThat(innerInnerHits.getAt(0).getId(), equalTo("baron2"));

        innerInnerHits = innerHits.getAt(2).getInnerHits().get("barons");
        assertThat(innerInnerHits.totalHits(), equalTo(1l));
        assertThat(innerInnerHits.getAt(0).getId(), equalTo("baron3"));

        innerInnerHits = innerHits.getAt(3).getInnerHits().get("barons");
        assertThat(innerInnerHits.totalHits(), equalTo(1l));
        assertThat(innerInnerHits.getAt(0).getId(), equalTo("baron4"));

        innerHits = response.getHits().getAt(0).getInnerHits().get("princes");
        assertThat(innerHits.getTotalHits(), equalTo(1l));
        assertThat(innerHits.getAt(0).getId(), equalTo("prince"));

        innerInnerHits = innerHits.getAt(0).getInnerHits().get("kings");
        assertThat(innerInnerHits.totalHits(), equalTo(1l));
        assertThat(innerInnerHits.getAt(0).getId(), equalTo("king"));
    }

    @Test
    public void matchesQueries_nestedInnerHits() throws Exception {
        XContentBuilder builder = jsonBuilder().startObject().startObject("type1").startObject("properties")
                .startObject("nested1").field("type", "nested").endObject().startObject("field1")
                .field("type", "long").endObject().endObject().endObject().endObject();
        assertAcked(prepareCreate("test").addMapping("type1", builder));
        ensureGreen();

        List<IndexRequestBuilder> requests = new ArrayList<>();
        int numDocs = randomIntBetween(2, 35);
        requests.add(client().prepareIndex("test", "type1", "0")
                .setSource(jsonBuilder().startObject().field("field1", 0).startArray("nested1").startObject()
                        .field("n_field1", "n_value1_1").field("n_field2", "n_value2_1").endObject().startObject()
                        .field("n_field1", "n_value1_2").field("n_field2", "n_value2_2").endObject().endArray()
                        .endObject()));
        requests.add(client().prepareIndex("test", "type1", "1")
                .setSource(jsonBuilder().startObject().field("field1", 1).startArray("nested1").startObject()
                        .field("n_field1", "n_value1_8").field("n_field2", "n_value2_5").endObject().startObject()
                        .field("n_field1", "n_value1_3").field("n_field2", "n_value2_1").endObject().endArray()
                        .endObject()));

        for (int i = 2; i < numDocs; i++) {
            requests.add(client().prepareIndex("test", "type1", String.valueOf(i))
                    .setSource(jsonBuilder().startObject().field("field1", i).startArray("nested1").startObject()
                            .field("n_field1", "n_value1_8").field("n_field2", "n_value2_5").endObject()
                            .startObject().field("n_field1", "n_value1_2").field("n_field2", "n_value2_2")
                            .endObject().endArray().endObject()));
        }

        indexRandom(true, requests);
        waitForRelocation(ClusterHealthStatus.GREEN);

        SearchResponse searchResponse = client().prepareSearch("test")
                .setQuery(nestedQuery("nested1",
                        boolQuery().should(termQuery("nested1.n_field1", "n_value1_1").queryName("test1"))
                                .should(termQuery("nested1.n_field1", "n_value1_3").queryName("test2"))
                                .should(termQuery("nested1.n_field2", "n_value2_2").queryName("test3"))).innerHit(
                                        new QueryInnerHitBuilder().addSort("nested1.n_field1", SortOrder.ASC)))
                .setSize(numDocs).addSort("field1", SortOrder.ASC).get();
        assertNoFailures(searchResponse);
        assertAllSuccessful(searchResponse);
        assertThat(searchResponse.getHits().totalHits(), equalTo((long) numDocs));
        assertThat(searchResponse.getHits().getAt(0).id(), equalTo("0"));
        assertThat(searchResponse.getHits().getAt(0).getInnerHits().get("nested1").getTotalHits(), equalTo(2l));
        assertThat(
                searchResponse.getHits().getAt(0).getInnerHits().get("nested1").getAt(0).getMatchedQueries().length,
                equalTo(1));
        assertThat(searchResponse.getHits().getAt(0).getInnerHits().get("nested1").getAt(0).getMatchedQueries()[0],
                equalTo("test1"));
        assertThat(
                searchResponse.getHits().getAt(0).getInnerHits().get("nested1").getAt(1).getMatchedQueries().length,
                equalTo(1));
        assertThat(searchResponse.getHits().getAt(0).getInnerHits().get("nested1").getAt(1).getMatchedQueries()[0],
                equalTo("test3"));

        assertThat(searchResponse.getHits().getAt(1).id(), equalTo("1"));
        assertThat(searchResponse.getHits().getAt(1).getInnerHits().get("nested1").getTotalHits(), equalTo(1l));
        assertThat(
                searchResponse.getHits().getAt(1).getInnerHits().get("nested1").getAt(0).getMatchedQueries().length,
                equalTo(1));
        assertThat(searchResponse.getHits().getAt(1).getInnerHits().get("nested1").getAt(0).getMatchedQueries()[0],
                equalTo("test2"));

        for (int i = 2; i < numDocs; i++) {
            assertThat(searchResponse.getHits().getAt(i).id(), equalTo(String.valueOf(i)));
            assertThat(searchResponse.getHits().getAt(i).getInnerHits().get("nested1").getTotalHits(), equalTo(1l));
            assertThat(searchResponse.getHits().getAt(i).getInnerHits().get("nested1").getAt(0)
                    .getMatchedQueries().length, equalTo(1));
            assertThat(
                    searchResponse.getHits().getAt(i).getInnerHits().get("nested1").getAt(0).getMatchedQueries()[0],
                    equalTo("test3"));
        }
    }

    @Test
    public void matchesQueries_parentChildInnerHits() throws Exception {
        assertAcked(prepareCreate("index").addMapping("child", "_parent", "type=parent"));
        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("index", "parent", "1").setSource("{}"));
        requests.add(client().prepareIndex("index", "child", "1").setParent("1").setSource("field", "value1"));
        requests.add(client().prepareIndex("index", "child", "2").setParent("1").setSource("field", "value2"));
        requests.add(client().prepareIndex("index", "parent", "2").setSource("{}"));
        requests.add(client().prepareIndex("index", "child", "3").setParent("2").setSource("field", "value1"));
        indexRandom(true, requests);

        SearchResponse response = client().prepareSearch("index")
                .setQuery(hasChildQuery("child", matchQuery("field", "value1").queryName("_name1"))
                        .innerHit(new QueryInnerHitBuilder()))
                .addSort("_uid", SortOrder.ASC).get();
        assertHitCount(response, 2);
        assertThat(response.getHits().getAt(0).id(), equalTo("1"));
        assertThat(response.getHits().getAt(0).getInnerHits().get("child").getTotalHits(), equalTo(1l));
        assertThat(response.getHits().getAt(0).getInnerHits().get("child").getAt(0).getMatchedQueries().length,
                equalTo(1));
        assertThat(response.getHits().getAt(0).getInnerHits().get("child").getAt(0).getMatchedQueries()[0],
                equalTo("_name1"));

        assertThat(response.getHits().getAt(1).id(), equalTo("2"));
        assertThat(response.getHits().getAt(1).getInnerHits().get("child").getTotalHits(), equalTo(1l));
        assertThat(response.getHits().getAt(1).getInnerHits().get("child").getAt(0).getMatchedQueries().length,
                equalTo(1));
        assertThat(response.getHits().getAt(1).getInnerHits().get("child").getAt(0).getMatchedQueries()[0],
                equalTo("_name1"));

        response = client().prepareSearch("index")
                .setQuery(hasChildQuery("child", matchQuery("field", "value2").queryName("_name2"))
                        .innerHit(new QueryInnerHitBuilder()))
                .addSort("_id", SortOrder.ASC).get();
        assertHitCount(response, 1);
        assertThat(response.getHits().getAt(0).id(), equalTo("1"));
        assertThat(response.getHits().getAt(0).getInnerHits().get("child").getTotalHits(), equalTo(1l));
        assertThat(response.getHits().getAt(0).getInnerHits().get("child").getAt(0).getMatchedQueries().length,
                equalTo(1));
        assertThat(response.getHits().getAt(0).getInnerHits().get("child").getAt(0).getMatchedQueries()[0],
                equalTo("_name2"));
    }

    @Test
    public void testDontExplode() throws Exception {
        assertAcked(prepareCreate("index1").addMapping("child", "_parent", "type=parent"));
        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("index1", "parent", "1").setSource("{}"));
        requests.add(client().prepareIndex("index1", "child", "1").setParent("1").setSource("field", "value1"));
        indexRandom(true, requests);

        SearchResponse response = client().prepareSearch("index1")
                .setQuery(hasChildQuery("child", matchQuery("field", "value1"))
                        .innerHit(new QueryInnerHitBuilder().setSize(ArrayUtil.MAX_ARRAY_LENGTH - 1)))
                .addSort("_uid", SortOrder.ASC).get();
        assertNoFailures(response);
        assertHitCount(response, 1);

        assertAcked(prepareCreate("index2").addMapping("type", "nested", "type=nested"));
        client().prepareIndex("index2", "type", "1").setSource(jsonBuilder().startObject().startArray("nested")
                .startObject().field("field", "value1").endObject().endArray().endObject()).setRefresh(true).get();

        response = client().prepareSearch("index2")
                .setQuery(nestedQuery("nested", matchQuery("nested.field", "value1"))
                        .innerHit(new QueryInnerHitBuilder().setSize(ArrayUtil.MAX_ARRAY_LENGTH - 1)))
                .addSort("_uid", SortOrder.ASC).get();
        assertNoFailures(response);
        assertHitCount(response, 1);
    }

    public void testTopLevelInnerHitsWithQueryInnerHits() throws Exception {
        // top level inner hits shouldn't overwrite query inner hits definitions

        assertAcked(prepareCreate("index1").addMapping("child", "_parent", "type=parent"));
        List<IndexRequestBuilder> requests = new ArrayList<>();
        requests.add(client().prepareIndex("index1", "parent", "1").setSource("{}"));
        requests.add(client().prepareIndex("index1", "child", "2").setParent("1").setSource("{}"));
        indexRandom(true, requests);

        InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
        SearchResponse response = client().prepareSearch("index1")
                .setQuery(hasChildQuery("child", matchAllQuery()).innerHit(new QueryInnerHitBuilder()))
                .addInnerHit("my-inner-hit", new InnerHitsBuilder.InnerHit().setType("child")).get();
        assertHitCount(response, 1);
        assertThat(response.getHits().getAt(0).getInnerHits().size(), equalTo(2));
        assertThat(response.getHits().getAt(0).getInnerHits().get("child").getAt(0).getId(), equalTo("2"));
        assertThat(response.getHits().getAt(0).getInnerHits().get("my-inner-hit").getAt(0).getId(), equalTo("2"));
    }

}