Parent/Child: Added min_children/max_children to has_child query/filter
Added support for min_children and max_children parameters to the has_child query and filter. A parent document will only be considered if a match if the number of matching children fall between the min/max bounds. Closes #6019
This commit is contained in:
parent
48ccb06160
commit
46a67b638d
|
@ -16,7 +16,7 @@ the query. Here is an example:
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
--------------------------------------------------
|
||||
|
||||
The `type` is the child type to query against. The parent type to return
|
||||
|
@ -39,9 +39,39 @@ The `has_child` filter also accepts a filter instead of a query:
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
--------------------------------------------------
|
||||
|
||||
[float]
|
||||
==== Min/Max Children
|
||||
|
||||
coming[1.3.0]
|
||||
|
||||
The `has_child` filter allows you to specify that a minimum and/or maximum
|
||||
number of children are required to match for the parent doc to be considered
|
||||
a match:
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
{
|
||||
"has_child" : {
|
||||
"type" : "comment",
|
||||
"min_children": 2, <1>
|
||||
"max_children": 10, <1>
|
||||
"filter" : {
|
||||
"term" : {
|
||||
"user" : "john"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
--------------------------------------------------
|
||||
<1> Both `min_children` and `max_children` are optional.
|
||||
|
||||
The execution speed of the `has_child` filter is equivalent
|
||||
to that of the `has_child` query when `min_children` or `max_children`
|
||||
is specified.
|
||||
|
||||
[float]
|
||||
==== Memory Considerations
|
||||
|
||||
|
|
|
@ -53,6 +53,36 @@ inside the `has_child` query:
|
|||
}
|
||||
--------------------------------------------------
|
||||
|
||||
[float]
|
||||
==== Min/Max Children
|
||||
|
||||
coming[1.3.0]
|
||||
|
||||
The `has_child` query allows you to specify that a minimum and/or maximum
|
||||
number of children are required to match for the parent doc to be considered
|
||||
a match:
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
{
|
||||
"has_child" : {
|
||||
"type" : "blog_tag",
|
||||
"score_mode" : "sum",
|
||||
"min_children": 2, <1>
|
||||
"max_children": 10, <1>
|
||||
"query" : {
|
||||
"term" : {
|
||||
"tag" : "something"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
--------------------------------------------------
|
||||
<1> Both `min_children` and `max_children` are optional.
|
||||
|
||||
The `min_children` and `max_children` parameters can be combined with
|
||||
the `score_mode` parameter.
|
||||
|
||||
[float]
|
||||
==== Memory Considerations
|
||||
|
||||
|
|
|
@ -32,6 +32,9 @@ public class HasChildFilterBuilder extends BaseFilterBuilder {
|
|||
private String childType;
|
||||
private String filterName;
|
||||
private Integer shortCircuitCutoff;
|
||||
private Integer minChildren;
|
||||
private Integer maxChildren;
|
||||
|
||||
|
||||
public HasChildFilterBuilder(String type, QueryBuilder queryBuilder) {
|
||||
this.childType = type;
|
||||
|
@ -53,6 +56,23 @@ public class HasChildFilterBuilder extends BaseFilterBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the minimum number of children that are required to match for the parent to be considered a match.
|
||||
*/
|
||||
public HasChildFilterBuilder minChildren(int minChildren) {
|
||||
this.minChildren = minChildren;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the maximum number of children that are required to match for the parent to be considered a match.
|
||||
*/
|
||||
public HasChildFilterBuilder maxChildren(int maxChildren) {
|
||||
this.maxChildren = maxChildren;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a noop since has_child can't be cached.
|
||||
*/
|
||||
|
@ -87,6 +107,12 @@ public class HasChildFilterBuilder extends BaseFilterBuilder {
|
|||
filterBuilder.toXContent(builder, params);
|
||||
}
|
||||
builder.field("child_type", childType);
|
||||
if (minChildren != null) {
|
||||
builder.field("min_children", minChildren);
|
||||
}
|
||||
if (maxChildren != null) {
|
||||
builder.field("max_children", maxChildren);
|
||||
}
|
||||
if (filterName != null) {
|
||||
builder.field("_name", filterName);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,9 @@ import org.elasticsearch.index.mapper.DocumentMapper;
|
|||
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
|
||||
import org.elasticsearch.index.query.support.XContentStructure;
|
||||
import org.elasticsearch.index.search.child.ChildrenConstantScoreQuery;
|
||||
import org.elasticsearch.index.search.child.ChildrenQuery;
|
||||
import org.elasticsearch.index.search.child.CustomQueryWrappingFilter;
|
||||
import org.elasticsearch.index.search.child.ScoreType;
|
||||
import org.elasticsearch.index.search.nested.NonNestedDocsFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -61,6 +63,8 @@ public class HasChildFilterParser implements FilterParser {
|
|||
boolean filterFound = false;
|
||||
String childType = null;
|
||||
int shortCircuitParentDocSet = 8192; // Tests show a cut of point between 8192 and 16384.
|
||||
int minChildren = 0;
|
||||
int maxChildren = 0;
|
||||
|
||||
String filterName = null;
|
||||
String currentFieldName = null;
|
||||
|
@ -97,6 +101,10 @@ public class HasChildFilterParser implements FilterParser {
|
|||
// noop to be backwards compatible
|
||||
} else if ("short_circuit_cutoff".equals(currentFieldName)) {
|
||||
shortCircuitParentDocSet = parser.intValue();
|
||||
} else if ("min_children".equals(currentFieldName) || "minChildren".equals(currentFieldName)) {
|
||||
minChildren = parser.intValue(true);
|
||||
} else if ("max_children".equals(currentFieldName) || "maxChildren".equals(currentFieldName)) {
|
||||
maxChildren = parser.intValue(true);
|
||||
} else {
|
||||
throw new QueryParsingException(parseContext.index(), "[has_child] filter does not support [" + currentFieldName + "]");
|
||||
}
|
||||
|
@ -138,6 +146,10 @@ public class HasChildFilterParser implements FilterParser {
|
|||
throw new QueryParsingException(parseContext.index(), "[has_child] Type [" + childType + "] points to a non existent parent type [" + parentType + "]");
|
||||
}
|
||||
|
||||
if (maxChildren > 0 && maxChildren < minChildren) {
|
||||
throw new QueryParsingException(parseContext.index(), "[has_child] 'max_children' is less than 'min_children'");
|
||||
}
|
||||
|
||||
Filter nonNestedDocsFilter = null;
|
||||
if (parentDocMapper.hasNestedObjects()) {
|
||||
nonNestedDocsFilter = parseContext.cacheFilter(NonNestedDocsFilter.INSTANCE, null);
|
||||
|
@ -145,12 +157,18 @@ public class HasChildFilterParser implements FilterParser {
|
|||
|
||||
Filter parentFilter = parseContext.cacheFilter(parentDocMapper.typeFilter(), null);
|
||||
ParentChildIndexFieldData parentChildIndexFieldData = parseContext.fieldData().getForField(parentFieldMapper);
|
||||
Query childrenConstantScoreQuery = new ChildrenConstantScoreQuery(parentChildIndexFieldData, query, parentType, childType, parentFilter, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
|
||||
if (filterName != null) {
|
||||
parseContext.addNamedFilter(filterName, new CustomQueryWrappingFilter(childrenConstantScoreQuery));
|
||||
Query childrenQuery;
|
||||
if (minChildren > 1 || maxChildren > 0) {
|
||||
childrenQuery = new ChildrenQuery(parentChildIndexFieldData, parentType, childType, parentFilter,query,ScoreType.NONE,minChildren, maxChildren, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
} else {
|
||||
childrenQuery = new ChildrenConstantScoreQuery(parentChildIndexFieldData, query, parentType, childType, parentFilter,
|
||||
shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
}
|
||||
return new CustomQueryWrappingFilter(childrenConstantScoreQuery);
|
||||
if (filterName != null) {
|
||||
parseContext.addNamedFilter(filterName, new CustomQueryWrappingFilter(childrenQuery));
|
||||
}
|
||||
return new CustomQueryWrappingFilter(childrenQuery);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,10 @@ public class HasChildQueryBuilder extends BaseQueryBuilder implements BoostableQ
|
|||
|
||||
private String scoreType;
|
||||
|
||||
private Integer minChildren;
|
||||
|
||||
private Integer maxChildren;
|
||||
|
||||
private Integer shortCircuitCutoff;
|
||||
|
||||
private String queryName;
|
||||
|
@ -61,6 +65,22 @@ public class HasChildQueryBuilder extends BaseQueryBuilder implements BoostableQ
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the minimum number of children that are required to match for the parent to be considered a match.
|
||||
*/
|
||||
public HasChildQueryBuilder minChildren(int minChildren) {
|
||||
this.minChildren = minChildren;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the maximum number of children that are required to match for the parent to be considered a match.
|
||||
*/
|
||||
public HasChildQueryBuilder maxChildren(int maxChildren) {
|
||||
this.maxChildren = maxChildren;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures at what cut off point only to evaluate parent documents that contain the matching parent id terms
|
||||
* instead of evaluating all parent docs.
|
||||
|
@ -90,6 +110,12 @@ public class HasChildQueryBuilder extends BaseQueryBuilder implements BoostableQ
|
|||
if (scoreType != null) {
|
||||
builder.field("score_type", scoreType);
|
||||
}
|
||||
if (minChildren != null) {
|
||||
builder.field("min_children", minChildren);
|
||||
}
|
||||
if (maxChildren != null) {
|
||||
builder.field("max_children", maxChildren);
|
||||
}
|
||||
if (shortCircuitCutoff != null) {
|
||||
builder.field("short_circuit_cutoff", shortCircuitCutoff);
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ public class HasChildQueryParser implements QueryParser {
|
|||
|
||||
@Override
|
||||
public String[] names() {
|
||||
return new String[]{NAME, Strings.toCamelCase(NAME)};
|
||||
return new String[] { NAME, Strings.toCamelCase(NAME) };
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,7 +63,9 @@ public class HasChildQueryParser implements QueryParser {
|
|||
boolean queryFound = false;
|
||||
float boost = 1.0f;
|
||||
String childType = null;
|
||||
ScoreType scoreType = null;
|
||||
ScoreType scoreType = ScoreType.NONE;
|
||||
int minChildren = 0;
|
||||
int maxChildren = 0;
|
||||
int shortCircuitParentDocSet = 8192;
|
||||
String queryName = null;
|
||||
|
||||
|
@ -79,7 +81,7 @@ public class HasChildQueryParser implements QueryParser {
|
|||
// XContentStructure.<type> facade to parse if available,
|
||||
// or delay parsing if not.
|
||||
if ("query".equals(currentFieldName)) {
|
||||
iq = new XContentStructure.InnerQuery(parseContext, childType == null ? null : new String[] {childType});
|
||||
iq = new XContentStructure.InnerQuery(parseContext, childType == null ? null : new String[] { childType });
|
||||
queryFound = true;
|
||||
} else {
|
||||
throw new QueryParsingException(parseContext.index(), "[has_child] query does not support [" + currentFieldName + "]");
|
||||
|
@ -88,19 +90,18 @@ public class HasChildQueryParser implements QueryParser {
|
|||
if ("type".equals(currentFieldName) || "child_type".equals(currentFieldName) || "childType".equals(currentFieldName)) {
|
||||
childType = parser.text();
|
||||
} else if ("_scope".equals(currentFieldName)) {
|
||||
throw new QueryParsingException(parseContext.index(), "the [_scope] support in [has_child] query has been removed, use a filter as a facet_filter in the relevant global facet");
|
||||
throw new QueryParsingException(parseContext.index(),
|
||||
"the [_scope] support in [has_child] query has been removed, use a filter as a facet_filter in the relevant global facet");
|
||||
} else if ("score_type".equals(currentFieldName) || "scoreType".equals(currentFieldName)) {
|
||||
String scoreTypeValue = parser.text();
|
||||
if (!"none".equals(scoreTypeValue)) {
|
||||
scoreType = ScoreType.fromString(scoreTypeValue);
|
||||
}
|
||||
scoreType = ScoreType.fromString(parser.text());
|
||||
} else if ("score_mode".equals(currentFieldName) || "scoreMode".equals(currentFieldName)) {
|
||||
String scoreModeValue = parser.text();
|
||||
if (!"none".equals(scoreModeValue)) {
|
||||
scoreType = ScoreType.fromString(scoreModeValue);
|
||||
}
|
||||
scoreType = ScoreType.fromString(parser.text());
|
||||
} else if ("boost".equals(currentFieldName)) {
|
||||
boost = parser.floatValue();
|
||||
} else if ("min_children".equals(currentFieldName) || "minChildren".equals(currentFieldName)) {
|
||||
minChildren = parser.intValue(true);
|
||||
} else if ("max_children".equals(currentFieldName) || "maxChildren".equals(currentFieldName)) {
|
||||
maxChildren = parser.intValue(true);
|
||||
} else if ("short_circuit_cutoff".equals(currentFieldName)) {
|
||||
shortCircuitParentDocSet = parser.intValue();
|
||||
} else if ("_name".equals(currentFieldName)) {
|
||||
|
@ -140,7 +141,12 @@ public class HasChildQueryParser implements QueryParser {
|
|||
String parentType = parentFieldMapper.type();
|
||||
DocumentMapper parentDocMapper = parseContext.mapperService().documentMapper(parentType);
|
||||
if (parentDocMapper == null) {
|
||||
throw new QueryParsingException(parseContext.index(), "[has_child] Type [" + childType + "] points to a non existent parent type [" + parentType + "]");
|
||||
throw new QueryParsingException(parseContext.index(), "[has_child] Type [" + childType
|
||||
+ "] points to a non existent parent type [" + parentType + "]");
|
||||
}
|
||||
|
||||
if (maxChildren > 0 && maxChildren < minChildren) {
|
||||
throw new QueryParsingException(parseContext.index(), "[has_child] 'max_children' is less than 'min_children'");
|
||||
}
|
||||
|
||||
Filter nonNestedDocsFilter = null;
|
||||
|
@ -154,10 +160,12 @@ public class HasChildQueryParser implements QueryParser {
|
|||
Query query;
|
||||
Filter parentFilter = parseContext.cacheFilter(parentDocMapper.typeFilter(), null);
|
||||
ParentChildIndexFieldData parentChildIndexFieldData = parseContext.fieldData().getForField(parentFieldMapper);
|
||||
if (scoreType != null) {
|
||||
query = new ChildrenQuery(parentChildIndexFieldData, parentType, childType, parentFilter, innerQuery, scoreType, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
if (minChildren > 1 || maxChildren > 0 || scoreType != ScoreType.NONE) {
|
||||
query = new ChildrenQuery(parentChildIndexFieldData, parentType, childType, parentFilter, innerQuery, scoreType, minChildren,
|
||||
maxChildren, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
} else {
|
||||
query = new ChildrenConstantScoreQuery(parentChildIndexFieldData, innerQuery, parentType, childType, parentFilter, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
query = new ChildrenConstantScoreQuery(parentChildIndexFieldData, innerQuery, parentType, childType, parentFilter,
|
||||
shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
}
|
||||
if (queryName != null) {
|
||||
parseContext.addNamedFilter(queryName, new CustomQueryWrappingFilter(query));
|
||||
|
|
|
@ -56,19 +56,21 @@ import java.util.Set;
|
|||
*/
|
||||
public class ChildrenQuery extends Query {
|
||||
|
||||
private final ParentChildIndexFieldData ifd;
|
||||
private final String parentType;
|
||||
private final String childType;
|
||||
private final Filter parentFilter;
|
||||
private final ScoreType scoreType;
|
||||
private Query originalChildQuery;
|
||||
private final int shortCircuitParentDocSet;
|
||||
private final Filter nonNestedDocsFilter;
|
||||
protected final ParentChildIndexFieldData ifd;
|
||||
protected final String parentType;
|
||||
protected final String childType;
|
||||
protected final Filter parentFilter;
|
||||
protected final ScoreType scoreType;
|
||||
protected Query originalChildQuery;
|
||||
protected final int minChildren;
|
||||
protected final int maxChildren;
|
||||
protected final int shortCircuitParentDocSet;
|
||||
protected final Filter nonNestedDocsFilter;
|
||||
|
||||
private Query rewrittenChildQuery;
|
||||
private IndexReader rewriteIndexReader;
|
||||
protected Query rewrittenChildQuery;
|
||||
protected IndexReader rewriteIndexReader;
|
||||
|
||||
public ChildrenQuery(ParentChildIndexFieldData ifd, String parentType, String childType, Filter parentFilter, Query childQuery, ScoreType scoreType, int shortCircuitParentDocSet, Filter nonNestedDocsFilter) {
|
||||
public ChildrenQuery(ParentChildIndexFieldData ifd, String parentType, String childType, Filter parentFilter, Query childQuery, ScoreType scoreType, int minChildren, int maxChildren, int shortCircuitParentDocSet, Filter nonNestedDocsFilter) {
|
||||
this.ifd = ifd;
|
||||
this.parentType = parentType;
|
||||
this.childType = childType;
|
||||
|
@ -77,6 +79,9 @@ public class ChildrenQuery extends Query {
|
|||
this.scoreType = scoreType;
|
||||
this.shortCircuitParentDocSet = shortCircuitParentDocSet;
|
||||
this.nonNestedDocsFilter = nonNestedDocsFilter;
|
||||
assert maxChildren == 0 || minChildren <= maxChildren;
|
||||
this.minChildren = minChildren > 1 ? minChildren : 0;
|
||||
this.maxChildren = maxChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -98,6 +103,12 @@ public class ChildrenQuery extends Query {
|
|||
if (getBoost() != that.getBoost()) {
|
||||
return false;
|
||||
}
|
||||
if (minChildren != that.minChildren) {
|
||||
return false;
|
||||
}
|
||||
if (maxChildren != that.maxChildren) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -106,13 +117,16 @@ public class ChildrenQuery extends Query {
|
|||
int result = originalChildQuery.hashCode();
|
||||
result = 31 * result + childType.hashCode();
|
||||
result = 31 * result + Float.floatToIntBits(getBoost());
|
||||
result = 31 * result + minChildren;
|
||||
result = 31 * result + maxChildren;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
return "ChildrenQuery[" + childType + "/" + parentType + "](" + originalChildQuery
|
||||
.toString(field) + ')' + ToStringUtils.boost(getBoost());
|
||||
int max = maxChildren == 0 ? Integer.MAX_VALUE : maxChildren;
|
||||
return "ChildrenQuery[min(" + Integer.toString(minChildren) + ") max(" + Integer.toString(max) + ")of " + childType + "/"
|
||||
+ parentType + "](" + originalChildQuery.toString(field) + ')' + ToStringUtils.boost(getBoost());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -144,12 +158,13 @@ public class ChildrenQuery extends Query {
|
|||
public Weight createWeight(IndexSearcher searcher) throws IOException {
|
||||
SearchContext sc = SearchContext.current();
|
||||
assert rewrittenChildQuery != null;
|
||||
assert rewriteIndexReader == searcher.getIndexReader() : "not equal, rewriteIndexReader=" + rewriteIndexReader + " searcher.getIndexReader()=" + searcher.getIndexReader();
|
||||
assert rewriteIndexReader == searcher.getIndexReader() : "not equal, rewriteIndexReader=" + rewriteIndexReader
|
||||
+ " searcher.getIndexReader()=" + searcher.getIndexReader();
|
||||
final Query childQuery = rewrittenChildQuery;
|
||||
|
||||
IndexFieldData.WithOrdinals globalIfd = ifd.getGlobalParentChild(parentType, searcher.getIndexReader());
|
||||
if (globalIfd == null) {
|
||||
// No docs of the specified type don't exist on this shard
|
||||
// No docs of the specified type exist on this shard
|
||||
return Queries.newMatchNoDocsQuery().createWeight(searcher);
|
||||
}
|
||||
IndexSearcher indexSearcher = new IndexSearcher(searcher.getIndexReader());
|
||||
|
@ -157,21 +172,35 @@ public class ChildrenQuery extends Query {
|
|||
|
||||
boolean abort = true;
|
||||
long numFoundParents;
|
||||
ParentOrdAndScoreCollector collector = null;
|
||||
ParentCollector collector = null;
|
||||
try {
|
||||
switch (scoreType) {
|
||||
if (minChildren == 0 && maxChildren == 0 && scoreType != ScoreType.NONE) {
|
||||
switch (scoreType) {
|
||||
case MAX:
|
||||
collector = new MaxCollector(globalIfd, sc);
|
||||
break;
|
||||
case SUM:
|
||||
collector = new SumCollector(globalIfd, sc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (collector == null) {
|
||||
switch (scoreType) {
|
||||
case MAX:
|
||||
collector = new MaxCountCollector(globalIfd, sc);
|
||||
break;
|
||||
case SUM:
|
||||
case AVG:
|
||||
collector = new AvgCollector(globalIfd, sc);
|
||||
collector = new SumCountAndAvgCollector(globalIfd, sc);
|
||||
break;
|
||||
case NONE:
|
||||
collector = new CountCollector(globalIfd, sc);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Are we missing a score type here? -- " + scoreType);
|
||||
}
|
||||
}
|
||||
|
||||
indexSearcher.search(childQuery, collector);
|
||||
numFoundParents = collector.foundParents();
|
||||
if (numFoundParents == 0) {
|
||||
|
@ -186,28 +215,34 @@ public class ChildrenQuery extends Query {
|
|||
sc.addReleasable(collector, Lifetime.COLLECTION);
|
||||
final Filter parentFilter;
|
||||
if (numFoundParents <= shortCircuitParentDocSet) {
|
||||
parentFilter = ParentIdsFilter.createShortCircuitFilter(
|
||||
nonNestedDocsFilter, sc, parentType, collector.values, collector.parentIdxs, numFoundParents
|
||||
);
|
||||
parentFilter = ParentIdsFilter.createShortCircuitFilter(nonNestedDocsFilter, sc, parentType, collector.values,
|
||||
collector.parentIdxs, numFoundParents);
|
||||
} else {
|
||||
parentFilter = new ApplyAcceptedDocsFilter(this.parentFilter);
|
||||
}
|
||||
return new ParentWeight(rewrittenChildQuery.createWeight(searcher), parentFilter, numFoundParents, collector);
|
||||
return new ParentWeight(rewrittenChildQuery.createWeight(searcher), parentFilter, numFoundParents, collector, minChildren,
|
||||
maxChildren);
|
||||
}
|
||||
|
||||
private final class ParentWeight extends Weight {
|
||||
protected class ParentWeight extends Weight {
|
||||
|
||||
private final Weight childWeight;
|
||||
private final Filter parentFilter;
|
||||
private final ParentOrdAndScoreCollector collector;
|
||||
protected final Weight childWeight;
|
||||
protected final Filter parentFilter;
|
||||
protected final ParentCollector collector;
|
||||
protected final int minChildren;
|
||||
protected final int maxChildren;
|
||||
|
||||
private long remaining;
|
||||
protected long remaining;
|
||||
protected float queryNorm;
|
||||
protected float queryWeight;
|
||||
|
||||
private ParentWeight(Weight childWeight, Filter parentFilter, long remaining, ParentOrdAndScoreCollector collector) {
|
||||
protected ParentWeight(Weight childWeight, Filter parentFilter, long remaining, ParentCollector collector, int minChildren, int maxChildren) {
|
||||
this.childWeight = childWeight;
|
||||
this.parentFilter = parentFilter;
|
||||
this.remaining = remaining;
|
||||
this.collector = collector;
|
||||
this.minChildren = minChildren;
|
||||
this.maxChildren = maxChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -221,14 +256,20 @@ public class ChildrenQuery extends Query {
|
|||
}
|
||||
|
||||
@Override
|
||||
public float getValueForNormalization() throws IOException {
|
||||
float sum = childWeight.getValueForNormalization();
|
||||
sum *= getBoost() * getBoost();
|
||||
return sum;
|
||||
public void normalize(float norm, float topLevelBoost) {
|
||||
this.queryNorm = norm * topLevelBoost;
|
||||
queryWeight *= this.queryNorm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void normalize(float norm, float topLevelBoost) {
|
||||
public float getValueForNormalization() throws IOException {
|
||||
queryWeight = getBoost();
|
||||
if (scoreType == ScoreType.NONE) {
|
||||
return queryWeight * queryWeight;
|
||||
}
|
||||
float sum = childWeight.getValueForNormalization();
|
||||
sum *= queryWeight * queryWeight;
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -241,59 +282,80 @@ public class ChildrenQuery extends Query {
|
|||
// We can't be sure of the fact that liveDocs have been applied, so we apply it here. The "remaining"
|
||||
// count down (short circuit) logic will then work as expected.
|
||||
DocIdSetIterator parents = BitsFilteredDocIdSet.wrap(parentsSet, context.reader().getLiveDocs()).iterator();
|
||||
BytesValues.WithOrdinals bytesValues = collector.globalIfd.load(context).getBytesValues(false);
|
||||
if (bytesValues == null) {
|
||||
return null;
|
||||
}
|
||||
switch (scoreType) {
|
||||
case AVG:
|
||||
return new AvgParentScorer(this, parents, collector, bytesValues.ordinals());
|
||||
default:
|
||||
return new ParentScorer(this, parents, collector, bytesValues.ordinals());
|
||||
}
|
||||
}
|
||||
|
||||
if (parents != null) {
|
||||
BytesValues.WithOrdinals bytesValues = collector.globalIfd.load(context).getBytesValues(false);
|
||||
if (bytesValues == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Ordinals.Docs globalOrdinals = bytesValues.ordinals();
|
||||
|
||||
if (minChildren > 0 || maxChildren != 0 || scoreType == ScoreType.NONE) {
|
||||
switch (scoreType) {
|
||||
case NONE:
|
||||
DocIdSetIterator parentIdIterator = new CountParentOrdIterator(this, parents, collector, globalOrdinals,
|
||||
minChildren, maxChildren);
|
||||
return ConstantScorer.create(parentIdIterator, this, queryWeight);
|
||||
case AVG:
|
||||
return new AvgParentCountScorer(this, parents, collector, globalOrdinals, minChildren, maxChildren);
|
||||
default:
|
||||
return new ParentCountScorer(this, parents, collector, globalOrdinals, minChildren, maxChildren);
|
||||
}
|
||||
}
|
||||
switch (scoreType) {
|
||||
case AVG:
|
||||
return new AvgParentScorer(this, parents, collector, globalOrdinals);
|
||||
default:
|
||||
return new ParentScorer(this, parents, collector, globalOrdinals);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class ParentOrdAndScoreCollector extends NoopCollector implements Releasable {
|
||||
protected abstract static class ParentCollector extends NoopCollector implements Releasable {
|
||||
|
||||
private final IndexFieldData.WithOrdinals globalIfd;
|
||||
protected final IndexFieldData.WithOrdinals globalIfd;
|
||||
protected final LongHash parentIdxs;
|
||||
protected final BigArrays bigArrays;
|
||||
protected FloatArray scores;
|
||||
protected final SearchContext searchContext;
|
||||
|
||||
protected Ordinals.Docs globalOrdinals;
|
||||
protected BytesValues.WithOrdinals values;
|
||||
protected Scorer scorer;
|
||||
|
||||
private ParentOrdAndScoreCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
protected ParentCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
this.globalIfd = globalIfd;
|
||||
this.searchContext = searchContext;
|
||||
this.bigArrays = searchContext.bigArrays();
|
||||
this.parentIdxs = new LongHash(512, bigArrays);
|
||||
this.scores = bigArrays.newFloatArray(512, false);
|
||||
this.searchContext = searchContext;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void collect(int doc) throws IOException {
|
||||
public final void collect(int doc) throws IOException {
|
||||
if (globalOrdinals != null) {
|
||||
final long globalOrdinal = globalOrdinals.getOrd(doc);
|
||||
if (globalOrdinal != Ordinals.MISSING_ORDINAL) {
|
||||
long parentIdx = parentIdxs.add(globalOrdinal);
|
||||
if (parentIdx >= 0) {
|
||||
scores = bigArrays.grow(scores, parentIdx + 1);
|
||||
scores.set(parentIdx, scorer.score());
|
||||
newParent(parentIdx);
|
||||
} else {
|
||||
parentIdx = -1 - parentIdx;
|
||||
doScore(parentIdx);
|
||||
existingParent(parentIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void doScore(long index) throws IOException {
|
||||
protected void newParent(long parentIdx) throws IOException {
|
||||
}
|
||||
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
}
|
||||
|
||||
public long foundParents() {
|
||||
return parentIdxs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -302,11 +364,6 @@ public class ChildrenQuery extends Query {
|
|||
if (values != null) {
|
||||
globalOrdinals = values.ordinals();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public long foundParents() {
|
||||
return parentIdxs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -314,71 +371,133 @@ public class ChildrenQuery extends Query {
|
|||
this.scorer = scorer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ElasticsearchException {
|
||||
Releasables.close(parentIdxs);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract static class ParentScoreCollector extends ParentCollector implements Releasable {
|
||||
|
||||
protected FloatArray scores;
|
||||
|
||||
protected ParentScoreCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
super(globalIfd, searchContext);
|
||||
this.scores = this.bigArrays.newFloatArray(512, false);
|
||||
}
|
||||
|
||||
protected void newParent(long parentIdx) throws IOException {
|
||||
scores = bigArrays.grow(scores, parentIdx + 1);
|
||||
scores.set(parentIdx, scorer.score());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ElasticsearchException {
|
||||
Releasables.close(parentIdxs, scores);
|
||||
}
|
||||
}
|
||||
|
||||
private final static class SumCollector extends ParentOrdAndScoreCollector {
|
||||
protected abstract static class ParentScoreCountCollector extends ParentScoreCollector implements Releasable {
|
||||
|
||||
protected IntArray occurrences;
|
||||
|
||||
protected ParentScoreCountCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
super(globalIfd, searchContext);
|
||||
this.occurrences = bigArrays.newIntArray(512, false);
|
||||
}
|
||||
|
||||
protected void newParent(long parentIdx) throws IOException {
|
||||
scores = bigArrays.grow(scores, parentIdx + 1);
|
||||
scores.set(parentIdx, scorer.score());
|
||||
occurrences = bigArrays.grow(occurrences, parentIdx + 1);
|
||||
occurrences.set(parentIdx, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ElasticsearchException {
|
||||
Releasables.close(parentIdxs, scores, occurrences);
|
||||
}
|
||||
}
|
||||
|
||||
private final static class CountCollector extends ParentCollector implements Releasable {
|
||||
|
||||
protected IntArray occurrences;
|
||||
|
||||
protected CountCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
super(globalIfd, searchContext);
|
||||
this.occurrences = bigArrays.newIntArray(512, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void newParent(long parentIdx) throws IOException {
|
||||
occurrences = bigArrays.grow(occurrences, parentIdx + 1);
|
||||
occurrences.set(parentIdx, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
occurrences.increment(parentIdx, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ElasticsearchException {
|
||||
Releasables.close(parentIdxs, occurrences);
|
||||
}
|
||||
}
|
||||
|
||||
private final static class SumCollector extends ParentScoreCollector {
|
||||
|
||||
private SumCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
super(globalIfd, searchContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doScore(long index) throws IOException {
|
||||
scores.increment(index, scorer.score());
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
scores.increment(parentIdx, scorer.score());
|
||||
}
|
||||
}
|
||||
|
||||
private final static class MaxCollector extends ParentOrdAndScoreCollector {
|
||||
private final static class MaxCollector extends ParentScoreCollector {
|
||||
|
||||
private MaxCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
super(globalIfd, searchContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doScore(long index) throws IOException {
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
float currentScore = scorer.score();
|
||||
if (currentScore > scores.get(index)) {
|
||||
scores.set(index, currentScore);
|
||||
if (currentScore > scores.get(parentIdx)) {
|
||||
scores.set(parentIdx, currentScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final static class AvgCollector extends ParentOrdAndScoreCollector {
|
||||
private final static class MaxCountCollector extends ParentScoreCountCollector {
|
||||
|
||||
private IntArray occurrences;
|
||||
|
||||
AvgCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
private MaxCountCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
super(globalIfd, searchContext);
|
||||
this.occurrences = bigArrays.newIntArray(512, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(int doc) throws IOException {
|
||||
if (globalOrdinals != null) {
|
||||
final long globalOrdinal = globalOrdinals.getOrd(doc);
|
||||
if (globalOrdinal != Ordinals.MISSING_ORDINAL) {
|
||||
long parentIdx = parentIdxs.add(globalOrdinal);
|
||||
if (parentIdx >= 0) {
|
||||
scores = bigArrays.grow(scores, parentIdx + 1);
|
||||
occurrences = bigArrays.grow(occurrences, parentIdx + 1);
|
||||
scores.set(parentIdx, scorer.score());
|
||||
occurrences.set(parentIdx, 1);
|
||||
} else {
|
||||
parentIdx = -1 - parentIdx;
|
||||
scores.increment(parentIdx, scorer.score());
|
||||
occurrences.increment(parentIdx, 1);
|
||||
}
|
||||
}
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
float currentScore = scorer.score();
|
||||
if (currentScore > scores.get(parentIdx)) {
|
||||
scores.set(parentIdx, currentScore);
|
||||
}
|
||||
occurrences.increment(parentIdx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private final static class SumCountAndAvgCollector extends ParentScoreCountCollector {
|
||||
|
||||
SumCountAndAvgCollector(IndexFieldData.WithOrdinals globalIfd, SearchContext searchContext) {
|
||||
super(globalIfd, searchContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ElasticsearchException {
|
||||
Releasables.close(parentIdxs, scores, occurrences);
|
||||
protected void existingParent(long parentIdx) throws IOException {
|
||||
scores.increment(parentIdx, scorer.score());
|
||||
occurrences.increment(parentIdx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,13 +513,13 @@ public class ChildrenQuery extends Query {
|
|||
int currentDocId = -1;
|
||||
float currentScore;
|
||||
|
||||
ParentScorer(ParentWeight parentWeight, DocIdSetIterator parentsIterator, ParentOrdAndScoreCollector collector, Ordinals.Docs globalOrdinals) {
|
||||
ParentScorer(ParentWeight parentWeight, DocIdSetIterator parentsIterator, ParentCollector collector, Ordinals.Docs globalOrdinals) {
|
||||
super(parentWeight);
|
||||
this.parentWeight = parentWeight;
|
||||
this.globalOrdinals = globalOrdinals;
|
||||
this.parentsIterator = parentsIterator;
|
||||
this.parentIds = collector.parentIdxs;
|
||||
this.scores = collector.scores;
|
||||
this.scores = ((ParentScoreCollector) collector).scores;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -408,6 +527,11 @@ public class ChildrenQuery extends Query {
|
|||
return currentScore;
|
||||
}
|
||||
|
||||
protected boolean acceptAndScore(long parentIdx) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int freq() throws IOException {
|
||||
// We don't have the original child query hit info here...
|
||||
|
@ -439,9 +563,10 @@ public class ChildrenQuery extends Query {
|
|||
|
||||
final long parentIdx = parentIds.find(globalOrdinal);
|
||||
if (parentIdx != -1) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
parentWeight.remaining--;
|
||||
return currentDocId;
|
||||
if (acceptAndScore(parentIdx)) {
|
||||
return currentDocId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -464,12 +589,12 @@ public class ChildrenQuery extends Query {
|
|||
|
||||
final long parentIdx = parentIds.find(globalOrdinal);
|
||||
if (parentIdx != -1) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
parentWeight.remaining--;
|
||||
return currentDocId;
|
||||
} else {
|
||||
return nextDoc();
|
||||
if (acceptAndScore(parentIdx)) {
|
||||
return currentDocId;
|
||||
}
|
||||
}
|
||||
return nextDoc();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -478,67 +603,103 @@ public class ChildrenQuery extends Query {
|
|||
}
|
||||
}
|
||||
|
||||
private static final class AvgParentScorer extends ParentScorer {
|
||||
private static class ParentCountScorer extends ParentScorer {
|
||||
|
||||
private final IntArray occurrences;
|
||||
protected final IntArray occurrences;
|
||||
protected final int minChildren;
|
||||
protected final int maxChildren;
|
||||
|
||||
AvgParentScorer(ParentWeight weight, DocIdSetIterator parentsIterator, ParentOrdAndScoreCollector collector, Ordinals.Docs globalOrdinals) {
|
||||
super(weight, parentsIterator, collector, globalOrdinals);
|
||||
this.occurrences = ((AvgCollector) collector).occurrences;
|
||||
ParentCountScorer(ParentWeight parentWeight, DocIdSetIterator parentsIterator, ParentCollector collector, Ordinals.Docs globalOrdinals, int minChildren, int maxChildren) {
|
||||
super(parentWeight, parentsIterator, (ParentScoreCollector) collector, globalOrdinals);
|
||||
this.minChildren = minChildren;
|
||||
this.maxChildren = maxChildren == 0 ? Integer.MAX_VALUE : maxChildren;
|
||||
this.occurrences = ((ParentScoreCountCollector) collector).occurrences;
|
||||
}
|
||||
|
||||
protected boolean acceptAndScore(long parentIdx) {
|
||||
int count = occurrences.get(parentIdx);
|
||||
if (count < minChildren || count > maxChildren) {
|
||||
return false;
|
||||
}
|
||||
return super.acceptAndScore(parentIdx);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AvgParentScorer extends ParentCountScorer {
|
||||
|
||||
AvgParentScorer(ParentWeight weight, DocIdSetIterator parentsIterator, ParentCollector collector, Ordinals.Docs globalOrdinals) {
|
||||
super(weight, parentsIterator, collector, globalOrdinals, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
protected boolean acceptAndScore(long parentIdx) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
currentScore /= occurrences.get(parentIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class AvgParentCountScorer extends ParentCountScorer {
|
||||
|
||||
AvgParentCountScorer(ParentWeight weight, DocIdSetIterator parentsIterator, ParentCollector collector, Ordinals.Docs globalOrdinals, int minChildren, int maxChildren) {
|
||||
super(weight, parentsIterator, collector, globalOrdinals, minChildren, maxChildren);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean acceptAndScore(long parentIdx) {
|
||||
int count = occurrences.get(parentIdx);
|
||||
if (count < minChildren || count > maxChildren) {
|
||||
return false;
|
||||
}
|
||||
currentScore = scores.get(parentIdx);
|
||||
currentScore /= occurrences.get(parentIdx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private final static class CountParentOrdIterator extends FilteredDocIdSetIterator {
|
||||
|
||||
private final LongHash parentIds;
|
||||
protected final IntArray occurrences;
|
||||
private final int minChildren;
|
||||
private final int maxChildren;
|
||||
private final Ordinals.Docs ordinals;
|
||||
private final ParentWeight parentWeight;
|
||||
|
||||
private CountParentOrdIterator(ParentWeight parentWeight, DocIdSetIterator innerIterator, ParentCollector collector, Ordinals.Docs ordinals, int minChildren, int maxChildren) {
|
||||
super(innerIterator);
|
||||
this.parentIds = ((CountCollector) collector).parentIdxs;
|
||||
this.occurrences = ((CountCollector) collector).occurrences;
|
||||
this.ordinals = ordinals;
|
||||
this.parentWeight = parentWeight;
|
||||
this.minChildren = minChildren;
|
||||
this.maxChildren = maxChildren == 0 ? Integer.MAX_VALUE : maxChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(int doc) {
|
||||
if (parentWeight.remaining == 0) {
|
||||
return currentDocId = NO_MORE_DOCS;
|
||||
try {
|
||||
advance(DocIdSetIterator.NO_MORE_DOCS);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
currentDocId = parentsIterator.nextDoc();
|
||||
if (currentDocId == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
return currentDocId;
|
||||
}
|
||||
|
||||
final long globalOrdinal = globalOrdinals.getOrd(currentDocId);
|
||||
if (globalOrdinal == Ordinals.MISSING_ORDINAL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final long parentIdx = parentIds.find(globalOrdinal);
|
||||
final long parentOrd = ordinals.getOrd(doc);
|
||||
if (parentOrd != Ordinals.MISSING_ORDINAL) {
|
||||
final long parentIdx = parentIds.find(parentOrd);
|
||||
if (parentIdx != -1) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
currentScore /= occurrences.get(parentIdx);
|
||||
parentWeight.remaining--;
|
||||
return currentDocId;
|
||||
int count = occurrences.get(parentIdx);
|
||||
if (count >= minChildren && count <= maxChildren) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advance(int target) throws IOException {
|
||||
if (parentWeight.remaining == 0) {
|
||||
return currentDocId = NO_MORE_DOCS;
|
||||
}
|
||||
|
||||
currentDocId = parentsIterator.advance(target);
|
||||
if (currentDocId == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
return currentDocId;
|
||||
}
|
||||
|
||||
final long globalOrdinal = globalOrdinals.getOrd(currentDocId);
|
||||
if (globalOrdinal == Ordinals.MISSING_ORDINAL) {
|
||||
return nextDoc();
|
||||
}
|
||||
|
||||
final long parentIdx = parentIds.find(globalOrdinal);
|
||||
if (parentIdx != -1) {
|
||||
currentScore = scores.get(parentIdx);
|
||||
currentScore /= occurrences.get(parentIdx);
|
||||
parentWeight.remaining--;
|
||||
return currentDocId;
|
||||
} else {
|
||||
return nextDoc();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,24 +24,33 @@ import org.elasticsearch.ElasticsearchIllegalArgumentException;
|
|||
* Defines how scores from child documents are mapped into the parent document.
|
||||
*/
|
||||
public enum ScoreType {
|
||||
|
||||
/**
|
||||
* Only the highest score of all matching child documents is mapped into the parent.
|
||||
* Only the highest score of all matching child documents is mapped into the
|
||||
* parent.
|
||||
*/
|
||||
MAX,
|
||||
|
||||
/**
|
||||
* The average score based on all matching child documents are mapped into the parent.
|
||||
* The average score based on all matching child documents are mapped into
|
||||
* the parent.
|
||||
*/
|
||||
AVG,
|
||||
|
||||
/**
|
||||
* The matching children scores is summed up and mapped into the parent.
|
||||
*/
|
||||
SUM;
|
||||
SUM,
|
||||
|
||||
/**
|
||||
* Scores are not taken into account
|
||||
*/
|
||||
NONE;
|
||||
|
||||
|
||||
public static ScoreType fromString(String type) {
|
||||
if ("max".equals(type)) {
|
||||
if ("none".equals(type)) {
|
||||
return NONE;
|
||||
} else if ("max".equals(type)) {
|
||||
return MAX;
|
||||
} else if ("avg".equals(type)) {
|
||||
return AVG;
|
||||
|
|
|
@ -78,7 +78,10 @@ public class ChildrenQueryTests extends ElasticsearchLuceneTestCase {
|
|||
ParentFieldMapper parentFieldMapper = SearchContext.current().mapperService().documentMapper("child").parentFieldMapper();
|
||||
ParentChildIndexFieldData parentChildIndexFieldData = SearchContext.current().fieldData().getForField(parentFieldMapper);
|
||||
Filter parentFilter = new TermFilter(new Term(TypeFieldMapper.NAME, "parent"));
|
||||
Query query = new ChildrenQuery(parentChildIndexFieldData, "parent", "child", parentFilter, childQuery, scoreType, 12, NonNestedDocsFilter.INSTANCE);
|
||||
int minChildren = random().nextInt(10);
|
||||
int maxChildren = scaledRandomIntBetween(minChildren, 10);
|
||||
Query query = new ChildrenQuery(parentChildIndexFieldData, "parent", "child", parentFilter, childQuery, scoreType, minChildren,
|
||||
maxChildren, 12, NonNestedDocsFilter.INSTANCE);
|
||||
QueryUtils.check(query);
|
||||
}
|
||||
|
||||
|
@ -219,7 +222,13 @@ public class ChildrenQueryTests extends ElasticsearchLuceneTestCase {
|
|||
int shortCircuitParentDocSet = random().nextInt(numParentDocs);
|
||||
ScoreType scoreType = ScoreType.values()[random().nextInt(ScoreType.values().length)];
|
||||
Filter nonNestedDocsFilter = random().nextBoolean() ? NonNestedDocsFilter.INSTANCE : null;
|
||||
Query query = new ChildrenQuery(parentChildIndexFieldData, "parent", "child", parentFilter, childQuery, scoreType, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
|
||||
// leave min/max set to 0 half the time
|
||||
int minChildren = random().nextInt(2) * scaledRandomIntBetween(0, 110);
|
||||
int maxChildren = random().nextInt(2) * scaledRandomIntBetween(minChildren, 110);
|
||||
|
||||
Query query = new ChildrenQuery(parentChildIndexFieldData, "parent", "child", parentFilter, childQuery, scoreType, minChildren,
|
||||
maxChildren, shortCircuitParentDocSet, nonNestedDocsFilter);
|
||||
query = new XFilteredQuery(query, filterMe);
|
||||
BitSetCollector collector = new BitSetCollector(indexReader.maxDoc());
|
||||
int numHits = 1 + random().nextInt(25);
|
||||
|
@ -239,14 +248,17 @@ public class ChildrenQueryTests extends ElasticsearchLuceneTestCase {
|
|||
TermsEnum termsEnum = terms.iterator(null);
|
||||
DocsEnum docsEnum = null;
|
||||
for (Map.Entry<String, FloatArrayList> entry : parentIdToChildScores.entrySet()) {
|
||||
TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(Uid.createUidAsBytes("parent", entry.getKey()));
|
||||
if (seekStatus == TermsEnum.SeekStatus.FOUND) {
|
||||
docsEnum = termsEnum.docs(slowAtomicReader.getLiveDocs(), docsEnum, DocsEnum.FLAG_NONE);
|
||||
expectedResult.set(docsEnum.nextDoc());
|
||||
mockScorer.scores = entry.getValue();
|
||||
expectedTopDocsCollector.collect(docsEnum.docID());
|
||||
} else if (seekStatus == TermsEnum.SeekStatus.END) {
|
||||
break;
|
||||
int count = entry.getValue().elementsCount;
|
||||
if (count >= minChildren && (maxChildren == 0 || count <= maxChildren)) {
|
||||
TermsEnum.SeekStatus seekStatus = termsEnum.seekCeil(Uid.createUidAsBytes("parent", entry.getKey()));
|
||||
if (seekStatus == TermsEnum.SeekStatus.FOUND) {
|
||||
docsEnum = termsEnum.docs(slowAtomicReader.getLiveDocs(), docsEnum, DocsEnum.FLAG_NONE);
|
||||
expectedResult.set(docsEnum.nextDoc());
|
||||
mockScorer.scores = entry.getValue();
|
||||
expectedTopDocsCollector.collect(docsEnum.docID());
|
||||
} else if (seekStatus == TermsEnum.SeekStatus.END) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,9 @@ class MockScorer extends Scorer {
|
|||
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
if (scoreType == ScoreType.NONE) {
|
||||
return 1.0f;
|
||||
}
|
||||
float aggregateScore = 0;
|
||||
for (int i = 0; i < scores.elementsCount; i++) {
|
||||
float score = scores.buffer[i];
|
||||
|
|
|
@ -59,6 +59,7 @@ import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilde
|
|||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.index.query.FilterBuilders.*;
|
||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||
import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.factorFunction;
|
||||
import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.scriptFunction;
|
||||
import static org.elasticsearch.search.facet.FacetBuilders.termsFacet;
|
||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
|
||||
|
@ -1999,6 +2000,480 @@ public class SimpleChildQuerySearchTests extends ElasticsearchIntegrationTest {
|
|||
assertThat(response.getHits().getAt(0).id(), equalTo("1"));
|
||||
}
|
||||
|
||||
List<IndexRequestBuilder> createMinMaxDocBuilders() {
|
||||
List<IndexRequestBuilder> indexBuilders = new ArrayList<>();
|
||||
// Parent 1 and its children
|
||||
indexBuilders.add(client().prepareIndex().setType("parent").setId("1").setIndex("test").setSource("id",1));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("10").setIndex("test")
|
||||
.setSource("foo", "one").setParent("1"));
|
||||
|
||||
// Parent 2 and its children
|
||||
indexBuilders.add(client().prepareIndex().setType("parent").setId("2").setIndex("test").setSource("id",2));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("11").setIndex("test")
|
||||
.setSource("foo", "one").setParent("2"));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("12").setIndex("test")
|
||||
.setSource("foo", "one two").setParent("2"));
|
||||
|
||||
// Parent 3 and its children
|
||||
indexBuilders.add(client().prepareIndex().setType("parent").setId("3").setIndex("test").setSource("id",3));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("13").setIndex("test")
|
||||
.setSource("foo", "one").setParent("3"));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("14").setIndex("test")
|
||||
.setSource("foo", "one two").setParent("3"));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("15").setIndex("test")
|
||||
.setSource("foo", "one two three").setParent("3"));
|
||||
|
||||
// Parent 4 and its children
|
||||
indexBuilders.add(client().prepareIndex().setType("parent").setId("4").setIndex("test").setSource("id",4));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("16").setIndex("test")
|
||||
.setSource("foo", "one").setParent("4"));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("17").setIndex("test")
|
||||
.setSource("foo", "one two").setParent("4"));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("18").setIndex("test")
|
||||
.setSource("foo", "one two three").setParent("4"));
|
||||
indexBuilders.add(client().prepareIndex().setType("child").setId("19").setIndex("test")
|
||||
.setSource("foo", "one two three four").setParent("4"));
|
||||
|
||||
return indexBuilders;
|
||||
}
|
||||
|
||||
SearchResponse MinMaxQuery(String scoreType, int minChildren, int maxChildren, int cutoff) throws SearchPhaseExecutionException {
|
||||
return client()
|
||||
.prepareSearch("test")
|
||||
.setQuery(
|
||||
QueryBuilders
|
||||
.hasChildQuery(
|
||||
"child",
|
||||
QueryBuilders.functionScoreQuery(constantScoreQuery(FilterBuilders.termFilter("foo", "two"))).boostMode("replace").scoreMode("sum")
|
||||
.add(FilterBuilders.matchAllFilter(), factorFunction(1))
|
||||
.add(FilterBuilders.termFilter("foo", "three"), factorFunction(1))
|
||||
.add(FilterBuilders.termFilter("foo", "four"), factorFunction(1))).scoreType(scoreType)
|
||||
.minChildren(minChildren).maxChildren(maxChildren).setShortCircuitCutoff(cutoff))
|
||||
.addSort("_score", SortOrder.DESC).addSort("id", SortOrder.ASC).get();
|
||||
}
|
||||
|
||||
SearchResponse MinMaxFilter( int minChildren, int maxChildren, int cutoff) throws SearchPhaseExecutionException {
|
||||
return client()
|
||||
.prepareSearch("test")
|
||||
.setQuery(
|
||||
QueryBuilders.constantScoreQuery(FilterBuilders.hasChildFilter("child", termFilter("foo", "two"))
|
||||
.minChildren(minChildren).maxChildren(maxChildren).setShortCircuitCutoff(cutoff)))
|
||||
.addSort("id", SortOrder.ASC).setTrackScores(true).get();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMinMaxChildren() throws Exception {
|
||||
assertAcked(prepareCreate("test").addMapping("parent").addMapping("child", "_parent", "type=parent"));
|
||||
ensureGreen();
|
||||
|
||||
indexRandom(true, createMinMaxDocBuilders().toArray(new IndexRequestBuilder[0]));
|
||||
SearchResponse response;
|
||||
int cutoff = getRandom().nextInt(4);
|
||||
|
||||
// Score mode = NONE
|
||||
response = MinMaxQuery("none", 0, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("none", 1, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("none", 2, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("none", 3, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("none", 4, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = MinMaxQuery("none", 0, 4, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("none", 0, 3, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("none", 0, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("none", 2, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
|
||||
try {
|
||||
response = MinMaxQuery("none", 3, 2, cutoff);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.getMessage(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
}
|
||||
|
||||
// Score mode = SUM
|
||||
response = MinMaxQuery("sum", 0, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(6f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("sum", 1, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(6f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("sum", 2, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(6f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(3f));
|
||||
|
||||
response = MinMaxQuery("sum", 3, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(6f));
|
||||
|
||||
response = MinMaxQuery("sum", 4, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = MinMaxQuery("sum", 0, 4, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(6f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("sum", 0, 3, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(6f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("sum", 0, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("sum", 2, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
|
||||
try {
|
||||
response = MinMaxQuery("sum", 3, 2, cutoff);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.getMessage(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
}
|
||||
|
||||
// Score mode = MAX
|
||||
response = MinMaxQuery("max", 0, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("max", 1, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("max", 2, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(2f));
|
||||
|
||||
response = MinMaxQuery("max", 3, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
|
||||
response = MinMaxQuery("max", 4, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = MinMaxQuery("max", 0, 4, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("max", 0, 3, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(3f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("max", 0, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("max", 2, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
|
||||
try {
|
||||
response = MinMaxQuery("max", 3, 2, cutoff);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.getMessage(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
}
|
||||
|
||||
// Score mode = AVG
|
||||
response = MinMaxQuery("avg", 0, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1.5f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("avg", 1, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1.5f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("avg", 2, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1.5f));
|
||||
|
||||
response = MinMaxQuery("avg", 3, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
|
||||
response = MinMaxQuery("avg", 4, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = MinMaxQuery("avg", 0, 4, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1.5f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("avg", 0, 3, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(2f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1.5f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("avg", 0, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1.5f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxQuery("avg", 2, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1.5f));
|
||||
|
||||
try {
|
||||
response = MinMaxQuery("avg", 3, 2, cutoff);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.getMessage(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
}
|
||||
|
||||
// HasChildFilter
|
||||
response = MinMaxFilter(0, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxFilter(1, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxFilter(2, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxFilter(3, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxFilter(4, 0, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(0l));
|
||||
|
||||
response = MinMaxFilter(0, 4, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxFilter(0, 3, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(3l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[2].id(), equalTo("4"));
|
||||
assertThat(response.getHits().hits()[2].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxFilter(0, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(2l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("2"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
assertThat(response.getHits().hits()[1].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[1].score(), equalTo(1f));
|
||||
|
||||
response = MinMaxFilter(2, 2, cutoff);
|
||||
|
||||
assertThat(response.getHits().totalHits(), equalTo(1l));
|
||||
assertThat(response.getHits().hits()[0].id(), equalTo("3"));
|
||||
assertThat(response.getHits().hits()[0].score(), equalTo(1f));
|
||||
|
||||
try {
|
||||
response = MinMaxFilter(3, 2, cutoff);
|
||||
fail();
|
||||
} catch (SearchPhaseExecutionException e) {
|
||||
assertThat(e.getMessage(), containsString("[has_child] 'max_children' is less than 'min_children'"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static HasChildFilterBuilder hasChildFilter(String type, QueryBuilder queryBuilder) {
|
||||
HasChildFilterBuilder hasChildFilterBuilder = FilterBuilders.hasChildFilter(type, queryBuilder);
|
||||
hasChildFilterBuilder.setShortCircuitCutoff(randomInt(10));
|
||||
|
|
Loading…
Reference in New Issue