diff --git a/src/main/java/org/elasticsearch/index/query/HasChildFilterParser.java b/src/main/java/org/elasticsearch/index/query/HasChildFilterParser.java index d69b2885d721c..0e725afea0eb7 100644 --- a/src/main/java/org/elasticsearch/index/query/HasChildFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/HasChildFilterParser.java @@ -22,9 +22,9 @@ 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.mapper.DocumentMapper; import org.elasticsearch.index.search.child.ChildrenConstantScoreQuery; import org.elasticsearch.index.search.child.CustomQueryWrappingFilter; @@ -54,38 +54,30 @@ public String[] names() { 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. 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 + "]"); } @@ -107,16 +99,24 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar } } } - 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 + "]"); diff --git a/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java b/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java index c5b1ed98bbbd2..b502b9ef4e4f6 100644 --- a/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java @@ -26,6 +26,7 @@ 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.mapper.DocumentMapper; import org.elasticsearch.index.search.child.*; import org.elasticsearch.index.search.nested.NonNestedDocsFilter; @@ -53,7 +54,6 @@ public String[] names() { 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; @@ -63,20 +63,18 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars 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. 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 + "]"); } @@ -109,12 +107,15 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars 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); diff --git a/src/main/java/org/elasticsearch/index/query/HasParentFilterParser.java b/src/main/java/org/elasticsearch/index/query/HasParentFilterParser.java index f91088379f6be..4bfa918fa9c87 100644 --- a/src/main/java/org/elasticsearch/index/query/HasParentFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/HasParentFilterParser.java @@ -25,10 +25,9 @@ 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.mapper.DocumentMapper; import org.elasticsearch.index.mapper.internal.ParentFieldMapper; import org.elasticsearch.index.search.child.CustomQueryWrappingFilter; @@ -60,38 +59,29 @@ public String[] names() { 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. 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 + "]"); } @@ -103,25 +93,32 @@ public Filter parse(QueryParseContext parseContext) throws IOException, QueryPar } 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"); diff --git a/src/main/java/org/elasticsearch/index/query/HasParentQueryParser.java b/src/main/java/org/elasticsearch/index/query/HasParentQueryParser.java index 94542ac939572..49486e962919b 100644 --- a/src/main/java/org/elasticsearch/index/query/HasParentQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/HasParentQueryParser.java @@ -28,6 +28,7 @@ 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.mapper.DocumentMapper; import org.elasticsearch.index.mapper.internal.ParentFieldMapper; import org.elasticsearch.index.search.child.CustomQueryWrappingFilter; @@ -57,7 +58,6 @@ public String[] names() { 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; @@ -66,19 +66,18 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars 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. 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 + "]"); } @@ -113,14 +112,16 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars 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"); diff --git a/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java b/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java index f235bbc5cf854..53a1b00eee7d5 100644 --- a/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java @@ -26,6 +26,7 @@ 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 String[] names() { 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 Query parse(QueryParseContext parseContext) throws IOException, QueryPars 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 Query parse(QueryParseContext parseContext) throws IOException, QueryPars 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); diff --git a/src/main/java/org/elasticsearch/index/query/QueryParseContext.java b/src/main/java/org/elasticsearch/index/query/QueryParseContext.java index d92fc1571cbbb..ff128f58259d1 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryParseContext.java +++ b/src/main/java/org/elasticsearch/index/query/QueryParseContext.java @@ -113,6 +113,10 @@ public Index index() { return this.index; } + public void parser(XContentParser parser) { + this.parser = parser; + } + public XContentParser parser() { return parser; } diff --git a/src/main/java/org/elasticsearch/index/query/TopChildrenQueryParser.java b/src/main/java/org/elasticsearch/index/query/TopChildrenQueryParser.java index 1eaef1efa587d..ca62d4d183b1f 100644 --- a/src/main/java/org/elasticsearch/index/query/TopChildrenQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/TopChildrenQueryParser.java @@ -23,6 +23,7 @@ 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.mapper.DocumentMapper; import org.elasticsearch.index.search.child.CustomQueryWrappingFilter; import org.elasticsearch.index.search.child.ScoreType; @@ -51,7 +52,6 @@ public String[] names() { 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; @@ -62,20 +62,18 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars 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. 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 + "]"); } @@ -108,6 +106,8 @@ public Query parse(QueryParseContext parseContext) throws IOException, QueryPars throw new QueryParsingException(parseContext.index(), "[top_children] requires 'type' field"); } + Query innerQuery = iq.asQuery(childType); + if (innerQuery == null) { return null; } diff --git a/src/main/java/org/elasticsearch/index/query/support/XContentStructure.java b/src/main/java/org/elasticsearch/index/query/support/XContentStructure.java new file mode 100644 index 0000000000000..0ddec54563497 --- /dev/null +++ b/src/main/java/org/elasticsearch/index/query/support/XContentStructure.java @@ -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; + } + } +} diff --git a/src/test/java/org/elasticsearch/search/child/SimpleChildQuerySearchTests.java b/src/test/java/org/elasticsearch/search/child/SimpleChildQuerySearchTests.java index a96dba194ffe7..47c1b2a7ca33b 100644 --- a/src/test/java/org/elasticsearch/search/child/SimpleChildQuerySearchTests.java +++ b/src/test/java/org/elasticsearch/search/child/SimpleChildQuerySearchTests.java @@ -2225,6 +2225,31 @@ public void testValidateThatHasChildAndHasParentFilterAreNeverCached() throws Ex 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));