Refactored inner hits parsing and intoduced InnerHitBuilder
Both top level and inline inner hits are now covered by InnerHitBuilder. Although there are differences between top level and inline inner hits, they now make use of the same builder logic. The parsing of top level inner hits slightly changed to be more readable. Before the nested path or parent/child type had to be specified as encapsuting json object, now these settings are simple fields. Before this was required to allow streaming parsing of inner hits without missing contextual information. Once some issues are fixed with inline inner hits (around multi level hierachy of inner hits), top level inner hits will be deprecated and removed in the next major version.
This commit is contained in:
parent
a1eb332dce
commit
7e2696c570
|
@ -31,7 +31,7 @@ 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.search.fetch.innerhits.InnerHitsBuilder;
|
||||
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;
|
||||
|
|
|
@ -31,17 +31,13 @@ import org.elasticsearch.common.io.stream.StreamInput;
|
|||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.lucene.search.Queries;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
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.QueryInnerHits;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
@ -77,16 +73,20 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
|
|||
|
||||
private int maxChildren = DEFAULT_MAX_CHILDREN;
|
||||
|
||||
private QueryInnerHits queryInnerHits;
|
||||
private InnerHitBuilder innerHitBuilder;
|
||||
|
||||
static final HasChildQueryBuilder PROTOTYPE = new HasChildQueryBuilder("", EmptyQueryBuilder.PROTOTYPE);
|
||||
|
||||
public HasChildQueryBuilder(String type, QueryBuilder query, int maxChildren, int minChildren, ScoreMode scoreMode, QueryInnerHits queryInnerHits) {
|
||||
public HasChildQueryBuilder(String type, QueryBuilder query, int maxChildren, int minChildren, ScoreMode scoreMode, InnerHitBuilder innerHitBuilder) {
|
||||
this(type, query);
|
||||
scoreMode(scoreMode);
|
||||
this.maxChildren = maxChildren;
|
||||
this.minChildren = minChildren;
|
||||
this.queryInnerHits = queryInnerHits;
|
||||
this.innerHitBuilder = innerHitBuilder;
|
||||
if (this.innerHitBuilder != null) {
|
||||
this.innerHitBuilder.setParentChildType(type);
|
||||
this.innerHitBuilder.setQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
public HasChildQueryBuilder(String type, QueryBuilder query) {
|
||||
|
@ -136,16 +136,18 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
|
|||
/**
|
||||
* Sets the query name for the filter that can be used when searching for matched_filters per hit.
|
||||
*/
|
||||
public HasChildQueryBuilder innerHit(QueryInnerHits queryInnerHits) {
|
||||
this.queryInnerHits = queryInnerHits;
|
||||
public HasChildQueryBuilder innerHit(InnerHitBuilder innerHitBuilder) {
|
||||
this.innerHitBuilder = Objects.requireNonNull(innerHitBuilder);
|
||||
this.innerHitBuilder.setParentChildType(type);
|
||||
this.innerHitBuilder.setQuery(query);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns inner hit definition in the scope of this query and reusing the defined type and query.
|
||||
*/
|
||||
public QueryInnerHits innerHit() {
|
||||
return queryInnerHits;
|
||||
public InnerHitBuilder innerHit() {
|
||||
return innerHitBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -193,8 +195,8 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
|
|||
builder.field(HasChildQueryParser.MIN_CHILDREN_FIELD.getPreferredName(), minChildren);
|
||||
builder.field(HasChildQueryParser.MAX_CHILDREN_FIELD.getPreferredName(), maxChildren);
|
||||
printBoostAndQueryName(builder);
|
||||
if (queryInnerHits != null) {
|
||||
queryInnerHits.toXContent(builder, params);
|
||||
if (innerHitBuilder != null) {
|
||||
builder.field(HasChildQueryParser.INNER_HITS_FIELD.getPreferredName(), innerHitBuilder, params);
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
|
@ -226,20 +228,8 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
|
|||
if (parentFieldMapper.active() == false) {
|
||||
throw new QueryShardException(context, "[" + NAME + "] _parent field has no parent type configured");
|
||||
}
|
||||
if (queryInnerHits != null) {
|
||||
try (XContentParser parser = queryInnerHits.getXcontentParser()) {
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new IllegalStateException("start object expected but was: [" + token + "]");
|
||||
}
|
||||
InnerHitsSubSearchContext innerHits = context.getInnerHitsContext(parser);
|
||||
if (innerHits != null) {
|
||||
ParsedQuery parsedQuery = new ParsedQuery(innerQuery, context.copyNamedQueries());
|
||||
InnerHitsContext.ParentChildInnerHits parentChildInnerHits = new InnerHitsContext.ParentChildInnerHits(innerHits.getSubSearchContext(), parsedQuery, null, context.getMapperService(), childDocMapper);
|
||||
String name = innerHits.getName() != null ? innerHits.getName() : type;
|
||||
context.addInnerHits(name, parentChildInnerHits);
|
||||
}
|
||||
}
|
||||
if (innerHitBuilder != null) {
|
||||
context.addInnerHit(innerHitBuilder);
|
||||
}
|
||||
|
||||
String parentType = parentFieldMapper.type();
|
||||
|
@ -363,12 +353,12 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
|
|||
&& Objects.equals(scoreMode, that.scoreMode)
|
||||
&& Objects.equals(minChildren, that.minChildren)
|
||||
&& Objects.equals(maxChildren, that.maxChildren)
|
||||
&& Objects.equals(queryInnerHits, that.queryInnerHits);
|
||||
&& Objects.equals(innerHitBuilder, that.innerHitBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int doHashCode() {
|
||||
return Objects.hash(query, type, scoreMode, minChildren, maxChildren, queryInnerHits);
|
||||
return Objects.hash(query, type, scoreMode, minChildren, maxChildren, innerHitBuilder);
|
||||
}
|
||||
|
||||
protected HasChildQueryBuilder(StreamInput in) throws IOException {
|
||||
|
@ -378,9 +368,7 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
|
|||
final int ordinal = in.readVInt();
|
||||
scoreMode = ScoreMode.values()[ordinal];
|
||||
query = in.readQuery();
|
||||
if (in.readBoolean()) {
|
||||
queryInnerHits = new QueryInnerHits(in);
|
||||
}
|
||||
innerHitBuilder = InnerHitBuilder.optionalReadFromStream(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -395,9 +383,9 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
|
|||
out.writeInt(maxChildren());
|
||||
out.writeVInt(scoreMode.ordinal());
|
||||
out.writeQuery(query);
|
||||
if (queryInnerHits != null) {
|
||||
if (innerHitBuilder != null) {
|
||||
out.writeBoolean(true);
|
||||
queryInnerHits.writeTo(out);
|
||||
innerHitBuilder.writeTo(out);
|
||||
} else {
|
||||
out.writeBoolean(false);
|
||||
}
|
||||
|
@ -408,10 +396,10 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
|
|||
QueryBuilder rewrite = query.rewrite(queryRewriteContext);
|
||||
if (rewrite != query) {
|
||||
HasChildQueryBuilder hasChildQueryBuilder = new HasChildQueryBuilder(type, rewrite);
|
||||
hasChildQueryBuilder.minChildren = minChildren;
|
||||
hasChildQueryBuilder.maxChildren = maxChildren;
|
||||
hasChildQueryBuilder.scoreMode = scoreMode;
|
||||
hasChildQueryBuilder.queryInnerHits = queryInnerHits;
|
||||
hasChildQueryBuilder.minChildren(minChildren);
|
||||
hasChildQueryBuilder.maxChildren(maxChildren);
|
||||
hasChildQueryBuilder.scoreMode(scoreMode);
|
||||
hasChildQueryBuilder.innerHit(innerHitBuilder);
|
||||
return hasChildQueryBuilder;
|
||||
}
|
||||
return this;
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.elasticsearch.common.ParseField;
|
|||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.query.support.QueryInnerHits;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
@ -55,7 +55,7 @@ public class HasChildQueryParser implements QueryParser<HasChildQueryBuilder> {
|
|||
int minChildren = HasChildQueryBuilder.DEFAULT_MIN_CHILDREN;
|
||||
int maxChildren = HasChildQueryBuilder.DEFAULT_MAX_CHILDREN;
|
||||
String queryName = null;
|
||||
QueryInnerHits queryInnerHits = null;
|
||||
InnerHitBuilder innerHitBuilder = null;
|
||||
String currentFieldName = null;
|
||||
XContentParser.Token token;
|
||||
QueryBuilder iqb = null;
|
||||
|
@ -68,7 +68,7 @@ public class HasChildQueryParser implements QueryParser<HasChildQueryBuilder> {
|
|||
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
|
||||
iqb = parseContext.parseInnerQueryBuilder();
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, INNER_HITS_FIELD)) {
|
||||
queryInnerHits = new QueryInnerHits(parser);
|
||||
innerHitBuilder = InnerHitBuilder.fromXContent(parser, parseContext);
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "[has_child] query does not support [" + currentFieldName + "]");
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ public class HasChildQueryParser implements QueryParser<HasChildQueryBuilder> {
|
|||
}
|
||||
}
|
||||
}
|
||||
HasChildQueryBuilder hasChildQueryBuilder = new HasChildQueryBuilder(childType, iqb, maxChildren, minChildren, scoreMode, queryInnerHits);
|
||||
HasChildQueryBuilder hasChildQueryBuilder = new HasChildQueryBuilder(childType, iqb, maxChildren, minChildren, scoreMode, innerHitBuilder);
|
||||
hasChildQueryBuilder.queryName(queryName);
|
||||
hasChildQueryBuilder.boost(boost);
|
||||
return hasChildQueryBuilder;
|
||||
|
|
|
@ -26,13 +26,10 @@ import org.elasticsearch.common.io.stream.StreamInput;
|
|||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.lucene.search.Queries;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
|
||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.query.support.QueryInnerHits;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
|
@ -49,7 +46,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
|
|||
private final QueryBuilder query;
|
||||
private final String type;
|
||||
private boolean score = DEFAULT_SCORE;
|
||||
private QueryInnerHits innerHit;
|
||||
private InnerHitBuilder innerHit;
|
||||
|
||||
/**
|
||||
* @param type The parent type
|
||||
|
@ -66,10 +63,14 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
|
|||
this.query = query;
|
||||
}
|
||||
|
||||
public HasParentQueryBuilder(String type, QueryBuilder query, boolean score, QueryInnerHits innerHits) {
|
||||
public HasParentQueryBuilder(String type, QueryBuilder query, boolean score, InnerHitBuilder innerHit) {
|
||||
this(type, query);
|
||||
this.score = score;
|
||||
this.innerHit = innerHits;
|
||||
this.innerHit = innerHit;
|
||||
if (this.innerHit != null) {
|
||||
this.innerHit.setParentChildType(type);
|
||||
this.innerHit.setQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,8 +84,10 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
|
|||
/**
|
||||
* Sets inner hit definition in the scope of this query and reusing the defined type and query.
|
||||
*/
|
||||
public HasParentQueryBuilder innerHit(QueryInnerHits innerHit) {
|
||||
this.innerHit = innerHit;
|
||||
public HasParentQueryBuilder innerHit(InnerHitBuilder innerHit) {
|
||||
this.innerHit = Objects.requireNonNull(innerHit);
|
||||
this.innerHit.setParentChildType(type);
|
||||
this.innerHit.setQuery(query);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -112,7 +115,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
|
|||
/**
|
||||
* Returns inner hit definition in the scope of this query and reusing the defined type and query.
|
||||
*/
|
||||
public QueryInnerHits innerHit() {
|
||||
public InnerHitBuilder innerHit() {
|
||||
return innerHit;
|
||||
}
|
||||
|
||||
|
@ -137,19 +140,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
|
|||
}
|
||||
|
||||
if (innerHit != null) {
|
||||
try (XContentParser parser = innerHit.getXcontentParser()) {
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new IllegalStateException("start object expected but was: [" + token + "]");
|
||||
}
|
||||
InnerHitsSubSearchContext innerHits = context.getInnerHitsContext(parser);
|
||||
if (innerHits != null) {
|
||||
ParsedQuery parsedQuery = new ParsedQuery(innerQuery, context.copyNamedQueries());
|
||||
InnerHitsContext.ParentChildInnerHits parentChildInnerHits = new InnerHitsContext.ParentChildInnerHits(innerHits.getSubSearchContext(), parsedQuery, null, context.getMapperService(), parentDocMapper);
|
||||
String name = innerHits.getName() != null ? innerHits.getName() : type;
|
||||
context.addInnerHits(name, parentChildInnerHits);
|
||||
}
|
||||
}
|
||||
context.addInnerHit(innerHit);
|
||||
}
|
||||
|
||||
Set<String> childTypes = new HashSet<>();
|
||||
|
@ -200,7 +191,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
|
|||
builder.field(HasParentQueryParser.SCORE_FIELD.getPreferredName(), score);
|
||||
printBoostAndQueryName(builder);
|
||||
if (innerHit != null) {
|
||||
innerHit.toXContent(builder, params);
|
||||
builder.field(HasParentQueryParser.INNER_HITS_FIELD.getPreferredName(), innerHit, params);
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
|
@ -214,9 +205,7 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
|
|||
type = in.readString();
|
||||
score = in.readBoolean();
|
||||
query = in.readQuery();
|
||||
if (in.readBoolean()) {
|
||||
innerHit = new QueryInnerHits(in);
|
||||
}
|
||||
innerHit = InnerHitBuilder.optionalReadFromStream(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -255,8 +244,8 @@ public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBu
|
|||
QueryBuilder rewrite = query.rewrite(queryShardContext);
|
||||
if (rewrite != query) {
|
||||
HasParentQueryBuilder hasParentQueryBuilder = new HasParentQueryBuilder(type, rewrite);
|
||||
hasParentQueryBuilder.score = score;
|
||||
hasParentQueryBuilder.innerHit = innerHit;
|
||||
hasParentQueryBuilder.score(score);
|
||||
hasParentQueryBuilder.innerHit(innerHit);
|
||||
return hasParentQueryBuilder;
|
||||
}
|
||||
return this;
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.elasticsearch.common.ParseField;
|
|||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.query.support.QueryInnerHits;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -35,6 +35,7 @@ public class HasParentQueryParser implements QueryParser<HasParentQueryBuilder>
|
|||
public static final ParseField SCORE_MODE_FIELD = new ParseField("score_mode").withAllDeprecated("score");
|
||||
public static final ParseField TYPE_FIELD = new ParseField("parent_type", "type");
|
||||
public static final ParseField SCORE_FIELD = new ParseField("score");
|
||||
public static final ParseField INNER_HITS_FIELD = new ParseField("inner_hits");
|
||||
|
||||
@Override
|
||||
public String[] names() {
|
||||
|
@ -48,7 +49,7 @@ public class HasParentQueryParser implements QueryParser<HasParentQueryBuilder>
|
|||
String parentType = null;
|
||||
boolean score = HasParentQueryBuilder.DEFAULT_SCORE;
|
||||
String queryName = null;
|
||||
QueryInnerHits innerHits = null;
|
||||
InnerHitBuilder innerHits = null;
|
||||
|
||||
String currentFieldName = null;
|
||||
XContentParser.Token token;
|
||||
|
@ -59,8 +60,8 @@ public class HasParentQueryParser implements QueryParser<HasParentQueryBuilder>
|
|||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
|
||||
iqb = parseContext.parseInnerQueryBuilder();
|
||||
} else if ("inner_hits".equals(currentFieldName)) {
|
||||
innerHits = new QueryInnerHits(parser);
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, INNER_HITS_FIELD)) {
|
||||
innerHits = InnerHitBuilder.fromXContent(parser, parseContext);
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "[has_parent] query does not support [" + currentFieldName + "]");
|
||||
}
|
||||
|
|
|
@ -27,14 +27,10 @@ import org.elasticsearch.common.io.stream.StreamInput;
|
|||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
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.QueryInnerHits;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder> {
|
||||
|
@ -55,7 +51,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
|
||||
private ScoreMode scoreMode = DEFAULT_SCORE_MODE;
|
||||
|
||||
private QueryInnerHits queryInnerHits;
|
||||
private InnerHitBuilder innerHitBuilder;
|
||||
|
||||
public NestedQueryBuilder(String path, QueryBuilder query) {
|
||||
if (path == null) {
|
||||
|
@ -68,10 +64,14 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
this.query = query;
|
||||
}
|
||||
|
||||
public NestedQueryBuilder(String path, QueryBuilder query, ScoreMode scoreMode, QueryInnerHits queryInnerHits) {
|
||||
public NestedQueryBuilder(String path, QueryBuilder query, ScoreMode scoreMode, InnerHitBuilder innerHitBuilder) {
|
||||
this(path, query);
|
||||
scoreMode(scoreMode);
|
||||
this.queryInnerHits = queryInnerHits;
|
||||
this.innerHitBuilder = innerHitBuilder;
|
||||
if (this.innerHitBuilder != null) {
|
||||
this.innerHitBuilder.setNestedPath(path);
|
||||
this.innerHitBuilder.setQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,8 +88,10 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
/**
|
||||
* Sets inner hit definition in the scope of this nested query and reusing the defined path and query.
|
||||
*/
|
||||
public NestedQueryBuilder innerHit(QueryInnerHits innerHit) {
|
||||
this.queryInnerHits = innerHit;
|
||||
public NestedQueryBuilder innerHit(InnerHitBuilder innerHit) {
|
||||
this.innerHitBuilder = Objects.requireNonNull(innerHit);
|
||||
this.innerHitBuilder.setNestedPath(path);
|
||||
this.innerHitBuilder.setQuery(query);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -103,8 +105,8 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
/**
|
||||
* Returns inner hit definition in the scope of this query and reusing the defined type and query.
|
||||
*/
|
||||
public QueryInnerHits innerHit() {
|
||||
return queryInnerHits;
|
||||
public InnerHitBuilder innerHit() {
|
||||
return innerHitBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,8 +126,8 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
builder.field(NestedQueryParser.SCORE_MODE_FIELD.getPreferredName(), HasChildQueryParser.scoreModeAsString(scoreMode));
|
||||
}
|
||||
printBoostAndQueryName(builder);
|
||||
if (queryInnerHits != null) {
|
||||
queryInnerHits.toXContent(builder, params);
|
||||
if (innerHitBuilder != null) {
|
||||
builder.field(NestedQueryParser.INNER_HITS_FIELD.getPreferredName(), innerHitBuilder, params);
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
|
@ -140,12 +142,12 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
return Objects.equals(query, that.query)
|
||||
&& Objects.equals(path, that.path)
|
||||
&& Objects.equals(scoreMode, that.scoreMode)
|
||||
&& Objects.equals(queryInnerHits, that.queryInnerHits);
|
||||
&& Objects.equals(innerHitBuilder, that.innerHitBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int doHashCode() {
|
||||
return Objects.hash(query, path, scoreMode, queryInnerHits);
|
||||
return Objects.hash(query, path, scoreMode, innerHitBuilder);
|
||||
}
|
||||
|
||||
private NestedQueryBuilder(StreamInput in) throws IOException {
|
||||
|
@ -153,9 +155,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
final int ordinal = in.readVInt();
|
||||
scoreMode = ScoreMode.values()[ordinal];
|
||||
query = in.readQuery();
|
||||
if (in.readBoolean()) {
|
||||
queryInnerHits = new QueryInnerHits(in);
|
||||
}
|
||||
innerHitBuilder = InnerHitBuilder.optionalReadFromStream(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -163,9 +163,9 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
out.writeString(path);
|
||||
out.writeVInt(scoreMode.ordinal());
|
||||
out.writeQuery(query);
|
||||
if (queryInnerHits != null) {
|
||||
if (innerHitBuilder != null) {
|
||||
out.writeBoolean(true);
|
||||
queryInnerHits.writeTo(out);
|
||||
innerHitBuilder.writeTo(out);
|
||||
} else {
|
||||
out.writeBoolean(false);
|
||||
}
|
||||
|
@ -187,17 +187,19 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
}
|
||||
final BitSetProducer parentFilter;
|
||||
final Query childFilter;
|
||||
final ObjectMapper parentObjectMapper;
|
||||
final Query innerQuery;
|
||||
ObjectMapper objectMapper = context.nestedScope().getObjectMapper();
|
||||
if (innerHitBuilder != null) {
|
||||
context.addInnerHit(innerHitBuilder);
|
||||
}
|
||||
if (objectMapper == null) {
|
||||
parentFilter = context.bitsetFilter(Queries.newNonNestedFilter());
|
||||
} else {
|
||||
parentFilter = context.bitsetFilter(objectMapper.nestedTypeFilter());
|
||||
}
|
||||
childFilter = nestedObjectMapper.nestedTypeFilter();
|
||||
try {
|
||||
if (objectMapper == null) {
|
||||
parentFilter = context.bitsetFilter(Queries.newNonNestedFilter());
|
||||
} else {
|
||||
parentFilter = context.bitsetFilter(objectMapper.nestedTypeFilter());
|
||||
}
|
||||
childFilter = nestedObjectMapper.nestedTypeFilter();
|
||||
parentObjectMapper = context.nestedScope().nextLevel(nestedObjectMapper);
|
||||
context.nestedScope().nextLevel(nestedObjectMapper);
|
||||
innerQuery = this.query.toQuery(context);
|
||||
if (innerQuery == null) {
|
||||
return null;
|
||||
|
@ -205,23 +207,6 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
} finally {
|
||||
context.nestedScope().previousLevel();
|
||||
}
|
||||
|
||||
if (queryInnerHits != null) {
|
||||
try (XContentParser parser = queryInnerHits.getXcontentParser()) {
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new IllegalStateException("start object expected but was: [" + token + "]");
|
||||
}
|
||||
InnerHitsSubSearchContext innerHits = context.getInnerHitsContext(parser);
|
||||
if (innerHits != null) {
|
||||
ParsedQuery parsedQuery = new ParsedQuery(innerQuery, context.copyNamedQueries());
|
||||
|
||||
InnerHitsContext.NestedInnerHits nestedInnerHits = new InnerHitsContext.NestedInnerHits(innerHits.getSubSearchContext(), parsedQuery, null, parentObjectMapper, nestedObjectMapper);
|
||||
String name = innerHits.getName() != null ? innerHits.getName() : path;
|
||||
context.addInnerHits(name, nestedInnerHits);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ToParentBlockJoinQuery(Queries.filtered(innerQuery, childFilter), parentFilter, scoreMode);
|
||||
}
|
||||
|
||||
|
@ -229,7 +214,7 @@ public class NestedQueryBuilder extends AbstractQueryBuilder<NestedQueryBuilder>
|
|||
protected QueryBuilder<?> doRewrite(QueryRewriteContext queryRewriteContext) throws IOException {
|
||||
QueryBuilder rewrite = query.rewrite(queryRewriteContext);
|
||||
if (rewrite != query) {
|
||||
return new NestedQueryBuilder(path, rewrite).scoreMode(scoreMode);
|
||||
return new NestedQueryBuilder(path, rewrite).scoreMode(scoreMode).innerHit(innerHitBuilder);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.apache.lucene.search.join.ScoreMode;
|
|||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.query.support.QueryInnerHits;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
|
||||
public class NestedQueryParser implements QueryParser<NestedQueryBuilder> {
|
||||
|
||||
|
@ -49,7 +49,7 @@ public class NestedQueryParser implements QueryParser<NestedQueryBuilder> {
|
|||
QueryBuilder query = null;
|
||||
String path = null;
|
||||
String currentFieldName = null;
|
||||
QueryInnerHits queryInnerHits = null;
|
||||
InnerHitBuilder innerHitBuilder = null;
|
||||
XContentParser.Token token;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
|
@ -58,7 +58,7 @@ public class NestedQueryParser implements QueryParser<NestedQueryBuilder> {
|
|||
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
|
||||
query = parseContext.parseInnerQueryBuilder();
|
||||
} else if (parseContext.parseFieldMatcher().match(currentFieldName, INNER_HITS_FIELD)) {
|
||||
queryInnerHits = new QueryInnerHits(parser);
|
||||
innerHitBuilder = InnerHitBuilder.fromXContent(parser, parseContext);
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "[nested] query does not support [" + currentFieldName + "]");
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ public class NestedQueryParser implements QueryParser<NestedQueryBuilder> {
|
|||
}
|
||||
}
|
||||
}
|
||||
return new NestedQueryBuilder(path, query, scoreMode, queryInnerHits).queryName(queryName).boost(boost);
|
||||
return new NestedQueryBuilder(path, query, scoreMode, innerHitBuilder).queryName(queryName).boost(boost);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -47,13 +47,13 @@ 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.InnerHitsQueryParserHelper;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
import org.elasticsearch.index.query.support.InnerHitsBuilder;
|
||||
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.fetch.innerhits.InnerHitsSubSearchContext;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
||||
|
@ -141,10 +141,6 @@ public class QueryShardContext extends QueryRewriteContext {
|
|||
this.parseContext.reset(jp);
|
||||
}
|
||||
|
||||
public InnerHitsSubSearchContext getInnerHitsContext(XContentParser parser) throws IOException {
|
||||
return InnerHitsQueryParserHelper.parse(parser);
|
||||
}
|
||||
|
||||
public AnalysisService getAnalysisService() {
|
||||
return mapperService.analysisService();
|
||||
}
|
||||
|
@ -208,14 +204,14 @@ public class QueryShardContext extends QueryRewriteContext {
|
|||
return isFilter;
|
||||
}
|
||||
|
||||
public void addInnerHits(String name, InnerHitsContext.BaseInnerHits context) {
|
||||
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(name, context);
|
||||
innerHitsContext.addInnerHitDefinition(innerHitBuilder.buildInline(sc, this));
|
||||
}
|
||||
|
||||
public Collection<String> simpleMatchToIndexNames(String pattern) {
|
||||
|
|
|
@ -0,0 +1,597 @@
|
|||
/*
|
||||
* 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.apache.lucene.search.Sort;
|
||||
import org.elasticsearch.action.support.ToXContentToBytes;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||
import org.elasticsearch.index.mapper.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;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder.ScriptField;
|
||||
import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsContext;
|
||||
import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsFetchSubPhase;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
|
||||
import org.elasticsearch.search.fetch.source.FetchSourceContext;
|
||||
import org.elasticsearch.search.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.XContentParser.Token.END_OBJECT;
|
||||
|
||||
public final class InnerHitBuilder extends ToXContentToBytes implements Writeable<InnerHitBuilder> {
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
PARSER.declareBoolean(InnerHitBuilder::setVersion, SearchSourceBuilder.VERSION_FIELD);
|
||||
PARSER.declareBoolean(InnerHitBuilder::setTrackScores, SearchSourceBuilder.TRACK_SCORES_FIELD);
|
||||
PARSER.declareStringArray(InnerHitBuilder::setFieldNames, SearchSourceBuilder.FIELDS_FIELD);
|
||||
PARSER.declareStringArray(InnerHitBuilder::setFieldDataFields, SearchSourceBuilder.FIELDDATA_FIELDS_FIELD);
|
||||
PARSER.declareField((p, i, c) -> {
|
||||
try {
|
||||
List<ScriptField> scriptFields = new ArrayList<>();
|
||||
for (XContentParser.Token token = p.nextToken(); token != END_OBJECT; token = p.nextToken()) {
|
||||
scriptFields.add(new ScriptField(p, c));
|
||||
}
|
||||
i.setScriptFields(scriptFields);
|
||||
} catch (IOException e) {
|
||||
throw new ParsingException(p.getTokenLocation(), "Could not parse inner script definition", e);
|
||||
}
|
||||
}, SearchSourceBuilder.SCRIPT_FIELDS_FIELD, ObjectParser.ValueType.OBJECT);
|
||||
PARSER.declareField((p, i, c) -> i.setSorts(SortBuilder.fromXContent(c)), SearchSourceBuilder.SORT_FIELD,
|
||||
ObjectParser.ValueType.OBJECT_ARRAY);
|
||||
PARSER.declareField((p, i, c) -> {
|
||||
try {
|
||||
i.setFetchSourceContext(FetchSourceContext.parse(p, c));
|
||||
} catch (IOException e) {
|
||||
throw new ParsingException(p.getTokenLocation(), "Could not parse inner _source definition", e);
|
||||
}
|
||||
}, SearchSourceBuilder._SOURCE_FIELD, ObjectParser.ValueType.OBJECT_OR_BOOLEAN);
|
||||
PARSER.declareObject(InnerHitBuilder::setHighlightBuilder, (p, c) -> {
|
||||
try {
|
||||
return HighlightBuilder.PROTOTYPE.fromXContent(c);
|
||||
} catch (IOException e) {
|
||||
throw new ParsingException(p.getTokenLocation(), "Could not parse inner highlight definition", e);
|
||||
}
|
||||
}, SearchSourceBuilder.HIGHLIGHT_FIELD);
|
||||
PARSER.declareObject(InnerHitBuilder::setQuery, (p, c) ->{
|
||||
try {
|
||||
return c.parseInnerQueryBuilder();
|
||||
} catch (IOException e) {
|
||||
throw new ParsingException(p.getTokenLocation(), "Could not parse inner query definition", e);
|
||||
}
|
||||
}, SearchSourceBuilder.QUERY_FIELD);
|
||||
PARSER.declareObject(InnerHitBuilder::setInnerHitsBuilder, (p, c) -> {
|
||||
try {
|
||||
return InnerHitsBuilder.fromXContent(p, c);
|
||||
} catch (IOException e) {
|
||||
throw new ParsingException(p.getTokenLocation(), "Could not parse inner query definition", e);
|
||||
}
|
||||
}, SearchSourceBuilder.INNER_HITS_FIELD);
|
||||
}
|
||||
|
||||
public static InnerHitBuilder optionalReadFromStream(StreamInput in) throws IOException {
|
||||
if (in.readBoolean()) {
|
||||
return new InnerHitBuilder(in);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String name;
|
||||
private String nestedPath;
|
||||
private String parentChildType;
|
||||
|
||||
private int from;
|
||||
private int size = 3;
|
||||
private boolean explain;
|
||||
private boolean version;
|
||||
private boolean trackScores;
|
||||
|
||||
private List<String> fieldNames;
|
||||
private QueryBuilder<?> query = new MatchAllQueryBuilder();
|
||||
private List<SortBuilder<?>> sorts;
|
||||
private List<String> fieldDataFields;
|
||||
private List<ScriptField> scriptFields;
|
||||
private HighlightBuilder highlightBuilder;
|
||||
private InnerHitsBuilder innerHitsBuilder;
|
||||
private FetchSourceContext fetchSourceContext;
|
||||
|
||||
// pkg protected, because is used in InnerHitsBuilder
|
||||
InnerHitBuilder(StreamInput in) throws IOException {
|
||||
name = in.readOptionalString();
|
||||
nestedPath = in.readOptionalString();
|
||||
parentChildType = in.readOptionalString();
|
||||
from = in.readVInt();
|
||||
size = in.readVInt();
|
||||
explain = in.readBoolean();
|
||||
version = in.readBoolean();
|
||||
trackScores = in.readBoolean();
|
||||
fieldNames = (List<String>) in.readGenericValue();
|
||||
fieldDataFields = (List<String>) in.readGenericValue();
|
||||
if (in.readBoolean()) {
|
||||
scriptFields = in.readList(t -> ScriptField.PROTOTYPE.readFrom(in));
|
||||
}
|
||||
fetchSourceContext = FetchSourceContext.optionalReadFromStream(in);
|
||||
if (in.readBoolean()) {
|
||||
int size = in.readVInt();
|
||||
sorts = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
sorts.add(in.readSortBuilder());
|
||||
}
|
||||
}
|
||||
highlightBuilder = in.readOptionalWriteable(HighlightBuilder.PROTOTYPE::readFrom);
|
||||
query = in.readQuery();
|
||||
innerHitsBuilder = in.readOptionalWriteable(InnerHitsBuilder.PROTO::readFrom);
|
||||
}
|
||||
|
||||
public InnerHitBuilder() {
|
||||
}
|
||||
|
||||
public InnerHitBuilder setParentChildType(String parentChildType) {
|
||||
this.parentChildType = parentChildType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setNestedPath(String nestedPath) {
|
||||
this.nestedPath = nestedPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setName(String name) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setFrom(int from) {
|
||||
if (from < 0) {
|
||||
throw new IllegalArgumentException("illegal from value, at least 0 or higher");
|
||||
}
|
||||
this.from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setSize(int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException("illegal size value, at least 0 or higher");
|
||||
}
|
||||
this.size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isExplain() {
|
||||
return explain;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setExplain(boolean explain) {
|
||||
this.explain = explain;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setVersion(boolean version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isTrackScores() {
|
||||
return trackScores;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setTrackScores(boolean trackScores) {
|
||||
this.trackScores = trackScores;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getFieldNames() {
|
||||
return fieldNames;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setFieldNames(List<String> fieldNames) {
|
||||
this.fieldNames = fieldNames;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getFieldDataFields() {
|
||||
return fieldDataFields;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setFieldDataFields(List<String> fieldDataFields) {
|
||||
this.fieldDataFields = fieldDataFields;
|
||||
return this;
|
||||
}
|
||||
|
||||
public InnerHitBuilder addFieldDataField(String field) {
|
||||
if (fieldDataFields == null) {
|
||||
fieldDataFields = new ArrayList<>();
|
||||
}
|
||||
fieldDataFields.add(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<ScriptField> getScriptFields() {
|
||||
return scriptFields;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setScriptFields(List<ScriptField> scriptFields) {
|
||||
this.scriptFields = scriptFields;
|
||||
return this;
|
||||
}
|
||||
|
||||
public InnerHitBuilder addScriptField(String name, Script script) {
|
||||
if (scriptFields == null) {
|
||||
scriptFields = new ArrayList<>();
|
||||
}
|
||||
scriptFields.add(new ScriptField(name, script, false));
|
||||
return this;
|
||||
}
|
||||
|
||||
public FetchSourceContext getFetchSourceContext() {
|
||||
return fetchSourceContext;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setFetchSourceContext(FetchSourceContext fetchSourceContext) {
|
||||
this.fetchSourceContext = fetchSourceContext;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<SortBuilder<?>> getSorts() {
|
||||
return sorts;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setSorts(List<SortBuilder<?>> sorts) {
|
||||
this.sorts = sorts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public InnerHitBuilder addSort(SortBuilder sort) {
|
||||
if (sorts == null) {
|
||||
sorts = new ArrayList<>();
|
||||
}
|
||||
sorts.add(sort);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HighlightBuilder getHighlightBuilder() {
|
||||
return highlightBuilder;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setHighlightBuilder(HighlightBuilder highlightBuilder) {
|
||||
this.highlightBuilder = highlightBuilder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder<?> getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setQuery(QueryBuilder<?> query) {
|
||||
this.query = Objects.requireNonNull(query);
|
||||
return this;
|
||||
}
|
||||
|
||||
public InnerHitBuilder setInnerHitsBuilder(InnerHitsBuilder innerHitsBuilder) {
|
||||
this.innerHitsBuilder = innerHitsBuilder;
|
||||
return this;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top level inner hits are different than inline inner hits:
|
||||
* 1) Nesting. Top level inner hits can be hold nested inner hits, that why this method is recursive (via buildChildInnerHits)
|
||||
* 2) Top level inner hits query is an option, whereas with inline inner hits that is based on the nested, has_child
|
||||
* or has_parent's inner query.
|
||||
*
|
||||
* Because of these changes there are different methods for building inline (which is simpler) and top level inner
|
||||
* hits. Also top level inner hits will soon be deprecated.
|
||||
*/
|
||||
public InnerHitsContext.BaseInnerHits buildTopLevel(SearchContext parentSearchContext, QueryShardContext context,
|
||||
InnerHitsContext innerHitsContext) throws IOException {
|
||||
if (nestedPath != null) {
|
||||
ObjectMapper nestedObjectMapper = context.getObjectMapper(nestedPath);
|
||||
ObjectMapper parentObjectMapper = context.nestedScope().nextLevel(nestedObjectMapper);
|
||||
InnerHitsContext.NestedInnerHits nestedInnerHits = new InnerHitsContext.NestedInnerHits(
|
||||
name, parentSearchContext, parentObjectMapper, nestedObjectMapper
|
||||
);
|
||||
setupInnerHitsContext(context, nestedInnerHits);
|
||||
if (innerHitsBuilder != null) {
|
||||
buildChildInnerHits(parentSearchContext, context, nestedInnerHits);
|
||||
}
|
||||
context.nestedScope().previousLevel();
|
||||
innerHitsContext.addInnerHitDefinition(nestedInnerHits);
|
||||
return nestedInnerHits;
|
||||
} else if (parentChildType != null) {
|
||||
DocumentMapper documentMapper = context.getMapperService().documentMapper(parentChildType);
|
||||
InnerHitsContext.ParentChildInnerHits parentChildInnerHits = new InnerHitsContext.ParentChildInnerHits(
|
||||
name, parentSearchContext, context.getMapperService(), documentMapper
|
||||
);
|
||||
setupInnerHitsContext(context, parentChildInnerHits);
|
||||
if (innerHitsBuilder != null) {
|
||||
buildChildInnerHits(parentSearchContext, context, parentChildInnerHits);
|
||||
}
|
||||
innerHitsContext.addInnerHitDefinition( parentChildInnerHits);
|
||||
return parentChildInnerHits;
|
||||
} else {
|
||||
throw new IllegalStateException("Neither a nested or parent/child inner hit");
|
||||
}
|
||||
}
|
||||
|
||||
private void buildChildInnerHits(SearchContext parentSearchContext, QueryShardContext context,
|
||||
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()
|
||||
);
|
||||
childInnerHits.put(entry.getKey(), childInnerHit);
|
||||
}
|
||||
innerHits.setChildInnerHits(childInnerHits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InnerHitBuilder readFrom(StreamInput in) throws IOException {
|
||||
return new InnerHitBuilder(in);
|
||||
}
|
||||
|
||||
private void setupInnerHitsContext(QueryShardContext context, InnerHitsContext.BaseInnerHits innerHitsContext) throws IOException {
|
||||
innerHitsContext.from(from);
|
||||
innerHitsContext.size(size);
|
||||
innerHitsContext.explain(explain);
|
||||
innerHitsContext.version(version);
|
||||
innerHitsContext.trackScores(trackScores);
|
||||
if (fieldNames != null) {
|
||||
if (fieldNames.isEmpty()) {
|
||||
innerHitsContext.emptyFieldNames();
|
||||
} else {
|
||||
for (String fieldName : fieldNames) {
|
||||
innerHitsContext.fieldNames().add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fieldDataFields != null) {
|
||||
FieldDataFieldsContext fieldDataFieldsContext = innerHitsContext
|
||||
.getFetchSubPhaseContext(FieldDataFieldsFetchSubPhase.CONTEXT_FACTORY);
|
||||
for (String field : fieldDataFields) {
|
||||
fieldDataFieldsContext.add(new FieldDataFieldsContext.FieldDataField(field));
|
||||
}
|
||||
fieldDataFieldsContext.setHitExecutionNeeded(true);
|
||||
}
|
||||
if (scriptFields != null) {
|
||||
for (ScriptField field : scriptFields) {
|
||||
SearchScript searchScript = innerHitsContext.scriptService().search(innerHitsContext.lookup(), field.script(),
|
||||
ScriptContext.Standard.SEARCH, Collections.emptyMap());
|
||||
innerHitsContext.scriptFields().add(new org.elasticsearch.search.fetch.script.ScriptFieldsContext.ScriptField(
|
||||
field.fieldName(), searchScript, field.ignoreFailure()));
|
||||
}
|
||||
}
|
||||
if (fetchSourceContext != null) {
|
||||
innerHitsContext.fetchSourceContext(fetchSourceContext);
|
||||
}
|
||||
if (sorts != null) {
|
||||
Optional<Sort> optionalSort = SortBuilder.buildSort(sorts, context);
|
||||
if (optionalSort.isPresent()) {
|
||||
innerHitsContext.sort(optionalSort.get());
|
||||
}
|
||||
}
|
||||
if (highlightBuilder != null) {
|
||||
innerHitsContext.highlight(highlightBuilder.build(context));
|
||||
}
|
||||
ParsedQuery parsedQuery = new ParsedQuery(query.toQuery(context), context.copyNamedQueries());
|
||||
innerHitsContext.parsedQuery(parsedQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeOptionalString(name);
|
||||
out.writeOptionalString(nestedPath);
|
||||
out.writeOptionalString(parentChildType);
|
||||
out.writeVInt(from);
|
||||
out.writeVInt(size);
|
||||
out.writeBoolean(explain);
|
||||
out.writeBoolean(version);
|
||||
out.writeBoolean(trackScores);
|
||||
out.writeGenericValue(fieldNames);
|
||||
out.writeGenericValue(fieldDataFields);
|
||||
boolean hasScriptFields = scriptFields != null;
|
||||
out.writeBoolean(hasScriptFields);
|
||||
if (hasScriptFields) {
|
||||
out.writeList(scriptFields);
|
||||
}
|
||||
FetchSourceContext.optionalWriteToStream(fetchSourceContext, out);
|
||||
boolean hasSorts = sorts != null;
|
||||
out.writeBoolean(hasSorts);
|
||||
if (hasSorts) {
|
||||
out.writeVInt(sorts.size());
|
||||
for (SortBuilder<?> sort : sorts) {
|
||||
out.writeSortBuilder(sort);
|
||||
}
|
||||
}
|
||||
out.writeOptionalWriteable(highlightBuilder);
|
||||
out.writeQuery(query);
|
||||
out.writeOptionalWriteable(innerHitsBuilder);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
builder.field(SearchSourceBuilder.FROM_FIELD.getPreferredName(), from);
|
||||
builder.field(SearchSourceBuilder.SIZE_FIELD.getPreferredName(), size);
|
||||
builder.field(SearchSourceBuilder.VERSION_FIELD.getPreferredName(), version);
|
||||
builder.field(SearchSourceBuilder.EXPLAIN_FIELD.getPreferredName(), explain);
|
||||
builder.field(SearchSourceBuilder.TRACK_SCORES_FIELD.getPreferredName(), trackScores);
|
||||
if (fetchSourceContext != null) {
|
||||
builder.field(SearchSourceBuilder._SOURCE_FIELD.getPreferredName(), fetchSourceContext, params);
|
||||
}
|
||||
if (fieldNames != null) {
|
||||
if (fieldNames.size() == 1) {
|
||||
builder.field(SearchSourceBuilder.FIELDS_FIELD.getPreferredName(), fieldNames.get(0));
|
||||
} else {
|
||||
builder.startArray(SearchSourceBuilder.FIELDS_FIELD.getPreferredName());
|
||||
for (String fieldName : fieldNames) {
|
||||
builder.value(fieldName);
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
}
|
||||
if (fieldDataFields != null) {
|
||||
builder.startArray(SearchSourceBuilder.FIELDDATA_FIELDS_FIELD.getPreferredName());
|
||||
for (String fieldDataField : fieldDataFields) {
|
||||
builder.value(fieldDataField);
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
if (scriptFields != null) {
|
||||
builder.startObject(SearchSourceBuilder.SCRIPT_FIELDS_FIELD.getPreferredName());
|
||||
for (ScriptField scriptField : scriptFields) {
|
||||
scriptField.toXContent(builder, params);
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
if (sorts != null) {
|
||||
builder.startArray(SearchSourceBuilder.SORT_FIELD.getPreferredName());
|
||||
for (SortBuilder<?> sort : sorts) {
|
||||
sort.toXContent(builder, params);
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
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);
|
||||
}
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
InnerHitBuilder that = (InnerHitBuilder) o;
|
||||
return Objects.equals(name, that.name) &&
|
||||
Objects.equals(nestedPath, that.nestedPath) &&
|
||||
Objects.equals(parentChildType, that.parentChildType) &&
|
||||
Objects.equals(from, that.from) &&
|
||||
Objects.equals(size, that.size) &&
|
||||
Objects.equals(explain, that.explain) &&
|
||||
Objects.equals(version, that.version) &&
|
||||
Objects.equals(trackScores, that.trackScores) &&
|
||||
Objects.equals(fieldNames, that.fieldNames) &&
|
||||
Objects.equals(fieldDataFields, that.fieldDataFields) &&
|
||||
Objects.equals(scriptFields, that.scriptFields) &&
|
||||
Objects.equals(fetchSourceContext, that.fetchSourceContext) &&
|
||||
Objects.equals(sorts, that.sorts) &&
|
||||
Objects.equals(highlightBuilder, that.highlightBuilder) &&
|
||||
Objects.equals(query, that.query) &&
|
||||
Objects.equals(innerHitsBuilder, that.innerHitsBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, nestedPath, parentChildType, from, size, explain, version, trackScores, fieldNames,
|
||||
fieldDataFields, scriptFields, fetchSourceContext, sorts, highlightBuilder, query, innerHitsBuilder);
|
||||
}
|
||||
|
||||
public static InnerHitBuilder fromXContent(XContentParser parser, QueryParseContext context) throws IOException {
|
||||
return PARSER.parse(parser, new InnerHitBuilder(), context);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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.ToXContent;
|
||||
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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class InnerHitsBuilder extends ToXContentToBytes implements Writeable<InnerHitsBuilder> {
|
||||
|
||||
public final static InnerHitsBuilder PROTO = new InnerHitsBuilder(Collections.emptyMap());
|
||||
|
||||
private final Map<String, InnerHitBuilder> innerHitsBuilders;
|
||||
|
||||
public InnerHitsBuilder() {
|
||||
this.innerHitsBuilders = new HashMap<>();
|
||||
}
|
||||
|
||||
public InnerHitsBuilder(Map<String, InnerHitBuilder> innerHitsBuilders) {
|
||||
this.innerHitsBuilders = Objects.requireNonNull(innerHitsBuilders);
|
||||
}
|
||||
|
||||
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 InnerHitsBuilder readFrom(StreamInput in) throws IOException {
|
||||
int size = in.readVInt();
|
||||
Map<String, InnerHitBuilder> innerHitsBuilders = new HashMap<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
innerHitsBuilders.put(in.readString(), new InnerHitBuilder(in));
|
||||
}
|
||||
return new InnerHitsBuilder(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 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);
|
||||
}
|
||||
}
|
||||
|
||||
@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(XContentParser parser, QueryParseContext context) throws IOException {
|
||||
Map<String, InnerHitBuilder> innerHitBuilders = new HashMap<>();
|
||||
String innerHitName = null;
|
||||
for (Token token = parser.nextToken(); token != Token.END_OBJECT; token = parser.nextToken()) {
|
||||
switch (token) {
|
||||
case START_OBJECT:
|
||||
InnerHitBuilder innerHitBuilder = InnerHitBuilder.fromXContent(parser, 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,133 +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.xcontent.XContentParser;
|
||||
import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsParseElement;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
|
||||
import org.elasticsearch.search.fetch.script.ScriptFieldsParseElement;
|
||||
import org.elasticsearch.search.fetch.source.FetchSourceParseElement;
|
||||
import org.elasticsearch.search.highlight.HighlighterParseElement;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.internal.SubSearchContext;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class InnerHitsQueryParserHelper {
|
||||
|
||||
public static final InnerHitsQueryParserHelper INSTANCE = new InnerHitsQueryParserHelper();
|
||||
|
||||
private static final FetchSourceParseElement sourceParseElement = new FetchSourceParseElement();
|
||||
private static final HighlighterParseElement highlighterParseElement = new HighlighterParseElement();
|
||||
private static final ScriptFieldsParseElement scriptFieldsParseElement = new ScriptFieldsParseElement();
|
||||
private static final FieldDataFieldsParseElement fieldDataFieldsParseElement = new FieldDataFieldsParseElement();
|
||||
|
||||
public static InnerHitsSubSearchContext parse(XContentParser parser) throws IOException {
|
||||
String fieldName = null;
|
||||
XContentParser.Token token;
|
||||
String innerHitName = null;
|
||||
SubSearchContext subSearchContext = new SubSearchContext(SearchContext.current());
|
||||
try {
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
fieldName = parser.currentName();
|
||||
} else if (token.isValue()) {
|
||||
if ("name".equals(fieldName)) {
|
||||
innerHitName = parser.textOrNull();
|
||||
} else {
|
||||
parseCommonInnerHitOptions(parser, token, fieldName, subSearchContext, sourceParseElement, highlighterParseElement, scriptFieldsParseElement, fieldDataFieldsParseElement);
|
||||
}
|
||||
} else {
|
||||
parseCommonInnerHitOptions(parser, token, fieldName, subSearchContext, sourceParseElement, highlighterParseElement, scriptFieldsParseElement, fieldDataFieldsParseElement);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Failed to parse [_inner_hits]", e);
|
||||
}
|
||||
return new InnerHitsSubSearchContext(innerHitName, subSearchContext);
|
||||
}
|
||||
|
||||
public static void parseCommonInnerHitOptions(XContentParser parser, XContentParser.Token token, String fieldName, SubSearchContext subSearchContext,
|
||||
FetchSourceParseElement sourceParseElement, HighlighterParseElement highlighterParseElement,
|
||||
ScriptFieldsParseElement scriptFieldsParseElement, FieldDataFieldsParseElement fieldDataFieldsParseElement) throws Exception {
|
||||
if ("sort".equals(fieldName)) {
|
||||
SortBuilder.parseSort(parser, subSearchContext);
|
||||
} else if ("_source".equals(fieldName)) {
|
||||
sourceParseElement.parse(parser, subSearchContext);
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
switch (fieldName) {
|
||||
case "highlight":
|
||||
highlighterParseElement.parse(parser, subSearchContext);
|
||||
break;
|
||||
case "scriptFields":
|
||||
case "script_fields":
|
||||
scriptFieldsParseElement.parse(parser, subSearchContext);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown key for a " + token + " for nested query: [" + fieldName + "].");
|
||||
}
|
||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||
switch (fieldName) {
|
||||
case "fielddataFields":
|
||||
case "fielddata_fields":
|
||||
fieldDataFieldsParseElement.parse(parser, subSearchContext);
|
||||
break;
|
||||
case "fields":
|
||||
boolean added = false;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
||||
String name = parser.text();
|
||||
added = true;
|
||||
subSearchContext.fieldNames().add(name);
|
||||
}
|
||||
if (!added) {
|
||||
subSearchContext.emptyFieldNames();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown key for a " + token + " for nested query: [" + fieldName + "].");
|
||||
}
|
||||
} else if (token.isValue()) {
|
||||
switch (fieldName) {
|
||||
case "from":
|
||||
subSearchContext.from(parser.intValue());
|
||||
break;
|
||||
case "size":
|
||||
subSearchContext.size(parser.intValue());
|
||||
break;
|
||||
case "track_scores":
|
||||
case "trackScores":
|
||||
subSearchContext.trackScores(parser.booleanValue());
|
||||
break;
|
||||
case "version":
|
||||
subSearchContext.version(parser.booleanValue());
|
||||
break;
|
||||
case "explain":
|
||||
subSearchContext.explain(parser.booleanValue());
|
||||
break;
|
||||
case "fields":
|
||||
subSearchContext.fieldNames().add(parser.text());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown key for a " + token + " for nested query: [" + fieldName + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +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.ElasticsearchException;
|
||||
import org.elasticsearch.action.support.ToXContentToBytes;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
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.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class QueryInnerHits extends ToXContentToBytes implements Writeable<QueryInnerHits> {
|
||||
private final BytesReference queryInnerHitsSearchSource;
|
||||
|
||||
public QueryInnerHits(StreamInput input) throws IOException {
|
||||
queryInnerHitsSearchSource = input.readBytesReference();
|
||||
}
|
||||
|
||||
public QueryInnerHits(XContentParser parser) throws IOException {
|
||||
BytesStreamOutput out = new BytesStreamOutput();
|
||||
try (XContentBuilder builder = XContentFactory.cborBuilder(out)) {
|
||||
builder.copyCurrentStructure(parser);
|
||||
queryInnerHitsSearchSource = builder.bytes();
|
||||
}
|
||||
}
|
||||
|
||||
public QueryInnerHits() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public QueryInnerHits(String name, InnerHitsBuilder.InnerHit innerHit) {
|
||||
BytesStreamOutput out = new BytesStreamOutput();
|
||||
try (XContentBuilder builder = XContentFactory.cborBuilder(out)) {
|
||||
builder.startObject();
|
||||
if (name != null) {
|
||||
builder.field("name", name);
|
||||
}
|
||||
if (innerHit != null) {
|
||||
innerHit.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
}
|
||||
builder.endObject();
|
||||
this.queryInnerHitsSearchSource = builder.bytes();
|
||||
} catch (IOException e) {
|
||||
throw new ElasticsearchException("failed to build xcontent", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryInnerHits readFrom(StreamInput in) throws IOException {
|
||||
return new QueryInnerHits(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.field("inner_hits");
|
||||
try (XContentParser parser = XContentType.CBOR.xContent().createParser(queryInnerHitsSearchSource)) {
|
||||
builder.copyCurrentStructure(parser);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeBytesReference(queryInnerHitsSearchSource);
|
||||
}
|
||||
|
||||
public XContentParser getXcontentParser() throws IOException {
|
||||
return XContentType.CBOR.xContent().createParser(queryInnerHitsSearchSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
QueryInnerHits that = (QueryInnerHits) o;
|
||||
|
||||
return queryInnerHitsSearchSource.equals(that.queryInnerHitsSearchSource);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return queryInnerHitsSearchSource.hashCode();
|
||||
}
|
||||
}
|
|
@ -51,6 +51,7 @@ import org.elasticsearch.index.engine.Engine;
|
|||
import org.elasticsearch.index.fieldstats.FieldStatsProvider;
|
||||
import org.elasticsearch.index.query.QueryParseContext;
|
||||
import org.elasticsearch.index.query.QueryShardContext;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
import org.elasticsearch.index.search.stats.StatsGroupsParseElement;
|
||||
import org.elasticsearch.index.shard.IndexEventListener;
|
||||
import org.elasticsearch.index.shard.IndexShard;
|
||||
|
@ -76,6 +77,7 @@ 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;
|
||||
|
@ -754,20 +756,22 @@ public class SearchService extends AbstractLifecycleComponent<SearchService> imp
|
|||
}
|
||||
}
|
||||
if (source.innerHits() != null) {
|
||||
XContentParser innerHitsParser = null;
|
||||
try {
|
||||
innerHitsParser = XContentFactory.xContent(source.innerHits()).createParser(source.innerHits());
|
||||
innerHitsParser.nextToken();
|
||||
this.elementParsers.get("inner_hits").parse(innerHitsParser, context);
|
||||
} catch (Exception e) {
|
||||
String sSource = "_na_";
|
||||
for (Map.Entry<String, InnerHitBuilder> entry : source.innerHits().getInnerHitsBuilders().entrySet()) {
|
||||
try {
|
||||
sSource = source.toString();
|
||||
} catch (Throwable e1) {
|
||||
// ignore
|
||||
// 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();
|
||||
}
|
||||
XContentLocation location = innerHitsParser != null ? innerHitsParser.getTokenLocation() : null;
|
||||
throw new SearchParseException(context, "failed to parse suggest source [" + sSource + "]", location, e);
|
||||
}
|
||||
}
|
||||
if (source.scriptFields() != null) {
|
||||
|
|
|
@ -79,6 +79,7 @@ public class TopHitsAggregatorFactory extends AggregatorFactory<TopHitsAggregato
|
|||
public Aggregator createInternal(Aggregator parent, boolean collectsFromSingleBucket, List<PipelineAggregator> pipelineAggregators,
|
||||
Map<String, Object> metaData) throws IOException {
|
||||
SubSearchContext subSearchContext = new SubSearchContext(context.searchContext());
|
||||
subSearchContext.parsedQuery(context.searchContext().parsedQuery());
|
||||
subSearchContext.explain(explain);
|
||||
subSearchContext.version(version);
|
||||
subSearchContext.trackScores(trackScores);
|
||||
|
|
|
@ -46,7 +46,7 @@ import org.elasticsearch.search.aggregations.AggregatorBuilder;
|
|||
import org.elasticsearch.search.aggregations.AggregatorFactories;
|
||||
import org.elasticsearch.search.aggregations.AggregatorParsers;
|
||||
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregatorBuilder;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder;
|
||||
import org.elasticsearch.index.query.support.InnerHitsBuilder;
|
||||
import org.elasticsearch.search.fetch.source.FetchSourceContext;
|
||||
import org.elasticsearch.search.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
@ -162,7 +162,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
|
||||
private SuggestBuilder suggestBuilder;
|
||||
|
||||
private BytesReference innerHitsBuilder;
|
||||
private InnerHitsBuilder innerHitsBuilder;
|
||||
|
||||
private List<RescoreBuilder<?>> rescoreBuilders;
|
||||
|
||||
|
@ -457,22 +457,11 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
}
|
||||
|
||||
public SearchSourceBuilder innerHits(InnerHitsBuilder innerHitsBuilder) {
|
||||
try {
|
||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
builder.startObject();
|
||||
innerHitsBuilder.innerXContent(builder, EMPTY_PARAMS);
|
||||
builder.endObject();
|
||||
this.innerHitsBuilder = builder.bytes();
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.innerHitsBuilder = innerHitsBuilder;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bytes representing the inner hits builder for this request.
|
||||
*/
|
||||
public BytesReference innerHits() {
|
||||
public InnerHitsBuilder innerHits() {
|
||||
return innerHitsBuilder;
|
||||
}
|
||||
|
||||
|
@ -857,40 +846,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
} else if (context.parseFieldMatcher().match(currentFieldName, SCRIPT_FIELDS_FIELD)) {
|
||||
scriptFields = new ArrayList<>();
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
String scriptFieldName = parser.currentName();
|
||||
token = parser.nextToken();
|
||||
if (token == XContentParser.Token.START_OBJECT) {
|
||||
Script script = null;
|
||||
boolean ignoreFailure = false;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (token.isValue()) {
|
||||
if (context.parseFieldMatcher().match(currentFieldName, SCRIPT_FIELD)) {
|
||||
script = Script.parse(parser, context.parseFieldMatcher());
|
||||
} else if (context.parseFieldMatcher().match(currentFieldName, IGNORE_FAILURE_FIELD)) {
|
||||
ignoreFailure = parser.booleanValue();
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token + " in [" + currentFieldName
|
||||
+ "].", parser.getTokenLocation());
|
||||
}
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
if (context.parseFieldMatcher().match(currentFieldName, SCRIPT_FIELD)) {
|
||||
script = Script.parse(parser, context.parseFieldMatcher());
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token + " in [" + currentFieldName
|
||||
+ "].", parser.getTokenLocation());
|
||||
}
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token + " in [" + currentFieldName
|
||||
+ "].", parser.getTokenLocation());
|
||||
}
|
||||
}
|
||||
scriptFields.add(new ScriptField(scriptFieldName, script, ignoreFailure));
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Expected [" + XContentParser.Token.START_OBJECT + "] in ["
|
||||
+ currentFieldName + "] but found [" + token + "]", parser.getTokenLocation());
|
||||
}
|
||||
scriptFields.add(new ScriptField(parser, context));
|
||||
}
|
||||
} else if (context.parseFieldMatcher().match(currentFieldName, INDICES_BOOST_FIELD)) {
|
||||
indexBoost = new ObjectFloatHashMap<String>();
|
||||
|
@ -909,8 +865,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
} else if (context.parseFieldMatcher().match(currentFieldName, HIGHLIGHT_FIELD)) {
|
||||
highlightBuilder = HighlightBuilder.PROTOTYPE.fromXContent(context);
|
||||
} else if (context.parseFieldMatcher().match(currentFieldName, INNER_HITS_FIELD)) {
|
||||
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder().copyCurrentStructure(parser);
|
||||
innerHitsBuilder = xContentBuilder.bytes();
|
||||
innerHitsBuilder = InnerHitsBuilder.fromXContent(parser, context);
|
||||
} else if (context.parseFieldMatcher().match(currentFieldName, SUGGEST_FIELD)) {
|
||||
suggestBuilder = SuggestBuilder.fromXContent(context, suggesters);
|
||||
} else if (context.parseFieldMatcher().match(currentFieldName, SORT_FIELD)) {
|
||||
|
@ -1094,10 +1049,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
}
|
||||
|
||||
if (innerHitsBuilder != null) {
|
||||
builder.field(INNER_HITS_FIELD.getPreferredName());
|
||||
XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(innerHitsBuilder);
|
||||
parser.nextToken();
|
||||
builder.copyCurrentStructure(parser);
|
||||
builder.field(INNER_HITS_FIELD.getPreferredName(), innerHitsBuilder, params);
|
||||
}
|
||||
|
||||
if (suggestBuilder != null) {
|
||||
|
@ -1126,7 +1078,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
|
||||
public static class ScriptField implements Writeable<ScriptField>, ToXContent {
|
||||
|
||||
public static final ScriptField PROTOTYPE = new ScriptField(null, null);
|
||||
public static final ScriptField PROTOTYPE = new ScriptField((String) null, (Script) null);
|
||||
|
||||
private final boolean ignoreFailure;
|
||||
private final String fieldName;
|
||||
|
@ -1142,6 +1094,48 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
this.ignoreFailure = ignoreFailure;
|
||||
}
|
||||
|
||||
public ScriptField(XContentParser parser, QueryParseContext context) throws IOException {
|
||||
boolean ignoreFailure = false;
|
||||
String scriptFieldName = parser.currentName();
|
||||
Script script = null;
|
||||
|
||||
XContentParser.Token token;
|
||||
token = parser.nextToken();
|
||||
if (token == XContentParser.Token.START_OBJECT) {
|
||||
String currentFieldName = null;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (token.isValue()) {
|
||||
if (context.parseFieldMatcher().match(currentFieldName, SCRIPT_FIELD)) {
|
||||
script = Script.parse(parser, context.parseFieldMatcher());
|
||||
} else if (context.parseFieldMatcher().match(currentFieldName, IGNORE_FAILURE_FIELD)) {
|
||||
ignoreFailure = parser.booleanValue();
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token + " in [" + currentFieldName
|
||||
+ "].", parser.getTokenLocation());
|
||||
}
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
if (context.parseFieldMatcher().match(currentFieldName, SCRIPT_FIELD)) {
|
||||
script = Script.parse(parser, context.parseFieldMatcher());
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token + " in [" + currentFieldName
|
||||
+ "].", parser.getTokenLocation());
|
||||
}
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token + " in [" + currentFieldName
|
||||
+ "].", parser.getTokenLocation());
|
||||
}
|
||||
}
|
||||
this.ignoreFailure = ignoreFailure;
|
||||
this.fieldName = scriptFieldName;
|
||||
this.script = script;
|
||||
} else {
|
||||
throw new ParsingException(parser.getTokenLocation(), "Expected [" + XContentParser.Token.START_OBJECT + "] in ["
|
||||
+ parser.currentName() + "] but found [" + token + "]", parser.getTokenLocation());
|
||||
}
|
||||
}
|
||||
|
||||
public String fieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
@ -1235,7 +1229,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
builder.indexBoost = indexBoost;
|
||||
}
|
||||
if (in.readBoolean()) {
|
||||
builder.innerHitsBuilder = in.readBytesReference();
|
||||
builder.innerHitsBuilder = InnerHitsBuilder.PROTO.readFrom(in);
|
||||
}
|
||||
if (in.readBoolean()) {
|
||||
builder.minScore = in.readFloat();
|
||||
|
@ -1343,7 +1337,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
boolean hasInnerHitsBuilder = innerHitsBuilder != null;
|
||||
out.writeBoolean(hasInnerHitsBuilder);
|
||||
if (hasInnerHitsBuilder) {
|
||||
out.writeBytesReference(innerHitsBuilder);
|
||||
innerHitsBuilder.writeTo(out);
|
||||
}
|
||||
boolean hasMinScore = minScore != null;
|
||||
out.writeBoolean(hasMinScore);
|
||||
|
|
|
@ -1,305 +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.search.fetch.innerhits;
|
||||
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class InnerHitsBuilder implements ToXContent {
|
||||
|
||||
private final Map<String, InnerHitsHolder> innerHits = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject("inner_hits");
|
||||
innerXContent(builder, params);
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
public void innerXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
for (Map.Entry<String, InnerHitsHolder> entry : innerHits.entrySet()) {
|
||||
builder.startObject(entry.getKey());
|
||||
entry.getValue().toXContent(builder, params);
|
||||
builder.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For nested inner hits the path to collect child nested docs for.
|
||||
* @param name the name / key of the inner hits in the response
|
||||
* @param path the path into the nested to collect inner hits for
|
||||
* @param innerHit the inner hits definition
|
||||
*/
|
||||
public void addNestedInnerHits(String name, String path, InnerHit innerHit) {
|
||||
if (innerHits.containsKey(name)) {
|
||||
throw new IllegalArgumentException("inner hits for name: [" + name +"] is already registered");
|
||||
}
|
||||
innerHits.put(name, new NestedInnerHitsHolder(path, innerHit));
|
||||
}
|
||||
|
||||
/**
|
||||
* For parent/child inner hits the type to collect inner hits for.
|
||||
* @param name the name / key of the inner hits in the response
|
||||
* @param type the document type to collect inner hits for
|
||||
* @param innerHit the inner hits definition
|
||||
*/
|
||||
public void addParentChildInnerHits(String name, String type, InnerHit innerHit) {
|
||||
innerHits.put(name, new ParentChildInnerHitsHolder(type, innerHit));
|
||||
}
|
||||
|
||||
private static class InnerHitsHolder implements ToXContent{
|
||||
private final InnerHit hits;
|
||||
|
||||
private InnerHitsHolder(InnerHit hits) {
|
||||
this.hits = hits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return hits.toXContent(builder, params);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ParentChildInnerHitsHolder extends InnerHitsHolder {
|
||||
|
||||
private final String type;
|
||||
|
||||
private ParentChildInnerHitsHolder(String type, InnerHit hits) {
|
||||
super(hits);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject("type").startObject(type);
|
||||
super.toXContent(builder, params);
|
||||
return builder.endObject().endObject();
|
||||
}
|
||||
}
|
||||
|
||||
private static class NestedInnerHitsHolder extends InnerHitsHolder {
|
||||
|
||||
private final String path;
|
||||
|
||||
private NestedInnerHitsHolder(String path, InnerHit hits) {
|
||||
super(hits);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject("path").startObject(path);
|
||||
super.toXContent(builder, params);
|
||||
return builder.endObject().endObject();
|
||||
}
|
||||
}
|
||||
|
||||
public static class InnerHit implements ToXContent {
|
||||
|
||||
private SearchSourceBuilder sourceBuilder;
|
||||
private String path;
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* The index to start to return hits from. Defaults to <tt>0</tt>.
|
||||
*/
|
||||
public InnerHit setFrom(int from) {
|
||||
sourceBuilder().from(from);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of search hits to return. Defaults to <tt>10</tt>.
|
||||
*/
|
||||
public InnerHit setSize(int size) {
|
||||
sourceBuilder().size(size);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies when sorting, and controls if scores will be tracked as well. Defaults to
|
||||
* <tt>false</tt>.
|
||||
*/
|
||||
public InnerHit setTrackScores(boolean trackScores) {
|
||||
sourceBuilder().trackScores(trackScores);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should each {@link org.elasticsearch.search.SearchHit} be returned with an
|
||||
* explanation of the hit (ranking).
|
||||
*/
|
||||
public InnerHit setExplain(boolean explain) {
|
||||
sourceBuilder().explain(explain);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should each {@link org.elasticsearch.search.SearchHit} be returned with its
|
||||
* version.
|
||||
*/
|
||||
public InnerHit setVersion(boolean version) {
|
||||
sourceBuilder().version(version);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a stored field to be loaded and returned with the inner hit.
|
||||
*/
|
||||
public InnerHit field(String name) {
|
||||
sourceBuilder().field(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets no fields to be loaded, resulting in only id and type to be returned per field.
|
||||
*/
|
||||
public InnerHit setNoFields() {
|
||||
sourceBuilder().noFields();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the response should contain the stored _source for every hit
|
||||
*/
|
||||
public InnerHit setFetchSource(boolean fetch) {
|
||||
sourceBuilder().fetchSource(fetch);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that _source should be returned with every hit, with an "include" and/or "exclude" set which can include simple wildcard
|
||||
* elements.
|
||||
*
|
||||
* @param include An optional include (optionally wildcarded) pattern to filter the returned _source
|
||||
* @param exclude An optional exclude (optionally wildcarded) pattern to filter the returned _source
|
||||
*/
|
||||
public InnerHit setFetchSource(@Nullable String include, @Nullable String exclude) {
|
||||
sourceBuilder().fetchSource(include, exclude);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that _source should be returned with every hit, with an "include" and/or "exclude" set which can include simple wildcard
|
||||
* elements.
|
||||
*
|
||||
* @param includes An optional list of include (optionally wildcarded) pattern to filter the returned _source
|
||||
* @param excludes An optional list of exclude (optionally wildcarded) pattern to filter the returned _source
|
||||
*/
|
||||
public InnerHit setFetchSource(@Nullable String[] includes, @Nullable String[] excludes) {
|
||||
sourceBuilder().fetchSource(includes, excludes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field data based field to load and return. The field does not have to be stored,
|
||||
* but its recommended to use non analyzed or numeric fields.
|
||||
*
|
||||
* @param name The field to get from the field data cache
|
||||
*/
|
||||
public InnerHit addFieldDataField(String name) {
|
||||
sourceBuilder().fieldDataField(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a script based field to load and return. The field does not have to be stored,
|
||||
* but its recommended to use non analyzed or numeric fields.
|
||||
*
|
||||
* @param name The name that will represent this value in the return hit
|
||||
* @param script The script to use
|
||||
*/
|
||||
public InnerHit addScriptField(String name, Script script) {
|
||||
sourceBuilder().scriptField(name, script);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a sort against the given field name and the sort ordering.
|
||||
*
|
||||
* @param field The name of the field
|
||||
* @param order The sort ordering
|
||||
*/
|
||||
public InnerHit addSort(String field, SortOrder order) {
|
||||
sourceBuilder().sort(field, order);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a generic sort builder.
|
||||
*
|
||||
* @see org.elasticsearch.search.sort.SortBuilders
|
||||
*/
|
||||
public InnerHit addSort(SortBuilder sort) {
|
||||
sourceBuilder().sort(sort);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HighlightBuilder highlighter() {
|
||||
return sourceBuilder().highlighter();
|
||||
}
|
||||
|
||||
public InnerHit highlighter(HighlightBuilder highlightBuilder) {
|
||||
sourceBuilder().highlighter(highlightBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected SearchSourceBuilder sourceBuilder() {
|
||||
if (sourceBuilder == null) {
|
||||
sourceBuilder = new SearchSourceBuilder();
|
||||
}
|
||||
return sourceBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the query to run for collecting the inner hits.
|
||||
*/
|
||||
public InnerHit setQuery(QueryBuilder query) {
|
||||
sourceBuilder().query(query);
|
||||
return this;
|
||||
}
|
||||
|
||||
public InnerHit innerHits(InnerHitsBuilder innerHitsBuilder) {
|
||||
sourceBuilder().innerHits(innerHitsBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
if (sourceBuilder != null) {
|
||||
sourceBuilder.innerToXContent(builder, params);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,16 +48,16 @@ import org.elasticsearch.index.mapper.Uid;
|
|||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
|
||||
import org.elasticsearch.index.mapper.object.ObjectMapper;
|
||||
import org.elasticsearch.index.query.ParsedQuery;
|
||||
import org.elasticsearch.search.SearchHitField;
|
||||
import org.elasticsearch.search.fetch.FetchSubPhase;
|
||||
import org.elasticsearch.search.internal.FilteredSearchContext;
|
||||
import org.elasticsearch.search.internal.InternalSearchHit;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.internal.SubSearchContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -70,59 +70,46 @@ public final class InnerHitsContext {
|
|||
}
|
||||
|
||||
public InnerHitsContext(Map<String, BaseInnerHits> innerHits) {
|
||||
this.innerHits = innerHits;
|
||||
this.innerHits = Objects.requireNonNull(innerHits);
|
||||
}
|
||||
|
||||
public Map<String, BaseInnerHits> getInnerHits() {
|
||||
return innerHits;
|
||||
}
|
||||
|
||||
public void addInnerHitDefinition(String name, BaseInnerHits innerHit) {
|
||||
if (innerHits.containsKey(name)) {
|
||||
throw new IllegalArgumentException("inner_hit definition with the name [" + name + "] already exists. Use a different inner_hit name");
|
||||
public void addInnerHitDefinition(BaseInnerHits innerHit) {
|
||||
if (innerHits.containsKey(innerHit.getName())) {
|
||||
throw new IllegalArgumentException("inner_hit definition with the name [" + innerHit.getName() +
|
||||
"] already exists. Use a different inner_hit name");
|
||||
}
|
||||
|
||||
innerHits.put(name, innerHit);
|
||||
innerHits.put(innerHit.getName(), innerHit);
|
||||
}
|
||||
|
||||
public void addInnerHitDefinitions(Map<String, BaseInnerHits> innerHits) {
|
||||
for (Map.Entry<String, BaseInnerHits> entry : innerHits.entrySet()) {
|
||||
addInnerHitDefinition(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
public static abstract class BaseInnerHits extends SubSearchContext {
|
||||
|
||||
public static abstract class BaseInnerHits extends FilteredSearchContext {
|
||||
private final String name;
|
||||
private InnerHitsContext childInnerHits;
|
||||
|
||||
protected final ParsedQuery query;
|
||||
private final InnerHitsContext childInnerHits;
|
||||
|
||||
protected BaseInnerHits(SearchContext context, ParsedQuery query, Map<String, BaseInnerHits> childInnerHits) {
|
||||
protected BaseInnerHits(String name, SearchContext context) {
|
||||
super(context);
|
||||
this.query = query;
|
||||
if (childInnerHits != null && !childInnerHits.isEmpty()) {
|
||||
this.childInnerHits = new InnerHitsContext(childInnerHits);
|
||||
} else {
|
||||
this.childInnerHits = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query query() {
|
||||
return query.query();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsedQuery parsedQuery() {
|
||||
return query;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public abstract TopDocs topDocs(SearchContext context, FetchSubPhase.HitContext hitContext) throws IOException;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InnerHitsContext innerHits() {
|
||||
return childInnerHits;
|
||||
}
|
||||
|
||||
public void setChildInnerHits(Map<String, InnerHitsContext.BaseInnerHits> childInnerHits) {
|
||||
this.childInnerHits = new InnerHitsContext(childInnerHits);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class NestedInnerHits extends BaseInnerHits {
|
||||
|
@ -130,8 +117,8 @@ public final class InnerHitsContext {
|
|||
private final ObjectMapper parentObjectMapper;
|
||||
private final ObjectMapper childObjectMapper;
|
||||
|
||||
public NestedInnerHits(SearchContext context, ParsedQuery query, Map<String, BaseInnerHits> childInnerHits, ObjectMapper parentObjectMapper, ObjectMapper childObjectMapper) {
|
||||
super(context, query, childInnerHits);
|
||||
public NestedInnerHits(String name, SearchContext context, ObjectMapper parentObjectMapper, ObjectMapper childObjectMapper) {
|
||||
super(name != null ? name : childObjectMapper.fullPath(), context);
|
||||
this.parentObjectMapper = parentObjectMapper;
|
||||
this.childObjectMapper = childObjectMapper;
|
||||
}
|
||||
|
@ -146,7 +133,7 @@ public final class InnerHitsContext {
|
|||
}
|
||||
BitSetProducer parentFilter = context.bitsetFilterCache().getBitSetProducer(rawParentFilter);
|
||||
Query childFilter = childObjectMapper.nestedTypeFilter();
|
||||
Query q = Queries.filtered(query.query(), new NestedChildrenQuery(parentFilter, childFilter, hitContext));
|
||||
Query q = Queries.filtered(query(), new NestedChildrenQuery(parentFilter, childFilter, hitContext));
|
||||
|
||||
if (size() == 0) {
|
||||
return new TopDocs(context.searcher().count(q), Lucene.EMPTY_SCORE_DOCS, 0);
|
||||
|
@ -292,8 +279,8 @@ public final class InnerHitsContext {
|
|||
private final MapperService mapperService;
|
||||
private final DocumentMapper documentMapper;
|
||||
|
||||
public ParentChildInnerHits(SearchContext context, ParsedQuery query, Map<String, BaseInnerHits> childInnerHits, MapperService mapperService, DocumentMapper documentMapper) {
|
||||
super(context, query, childInnerHits);
|
||||
public ParentChildInnerHits(String name, SearchContext context, MapperService mapperService, DocumentMapper documentMapper) {
|
||||
super(name != null ? name : documentMapper.type(), context);
|
||||
this.mapperService = mapperService;
|
||||
this.documentMapper = documentMapper;
|
||||
}
|
||||
|
@ -317,7 +304,7 @@ public final class InnerHitsContext {
|
|||
}
|
||||
|
||||
BooleanQuery q = new BooleanQuery.Builder()
|
||||
.add(query.query(), Occur.MUST)
|
||||
.add(query(), Occur.MUST)
|
||||
// Only include docs that have the current hit as parent
|
||||
.add(hitQuery, Occur.FILTER)
|
||||
// Only include docs that have this inner hits type
|
||||
|
|
|
@ -28,36 +28,30 @@ import org.elasticsearch.search.SearchParseElement;
|
|||
import org.elasticsearch.search.fetch.FetchPhase;
|
||||
import org.elasticsearch.search.fetch.FetchSearchResult;
|
||||
import org.elasticsearch.search.fetch.FetchSubPhase;
|
||||
import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsParseElement;
|
||||
import org.elasticsearch.search.fetch.script.ScriptFieldsParseElement;
|
||||
import org.elasticsearch.search.fetch.source.FetchSourceParseElement;
|
||||
import org.elasticsearch.search.highlight.HighlighterParseElement;
|
||||
import org.elasticsearch.search.internal.InternalSearchHit;
|
||||
import org.elasticsearch.search.internal.InternalSearchHits;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class InnerHitsFetchSubPhase implements FetchSubPhase {
|
||||
private final Map<String, ? extends SearchParseElement> parseElements;
|
||||
|
||||
private FetchPhase fetchPhase;
|
||||
|
||||
@Inject
|
||||
public InnerHitsFetchSubPhase(FetchSourceParseElement sourceParseElement, HighlighterParseElement highlighterParseElement, FieldDataFieldsParseElement fieldDataFieldsParseElement, ScriptFieldsParseElement scriptFieldsParseElement) {
|
||||
parseElements = singletonMap("inner_hits", new InnerHitsParseElement(sourceParseElement, highlighterParseElement,
|
||||
fieldDataFieldsParseElement, scriptFieldsParseElement));
|
||||
public InnerHitsFetchSubPhase() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ? extends SearchParseElement> parseElements() {
|
||||
return parseElements;
|
||||
// SearchParse elements needed because everything is parsed by InnerHitBuilder and eventually put
|
||||
// into the search context.
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,221 +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.search.fetch.innerhits;
|
||||
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||
import org.elasticsearch.index.mapper.object.ObjectMapper;
|
||||
import org.elasticsearch.index.query.ParsedQuery;
|
||||
import org.elasticsearch.index.query.QueryShardContext;
|
||||
import org.elasticsearch.search.SearchParseElement;
|
||||
import org.elasticsearch.search.fetch.fielddata.FieldDataFieldsParseElement;
|
||||
import org.elasticsearch.search.fetch.script.ScriptFieldsParseElement;
|
||||
import org.elasticsearch.search.fetch.source.FetchSourceParseElement;
|
||||
import org.elasticsearch.search.highlight.HighlighterParseElement;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.internal.SubSearchContext;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.index.query.support.InnerHitsQueryParserHelper.parseCommonInnerHitOptions;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class InnerHitsParseElement implements SearchParseElement {
|
||||
|
||||
private final FetchSourceParseElement sourceParseElement;
|
||||
private final HighlighterParseElement highlighterParseElement;
|
||||
private final FieldDataFieldsParseElement fieldDataFieldsParseElement;
|
||||
private final ScriptFieldsParseElement scriptFieldsParseElement;
|
||||
|
||||
public InnerHitsParseElement(FetchSourceParseElement sourceParseElement, HighlighterParseElement highlighterParseElement, FieldDataFieldsParseElement fieldDataFieldsParseElement, ScriptFieldsParseElement scriptFieldsParseElement) {
|
||||
this.sourceParseElement = sourceParseElement;
|
||||
this.highlighterParseElement = highlighterParseElement;
|
||||
this.fieldDataFieldsParseElement = fieldDataFieldsParseElement;
|
||||
this.scriptFieldsParseElement = scriptFieldsParseElement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(XContentParser parser, SearchContext searchContext) throws Exception {
|
||||
QueryShardContext context = searchContext.getQueryShardContext();
|
||||
context.reset(parser);
|
||||
Map<String, InnerHitsContext.BaseInnerHits> topLevelInnerHits = parseInnerHits(parser, context, searchContext);
|
||||
if (topLevelInnerHits != null) {
|
||||
InnerHitsContext innerHitsContext = searchContext.innerHits();
|
||||
innerHitsContext.addInnerHitDefinitions(topLevelInnerHits);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, InnerHitsContext.BaseInnerHits> parseInnerHits(XContentParser parser, QueryShardContext context, SearchContext searchContext) throws Exception {
|
||||
XContentParser.Token token;
|
||||
Map<String, InnerHitsContext.BaseInnerHits> innerHitsMap = null;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token != XContentParser.Token.FIELD_NAME) {
|
||||
throw new IllegalArgumentException("Unexpected token " + token + " in [inner_hits]: inner_hit definitions must start with the name of the inner_hit.");
|
||||
}
|
||||
final String innerHitName = parser.currentName();
|
||||
token = parser.nextToken();
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new IllegalArgumentException("Inner hit definition for [" + innerHitName + " starts with a [" + token + "], expected a [" + XContentParser.Token.START_OBJECT + "].");
|
||||
}
|
||||
InnerHitsContext.BaseInnerHits innerHits = parseInnerHit(parser, context, searchContext, innerHitName);
|
||||
if (innerHitsMap == null) {
|
||||
innerHitsMap = new HashMap<>();
|
||||
}
|
||||
innerHitsMap.put(innerHitName, innerHits);
|
||||
}
|
||||
return innerHitsMap;
|
||||
}
|
||||
|
||||
private InnerHitsContext.BaseInnerHits parseInnerHit(XContentParser parser, QueryShardContext context, SearchContext searchContext, String innerHitName) throws Exception {
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
if (token != XContentParser.Token.FIELD_NAME) {
|
||||
throw new IllegalArgumentException("Unexpected token " + token + " inside inner hit definition. Either specify [path] or [type] object");
|
||||
}
|
||||
String fieldName = parser.currentName();
|
||||
token = parser.nextToken();
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new IllegalArgumentException("Inner hit definition for [" + innerHitName + " starts with a [" + token + "], expected a [" + XContentParser.Token.START_OBJECT + "].");
|
||||
}
|
||||
|
||||
String nestedPath = null;
|
||||
String type = null;
|
||||
switch (fieldName) {
|
||||
case "path":
|
||||
nestedPath = parser.currentName();
|
||||
break;
|
||||
case "type":
|
||||
type = parser.currentName();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Either path or type object must be defined");
|
||||
}
|
||||
token = parser.nextToken();
|
||||
if (token != XContentParser.Token.FIELD_NAME) {
|
||||
throw new IllegalArgumentException("Unexpected token " + token + " inside inner hit definition. Either specify [path] or [type] object");
|
||||
}
|
||||
fieldName = parser.currentName();
|
||||
token = parser.nextToken();
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new IllegalArgumentException("Inner hit definition for [" + innerHitName + " starts with a [" + token + "], expected a [" + XContentParser.Token.START_OBJECT + "].");
|
||||
}
|
||||
|
||||
final InnerHitsContext.BaseInnerHits innerHits;
|
||||
if (nestedPath != null) {
|
||||
innerHits = parseNested(parser, context, searchContext, fieldName);
|
||||
} else if (type != null) {
|
||||
innerHits = parseParentChild(parser, context, searchContext, fieldName);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Either [path] or [type] must be defined");
|
||||
}
|
||||
|
||||
// Completely consume all json objects:
|
||||
token = parser.nextToken();
|
||||
if (token != XContentParser.Token.END_OBJECT) {
|
||||
throw new IllegalArgumentException("Expected [" + XContentParser.Token.END_OBJECT + "] token, but got a [" + token + "] token.");
|
||||
}
|
||||
token = parser.nextToken();
|
||||
if (token != XContentParser.Token.END_OBJECT) {
|
||||
throw new IllegalArgumentException("Expected [" + XContentParser.Token.END_OBJECT + "] token, but got a [" + token + "] token.");
|
||||
}
|
||||
|
||||
return innerHits;
|
||||
}
|
||||
|
||||
private InnerHitsContext.ParentChildInnerHits parseParentChild(XContentParser parser, QueryShardContext context, SearchContext searchContext, String type) throws Exception {
|
||||
ParseResult parseResult = parseSubSearchContext(searchContext, context, parser);
|
||||
DocumentMapper documentMapper = searchContext.mapperService().documentMapper(type);
|
||||
if (documentMapper == null) {
|
||||
throw new IllegalArgumentException("type [" + type + "] doesn't exist");
|
||||
}
|
||||
return new InnerHitsContext.ParentChildInnerHits(parseResult.context(), parseResult.query(), parseResult.childInnerHits(), context.getMapperService(), documentMapper);
|
||||
}
|
||||
|
||||
private InnerHitsContext.NestedInnerHits parseNested(XContentParser parser, QueryShardContext context, SearchContext searchContext, String nestedPath) throws Exception {
|
||||
ObjectMapper objectMapper = searchContext.getObjectMapper(nestedPath);
|
||||
if (objectMapper == null) {
|
||||
throw new IllegalArgumentException("path [" + nestedPath +"] doesn't exist");
|
||||
}
|
||||
if (objectMapper.nested().isNested() == false) {
|
||||
throw new IllegalArgumentException("path [" + nestedPath +"] isn't nested");
|
||||
}
|
||||
ObjectMapper parentObjectMapper = context.nestedScope().nextLevel(objectMapper);
|
||||
ParseResult parseResult = parseSubSearchContext(searchContext, context, parser);
|
||||
context.nestedScope().previousLevel();
|
||||
|
||||
return new InnerHitsContext.NestedInnerHits(parseResult.context(), parseResult.query(), parseResult.childInnerHits(), parentObjectMapper, objectMapper);
|
||||
}
|
||||
|
||||
private ParseResult parseSubSearchContext(SearchContext searchContext, QueryShardContext context, XContentParser parser) throws Exception {
|
||||
ParsedQuery query = null;
|
||||
Map<String, InnerHitsContext.BaseInnerHits> childInnerHits = null;
|
||||
SubSearchContext subSearchContext = new SubSearchContext(searchContext);
|
||||
String fieldName = null;
|
||||
XContentParser.Token token;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
fieldName = parser.currentName();
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
if ("query".equals(fieldName)) {
|
||||
Query q = context.parseInnerQuery();
|
||||
query = new ParsedQuery(q, context.copyNamedQueries());
|
||||
} else if ("inner_hits".equals(fieldName)) {
|
||||
childInnerHits = parseInnerHits(parser, context, searchContext);
|
||||
} else {
|
||||
parseCommonInnerHitOptions(parser, token, fieldName, subSearchContext, sourceParseElement, highlighterParseElement, scriptFieldsParseElement, fieldDataFieldsParseElement);
|
||||
}
|
||||
} else {
|
||||
parseCommonInnerHitOptions(parser, token, fieldName, subSearchContext, sourceParseElement, highlighterParseElement, scriptFieldsParseElement, fieldDataFieldsParseElement);
|
||||
}
|
||||
}
|
||||
|
||||
if (query == null) {
|
||||
query = ParsedQuery.parsedMatchAllQuery();
|
||||
}
|
||||
return new ParseResult(subSearchContext, query, childInnerHits);
|
||||
}
|
||||
|
||||
private static final class ParseResult {
|
||||
|
||||
private final SubSearchContext context;
|
||||
private final ParsedQuery query;
|
||||
private final Map<String, InnerHitsContext.BaseInnerHits> childInnerHits;
|
||||
|
||||
private ParseResult(SubSearchContext context, ParsedQuery query, Map<String, InnerHitsContext.BaseInnerHits> childInnerHits) {
|
||||
this.context = context;
|
||||
this.query = query;
|
||||
this.childInnerHits = childInnerHits;
|
||||
}
|
||||
|
||||
public SubSearchContext context() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public ParsedQuery query() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public Map<String, InnerHitsContext.BaseInnerHits> childInnerHits() {
|
||||
return childInnerHits;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +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.search.fetch.innerhits;
|
||||
|
||||
import org.elasticsearch.search.internal.SubSearchContext;
|
||||
|
||||
public class InnerHitsSubSearchContext {
|
||||
private final String name;
|
||||
private final SubSearchContext subSearchContext;
|
||||
|
||||
public InnerHitsSubSearchContext(String name, SubSearchContext subSearchContext) {
|
||||
this.name = name;
|
||||
this.subSearchContext = subSearchContext;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public SubSearchContext getSubSearchContext() {
|
||||
return subSearchContext;
|
||||
}
|
||||
}
|
|
@ -49,6 +49,8 @@ public class SubSearchContext extends FilteredSearchContext {
|
|||
private int from;
|
||||
private int size = DEFAULT_SIZE;
|
||||
private Sort sort;
|
||||
private ParsedQuery parsedQuery;
|
||||
private Query query;
|
||||
|
||||
private final FetchSearchResult fetchSearchResult;
|
||||
private final QuerySearchResult querySearchResult;
|
||||
|
@ -185,6 +187,25 @@ public class SubSearchContext extends FilteredSearchContext {
|
|||
return sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchContext parsedQuery(ParsedQuery parsedQuery) {
|
||||
this.parsedQuery = parsedQuery;
|
||||
if (parsedQuery != null) {
|
||||
this.query = parsedQuery.query();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsedQuery parsedQuery() {
|
||||
return parsedQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query query() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchContext trackScores(boolean trackScores) {
|
||||
this.trackScores = trackScores;
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.apache.lucene.search.ConstantScoreQuery;
|
|||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.TermQuery;
|
||||
import org.apache.lucene.search.join.ScoreMode;
|
||||
import org.apache.lucene.search.similarities.DFISimilarity;
|
||||
import org.apache.lucene.search.similarities.PerFieldSimilarityWrapper;
|
||||
import org.apache.lucene.search.similarities.Similarity;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
|
@ -42,18 +41,17 @@ 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.QueryInnerHits;
|
||||
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.InnerHitsBuilder;
|
||||
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 org.elasticsearch.test.TestSearchContext;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
|
@ -118,11 +116,13 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
|
|||
protected HasChildQueryBuilder doCreateTestQueryBuilder() {
|
||||
int min = randomIntBetween(0, Integer.MAX_VALUE / 2);
|
||||
int max = randomIntBetween(min, Integer.MAX_VALUE);
|
||||
InnerHitsBuilder.InnerHit innerHit = new InnerHitsBuilder.InnerHit().setSize(100).addSort(STRING_FIELD_NAME_2, SortOrder.ASC);
|
||||
return new HasChildQueryBuilder(CHILD_TYPE,
|
||||
RandomQueryBuilder.createQuery(random()), max, min,
|
||||
RandomPicks.randomFrom(random(), ScoreMode.values()),
|
||||
randomBoolean() ? null : new QueryInnerHits("inner_hits_name", innerHit));
|
||||
randomBoolean() ? null : new InnerHitBuilder()
|
||||
.setName(randomAsciiOfLengthBetween(1, 10))
|
||||
.setSize(randomIntBetween(0, 100))
|
||||
.addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -142,9 +142,10 @@ public class HasChildQueryBuilderTests extends AbstractQueryTestCase<HasChildQue
|
|||
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);
|
||||
assertTrue(SearchContext.current().innerHits().getInnerHits().containsKey(queryBuilder.innerHit().getName()));
|
||||
InnerHitsContext.BaseInnerHits innerHits =
|
||||
SearchContext.current().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 {
|
||||
|
@ -217,13 +218,29 @@ 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" +
|
||||
" \"version\" : false,\n" +
|
||||
" \"explain\" : false,\n" +
|
||||
" \"track_scores\" : false,\n" +
|
||||
" \"sort\" : [ {\n" +
|
||||
" \"mapped_string\" : {\n" +
|
||||
" \"order\" : \"asc\"\n" +
|
||||
" }\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" +
|
||||
"}";
|
||||
|
@ -236,7 +253,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 QueryInnerHits("inner_hits_name", new InnerHitsBuilder.InnerHit().setSize(100).addSort("mapped_string", SortOrder.ASC)));
|
||||
assertEquals(query, queryBuilder.innerHit(), new InnerHitBuilder().setParentChildType("child")
|
||||
.setName("inner_hits_name")
|
||||
.setSize(100)
|
||||
.addSort(new FieldSortBuilder("mapped_string").order(SortOrder.ASC))
|
||||
.setQuery(queryBuilder.query()));
|
||||
|
||||
}
|
||||
public void testToQueryInnerQueryType() throws IOException {
|
||||
|
|
|
@ -33,11 +33,11 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.index.fielddata.IndexFieldDataService;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.index.query.support.QueryInnerHits;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
import org.elasticsearch.script.Script.ScriptParseException;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder;
|
||||
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 org.elasticsearch.test.TestSearchContext;
|
||||
import org.junit.BeforeClass;
|
||||
|
@ -104,10 +104,12 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQ
|
|||
*/
|
||||
@Override
|
||||
protected HasParentQueryBuilder doCreateTestQueryBuilder() {
|
||||
InnerHitsBuilder.InnerHit innerHit = new InnerHitsBuilder.InnerHit().setSize(100).addSort(STRING_FIELD_NAME_2, SortOrder.ASC);
|
||||
return new HasParentQueryBuilder(PARENT_TYPE,
|
||||
RandomQueryBuilder.createQuery(random()),randomBoolean(),
|
||||
randomBoolean() ? null : new QueryInnerHits("inner_hits_name", innerHit));
|
||||
randomBoolean() ? null : new InnerHitBuilder()
|
||||
.setName(randomAsciiOfLengthBetween(1, 10))
|
||||
.setSize(randomIntBetween(0, 100))
|
||||
.addSort(new FieldSortBuilder(STRING_FIELD_NAME_2).order(SortOrder.ASC)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -125,9 +127,10 @@ public class HasParentQueryBuilderTests extends AbstractQueryTestCase<HasParentQ
|
|||
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);
|
||||
assertTrue(SearchContext.current().innerHits().getInnerHits().containsKey(queryBuilder.innerHit().getName()));
|
||||
InnerHitsContext.BaseInnerHits innerHits = SearchContext.current().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 {
|
||||
|
|
|
@ -28,10 +28,10 @@ import org.elasticsearch.common.compress.CompressedXContent;
|
|||
import org.elasticsearch.index.fielddata.IndexFieldDataService;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.sort.FieldSortBuilder;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.elasticsearch.test.TestSearchContext;
|
||||
import org.elasticsearch.index.query.support.QueryInnerHits;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -82,10 +82,12 @@ public class NestedQueryBuilderTests extends AbstractQueryTestCase<NestedQueryBu
|
|||
*/
|
||||
@Override
|
||||
protected NestedQueryBuilder doCreateTestQueryBuilder() {
|
||||
InnerHitsBuilder.InnerHit innerHit = new InnerHitsBuilder.InnerHit().setSize(100).addSort(STRING_FIELD_NAME, SortOrder.ASC);
|
||||
return new NestedQueryBuilder("nested1", RandomQueryBuilder.createQuery(random()),
|
||||
RandomPicks.randomFrom(random(), ScoreMode.values()),
|
||||
SearchContext.current() == null ? null : new QueryInnerHits("inner_hits_name", innerHit));
|
||||
SearchContext.current() == null ? null : new InnerHitBuilder()
|
||||
.setName(randomAsciiOfLengthBetween(1, 10))
|
||||
.setSize(randomIntBetween(0, 100))
|
||||
.addSort(new FieldSortBuilder(STRING_FIELD_NAME).order(SortOrder.ASC)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* 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.AbstractQueryTestCase;
|
||||
import org.elasticsearch.index.query.MatchQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryParseContext;
|
||||
import org.elasticsearch.indices.query.IndicesQueriesRegistry;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.search.SearchModule;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.fetch.source.FetchSourceContext;
|
||||
import org.elasticsearch.search.highlight.HighlightBuilderTests;
|
||||
import org.elasticsearch.search.sort.FieldSortBuilder;
|
||||
import org.elasticsearch.search.sort.ScriptSortBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilders;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
|
||||
public class InnerHitBuilderTests 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).buildQueryParserRegistry();
|
||||
}
|
||||
|
||||
@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++) {
|
||||
InnerHitBuilder original = randomInnerHits();
|
||||
InnerHitBuilder deserialized = serializedCopy(original);
|
||||
assertEquals(deserialized, original);
|
||||
assertEquals(deserialized.hashCode(), original.hashCode());
|
||||
assertNotSame(deserialized, original);
|
||||
}
|
||||
}
|
||||
|
||||
public void testFromAndToXContent() throws Exception {
|
||||
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
|
||||
context.parseFieldMatcher(new ParseFieldMatcher(Settings.EMPTY));
|
||||
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
|
||||
InnerHitBuilder innerHit = randomInnerHits();
|
||||
XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values()));
|
||||
if (randomBoolean()) {
|
||||
builder.prettyPrint();
|
||||
}
|
||||
innerHit.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
|
||||
XContentParser parser = XContentHelper.createParser(builder.bytes());
|
||||
context.reset(parser);
|
||||
InnerHitBuilder secondInnerHits = InnerHitBuilder.fromXContent(parser, context);
|
||||
assertThat(innerHit, not(sameInstance(secondInnerHits)));
|
||||
assertThat(innerHit, equalTo(secondInnerHits));
|
||||
assertThat(innerHit.hashCode(), equalTo(secondInnerHits.hashCode()));
|
||||
}
|
||||
}
|
||||
|
||||
public void testEqualsAndHashcode() throws IOException {
|
||||
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
|
||||
InnerHitBuilder firstInnerHit = randomInnerHits();
|
||||
assertFalse("inner hit is equal to null", firstInnerHit.equals(null));
|
||||
assertFalse("inner hit is equal to incompatible type", firstInnerHit.equals(""));
|
||||
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)));
|
||||
|
||||
InnerHitBuilder secondBuilder = serializedCopy(firstInnerHit);
|
||||
assertTrue("inner hit is not equal to self", secondBuilder.equals(secondBuilder));
|
||||
assertTrue("inner hit is not equal to its copy", firstInnerHit.equals(secondBuilder));
|
||||
assertTrue("equals is not symmetric", secondBuilder.equals(firstInnerHit));
|
||||
assertThat("inner hits copy's hashcode is different from original hashcode", secondBuilder.hashCode(),
|
||||
equalTo(firstInnerHit.hashCode()));
|
||||
|
||||
InnerHitBuilder 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", firstInnerHit.equals(thirdBuilder));
|
||||
assertThat("inner hit copy's hashcode is different from original hashcode", firstInnerHit.hashCode(),
|
||||
equalTo(thirdBuilder.hashCode()));
|
||||
assertTrue("equals is not symmetric", thirdBuilder.equals(secondBuilder));
|
||||
assertTrue("equals is not symmetric", thirdBuilder.equals(firstInnerHit));
|
||||
}
|
||||
}
|
||||
|
||||
public static InnerHitBuilder randomInnerHits() {
|
||||
return randomInnerHits(true);
|
||||
}
|
||||
|
||||
public static InnerHitBuilder randomInnerHits(boolean recursive) {
|
||||
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));
|
||||
innerHits.setExplain(randomBoolean());
|
||||
innerHits.setVersion(randomBoolean());
|
||||
innerHits.setTrackScores(randomBoolean());
|
||||
innerHits.setFieldNames(randomListStuff(16, () -> randomAsciiOfLengthBetween(1, 16)));
|
||||
innerHits.setFieldDataFields(randomListStuff(16, () -> randomAsciiOfLengthBetween(1, 16)));
|
||||
innerHits.setScriptFields(randomListStuff(16, InnerHitBuilderTests::randomScript));
|
||||
FetchSourceContext randomFetchSourceContext;
|
||||
if (randomBoolean()) {
|
||||
randomFetchSourceContext = new FetchSourceContext(randomBoolean());
|
||||
} else {
|
||||
randomFetchSourceContext = new FetchSourceContext(
|
||||
generateRandomStringArray(12, 16, false),
|
||||
generateRandomStringArray(12, 16, false)
|
||||
);
|
||||
}
|
||||
innerHits.setFetchSourceContext(randomFetchSourceContext);
|
||||
if (randomBoolean()) {
|
||||
innerHits.setSorts(randomListStuff(16,
|
||||
() -> SortBuilders.fieldSort(randomAsciiOfLengthBetween(5, 20)).order(randomFrom(SortOrder.values())))
|
||||
);
|
||||
}
|
||||
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.setInnerHitsBuilder(innerHitsBuilder);
|
||||
}
|
||||
|
||||
return innerHits;
|
||||
}
|
||||
|
||||
static InnerHitBuilder mutate(InnerHitBuilder innerHits) throws IOException {
|
||||
InnerHitBuilder copy = serializedCopy(innerHits);
|
||||
int surprise = randomIntBetween(0, 10);
|
||||
switch (surprise) {
|
||||
case 0:
|
||||
copy.setFrom(randomValueOtherThan(innerHits.getFrom(), () -> randomIntBetween(0, 128)));
|
||||
break;
|
||||
case 1:
|
||||
copy.setSize(randomValueOtherThan(innerHits.getSize(), () -> randomIntBetween(0, 128)));
|
||||
break;
|
||||
case 2:
|
||||
copy.setExplain(!copy.isExplain());
|
||||
break;
|
||||
case 3:
|
||||
copy.setVersion(!copy.isVersion());
|
||||
break;
|
||||
case 4:
|
||||
copy.setTrackScores(!copy.isTrackScores());
|
||||
break;
|
||||
case 5:
|
||||
copy.setName(randomValueOtherThan(innerHits.getName(), () -> randomAsciiOfLengthBetween(1, 16)));
|
||||
break;
|
||||
case 6:
|
||||
copy.setFieldDataFields(randomValueOtherThan(copy.getFieldDataFields(), () -> {
|
||||
return randomListStuff(16, () -> randomAsciiOfLengthBetween(1, 16));
|
||||
}));
|
||||
break;
|
||||
case 7:
|
||||
copy.setScriptFields(randomValueOtherThan(copy.getScriptFields(), () -> {
|
||||
return randomListStuff(16, InnerHitBuilderTests::randomScript);}));
|
||||
break;
|
||||
case 8:
|
||||
copy.setFetchSourceContext(randomValueOtherThan(copy.getFetchSourceContext(), () -> {
|
||||
FetchSourceContext randomFetchSourceContext;
|
||||
if (randomBoolean()) {
|
||||
randomFetchSourceContext = new FetchSourceContext(randomBoolean());
|
||||
} else {
|
||||
randomFetchSourceContext = new FetchSourceContext(
|
||||
generateRandomStringArray(12, 16, false),
|
||||
generateRandomStringArray(12, 16, false)
|
||||
);
|
||||
}
|
||||
return randomFetchSourceContext;
|
||||
}));
|
||||
break;
|
||||
case 9:
|
||||
copy.setSorts(randomValueOtherThan(copy.getSorts(), () -> {
|
||||
return randomListStuff(16,
|
||||
() -> SortBuilders.fieldSort(randomAsciiOfLengthBetween(5, 20)).order(randomFrom(SortOrder.values())));
|
||||
}));
|
||||
break;
|
||||
case 10:
|
||||
copy.setHighlightBuilder(randomValueOtherThan(copy.getHighlightBuilder(),
|
||||
HighlightBuilderTests::randomHighlighterBuilder));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("unexpected surprise [" + surprise + "]");
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
static SearchSourceBuilder.ScriptField randomScript() {
|
||||
ScriptService.ScriptType randomScriptType = randomFrom(ScriptService.ScriptType.values());
|
||||
Map<String, Object> randomMap = null;
|
||||
if (randomBoolean()) {
|
||||
randomMap = new HashMap<>();
|
||||
int numEntries = randomIntBetween(0, 32);
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
randomMap.put(String.valueOf(i), randomAsciiOfLength(16));
|
||||
}
|
||||
}
|
||||
Script script = new Script(randomAsciiOfLength(128), randomScriptType, randomAsciiOfLengthBetween(1, 4),randomMap);
|
||||
return new SearchSourceBuilder.ScriptField(randomAsciiOfLengthBetween(1, 32), script, randomBoolean());
|
||||
}
|
||||
|
||||
static <T> List<T> randomListStuff(int maxSize, Supplier<T> valueSupplier) {
|
||||
int size = randomIntBetween(0, maxSize);
|
||||
List<T> list = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
list.add(valueSupplier.get());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static InnerHitBuilder serializedCopy(InnerHitBuilder original) throws IOException {
|
||||
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||
original.writeTo(output);
|
||||
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
|
||||
return new InnerHitBuilder(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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.MatchQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryParseContext;
|
||||
import org.elasticsearch.indices.query.IndicesQueriesRegistry;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.search.SearchModule;
|
||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||
import org.elasticsearch.search.fetch.source.FetchSourceContext;
|
||||
import org.elasticsearch.search.highlight.HighlightBuilderTests;
|
||||
import org.elasticsearch.search.sort.FieldSortBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilders;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
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).buildQueryParserRegistry();
|
||||
}
|
||||
|
||||
@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 {
|
||||
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
|
||||
context.parseFieldMatcher(new ParseFieldMatcher(Settings.EMPTY));
|
||||
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());
|
||||
context.reset(parser);
|
||||
parser.nextToken();
|
||||
InnerHitsBuilder secondInnerHits = InnerHitsBuilder.fromXContent(parser, 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 InnerHitsBuilder.PROTO.readFrom(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,79 +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.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class QueryInnerHitsTests extends ESTestCase {
|
||||
|
||||
public void testSerialize() throws IOException {
|
||||
copyAndAssert(new QueryInnerHits());
|
||||
copyAndAssert(new QueryInnerHits("foo", new InnerHitsBuilder.InnerHit()));
|
||||
copyAndAssert(new QueryInnerHits("foo", null));
|
||||
copyAndAssert(new QueryInnerHits("foo", new InnerHitsBuilder.InnerHit().setSize(randomIntBetween(0, 100))));
|
||||
}
|
||||
|
||||
public void testToXContent() throws IOException {
|
||||
assertJson("{\"inner_hits\":{}}", new QueryInnerHits());
|
||||
assertJson("{\"inner_hits\":{\"name\":\"foo\"}}", new QueryInnerHits("foo", new InnerHitsBuilder.InnerHit()));
|
||||
assertJson("{\"inner_hits\":{\"name\":\"bar\"}}", new QueryInnerHits("bar", null));
|
||||
assertJson("{\"inner_hits\":{\"name\":\"foo\",\"size\":42}}", new QueryInnerHits("foo", new InnerHitsBuilder.InnerHit().setSize(42)));
|
||||
assertJson("{\"inner_hits\":{\"name\":\"boom\",\"from\":66,\"size\":666}}", new QueryInnerHits("boom", new InnerHitsBuilder.InnerHit().setFrom(66).setSize(666)));
|
||||
}
|
||||
|
||||
private void assertJson(String expected, QueryInnerHits hits) throws IOException {
|
||||
QueryInnerHits queryInnerHits = copyAndAssert(hits);
|
||||
String actual;
|
||||
if (randomBoolean()) {
|
||||
actual = oneLineJSON(queryInnerHits);
|
||||
} else {
|
||||
actual = oneLineJSON(hits);
|
||||
}
|
||||
assertEquals(expected, actual);
|
||||
XContentParser parser = hits.getXcontentParser();
|
||||
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
|
||||
QueryInnerHits other = copyAndAssert(new QueryInnerHits(parser));
|
||||
assertEquals(expected, oneLineJSON(other));
|
||||
}
|
||||
|
||||
public QueryInnerHits copyAndAssert(QueryInnerHits hits) throws IOException {
|
||||
BytesStreamOutput out = new BytesStreamOutput();
|
||||
hits.writeTo(out);
|
||||
QueryInnerHits copy = randomBoolean() ? hits.readFrom(StreamInput.wrap(out.bytes())) : new QueryInnerHits(StreamInput.wrap(out.bytes()));
|
||||
assertEquals(copy.toString() + " vs. " + hits.toString(), copy, hits);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private String oneLineJSON(QueryInnerHits hits) throws IOException {
|
||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
builder.startObject();
|
||||
hits.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
builder.endObject();
|
||||
return builder.string().trim();
|
||||
}
|
||||
}
|
|
@ -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.weight.WeightBuilder;
|
||||
import org.elasticsearch.index.query.support.QueryInnerHits;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
import org.elasticsearch.search.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
|
||||
|
@ -1789,7 +1789,7 @@ public class PercolatorIT extends ESIntegTestCase {
|
|||
assertAcked(prepareCreate("index").addMapping("mapping", mapping));
|
||||
try {
|
||||
client().prepareIndex("index", PercolatorFieldMapper.TYPE_NAME, "1")
|
||||
.setSource(jsonBuilder().startObject().field("query", nestedQuery("nested", matchQuery("nested.name", "value")).innerHit(new QueryInnerHits())).endObject())
|
||||
.setSource(jsonBuilder().startObject().field("query", nestedQuery("nested", matchQuery("nested.name", "value")).innerHit(new InnerHitBuilder())).endObject())
|
||||
.execute().actionGet();
|
||||
fail("Expected a parse error, because inner_hits isn't supported in the percolate api");
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -52,6 +52,7 @@ 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.indices.IndicesModule;
|
||||
import org.elasticsearch.indices.breaker.CircuitBreakerService;
|
||||
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
|
||||
|
@ -68,8 +69,7 @@ import org.elasticsearch.script.ScriptSettings;
|
|||
import org.elasticsearch.search.SearchModule;
|
||||
import org.elasticsearch.search.aggregations.AggregationBuilders;
|
||||
import org.elasticsearch.search.aggregations.AggregatorParsers;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder.InnerHit;
|
||||
import org.elasticsearch.index.query.support.InnerHitsBuilder;
|
||||
import org.elasticsearch.search.fetch.source.FetchSourceContext;
|
||||
import org.elasticsearch.search.highlight.HighlightBuilderTests;
|
||||
import org.elasticsearch.search.rescore.QueryRescoreBuilderTests;
|
||||
|
@ -417,11 +417,11 @@ public class SearchSourceBuilderTests extends ESTestCase {
|
|||
builder.suggest(SuggestBuilderTests.randomSuggestBuilder());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
// NORELEASE need a random inner hits builder method
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
InnerHit innerHit = new InnerHit();
|
||||
innerHit.field(randomAsciiOfLengthBetween(5, 20));
|
||||
innerHitsBuilder.addNestedInnerHits(randomAsciiOfLengthBetween(5, 20), randomAsciiOfLengthBetween(5, 20), innerHit);
|
||||
int num = randomIntBetween(0, 3);
|
||||
for (int i = 0; i < num; i++) {
|
||||
innerHitsBuilder.addInnerHit(randomAsciiOfLengthBetween(5, 20), InnerHitBuilderTests.randomInnerHits());
|
||||
}
|
||||
builder.innerHits(innerHitsBuilder);
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
|
|
|
@ -27,15 +27,16 @@ 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.QueryInnerHits;
|
||||
import org.elasticsearch.index.query.support.InnerHitBuilder;
|
||||
import org.elasticsearch.index.query.support.InnerHitsBuilder;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.script.MockScriptEngine;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.elasticsearch.search.fetch.innerhits.InnerHitsBuilder;
|
||||
import org.elasticsearch.search.highlight.HighlightBuilder;
|
||||
import org.elasticsearch.search.sort.FieldSortBuilder;
|
||||
import org.elasticsearch.search.sort.SortBuilders;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
|
@ -75,7 +76,8 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
}
|
||||
|
||||
public void testSimpleNested() throws Exception {
|
||||
assertAcked(prepareCreate("articles").addMapping("article", jsonBuilder().startObject().startObject("article").startObject("properties")
|
||||
assertAcked(prepareCreate("articles").addMapping("article", jsonBuilder().startObject().startObject("article")
|
||||
.startObject("properties")
|
||||
.startObject("comments")
|
||||
.field("type", "nested")
|
||||
.startObject("properties")
|
||||
|
@ -110,11 +112,14 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addNestedInnerHits("comment", "comments",
|
||||
new InnerHitsBuilder.InnerHit().setQuery(matchQuery("comments.message", "fox")));
|
||||
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")).innerHit(new QueryInnerHits("comment", null))).request(),
|
||||
client().prepareSearch("articles").setQuery(nestedQuery("comments", matchQuery("comments.message", "fox"))
|
||||
.innerHit(new InnerHitBuilder().setName("comment"))).request(),
|
||||
client().prepareSearch("articles").setQuery(nestedQuery("comments", matchQuery("comments.message", "fox")))
|
||||
.innerHits(innerHitsBuilder).request()
|
||||
};
|
||||
|
@ -136,8 +141,9 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
}
|
||||
|
||||
innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addNestedInnerHits("comment", "comments",
|
||||
new InnerHitsBuilder.InnerHit().setQuery(matchQuery("comments.message", "elephant")));
|
||||
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[] {
|
||||
|
@ -145,9 +151,9 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant")))
|
||||
.innerHits(innerHitsBuilder).request(),
|
||||
client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant")).innerHit(new QueryInnerHits("comment", null))).request(),
|
||||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant")).innerHit(new InnerHitBuilder().setName("comment"))).request(),
|
||||
client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant")).innerHit(new QueryInnerHits("comment", new InnerHitsBuilder.InnerHit().addSort("_doc", SortOrder.DESC)))).request()
|
||||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "elephant")).innerHit(new InnerHitBuilder().setName("comment").addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)))).request()
|
||||
};
|
||||
for (SearchRequest searchRequest : searchRequests) {
|
||||
SearchResponse response = client().search(searchRequest).actionGet();
|
||||
|
@ -169,26 +175,28 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
assertThat(innerHits.getAt(2).getNestedIdentity().getField().string(), equalTo("comments"));
|
||||
assertThat(innerHits.getAt(2).getNestedIdentity().getOffset(), equalTo(2));
|
||||
}
|
||||
InnerHitsBuilder.InnerHit innerHit = new InnerHitsBuilder.InnerHit();
|
||||
innerHit.highlighter(new HighlightBuilder().field("comments.message"));
|
||||
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.addNestedInnerHits("comments", "comments", new InnerHitsBuilder.InnerHit()
|
||||
.setQuery(matchQuery("comments.message", "fox"))
|
||||
.highlighter(new HighlightBuilder().field("comments.message"))
|
||||
.setExplain(true)
|
||||
.addFieldDataField("comments.message")
|
||||
.addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE, MockScriptEngine.NAME, Collections.emptyMap()))
|
||||
.setSize(1));
|
||||
innerHitsBuilder.addInnerHit("comments", innerHit);
|
||||
searchRequests = new SearchRequest[] {
|
||||
client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "fox")))
|
||||
.innerHits(innerHitsBuilder).request(),
|
||||
client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "fox")).innerHit(new QueryInnerHits(null, innerHit))).request()
|
||||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "fox")).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) {
|
||||
|
@ -231,8 +239,11 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
SearchResponse searchResponse;
|
||||
if (randomBoolean()) {
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addNestedInnerHits("a", "field1", new InnerHitsBuilder.InnerHit().addSort("_doc", SortOrder.DESC).setSize(size)); // Sort order is DESC, because we reverse the inner objects during indexing!
|
||||
innerHitsBuilder.addNestedInnerHits("b", "field2", new InnerHitsBuilder.InnerHit().addSort("_doc", SortOrder.DESC).setSize(size));
|
||||
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)
|
||||
|
@ -241,11 +252,15 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
} else {
|
||||
BoolQueryBuilder boolQuery = new BoolQueryBuilder();
|
||||
if (randomBoolean()) {
|
||||
boolQuery.should(nestedQuery("field1", matchAllQuery()).innerHit(new QueryInnerHits("a", new InnerHitsBuilder.InnerHit().addSort("_doc", SortOrder.DESC).setSize(size))));
|
||||
boolQuery.should(nestedQuery("field2", matchAllQuery()).innerHit(new QueryInnerHits("b", new InnerHitsBuilder.InnerHit().addSort("_doc", SortOrder.DESC).setSize(size))));
|
||||
boolQuery.should(nestedQuery("field1", matchAllQuery()).innerHit(new InnerHitBuilder().setName("a").setSize(size)
|
||||
.addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC))));
|
||||
boolQuery.should(nestedQuery("field2", matchAllQuery()).innerHit(new InnerHitBuilder().setName("b")
|
||||
.addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)).setSize(size)));
|
||||
} else {
|
||||
boolQuery.should(constantScoreQuery(nestedQuery("field1", matchAllQuery()).innerHit(new QueryInnerHits("a", new InnerHitsBuilder.InnerHit().addSort("_doc", SortOrder.DESC).setSize(size)))));
|
||||
boolQuery.should(constantScoreQuery(nestedQuery("field2", matchAllQuery()).innerHit(new QueryInnerHits("b", new InnerHitsBuilder.InnerHit().addSort("_doc", SortOrder.DESC).setSize(size)))));
|
||||
boolQuery.should(constantScoreQuery(nestedQuery("field1", matchAllQuery()).innerHit(new InnerHitBuilder().setName("a")
|
||||
.setSize(size).addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)))));
|
||||
boolQuery.should(constantScoreQuery(nestedQuery("field2", matchAllQuery()).innerHit(new InnerHitBuilder().setName("b")
|
||||
.setSize(size).addSort(new FieldSortBuilder("_doc").order(SortOrder.DESC)))));
|
||||
}
|
||||
searchResponse = client().prepareSearch("idx")
|
||||
.setQuery(boolQuery)
|
||||
|
@ -298,14 +313,15 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addParentChildInnerHits("comment", "comment", new InnerHitsBuilder.InnerHit().setQuery(matchQuery("message", "fox")));
|
||||
innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder().setParentChildType("comment")
|
||||
.setQuery(matchQuery("message", "fox")));
|
||||
SearchRequest[] searchRequests = new SearchRequest[]{
|
||||
client().prepareSearch("articles")
|
||||
.setQuery(hasChildQuery("comment", matchQuery("message", "fox")))
|
||||
.innerHits(innerHitsBuilder)
|
||||
.request(),
|
||||
client().prepareSearch("articles")
|
||||
.setQuery(hasChildQuery("comment", matchQuery("message", "fox")).innerHit(new QueryInnerHits("comment", null)))
|
||||
.setQuery(hasChildQuery("comment", matchQuery("message", "fox")).innerHit(new InnerHitBuilder().setName("comment")))
|
||||
.request()
|
||||
};
|
||||
for (SearchRequest searchRequest : searchRequests) {
|
||||
|
@ -326,14 +342,15 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
}
|
||||
|
||||
innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addParentChildInnerHits("comment", "comment", new InnerHitsBuilder.InnerHit().setQuery(matchQuery("message", "elephant")));
|
||||
innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder().setParentChildType("comment")
|
||||
.setQuery(matchQuery("message", "elephant")));
|
||||
searchRequests = new SearchRequest[] {
|
||||
client().prepareSearch("articles")
|
||||
.setQuery(hasChildQuery("comment", matchQuery("message", "elephant")))
|
||||
.innerHits(innerHitsBuilder)
|
||||
.request(),
|
||||
client().prepareSearch("articles")
|
||||
.setQuery(hasChildQuery("comment", matchQuery("message", "elephant")).innerHit(new QueryInnerHits()))
|
||||
.setQuery(hasChildQuery("comment", matchQuery("message", "elephant")).innerHit(new InnerHitBuilder()))
|
||||
.request()
|
||||
};
|
||||
for (SearchRequest searchRequest : searchRequests) {
|
||||
|
@ -353,20 +370,16 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
assertThat(innerHits.getAt(2).getId(), equalTo("6"));
|
||||
assertThat(innerHits.getAt(2).type(), equalTo("comment"));
|
||||
}
|
||||
InnerHitsBuilder.InnerHit innerHit = new InnerHitsBuilder.InnerHit();
|
||||
innerHit.highlighter(new HighlightBuilder().field("message"));
|
||||
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.addParentChildInnerHits("comment", "comment", new InnerHitsBuilder.InnerHit()
|
||||
.setQuery(matchQuery("message", "fox"))
|
||||
.highlighter(new HighlightBuilder().field("message"))
|
||||
.setExplain(true)
|
||||
.addFieldDataField("message")
|
||||
.addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE, MockScriptEngine.NAME, Collections.emptyMap()))
|
||||
.setSize(1));
|
||||
innerHitsBuilder.addInnerHit("comment", innerHit);
|
||||
searchRequests = new SearchRequest[] {
|
||||
client().prepareSearch("articles")
|
||||
.setQuery(hasChildQuery("comment", matchQuery("message", "fox")))
|
||||
|
@ -376,7 +389,14 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
client().prepareSearch("articles")
|
||||
.setQuery(
|
||||
hasChildQuery("comment", matchQuery("message", "fox")).innerHit(
|
||||
new QueryInnerHits(null, innerHit))).request() };
|
||||
new InnerHitBuilder()
|
||||
.addFieldDataField("message")
|
||||
.setHighlightBuilder(new HighlightBuilder().field("message"))
|
||||
.setExplain(true).setSize(1)
|
||||
.addScriptField("script", new Script("5", ScriptService.ScriptType.INLINE,
|
||||
MockScriptEngine.NAME, Collections.emptyMap()))
|
||||
)
|
||||
).request() };
|
||||
|
||||
for (SearchRequest searchRequest : searchRequests) {
|
||||
SearchResponse response = client().search(searchRequest).actionGet();
|
||||
|
@ -422,8 +442,8 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
|
||||
int size = randomIntBetween(0, numDocs);
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addParentChildInnerHits("a", "child1", new InnerHitsBuilder.InnerHit().addSort("_uid", SortOrder.ASC).setSize(size));
|
||||
innerHitsBuilder.addParentChildInnerHits("b", "child2", new InnerHitsBuilder.InnerHit().addSort("_uid", SortOrder.ASC).setSize(size));
|
||||
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")
|
||||
|
@ -435,11 +455,11 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
} else {
|
||||
BoolQueryBuilder boolQuery = new BoolQueryBuilder();
|
||||
if (randomBoolean()) {
|
||||
boolQuery.should(hasChildQuery("child1", matchAllQuery()).innerHit(new QueryInnerHits("a", new InnerHitsBuilder.InnerHit().addSort("_uid", SortOrder.ASC).setSize(size))));
|
||||
boolQuery.should(hasChildQuery("child2", matchAllQuery()).innerHit(new QueryInnerHits("b", new InnerHitsBuilder.InnerHit().addSort("_uid", SortOrder.ASC).setSize(size))));
|
||||
boolQuery.should(hasChildQuery("child1", matchAllQuery()).innerHit(new InnerHitBuilder().setName("a").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size)));
|
||||
boolQuery.should(hasChildQuery("child2", matchAllQuery()).innerHit(new InnerHitBuilder().setName("b").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size)));
|
||||
} else {
|
||||
boolQuery.should(constantScoreQuery(hasChildQuery("child1", matchAllQuery()).innerHit(new QueryInnerHits("a", new InnerHitsBuilder.InnerHit().addSort("_uid", SortOrder.ASC).setSize(size)))));
|
||||
boolQuery.should(constantScoreQuery(hasChildQuery("child2", matchAllQuery()).innerHit(new QueryInnerHits("b", new InnerHitsBuilder.InnerHit().addSort("_uid", SortOrder.ASC).setSize(size)))));
|
||||
boolQuery.should(constantScoreQuery(hasChildQuery("child1", matchAllQuery()).innerHit(new InnerHitBuilder().setName("a").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size))));
|
||||
boolQuery.should(constantScoreQuery(hasChildQuery("child2", matchAllQuery()).innerHit(new InnerHitBuilder().setName("b").addSort(new FieldSortBuilder("_uid").order(SortOrder.ASC)).setSize(size))));
|
||||
}
|
||||
searchResponse = client().prepareSearch("idx")
|
||||
.setSize(numDocs)
|
||||
|
@ -485,22 +505,6 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@AwaitsFix(bugUrl = "need validation of type or path defined in InnerHitsBuilder")
|
||||
public void testPathOrTypeMustBeDefined() {
|
||||
createIndex("articles");
|
||||
ensureGreen("articles");
|
||||
try {
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addParentChildInnerHits("comment", null, new InnerHitsBuilder.InnerHit());
|
||||
client().prepareSearch("articles")
|
||||
.innerHits(innerHitsBuilder)
|
||||
.get();
|
||||
} catch (Exception e) {
|
||||
assertThat(e.getMessage(), containsString("Failed to build"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testInnerHitsOnHasParent() throws Exception {
|
||||
assertAcked(prepareCreate("stack")
|
||||
.addMapping("question", "body", "type=text")
|
||||
|
@ -519,7 +523,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
.setQuery(
|
||||
boolQuery()
|
||||
.must(matchQuery("body", "fail2ban"))
|
||||
.must(hasParentQuery("question", matchAllQuery()).innerHit(new QueryInnerHits()))
|
||||
.must(hasParentQuery("question", matchAllQuery()).innerHit(new InnerHitBuilder()))
|
||||
).get();
|
||||
assertNoFailures(response);
|
||||
assertHitCount(response, 2);
|
||||
|
@ -556,11 +560,15 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
InnerHitsBuilder innerInnerHitsBuilder = new InnerHitsBuilder();
|
||||
innerInnerHitsBuilder.addParentChildInnerHits("remark", "remark", new InnerHitsBuilder.InnerHit().setQuery(matchQuery("message", "good")));
|
||||
innerInnerHitsBuilder.addInnerHit("remark", new InnerHitBuilder()
|
||||
.setParentChildType("remark")
|
||||
.setQuery(matchQuery("message", "good"))
|
||||
);
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addParentChildInnerHits("comment", "comment", new InnerHitsBuilder.InnerHit()
|
||||
innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
|
||||
.setParentChildType("comment")
|
||||
.setQuery(hasChildQuery("remark", matchQuery("message", "good")))
|
||||
.innerHits(innerInnerHitsBuilder));
|
||||
.setInnerHitsBuilder(innerInnerHitsBuilder));
|
||||
SearchResponse response = client().prepareSearch("articles")
|
||||
.setQuery(hasChildQuery("comment", hasChildQuery("remark", matchQuery("message", "good"))))
|
||||
.innerHits(innerHitsBuilder)
|
||||
|
@ -582,11 +590,14 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
assertThat(innerHits.getAt(0).type(), equalTo("remark"));
|
||||
|
||||
innerInnerHitsBuilder = new InnerHitsBuilder();
|
||||
innerInnerHitsBuilder.addParentChildInnerHits("remark", "remark", new InnerHitsBuilder.InnerHit().setQuery(matchQuery("message", "bad")));
|
||||
innerInnerHitsBuilder.addInnerHit("remark", new InnerHitBuilder()
|
||||
.setParentChildType("remark")
|
||||
.setQuery(matchQuery("message", "bad")));
|
||||
innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addParentChildInnerHits("comment", "comment", new InnerHitsBuilder.InnerHit()
|
||||
innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
|
||||
.setParentChildType("comment")
|
||||
.setQuery(hasChildQuery("remark", matchQuery("message", "bad")))
|
||||
.innerHits(innerInnerHitsBuilder));
|
||||
.setInnerHitsBuilder(innerInnerHitsBuilder));
|
||||
response = client().prepareSearch("articles")
|
||||
.setQuery(hasChildQuery("comment", hasChildQuery("remark", matchQuery("message", "bad"))))
|
||||
.innerHits(innerHitsBuilder)
|
||||
|
@ -651,11 +662,14 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
InnerHitsBuilder innerInnerHitsBuilder = new InnerHitsBuilder();
|
||||
innerInnerHitsBuilder.addNestedInnerHits("remark", "comments.remarks", new InnerHitsBuilder.InnerHit().setQuery(matchQuery("comments.remarks.message", "good")));
|
||||
innerInnerHitsBuilder.addInnerHit("remark", new InnerHitBuilder()
|
||||
.setNestedPath("comments.remarks")
|
||||
.setQuery(matchQuery("comments.remarks.message", "good")));
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addNestedInnerHits("comment", "comments", new InnerHitsBuilder.InnerHit()
|
||||
innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
|
||||
.setNestedPath("comments")
|
||||
.setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "good")))
|
||||
.innerHits(innerInnerHitsBuilder)
|
||||
.setInnerHitsBuilder(innerInnerHitsBuilder)
|
||||
);
|
||||
SearchResponse response = client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments", nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "good"))))
|
||||
|
@ -681,7 +695,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
|
||||
// Directly refer to the second level:
|
||||
response = client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad")).innerHit(new QueryInnerHits()))
|
||||
.setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad")).innerHit(new InnerHitBuilder()))
|
||||
.get();
|
||||
assertNoFailures(response);
|
||||
assertHitCount(response, 1);
|
||||
|
@ -697,11 +711,14 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
assertThat(innerHits.getAt(0).getNestedIdentity().getChild().getOffset(), equalTo(0));
|
||||
|
||||
innerInnerHitsBuilder = new InnerHitsBuilder();
|
||||
innerInnerHitsBuilder.addNestedInnerHits("remark", "comments.remarks", new InnerHitsBuilder.InnerHit().setQuery(matchQuery("comments.remarks.message", "bad")));
|
||||
innerInnerHitsBuilder.addInnerHit("remark", new InnerHitBuilder()
|
||||
.setNestedPath("comments.remarks")
|
||||
.setQuery(matchQuery("comments.remarks.message", "bad")));
|
||||
innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addNestedInnerHits("comment", "comments", new InnerHitsBuilder.InnerHit()
|
||||
.setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad")))
|
||||
.innerHits(innerInnerHitsBuilder));
|
||||
innerHitsBuilder.addInnerHit("comment", new InnerHitBuilder()
|
||||
.setNestedPath("comments")
|
||||
.setQuery(nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad")))
|
||||
.setInnerHitsBuilder(innerInnerHitsBuilder));
|
||||
response = client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments", nestedQuery("comments.remarks", matchQuery("comments.remarks.message", "bad"))))
|
||||
.innerHits(innerHitsBuilder)
|
||||
|
@ -738,7 +755,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
SearchResponse response = client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "fox")).innerHit(new QueryInnerHits()))
|
||||
.setQuery(nestedQuery("comments", matchQuery("comments.message", "fox")).innerHit(new InnerHitBuilder()))
|
||||
.get();
|
||||
assertNoFailures(response);
|
||||
assertHitCount(response, 1);
|
||||
|
@ -778,7 +795,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
SearchResponse response = client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox")).innerHit(new QueryInnerHits()))
|
||||
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox")).innerHit(new InnerHitBuilder()))
|
||||
.get();
|
||||
assertNoFailures(response);
|
||||
assertHitCount(response, 1);
|
||||
|
@ -790,7 +807,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().getAt(0).getInnerHits().get("comments.messages").getAt(0).getNestedIdentity().getChild(), nullValue());
|
||||
|
||||
response = client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "bear")).innerHit(new QueryInnerHits()))
|
||||
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "bear")).innerHit(new InnerHitBuilder()))
|
||||
.get();
|
||||
assertNoFailures(response);
|
||||
assertHitCount(response, 1);
|
||||
|
@ -809,7 +826,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
.endObject()));
|
||||
indexRandom(true, requests);
|
||||
response = client().prepareSearch("articles")
|
||||
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox")).innerHit(new QueryInnerHits()))
|
||||
.setQuery(nestedQuery("comments.messages", matchQuery("comments.messages.message", "fox")).innerHit(new InnerHitBuilder()))
|
||||
.get();
|
||||
assertNoFailures(response);
|
||||
assertHitCount(response, 1);
|
||||
|
@ -846,19 +863,18 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
InnerHitsBuilder innerInnerHitsBuilder = new InnerHitsBuilder();
|
||||
innerInnerHitsBuilder.addParentChildInnerHits("barons", "baron", new InnerHitsBuilder.InnerHit());
|
||||
innerInnerHitsBuilder.addInnerHit("barons", new InnerHitBuilder().setParentChildType("baron"));
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addParentChildInnerHits("earls", "earl", new InnerHitsBuilder.InnerHit()
|
||||
.addSort(SortBuilders.fieldSort("_uid").order(SortOrder.ASC))
|
||||
.setSize(4)
|
||||
.innerHits(innerInnerHitsBuilder)
|
||||
innerHitsBuilder.addInnerHit("earls", new InnerHitBuilder()
|
||||
.setParentChildType("earl")
|
||||
.addSort(SortBuilders.fieldSort("_uid").order(SortOrder.ASC))
|
||||
.setSize(4)
|
||||
.setInnerHitsBuilder(innerInnerHitsBuilder)
|
||||
);
|
||||
innerInnerHitsBuilder = new InnerHitsBuilder();
|
||||
innerInnerHitsBuilder.addParentChildInnerHits("kings", "king", new InnerHitsBuilder.InnerHit());
|
||||
innerHitsBuilder.addParentChildInnerHits("princes", "prince",
|
||||
new InnerHitsBuilder.InnerHit()
|
||||
.innerHits(innerInnerHitsBuilder)
|
||||
);
|
||||
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)
|
||||
|
@ -972,7 +988,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
.should(termQuery("nested1.n_field1", "n_value1_1").queryName("test1"))
|
||||
.should(termQuery("nested1.n_field1", "n_value1_3").queryName("test2"))
|
||||
.should(termQuery("nested1.n_field2", "n_value2_2").queryName("test3"))
|
||||
).innerHit(new QueryInnerHits(null, new InnerHitsBuilder.InnerHit().addSort("nested1.n_field1", SortOrder.ASC))))
|
||||
).innerHit(new InnerHitBuilder().addSort(new FieldSortBuilder("nested1.n_field1").order(SortOrder.ASC))))
|
||||
.setSize(numDocs)
|
||||
.addSort("field1", SortOrder.ASC)
|
||||
.get();
|
||||
|
@ -1011,7 +1027,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
SearchResponse response = client().prepareSearch("index")
|
||||
.setQuery(hasChildQuery("child", matchQuery("field", "value1").queryName("_name1")).innerHit(new QueryInnerHits()))
|
||||
.setQuery(hasChildQuery("child", matchQuery("field", "value1").queryName("_name1")).innerHit(new InnerHitBuilder()))
|
||||
.addSort("_uid", SortOrder.ASC)
|
||||
.get();
|
||||
assertHitCount(response, 2);
|
||||
|
@ -1026,7 +1042,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
assertThat(response.getHits().getAt(1).getInnerHits().get("child").getAt(0).getMatchedQueries()[0], equalTo("_name1"));
|
||||
|
||||
response = client().prepareSearch("index")
|
||||
.setQuery(hasChildQuery("child", matchQuery("field", "value2").queryName("_name2")).innerHit(new QueryInnerHits()))
|
||||
.setQuery(hasChildQuery("child", matchQuery("field", "value2").queryName("_name2")).innerHit(new InnerHitBuilder()))
|
||||
.addSort("_uid", SortOrder.ASC)
|
||||
.get();
|
||||
assertHitCount(response, 1);
|
||||
|
@ -1044,7 +1060,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
SearchResponse response = client().prepareSearch("index1")
|
||||
.setQuery(hasChildQuery("child", matchQuery("field", "value1")).innerHit(new QueryInnerHits(null, new InnerHitsBuilder.InnerHit().setSize(ArrayUtil.MAX_ARRAY_LENGTH - 1))))
|
||||
.setQuery(hasChildQuery("child", matchQuery("field", "value1")).innerHit(new InnerHitBuilder().setSize(ArrayUtil.MAX_ARRAY_LENGTH - 1)))
|
||||
.addSort("_uid", SortOrder.ASC)
|
||||
.get();
|
||||
assertNoFailures(response);
|
||||
|
@ -1062,7 +1078,7 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
.get();
|
||||
|
||||
response = client().prepareSearch("index2")
|
||||
.setQuery(nestedQuery("nested", matchQuery("nested.field", "value1")).innerHit(new QueryInnerHits(null, new InnerHitsBuilder.InnerHit().setSize(ArrayUtil.MAX_ARRAY_LENGTH - 1))))
|
||||
.setQuery(nestedQuery("nested", matchQuery("nested.field", "value1")).innerHit(new InnerHitBuilder().setSize(ArrayUtil.MAX_ARRAY_LENGTH - 1)))
|
||||
.addSort("_uid", SortOrder.ASC)
|
||||
.get();
|
||||
assertNoFailures(response);
|
||||
|
@ -1079,9 +1095,9 @@ public class InnerHitsIT extends ESIntegTestCase {
|
|||
indexRandom(true, requests);
|
||||
|
||||
InnerHitsBuilder innerHitsBuilder = new InnerHitsBuilder();
|
||||
innerHitsBuilder.addParentChildInnerHits("my-inner-hit", "child", new InnerHitsBuilder.InnerHit());
|
||||
innerHitsBuilder.addInnerHit("my-inner-hit", new InnerHitBuilder().setParentChildType("child"));
|
||||
SearchResponse response = client().prepareSearch("index1")
|
||||
.setQuery(hasChildQuery("child", new MatchAllQueryBuilder()).innerHit(new QueryInnerHits()))
|
||||
.setQuery(hasChildQuery("child", new MatchAllQueryBuilder()).innerHit(new InnerHitBuilder()))
|
||||
.innerHits(innerHitsBuilder)
|
||||
.get();
|
||||
assertHitCount(response, 1);
|
||||
|
|
|
@ -146,3 +146,8 @@ vectors don't support distributed document frequencies anymore.
|
|||
|
||||
The `reverse` parameter has been removed, in favour of explicitly
|
||||
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.
|
||||
|
|
|
@ -276,20 +276,17 @@ An example that shows the use of nested inner hits via the top level notation:
|
|||
}
|
||||
},
|
||||
"inner_hits" : {
|
||||
"comment" : {
|
||||
"path" : { <1>
|
||||
"comments" : { <2>
|
||||
"query" : {
|
||||
"match" : {"comments.message" : "[different query]"} <3>
|
||||
}
|
||||
}
|
||||
"comment" : { <1>
|
||||
"path" : "comments", <2>
|
||||
"query" : {
|
||||
"match" : {"comments.message" : "[different query]"} <3>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
--------------------------------------------------
|
||||
|
||||
<1> The inner hit definition is nested and requires the `path` option.
|
||||
<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
|
||||
|
|
Loading…
Reference in New Issue