Parse has_child query/filter after child type has been parsed

Fixes #5783
Fixes #5838
This commit is contained in:
Lee Hinman 2014-04-16 11:08:42 -06:00
parent 8136a38b3f
commit 029b13cf68
9 changed files with 335 additions and 105 deletions

View File

@ -22,9 +22,9 @@ import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.XConstantScoreQuery;
import org.elasticsearch.common.lucene.search.XFilteredQuery;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.support.XContentStructure;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
@ -56,38 +56,30 @@ public class HasChildFilterParser implements FilterParser {
public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
Query query = null;
boolean queryFound = false;
boolean filterFound = false;
String childType = null;
int shortCircuitParentDocSet = 8192; // Tests show a cut of point between 8192 and 16384.
String filterName = null;
String currentFieldName = null;
XContentParser.Token token;
XContentStructure.InnerQuery innerQuery = null;
XContentStructure.InnerFilter innerFilter = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
// Usually, the query would be parsed here, but the child
// type may not have been extracted yet, so use the
// XContentStructure.<type> facade to parse if available,
// or delay parsing if not.
if ("query".equals(currentFieldName)) {
// TODO we need to set the type, but, `query` can come before `type`...
// since we switch types, make sure we change the context
String[] origTypes = QueryParseContext.setTypesWithPrevious(childType == null ? null : new String[]{childType});
try {
query = parseContext.parseInnerQuery();
queryFound = true;
} finally {
QueryParseContext.setTypes(origTypes);
}
innerQuery = new XContentStructure.InnerQuery(parseContext, childType == null ? null : new String[] {childType});
queryFound = true;
} else if ("filter".equals(currentFieldName)) {
// TODO handle `filter` element before `type` element...
String[] origTypes = QueryParseContext.setTypesWithPrevious(childType == null ? null : new String[]{childType});
try {
Filter innerFilter = parseContext.parseInnerFilter();
query = new XConstantScoreQuery(innerFilter);
queryFound = true;
} finally {
QueryParseContext.setTypes(origTypes);
}
innerFilter = new XContentStructure.InnerFilter(parseContext, childType == null ? null : new String[] {childType});
filterFound = true;
} else {
throw new QueryParsingException(parseContext.index(), "[has_child] filter does not support [" + currentFieldName + "]");
}
@ -109,16 +101,24 @@ public class HasChildFilterParser implements FilterParser {
}
}
}
if (!queryFound) {
throw new QueryParsingException(parseContext.index(), "[has_child] filter requires 'query' field");
}
if (query == null) {
return null;
if (!queryFound && !filterFound) {
throw new QueryParsingException(parseContext.index(), "[has_child] filter requires 'query' or 'filter' field");
}
if (childType == null) {
throw new QueryParsingException(parseContext.index(), "[has_child] filter requires 'type' field");
}
Query query;
if (queryFound) {
query = innerQuery.asQuery(childType);
} else {
query = innerFilter.asFilter(childType);
}
if (query == null) {
return null;
}
DocumentMapper childDocMapper = parseContext.mapperService().documentMapper(childType);
if (childDocMapper == null) {
throw new QueryParsingException(parseContext.index(), "No mapping for for type [" + childType + "]");

View File

@ -26,6 +26,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.XConstantScoreQuery;
import org.elasticsearch.common.lucene.search.XFilteredQuery;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.support.XContentStructure;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
@ -55,7 +56,6 @@ public class HasChildQueryParser implements QueryParser {
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
Query innerQuery = null;
boolean queryFound = false;
float boost = 1.0f;
String childType = null;
@ -65,20 +65,18 @@ public class HasChildQueryParser implements QueryParser {
String currentFieldName = null;
XContentParser.Token token;
XContentStructure.InnerQuery iq = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
// Usually, the query would be parsed here, but the child
// type may not have been extracted yet, so use the
// XContentStructure.<type> facade to parse if available,
// or delay parsing if not.
if ("query".equals(currentFieldName)) {
// TODO we need to set the type, but, `query` can come before `type`... (see HasChildFilterParser)
// since we switch types, make sure we change the context
String[] origTypes = QueryParseContext.setTypesWithPrevious(childType == null ? null : new String[]{childType});
try {
innerQuery = parseContext.parseInnerQuery();
queryFound = true;
} finally {
QueryParseContext.setTypes(origTypes);
}
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 + "]");
}
@ -111,12 +109,15 @@ public class HasChildQueryParser implements QueryParser {
if (!queryFound) {
throw new QueryParsingException(parseContext.index(), "[has_child] requires 'query' field");
}
if (innerQuery == null) {
return null;
}
if (childType == null) {
throw new QueryParsingException(parseContext.index(), "[has_child] requires 'type' field");
}
Query innerQuery = iq.asQuery(childType);
if (innerQuery == null) {
return null;
}
innerQuery.setBoost(boost);
DocumentMapper childDocMapper = parseContext.mapperService().documentMapper(childType);

View File

@ -25,10 +25,9 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.NotFilter;
import org.elasticsearch.common.lucene.search.XBooleanFilter;
import org.elasticsearch.common.lucene.search.XConstantScoreQuery;
import org.elasticsearch.common.lucene.search.XFilteredQuery;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.cache.filter.support.CacheKeyFilter;
import org.elasticsearch.index.query.support.XContentStructure;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
@ -61,38 +60,29 @@ public class HasParentFilterParser implements FilterParser {
public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
Query query = null;
boolean queryFound = false;
boolean filterFound = false;
String parentType = null;
boolean cache = false;
CacheKeyFilter.Key cacheKey = null;
String filterName = null;
String currentFieldName = null;
XContentParser.Token token;
XContentStructure.InnerQuery innerQuery = null;
XContentStructure.InnerFilter innerFilter = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
// Usually, the query would be parsed here, but the child
// type may not have been extracted yet, so use the
// XContentStructure.<type> facade to parse if available,
// or delay parsing if not.
if ("query".equals(currentFieldName)) {
// TODO handle `query` element before `type` element...
String[] origTypes = QueryParseContext.setTypesWithPrevious(parentType == null ? null : new String[]{parentType});
try {
query = parseContext.parseInnerQuery();
queryFound = true;
} finally {
QueryParseContext.setTypes(origTypes);
}
innerQuery = new XContentStructure.InnerQuery(parseContext, parentType == null ? null : new String[] {parentType});
queryFound = true;
} else if ("filter".equals(currentFieldName)) {
// TODO handle `filter` element before `type` element...
String[] origTypes = QueryParseContext.setTypesWithPrevious(parentType == null ? null : new String[]{parentType});
try {
Filter innerFilter = parseContext.parseInnerFilter();
query = new XConstantScoreQuery(innerFilter);
queryFound = true;
} finally {
QueryParseContext.setTypes(origTypes);
}
innerFilter = new XContentStructure.InnerFilter(parseContext, parentType == null ? null : new String[] {parentType});
filterFound = true;
} else {
throw new QueryParsingException(parseContext.index(), "[has_parent] filter does not support [" + currentFieldName + "]");
}
@ -104,25 +94,32 @@ public class HasParentFilterParser implements FilterParser {
} else if ("_name".equals(currentFieldName)) {
filterName = parser.text();
} else if ("_cache".equals(currentFieldName)) {
cache = parser.booleanValue();
// noop to be backwards compatible
} else if ("_cache_key".equals(currentFieldName) || "_cacheKey".equals(currentFieldName)) {
cacheKey = new CacheKeyFilter.Key(parser.text());
// noop to be backwards compatible
} else {
throw new QueryParsingException(parseContext.index(), "[has_parent] filter does not support [" + currentFieldName + "]");
}
}
}
if (!queryFound) {
throw new QueryParsingException(parseContext.index(), "[has_parent] filter requires 'query' field");
if (!queryFound && !filterFound) {
throw new QueryParsingException(parseContext.index(), "[has_parent] filter requires 'query' or 'filter' field");
}
if (query == null) {
return null;
}
if (parentType == null) {
throw new QueryParsingException(parseContext.index(), "[has_parent] filter requires 'parent_type' field");
}
Query query;
if (queryFound) {
query = innerQuery.asQuery(parentType);
} else {
query = innerFilter.asFilter(parentType);
}
if (query == null) {
return null;
}
DocumentMapper parentDocMapper = parseContext.mapperService().documentMapper(parentType);
if (parentDocMapper == null) {
throw new QueryParsingException(parseContext.index(), "[has_parent] filter configured 'parent_type' [" + parentType + "] is not a valid type");

View File

@ -28,6 +28,7 @@ import org.elasticsearch.common.lucene.search.XBooleanFilter;
import org.elasticsearch.common.lucene.search.XConstantScoreQuery;
import org.elasticsearch.common.lucene.search.XFilteredQuery;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.support.XContentStructure;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
@ -58,7 +59,6 @@ public class HasParentQueryParser implements QueryParser {
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
Query innerQuery = null;
boolean queryFound = false;
float boost = 1.0f;
String parentType = null;
@ -67,19 +67,18 @@ public class HasParentQueryParser implements QueryParser {
String currentFieldName = null;
XContentParser.Token token;
XContentStructure.InnerQuery iq = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
// Usually, the query would be parsed here, but the child
// type may not have been extracted yet, so use the
// XContentStructure.<type> facade to parse if available,
// or delay parsing if not.
if ("query".equals(currentFieldName)) {
// TODO handle `query` element before `type` element...
String[] origTypes = QueryParseContext.setTypesWithPrevious(parentType == null ? null : new String[]{parentType});
try {
innerQuery = parseContext.parseInnerQuery();
queryFound = true;
} finally {
QueryParseContext.setTypes(origTypes);
}
iq = new XContentStructure.InnerQuery(parseContext, parentType == null ? null : new String[] {parentType});
queryFound = true;
} else {
throw new QueryParsingException(parseContext.index(), "[has_parent] query does not support [" + currentFieldName + "]");
}
@ -114,14 +113,16 @@ public class HasParentQueryParser implements QueryParser {
if (!queryFound) {
throw new QueryParsingException(parseContext.index(), "[has_parent] query requires 'query' field");
}
if (innerQuery == null) {
return null;
}
if (parentType == null) {
throw new QueryParsingException(parseContext.index(), "[has_parent] query requires 'parent_type' field");
}
Query innerQuery = iq.asQuery(parentType);
if (innerQuery == null) {
return null;
}
DocumentMapper parentDocMapper = parseContext.mapperService().documentMapper(parentType);
if (parentDocMapper == null) {
throw new QueryParsingException(parseContext.index(), "[has_parent] query configured 'parent_type' [" + parentType + "] is not a valid type");

View File

@ -26,6 +26,7 @@ import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.support.XContentStructure;
import java.io.IOException;
import java.util.ArrayList;
@ -54,8 +55,7 @@ public class IndicesQueryParser implements QueryParser {
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
Query query = null;
Query noMatchQuery = Queries.newMatchAllQuery();
Query noMatchQuery = null;
boolean queryFound = false;
boolean indicesFound = false;
boolean currentIndexMatchesIndices = false;
@ -63,24 +63,17 @@ public class IndicesQueryParser implements QueryParser {
String currentFieldName = null;
XContentParser.Token token;
XContentStructure.InnerQuery innerQuery = null;
XContentStructure.InnerQuery innerNoMatchQuery = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
if ("query".equals(currentFieldName)) {
//TODO We are able to decide whether to parse the query or not only if indices in the query appears first
innerQuery = new XContentStructure.InnerQuery(parseContext, null);
queryFound = true;
if (indicesFound && !currentIndexMatchesIndices) {
parseContext.parser().skipChildren(); // skip the query object without parsing it
} else {
query = parseContext.parseInnerQuery();
}
} else if ("no_match_query".equals(currentFieldName)) {
if (indicesFound && currentIndexMatchesIndices) {
parseContext.parser().skipChildren(); // skip the query object without parsing it
} else {
noMatchQuery = parseContext.parseInnerQuery();
}
innerNoMatchQuery = new XContentStructure.InnerQuery(parseContext, null);
} else {
throw new QueryParsingException(parseContext.index(), "[indices] query does not support [" + currentFieldName + "]");
}
@ -132,9 +125,19 @@ public class IndicesQueryParser implements QueryParser {
Query chosenQuery;
if (currentIndexMatchesIndices) {
chosenQuery = query;
chosenQuery = innerQuery.asQuery();
} else {
chosenQuery = noMatchQuery;
// If noMatchQuery is set, it means "no_match_query" was "all" or "none"
if (noMatchQuery != null) {
chosenQuery = noMatchQuery;
} else {
// There might be no "no_match_query" set, so default to the match_all if not set
if (innerNoMatchQuery == null) {
chosenQuery = Queries.newMatchAllQuery();
} else {
chosenQuery = innerNoMatchQuery.asQuery();
}
}
}
if (queryName != null) {
parseContext.addNamedQuery(queryName, chosenQuery);

View File

@ -113,6 +113,10 @@ public class QueryParseContext {
return this.index;
}
public void parser(XContentParser parser) {
this.parser = parser;
}
public XContentParser parser() {
return parser;
}

View File

@ -24,6 +24,7 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.XFilteredQuery;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.support.XContentStructure;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
@ -55,7 +56,6 @@ public class TopChildrenQueryParser implements QueryParser {
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
Query innerQuery = null;
boolean queryFound = false;
float boost = 1.0f;
String childType = null;
@ -66,20 +66,18 @@ public class TopChildrenQueryParser implements QueryParser {
String currentFieldName = null;
XContentParser.Token token;
XContentStructure.InnerQuery iq = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
// Usually, the query would be parsed here, but the child
// type may not have been extracted yet, so use the
// 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});
queryFound = true;
// TODO we need to set the type, but, `query` can come before `type`... (see HasChildFilterParser)
// since we switch types, make sure we change the context
String[] origTypes = QueryParseContext.setTypesWithPrevious(childType == null ? null : new String[]{childType});
try {
innerQuery = parseContext.parseInnerQuery();
} finally {
QueryParseContext.setTypes(origTypes);
}
} else {
throw new QueryParsingException(parseContext.index(), "[top_children] query does not support [" + currentFieldName + "]");
}
@ -112,6 +110,8 @@ public class TopChildrenQueryParser implements QueryParser {
throw new QueryParsingException(parseContext.index(), "[top_children] requires 'type' field");
}
Query innerQuery = iq.asQuery(childType);
if (innerQuery == null) {
return null;
}

View File

@ -0,0 +1,199 @@
/*
* 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.Filter;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.lucene.search.XConstantScoreQuery;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import java.io.IOException;
/**
* XContentStructure is a class used to capture a subset of query, to be parsed
* at a later time when more information (in this case, types) is available.
* Note that using this class requires copying the parser's data, which will
* result in additional overhead versus parsing the inner query/filter
* immediately, however, the extra overhead means that the type not be
* extracted prior to query parsing (in the case of unordered JSON).
*/
public abstract class XContentStructure {
private final QueryParseContext parseContext;
private BytesReference innerBytes;
/**
* Create a new XContentStructure for the current parsing context.
*/
public XContentStructure(QueryParseContext queryParseContext) {
this.parseContext = queryParseContext;
}
/**
* "Freeze" the parsing content, which means copying the current parser's
* structure into an internal {@link BytesReference} to be parsed later.
* @return the original XContentStructure object
*/
public XContentStructure freeze() throws IOException {
this.bytes(XContentFactory.smileBuilder().copyCurrentStructure(parseContext.parser()).bytes());
return this;
}
/**
* Set the bytes to be used for parsing
*/
public void bytes(BytesReference innerBytes) {
this.innerBytes = innerBytes;
}
/**
* Return the bytes that are going to be used for parsing
*/
public BytesReference bytes() {
return this.innerBytes;
}
/**
* Use the captured bytes to parse the inner query using the specified
* types. The original QueryParseContext's parser is switched during this
* parsing, so this method is NOT thread-safe.
* @param types types to be used during the inner query parsing
* @return {@link Query} parsed from the bytes captured in {@code freeze()}
*/
public Query asQuery(String... types) throws IOException {
BytesReference br = this.bytes();
assert br != null : "innerBytes must be set with .bytes(bytes) or .freeze() before parsing";
XContentParser innerParser = XContentHelper.createParser(br);
String[] origTypes = QueryParseContext.setTypesWithPrevious(types);
XContentParser old = parseContext.parser();
parseContext.parser(innerParser);
try {
return parseContext.parseInnerQuery();
} finally {
parseContext.parser(old);
QueryParseContext.setTypes(origTypes);
}
}
/**
* Use the captured bytes to parse the inner filter using the specified
* types. The original QueryParseContext's parser is switched during this
* parsing, so this method is NOT thread-safe.
* @param types types to be used during the inner filter parsing
* @return {@link XConstantScoreQuery} wrapping the filter parsed from the bytes captured in {@code freeze()}
*/
public Query asFilter(String... types) throws IOException {
BytesReference br = this.bytes();
assert br != null : "innerBytes must be set with .bytes(bytes) or .freeze() before parsing";
XContentParser innerParser = XContentHelper.createParser(br);
String[] origTypes = QueryParseContext.setTypesWithPrevious(types);
XContentParser old = parseContext.parser();
parseContext.parser(innerParser);
try {
Filter innerFilter = parseContext.parseInnerFilter();
return new XConstantScoreQuery(innerFilter);
} finally {
parseContext.parser(old);
QueryParseContext.setTypes(origTypes);
}
}
/**
* InnerQuery is an extension of {@code XContentStructure} that eagerly
* parses the query in a streaming manner if the types are available at
* construction time.
*/
public static class InnerQuery extends XContentStructure {
private Query query = null;
public InnerQuery(QueryParseContext parseContext1, @Nullable String... types) throws IOException {
super(parseContext1);
if (types != null) {
String[] origTypes = QueryParseContext.setTypesWithPrevious(types);
try {
query = parseContext1.parseInnerQuery();
} finally {
QueryParseContext.setTypes(origTypes);
}
} else {
BytesReference innerBytes = XContentFactory.smileBuilder().copyCurrentStructure(parseContext1.parser()).bytes();
super.bytes(innerBytes);
}
}
/**
* Return the query represented by the XContentStructure object,
* returning the cached Query if it has already been parsed.
* @param types types to be used during the inner query parsing
*/
@Override
public Query asQuery(String... types) throws IOException {
if (this.query == null) {
this.query = super.asQuery(types);
}
return this.query;
}
}
/**
* InnerFilter is an extension of {@code XContentStructure} that eagerly
* parses the filter in a streaming manner if the types are available at
* construction time.
*/
public static class InnerFilter extends XContentStructure {
private Query query = null;
public InnerFilter(QueryParseContext parseContext1, @Nullable String... types) throws IOException {
super(parseContext1);
if (types != null) {
String[] origTypes = QueryParseContext.setTypesWithPrevious(types);
try {
Filter innerFilter = parseContext1.parseInnerFilter();
query = new XConstantScoreQuery(innerFilter);
} finally {
QueryParseContext.setTypes(origTypes);
}
} else {
BytesReference innerBytes = XContentFactory.smileBuilder().copyCurrentStructure(parseContext1.parser()).bytes();
super.bytes(innerBytes);
}
}
/**
* Return the filter as an
* {@link org.elasticsearch.common.lucene.search.XConstantScoreQuery}
* represented by the XContentStructure object,
* returning the cached Query if it has already been parsed.
* @param types types to be used during the inner filter parsing
*/
@Override
public Query asFilter(String... types) throws IOException {
if (this.query == null) {
this.query = super.asFilter(types);
}
return this.query;
}
}
}

View File

@ -1924,6 +1924,31 @@ public class SimpleChildQuerySearchTests extends ElasticsearchIntegrationTest {
assertThat(statsResponse.getIndex("test").getTotal().getFilterCache().getMemorySizeInBytes(), greaterThan(initialCacheSize));
}
// https://github.com/elasticsearch/elasticsearch/issues/5783
@Test
public void testQueryBeforeChildType() throws Exception {
assertAcked(prepareCreate("test")
.addMapping("features")
.addMapping("posts", "_parent", "type=features")
.addMapping("specials"));
ensureGreen();
client().prepareIndex("test", "features", "1").setSource("field", "foo").get();
client().prepareIndex("test", "posts", "1").setParent("1").setSource("field", "bar").get();
refresh();
SearchResponse resp;
resp = client().prepareSearch("test")
.setSource("{\"query\": {\"has_child\": {\"type\": \"posts\", \"query\": {\"match\": {\"field\": \"bar\"}}}}}").get();
assertHitCount(resp, 1L);
// Now reverse the order for the type after the query
resp = client().prepareSearch("test")
.setSource("{\"query\": {\"has_child\": {\"query\": {\"match\": {\"field\": \"bar\"}}, \"type\": \"posts\"}}}").get();
assertHitCount(resp, 1L);
}
private static HasChildFilterBuilder hasChildFilter(String type, QueryBuilder queryBuilder) {
HasChildFilterBuilder hasChildFilterBuilder = FilterBuilders.hasChildFilter(type, queryBuilder);
hasChildFilterBuilder.setShortCircuitCutoff(randomInt(10));