Parse has_child query/filter after child type has been parsed
Fixes #5783 Fixes #5838
This commit is contained in:
parent
8136a38b3f
commit
029b13cf68
|
@ -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 + "]");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -113,6 +113,10 @@ public class QueryParseContext {
|
|||
return this.index;
|
||||
}
|
||||
|
||||
public void parser(XContentParser parser) {
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
public XContentParser parser() {
|
||||
return parser;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue