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

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

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

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

Closes #11118
This commit is contained in:
Martijn van Groningen 2016-04-18 11:04:25 +02:00
parent 0cf1d16187
commit 6c3beaa2eb
24 changed files with 647 additions and 1000 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,126 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.index.query.support;
import org.elasticsearch.action.support.ToXContentToBytes;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.index.query.QueryParseContext;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public final class InnerHitsBuilder extends ToXContentToBytes implements Writeable {
private final Map<String, InnerHitBuilder> innerHitsBuilders;
public InnerHitsBuilder() {
this.innerHitsBuilders = new HashMap<>();
}
public InnerHitsBuilder(Map<String, InnerHitBuilder> innerHitsBuilders) {
this.innerHitsBuilders = Objects.requireNonNull(innerHitsBuilders);
}
/**
* Read from a stream.
*/
public InnerHitsBuilder(StreamInput in) throws IOException {
int size = in.readVInt();
innerHitsBuilders = new HashMap<>(size);
for (int i = 0; i < size; i++) {
innerHitsBuilders.put(in.readString(), new InnerHitBuilder(in));
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(innerHitsBuilders.size());
for (Map.Entry<String, InnerHitBuilder> entry : innerHitsBuilders.entrySet()) {
out.writeString(entry.getKey());
entry.getValue().writeTo(out);
}
}
public InnerHitsBuilder addInnerHit(String name, InnerHitBuilder builder) {
Objects.requireNonNull(name);
Objects.requireNonNull(builder);
this.innerHitsBuilders.put(name, builder.setName(name));
return this;
}
public Map<String, InnerHitBuilder> getInnerHitsBuilders() {
return innerHitsBuilders;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
for (Map.Entry<String, InnerHitBuilder> entry : innerHitsBuilders.entrySet()) {
builder.field(entry.getKey(), entry.getValue(), params);
}
builder.endObject();
return builder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InnerHitsBuilder that = (InnerHitsBuilder) o;
return innerHitsBuilders.equals(that.innerHitsBuilders);
}
@Override
public int hashCode() {
return innerHitsBuilders.hashCode();
}
public static InnerHitsBuilder fromXContent(QueryParseContext context) throws IOException {
Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
String innerHitName = null;
XContentParser parser = context.parser();
for (Token token = parser.nextToken(); token != Token.END_OBJECT; token = parser.nextToken()) {
switch (token) {
case START_OBJECT:
InnerHitBuilder innerHitBuilder = InnerHitBuilder.fromXContent(context);
innerHitBuilder.setName(innerHitName);
innerHitBuilders.put(innerHitName, innerHitBuilder);
break;
case FIELD_NAME:
innerHitName = parser.currentName();
break;
default:
throw new ParsingException(parser.getTokenLocation(), "Expected [" + XContentParser.Token.START_OBJECT + "] in ["
+ parser.currentName() + "] but found [" + token + "]", parser.getTokenLocation());
}
}
return new InnerHitsBuilder(innerHitBuilders);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,140 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.index.query.support;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.indices.query.IndicesQueriesRegistry;
import org.elasticsearch.search.SearchModule;
import org.elasticsearch.test.ESTestCase;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import java.io.IOException;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.sameInstance;
public class InnerHitsBuilderTests extends ESTestCase {
private static final int NUMBER_OF_TESTBUILDERS = 20;
private static NamedWriteableRegistry namedWriteableRegistry;
private static IndicesQueriesRegistry indicesQueriesRegistry;
@BeforeClass
public static void init() {
namedWriteableRegistry = new NamedWriteableRegistry();
indicesQueriesRegistry = new SearchModule(Settings.EMPTY, namedWriteableRegistry).getQueryParserRegistry();
}
@AfterClass
public static void afterClass() throws Exception {
namedWriteableRegistry = null;
indicesQueriesRegistry = null;
}
public void testSerialization() throws Exception {
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
InnerHitsBuilder original = randomInnerHits();
InnerHitsBuilder deserialized = serializedCopy(original);
assertEquals(deserialized, original);
assertEquals(deserialized.hashCode(), original.hashCode());
assertNotSame(deserialized, original);
}
}
public void testFromAndToXContent() throws Exception {
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
InnerHitsBuilder innerHits = randomInnerHits();
XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values()));
if (randomBoolean()) {
builder.prettyPrint();
}
innerHits.toXContent(builder, ToXContent.EMPTY_PARAMS);
XContentParser parser = XContentHelper.createParser(builder.bytes());
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry, parser, ParseFieldMatcher.EMPTY);
parser.nextToken();
InnerHitsBuilder secondInnerHits = InnerHitsBuilder.fromXContent(context);
assertThat(innerHits, not(sameInstance(secondInnerHits)));
assertThat(innerHits, equalTo(secondInnerHits));
assertThat(innerHits.hashCode(), equalTo(secondInnerHits.hashCode()));
}
}
public void testEqualsAndHashcode() throws IOException {
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
InnerHitsBuilder firstInnerHits = randomInnerHits();
assertFalse("inner hit is equal to null", firstInnerHits.equals(null));
assertFalse("inner hit is equal to incompatible type", firstInnerHits.equals(""));
assertTrue("inner it is not equal to self", firstInnerHits.equals(firstInnerHits));
assertThat("same inner hit's hashcode returns different values if called multiple times", firstInnerHits.hashCode(),
equalTo(firstInnerHits.hashCode()));
InnerHitsBuilder secondBuilder = serializedCopy(firstInnerHits);
assertTrue("inner hit is not equal to self", secondBuilder.equals(secondBuilder));
assertTrue("inner hit is not equal to its copy", firstInnerHits.equals(secondBuilder));
assertTrue("equals is not symmetric", secondBuilder.equals(firstInnerHits));
assertThat("inner hits copy's hashcode is different from original hashcode", secondBuilder.hashCode(),
equalTo(firstInnerHits.hashCode()));
InnerHitsBuilder thirdBuilder = serializedCopy(secondBuilder);
assertTrue("inner hit is not equal to self", thirdBuilder.equals(thirdBuilder));
assertTrue("inner hit is not equal to its copy", secondBuilder.equals(thirdBuilder));
assertThat("inner hit copy's hashcode is different from original hashcode", secondBuilder.hashCode(),
equalTo(thirdBuilder.hashCode()));
assertTrue("equals is not transitive", firstInnerHits.equals(thirdBuilder));
assertThat("inner hit copy's hashcode is different from original hashcode", firstInnerHits.hashCode(),
equalTo(thirdBuilder.hashCode()));
assertTrue("equals is not symmetric", thirdBuilder.equals(secondBuilder));
assertTrue("equals is not symmetric", thirdBuilder.equals(firstInnerHits));
}
}
public static InnerHitsBuilder randomInnerHits() {
InnerHitsBuilder innerHits = new InnerHitsBuilder();
int numInnerHits = randomIntBetween(0, 12);
for (int i = 0; i < numInnerHits; i++) {
innerHits.addInnerHit(randomAsciiOfLength(5), InnerHitBuilderTests.randomInnerHits());
}
return innerHits;
}
private static InnerHitsBuilder serializedCopy(InnerHitsBuilder original) throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
original.writeTo(output);
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
return new InnerHitsBuilder(in);
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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