Browse Source

Drop top level inner hits in favour of inner hits defined in the query dsl.

Fix a limitation that prevent from hierarchical inner hits be defined in query dsl.

Removed the nested_path, parent_child_type and query options from inner hits dsl. These options are only set by ES
upon parsing the has_child, has_parent and nested queries are using their respective query builders.

These options are still used internally, when these options are set a new private copy is created based on the
provided InnerHitBuilder and configuring either nested_path or parent_child_type and the inner query of the query builder
being used.

Closes #11118
Martijn van Groningen 9 years ago
parent
commit
6c3beaa2eb
24 changed files with 652 additions and 1005 deletions
  1. 0 6
      core/src/main/java/org/elasticsearch/action/search/SearchRequestBuilder.java
  2. 10 0
      core/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java
  3. 12 0
      core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java
  4. 7 0
      core/src/main/java/org/elasticsearch/index/query/BoostingQueryBuilder.java
  5. 6 0
      core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java
  6. 14 10
      core/src/main/java/org/elasticsearch/index/query/HasChildQueryBuilder.java
  7. 16 9
      core/src/main/java/org/elasticsearch/index/query/HasParentQueryBuilder.java
  8. 150 96
      core/src/main/java/org/elasticsearch/index/query/InnerHitBuilder.java
  9. 16 8
      core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java
  10. 0 12
      core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java
  11. 10 1
      core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java
  12. 0 126
      core/src/main/java/org/elasticsearch/index/query/support/InnerHitsBuilder.java
  13. 13 21
      core/src/main/java/org/elasticsearch/search/SearchService.java
  14. 2 31
      core/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java
  15. 18 23
      core/src/test/java/org/elasticsearch/index/query/HasChildQueryBuilderTests.java
  16. 14 9
      core/src/test/java/org/elasticsearch/index/query/HasParentQueryBuilderTests.java
  17. 145 43
      core/src/test/java/org/elasticsearch/index/query/InnerHitBuilderTests.java
  18. 52 11
      core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java
  19. 0 140
      core/src/test/java/org/elasticsearch/index/query/support/InnerHitsBuilderTests.java
  20. 1 30
      core/src/test/java/org/elasticsearch/percolator/PercolatorIT.java
  21. 0 10
      core/src/test/java/org/elasticsearch/search/builder/SearchSourceBuilderTests.java
  22. 162 342
      core/src/test/java/org/elasticsearch/search/innerhits/InnerHitsIT.java
  23. 3 2
      docs/reference/migration/migrate_5_0/search.asciidoc
  24. 1 75
      docs/reference/search/request/inner-hits.asciidoc

+ 0 - 6
core/src/main/java/org/elasticsearch/action/search/SearchRequestBuilder.java

@@ -31,7 +31,6 @@ import org.elasticsearch.search.Scroll;
 import org.elasticsearch.search.aggregations.AggregatorBuilder;
 import org.elasticsearch.search.aggregations.pipeline.PipelineAggregatorBuilder;
 import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.elasticsearch.index.query.support.InnerHitsBuilder;
 import org.elasticsearch.search.highlight.HighlightBuilder;
 import org.elasticsearch.search.rescore.RescoreBuilder;
 import org.elasticsearch.search.sort.SortBuilder;
@@ -400,11 +399,6 @@ public class SearchRequestBuilder extends ActionRequestBuilder<SearchRequest, Se
         return this;
     }
 
-    public SearchRequestBuilder innerHits(InnerHitsBuilder innerHitsBuilder) {
-        sourceBuilder().innerHits(innerHitsBuilder);
-        return this;
-    }
-
     /**
      * Clears all rescorers on the builder and sets the first one.  To use multiple rescore windows use
      * {@link #addRescorer(org.elasticsearch.search.rescore.RescoreBuilder, int)}.

+ 10 - 0
core/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java

@@ -36,6 +36,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -273,6 +274,15 @@ public abstract class AbstractQueryBuilder<QB extends AbstractQueryBuilder<QB>>
         return this;
     }
 
+    /**
+     * For internal usage only!
+     *
+     * Extracts the inner hits from the query tree.
+     * While it extracts inner hits, child inner hits are inlined into the inner hit builder they belong to.
+     */
+    protected void extractInnerHitBuilders(Map<String, InnerHitBuilder> innerHits) {
+    }
+
     // Like Objects.requireNotNull(...) but instead throws a IllegalArgumentException
     protected static <T> T requireValue(T value, String message) {
         if (value == null) {

+ 12 - 0
core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java

@@ -35,6 +35,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.function.Consumer;
 
@@ -495,6 +496,17 @@ public class BoolQueryBuilder extends AbstractQueryBuilder<BoolQueryBuilder> {
         return this;
     }
 
+    @Override
+    protected void extractInnerHitBuilders(Map<String, InnerHitBuilder> innerHits) {
+        List<QueryBuilder<?>> clauses = new ArrayList<>(filter());
+        clauses.addAll(must());
+        clauses.addAll(should());
+        // no need to include must_not (since there will be no hits for it)
+        for (QueryBuilder<?> clause : clauses) {
+            InnerHitBuilder.extractInnerHits(clause, innerHits);
+        }
+    }
+
     private static boolean rewriteClauses(QueryRewriteContext queryRewriteContext, List<QueryBuilder<?>> builders,
                                           Consumer<QueryBuilder<?>> consumer) throws IOException {
         boolean changed = false;

+ 7 - 0
core/src/main/java/org/elasticsearch/index/query/BoostingQueryBuilder.java

@@ -29,6 +29,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -235,4 +236,10 @@ public class BoostingQueryBuilder extends AbstractQueryBuilder<BoostingQueryBuil
         }
         return this;
     }
+
+    @Override
+    protected void extractInnerHitBuilders(Map<String, InnerHitBuilder> innerHits) {
+        InnerHitBuilder.extractInnerHits(positiveQuery, innerHits);
+        InnerHitBuilder.extractInnerHits(negativeQuery, innerHits);
+    }
 }

+ 6 - 0
core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java

@@ -29,6 +29,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -169,4 +170,9 @@ public class ConstantScoreQueryBuilder extends AbstractQueryBuilder<ConstantScor
         }
         return this;
     }
+
+    @Override
+    protected void extractInnerHitBuilders(Map<String, InnerHitBuilder> innerHits) {
+        InnerHitBuilder.extractInnerHits(filterBuilder, innerHits);
+    }
 }

+ 14 - 10
core/src/main/java/org/elasticsearch/index/query/HasChildQueryBuilder.java

@@ -38,10 +38,10 @@ import org.elasticsearch.index.fielddata.IndexParentChildFieldData;
 import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
 import org.elasticsearch.index.mapper.DocumentMapper;
 import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
 
 import java.io.IOException;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -151,9 +151,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
     }
 
     public HasChildQueryBuilder innerHit(InnerHitBuilder innerHit) {
-        innerHit.setParentChildType(type);
-        innerHit.setQuery(query);
-        this.innerHitBuilder = innerHit;
+        this.innerHitBuilder = new InnerHitBuilder(Objects.requireNonNull(innerHit), query, type);
         return this;
     }
 
@@ -274,8 +272,11 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
                 }
             }
         }
-        HasChildQueryBuilder hasChildQueryBuilder = new HasChildQueryBuilder(childType, iqb, minChildren, maxChildren,
-                scoreMode, innerHitBuilder);
+        HasChildQueryBuilder hasChildQueryBuilder = new HasChildQueryBuilder(childType, iqb, scoreMode);
+        if (innerHitBuilder != null) {
+            hasChildQueryBuilder.innerHit(innerHitBuilder);
+        }
+        hasChildQueryBuilder.minMaxChildren(minChildren, maxChildren);
         hasChildQueryBuilder.queryName(queryName);
         hasChildQueryBuilder.boost(boost);
         hasChildQueryBuilder.ignoreUnmapped(ignoreUnmapped);
@@ -337,10 +338,6 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
         if (parentFieldMapper.active() == false) {
             throw new QueryShardException(context, "[" + NAME + "] _parent field has no parent type configured");
         }
-        if (innerHitBuilder != null) {
-            context.addInnerHit(innerHitBuilder);
-        }
-
         String parentType = parentFieldMapper.type();
         DocumentMapper parentDocMapper = context.getMapperService().documentMapper(parentType);
         if (parentDocMapper == null) {
@@ -477,4 +474,11 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
         }
         return this;
     }
+
+    @Override
+    protected void extractInnerHitBuilders(Map<String, InnerHitBuilder> innerHits) {
+        if (innerHitBuilder != null) {
+            innerHitBuilder.inlineInnerHits(innerHits);
+        }
+    }
 }

+ 16 - 9
core/src/main/java/org/elasticsearch/index/query/HasParentQueryBuilder.java

@@ -33,10 +33,10 @@ import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
 import org.elasticsearch.index.mapper.DocumentMapper;
 import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
 
 import java.io.IOException;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -127,9 +127,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
     }
 
     public HasParentQueryBuilder innerHit(InnerHitBuilder innerHit) {
-        innerHit.setParentChildType(type);
-        innerHit.setQuery(query);
-        this.innerHit = innerHit;
+        this.innerHit = new InnerHitBuilder(innerHit, query, type);
         return this;
     }
 
@@ -175,10 +173,6 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
             }
         }
 
-        if (innerHit != null) {
-            context.addInnerHit(innerHit);
-        }
-
         Set<String> childTypes = new HashSet<>();
         ParentChildIndexFieldData parentChildIndexFieldData = null;
         for (DocumentMapper documentMapper : context.getMapperService().docMappers(false)) {
@@ -282,8 +276,14 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
                 }
             }
         }
-        return new HasParentQueryBuilder(parentType, iqb, score, innerHits).ignoreUnmapped(ignoreUnmapped).queryName(queryName)
+        HasParentQueryBuilder queryBuilder =  new HasParentQueryBuilder(parentType, iqb, score)
+                .ignoreUnmapped(ignoreUnmapped)
+                .queryName(queryName)
                 .boost(boost);
+        if (innerHits != null) {
+            queryBuilder.innerHit(innerHits);
+        }
+        return queryBuilder;
     }
 
     @Override
@@ -313,4 +313,11 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
         }
         return this;
     }
+
+    @Override
+    protected void extractInnerHitBuilders(Map<String, InnerHitBuilder> innerHits) {
+        if (innerHit!= null) {
+            innerHit.inlineInnerHits(innerHits);
+        }
+    }
 }

+ 150 - 96
core/src/main/java/org/elasticsearch/index/query/support/InnerHitBuilder.java → core/src/main/java/org/elasticsearch/index/query/InnerHitBuilder.java

@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.elasticsearch.index.query.support;
+package org.elasticsearch.index.query;
 
 import org.apache.lucene.search.Sort;
 import org.elasticsearch.action.support.ToXContentToBytes;
@@ -30,11 +30,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.mapper.DocumentMapper;
 import org.elasticsearch.index.mapper.object.ObjectMapper;
-import org.elasticsearch.index.query.MatchAllQueryBuilder;
-import org.elasticsearch.index.query.ParsedQuery;
-import org.elasticsearch.index.query.QueryBuilder;
-import org.elasticsearch.index.query.QueryParseContext;
-import org.elasticsearch.index.query.QueryShardContext;
 import org.elasticsearch.script.Script;
 import org.elasticsearch.script.ScriptContext;
 import org.elasticsearch.script.SearchScript;
@@ -62,15 +57,12 @@ import static org.elasticsearch.common.xcontent.XContentParser.Token.END_OBJECT;
 public final class InnerHitBuilder extends ToXContentToBytes implements Writeable {
 
     public static final ParseField NAME_FIELD = new ParseField("name");
-    public static final ParseField NESTED_PATH_FIELD = new ParseField("path");
-    public static final ParseField PARENT_CHILD_TYPE_FIELD = new ParseField("type");
+    public static final ParseField INNER_HITS_FIELD = new ParseField("inner_hits");
 
     private final static ObjectParser<InnerHitBuilder, QueryParseContext> PARSER = new ObjectParser<>("inner_hits", InnerHitBuilder::new);
 
     static {
         PARSER.declareString(InnerHitBuilder::setName, NAME_FIELD);
-        PARSER.declareString(InnerHitBuilder::setNestedPath, NESTED_PATH_FIELD);
-        PARSER.declareString(InnerHitBuilder::setParentChildType, PARENT_CHILD_TYPE_FIELD);
         PARSER.declareInt(InnerHitBuilder::setFrom, SearchSourceBuilder.FROM_FIELD);
         PARSER.declareInt(InnerHitBuilder::setSize, SearchSourceBuilder.SIZE_FIELD);
         PARSER.declareBoolean(InnerHitBuilder::setExplain, SearchSourceBuilder.EXPLAIN_FIELD);
@@ -100,20 +92,30 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl
         }, SearchSourceBuilder._SOURCE_FIELD, ObjectParser.ValueType.OBJECT_OR_BOOLEAN);
         PARSER.declareObject(InnerHitBuilder::setHighlightBuilder, (p, c) -> HighlightBuilder.fromXContent(c),
                 SearchSourceBuilder.HIGHLIGHT_FIELD);
-        PARSER.declareObject(InnerHitBuilder::setQuery, (p, c) ->{
+        PARSER.declareObject(InnerHitBuilder::setChildInnerHits, (p, c) -> {
             try {
-                return c.parseInnerQueryBuilder();
-            } catch (IOException e) {
-                throw new ParsingException(p.getTokenLocation(), "Could not parse inner query definition", e);
-            }
-        }, SearchSourceBuilder.QUERY_FIELD);
-        PARSER.declareObject(InnerHitBuilder::setInnerHitsBuilder, (p, c) -> {
-            try {
-                return InnerHitsBuilder.fromXContent(c);
+                Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+                String innerHitName = null;
+                for (XContentParser.Token token = p.nextToken(); token != XContentParser.Token.END_OBJECT; token = p.nextToken()) {
+                    switch (token) {
+                        case START_OBJECT:
+                            InnerHitBuilder innerHitBuilder = InnerHitBuilder.fromXContent(c);
+                            innerHitBuilder.setName(innerHitName);
+                            innerHitBuilders.put(innerHitName, innerHitBuilder);
+                            break;
+                        case FIELD_NAME:
+                            innerHitName = p.currentName();
+                            break;
+                        default:
+                            throw new ParsingException(p.getTokenLocation(), "Expected [" + XContentParser.Token.START_OBJECT + "] in ["
+                                    + p.currentName() + "] but found [" + token + "]", p.getTokenLocation());
+                    }
+                }
+                return innerHitBuilders;
             } catch (IOException e) {
                 throw new ParsingException(p.getTokenLocation(), "Could not parse inner query definition", e);
             }
-        }, SearchSourceBuilder.INNER_HITS_FIELD);
+        }, INNER_HITS_FIELD);
     }
 
     private String name;
@@ -132,8 +134,8 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl
     private List<String> fieldDataFields;
     private List<ScriptField> scriptFields;
     private HighlightBuilder highlightBuilder;
-    private InnerHitsBuilder innerHitsBuilder;
     private FetchSourceContext fetchSourceContext;
+    private Map<String, InnerHitBuilder> childInnerHits;
 
     public InnerHitBuilder() {
     }
@@ -165,7 +167,62 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl
         }
         highlightBuilder = in.readOptionalWriteable(HighlightBuilder::new);
         query = in.readNamedWriteable(QueryBuilder.class);
-        innerHitsBuilder = in.readOptionalWriteable(InnerHitsBuilder::new);
+        if (in.readBoolean()) {
+            int size = in.readVInt();
+            childInnerHits = new HashMap<>(size);
+            for (int i = 0; i < size; i++) {
+                childInnerHits.put(in.readString(), new InnerHitBuilder(in));
+            }
+        }
+    }
+
+    private InnerHitBuilder(InnerHitBuilder other) {
+        name = other.name;
+        from = other.from;
+        size = other.size;
+        explain = other.explain;
+        version = other.version;
+        trackScores = other.trackScores;
+        if (other.fieldNames != null) {
+            fieldNames = new ArrayList<>(other.fieldNames);
+        }
+        if (other.fieldDataFields != null) {
+            fieldDataFields = new ArrayList<>(other.fieldDataFields);
+        }
+        if (other.scriptFields != null) {
+            scriptFields = new ArrayList<>(other.scriptFields);
+        }
+        if (other.fetchSourceContext != null) {
+            fetchSourceContext = new FetchSourceContext(
+                    other.fetchSourceContext.fetchSource(), other.fetchSourceContext.includes(), other.fetchSourceContext.excludes()
+            );
+        }
+        if (other.sorts != null) {
+            sorts = new ArrayList<>(other.sorts);
+        }
+        highlightBuilder = other.highlightBuilder;
+        if (other.childInnerHits != null) {
+            childInnerHits = new HashMap<>(other.childInnerHits);
+        }
+    }
+
+
+    InnerHitBuilder(InnerHitBuilder other, String nestedPath, QueryBuilder query) {
+        this(other);
+        this.query = query;
+        this.nestedPath = nestedPath;
+        if (name == null) {
+            this.name = nestedPath;
+        }
+    }
+
+    InnerHitBuilder(InnerHitBuilder other, QueryBuilder query, String parentChildType) {
+        this(other);
+        this.query = query;
+        this.parentChildType = parentChildType;
+        if (name == null) {
+            this.name = parentChildType;
+        }
     }
 
     @Override
@@ -196,17 +253,15 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl
         }
         out.writeOptionalWriteable(highlightBuilder);
         out.writeNamedWriteable(query);
-        out.writeOptionalWriteable(innerHitsBuilder);
-    }
-
-    public InnerHitBuilder setParentChildType(String parentChildType) {
-        this.parentChildType = parentChildType;
-        return this;
-    }
-
-    public InnerHitBuilder setNestedPath(String nestedPath) {
-        this.nestedPath = nestedPath;
-        return this;
+        boolean hasChildInnerHits = childInnerHits != null;
+        out.writeBoolean(hasChildInnerHits);
+        if (hasChildInnerHits) {
+            out.writeVInt(childInnerHits.size());
+            for (Map.Entry<String, InnerHitBuilder> entry : childInnerHits.entrySet()) {
+                out.writeString(entry.getKey());
+                entry.getValue().writeTo(out);
+            }
+        }
     }
 
     public String getName() {
@@ -347,72 +402,53 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl
         return this;
     }
 
-    public QueryBuilder<?> getQuery() {
+    QueryBuilder<?> getQuery() {
         return query;
     }
 
-    public InnerHitBuilder setQuery(QueryBuilder<?> query) {
-        this.query = Objects.requireNonNull(query);
-        return this;
+    void setChildInnerHits(Map<String, InnerHitBuilder> childInnerHits) {
+        this.childInnerHits = childInnerHits;
     }
 
-    public InnerHitBuilder setInnerHitsBuilder(InnerHitsBuilder innerHitsBuilder) {
-        this.innerHitsBuilder = innerHitsBuilder;
-        return this;
+    String getParentChildType() {
+        return parentChildType;
     }
 
-    public InnerHitsContext.BaseInnerHits buildInline(SearchContext parentSearchContext, QueryShardContext context) throws IOException {
-        InnerHitsContext.BaseInnerHits innerHitsContext;
-        if (nestedPath != null) {
-            ObjectMapper nestedObjectMapper = context.getObjectMapper(nestedPath);
-            ObjectMapper parentObjectMapper = context.nestedScope().getObjectMapper();
-            innerHitsContext = new InnerHitsContext.NestedInnerHits(
-                    name, parentSearchContext, parentObjectMapper, nestedObjectMapper
-            );
-        } else if (parentChildType != null) {
-            DocumentMapper documentMapper = context.getMapperService().documentMapper(parentChildType);
-            innerHitsContext = new InnerHitsContext.ParentChildInnerHits(
-                    name, parentSearchContext, context.getMapperService(), documentMapper
-            );
-        } else {
-            throw new IllegalStateException("Neither a nested or parent/child inner hit");
+    String getNestedPath() {
+        return nestedPath;
+    }
+
+    void addChildInnerHit(InnerHitBuilder innerHitBuilder) {
+        if (childInnerHits == null) {
+            childInnerHits = new HashMap<>();
         }
-        setupInnerHitsContext(context, innerHitsContext);
-        return innerHitsContext;
+        this.childInnerHits.put(innerHitBuilder.getName(), innerHitBuilder);
     }
 
-    /**
-     * Top level inner hits are different than inline inner hits:
-     * 1) Nesting. Top level inner hits can be hold nested inner hits, that why this method is recursive (via buildChildInnerHits)
-     * 2) Top level inner hits query is an option, whereas with inline inner hits that is based on the nested, has_child
-     *    or has_parent's inner query.
-     *
-     *  Because of these changes there are different methods for building inline (which is simpler) and top level inner
-     *  hits. Also top level inner hits will soon be deprecated.
-     */
-    public InnerHitsContext.BaseInnerHits buildTopLevel(SearchContext parentSearchContext, QueryShardContext context,
-                                                        InnerHitsContext innerHitsContext) throws IOException {
+    public InnerHitsContext.BaseInnerHits build(SearchContext parentSearchContext,
+                                                InnerHitsContext innerHitsContext) throws IOException {
+        QueryShardContext queryShardContext = parentSearchContext.getQueryShardContext();
         if (nestedPath != null) {
-            ObjectMapper nestedObjectMapper = context.getObjectMapper(nestedPath);
-            ObjectMapper parentObjectMapper = context.nestedScope().nextLevel(nestedObjectMapper);
+            ObjectMapper nestedObjectMapper = queryShardContext.getObjectMapper(nestedPath);
+            ObjectMapper parentObjectMapper = queryShardContext.nestedScope().nextLevel(nestedObjectMapper);
             InnerHitsContext.NestedInnerHits nestedInnerHits = new InnerHitsContext.NestedInnerHits(
                     name, parentSearchContext, parentObjectMapper, nestedObjectMapper
             );
-            setupInnerHitsContext(context, nestedInnerHits);
-            if (innerHitsBuilder != null) {
-                buildChildInnerHits(parentSearchContext, context, nestedInnerHits);
+            setupInnerHitsContext(queryShardContext, nestedInnerHits);
+            if (childInnerHits != null) {
+                buildChildInnerHits(parentSearchContext, nestedInnerHits);
             }
-            context.nestedScope().previousLevel();
+            queryShardContext.nestedScope().previousLevel();
             innerHitsContext.addInnerHitDefinition(nestedInnerHits);
             return nestedInnerHits;
         } else if (parentChildType != null) {
-            DocumentMapper documentMapper = context.getMapperService().documentMapper(parentChildType);
+            DocumentMapper documentMapper = queryShardContext.getMapperService().documentMapper(parentChildType);
             InnerHitsContext.ParentChildInnerHits parentChildInnerHits = new InnerHitsContext.ParentChildInnerHits(
-                    name, parentSearchContext, context.getMapperService(), documentMapper
+                    name, parentSearchContext, queryShardContext.getMapperService(), documentMapper
             );
-            setupInnerHitsContext(context, parentChildInnerHits);
-            if (innerHitsBuilder != null) {
-                buildChildInnerHits(parentSearchContext, context, parentChildInnerHits);
+            setupInnerHitsContext(queryShardContext, parentChildInnerHits);
+            if (childInnerHits != null) {
+                buildChildInnerHits(parentSearchContext, parentChildInnerHits);
             }
             innerHitsContext.addInnerHitDefinition( parentChildInnerHits);
             return parentChildInnerHits;
@@ -421,12 +457,11 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl
         }
     }
 
-    private void buildChildInnerHits(SearchContext parentSearchContext, QueryShardContext context,
-                                     InnerHitsContext.BaseInnerHits innerHits) throws IOException {
+    private void buildChildInnerHits(SearchContext parentSearchContext, InnerHitsContext.BaseInnerHits innerHits) throws IOException {
         Map<String, InnerHitsContext.BaseInnerHits> childInnerHits = new HashMap<>();
-        for (Map.Entry<String, InnerHitBuilder> entry : innerHitsBuilder.getInnerHitsBuilders().entrySet()) {
-            InnerHitsContext.BaseInnerHits childInnerHit = entry.getValue().buildTopLevel(
-                    parentSearchContext, context, new InnerHitsContext()
+        for (Map.Entry<String, InnerHitBuilder> entry : this.childInnerHits.entrySet()) {
+            InnerHitsContext.BaseInnerHits childInnerHit = entry.getValue().build(
+                    parentSearchContext, new InnerHitsContext()
             );
             childInnerHits.put(entry.getKey(), childInnerHit);
         }
@@ -480,16 +515,23 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl
         innerHitsContext.parsedQuery(parsedQuery);
     }
 
+    public void inlineInnerHits(Map<String, InnerHitBuilder> innerHits) {
+        InnerHitBuilder copy = new InnerHitBuilder(this);
+        copy.parentChildType = this.parentChildType;
+        copy.nestedPath = this.nestedPath;
+        copy.query = this.query;
+        innerHits.put(copy.getName(), copy);
+
+        Map<String, InnerHitBuilder> childInnerHits = new HashMap<>();
+        extractInnerHits(query, childInnerHits);
+        if (childInnerHits.size() > 0) {
+            copy.setChildInnerHits(childInnerHits);
+        }
+    }
+
     @Override
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject();
-
-        if (nestedPath != null) {
-            builder.field(NESTED_PATH_FIELD.getPreferredName(), nestedPath);
-        }
-        if (parentChildType != null) {
-            builder.field(PARENT_CHILD_TYPE_FIELD.getPreferredName(), parentChildType);
-        }
         if (name != null) {
             builder.field(NAME_FIELD.getPreferredName(), name);
         }
@@ -536,9 +578,12 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl
         if (highlightBuilder != null) {
             builder.field(SearchSourceBuilder.HIGHLIGHT_FIELD.getPreferredName(), highlightBuilder, params);
         }
-        builder.field(SearchSourceBuilder.QUERY_FIELD.getPreferredName(), query, params);
-        if (innerHitsBuilder != null) {
-            builder.field(SearchSourceBuilder.INNER_HITS_FIELD.getPreferredName(), innerHitsBuilder, params);
+        if (childInnerHits != null) {
+            builder.startObject(INNER_HITS_FIELD.getPreferredName());
+            for (Map.Entry<String, InnerHitBuilder> entry : childInnerHits.entrySet()) {
+                builder.field(entry.getKey(), entry.getValue(), params);
+            }
+            builder.endObject();
         }
         builder.endObject();
         return builder;
@@ -565,17 +610,26 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl
                 Objects.equals(sorts, that.sorts) &&
                 Objects.equals(highlightBuilder, that.highlightBuilder) &&
                 Objects.equals(query, that.query) &&
-                Objects.equals(innerHitsBuilder, that.innerHitsBuilder);
+                Objects.equals(childInnerHits, that.childInnerHits);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(name, nestedPath, parentChildType, from, size, explain, version, trackScores, fieldNames,
-                fieldDataFields, scriptFields, fetchSourceContext, sorts, highlightBuilder, query, innerHitsBuilder);
+                fieldDataFields, scriptFields, fetchSourceContext, sorts, highlightBuilder, query, childInnerHits);
     }
 
     public static InnerHitBuilder fromXContent(QueryParseContext context) throws IOException {
         return PARSER.parse(context.parser(), new InnerHitBuilder(), context);
     }
 
+    public static void extractInnerHits(QueryBuilder<?> query, Map<String, InnerHitBuilder> innerHitBuilders) {
+        if (query instanceof AbstractQueryBuilder) {
+            ((AbstractQueryBuilder) query).extractInnerHitBuilders(innerHitBuilders);
+        } else {
+            throw new IllegalStateException("provided query builder [" + query.getClass() +
+                    "] class should inherit from AbstractQueryBuilder, but it doesn't");
+        }
+    }
+
 }

+ 16 - 8
core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java

@@ -32,9 +32,9 @@ import org.elasticsearch.common.lucene.search.Queries;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.index.mapper.object.ObjectMapper;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
 
 import java.io.IOException;
+import java.util.Map;
 import java.util.Objects;
 
 public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder> {
@@ -109,9 +109,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
     }
 
     public NestedQueryBuilder innerHit(InnerHitBuilder innerHit) {
-        innerHit.setNestedPath(path);
-        innerHit.setQuery(query);
-        this.innerHitBuilder = innerHit;
+        this.innerHitBuilder = new InnerHitBuilder(innerHit, path, query);
         return this;
     }
 
@@ -196,8 +194,14 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
                 }
             }
         }
-        return new NestedQueryBuilder(path, query, scoreMode, innerHitBuilder).ignoreUnmapped(ignoreUnmapped).queryName(queryName)
+        NestedQueryBuilder queryBuilder =  new NestedQueryBuilder(path, query, scoreMode)
+                .ignoreUnmapped(ignoreUnmapped)
+                .queryName(queryName)
                 .boost(boost);
+        if (innerHitBuilder != null) {
+            queryBuilder.innerHit(innerHitBuilder);
+        }
+        return queryBuilder;
     }
 
     @Override
@@ -236,9 +240,6 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
         final Query childFilter;
         final Query innerQuery;
         ObjectMapper objectMapper = context.nestedScope().getObjectMapper();
-        if (innerHitBuilder != null) {
-            context.addInnerHit(innerHitBuilder);
-        }
         if (objectMapper == null) {
             parentFilter = context.bitsetFilter(Queries.newNonNestedFilter());
         } else {
@@ -265,4 +266,11 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
         }
         return this;
     }
+
+    @Override
+    protected void extractInnerHitBuilders(Map<String, InnerHitBuilder> innerHits) {
+        if (innerHitBuilder != null) {
+            innerHitBuilder.inlineInnerHits(innerHits);
+        }
+    }
 }

+ 0 - 12
core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java

@@ -57,12 +57,10 @@ import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.index.mapper.core.TextFieldMapper;
 import org.elasticsearch.index.mapper.object.ObjectMapper;
 import org.elasticsearch.index.percolator.PercolatorQueryCache;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
 import org.elasticsearch.index.query.support.NestedScope;
 import org.elasticsearch.index.similarity.SimilarityService;
 import org.elasticsearch.indices.query.IndicesQueriesRegistry;
 import org.elasticsearch.script.ScriptService;
-import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
 import org.elasticsearch.search.internal.SearchContext;
 import org.elasticsearch.search.lookup.SearchLookup;
 
@@ -185,16 +183,6 @@ public class QueryShardContext extends QueryRewriteContext {
         return isFilter;
     }
 
-    public void addInnerHit(InnerHitBuilder innerHitBuilder) throws IOException {
-        SearchContext sc = SearchContext.current();
-        if (sc == null) {
-            throw new QueryShardException(this, "inner_hits unsupported");
-        }
-
-        InnerHitsContext innerHitsContext = sc.innerHits();
-        innerHitsContext.addInnerHitDefinition(innerHitBuilder.buildInline(sc, this));
-    }
-
     public Collection<String> simpleMatchToIndexNames(String pattern) {
         return mapperService.simpleMatchToIndexNames(pattern);
     }

+ 10 - 1
core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java

@@ -42,12 +42,14 @@ import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryParseContext;
 import org.elasticsearch.index.query.QueryRewriteContext;
 import org.elasticsearch.index.query.QueryShardContext;
+import org.elasticsearch.index.query.InnerHitBuilder;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -429,8 +431,15 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
         return this;
     }
 
+
+
+    @Override
+    protected void extractInnerHitBuilders(Map<String, InnerHitBuilder> innerHits) {
+        InnerHitBuilder.extractInnerHits(query(), innerHits);
+    }
+
     public static FunctionScoreQueryBuilder fromXContent(ParseFieldRegistry<ScoreFunctionParser<?>> scoreFunctionsRegistry,
-            QueryParseContext parseContext) throws IOException {
+                                                         QueryParseContext parseContext) throws IOException {
         XContentParser parser = parseContext.parser();
 
         QueryBuilder<?> query = null;

+ 0 - 126
core/src/main/java/org/elasticsearch/index/query/support/InnerHitsBuilder.java

@@ -1,126 +0,0 @@
-/*
- * 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.index.query.support;
-
-import org.elasticsearch.action.support.ToXContentToBytes;
-import org.elasticsearch.common.ParsingException;
-import org.elasticsearch.common.io.stream.StreamInput;
-import org.elasticsearch.common.io.stream.StreamOutput;
-import org.elasticsearch.common.io.stream.Writeable;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.common.xcontent.XContentParser.Token;
-import org.elasticsearch.index.query.QueryParseContext;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-
-public final class InnerHitsBuilder extends ToXContentToBytes implements Writeable {
-    private final Map<String, InnerHitBuilder> innerHitsBuilders;
-
-    public InnerHitsBuilder() {
-        this.innerHitsBuilders = new HashMap<>();
-    }
-
-    public InnerHitsBuilder(Map<String, InnerHitBuilder> innerHitsBuilders) {
-        this.innerHitsBuilders = Objects.requireNonNull(innerHitsBuilders);
-    }
-
-    /**
-     * Read from a stream.
-     */
-    public InnerHitsBuilder(StreamInput in) throws IOException {
-        int size = in.readVInt();
-        innerHitsBuilders = new HashMap<>(size);
-        for (int i = 0; i < size; i++) {
-            innerHitsBuilders.put(in.readString(), new InnerHitBuilder(in));
-        }
-    }
-
-    @Override
-    public void writeTo(StreamOutput out) throws IOException {
-        out.writeVInt(innerHitsBuilders.size());
-        for (Map.Entry<String, InnerHitBuilder> entry : innerHitsBuilders.entrySet()) {
-            out.writeString(entry.getKey());
-            entry.getValue().writeTo(out);
-        }
-    }
-
-    public InnerHitsBuilder addInnerHit(String name, InnerHitBuilder builder) {
-        Objects.requireNonNull(name);
-        Objects.requireNonNull(builder);
-        this.innerHitsBuilders.put(name, builder.setName(name));
-        return this;
-    }
-
-    public Map<String, InnerHitBuilder> getInnerHitsBuilders() {
-        return innerHitsBuilders;
-    }
-
-    @Override
-    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-        builder.startObject();
-        for (Map.Entry<String, InnerHitBuilder> entry : innerHitsBuilders.entrySet()) {
-            builder.field(entry.getKey(), entry.getValue(), params);
-        }
-        builder.endObject();
-        return builder;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        InnerHitsBuilder that = (InnerHitsBuilder) o;
-        return innerHitsBuilders.equals(that.innerHitsBuilders);
-
-    }
-
-    @Override
-    public int hashCode() {
-        return innerHitsBuilders.hashCode();
-    }
-
-    public static InnerHitsBuilder fromXContent(QueryParseContext context) throws IOException {
-        Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
-        String innerHitName = null;
-        XContentParser parser = context.parser();
-        for (Token token = parser.nextToken(); token != Token.END_OBJECT; token = parser.nextToken()) {
-            switch (token) {
-                case START_OBJECT:
-                    InnerHitBuilder innerHitBuilder = InnerHitBuilder.fromXContent(context);
-                    innerHitBuilder.setName(innerHitName);
-                    innerHitBuilders.put(innerHitName, innerHitBuilder);
-                    break;
-                case FIELD_NAME:
-                    innerHitName = parser.currentName();
-                    break;
-                default:
-                    throw new ParsingException(parser.getTokenLocation(), "Expected [" + XContentParser.Token.START_OBJECT + "] in ["
-                            + parser.currentName() + "] but found [" + token + "]", parser.getTokenLocation());
-            }
-        }
-        return new InnerHitsBuilder(innerHitBuilders);
-    }
-
-
-}

+ 13 - 21
core/src/main/java/org/elasticsearch/search/SearchService.java

@@ -62,7 +62,7 @@ import org.elasticsearch.index.IndexService;
 import org.elasticsearch.index.engine.Engine;
 import org.elasticsearch.index.query.QueryParseContext;
 import org.elasticsearch.index.query.QueryShardContext;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
+import org.elasticsearch.index.query.InnerHitBuilder;
 import org.elasticsearch.index.search.stats.StatsGroupsParseElement;
 import org.elasticsearch.index.shard.IndexEventListener;
 import org.elasticsearch.index.shard.IndexShard;
@@ -88,7 +88,6 @@ import org.elasticsearch.search.fetch.ShardFetchRequest;
 import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext;
 import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext.FieldDataField;
 import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsFetchSubPhase;
-import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
 import org.elasticsearch.search.fetch.script.ScriptFieldsContext.ScriptField;
 import org.elasticsearch.search.highlight.HighlightBuilder;
 import org.elasticsearch.search.internal.DefaultSearchContext;
@@ -679,12 +678,24 @@ public class SearchService extends AbstractLifecycleComponent<SearchService> imp
                 context.queryBoost(indexBoost);
             }
         }
+        Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
         if (source.query() != null) {
+            InnerHitBuilder.extractInnerHits(source.query(), innerHitBuilders);
             context.parsedQuery(queryShardContext.toQuery(source.query()));
         }
         if (source.postFilter() != null) {
+            InnerHitBuilder.extractInnerHits(source.postFilter(), innerHitBuilders);
             context.parsedPostFilter(queryShardContext.toQuery(source.postFilter()));
         }
+        if (innerHitBuilders.size() > 0) {
+            for (Map.Entry<String, InnerHitBuilder> entry : innerHitBuilders.entrySet()) {
+                try {
+                    entry.getValue().build(context, context.innerHits());
+                } catch (IOException e) {
+                    throw new SearchContextException(context, "failed to build inner_hits", e);
+                }
+            }
+        }
         if (source.sorts() != null) {
             try {
                 Optional<Sort> optionalSort = SortBuilder.buildSort(source.sorts(), context.getQueryShardContext());
@@ -754,25 +765,6 @@ public class SearchService extends AbstractLifecycleComponent<SearchService> imp
                 throw new SearchContextException(context, "failed to create SearchContextHighlighter", e);
             }
         }
-        if (source.innerHits() != null) {
-            for (Map.Entry<String, InnerHitBuilder> entry : source.innerHits().getInnerHitsBuilders().entrySet()) {
-                try {
-                    // This is the same logic in QueryShardContext#toQuery() where we reset also twice.
-                    // Personally I think a reset at the end is sufficient, but I kept the logic consistent with this method.
-
-                    // The reason we need to invoke reset at all here is because inner hits may modify the QueryShardContext#nestedScope,
-                    // so we need to reset at the end.
-                    queryShardContext.reset();
-                    InnerHitBuilder innerHitBuilder = entry.getValue();
-                    InnerHitsContext innerHitsContext = context.innerHits();
-                    innerHitBuilder.buildTopLevel(context, queryShardContext, innerHitsContext);
-                } catch (IOException e) {
-                    throw new SearchContextException(context, "failed to create InnerHitsContext", e);
-                } finally {
-                    queryShardContext.reset();
-                }
-            }
-        }
         if (source.scriptFields() != null) {
             for (org.elasticsearch.search.builder.SearchSourceBuilder.ScriptField field : source.scriptFields()) {
                 SearchScript searchScript = context.scriptService().search(context.lookup(), field.script(), ScriptContext.Standard.SEARCH,

+ 2 - 31
core/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java

@@ -40,7 +40,6 @@ import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryParseContext;
 import org.elasticsearch.index.query.QueryShardContext;
-import org.elasticsearch.index.query.support.InnerHitsBuilder;
 import org.elasticsearch.script.Script;
 import org.elasticsearch.search.aggregations.AggregatorBuilder;
 import org.elasticsearch.search.aggregations.AggregatorFactories;
@@ -93,7 +92,6 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
     public static final ParseField INDICES_BOOST_FIELD = new ParseField("indices_boost");
     public static final ParseField AGGREGATIONS_FIELD = new ParseField("aggregations", "aggs");
     public static final ParseField HIGHLIGHT_FIELD = new ParseField("highlight");
-    public static final ParseField INNER_HITS_FIELD = new ParseField("inner_hits");
     public static final ParseField SUGGEST_FIELD = new ParseField("suggest");
     public static final ParseField RESCORE_FIELD = new ParseField("rescore");
     public static final ParseField STATS_FIELD = new ParseField("stats");
@@ -156,8 +154,6 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
 
     private SuggestBuilder suggestBuilder;
 
-    private InnerHitsBuilder innerHitsBuilder;
-
     private List<RescoreBuilder<?>> rescoreBuilders;
 
     private ObjectFloatHashMap<String> indexBoost = null;
@@ -205,14 +201,11 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
         boolean hasIndexBoost = in.readBoolean();
         if (hasIndexBoost) {
             int size = in.readVInt();
-            indexBoost = new ObjectFloatHashMap<String>(size);
+            indexBoost = new ObjectFloatHashMap<>(size);
             for (int i = 0; i < size; i++) {
                 indexBoost.put(in.readString(), in.readFloat());
             }
         }
-        if (in.readBoolean()) {
-            innerHitsBuilder = new InnerHitsBuilder(in);
-        }
         if (in.readBoolean()) {
             minScore = in.readFloat();
         }
@@ -303,11 +296,6 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
                 out.writeFloat(indexBoost.get(key.value));
             }
         }
-        boolean hasInnerHitsBuilder = innerHitsBuilder != null;
-        out.writeBoolean(hasInnerHitsBuilder);
-        if (hasInnerHitsBuilder) {
-            innerHitsBuilder.writeTo(out);
-        }
         boolean hasMinScore = minScore != null;
         out.writeBoolean(hasMinScore);
         if (hasMinScore) {
@@ -653,15 +641,6 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
         return highlightBuilder;
     }
 
-    public SearchSourceBuilder innerHits(InnerHitsBuilder innerHitsBuilder) {
-        this.innerHitsBuilder = innerHitsBuilder;
-        return this;
-    }
-
-    public InnerHitsBuilder innerHits() {
-        return innerHitsBuilder;
-    }
-
     public SearchSourceBuilder suggest(SuggestBuilder suggestBuilder) {
         this.suggestBuilder = suggestBuilder;
         return this;
@@ -957,7 +936,6 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
             rewrittenBuilder.from = from;
             rewrittenBuilder.highlightBuilder = highlightBuilder;
             rewrittenBuilder.indexBoost = indexBoost;
-            rewrittenBuilder.innerHitsBuilder = innerHitsBuilder;
             rewrittenBuilder.minScore = minScore;
             rewrittenBuilder.postQueryBuilder = postQueryBuilder;
             rewrittenBuilder.profile = profile;
@@ -1051,8 +1029,6 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
                     aggregations = aggParsers.parseAggregators(context);
                 } else if (context.getParseFieldMatcher().match(currentFieldName, HIGHLIGHT_FIELD)) {
                     highlightBuilder = HighlightBuilder.fromXContent(context);
-                } else if (context.getParseFieldMatcher().match(currentFieldName, INNER_HITS_FIELD)) {
-                    innerHitsBuilder = InnerHitsBuilder.fromXContent(context);
                 } else if (context.getParseFieldMatcher().match(currentFieldName, SUGGEST_FIELD)) {
                     suggestBuilder = SuggestBuilder.fromXContent(context, suggesters);
                 } else if (context.getParseFieldMatcher().match(currentFieldName, SORT_FIELD)) {
@@ -1235,10 +1211,6 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
             builder.field(HIGHLIGHT_FIELD.getPreferredName(), highlightBuilder);
         }
 
-        if (innerHitsBuilder != null) {
-            builder.field(INNER_HITS_FIELD.getPreferredName(), innerHitsBuilder, params);
-        }
-
         if (suggestBuilder != null) {
             builder.field(SUGGEST_FIELD.getPreferredName(), suggestBuilder);
         }
@@ -1379,7 +1351,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
     @Override
     public int hashCode() {
         return Objects.hash(aggregations, explain, fetchSourceContext, fieldDataFields, fieldNames, from,
-                highlightBuilder, indexBoost, innerHitsBuilder, minScore, postQueryBuilder, queryBuilder, rescoreBuilders, scriptFields,
+                highlightBuilder, indexBoost, minScore, postQueryBuilder, queryBuilder, rescoreBuilders, scriptFields,
                 size, sorts, searchAfterBuilder, stats, suggestBuilder, terminateAfter, timeoutInMillis, trackScores, version, profile);
     }
 
@@ -1400,7 +1372,6 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
                 && Objects.equals(from, other.from)
                 && Objects.equals(highlightBuilder, other.highlightBuilder)
                 && Objects.equals(indexBoost, other.indexBoost)
-                && Objects.equals(innerHitsBuilder, other.innerHitsBuilder)
                 && Objects.equals(minScore, other.minScore)
                 && Objects.equals(postQueryBuilder, other.postQueryBuilder)
                 && Objects.equals(queryBuilder, other.queryBuilder)

+ 18 - 23
core/src/test/java/org/elasticsearch/index/query/HasChildQueryBuilderTests.java

@@ -42,7 +42,6 @@ import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.index.mapper.Uid;
 import org.elasticsearch.index.mapper.internal.TypeFieldMapper;
 import org.elasticsearch.index.mapper.internal.UidFieldMapper;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
 import org.elasticsearch.index.similarity.SimilarityService;
 import org.elasticsearch.script.Script.ScriptParseException;
 import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
@@ -53,6 +52,8 @@ import org.junit.BeforeClass;
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -125,18 +126,24 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
             assertEquals(queryBuilder.scoreMode(), lpq.getScoreMode()); // WTF is this why do we have two?
         }
         if (queryBuilder.innerHit() != null) {
-            assertNotNull(SearchContext.current());
+            SearchContext searchContext = SearchContext.current();
+            assertNotNull(searchContext);
             if (query != null) {
-                assertNotNull(SearchContext.current().innerHits());
-                assertEquals(1, SearchContext.current().innerHits().getInnerHits().size());
-                assertTrue(SearchContext.current().innerHits().getInnerHits().containsKey(queryBuilder.innerHit().getName()));
+                Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+                InnerHitBuilder.extractInnerHits(queryBuilder, innerHitBuilders);
+                for (InnerHitBuilder builder : innerHitBuilders.values()) {
+                    builder.build(searchContext, searchContext.innerHits());
+                }
+                assertNotNull(searchContext.innerHits());
+                assertEquals(1, searchContext.innerHits().getInnerHits().size());
+                assertTrue(searchContext.innerHits().getInnerHits().containsKey(queryBuilder.innerHit().getName()));
                 InnerHitsContext.BaseInnerHits innerHits =
-                        SearchContext.current().innerHits().getInnerHits().get(queryBuilder.innerHit().getName());
+                        searchContext.innerHits().getInnerHits().get(queryBuilder.innerHit().getName());
                 assertEquals(innerHits.size(), queryBuilder.innerHit().getSize());
                 assertEquals(innerHits.sort().getSort().length, 1);
                 assertEquals(innerHits.sort().getSort()[0].getField(), STRING_FIELD_NAME_2);
             } else {
-                assertThat(SearchContext.current().innerHits().getInnerHits().size(), equalTo(0));
+                assertThat(searchContext.innerHits().getInnerHits().size(), equalTo(0));
             }
         }
     }
@@ -188,7 +195,6 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
                 "    \"boost\" : 2.0,\n" +
                 "    \"_name\" : \"WNzYMJKRwePuRBh\",\n" +
                 "    \"inner_hits\" : {\n" +
-                "      \"type\" : \"child\",\n" +
                 "      \"name\" : \"inner_hits_name\",\n" +
                 "      \"from\" : 0,\n" +
                 "      \"size\" : 100,\n" +
@@ -199,18 +205,7 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
                 "        \"mapped_string\" : {\n" +
                 "          \"order\" : \"asc\"\n" +
                 "        }\n" +
-                "      } ],\n" +
-                "      \"query\" : {\n" +
-                "        \"range\" : {\n" +
-                "          \"mapped_string\" : {\n" +
-                "            \"from\" : \"agJhRET\",\n" +
-                "            \"to\" : \"zvqIq\",\n" +
-                "            \"include_lower\" : true,\n" +
-                "            \"include_upper\" : true,\n" +
-                "            \"boost\" : 1.0\n" +
-                "          }\n" +
-                "        }\n" +
-                "      }\n" +
+                "      } ]\n" +
                 "    }\n" +
                 "  }\n" +
                 "}";
@@ -223,11 +218,11 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
         assertEquals(query, queryBuilder.childType(), "child");
         assertEquals(query, queryBuilder.scoreMode(), ScoreMode.Avg);
         assertNotNull(query, queryBuilder.innerHit());
-        assertEquals(query, queryBuilder.innerHit(), new InnerHitBuilder().setParentChildType("child")
+        InnerHitBuilder expected = new InnerHitBuilder(new InnerHitBuilder(), queryBuilder.query(), "child")
                 .setName("inner_hits_name")
                 .setSize(100)
-                .addSort(new FieldSortBuilder("mapped_string").order(SortOrder.ASC))
-                .setQuery(queryBuilder.query()));
+                .addSort(new FieldSortBuilder("mapped_string").order(SortOrder.ASC));
+        assertEquals(query, queryBuilder.innerHit(), expected);
 
     }
     public void testToQueryInnerQueryType() throws IOException {

+ 14 - 9
core/src/test/java/org/elasticsearch/index/query/HasParentQueryBuilderTests.java

@@ -19,7 +19,6 @@
 
 package org.elasticsearch.index.query;
 
-import com.carrotsearch.randomizedtesting.generators.RandomPicks;
 import com.fasterxml.jackson.core.JsonParseException;
 
 import org.apache.lucene.search.MatchNoDocsQuery;
@@ -34,7 +33,6 @@ import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.index.mapper.MapperService;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
 import org.elasticsearch.script.Script.ScriptParseException;
 import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
 import org.elasticsearch.search.internal.SearchContext;
@@ -43,7 +41,8 @@ import org.elasticsearch.search.sort.SortOrder;
 import org.junit.BeforeClass;
 
 import java.io.IOException;
-import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -108,18 +107,24 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQ
             assertEquals(queryBuilder.score() ? ScoreMode.Max : ScoreMode.None, lpq.getScoreMode());
         }
         if (queryBuilder.innerHit() != null) {
-            assertNotNull(SearchContext.current());
+            SearchContext searchContext = SearchContext.current();
+            assertNotNull(searchContext);
             if (query != null) {
-                assertNotNull(SearchContext.current().innerHits());
-                assertEquals(1, SearchContext.current().innerHits().getInnerHits().size());
-                assertTrue(SearchContext.current().innerHits().getInnerHits().containsKey(queryBuilder.innerHit().getName()));
-                InnerHitsContext.BaseInnerHits innerHits = SearchContext.current().innerHits()
+                Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+                InnerHitBuilder.extractInnerHits(queryBuilder, innerHitBuilders);
+                for (InnerHitBuilder builder : innerHitBuilders.values()) {
+                    builder.build(searchContext, searchContext.innerHits());
+                }
+                assertNotNull(searchContext.innerHits());
+                assertEquals(1, searchContext.innerHits().getInnerHits().size());
+                assertTrue(searchContext.innerHits().getInnerHits().containsKey(queryBuilder.innerHit().getName()));
+                InnerHitsContext.BaseInnerHits innerHits = searchContext.innerHits()
                         .getInnerHits().get(queryBuilder.innerHit().getName());
                 assertEquals(innerHits.size(), queryBuilder.innerHit().getSize());
                 assertEquals(innerHits.sort().getSort().length, 1);
                 assertEquals(innerHits.sort().getSort()[0].getField(), STRING_FIELD_NAME_2);
             } else {
-                assertThat(SearchContext.current().innerHits().getInnerHits().size(), equalTo(0));
+                assertThat(searchContext.innerHits().getInnerHits().size(), equalTo(0));
             }
         }
     }

+ 145 - 43
core/src/test/java/org/elasticsearch/index/query/support/InnerHitBuilderTests.java → core/src/test/java/org/elasticsearch/index/query/InnerHitBuilderTests.java

@@ -16,11 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.elasticsearch.index.query.support;
+package org.elasticsearch.index.query;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.sameInstance;
+import static org.hamcrest.Matchers.nullValue;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -29,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
 
+import org.apache.lucene.search.join.ScoreMode;
 import org.elasticsearch.common.ParseFieldMatcher;
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
@@ -41,8 +44,7 @@ import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
-import org.elasticsearch.index.query.MatchQueryBuilder;
-import org.elasticsearch.index.query.QueryParseContext;
+import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
 import org.elasticsearch.indices.query.IndicesQueriesRegistry;
 import org.elasticsearch.script.Script;
 import org.elasticsearch.script.ScriptService;
@@ -87,7 +89,7 @@ public class InnerHitBuilderTests extends ESTestCase {
 
     public void testFromAndToXContent() throws Exception {
         for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
-            InnerHitBuilder innerHit = randomInnerHits();
+            InnerHitBuilder innerHit = randomInnerHits(true, false);
             XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values()));
             if (randomBoolean()) {
                 builder.prettyPrint();
@@ -111,7 +113,7 @@ public class InnerHitBuilderTests extends ESTestCase {
             assertTrue("inner it is not equal to self", firstInnerHit.equals(firstInnerHit));
             assertThat("same inner hit's hashcode returns different values if called multiple times", firstInnerHit.hashCode(),
                     equalTo(firstInnerHit.hashCode()));
-            assertThat("different inner hits should not be equal", mutate(firstInnerHit), not(equalTo(firstInnerHit)));
+            assertThat("different inner hits should not be equal", mutate(serializedCopy(firstInnerHit)), not(equalTo(firstInnerHit)));
 
             InnerHitBuilder secondBuilder = serializedCopy(firstInnerHit);
             assertTrue("inner hit is not equal to self", secondBuilder.equals(secondBuilder));
@@ -133,18 +135,83 @@ public class InnerHitBuilderTests extends ESTestCase {
         }
     }
 
+    public void testInlineLeafInnerHitsNestedQuery() throws Exception {
+        InnerHitBuilder leafInnerHits = randomInnerHits();
+        NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None);
+        nestedQueryBuilder.innerHit(leafInnerHits);
+        Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+        nestedQueryBuilder.extractInnerHitBuilders(innerHitBuilders);
+        assertThat(innerHitBuilders.get(leafInnerHits.getName()), notNullValue());
+    }
+
+    public void testInlineLeafInnerHitsHasChildQuery() throws Exception {
+        InnerHitBuilder leafInnerHits = randomInnerHits();
+        HasChildQueryBuilder hasChildQueryBuilder = new HasChildQueryBuilder("type", new MatchAllQueryBuilder(), ScoreMode.None)
+                .innerHit(leafInnerHits);
+        Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+        hasChildQueryBuilder.extractInnerHitBuilders(innerHitBuilders);
+        assertThat(innerHitBuilders.get(leafInnerHits.getName()), notNullValue());
+    }
+
+    public void testInlineLeafInnerHitsHasParentQuery() throws Exception {
+        InnerHitBuilder leafInnerHits = randomInnerHits();
+        HasParentQueryBuilder hasParentQueryBuilder = new HasParentQueryBuilder("type", new MatchAllQueryBuilder(), false)
+                .innerHit(leafInnerHits);
+        Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+        hasParentQueryBuilder.extractInnerHitBuilders(innerHitBuilders);
+        assertThat(innerHitBuilders.get(leafInnerHits.getName()), notNullValue());
+    }
+
+    public void testInlineLeafInnerHitsNestedQueryViaBoolQuery() {
+        InnerHitBuilder leafInnerHits = randomInnerHits();
+        NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None)
+                .innerHit(leafInnerHits);
+        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().should(nestedQueryBuilder);
+        Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+        boolQueryBuilder.extractInnerHitBuilders(innerHitBuilders);
+        assertThat(innerHitBuilders.get(leafInnerHits.getName()), notNullValue());
+    }
+
+    public void testInlineLeafInnerHitsNestedQueryViaConstantScoreQuery() {
+        InnerHitBuilder leafInnerHits = randomInnerHits();
+        NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None)
+                .innerHit(leafInnerHits);
+        ConstantScoreQueryBuilder constantScoreQueryBuilder = new ConstantScoreQueryBuilder(nestedQueryBuilder);
+        Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+        constantScoreQueryBuilder.extractInnerHitBuilders(innerHitBuilders);
+        assertThat(innerHitBuilders.get(leafInnerHits.getName()), notNullValue());
+    }
+
+    public void testInlineLeafInnerHitsNestedQueryViaBoostingQuery() {
+        InnerHitBuilder leafInnerHits1 = randomInnerHits();
+        NestedQueryBuilder nestedQueryBuilder1 = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None)
+                .innerHit(leafInnerHits1);
+        InnerHitBuilder leafInnerHits2 = randomInnerHits();
+        NestedQueryBuilder nestedQueryBuilder2 = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None)
+                .innerHit(leafInnerHits2);
+        BoostingQueryBuilder constantScoreQueryBuilder = new BoostingQueryBuilder(nestedQueryBuilder1, nestedQueryBuilder2);
+        Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+        constantScoreQueryBuilder.extractInnerHitBuilders(innerHitBuilders);
+        assertThat(innerHitBuilders.get(leafInnerHits1.getName()), notNullValue());
+        assertThat(innerHitBuilders.get(leafInnerHits2.getName()), notNullValue());
+    }
+
+    public void testInlineLeafInnerHitsNestedQueryViaFunctionScoreQuery() {
+        InnerHitBuilder leafInnerHits = randomInnerHits();
+        NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None)
+                .innerHit(leafInnerHits);
+        FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(nestedQueryBuilder);
+        Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+        ((AbstractQueryBuilder) functionScoreQueryBuilder).extractInnerHitBuilders(innerHitBuilders);
+        assertThat(innerHitBuilders.get(leafInnerHits.getName()), notNullValue());
+    }
+
     public static InnerHitBuilder randomInnerHits() {
-        return randomInnerHits(true);
+        return randomInnerHits(true, true);
     }
 
-    public static InnerHitBuilder randomInnerHits(boolean recursive) {
+    public static InnerHitBuilder randomInnerHits(boolean recursive, boolean includeQueryTypeOrPath) {
         InnerHitBuilder innerHits = new InnerHitBuilder();
-        if (randomBoolean()) {
-            innerHits.setNestedPath(randomAsciiOfLengthBetween(1, 16));
-        } else {
-            innerHits.setParentChildType(randomAsciiOfLengthBetween(1, 16));
-        }
-
         innerHits.setName(randomAsciiOfLengthBetween(1, 16));
         innerHits.setFrom(randomIntBetween(0, 128));
         innerHits.setSize(randomIntBetween(0, 128));
@@ -170,54 +237,76 @@ public class InnerHitBuilderTests extends ESTestCase {
             );
         }
         innerHits.setHighlightBuilder(HighlightBuilderTests.randomHighlighterBuilder());
-        if (randomBoolean()) {
-            innerHits.setQuery(new MatchQueryBuilder(randomAsciiOfLengthBetween(1, 16), randomAsciiOfLengthBetween(1, 16)));
-        }
         if (recursive && randomBoolean()) {
-            InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
             int size = randomIntBetween(1, 16);
             for (int i = 0; i < size; i++) {
-                innerHitsBuilder.addInnerHit(randomAsciiOfLengthBetween(1, 16), randomInnerHits(false));
+                innerHits.addChildInnerHit(randomInnerHits(false, includeQueryTypeOrPath));
+            }
+        }
+
+        if (includeQueryTypeOrPath) {
+            QueryBuilder query = new MatchQueryBuilder(randomAsciiOfLengthBetween(1, 16), randomAsciiOfLengthBetween(1, 16));
+            if (randomBoolean()) {
+                return new InnerHitBuilder(innerHits, randomAsciiOfLength(8), query);
+            } else {
+                return new InnerHitBuilder(innerHits, query, randomAsciiOfLength(8));
             }
-            innerHits.setInnerHitsBuilder(innerHitsBuilder);
+        } else {
+            return innerHits;
         }
+    }
 
-        return innerHits;
+    public void testCopyConstructor() throws Exception {
+        InnerHitBuilder original = randomInnerHits();
+        InnerHitBuilder copy = original.getNestedPath() != null ?
+                new InnerHitBuilder(original, original.getNestedPath(), original.getQuery()) :
+                new InnerHitBuilder(original, original.getQuery(), original.getParentChildType());
+        assertThat(copy, equalTo(original));
+        copy = mutate(copy);
+        assertThat(copy, not(equalTo(original)));
     }
 
-    static InnerHitBuilder mutate(InnerHitBuilder innerHits) throws IOException {
-        InnerHitBuilder copy = serializedCopy(innerHits);
-        int surprise = randomIntBetween(0, 10);
+    static InnerHitBuilder mutate(InnerHitBuilder instance) throws IOException {
+        int surprise = randomIntBetween(0, 11);
         switch (surprise) {
             case 0:
-                copy.setFrom(randomValueOtherThan(innerHits.getFrom(), () -> randomIntBetween(0, 128)));
+                instance.setFrom(randomValueOtherThan(instance.getFrom(), () -> randomIntBetween(0, 128)));
                 break;
             case 1:
-                copy.setSize(randomValueOtherThan(innerHits.getSize(), () -> randomIntBetween(0, 128)));
+                instance.setSize(randomValueOtherThan(instance.getSize(), () -> randomIntBetween(0, 128)));
                 break;
             case 2:
-                copy.setExplain(!copy.isExplain());
+                instance.setExplain(!instance.isExplain());
                 break;
             case 3:
-                copy.setVersion(!copy.isVersion());
+                instance.setVersion(!instance.isVersion());
                 break;
             case 4:
-                copy.setTrackScores(!copy.isTrackScores());
+                instance.setTrackScores(!instance.isTrackScores());
                 break;
             case 5:
-                copy.setName(randomValueOtherThan(innerHits.getName(), () -> randomAsciiOfLengthBetween(1, 16)));
+                instance.setName(randomValueOtherThan(instance.getName(), () -> randomAsciiOfLengthBetween(1, 16)));
                 break;
             case 6:
-                copy.setFieldDataFields(randomValueOtherThan(copy.getFieldDataFields(), () -> {
-                    return randomListStuff(16, () -> randomAsciiOfLengthBetween(1, 16));
-                }));
+                if (randomBoolean()) {
+                    instance.setFieldDataFields(randomValueOtherThan(instance.getFieldDataFields(), () -> {
+                        return randomListStuff(16, () -> randomAsciiOfLengthBetween(1, 16));
+                    }));
+                } else {
+                    instance.addFieldDataField(randomAsciiOfLengthBetween(1, 16));
+                }
                 break;
             case 7:
-                copy.setScriptFields(randomValueOtherThan(copy.getScriptFields(), () -> {
-                    return randomListStuff(16, InnerHitBuilderTests::randomScript);}));
+                if (randomBoolean()) {
+                    instance.setScriptFields(randomValueOtherThan(instance.getScriptFields(), () -> {
+                        return randomListStuff(16, InnerHitBuilderTests::randomScript);}));
+                } else {
+                    SearchSourceBuilder.ScriptField script = randomScript();
+                    instance.addScriptField(script.fieldName(), script.script());
+                }
                 break;
             case 8:
-                copy.setFetchSourceContext(randomValueOtherThan(copy.getFetchSourceContext(), () -> {
+                instance.setFetchSourceContext(randomValueOtherThan(instance.getFetchSourceContext(), () -> {
                     FetchSourceContext randomFetchSourceContext;
                     if (randomBoolean()) {
                         randomFetchSourceContext = new FetchSourceContext(randomBoolean());
@@ -231,21 +320,34 @@ public class InnerHitBuilderTests extends ESTestCase {
                 }));
                 break;
             case 9:
-                final List<SortBuilder<?>> sortBuilders = randomValueOtherThan(copy.getSorts(), () -> {
-                    List<SortBuilder<?>> builders = randomListStuff(16,
-                        () -> SortBuilders.fieldSort(randomAsciiOfLengthBetween(5, 20)).order(randomFrom(SortOrder.values())));
-                    return builders;
-                });
-                copy.setSorts(sortBuilders);
+                if (randomBoolean()) {
+                    final List<SortBuilder<?>> sortBuilders = randomValueOtherThan(instance.getSorts(), () -> {
+                        List<SortBuilder<?>> builders = randomListStuff(16,
+                                () -> SortBuilders.fieldSort(randomAsciiOfLengthBetween(5, 20)).order(randomFrom(SortOrder.values())));
+                        return builders;
+                    });
+                    instance.setSorts(sortBuilders);
+                } else {
+                    instance.addSort(SortBuilders.fieldSort(randomAsciiOfLengthBetween(5, 20)));
+                }
                 break;
             case 10:
-                copy.setHighlightBuilder(randomValueOtherThan(copy.getHighlightBuilder(),
+                instance.setHighlightBuilder(randomValueOtherThan(instance.getHighlightBuilder(),
                         HighlightBuilderTests::randomHighlighterBuilder));
                 break;
+            case 11:
+                if (instance.getFieldNames() == null || randomBoolean()) {
+                    instance.setFieldNames(randomValueOtherThan(instance.getFieldNames(), () -> {
+                        return randomListStuff(16, () -> randomAsciiOfLengthBetween(1, 16));
+                    }));
+                } else {
+                    instance.getFieldNames().add(randomAsciiOfLengthBetween(1, 16));
+                }
+                break;
             default:
                 throw new IllegalStateException("unexpected surprise [" + surprise + "]");
         }
-        return copy;
+        return instance;
     }
 
     static SearchSourceBuilder.ScriptField randomScript() {

+ 52 - 11
core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java

@@ -21,20 +21,25 @@ package org.elasticsearch.index.query;
 
 import com.carrotsearch.randomizedtesting.generators.RandomPicks;
 
+import com.fasterxml.jackson.core.JsonParseException;
 import org.apache.lucene.search.MatchNoDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.join.ScoreMode;
 import org.apache.lucene.search.join.ToParentBlockJoinQuery;
+import org.elasticsearch.ElasticsearchParseException;
 import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
+import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.index.mapper.MapperService;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
+import org.elasticsearch.script.Script;
 import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
 import org.elasticsearch.search.internal.SearchContext;
 import org.elasticsearch.search.sort.FieldSortBuilder;
 import org.elasticsearch.search.sort.SortOrder;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -66,11 +71,11 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBu
     protected NestedQueryBuilder doCreateTestQueryBuilder() {
         NestedQueryBuilder nqb = new NestedQueryBuilder("nested1", RandomQueryBuilder.createQuery(random()),
                 RandomPicks.randomFrom(random(), ScoreMode.values()));
-        if (SearchContext.current() != null) {
+        if (randomBoolean()) {
             nqb.innerHit(new InnerHitBuilder()
                     .setName(randomAsciiOfLengthBetween(1, 10))
                     .setSize(randomIntBetween(0, 100))
-                    .addSort(new FieldSortBuilder(STRING_FIELD_NAME).order(SortOrder.ASC)));
+                    .addSort(new FieldSortBuilder(INT_FIELD_NAME).order(SortOrder.ASC)));
         }
         nqb.ignoreUnmapped(randomBoolean());
         return nqb;
@@ -87,17 +92,23 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBu
             //TODO how to assert this?
         }
         if (queryBuilder.innerHit() != null) {
-            assertNotNull(SearchContext.current());
+            SearchContext searchContext = SearchContext.current();
+            assertNotNull(searchContext);
             if (query != null) {
-                assertNotNull(SearchContext.current().innerHits());
-                assertEquals(1, SearchContext.current().innerHits().getInnerHits().size());
-                assertTrue(SearchContext.current().innerHits().getInnerHits().containsKey("inner_hits_name"));
-                InnerHitsContext.BaseInnerHits innerHits = SearchContext.current().innerHits().getInnerHits().get("inner_hits_name");
-                assertEquals(innerHits.size(), 100);
+                Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
+                InnerHitBuilder.extractInnerHits(queryBuilder, innerHitBuilders);
+                for (InnerHitBuilder builder : innerHitBuilders.values()) {
+                    builder.build(searchContext, searchContext.innerHits());
+                }
+                assertNotNull(searchContext.innerHits());
+                assertEquals(1, searchContext.innerHits().getInnerHits().size());
+                assertTrue(searchContext.innerHits().getInnerHits().containsKey(queryBuilder.innerHit().getName()));
+                InnerHitsContext.BaseInnerHits innerHits = searchContext.innerHits().getInnerHits().get(queryBuilder.innerHit().getName());
+                assertEquals(innerHits.size(), queryBuilder.innerHit().getSize());
                 assertEquals(innerHits.sort().getSort().length, 1);
-                assertEquals(innerHits.sort().getSort()[0].getField(), STRING_FIELD_NAME);
+                assertEquals(innerHits.sort().getSort()[0].getField(), INT_FIELD_NAME);
             } else {
-                assertThat(SearchContext.current().innerHits().getInnerHits().size(), equalTo(0));
+                assertThat(searchContext.innerHits().getInnerHits().size(), equalTo(0));
             }
         }
     }
@@ -163,6 +174,36 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBu
         assertEquals(json, ScoreMode.Avg, parsed.scoreMode());
     }
 
+    /**
+     * override superclass test, because here we need to take care that mutation doesn't happen inside
+     * `inner_hits` structure, because we don't parse them yet and so no exception will be triggered
+     * for any mutation there.
+     */
+    @Override
+    public void testUnknownObjectException() throws IOException {
+        String validQuery = createTestQueryBuilder().toString();
+        assertThat(validQuery, containsString("{"));
+        int endPosition = validQuery.indexOf("inner_hits");
+        if (endPosition == -1) {
+            endPosition = validQuery.length() - 1;
+        }
+        for (int insertionPosition = 0; insertionPosition < endPosition; insertionPosition++) {
+            if (validQuery.charAt(insertionPosition) == '{') {
+                String testQuery = validQuery.substring(0, insertionPosition) + "{ \"newField\" : " +
+                        validQuery.substring(insertionPosition) + "}";
+                try {
+                    parseQuery(testQuery);
+                    fail("some parsing exception expected for query: " + testQuery);
+                } catch (ParsingException | Script.ScriptParseException | ElasticsearchParseException e) {
+                    // different kinds of exception wordings depending on location
+                    // of mutation, so no simple asserts possible here
+                } catch (JsonParseException e) {
+                    // mutation produced invalid json
+                }
+            }
+        }
+    }
+
     public void testIgnoreUnmapped() throws IOException {
         final NestedQueryBuilder queryBuilder = new NestedQueryBuilder("unmapped", new MatchAllQueryBuilder(), ScoreMode.None);
         queryBuilder.ignoreUnmapped(true);

+ 0 - 140
core/src/test/java/org/elasticsearch/index/query/support/InnerHitsBuilderTests.java

@@ -1,140 +0,0 @@
-/*
- * 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.index.query.support;
-
-import org.elasticsearch.common.ParseFieldMatcher;
-import org.elasticsearch.common.io.stream.BytesStreamOutput;
-import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
-import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
-import org.elasticsearch.common.io.stream.StreamInput;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.xcontent.ToXContent;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
-import org.elasticsearch.common.xcontent.XContentHelper;
-import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.common.xcontent.XContentType;
-import org.elasticsearch.index.query.QueryParseContext;
-import org.elasticsearch.indices.query.IndicesQueriesRegistry;
-import org.elasticsearch.search.SearchModule;
-import org.elasticsearch.test.ESTestCase;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-import java.io.IOException;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.sameInstance;
-
-public class InnerHitsBuilderTests extends ESTestCase {
-
-    private static final int NUMBER_OF_TESTBUILDERS = 20;
-    private static NamedWriteableRegistry namedWriteableRegistry;
-    private static IndicesQueriesRegistry indicesQueriesRegistry;
-
-    @BeforeClass
-    public static void init() {
-        namedWriteableRegistry = new NamedWriteableRegistry();
-        indicesQueriesRegistry = new SearchModule(Settings.EMPTY, namedWriteableRegistry).getQueryParserRegistry();
-    }
-
-    @AfterClass
-    public static void afterClass() throws Exception {
-        namedWriteableRegistry = null;
-        indicesQueriesRegistry = null;
-    }
-
-    public void testSerialization() throws Exception {
-        for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
-            InnerHitsBuilder original = randomInnerHits();
-            InnerHitsBuilder deserialized = serializedCopy(original);
-            assertEquals(deserialized, original);
-            assertEquals(deserialized.hashCode(), original.hashCode());
-            assertNotSame(deserialized, original);
-        }
-    }
-
-    public void testFromAndToXContent() throws Exception {
-        for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
-            InnerHitsBuilder innerHits = randomInnerHits();
-            XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values()));
-            if (randomBoolean()) {
-                builder.prettyPrint();
-            }
-            innerHits.toXContent(builder, ToXContent.EMPTY_PARAMS);
-
-            XContentParser parser = XContentHelper.createParser(builder.bytes());
-            QueryParseContext context = new QueryParseContext(indicesQueriesRegistry, parser, ParseFieldMatcher.EMPTY);
-            parser.nextToken();
-            InnerHitsBuilder secondInnerHits = InnerHitsBuilder.fromXContent(context);
-            assertThat(innerHits, not(sameInstance(secondInnerHits)));
-            assertThat(innerHits, equalTo(secondInnerHits));
-            assertThat(innerHits.hashCode(), equalTo(secondInnerHits.hashCode()));
-        }
-    }
-
-    public void testEqualsAndHashcode() throws IOException {
-        for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
-            InnerHitsBuilder firstInnerHits = randomInnerHits();
-            assertFalse("inner hit is equal to null", firstInnerHits.equals(null));
-            assertFalse("inner hit is equal to incompatible type", firstInnerHits.equals(""));
-            assertTrue("inner it is not equal to self", firstInnerHits.equals(firstInnerHits));
-            assertThat("same inner hit's hashcode returns different values if called multiple times", firstInnerHits.hashCode(),
-                    equalTo(firstInnerHits.hashCode()));
-
-            InnerHitsBuilder secondBuilder = serializedCopy(firstInnerHits);
-            assertTrue("inner hit is not equal to self", secondBuilder.equals(secondBuilder));
-            assertTrue("inner hit is not equal to its copy", firstInnerHits.equals(secondBuilder));
-            assertTrue("equals is not symmetric", secondBuilder.equals(firstInnerHits));
-            assertThat("inner hits copy's hashcode is different from original hashcode", secondBuilder.hashCode(),
-                    equalTo(firstInnerHits.hashCode()));
-
-            InnerHitsBuilder thirdBuilder = serializedCopy(secondBuilder);
-            assertTrue("inner hit is not equal to self", thirdBuilder.equals(thirdBuilder));
-            assertTrue("inner hit is not equal to its copy", secondBuilder.equals(thirdBuilder));
-            assertThat("inner hit copy's hashcode is different from original hashcode", secondBuilder.hashCode(),
-                    equalTo(thirdBuilder.hashCode()));
-            assertTrue("equals is not transitive", firstInnerHits.equals(thirdBuilder));
-            assertThat("inner hit copy's hashcode is different from original hashcode", firstInnerHits.hashCode(),
-                    equalTo(thirdBuilder.hashCode()));
-            assertTrue("equals is not symmetric", thirdBuilder.equals(secondBuilder));
-            assertTrue("equals is not symmetric", thirdBuilder.equals(firstInnerHits));
-        }
-    }
-
-    public static InnerHitsBuilder randomInnerHits() {
-        InnerHitsBuilder innerHits = new InnerHitsBuilder();
-        int numInnerHits = randomIntBetween(0, 12);
-        for (int i = 0; i < numInnerHits; i++) {
-            innerHits.addInnerHit(randomAsciiOfLength(5), InnerHitBuilderTests.randomInnerHits());
-        }
-        return innerHits;
-    }
-
-    private static InnerHitsBuilder serializedCopy(InnerHitsBuilder original) throws IOException {
-        try (BytesStreamOutput output = new BytesStreamOutput()) {
-            original.writeTo(output);
-            try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
-                return new InnerHitsBuilder(in);
-            }
-        }
-    }
-
-}

+ 1 - 30
core/src/test/java/org/elasticsearch/percolator/PercolatorIT.java

@@ -43,7 +43,7 @@ import org.elasticsearch.index.query.Operator;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.index.query.QueryShardException;
 import org.elasticsearch.index.query.functionscore.WeightBuilder;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
+import org.elasticsearch.index.query.InnerHitBuilder;
 import org.elasticsearch.search.highlight.HighlightBuilder;
 import org.elasticsearch.test.ESIntegTestCase;
 
@@ -1827,35 +1827,6 @@ public class PercolatorIT extends ESIntegTestCase {
         assertThat(response1.getMatches()[0].getId().string(), equalTo("1"));
     }
 
-    public void testFailNicelyWithInnerHits() throws Exception {
-        XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
-                .startObject("mapping")
-                    .startObject("properties")
-                        .startObject("nested")
-                            .field("type", "nested")
-                            .startObject("properties")
-                                .startObject("name")
-                                    .field("type", "text")
-                                .endObject()
-                            .endObject()
-                        .endObject()
-                    .endObject()
-                .endObject();
-
-        assertAcked(prepareCreate(INDEX_NAME)
-                .addMapping(TYPE_NAME, "query", "type=percolator")
-                .addMapping("mapping", mapping));
-        try {
-            client().prepareIndex(INDEX_NAME, TYPE_NAME, "1")
-                    .setSource(jsonBuilder().startObject().field("query", nestedQuery("nested", matchQuery("nested.name", "value"), ScoreMode.Avg).innerHit(new InnerHitBuilder())).endObject())
-                    .execute().actionGet();
-            fail("Expected a parse error, because inner_hits isn't supported in the percolate api");
-        } catch (Exception e) {
-            assertThat(e.getCause(), instanceOf(QueryShardException.class));
-            assertThat(e.getCause().getMessage(), containsString("inner_hits unsupported"));
-        }
-    }
-
     public void testParentChild() throws Exception {
         // We don't fail p/c queries, but those queries are unusable because only a single document can be provided in
         // the percolate api

+ 0 - 10
core/src/test/java/org/elasticsearch/search/builder/SearchSourceBuilderTests.java

@@ -52,8 +52,6 @@ import org.elasticsearch.index.query.AbstractQueryTestCase;
 import org.elasticsearch.index.query.EmptyQueryBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.index.query.QueryParseContext;
-import org.elasticsearch.index.query.support.InnerHitBuilderTests;
-import org.elasticsearch.index.query.support.InnerHitsBuilder;
 import org.elasticsearch.indices.IndicesModule;
 import org.elasticsearch.indices.breaker.CircuitBreakerService;
 import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
@@ -410,14 +408,6 @@ public class SearchSourceBuilderTests extends ESTestCase {
         if (randomBoolean()) {
             builder.suggest(SuggestBuilderTests.randomSuggestBuilder());
         }
-        if (randomBoolean()) {
-            InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
-            int num = randomIntBetween(0, 3);
-            for (int i = 0; i < num; i++) {
-                innerHitsBuilder.addInnerHit(randomAsciiOfLengthBetween(5, 20), InnerHitBuilderTests.randomInnerHits());
-            }
-            builder.innerHits(innerHitsBuilder);
-        }
         if (randomBoolean()) {
             int numRescores = randomIntBetween(1, 5);
             for (int i = 0; i < numRescores; i++) {

+ 162 - 342
core/src/test/java/org/elasticsearch/search/innerhits/InnerHitsIT.java

@@ -22,14 +22,11 @@ package org.elasticsearch.search.innerhits;
 import org.apache.lucene.search.join.ScoreMode;
 import org.apache.lucene.util.ArrayUtil;
 import org.elasticsearch.action.index.IndexRequestBuilder;
-import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.elasticsearch.index.query.MatchAllQueryBuilder;
-import org.elasticsearch.index.query.support.InnerHitBuilder;
-import org.elasticsearch.index.query.support.InnerHitsBuilder;
+import org.elasticsearch.index.query.InnerHitBuilder;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.script.MockScriptEngine;
 import org.elasticsearch.script.Script;
@@ -68,8 +65,6 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 
-/**
- */
 public class InnerHitsIT extends ESIntegTestCase {
     @Override
     protected Collection<Class<? extends Plugin>> nodePlugins() {
@@ -112,105 +107,62 @@ public class InnerHitsIT extends ESIntegTestCase {
                 .endObject()));
         indexRandom(true, requests);
 
-        InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
-                .setNestedPath("comments")
-                .setQuery(matchQuery("comments.message", "fox"))
-        );
-        // Inner hits can be defined in two ways: 1) with the query 2) as separate inner_hit definition
-        SearchRequest[] searchRequests = new SearchRequest[]{
-                client().prepareSearch("articles").setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"), ScoreMode.Avg).innerHit(
-                        new InnerHitBuilder().setName("comment"))).request(),
-                client().prepareSearch("articles").setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"), ScoreMode.Avg))
-                        .innerHits(innerHitsBuilder).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));
-        }
+        SearchResponse response = client().prepareSearch("articles")
+                .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"), ScoreMode.Avg)
+                        .innerHit(new InnerHitBuilder().setName("comment"))
+                ).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(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));
 
-        innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
-                .setQuery(matchQuery("comments.message", "elephant")).setNestedPath("comments")
-        );
-        // Inner hits can be defined in two ways: 1) with the query 2) as
-        // separate inner_hit definition
-        searchRequests = new SearchRequest[] {
-                client().prepareSearch("articles")
-                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant"), ScoreMode.Avg))
-                        .innerHits(innerHitsBuilder).request(),
-                client().prepareSearch("articles")
-                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant"), ScoreMode.Avg).innerHit(new InnerHitBuilder().setName("comment"))).request(),
-                client().prepareSearch("articles")
-                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant"), ScoreMode.Avg).innerHit(new InnerHitBuilder().setName("comment").addSort(new FieldSortBuilder("_doc").order(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));
-        }
-        InnerHitBuilder innerHit = new InnerHitBuilder();
-        innerHit.setNestedPath("comments");
-        innerHit.setQuery(matchQuery("comments.message", "fox"));
-        innerHit.setHighlightBuilder(new HighlightBuilder().field("comments.message"));
-        innerHit.setExplain(true);
-        innerHit.addFieldDataField("comments.message");
-        innerHit.addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE, MockScriptEngine.NAME, Collections.emptyMap()));
-        innerHit.setSize(1);
-        innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comments", innerHit);
-        searchRequests = new SearchRequest[] {
-                client().prepareSearch("articles")
-                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"), ScoreMode.Avg))
-                        .innerHits(innerHitsBuilder).request(),
-                client().prepareSearch("articles")
-                        .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"), ScoreMode.Avg).innerHit(
-                                new InnerHitBuilder().setHighlightBuilder(new HighlightBuilder().field("comments.message"))
-                                        .setExplain(true)
-                                        .addFieldDataField("comments.message")
-                                        .addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE, MockScriptEngine.NAME, Collections.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"));
-        }
+        response = client().prepareSearch("articles")
+                .setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant"), ScoreMode.Avg)
+                        .innerHit(new InnerHitBuilder().setName("comment"))
+                ).get();
+        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));
+        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));
+
+        response = client().prepareSearch("articles")
+                .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"), ScoreMode.Avg).innerHit(
+                        new InnerHitBuilder().setHighlightBuilder(new HighlightBuilder().field("comments.message"))
+                                .setExplain(true)
+                                .addFieldDataField("comments.message")
+                                .addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE, MockScriptEngine.NAME, Collections.emptyMap()))
+                                .setSize(1)
+                )).get();
+        assertNoFailures(response);
+        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"));
     }
 
     public void testRandomNested() throws Exception {
@@ -237,38 +189,16 @@ public class InnerHitsIT extends ESIntegTestCase {
         indexRandom(true, requestBuilders);
 
         int size = randomIntBetween(0, numDocs);
-        SearchResponse searchResponse;
-        if (randomBoolean()) {
-            InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
-            innerHitsBuilder.addInnerHit("a", new InnerHitBuilder().setNestedPath("field1")
-                    // Sort order is DESC, because we reverse the inner objects during indexing!
-                    .addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)).setSize(size));
-            innerHitsBuilder.addInnerHit("b", new InnerHitBuilder().setNestedPath("field2")
-                    .addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)).setSize(size));
-            searchResponse = client().prepareSearch("idx")
-                    .setSize(numDocs)
-                    .addSort("_uid", SortOrder.ASC)
-                    .innerHits(innerHitsBuilder)
-                    .get();
-        } else {
-            BoolQueryBuilder boolQuery = new BoolQueryBuilder();
-            if (randomBoolean()) {
-                boolQuery.should(nestedQuery("field1", matchAllQuery(), ScoreMode.Avg).innerHit(new InnerHitBuilder().setName("a").setSize(size)
-                        .addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC))));
-                boolQuery.should(nestedQuery("field2", matchAllQuery(), ScoreMode.Avg).innerHit(new InnerHitBuilder().setName("b")
-                        .addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)).setSize(size)));
-            } else {
-                boolQuery.should(constantScoreQuery(nestedQuery("field1", matchAllQuery(), ScoreMode.Avg).innerHit(new InnerHitBuilder().setName("a")
-                        .setSize(size).addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)))));
-                boolQuery.should(constantScoreQuery(nestedQuery("field2", matchAllQuery(), ScoreMode.Avg).innerHit(new InnerHitBuilder().setName("b")
-                        .setSize(size).addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)))));
-            }
-            searchResponse = client().prepareSearch("idx")
-                    .setQuery(boolQuery)
-                    .setSize(numDocs)
-                    .addSort("_uid", SortOrder.ASC)
-                    .get();
-        }
+        BoolQueryBuilder boolQuery = new BoolQueryBuilder();
+        boolQuery.should(nestedQuery("field1", matchAllQuery(), ScoreMode.Avg).innerHit(new InnerHitBuilder().setName("a").setSize(size)
+                .addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC))));
+        boolQuery.should(nestedQuery("field2", matchAllQuery(), ScoreMode.Avg).innerHit(new InnerHitBuilder().setName("b")
+                .addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)).setSize(size)));
+        SearchResponse searchResponse = client().prepareSearch("idx")
+                .setQuery(boolQuery)
+                .setSize(numDocs)
+                .addSort("_uid", SortOrder.ASC)
+                .get();
 
         assertNoFailures(searchResponse);
         assertHitCount(searchResponse, numDocs);
@@ -313,102 +243,59 @@ public class InnerHitsIT extends ESIntegTestCase {
         requests.add(client().prepareIndex("articles", "comment", "6").setParent("2").setSource("message", "elephant scared by mice x y"));
         indexRandom(true, requests);
 
-        InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder().setParentChildType("comment")
-                .setQuery(matchQuery("message", "fox")));
-        SearchRequest[] searchRequests = new SearchRequest[]{
-                client().prepareSearch("articles")
-                        .setQuery(hasChildQuery("comment", matchQuery("message", "fox"), ScoreMode.None))
-                        .innerHits(innerHitsBuilder)
-                        .request(),
-                client().prepareSearch("articles")
-                        .setQuery(hasChildQuery("comment", matchQuery("message", "fox"), ScoreMode.None).innerHit(new InnerHitBuilder().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"));
-        }
+        SearchResponse response = client().prepareSearch("articles")
+                .setQuery(hasChildQuery("comment", matchQuery("message", "fox"), ScoreMode.None).innerHit(new InnerHitBuilder()))
+                .get();
+        assertNoFailures(response);
+        assertHitCount(response, 1);
+        assertSearchHit(response, 1, hasId("1"));
+        assertThat(response.getHits().getAt(0).getShard(), notNullValue());
 
-        innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder().setParentChildType("comment")
-                .setQuery(matchQuery("message", "elephant")));
-        searchRequests = new SearchRequest[] {
-                client().prepareSearch("articles")
-                        .setQuery(hasChildQuery("comment", matchQuery("message", "elephant"), ScoreMode.None))
-                        .innerHits(innerHitsBuilder)
-                        .request(),
-                client().prepareSearch("articles")
-                        .setQuery(hasChildQuery("comment", matchQuery("message", "elephant"), ScoreMode.None).innerHit(new InnerHitBuilder()))
-                        .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"));
-        }
-        InnerHitBuilder innerHit = new InnerHitBuilder();
-        innerHit.setQuery(matchQuery("message", "fox"));
-        innerHit.setParentChildType("comment");
-        innerHit.setHighlightBuilder(new HighlightBuilder().field("message"));
-        innerHit.setExplain(true);
-        innerHit.addFieldDataField("message");
-        innerHit.addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE, MockScriptEngine.NAME, Collections.emptyMap()));
-        innerHit.setSize(1);
-        innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comment", innerHit);
-        searchRequests = new SearchRequest[] {
-                client().prepareSearch("articles")
-                        .setQuery(hasChildQuery("comment", matchQuery("message", "fox"), ScoreMode.None))
-                        .innerHits(innerHitsBuilder)
-                        .request(),
-
-                client().prepareSearch("articles")
-                        .setQuery(
-                                hasChildQuery("comment", matchQuery("message", "fox"), ScoreMode.None).innerHit(
-                                        new InnerHitBuilder()
-                                                .addFieldDataField("message")
-                                                .setHighlightBuilder(new HighlightBuilder().field("message"))
-                                                .setExplain(true).setSize(1)
-                                                .addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE,
-                                                        MockScriptEngine.NAME, Collections.emptyMap()))
-                                )
-                        ).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"));
-        }
+        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"));
+
+        response = client().prepareSearch("articles")
+                .setQuery(hasChildQuery("comment", matchQuery("message", "elephant"), ScoreMode.None).innerHit(new InnerHitBuilder()))
+                .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(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"));
+
+        response = client().prepareSearch("articles")
+                .setQuery(
+                        hasChildQuery("comment", matchQuery("message", "fox"), ScoreMode.None).innerHit(
+                                new InnerHitBuilder()
+                                        .addFieldDataField("message")
+                                        .setHighlightBuilder(new HighlightBuilder().field("message"))
+                                        .setExplain(true).setSize(1)
+                                        .addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE,
+                                                MockScriptEngine.NAME, Collections.emptyMap()))
+                        )
+                ).get();
+        assertNoFailures(response);
+        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"));
     }
 
     public void testRandomParentChild() throws Exception {
@@ -442,33 +329,17 @@ public class InnerHitsIT extends ESIntegTestCase {
         indexRandom(true, requestBuilders);
 
         int size = randomIntBetween(0, numDocs);
-        InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("a", new InnerHitBuilder().setParentChildType("child1").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size));
-        innerHitsBuilder.addInnerHit("b", new InnerHitBuilder().setParentChildType("child2").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size));
-        SearchResponse searchResponse;
-        if (randomBoolean()) {
-            searchResponse = client().prepareSearch("idx")
-                    .setSize(numDocs)
-                    .setTypes("parent")
-                    .addSort("_uid", SortOrder.ASC)
-                    .innerHits(innerHitsBuilder)
-                    .get();
-        } else {
-            BoolQueryBuilder boolQuery = new BoolQueryBuilder();
-            if (randomBoolean()) {
-                boolQuery.should(hasChildQuery("child1", matchAllQuery(), ScoreMode.None).innerHit(new InnerHitBuilder().setName("a").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size)));
-                boolQuery.should(hasChildQuery("child2", matchAllQuery(), ScoreMode.None).innerHit(new InnerHitBuilder().setName("b").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size)));
-            } else {
-                boolQuery.should(constantScoreQuery(hasChildQuery("child1", matchAllQuery(), ScoreMode.None).innerHit(new InnerHitBuilder().setName("a").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size))));
-                boolQuery.should(constantScoreQuery(hasChildQuery("child2", matchAllQuery(), ScoreMode.None).innerHit(new InnerHitBuilder().setName("b").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size))));
-            }
-            searchResponse = client().prepareSearch("idx")
-                    .setSize(numDocs)
-                    .setTypes("parent")
-                    .addSort("_uid", SortOrder.ASC)
-                    .setQuery(boolQuery)
-                    .get();
-        }
+        BoolQueryBuilder boolQuery = new BoolQueryBuilder();
+        boolQuery.should(constantScoreQuery(hasChildQuery("child1", matchAllQuery(), ScoreMode.None)
+                .innerHit(new InnerHitBuilder().setName("a").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size))));
+        boolQuery.should(constantScoreQuery(hasChildQuery("child2", matchAllQuery(), ScoreMode.None)
+                .innerHit(new InnerHitBuilder().setName("b").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size))));
+        SearchResponse searchResponse = client().prepareSearch("idx")
+                .setSize(numDocs)
+                .setTypes("parent")
+                .addSort("_uid", SortOrder.ASC)
+                .setQuery(boolQuery)
+                .get();
 
         assertNoFailures(searchResponse);
         assertHitCount(searchResponse, numDocs);
@@ -560,19 +431,10 @@ public class InnerHitsIT extends ESIntegTestCase {
         requests.add(client().prepareIndex("articles", "remark", "2").setParent("2").setRouting("2").setSource("message", "bad"));
         indexRandom(true, requests);
 
-        InnerHitsBuilder innerInnerHitsBuilder = new InnerHitsBuilder();
-        innerInnerHitsBuilder.addInnerHit("remark", new InnerHitBuilder()
-                .setParentChildType("remark")
-                .setQuery(matchQuery("message", "good"))
-        );
-        InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
-                            .setParentChildType("comment")
-                            .setQuery(hasChildQuery("remark", matchQuery("message", "good"), ScoreMode.None))
-                            .setInnerHitsBuilder(innerInnerHitsBuilder));
         SearchResponse response = client().prepareSearch("articles")
-                .setQuery(hasChildQuery("comment", hasChildQuery("remark", matchQuery("message", "good"), ScoreMode.None), ScoreMode.None))
-                .innerHits(innerHitsBuilder)
+                .setQuery(hasChildQuery("comment",
+                            hasChildQuery("remark", matchQuery("message", "good"), ScoreMode.None).innerHit(new InnerHitBuilder()),
+                        ScoreMode.None).innerHit(new InnerHitBuilder()))
                 .get();
 
         assertNoFailures(response);
@@ -590,18 +452,10 @@ public class InnerHitsIT extends ESIntegTestCase {
         assertThat(innerHits.getAt(0).getId(), equalTo("1"));
         assertThat(innerHits.getAt(0).type(), equalTo("remark"));
 
-        innerInnerHitsBuilder = new InnerHitsBuilder();
-        innerInnerHitsBuilder.addInnerHit("remark", new InnerHitBuilder()
-                .setParentChildType("remark")
-                .setQuery(matchQuery("message", "bad")));
-        innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
-                .setParentChildType("comment")
-                .setQuery(hasChildQuery("remark", matchQuery("message", "bad"), ScoreMode.None))
-                .setInnerHitsBuilder(innerInnerHitsBuilder));
         response = client().prepareSearch("articles")
-                .setQuery(hasChildQuery("comment", hasChildQuery("remark", matchQuery("message", "bad"), ScoreMode.None), ScoreMode.None))
-                .innerHits(innerHitsBuilder)
+                .setQuery(hasChildQuery("comment",
+                        hasChildQuery("remark", matchQuery("message", "bad"), ScoreMode.None).innerHit(new InnerHitBuilder()),
+                        ScoreMode.None).innerHit(new InnerHitBuilder()))
                 .get();
 
         assertNoFailures(response);
@@ -662,24 +516,18 @@ public class InnerHitsIT extends ESIntegTestCase {
                 .endObject()));
         indexRandom(true, requests);
 
-        InnerHitsBuilder innerInnerHitsBuilder = new InnerHitsBuilder();
-        innerInnerHitsBuilder.addInnerHit("remark", new InnerHitBuilder()
-                .setNestedPath("comments.remarks")
-                .setQuery(matchQuery("comments.remarks.message", "good")));
-        InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
-                .setNestedPath("comments")
-                .setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "good"), ScoreMode.Avg))
-                .setInnerHitsBuilder(innerInnerHitsBuilder)
-        );
         SearchResponse response = client().prepareSearch("articles")
-                .setQuery(nestedQuery("comments", nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "good"), ScoreMode.Avg), ScoreMode.Avg))
-                .innerHits(innerHitsBuilder).get();
+                .setQuery(
+                        nestedQuery("comments",
+                                nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "good"), ScoreMode.Avg)
+                                        .innerHit(new InnerHitBuilder().setName("remark")),
+                                ScoreMode.Avg).innerHit(new InnerHitBuilder())
+                ).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");
+        SearchHits innerHits = response.getHits().getAt(0).getInnerHits().get("comments");
         assertThat(innerHits.totalHits(), equalTo(1L));
         assertThat(innerHits.getHits().length, equalTo(1));
         assertThat(innerHits.getAt(0).getId(), equalTo("1"));
@@ -711,24 +559,18 @@ public class InnerHitsIT extends ESIntegTestCase {
         assertThat(innerHits.getAt(0).getNestedIdentity().getChild().getField().string(), equalTo("remarks"));
         assertThat(innerHits.getAt(0).getNestedIdentity().getChild().getOffset(), equalTo(0));
 
-        innerInnerHitsBuilder = new InnerHitsBuilder();
-        innerInnerHitsBuilder.addInnerHit("remark", new InnerHitBuilder()
-                .setNestedPath("comments.remarks")
-                .setQuery(matchQuery("comments.remarks.message", "bad")));
-        innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
-                .setNestedPath("comments")
-                .setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad"), ScoreMode.Avg))
-                .setInnerHitsBuilder(innerInnerHitsBuilder));
         response = client().prepareSearch("articles")
-                .setQuery(nestedQuery("comments", nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad"), ScoreMode.Avg), ScoreMode.Avg))
-                .innerHits(innerHitsBuilder)
-                .get();
+                .setQuery(
+                        nestedQuery("comments",
+                                nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad"), ScoreMode.Avg)
+                                        .innerHit(new InnerHitBuilder().setName("remark")),
+                                ScoreMode.Avg).innerHit(new InnerHitBuilder())
+                ).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");
+        innerHits = response.getHits().getAt(0).getInnerHits().get("comments");
         assertThat(innerHits.totalHits(), equalTo(1L));
         assertThat(innerHits.getHits().length, equalTo(1));
         assertThat(innerHits.getAt(0).getId(), equalTo("2"));
@@ -863,22 +705,21 @@ public class InnerHitsIT extends ESIntegTestCase {
         requests.add(client().prepareIndex("royals", "baron", "baron4").setParent("earl4").setRouting("king").setSource("{}"));
         indexRandom(true, requests);
 
-        InnerHitsBuilder innerInnerHitsBuilder = new InnerHitsBuilder();
-        innerInnerHitsBuilder.addInnerHit("barons", new InnerHitBuilder().setParentChildType("baron"));
-        InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
-        innerHitsBuilder.addInnerHit("earls", new InnerHitBuilder()
-                .setParentChildType("earl")
-                .addSort(SortBuilders.fieldSort("_uid").order(SortOrder.ASC))
-                .setSize(4)
-                .setInnerHitsBuilder(innerInnerHitsBuilder)
-        );
-        innerInnerHitsBuilder = new InnerHitsBuilder();
-        innerInnerHitsBuilder.addInnerHit("kings", new InnerHitBuilder().setParentChildType("king"));
-        innerHitsBuilder.addInnerHit("princes", new InnerHitBuilder().setParentChildType("prince")
-                .setInnerHitsBuilder(innerInnerHitsBuilder));
         SearchResponse response = client().prepareSearch("royals")
                 .setTypes("duke")
-                .innerHits(innerHitsBuilder)
+                .setQuery(boolQuery()
+                        .filter(hasParentQuery("prince",
+                                hasParentQuery("king", matchAllQuery(), false).innerHit(new InnerHitBuilder().setName("kings")),
+                                false).innerHit(new InnerHitBuilder().setName("princes"))
+                        )
+                        .filter(hasChildQuery("earl",
+                                hasChildQuery("baron", matchAllQuery(), ScoreMode.None).innerHit(new InnerHitBuilder().setName("barons")),
+                                ScoreMode.None).innerHit(new InnerHitBuilder()
+                                    .addSort(SortBuilders.fieldSort("_uid").order(SortOrder.ASC))
+                                    .setName("earls")
+                                    .setSize(4))
+                        )
+                )
                 .get();
         assertHitCount(response, 1);
         assertThat(response.getHits().getAt(0).getId(), equalTo("duke"));
@@ -1086,25 +927,4 @@ public class InnerHitsIT extends ESIntegTestCase {
         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();
-        innerHitsBuilder.addInnerHit("my-inner-hit", new InnerHitBuilder().setParentChildType("child"));
-        SearchResponse response = client().prepareSearch("index1")
-            .setQuery(hasChildQuery("child", new MatchAllQueryBuilder(), ScoreMode.None).innerHit(new InnerHitBuilder()))
-            .innerHits(innerHitsBuilder)
-            .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"));
-    }
-
 }

+ 3 - 2
docs/reference/migration/migrate_5_0/search.asciidoc

@@ -151,5 +151,6 @@ specifying the sort order with the `order` option.
 
 ==== Inner hits
 
-* The format of top level inner hits has been changed to be more readable. All options are now set on the same level.
-  So the `path` and `type` options are specified on the same level where `query` and other options are specified.
+* Top level inner hits syntax has been removed. Inner hits can now only be specified as part of the `nested`,
+`has_child` and `has_parent` queries. Use cases previously only possible with top level inner hits can now be done
+with inner hits defined inside the query dsl.

+ 1 - 75
docs/reference/search/request/inner-hits.asciidoc

@@ -226,78 +226,4 @@ An example of a response snippet that could be generated from the above search r
         }
      },
      ...
---------------------------------------------------
-
-[[top-level-inner-hits]]
-==== top level inner hits
-
-Besides defining inner hits on query and filters, inner hits can also be defined as a top level construct alongside the
-`query` and `aggregations` definition. The main reason for using the top level inner hits definition is to let the
-inner hits return documents that don't match with the main query. Also inner hits definitions can be nested via the
-top level notation. Other than that, the inner hit definition inside the query should be used because that is the most
-compact way for defining inner hits.
-
-The following snippet explains the basic structure of inner hits defined at the top level of the search request body:
-
-[source,js]
---------------------------------------------------
-"inner_hits" : {
-    "<inner_hits_name>" : {
-        "<path|type>" : {
-            "<path-to-nested-object-field|child-or-parent-type>" : {
-                <inner_hits_body>
-                [,"inner_hits" : { [<sub_inner_hits>]+ } ]?
-            }
-        }
-    }
-    [,"<inner_hits_name_2>" : { ... } ]*
-}
---------------------------------------------------
-
-Inside the `inner_hits` definition, first the name of the inner hit is defined then whether the inner_hit
-is a nested by defining `path` or a parent/child based definition by defining `type`. The next object layer contains
-the name of the nested object field if the inner_hits is nested or the parent or child type if the inner_hit definition
-is parent/child based.
-
-Multiple inner hit definitions can be defined in a single request. In the `<inner_hits_body>` any option for features
-that `inner_hits` support can be defined. Optionally another `inner_hits` definition can be defined in the `<inner_hits_body>`.
-
-An example that shows the use of nested inner hits via the top level notation:
-
-[source,js]
---------------------------------------------------
-{
-    "query" : {
-        "nested" : {
-            "path" : "comments",
-            "query" : {
-                "match" : {"comments.message" : "[actual query]"}
-            }
-        }
-    },
-    "inner_hits" : {
-        "comment" : { <1>
-            "path" : "comments", <2>
-            "query" : {
-                "match" : {"comments.message" : "[different query]"} <3>
-            }
-        }
-    }
-}
---------------------------------------------------
-
-<1> The inner hit definition with the name `comment`.
-<2> The path option refers to the nested object field `comments`
-<3> A query that runs to collect the nested inner documents for each search hit returned. If no query is defined all nested
-    inner documents will be included belonging to a search hit. This shows that it only make sense to the top level
-    inner hit definition if no query or a different query is specified.
-
-Additional options that are only available when using the top level inner hits notation:
-
-[horizontal]
-`path`:: Defines the nested scope where hits will be collected from.
-`type`:: Defines the parent or child type score where hits will be collected from.
-`query`:: Defines the query that will run in the defined nested, parent or child scope to collect and score hits. By default all document in the scope will be matched.
-
-Either `path` or `type` must be defined. The `path` or `type` defines the scope from where hits are fetched and
-used as inner hits.
+--------------------------------------------------