diff --git a/core/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java index 9d18e42138d..72af5414763 100644 --- a/core/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/AbstractQueryBuilder.java @@ -283,7 +283,7 @@ public abstract class AbstractQueryBuilder> * 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 innerHits) { + protected void extractInnerHitBuilders(Map innerHits) { } // Like Objects.requireNotNull(...) but instead throws a IllegalArgumentException diff --git a/core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java index dcab99ce6a1..c02ff4175c9 100644 --- a/core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/BoolQueryBuilder.java @@ -454,13 +454,13 @@ public class BoolQueryBuilder extends AbstractQueryBuilder { } @Override - protected void extractInnerHitBuilders(Map innerHits) { + protected void extractInnerHitBuilders(Map innerHits) { List 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); + InnerHitContextBuilder.extractInnerHits(clause, innerHits); } } diff --git a/core/src/main/java/org/elasticsearch/index/query/BoostingQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/BoostingQueryBuilder.java index cb16faf9b39..11aaa90fba4 100644 --- a/core/src/main/java/org/elasticsearch/index/query/BoostingQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/BoostingQueryBuilder.java @@ -230,8 +230,8 @@ public class BoostingQueryBuilder extends AbstractQueryBuilder innerHits) { - InnerHitBuilder.extractInnerHits(positiveQuery, innerHits); - InnerHitBuilder.extractInnerHits(negativeQuery, innerHits); + protected void extractInnerHitBuilders(Map innerHits) { + InnerHitContextBuilder.extractInnerHits(positiveQuery, innerHits); + InnerHitContextBuilder.extractInnerHits(negativeQuery, innerHits); } } diff --git a/core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java index 96035b94b16..9308d3495d7 100644 --- a/core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java @@ -166,7 +166,7 @@ public class ConstantScoreQueryBuilder extends AbstractQueryBuilder innerHits) { - InnerHitBuilder.extractInnerHits(filterBuilder, innerHits); + protected void extractInnerHitBuilders(Map innerHits) { + InnerHitContextBuilder.extractInnerHits(filterBuilder, innerHits); } } diff --git a/core/src/main/java/org/elasticsearch/index/query/DisMaxQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/DisMaxQueryBuilder.java index bd8e9031771..7bad1d0fe31 100644 --- a/core/src/main/java/org/elasticsearch/index/query/DisMaxQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/DisMaxQueryBuilder.java @@ -209,9 +209,9 @@ public class DisMaxQueryBuilder extends AbstractQueryBuilder } @Override - protected void extractInnerHitBuilders(Map innerHits) { + protected void extractInnerHitBuilders(Map innerHits) { for (QueryBuilder query : queries) { - InnerHitBuilder.extractInnerHits(query, innerHits); + InnerHitContextBuilder.extractInnerHits(query, innerHits); } } } diff --git a/core/src/main/java/org/elasticsearch/index/query/InnerHitBuilder.java b/core/src/main/java/org/elasticsearch/index/query/InnerHitBuilder.java index d21d3d66706..4419d6a93a6 100644 --- a/core/src/main/java/org/elasticsearch/index/query/InnerHitBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/InnerHitBuilder.java @@ -28,31 +28,21 @@ import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.mapper.DocumentMapper; -import org.elasticsearch.index.mapper.ObjectMapper; import org.elasticsearch.script.Script; -import org.elasticsearch.script.ScriptContext; -import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder.ScriptField; import org.elasticsearch.search.fetch.StoredFieldsContext; -import org.elasticsearch.search.fetch.subphase.DocValueFieldsContext; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; -import org.elasticsearch.search.fetch.subphase.InnerHitsContext; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; -import org.elasticsearch.search.internal.SearchContext; -import org.elasticsearch.search.sort.SortAndFormats; import org.elasticsearch.search.sort.SortBuilder; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import static org.elasticsearch.common.xcontent.XContentParser.Token.END_OBJECT; @@ -61,7 +51,6 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl public static final ParseField NAME_FIELD = new ParseField("name"); public static final ParseField IGNORE_UNMAPPED = new ParseField("ignore_unmapped"); - public static final ParseField INNER_HITS_FIELD = new ParseField("inner_hits"); public static final QueryBuilder DEFAULT_INNER_HIT_QUERY = new MatchAllQueryBuilder(); private static final ObjectParser PARSER = new ObjectParser<>("inner_hits", InnerHitBuilder::new); @@ -104,35 +93,9 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl }, SearchSourceBuilder._SOURCE_FIELD, ObjectParser.ValueType.OBJECT_ARRAY_BOOLEAN_OR_STRING); PARSER.declareObject(InnerHitBuilder::setHighlightBuilder, (p, c) -> HighlightBuilder.fromXContent(c), SearchSourceBuilder.HIGHLIGHT_FIELD); - PARSER.declareObject(InnerHitBuilder::setChildInnerHits, (p, c) -> { - try { - Map 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); - } - }, INNER_HITS_FIELD); } private String name; - private String nestedPath; - private String parentChildType; private boolean ignoreUnmapped; private int from; @@ -148,71 +111,25 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl private Set scriptFields; private HighlightBuilder highlightBuilder; private FetchSourceContext fetchSourceContext; - private Map childInnerHits; public InnerHitBuilder() { + this.name = null; } - private InnerHitBuilder(InnerHitBuilder other) { - name = other.name; - this.ignoreUnmapped = other.ignoreUnmapped; - from = other.from; - size = other.size; - explain = other.explain; - version = other.version; - trackScores = other.trackScores; - if (other.storedFieldsContext != null) { - storedFieldsContext = new StoredFieldsContext(other.storedFieldsContext); - } - if (other.docValueFields != null) { - docValueFields = new ArrayList<> (other.docValueFields); - } - if (other.scriptFields != null) { - scriptFields = new HashSet<> (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); - } + public InnerHitBuilder(String name) { + this.name = name; } - InnerHitBuilder(InnerHitBuilder other, String nestedPath, QueryBuilder query, boolean ignoreUnmapped) { - this(other); - this.query = query; - this.nestedPath = nestedPath; - this.ignoreUnmapped = ignoreUnmapped; - if (name == null) { - this.name = nestedPath; - } - } - - // NORELEASE Do not use this ctr, it is public for hasChild and hasParent query but this is temporary - public InnerHitBuilder(InnerHitBuilder other, QueryBuilder query, String parentChildType, boolean ignoreUnmapped) { - this(other); - this.query = query; - this.parentChildType = parentChildType; - this.ignoreUnmapped = ignoreUnmapped; - if (name == null) { - this.name = parentChildType; - } - } - /** * Read from a stream. */ public InnerHitBuilder(StreamInput in) throws IOException { name = in.readOptionalString(); - nestedPath = in.readOptionalString(); - parentChildType = in.readOptionalString(); + if (in.getVersion().before(Version.V_6_0_0_alpha2_UNRELEASED)) { + in.readOptionalString(); + in.readOptionalString(); + } if (in.getVersion().onOrAfter(Version.V_5_2_0)) { ignoreUnmapped = in.readBoolean(); } @@ -239,21 +156,94 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl } } highlightBuilder = in.readOptionalWriteable(HighlightBuilder::new); - query = in.readNamedWriteable(QueryBuilder.class); - 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)); - } + if (in.getVersion().before(Version.V_6_0_0_alpha2_UNRELEASED)) { + /** + * this is needed for BWC with nodes pre 5.5 + */ + in.readNamedWriteable(QueryBuilder.class); + boolean hasChildren = in.readBoolean(); + assert hasChildren == false; } } @Override public void writeTo(StreamOutput out) throws IOException { + if (out.getVersion().before(Version.V_6_0_0_alpha2_UNRELEASED)) { + throw new IOException("Invalid output version, must >= " + Version.V_6_0_0_alpha2_UNRELEASED.toString()); + } out.writeOptionalString(name); - out.writeOptionalString(nestedPath); - out.writeOptionalString(parentChildType); + out.writeBoolean(ignoreUnmapped); + out.writeVInt(from); + out.writeVInt(size); + out.writeBoolean(explain); + out.writeBoolean(version); + out.writeBoolean(trackScores); + out.writeOptionalWriteable(storedFieldsContext); + out.writeGenericValue(docValueFields); + boolean hasScriptFields = scriptFields != null; + out.writeBoolean(hasScriptFields); + if (hasScriptFields) { + out.writeVInt(scriptFields.size()); + Iterator iterator = scriptFields.stream() + .sorted(Comparator.comparing(ScriptField::fieldName)).iterator(); + while (iterator.hasNext()) { + iterator.next().writeTo(out); + } + } + out.writeOptionalWriteable(fetchSourceContext); + boolean hasSorts = sorts != null; + out.writeBoolean(hasSorts); + if (hasSorts) { + out.writeVInt(sorts.size()); + for (SortBuilder sort : sorts) { + out.writeNamedWriteable(sort); + } + } + out.writeOptionalWriteable(highlightBuilder); + } + + /** + * BWC serialization for nested {@link InnerHitBuilder}. + * Should only be used to send nested inner hits to nodes pre 5.5. + */ + protected void writeToNestedBWC(StreamOutput out, QueryBuilder query, String nestedPath) throws IOException { + assert out.getVersion().before(Version.V_6_0_0_alpha2_UNRELEASED) : + "invalid output version, must be < " + Version.V_6_0_0_alpha2_UNRELEASED.toString(); + writeToBWC(out, query, nestedPath, null); + } + + /** + * BWC serialization for collapsing {@link InnerHitBuilder}. + * Should only be used to send collapsing inner hits to nodes pre 5.5. + */ + public void writeToCollapseBWC(StreamOutput out) throws IOException { + assert out.getVersion().before(Version.V_6_0_0_alpha2_UNRELEASED) : + "invalid output version, must be < " + Version.V_6_0_0_alpha2_UNRELEASED.toString(); + writeToBWC(out, new MatchAllQueryBuilder(), null, null); + } + + /** + * BWC serialization for parent/child {@link InnerHitBuilder}. + * Should only be used to send hasParent or hasChild inner hits to nodes pre 5.5. + */ + public void writeToParentChildBWC(StreamOutput out, QueryBuilder query, String parentChildPath) throws IOException { + assert(out.getVersion().before(Version.V_6_0_0_alpha2_UNRELEASED)) : + "invalid output version, must be < " + Version.V_6_0_0_alpha2_UNRELEASED.toString(); + writeToBWC(out, query, null, parentChildPath); + } + + private void writeToBWC(StreamOutput out, + QueryBuilder query, + String nestedPath, + String parentChildPath) throws IOException { + out.writeOptionalString(name); + if (nestedPath != null) { + out.writeOptionalString(nestedPath); + out.writeOptionalString(null); + } else { + out.writeOptionalString(null); + out.writeOptionalString(parentChildPath); + } if (out.getVersion().onOrAfter(Version.V_5_2_0)) { out.writeBoolean(ignoreUnmapped); } @@ -269,7 +259,7 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl if (hasScriptFields) { out.writeVInt(scriptFields.size()); Iterator iterator = scriptFields.stream() - .sorted((a, b) -> a.fieldName().compareTo(b.fieldName())).iterator(); + .sorted(Comparator.comparing(ScriptField::fieldName)).iterator(); while (iterator.hasNext()) { iterator.next().writeTo(out); } @@ -285,18 +275,7 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl } out.writeOptionalWriteable(highlightBuilder); out.writeNamedWriteable(query); - boolean hasChildInnerHits = childInnerHits != null; - out.writeBoolean(hasChildInnerHits); - if (hasChildInnerHits) { - out.writeVInt(childInnerHits.size()); - Iterator> iterator = childInnerHits.entrySet().stream() - .sorted((a, b) -> a.getKey().compareTo(b.getKey())).iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - out.writeString(entry.getKey()); - entry.getValue().writeTo(out); - } - } + out.writeBoolean(false); } public String getName() { @@ -308,6 +287,11 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl return this; } + public InnerHitBuilder setIgnoreUnmapped(boolean value) { + this.ignoreUnmapped = value; + return this; + } + /** * Whether to include inner hits in the search response hits if required mappings is missing */ @@ -525,136 +509,6 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl return query; } - void setChildInnerHits(Map childInnerHits) { - this.childInnerHits = childInnerHits; - } - - String getParentChildType() { - return parentChildType; - } - - String getNestedPath() { - return nestedPath; - } - - void addChildInnerHit(InnerHitBuilder innerHitBuilder) { - if (childInnerHits == null) { - childInnerHits = new HashMap<>(); - } - this.childInnerHits.put(innerHitBuilder.getName(), innerHitBuilder); - } - - public InnerHitsContext.BaseInnerHits build(SearchContext parentSearchContext, - InnerHitsContext innerHitsContext) throws IOException { - QueryShardContext queryShardContext = parentSearchContext.getQueryShardContext(); - if (nestedPath != null) { - ObjectMapper nestedObjectMapper = queryShardContext.getObjectMapper(nestedPath); - if (nestedObjectMapper == null) { - if (ignoreUnmapped == false) { - throw new IllegalStateException("[" + query.getName() + "] no mapping found for type [" + nestedPath + "]"); - } else { - return null; - } - } - - ObjectMapper parentObjectMapper = queryShardContext.nestedScope().nextLevel(nestedObjectMapper); - InnerHitsContext.NestedInnerHits nestedInnerHits = new InnerHitsContext.NestedInnerHits( - name, parentSearchContext, parentObjectMapper, nestedObjectMapper - ); - setupInnerHitsContext(queryShardContext, nestedInnerHits); - if (childInnerHits != null) { - buildChildInnerHits(parentSearchContext, nestedInnerHits); - } - queryShardContext.nestedScope().previousLevel(); - innerHitsContext.addInnerHitDefinition(nestedInnerHits); - return nestedInnerHits; - } else if (parentChildType != null) { - DocumentMapper documentMapper = queryShardContext.documentMapper(parentChildType); - if (documentMapper == null) { - if (ignoreUnmapped == false) { - throw new IllegalStateException("[" + query.getName() + "] no mapping found for type [" + parentChildType + "]"); - } else { - return null; - } - } - - InnerHitsContext.ParentChildInnerHits parentChildInnerHits = new InnerHitsContext.ParentChildInnerHits( - name, parentSearchContext, queryShardContext.getMapperService(), documentMapper - ); - setupInnerHitsContext(queryShardContext, parentChildInnerHits); - if (childInnerHits != null) { - buildChildInnerHits(parentSearchContext, parentChildInnerHits); - } - innerHitsContext.addInnerHitDefinition( parentChildInnerHits); - return parentChildInnerHits; - } else { - throw new IllegalStateException("Neither a nested or parent/child inner hit"); - } - } - - private void buildChildInnerHits(SearchContext parentSearchContext, InnerHitsContext.BaseInnerHits innerHits) throws IOException { - Map childInnerHits = new HashMap<>(); - for (Map.Entry entry : this.childInnerHits.entrySet()) { - InnerHitsContext.BaseInnerHits childInnerHit = entry.getValue().build( - parentSearchContext, new InnerHitsContext() - ); - if (childInnerHit != null) { - childInnerHits.put(entry.getKey(), childInnerHit); - } - } - innerHits.setChildInnerHits(childInnerHits); - } - - private void setupInnerHitsContext(QueryShardContext context, InnerHitsContext.BaseInnerHits innerHitsContext) throws IOException { - innerHitsContext.from(from); - innerHitsContext.size(size); - innerHitsContext.explain(explain); - innerHitsContext.version(version); - innerHitsContext.trackScores(trackScores); - if (storedFieldsContext != null) { - innerHitsContext.storedFieldsContext(storedFieldsContext); - } - if (docValueFields != null) { - innerHitsContext.docValueFieldsContext(new DocValueFieldsContext(docValueFields)); - } - if (scriptFields != null) { - for (ScriptField field : scriptFields) { - SearchScript searchScript = innerHitsContext.getQueryShardContext().getSearchScript(field.script(), - ScriptContext.SEARCH); - innerHitsContext.scriptFields().add(new org.elasticsearch.search.fetch.subphase.ScriptFieldsContext.ScriptField( - field.fieldName(), searchScript, field.ignoreFailure())); - } - } - if (fetchSourceContext != null) { - innerHitsContext.fetchSourceContext(fetchSourceContext); - } - if (sorts != null) { - Optional optionalSort = SortBuilder.buildSort(sorts, context); - if (optionalSort.isPresent()) { - innerHitsContext.sort(optionalSort.get()); - } - } - if (highlightBuilder != null) { - innerHitsContext.highlight(highlightBuilder.build(context)); - } - ParsedQuery parsedQuery = new ParsedQuery(query.toQuery(context), context.copyNamedQueries()); - innerHitsContext.parsedQuery(parsedQuery); - } - - public void inlineInnerHits(Map innerHits) { - InnerHitBuilder copy = new InnerHitBuilder(this); - copy.parentChildType = this.parentChildType; - copy.nestedPath = this.nestedPath; - copy.query = this.query; - innerHits.put(copy.getName(), copy); - - Map 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(); @@ -697,13 +551,6 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl if (highlightBuilder != null) { builder.field(SearchSourceBuilder.HIGHLIGHT_FIELD.getPreferredName(), highlightBuilder, params); } - if (childInnerHits != null) { - builder.startObject(INNER_HITS_FIELD.getPreferredName()); - for (Map.Entry entry : childInnerHits.entrySet()) { - builder.field(entry.getKey(), entry.getValue(), params); - } - builder.endObject(); - } builder.endObject(); return builder; } @@ -715,8 +562,6 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl InnerHitBuilder that = (InnerHitBuilder) o; return Objects.equals(name, that.name) && - Objects.equals(nestedPath, that.nestedPath) && - Objects.equals(parentChildType, that.parentChildType) && Objects.equals(ignoreUnmapped, that.ignoreUnmapped) && Objects.equals(from, that.from) && Objects.equals(size, that.size) && @@ -728,41 +573,16 @@ public final class InnerHitBuilder extends ToXContentToBytes implements Writeabl Objects.equals(scriptFields, that.scriptFields) && Objects.equals(fetchSourceContext, that.fetchSourceContext) && Objects.equals(sorts, that.sorts) && - Objects.equals(highlightBuilder, that.highlightBuilder) && - Objects.equals(query, that.query) && - Objects.equals(childInnerHits, that.childInnerHits); + Objects.equals(highlightBuilder, that.highlightBuilder); } @Override public int hashCode() { - return Objects.hash(name, nestedPath, parentChildType, ignoreUnmapped, from, size, explain, version, trackScores, - storedFieldsContext, docValueFields, scriptFields, fetchSourceContext, sorts, highlightBuilder, query, childInnerHits); + return Objects.hash(name, ignoreUnmapped, from, size, explain, version, trackScores, + storedFieldsContext, docValueFields, scriptFields, fetchSourceContext, sorts, highlightBuilder); } public static InnerHitBuilder fromXContent(QueryParseContext context) throws IOException { return PARSER.parse(context.parser(), new InnerHitBuilder(), context); } - - public static void extractInnerHits(QueryBuilder query, Map 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"); - } - } - - // TODO public for hasParent and hasChild query - public static InnerHitBuilder rewrite(InnerHitBuilder original, QueryBuilder rewrittenQuery) { - if (original == null) { - return null; - } - - InnerHitBuilder copy = new InnerHitBuilder(original); - copy.query = rewrittenQuery; - copy.parentChildType = original.parentChildType; - copy.nestedPath = original.nestedPath; - return copy; - } - } diff --git a/core/src/main/java/org/elasticsearch/index/query/InnerHitContextBuilder.java b/core/src/main/java/org/elasticsearch/index/query/InnerHitContextBuilder.java new file mode 100644 index 00000000000..5863d02f903 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/index/query/InnerHitContextBuilder.java @@ -0,0 +1,116 @@ +/* + * 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; + +import org.elasticsearch.script.ScriptContext; +import org.elasticsearch.script.SearchScript; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.fetch.subphase.DocValueFieldsContext; +import org.elasticsearch.search.fetch.subphase.InnerHitsContext; +import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.search.sort.SortAndFormats; +import org.elasticsearch.search.sort.SortBuilder; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A builder for {@link InnerHitsContext.InnerHitSubContext} + */ +public abstract class InnerHitContextBuilder { + protected final QueryBuilder query; + protected final InnerHitBuilder innerHitBuilder; + protected final Map children; + + protected InnerHitContextBuilder(QueryBuilder query, InnerHitBuilder innerHitBuilder, Map children) { + this.innerHitBuilder = innerHitBuilder; + this.children = children; + this.query = query; + } + + public abstract void build(SearchContext parentSearchContext, + InnerHitsContext innerHitsContext) throws IOException; + + public static void extractInnerHits(QueryBuilder query, Map 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"); + } + } + + protected void setupInnerHitsContext(QueryShardContext queryShardContext, + InnerHitsContext.InnerHitSubContext innerHitsContext) throws IOException { + innerHitsContext.from(innerHitBuilder.getFrom()); + innerHitsContext.size(innerHitBuilder.getSize()); + innerHitsContext.explain(innerHitBuilder.isExplain()); + innerHitsContext.version(innerHitBuilder.isVersion()); + innerHitsContext.trackScores(innerHitBuilder.isTrackScores()); + if (innerHitBuilder.getStoredFieldsContext() != null) { + innerHitsContext.storedFieldsContext(innerHitBuilder.getStoredFieldsContext()); + } + if (innerHitBuilder.getDocValueFields() != null) { + innerHitsContext.docValueFieldsContext(new DocValueFieldsContext(innerHitBuilder.getDocValueFields())); + } + if (innerHitBuilder.getScriptFields() != null) { + for (SearchSourceBuilder.ScriptField field : innerHitBuilder.getScriptFields()) { + SearchScript searchScript = innerHitsContext.getQueryShardContext().getSearchScript(field.script(), + ScriptContext.SEARCH); + innerHitsContext.scriptFields().add(new org.elasticsearch.search.fetch.subphase.ScriptFieldsContext.ScriptField( + field.fieldName(), searchScript, field.ignoreFailure())); + } + } + if (innerHitBuilder.getFetchSourceContext() != null) { + innerHitsContext.fetchSourceContext(innerHitBuilder.getFetchSourceContext() ); + } + if (innerHitBuilder.getSorts() != null) { + Optional optionalSort = SortBuilder.buildSort(innerHitBuilder.getSorts(), queryShardContext); + if (optionalSort.isPresent()) { + innerHitsContext.sort(optionalSort.get()); + } + } + if (innerHitBuilder.getHighlightBuilder() != null) { + innerHitsContext.highlight(innerHitBuilder.getHighlightBuilder().build(queryShardContext)); + } + ParsedQuery parsedQuery = new ParsedQuery(query.toQuery(queryShardContext), queryShardContext.copyNamedQueries()); + innerHitsContext.parsedQuery(parsedQuery); + Map baseChildren = + buildChildInnerHits(innerHitsContext.parentSearchContext(), children); + innerHitsContext.setChildInnerHits(baseChildren); + } + + private static Map buildChildInnerHits(SearchContext parentSearchContext, + Map children) throws IOException { + + Map childrenInnerHits = new HashMap<>(); + for (Map.Entry entry : children.entrySet()) { + InnerHitsContext childInnerHitsContext = new InnerHitsContext(); + entry.getValue().build( + parentSearchContext, childInnerHitsContext); + if (childInnerHitsContext.getInnerHits() != null) { + childrenInnerHits.putAll(childInnerHitsContext.getInnerHits()); + } + } + return childrenInnerHits; + } +} diff --git a/core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java index 2d23f256f06..5ebb074b799 100644 --- a/core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/NestedQueryBuilder.java @@ -19,26 +19,43 @@ package org.elasticsearch.index.query; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.ReaderUtil; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.TopDocsCollector; +import org.apache.lucene.search.TopFieldCollector; +import org.apache.lucene.search.TopScoreDocCollector; +import org.apache.lucene.search.TotalHitCountCollector; +import org.apache.lucene.search.Weight; import org.apache.lucene.search.join.BitSetProducer; +import org.apache.lucene.search.join.ParentChildrenBlockJoinQuery; import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.Version; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.mapper.ObjectMapper; import org.elasticsearch.index.search.ESToParentBlockJoinQuery; import org.elasticsearch.index.search.NestedHelper; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.fetch.subphase.InnerHitsContext; +import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; +import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; +import static org.elasticsearch.search.fetch.subphase.InnerHitsContext.intersect; + public class NestedQueryBuilder extends AbstractQueryBuilder { public static final String NAME = "nested"; /** @@ -86,7 +103,15 @@ public class NestedQueryBuilder extends AbstractQueryBuilder out.writeString(path); out.writeVInt(scoreMode.ordinal()); out.writeNamedWriteable(query); - out.writeOptionalWriteable(innerHitBuilder); + if (out.getVersion().before(Version.V_6_0_0_alpha2_UNRELEASED)) { + final boolean hasInnerHit = innerHitBuilder != null; + out.writeBoolean(hasInnerHit); + if (hasInnerHit) { + innerHitBuilder.writeToNestedBWC(out, query, path); + } + } else { + out.writeOptionalWriteable(innerHitBuilder); + } out.writeBoolean(ignoreUnmapped); } @@ -105,8 +130,8 @@ public class NestedQueryBuilder extends AbstractQueryBuilder return innerHitBuilder; } - public NestedQueryBuilder innerHit(InnerHitBuilder innerHit, boolean ignoreUnmapped) { - this.innerHitBuilder = new InnerHitBuilder(innerHit, path, query, ignoreUnmapped); + public NestedQueryBuilder innerHit(InnerHitBuilder innerHitBuilder) { + this.innerHitBuilder = innerHitBuilder; return this; } @@ -191,13 +216,10 @@ public class NestedQueryBuilder extends AbstractQueryBuilder } } } - NestedQueryBuilder queryBuilder = new NestedQueryBuilder(path, query, scoreMode) - .ignoreUnmapped(ignoreUnmapped) - .queryName(queryName) - .boost(boost); - if (innerHitBuilder != null) { - queryBuilder.innerHit(innerHitBuilder, ignoreUnmapped); - } + NestedQueryBuilder queryBuilder = new NestedQueryBuilder(path, query, scoreMode, innerHitBuilder) + .ignoreUnmapped(ignoreUnmapped) + .queryName(queryName) + .boost(boost); return queryBuilder; } @@ -287,8 +309,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder protected QueryBuilder doRewrite(QueryRewriteContext queryRewriteContext) throws IOException { QueryBuilder rewrittenQuery = query.rewrite(queryRewriteContext); if (rewrittenQuery != query) { - InnerHitBuilder rewrittenInnerHit = InnerHitBuilder.rewrite(innerHitBuilder, rewrittenQuery); - NestedQueryBuilder nestedQuery = new NestedQueryBuilder(path, rewrittenQuery, scoreMode, rewrittenInnerHit); + NestedQueryBuilder nestedQuery = new NestedQueryBuilder(path, rewrittenQuery, scoreMode, innerHitBuilder); nestedQuery.ignoreUnmapped(ignoreUnmapped); return nestedQuery; } @@ -296,9 +317,102 @@ public class NestedQueryBuilder extends AbstractQueryBuilder } @Override - protected void extractInnerHitBuilders(Map innerHits) { + public void extractInnerHitBuilders(Map innerHits) { if (innerHitBuilder != null) { - innerHitBuilder.inlineInnerHits(innerHits); + Map children = new HashMap<>(); + InnerHitContextBuilder.extractInnerHits(query, children); + InnerHitContextBuilder innerHitContextBuilder = new NestedInnerHitContextBuilder(path, query, innerHitBuilder, children); + String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : path; + innerHits.put(name, innerHitContextBuilder); + } + } + + static class NestedInnerHitContextBuilder extends InnerHitContextBuilder { + private final String path; + + NestedInnerHitContextBuilder(String path, QueryBuilder query, InnerHitBuilder innerHitBuilder, + Map children) { + super(query, innerHitBuilder, children); + this.path = path; + } + + @Override + public void build(SearchContext parentSearchContext, + InnerHitsContext innerHitsContext) throws IOException { + QueryShardContext queryShardContext = parentSearchContext.getQueryShardContext(); + ObjectMapper nestedObjectMapper = queryShardContext.getObjectMapper(path); + if (nestedObjectMapper == null) { + if (innerHitBuilder.isIgnoreUnmapped() == false) { + throw new IllegalStateException("[" + query.getName() + "] no mapping found for type [" + path + "]"); + } else { + return; + } + } + String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : nestedObjectMapper.fullPath(); + ObjectMapper parentObjectMapper = queryShardContext.nestedScope().nextLevel(nestedObjectMapper); + NestedInnerHitSubContext nestedInnerHits = new NestedInnerHitSubContext( + name, parentSearchContext, parentObjectMapper, nestedObjectMapper + ); + setupInnerHitsContext(queryShardContext, nestedInnerHits); + queryShardContext.nestedScope().previousLevel(); + innerHitsContext.addInnerHitDefinition(nestedInnerHits); + } + } + + static final class NestedInnerHitSubContext extends InnerHitsContext.InnerHitSubContext { + + private final ObjectMapper parentObjectMapper; + private final ObjectMapper childObjectMapper; + + NestedInnerHitSubContext(String name, SearchContext context, ObjectMapper parentObjectMapper, ObjectMapper childObjectMapper) { + super(name, context); + this.parentObjectMapper = parentObjectMapper; + this.childObjectMapper = childObjectMapper; + } + + @Override + public TopDocs[] topDocs(SearchHit[] hits) throws IOException { + Weight innerHitQueryWeight = createInnerHitQueryWeight(); + TopDocs[] result = new TopDocs[hits.length]; + for (int i = 0; i < hits.length; i++) { + SearchHit hit = hits[i]; + Query rawParentFilter; + if (parentObjectMapper == null) { + rawParentFilter = Queries.newNonNestedFilter(); + } else { + rawParentFilter = parentObjectMapper.nestedTypeFilter(); + } + + int parentDocId = hit.docId(); + final int readerIndex = ReaderUtil.subIndex(parentDocId, searcher().getIndexReader().leaves()); + // With nested inner hits the nested docs are always in the same segement, so need to use the other segments + LeafReaderContext ctx = searcher().getIndexReader().leaves().get(readerIndex); + + Query childFilter = childObjectMapper.nestedTypeFilter(); + BitSetProducer parentFilter = context.bitsetFilterCache().getBitSetProducer(rawParentFilter); + Query q = new ParentChildrenBlockJoinQuery(parentFilter, childFilter, parentDocId); + Weight weight = context.searcher().createNormalizedWeight(q, false); + if (size() == 0) { + TotalHitCountCollector totalHitCountCollector = new TotalHitCountCollector(); + intersect(weight, innerHitQueryWeight, totalHitCountCollector, ctx); + result[i] = new TopDocs(totalHitCountCollector.getTotalHits(), Lucene.EMPTY_SCORE_DOCS, 0); + } else { + int topN = Math.min(from() + size(), context.searcher().getIndexReader().maxDoc()); + TopDocsCollector topDocsCollector; + if (sort() != null) { + topDocsCollector = TopFieldCollector.create(sort().sort, topN, true, trackScores(), trackScores()); + } else { + topDocsCollector = TopScoreDocCollector.create(topN); + } + try { + intersect(weight, innerHitQueryWeight, topDocsCollector, ctx); + } finally { + clearReleasables(Lifetime.COLLECTION); + } + result[i] = topDocsCollector.topDocs(from(), size()); + } + } + return result; } } } diff --git a/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java index fcb4ab4ddc6..a84aa678e57 100644 --- a/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java @@ -36,7 +36,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentLocation; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.AbstractQueryBuilder; -import org.elasticsearch.index.query.InnerHitBuilder; +import org.elasticsearch.index.query.InnerHitContextBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryParseContext; @@ -431,8 +431,8 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder innerHits) { - InnerHitBuilder.extractInnerHits(query(), innerHits); + protected void extractInnerHitBuilders(Map innerHits) { + InnerHitContextBuilder.extractInnerHits(query(), innerHits); } public static FunctionScoreQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException { diff --git a/core/src/main/java/org/elasticsearch/search/SearchService.java b/core/src/main/java/org/elasticsearch/search/SearchService.java index ac7c33094e3..a5ad038fd53 100644 --- a/core/src/main/java/org/elasticsearch/search/SearchService.java +++ b/core/src/main/java/org/elasticsearch/search/SearchService.java @@ -41,7 +41,7 @@ import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.engine.Engine; -import org.elasticsearch.index.query.InnerHitBuilder; +import org.elasticsearch.index.query.InnerHitContextBuilder; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexShard; @@ -603,17 +603,17 @@ public class SearchService extends AbstractLifecycleComponent implements IndexEv QueryShardContext queryShardContext = context.getQueryShardContext(); context.from(source.from()); context.size(source.size()); - Map innerHitBuilders = new HashMap<>(); + Map innerHitBuilders = new HashMap<>(); if (source.query() != null) { - InnerHitBuilder.extractInnerHits(source.query(), innerHitBuilders); + InnerHitContextBuilder.extractInnerHits(source.query(), innerHitBuilders); context.parsedQuery(queryShardContext.toQuery(source.query())); } if (source.postFilter() != null) { - InnerHitBuilder.extractInnerHits(source.postFilter(), innerHitBuilders); + InnerHitContextBuilder.extractInnerHits(source.postFilter(), innerHitBuilders); context.parsedPostFilter(queryShardContext.toQuery(source.postFilter())); } if (innerHitBuilders.size() > 0) { - for (Map.Entry entry : innerHitBuilders.entrySet()) { + for (Map.Entry entry : innerHitBuilders.entrySet()) { try { entry.getValue().build(context, context.innerHits()); } catch (IOException e) { diff --git a/core/src/main/java/org/elasticsearch/search/collapse/CollapseBuilder.java b/core/src/main/java/org/elasticsearch/search/collapse/CollapseBuilder.java index 542ae2c3ab9..d89e50c8a4b 100644 --- a/core/src/main/java/org/elasticsearch/search/collapse/CollapseBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/collapse/CollapseBuilder.java @@ -19,6 +19,7 @@ package org.elasticsearch.search.collapse; import org.apache.lucene.index.IndexOptions; +import org.elasticsearch.Version; import org.elasticsearch.action.support.ToXContentToBytes; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; @@ -81,7 +82,15 @@ public class CollapseBuilder extends ToXContentToBytes implements Writeable { public void writeTo(StreamOutput out) throws IOException { out.writeString(field); out.writeVInt(maxConcurrentGroupRequests); - out.writeOptionalWriteable(innerHit); + if (out.getVersion().before(Version.V_6_0_0_alpha2_UNRELEASED)) { + final boolean hasInnerHit = innerHit != null; + out.writeBoolean(hasInnerHit); + if (hasInnerHit) { + innerHit.writeToCollapseBWC(out); + } + } else { + out.writeOptionalWriteable(innerHit); + } } public static CollapseBuilder fromXContent(QueryParseContext context) throws IOException { diff --git a/core/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsContext.java b/core/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsContext.java index c8eee3e91ef..39f80ef498f 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsContext.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsContext.java @@ -20,38 +20,18 @@ package org.elasticsearch.search.fetch.subphase; import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.index.ReaderUtil; -import org.apache.lucene.index.Term; -import org.apache.lucene.search.BooleanClause.Occur; -import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.CollectionTerminatedException; import org.apache.lucene.search.Collector; import org.apache.lucene.search.ConjunctionDISI; import org.apache.lucene.search.DocIdSetIterator; -import org.apache.lucene.search.DocValuesTermsQuery; import org.apache.lucene.search.LeafCollector; -import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.ScorerSupplier; -import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TopDocs; -import org.apache.lucene.search.TopDocsCollector; -import org.apache.lucene.search.TopFieldCollector; -import org.apache.lucene.search.TopScoreDocCollector; -import org.apache.lucene.search.TotalHitCountCollector; import org.apache.lucene.search.Weight; -import org.apache.lucene.search.join.BitSetProducer; -import org.apache.lucene.search.join.ParentChildrenBlockJoinQuery; import org.apache.lucene.util.Bits; -import org.elasticsearch.common.lucene.Lucene; -import org.elasticsearch.common.lucene.search.Queries; -import org.elasticsearch.index.mapper.DocumentMapper; -import org.elasticsearch.index.mapper.MapperService; -import org.elasticsearch.index.mapper.ObjectMapper; -import org.elasticsearch.index.mapper.ParentFieldMapper; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SubSearchContext; @@ -61,23 +41,25 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +/** + * Context used for inner hits retrieval + */ public final class InnerHitsContext { - - private final Map innerHits; + private final Map innerHits; public InnerHitsContext() { this.innerHits = new HashMap<>(); } - InnerHitsContext(Map innerHits) { + InnerHitsContext(Map innerHits) { this.innerHits = Objects.requireNonNull(innerHits); } - public Map getInnerHits() { + public Map getInnerHits() { return innerHits; } - public void addInnerHitDefinition(BaseInnerHits innerHit) { + public void addInnerHitDefinition(InnerHitSubContext innerHit) { if (innerHits.containsKey(innerHit.getName())) { throw new IllegalArgumentException("inner_hit definition with the name [" + innerHit.getName() + "] already exists. Use a different inner_hit name or define one explicitly"); @@ -86,13 +68,17 @@ public final class InnerHitsContext { innerHits.put(innerHit.getName(), innerHit); } - public abstract static class BaseInnerHits extends SubSearchContext { + /** + * A {@link SubSearchContext} that associates {@link TopDocs} to each {@link SearchHit} + * in the parent search context + */ + public abstract static class InnerHitSubContext extends SubSearchContext { private final String name; - final SearchContext context; + protected final SearchContext context; private InnerHitsContext childInnerHits; - BaseInnerHits(String name, SearchContext context) { + protected InnerHitSubContext(String name, SearchContext context) { super(context); this.name = name; this.context = context; @@ -109,158 +95,22 @@ public final class InnerHitsContext { return childInnerHits; } - public void setChildInnerHits(Map childInnerHits) { + public void setChildInnerHits(Map childInnerHits) { this.childInnerHits = new InnerHitsContext(childInnerHits); } - Weight createInnerHitQueryWeight() throws IOException { + protected Weight createInnerHitQueryWeight() throws IOException { final boolean needsScores = size() != 0 && (sort() == null || sort().sort.needsScores()); return context.searcher().createNormalizedWeight(query(), needsScores); } + public SearchContext parentSearchContext() { + return context; + } + } - public static final class NestedInnerHits extends BaseInnerHits { - - private final ObjectMapper parentObjectMapper; - private final ObjectMapper childObjectMapper; - - public NestedInnerHits(String name, SearchContext context, ObjectMapper parentObjectMapper, ObjectMapper childObjectMapper) { - super(name != null ? name : childObjectMapper.fullPath(), context); - this.parentObjectMapper = parentObjectMapper; - this.childObjectMapper = childObjectMapper; - } - - @Override - public TopDocs[] topDocs(SearchHit[] hits) throws IOException { - Weight innerHitQueryWeight = createInnerHitQueryWeight(); - TopDocs[] result = new TopDocs[hits.length]; - for (int i = 0; i < hits.length; i++) { - SearchHit hit = hits[i]; - Query rawParentFilter; - if (parentObjectMapper == null) { - rawParentFilter = Queries.newNonNestedFilter(); - } else { - rawParentFilter = parentObjectMapper.nestedTypeFilter(); - } - - int parentDocId = hit.docId(); - final int readerIndex = ReaderUtil.subIndex(parentDocId, searcher().getIndexReader().leaves()); - // With nested inner hits the nested docs are always in the same segement, so need to use the other segments - LeafReaderContext ctx = searcher().getIndexReader().leaves().get(readerIndex); - - Query childFilter = childObjectMapper.nestedTypeFilter(); - BitSetProducer parentFilter = context.bitsetFilterCache().getBitSetProducer(rawParentFilter); - Query q = new ParentChildrenBlockJoinQuery(parentFilter, childFilter, parentDocId); - Weight weight = context.searcher().createNormalizedWeight(q, false); - if (size() == 0) { - TotalHitCountCollector totalHitCountCollector = new TotalHitCountCollector(); - intersect(weight, innerHitQueryWeight, totalHitCountCollector, ctx); - result[i] = new TopDocs(totalHitCountCollector.getTotalHits(), Lucene.EMPTY_SCORE_DOCS, 0); - } else { - int topN = Math.min(from() + size(), context.searcher().getIndexReader().maxDoc()); - TopDocsCollector topDocsCollector; - if (sort() != null) { - topDocsCollector = TopFieldCollector.create(sort().sort, topN, true, trackScores(), trackScores()); - } else { - topDocsCollector = TopScoreDocCollector.create(topN); - } - try { - intersect(weight, innerHitQueryWeight, topDocsCollector, ctx); - } finally { - clearReleasables(Lifetime.COLLECTION); - } - result[i] = topDocsCollector.topDocs(from(), size()); - } - } - return result; - } - } - - public static final class ParentChildInnerHits extends BaseInnerHits { - - private final MapperService mapperService; - private final DocumentMapper documentMapper; - - public ParentChildInnerHits(String name, SearchContext context, MapperService mapperService, DocumentMapper documentMapper) { - super(name != null ? name : documentMapper.type(), context); - this.mapperService = mapperService; - this.documentMapper = documentMapper; - } - - @Override - public TopDocs[] topDocs(SearchHit[] hits) throws IOException { - Weight innerHitQueryWeight = createInnerHitQueryWeight(); - TopDocs[] result = new TopDocs[hits.length]; - for (int i = 0; i < hits.length; i++) { - SearchHit hit = hits[i]; - final Query hitQuery; - if (isParentHit(hit)) { - String field = ParentFieldMapper.joinField(hit.getType()); - hitQuery = new DocValuesTermsQuery(field, hit.getId()); - } else if (isChildHit(hit)) { - DocumentMapper hitDocumentMapper = mapperService.documentMapper(hit.getType()); - final String parentType = hitDocumentMapper.parentFieldMapper().type(); - SearchHitField parentField = hit.field(ParentFieldMapper.NAME); - if (parentField == null) { - throw new IllegalStateException("All children must have a _parent"); - } - Term uidTerm = context.mapperService().createUidTerm(parentType, parentField.getValue()); - if (uidTerm == null) { - hitQuery = new MatchNoDocsQuery("Missing type: " + parentType); - } else { - hitQuery = new TermQuery(uidTerm); - } - } else { - result[i] = Lucene.EMPTY_TOP_DOCS; - continue; - } - - BooleanQuery q = new BooleanQuery.Builder() - // Only include docs that have the current hit as parent - .add(hitQuery, Occur.FILTER) - // Only include docs that have this inner hits type - .add(documentMapper.typeFilter(context.getQueryShardContext()), Occur.FILTER) - .build(); - Weight weight = context.searcher().createNormalizedWeight(q, false); - if (size() == 0) { - TotalHitCountCollector totalHitCountCollector = new TotalHitCountCollector(); - for (LeafReaderContext ctx : context.searcher().getIndexReader().leaves()) { - intersect(weight, innerHitQueryWeight, totalHitCountCollector, ctx); - } - result[i] = new TopDocs(totalHitCountCollector.getTotalHits(), Lucene.EMPTY_SCORE_DOCS, 0); - } else { - int topN = Math.min(from() + size(), context.searcher().getIndexReader().maxDoc()); - TopDocsCollector topDocsCollector; - if (sort() != null) { - topDocsCollector = TopFieldCollector.create(sort().sort, topN, true, trackScores(), trackScores()); - } else { - topDocsCollector = TopScoreDocCollector.create(topN); - } - try { - for (LeafReaderContext ctx : context.searcher().getIndexReader().leaves()) { - intersect(weight, innerHitQueryWeight, topDocsCollector, ctx); - } - } finally { - clearReleasables(Lifetime.COLLECTION); - } - result[i] = topDocsCollector.topDocs(from(), size()); - } - } - return result; - } - - private boolean isParentHit(SearchHit hit) { - return hit.getType().equals(documentMapper.parentFieldMapper().type()); - } - - private boolean isChildHit(SearchHit hit) { - DocumentMapper hitDocumentMapper = mapperService.documentMapper(hit.getType()); - return documentMapper.type().equals(hitDocumentMapper.parentFieldMapper().type()); - } - } - - static void intersect(Weight weight, Weight innerHitQueryWeight, Collector collector, LeafReaderContext ctx) throws IOException { + public static void intersect(Weight weight, Weight innerHitQueryWeight, Collector collector, LeafReaderContext ctx) throws IOException { ScorerSupplier scorerSupplier = weight.scorerSupplier(ctx); if (scorerSupplier == null) { return; diff --git a/core/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsFetchSubPhase.java b/core/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsFetchSubPhase.java index 90e1b5cf828..d1fd74eaa7b 100644 --- a/core/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsFetchSubPhase.java +++ b/core/src/main/java/org/elasticsearch/search/fetch/subphase/InnerHitsFetchSubPhase.java @@ -47,8 +47,8 @@ public final class InnerHitsFetchSubPhase implements FetchSubPhase { return; } - for (Map.Entry entry : context.innerHits().getInnerHits().entrySet()) { - InnerHitsContext.BaseInnerHits innerHits = entry.getValue(); + for (Map.Entry entry : context.innerHits().getInnerHits().entrySet()) { + InnerHitsContext.InnerHitSubContext innerHits = entry.getValue(); TopDocs[] topDocs = innerHits.topDocs(hits); for (int i = 0; i < hits.length; i++) { SearchHit hit = hits[i]; diff --git a/core/src/test/java/org/elasticsearch/index/query/InnerHitBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/InnerHitBuilderTests.java index 9e199f71cea..20b0fc1e16d 100644 --- a/core/src/test/java/org/elasticsearch/index/query/InnerHitBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/InnerHitBuilderTests.java @@ -18,7 +18,6 @@ */ package org.elasticsearch.index.query; -import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; @@ -29,15 +28,12 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; import org.elasticsearch.search.SearchModule; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; -import org.elasticsearch.search.fetch.subphase.InnerHitsContext; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilderTests; -import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.ShardSearchLocalRequest; import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.sort.SortBuilders; @@ -59,10 +55,7 @@ import static java.util.Collections.emptyList; import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode; 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.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class InnerHitBuilderTests extends ESTestCase { @@ -124,7 +117,7 @@ public class InnerHitBuilderTests extends ESTestCase { public void testFromAndToXContent() throws Exception { for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) { - InnerHitBuilder innerHit = randomInnerHits(true, false); + InnerHitBuilder innerHit = randomInnerHits(); XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values())); innerHit.toXContent(builder, ToXContent.EMPTY_PARAMS); //fields is printed out as an object but parsed into a List where order matters, we disable shuffling @@ -144,82 +137,7 @@ 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, false); - Map innerHitBuilders = new HashMap<>(); - nestedQueryBuilder.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, false); - BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder().should(nestedQueryBuilder); - Map 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, false); - ConstantScoreQueryBuilder constantScoreQueryBuilder = new ConstantScoreQueryBuilder(nestedQueryBuilder); - Map 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, false); - InnerHitBuilder leafInnerHits2 = randomInnerHits(); - NestedQueryBuilder nestedQueryBuilder2 = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None) - .innerHit(leafInnerHits2, false); - BoostingQueryBuilder constantScoreQueryBuilder = new BoostingQueryBuilder(nestedQueryBuilder1, nestedQueryBuilder2); - Map 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, false); - FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(nestedQueryBuilder); - Map innerHitBuilders = new HashMap<>(); - ((AbstractQueryBuilder) functionScoreQueryBuilder).extractInnerHitBuilders(innerHitBuilders); - assertThat(innerHitBuilders.get(leafInnerHits.getName()), notNullValue()); - } - - public void testBuildIgnoreUnmappedNestQuery() throws Exception { - QueryShardContext queryShardContext = mock(QueryShardContext.class); - when(queryShardContext.getObjectMapper("path")).thenReturn(null); - SearchContext searchContext = mock(SearchContext.class); - when(searchContext.getQueryShardContext()).thenReturn(queryShardContext); - - InnerHitBuilder leafInnerHits = randomInnerHits(); - NestedQueryBuilder query1 = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None); - query1.innerHit(leafInnerHits, false); - expectThrows(IllegalStateException.class, () -> query1.innerHit().build(searchContext, new InnerHitsContext())); - - NestedQueryBuilder query2 = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None); - query2.innerHit(leafInnerHits, true); - InnerHitsContext innerHitsContext = new InnerHitsContext(); - query2.innerHit().build(searchContext, innerHitsContext); - assertThat(innerHitsContext.getInnerHits().size(), equalTo(0)); - } - public static InnerHitBuilder randomInnerHits() { - return randomInnerHits(true, true); - } - - public static InnerHitBuilder randomInnerHits(boolean recursive, boolean includeQueryTypeOrPath) { InnerHitBuilder innerHits = new InnerHitBuilder(); innerHits.setName(randomAlphaOfLengthBetween(1, 16)); innerHits.setFrom(randomIntBetween(0, 128)); @@ -256,33 +174,7 @@ public class InnerHitBuilderTests extends ESTestCase { ); } innerHits.setHighlightBuilder(HighlightBuilderTests.randomHighlighterBuilder()); - if (recursive && randomBoolean()) { - int size = randomIntBetween(1, 16); - for (int i = 0; i < size; i++) { - innerHits.addChildInnerHit(randomInnerHits(false, includeQueryTypeOrPath)); - } - } - - if (includeQueryTypeOrPath) { - QueryBuilder query = new MatchQueryBuilder(randomAlphaOfLengthBetween(1, 16), randomAlphaOfLengthBetween(1, 16)); - if (randomBoolean()) { - return new InnerHitBuilder(innerHits, randomAlphaOfLength(8), query, randomBoolean()); - } else { - return new InnerHitBuilder(innerHits, query, randomAlphaOfLength(8), randomBoolean()); - } - } else { - return innerHits; - } - } - - public void testCopyConstructor() throws Exception { - InnerHitBuilder original = randomInnerHits(); - InnerHitBuilder copy = original.getNestedPath() != null ? - new InnerHitBuilder(original, original.getNestedPath(), original.getQuery(), original.isIgnoreUnmapped()) : - new InnerHitBuilder(original, original.getQuery(), original.getParentChildType(), original.isIgnoreUnmapped()); - assertThat(copy, equalTo(original)); - copy = mutate(copy); - assertThat(copy, not(equalTo(original))); + return innerHits; } static InnerHitBuilder mutate(InnerHitBuilder original) throws IOException { diff --git a/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java index ed4fbcd53cd..a9e1b6ce23c 100644 --- a/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/NestedQueryBuilderTests.java @@ -23,24 +23,31 @@ import com.carrotsearch.randomizedtesting.generators.RandomPicks; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.Version; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.search.ESToParentBlockJoinQuery; import org.elasticsearch.search.fetch.subphase.InnerHitsContext; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.AbstractQueryTestCase; +import org.elasticsearch.test.VersionUtils; +import org.hamcrest.Matchers; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import static org.elasticsearch.index.query.InnerHitBuilderTests.randomInnerHits; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class NestedQueryBuilderTests extends AbstractQueryTestCase { @@ -74,19 +81,17 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase innerHitBuilders = new HashMap<>(); - InnerHitBuilder.extractInnerHits(queryBuilder, innerHitBuilders); - for (InnerHitBuilder builder : innerHitBuilders.values()) { + Map innerHitInternals = new HashMap<>(); + InnerHitContextBuilder.extractInnerHits(queryBuilder, innerHitInternals); + for (InnerHitContextBuilder builder : innerHitInternals.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()); + InnerHitsContext.InnerHitSubContext innerHits = searchContext.innerHits().getInnerHits().get(queryBuilder.innerHit().getName()); assertEquals(innerHits.size(), queryBuilder.innerHit().getSize()); assertEquals(innerHits.sort().sort.getSort().length, 1); assertEquals(innerHits.sort().sort.getSort()[0].getField(), INT_FIELD_NAME); } } + /** + * Test (de)serialization on all previous released versions + */ + public void testSerializationBWC() throws IOException { + for (Version version : VersionUtils.allReleasedVersions()) { + NestedQueryBuilder testQuery = createTestQueryBuilder(); + if (version.before(Version.V_5_2_0) && testQuery.innerHit() != null) { + // ignore unmapped for inner_hits has been added on 5.2 + testQuery.innerHit().setIgnoreUnmapped(false); + } + assertSerialization(testQuery, version); + } + } + public void testValidate() { QueryBuilder innerQuery = RandomQueryBuilder.createQuery(random()); IllegalArgumentException e = @@ -245,4 +264,87 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase NestedQueryBuilder.parseScoreMode("unrecognized value")); assertEquals("No score mode for child query [unrecognized value] found", e.getMessage()); } + + public void testInlineLeafInnerHitsNestedQuery() throws Exception { + InnerHitBuilder leafInnerHits = randomInnerHits(); + NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None); + nestedQueryBuilder.innerHit(leafInnerHits); + Map innerHitBuilders = new HashMap<>(); + nestedQueryBuilder.extractInnerHitBuilders(innerHitBuilders); + assertThat(innerHitBuilders.get(leafInnerHits.getName()), Matchers.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 innerHitBuilders = new HashMap<>(); + boolQueryBuilder.extractInnerHitBuilders(innerHitBuilders); + assertThat(innerHitBuilders.get(leafInnerHits.getName()), Matchers.notNullValue()); + } + + public void testInlineLeafInnerHitsNestedQueryViaConstantScoreQuery() { + InnerHitBuilder leafInnerHits = randomInnerHits(); + NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None) + .innerHit(leafInnerHits); + ConstantScoreQueryBuilder constantScoreQueryBuilder = new ConstantScoreQueryBuilder(nestedQueryBuilder); + Map innerHitBuilders = new HashMap<>(); + constantScoreQueryBuilder.extractInnerHitBuilders(innerHitBuilders); + assertThat(innerHitBuilders.get(leafInnerHits.getName()), Matchers.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 innerHitBuilders = new HashMap<>(); + constantScoreQueryBuilder.extractInnerHitBuilders(innerHitBuilders); + assertThat(innerHitBuilders.get(leafInnerHits1.getName()), Matchers.notNullValue()); + assertThat(innerHitBuilders.get(leafInnerHits2.getName()), Matchers.notNullValue()); + } + + public void testInlineLeafInnerHitsNestedQueryViaFunctionScoreQuery() { + InnerHitBuilder leafInnerHits = randomInnerHits(); + NestedQueryBuilder nestedQueryBuilder = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None) + .innerHit(leafInnerHits); + FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(nestedQueryBuilder); + Map innerHitBuilders = new HashMap<>(); + ((AbstractQueryBuilder) functionScoreQueryBuilder).extractInnerHitBuilders(innerHitBuilders); + assertThat(innerHitBuilders.get(leafInnerHits.getName()), Matchers.notNullValue()); + } + + public void testBuildIgnoreUnmappedNestQuery() throws Exception { + QueryShardContext queryShardContext = mock(QueryShardContext.class); + when(queryShardContext.getObjectMapper("path")).thenReturn(null); + SearchContext searchContext = mock(SearchContext.class); + when(searchContext.getQueryShardContext()).thenReturn(queryShardContext); + + InnerHitBuilder leafInnerHits = randomInnerHits(); + NestedQueryBuilder query1 = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None); + query1.innerHit(leafInnerHits); + final Map innerHitBuilders = new HashMap<>(); + final InnerHitsContext innerHitsContext = new InnerHitsContext(); + expectThrows(IllegalStateException.class, () -> { + query1.extractInnerHitBuilders(innerHitBuilders); + assertThat(innerHitBuilders.size(), Matchers.equalTo(1)); + assertTrue(innerHitBuilders.containsKey(leafInnerHits.getName())); + innerHitBuilders.get(leafInnerHits.getName()).build(searchContext, innerHitsContext); + }); + innerHitBuilders.clear(); + NestedQueryBuilder query2 = new NestedQueryBuilder("path", new MatchAllQueryBuilder(), ScoreMode.None); + query2.innerHit(leafInnerHits.setIgnoreUnmapped(true)); + query2.extractInnerHitBuilders(innerHitBuilders); + assertThat(innerHitBuilders.size(), Matchers.equalTo(1)); + assertTrue(innerHitBuilders.containsKey(leafInnerHits.getName())); + assertThat(innerHitBuilders.get(leafInnerHits.getName()), instanceOf(NestedQueryBuilder.NestedInnerHitContextBuilder.class)); + NestedQueryBuilder.NestedInnerHitContextBuilder nestedContextBuilder = + (NestedQueryBuilder.NestedInnerHitContextBuilder) innerHitBuilders.get(leafInnerHits.getName()); + nestedContextBuilder.build(searchContext, innerHitsContext); + assertThat(innerHitsContext.getInnerHits().size(), Matchers.equalTo(0)); + } } diff --git a/core/src/test/java/org/elasticsearch/search/collapse/CollapseBuilderTests.java b/core/src/test/java/org/elasticsearch/search/collapse/CollapseBuilderTests.java index e8d8af2502b..a0533a396a8 100644 --- a/core/src/test/java/org/elasticsearch/search/collapse/CollapseBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/search/collapse/CollapseBuilderTests.java @@ -70,7 +70,7 @@ public class CollapseBuilderTests extends AbstractWireSerializingTestCase { CollapseBuilder builder = new CollapseBuilder(randomAlphaOfLength(10)); builder.setMaxConcurrentGroupRequests(randomIntBetween(1, 48)); if (randomBoolean()) { - InnerHitBuilder innerHit = InnerHitBuilderTests.randomInnerHits(false, false); + InnerHitBuilder innerHit = InnerHitBuilderTests.randomInnerHits(); builder.setInnerHits(innerHit); } return builder; diff --git a/core/src/test/java/org/elasticsearch/search/fetch/subphase/InnerHitsIT.java b/core/src/test/java/org/elasticsearch/search/fetch/subphase/InnerHitsIT.java index 8eca5055646..617aa28a0ba 100644 --- a/core/src/test/java/org/elasticsearch/search/fetch/subphase/InnerHitsIT.java +++ b/core/src/test/java/org/elasticsearch/search/fetch/subphase/InnerHitsIT.java @@ -123,7 +123,7 @@ public class InnerHitsIT extends ESIntegTestCase { SearchResponse response = client().prepareSearch("articles") .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"), ScoreMode.Avg) - .innerHit(new InnerHitBuilder().setName("comment"), false) + .innerHit(new InnerHitBuilder("comment")) ).get(); assertNoFailures(response); assertHitCount(response, 1); @@ -141,7 +141,7 @@ public class InnerHitsIT extends ESIntegTestCase { response = client().prepareSearch("articles") .setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant"), ScoreMode.Avg) - .innerHit(new InnerHitBuilder().setName("comment"), false) + .innerHit(new InnerHitBuilder("comment")) ).get(); assertNoFailures(response); assertHitCount(response, 1); @@ -168,8 +168,7 @@ public class InnerHitsIT extends ESIntegTestCase { .addDocValueField("comments.message") .addScriptField("script", new Script(ScriptType.INLINE, MockScriptEngine.NAME, "5", Collections.emptyMap())) - .setSize(1), - false)).get(); + .setSize(1))).get(); assertNoFailures(response); innerHits = response.getHits().getAt(0).getInnerHits().get("comments"); assertThat(innerHits.getTotalHits(), equalTo(2L)); @@ -208,10 +207,10 @@ public class InnerHitsIT extends ESIntegTestCase { int size = randomIntBetween(0, numDocs); 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)), false)); - boolQuery.should(nestedQuery("field2", matchAllQuery(), ScoreMode.Avg).innerHit(new InnerHitBuilder().setName("b") - .addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)).setSize(size), false)); + boolQuery.should(nestedQuery("field1", matchAllQuery(), ScoreMode.Avg).innerHit(new InnerHitBuilder("a").setSize(size) + .addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)))); + boolQuery.should(nestedQuery("field2", matchAllQuery(), ScoreMode.Avg).innerHit(new InnerHitBuilder("b") + .addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)).setSize(size))); SearchResponse searchResponse = client().prepareSearch("idx") .setQuery(boolQuery) .setSize(numDocs) @@ -291,8 +290,8 @@ public class InnerHitsIT extends ESIntegTestCase { .setQuery( nestedQuery("comments", nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "good"), ScoreMode.Avg) - .innerHit(new InnerHitBuilder().setName("remark"), false), - ScoreMode.Avg).innerHit(new InnerHitBuilder(), false) + .innerHit(new InnerHitBuilder("remark")), + ScoreMode.Avg).innerHit(new InnerHitBuilder()) ).get(); assertNoFailures(response); assertHitCount(response, 1); @@ -316,7 +315,7 @@ public class InnerHitsIT extends ESIntegTestCase { // Directly refer to the second level: response = client().prepareSearch("articles") .setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad"), ScoreMode.Avg) - .innerHit(new InnerHitBuilder(), false)).get(); + .innerHit(new InnerHitBuilder())).get(); assertNoFailures(response); assertHitCount(response, 1); assertSearchHit(response, 1, hasId("2")); @@ -334,8 +333,8 @@ public class InnerHitsIT extends ESIntegTestCase { .setQuery( nestedQuery("comments", nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad"), ScoreMode.Avg) - .innerHit(new InnerHitBuilder().setName("remark"), false), - ScoreMode.Avg).innerHit(new InnerHitBuilder(), false) + .innerHit(new InnerHitBuilder("remark")), + ScoreMode.Avg).innerHit(new InnerHitBuilder()) ).get(); assertNoFailures(response); assertHitCount(response, 1); @@ -370,7 +369,7 @@ public class InnerHitsIT extends ESIntegTestCase { SearchResponse response = client().prepareSearch("articles") .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"), ScoreMode.Avg) - .innerHit(new InnerHitBuilder(), false)) + .innerHit(new InnerHitBuilder())) .get(); assertNoFailures(response); assertHitCount(response, 1); @@ -412,7 +411,7 @@ public class InnerHitsIT extends ESIntegTestCase { SearchResponse response = client().prepareSearch("articles") .setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox"), ScoreMode.Avg) - .innerHit(new InnerHitBuilder(), false)).get(); + .innerHit(new InnerHitBuilder())).get(); assertNoFailures(response); assertHitCount(response, 1); SearchHit hit = response.getHits().getAt(0); @@ -426,7 +425,7 @@ public class InnerHitsIT extends ESIntegTestCase { response = client().prepareSearch("articles") .setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "bear"), ScoreMode.Avg) - .innerHit(new InnerHitBuilder(), false)).get(); + .innerHit(new InnerHitBuilder())).get(); assertNoFailures(response); assertHitCount(response, 1); hit = response.getHits().getAt(0); @@ -447,7 +446,7 @@ public class InnerHitsIT extends ESIntegTestCase { indexRandom(true, requests); response = client().prepareSearch("articles") .setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox"), ScoreMode.Avg) - .innerHit(new InnerHitBuilder(), false)).get(); + .innerHit(new InnerHitBuilder())).get(); assertNoFailures(response); assertHitCount(response, 1); hit = response.getHits().getAt(0);; @@ -534,7 +533,7 @@ public class InnerHitsIT extends ESIntegTestCase { .should(termQuery("nested1.n_field1", "n_value1_3").queryName("test2")) .should(termQuery("nested1.n_field2", "n_value2_2").queryName("test3")); query = nestedQuery("nested1", query, ScoreMode.Avg).innerHit( - new InnerHitBuilder().addSort(new FieldSortBuilder("nested1.n_field1").order(SortOrder.ASC)), false); + new InnerHitBuilder().addSort(new FieldSortBuilder("nested1.n_field1").order(SortOrder.ASC))); SearchResponse searchResponse = client().prepareSearch("test") .setQuery(query) .setSize(numDocs) @@ -581,7 +580,7 @@ public class InnerHitsIT extends ESIntegTestCase { SearchResponse response = client().prepareSearch() .setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"), ScoreMode.None) .innerHit(new InnerHitBuilder().setFetchSourceContext(new FetchSourceContext(true, - new String[]{"comments.message"}, null)), false)) + new String[]{"comments.message"}, null)))) .get(); assertNoFailures(response); assertHitCount(response, 1); @@ -608,7 +607,7 @@ public class InnerHitsIT extends ESIntegTestCase { SearchResponse response = client().prepareSearch("index1", "index2") .setQuery(boolQuery() .should(nestedQuery("nested_type", matchAllQuery(), ScoreMode.None).ignoreUnmapped(true) - .innerHit(new InnerHitBuilder(), true)) + .innerHit(new InnerHitBuilder().setIgnoreUnmapped(true))) .should(termQuery("key", "value")) ) .get(); diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasChildQueryBuilder.java b/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasChildQueryBuilder.java index 95d000e3ccc..566edf1cb8c 100644 --- a/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasChildQueryBuilder.java +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasChildQueryBuilder.java @@ -27,6 +27,7 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.join.JoinUtil; import org.apache.lucene.search.join.ScoreMode; import org.apache.lucene.search.similarities.Similarity; +import org.elasticsearch.Version; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamInput; @@ -41,6 +42,7 @@ import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.ParentFieldMapper; import org.elasticsearch.index.query.AbstractQueryBuilder; import org.elasticsearch.index.query.InnerHitBuilder; +import org.elasticsearch.index.query.InnerHitContextBuilder; import org.elasticsearch.index.query.NestedQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryParseContext; @@ -49,6 +51,7 @@ import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.QueryShardException; import java.io.IOException; +import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -123,7 +126,15 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder innerHits) { + protected void extractInnerHitBuilders(Map innerHits) { if (innerHitBuilder != null) { - innerHitBuilder.inlineInnerHits(innerHits); + Map children = new HashMap<>(); + InnerHitContextBuilder.extractInnerHits(query, children); + String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : type; + InnerHitContextBuilder innerHitContextBuilder = + new HasParentQueryBuilder.ParentChildInnerHitContextBuilder(type, query, innerHitBuilder, children); + innerHits.put(name, innerHitContextBuilder); } } } diff --git a/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java b/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java index b216e886a5d..30918233fa3 100644 --- a/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java +++ b/modules/parent-join/src/main/java/org/elasticsearch/join/query/HasParentQueryBuilder.java @@ -18,36 +18,57 @@ */ package org.elasticsearch.join.query; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.DocValuesTermsQuery; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.TopDocsCollector; +import org.apache.lucene.search.TopFieldCollector; +import org.apache.lucene.search.TopScoreDocCollector; +import org.apache.lucene.search.TotalHitCountCollector; +import org.apache.lucene.search.Weight; import org.apache.lucene.search.join.ScoreMode; +import org.elasticsearch.Version; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.fielddata.plain.SortedSetDVOrdinalsIndexFieldData; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.ParentFieldMapper; import org.elasticsearch.index.query.AbstractQueryBuilder; import org.elasticsearch.index.query.InnerHitBuilder; +import org.elasticsearch.index.query.InnerHitContextBuilder; 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.QueryShardException; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.SearchHitField; +import org.elasticsearch.search.fetch.subphase.InnerHitsContext; +import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; +import static org.elasticsearch.search.fetch.subphase.InnerHitsContext.intersect; + /** * Builder for the 'has_parent' query. */ @@ -69,18 +90,18 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder innerHits) { - if (innerHit!= null) { - innerHit.inlineInnerHits(innerHits); + protected void extractInnerHitBuilders(Map innerHits) { + if (innerHitBuilder != null) { + Map children = new HashMap<>(); + InnerHitContextBuilder.extractInnerHits(query, children); + String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : type; + InnerHitContextBuilder innerHitContextBuilder = + new ParentChildInnerHitContextBuilder(type, query, innerHitBuilder, children); + innerHits.put(name, innerHitContextBuilder); + } + } + + static class ParentChildInnerHitContextBuilder extends InnerHitContextBuilder { + private final String typeName; + + ParentChildInnerHitContextBuilder(String typeName, QueryBuilder query, InnerHitBuilder innerHitBuilder, + Map children) { + super(query, innerHitBuilder, children); + this.typeName = typeName; + } + + @Override + public void build(SearchContext parentSearchContext, InnerHitsContext innerHitsContext) throws IOException { + QueryShardContext queryShardContext = parentSearchContext.getQueryShardContext(); + DocumentMapper documentMapper = queryShardContext.documentMapper(typeName); + if (documentMapper == null) { + if (innerHitBuilder.isIgnoreUnmapped() == false) { + throw new IllegalStateException("[" + query.getName() + "] no mapping found for type [" + typeName + "]"); + } else { + return; + } + } + String name = innerHitBuilder.getName() != null ? innerHitBuilder.getName() : documentMapper.type(); + ParentChildInnerHitSubContext parentChildInnerHits = new ParentChildInnerHitSubContext( + name, parentSearchContext, queryShardContext.getMapperService(), documentMapper + ); + setupInnerHitsContext(queryShardContext, parentChildInnerHits); + innerHitsContext.addInnerHitDefinition(parentChildInnerHits); + } + } + + static final class ParentChildInnerHitSubContext extends InnerHitsContext.InnerHitSubContext { + private final MapperService mapperService; + private final DocumentMapper documentMapper; + + ParentChildInnerHitSubContext(String name, SearchContext context, MapperService mapperService, DocumentMapper documentMapper) { + super(name, context); + this.mapperService = mapperService; + this.documentMapper = documentMapper; + } + + @Override + public TopDocs[] topDocs(SearchHit[] hits) throws IOException { + Weight innerHitQueryWeight = createInnerHitQueryWeight(); + TopDocs[] result = new TopDocs[hits.length]; + for (int i = 0; i < hits.length; i++) { + SearchHit hit = hits[i]; + final Query hitQuery; + if (isParentHit(hit)) { + String field = ParentFieldMapper.joinField(hit.getType()); + hitQuery = new DocValuesTermsQuery(field, hit.getId()); + } else if (isChildHit(hit)) { + DocumentMapper hitDocumentMapper = mapperService.documentMapper(hit.getType()); + final String parentType = hitDocumentMapper.parentFieldMapper().type(); + SearchHitField parentField = hit.field(ParentFieldMapper.NAME); + if (parentField == null) { + throw new IllegalStateException("All children must have a _parent"); + } + Term uidTerm = context.mapperService().createUidTerm(parentType, parentField.getValue()); + if (uidTerm == null) { + hitQuery = new MatchNoDocsQuery("Missing type: " + parentType); + } else { + hitQuery = new TermQuery(uidTerm); + } + } else { + result[i] = Lucene.EMPTY_TOP_DOCS; + continue; + } + + BooleanQuery q = new BooleanQuery.Builder() + // Only include docs that have the current hit as parent + .add(hitQuery, BooleanClause.Occur.FILTER) + // Only include docs that have this inner hits type + .add(documentMapper.typeFilter(context.getQueryShardContext()), BooleanClause.Occur.FILTER) + .build(); + Weight weight = context.searcher().createNormalizedWeight(q, false); + if (size() == 0) { + TotalHitCountCollector totalHitCountCollector = new TotalHitCountCollector(); + for (LeafReaderContext ctx : context.searcher().getIndexReader().leaves()) { + intersect(weight, innerHitQueryWeight, totalHitCountCollector, ctx); + } + result[i] = new TopDocs(totalHitCountCollector.getTotalHits(), Lucene.EMPTY_SCORE_DOCS, 0); + } else { + int topN = Math.min(from() + size(), context.searcher().getIndexReader().maxDoc()); + TopDocsCollector topDocsCollector; + if (sort() != null) { + topDocsCollector = TopFieldCollector.create(sort().sort, topN, true, trackScores(), trackScores()); + } else { + topDocsCollector = TopScoreDocCollector.create(topN); + } + try { + for (LeafReaderContext ctx : context.searcher().getIndexReader().leaves()) { + intersect(weight, innerHitQueryWeight, topDocsCollector, ctx); + } + } finally { + clearReleasables(Lifetime.COLLECTION); + } + result[i] = topDocsCollector.topDocs(from(), size()); + } + } + return result; + } + + private boolean isParentHit(SearchHit hit) { + return hit.getType().equals(documentMapper.parentFieldMapper().type()); + } + + private boolean isChildHit(SearchHit hit) { + DocumentMapper hitDocumentMapper = mapperService.documentMapper(hit.getType()); + return documentMapper.type().equals(hitDocumentMapper.parentFieldMapper().type()); } } } diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java b/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java index 8da6dbcdf6c..1b67b2a22b7 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/aggregations/ChildrenIT.java @@ -356,7 +356,7 @@ public class ChildrenIT extends ESIntegTestCase { SearchResponse response = client().prepareSearch(indexName).setTypes(masterType) .setQuery(hasChildQuery(childType, termQuery("color", "orange"), ScoreMode.None)) -.addAggregation(children("my-refinements", childType) + .addAggregation(children("my-refinements", childType) .subAggregation(terms("my-colors").field("color")) .subAggregation(terms("my-sizes").field("size")) ).get(); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java index 87efdf0a5ca..2c828bb3ccf 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/ChildQuerySearchIT.java @@ -872,7 +872,7 @@ public class ChildQuerySearchIT extends ESIntegTestCase { hasChildQuery("child", matchQuery("c_field", "foo"), ScoreMode.None) .innerHit(new InnerHitBuilder().setHighlightBuilder( new HighlightBuilder().field(new Field("c_field") - .highlightQuery(QueryBuilders.matchQuery("c_field", "bar")))), false)) + .highlightQuery(QueryBuilders.matchQuery("c_field", "bar")))))) .get(); assertNoFailures(searchResponse); assertThat(searchResponse.getHits().getTotalHits(), equalTo(1L)); diff --git a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java index 8f4fc9d0c34..e95f3a7713d 100644 --- a/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java +++ b/modules/parent-join/src/test/java/org/elasticsearch/join/query/HasChildQueryBuilderTests.java @@ -42,6 +42,7 @@ import org.elasticsearch.index.mapper.Uid; import org.elasticsearch.index.mapper.UidFieldMapper; import org.elasticsearch.index.query.IdsQueryBuilder; import org.elasticsearch.index.query.InnerHitBuilder; +import org.elasticsearch.index.query.InnerHitContextBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryShardContext; @@ -56,6 +57,7 @@ import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.AbstractQueryTestCase; +import org.elasticsearch.test.VersionUtils; import java.io.IOException; import java.util.Collection; @@ -128,7 +130,8 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase innerHitBuilders = new HashMap<>(); - InnerHitBuilder.extractInnerHits(queryBuilder, innerHitBuilders); - for (InnerHitBuilder builder : innerHitBuilders.values()) { + Map innerHitBuilders = new HashMap<>(); + InnerHitContextBuilder.extractInnerHits(queryBuilder, innerHitBuilders); + for (InnerHitContextBuilder 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 = + InnerHitsContext.InnerHitSubContext innerHits = searchContext.innerHits().getInnerHits().get(queryBuilder.innerHit().getName()); assertEquals(innerHits.size(), queryBuilder.innerHit().getSize()); assertEquals(innerHits.sort().sort.getSort().length, 1); @@ -160,6 +163,20 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase innerHitBuilders = new HashMap<>(); - InnerHitBuilder.extractInnerHits(queryBuilder, innerHitBuilders); - for (InnerHitBuilder builder : innerHitBuilders.values()) { + Map innerHitBuilders = new HashMap<>(); + InnerHitContextBuilder.extractInnerHits(queryBuilder, innerHitBuilders); + for (InnerHitContextBuilder 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() + InnerHitsContext.InnerHitSubContext innerHits = searchContext.innerHits() .getInnerHits().get(queryBuilder.innerHit().getName()); assertEquals(innerHits.size(), queryBuilder.innerHit().getSize()); assertEquals(innerHits.sort().sort.getSort().length, 1); @@ -141,6 +145,20 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase> } } + protected static QueryBuilder assertSerialization(QueryBuilder testQuery) throws IOException { + return assertSerialization(testQuery, Version.CURRENT); + } + /** * Serialize the given query builder and asserts that both are equal */ - protected static QueryBuilder assertSerialization(QueryBuilder testQuery) throws IOException { + protected static QueryBuilder assertSerialization(QueryBuilder testQuery, Version version) throws IOException { try (BytesStreamOutput output = new BytesStreamOutput()) { + output.setVersion(version); output.writeNamedWriteable(testQuery); try (StreamInput in = new NamedWriteableAwareStreamInput(output.bytes().streamInput(), serviceHolder.namedWriteableRegistry)) { + in.setVersion(version); QueryBuilder deserializedQuery = in.readNamedWriteable(QueryBuilder.class); assertEquals(testQuery, deserializedQuery); assertEquals(testQuery.hashCode(), deserializedQuery.hashCode());