From 0998f3f8a65d13f178adda39263737681811e3f2 Mon Sep 17 00:00:00 2001 From: Shay Banon Date: Mon, 21 May 2012 21:43:17 +0200 Subject: [PATCH] support in compound queries / filter parsers for lower level ones returning null as the result of parsing the inner query / filter. --- .../common/lucene/search/Queries.java | 1 + .../index/query/AndFilterParser.java | 25 +++++++++++--- .../index/query/BoolFilterParser.java | 34 +++++++++++++++---- .../index/query/BoolQueryParser.java | 34 +++++++++++++++---- .../index/query/BoostingQueryParser.java | 13 +++++-- .../index/query/ConstantScoreQueryParser.java | 10 +++++- .../query/CustomBoostFactorQueryParser.java | 7 +++- .../query/CustomFiltersScoreQueryParser.java | 27 +++++++++++---- .../index/query/CustomScoreQueryParser.java | 7 +++- .../index/query/DisMaxQueryParser.java | 21 ++++++++++-- .../index/query/FQueryFilterParser.java | 8 +++++ .../index/query/FilterParser.java | 2 ++ .../index/query/FilteredQueryParser.java | 19 +++++++++-- .../index/query/HasChildFilterParser.java | 7 +++- .../index/query/HasChildQueryParser.java | 7 +++- .../index/query/IndexQueryParserService.java | 6 ++++ .../index/query/IndicesFilterParser.java | 8 ++++- .../index/query/IndicesQueryParser.java | 7 +++- .../index/query/NestedFilterParser.java | 10 +++++- .../index/query/NestedQueryParser.java | 10 +++++- .../index/query/NotFilterParser.java | 9 ++++- .../index/query/OrFilterParser.java | 25 +++++++++++--- .../index/query/QueryFilterParser.java | 3 ++ .../index/query/QueryParseContext.java | 2 ++ .../index/query/QueryParser.java | 4 +++ .../index/query/TopChildrenQueryParser.java | 8 ++++- 26 files changed, 270 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/elasticsearch/common/lucene/search/Queries.java b/src/main/java/org/elasticsearch/common/lucene/search/Queries.java index e964d085838..9ac4b1d085a 100644 --- a/src/main/java/org/elasticsearch/common/lucene/search/Queries.java +++ b/src/main/java/org/elasticsearch/common/lucene/search/Queries.java @@ -33,6 +33,7 @@ public class Queries { // We don't use MatchAllDocsQuery, its slower than the one below ... (much slower) public final static Query MATCH_ALL_QUERY = new DeletionAwareConstantScoreQuery(new MatchAllDocsFilter()); + public final static Query NO_MATCH_QUERY = MatchNoDocsQuery.INSTANCE; /** * A match all docs filter. Note, requires no caching!. diff --git a/src/main/java/org/elasticsearch/index/query/AndFilterParser.java b/src/main/java/org/elasticsearch/index/query/AndFilterParser.java index d17786519b0..acb94f09676 100644 --- a/src/main/java/org/elasticsearch/index/query/AndFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/AndFilterParser.java @@ -51,6 +51,7 @@ public class AndFilterParser implements FilterParser { XContentParser parser = parseContext.parser(); ArrayList filters = newArrayList(); + boolean filtersFound = false; boolean cache = false; CacheKeyFilter.Key cacheKey = null; @@ -60,7 +61,11 @@ public class AndFilterParser implements FilterParser { XContentParser.Token token = parser.currentToken(); if (token == XContentParser.Token.START_ARRAY) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - filters.add(parseContext.parseInnerFilter()); + filtersFound = true; + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + filters.add(filter); + } } } else { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { @@ -68,12 +73,20 @@ public class AndFilterParser implements FilterParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_ARRAY) { if ("filters".equals(currentFieldName)) { + filtersFound = true; while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - filters.add(parseContext.parseInnerFilter()); + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + filters.add(filter); + } } } else { + filtersFound = true; while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - filters.add(parseContext.parseInnerFilter()); + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + filters.add(filter); + } } } } else if (token.isValue()) { @@ -90,10 +103,14 @@ public class AndFilterParser implements FilterParser { } } - if (filters.isEmpty()) { + if (!filtersFound) { throw new QueryParsingException(parseContext.index(), "[and] filter requires 'filters' to be set on it'"); } + if (filters.isEmpty()) { + return null; + } + // no need to cache this one Filter filter = new AndFilter(filters); if (cache) { diff --git a/src/main/java/org/elasticsearch/index/query/BoolFilterParser.java b/src/main/java/org/elasticsearch/index/query/BoolFilterParser.java index f38244a1ca3..210ad61ffb8 100644 --- a/src/main/java/org/elasticsearch/index/query/BoolFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/BoolFilterParser.java @@ -62,26 +62,44 @@ public class BoolFilterParser implements FilterParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_OBJECT) { if ("must".equals(currentFieldName)) { - boolFilter.add(new FilterClause(parseContext.parseInnerFilter(), BooleanClause.Occur.MUST)); + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + boolFilter.add(new FilterClause(filter, BooleanClause.Occur.MUST)); + } } else if ("must_not".equals(currentFieldName) || "mustNot".equals(currentFieldName)) { - boolFilter.add(new FilterClause(parseContext.parseInnerFilter(), BooleanClause.Occur.MUST_NOT)); + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + boolFilter.add(new FilterClause(filter, BooleanClause.Occur.MUST_NOT)); + } } else if ("should".equals(currentFieldName)) { - boolFilter.add(new FilterClause(parseContext.parseInnerFilter(), BooleanClause.Occur.SHOULD)); + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + boolFilter.add(new FilterClause(filter, BooleanClause.Occur.SHOULD)); + } } else { throw new QueryParsingException(parseContext.index(), "[bool] filter does not support [" + currentFieldName + "]"); } } else if (token == XContentParser.Token.START_ARRAY) { if ("must".equals(currentFieldName)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - boolFilter.add(new FilterClause(parseContext.parseInnerFilter(), BooleanClause.Occur.MUST)); + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + boolFilter.add(new FilterClause(filter, BooleanClause.Occur.MUST)); + } } } else if ("must_not".equals(currentFieldName) || "mustNot".equals(currentFieldName)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - boolFilter.add(new FilterClause(parseContext.parseInnerFilter(), BooleanClause.Occur.MUST_NOT)); + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + boolFilter.add(new FilterClause(filter, BooleanClause.Occur.MUST_NOT)); + } } } else if ("should".equals(currentFieldName)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - boolFilter.add(new FilterClause(parseContext.parseInnerFilter(), BooleanClause.Occur.SHOULD)); + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + boolFilter.add(new FilterClause(filter, BooleanClause.Occur.SHOULD)); + } } } else { throw new QueryParsingException(parseContext.index(), "[bool] filter does not support [" + currentFieldName + "]"); @@ -99,6 +117,10 @@ public class BoolFilterParser implements FilterParser { } } + if (boolFilter.getMustFilters().isEmpty() && boolFilter.getNotFilters().isEmpty() && boolFilter.getShouldFilters().isEmpty()) { + return null; + } + Filter filter = boolFilter; if (cache) { filter = parseContext.cacheFilter(filter, cacheKey); diff --git a/src/main/java/org/elasticsearch/index/query/BoolQueryParser.java b/src/main/java/org/elasticsearch/index/query/BoolQueryParser.java index 67948b5db67..e13589db2a9 100644 --- a/src/main/java/org/elasticsearch/index/query/BoolQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/BoolQueryParser.java @@ -67,26 +67,44 @@ public class BoolQueryParser implements QueryParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_OBJECT) { if ("must".equals(currentFieldName)) { - clauses.add(new BooleanClause(parseContext.parseInnerQuery(), BooleanClause.Occur.MUST)); + Query query = parseContext.parseInnerQuery(); + if (query != null) { + clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST)); + } } else if ("must_not".equals(currentFieldName) || "mustNot".equals(currentFieldName)) { - clauses.add(new BooleanClause(parseContext.parseInnerQuery(), BooleanClause.Occur.MUST_NOT)); + Query query = parseContext.parseInnerQuery(); + if (query != null) { + clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST_NOT)); + } } else if ("should".equals(currentFieldName)) { - clauses.add(new BooleanClause(parseContext.parseInnerQuery(), BooleanClause.Occur.SHOULD)); + Query query = parseContext.parseInnerQuery(); + if (query != null) { + clauses.add(new BooleanClause(query, BooleanClause.Occur.SHOULD)); + } } else { throw new QueryParsingException(parseContext.index(), "[bool] query does not support [" + currentFieldName + "]"); } } else if (token == XContentParser.Token.START_ARRAY) { if ("must".equals(currentFieldName)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - clauses.add(new BooleanClause(parseContext.parseInnerQuery(), BooleanClause.Occur.MUST)); + Query query = parseContext.parseInnerQuery(); + if (query != null) { + clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST)); + } } } else if ("must_not".equals(currentFieldName) || "mustNot".equals(currentFieldName)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - clauses.add(new BooleanClause(parseContext.parseInnerQuery(), BooleanClause.Occur.MUST_NOT)); + Query query = parseContext.parseInnerQuery(); + if (query != null) { + clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST_NOT)); + } } } else if ("should".equals(currentFieldName)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - clauses.add(new BooleanClause(parseContext.parseInnerQuery(), BooleanClause.Occur.SHOULD)); + Query query = parseContext.parseInnerQuery(); + if (query != null) { + clauses.add(new BooleanClause(query, BooleanClause.Occur.SHOULD)); + } } } else { throw new QueryParsingException(parseContext.index(), "bool query does not support [" + currentFieldName + "]"); @@ -106,6 +124,10 @@ public class BoolQueryParser implements QueryParser { } } + if (clauses.isEmpty()) { + return null; + } + if (clauses.size() == 1) { BooleanClause clause = clauses.get(0); if (clause.getOccur() == BooleanClause.Occur.MUST) { diff --git a/src/main/java/org/elasticsearch/index/query/BoostingQueryParser.java b/src/main/java/org/elasticsearch/index/query/BoostingQueryParser.java index 091e4ac4cfe..e42f93d4a19 100644 --- a/src/main/java/org/elasticsearch/index/query/BoostingQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/BoostingQueryParser.java @@ -47,7 +47,9 @@ public class BoostingQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query positiveQuery = null; + boolean positiveQueryFound = false; Query negativeQuery = null; + boolean negativeQueryFound = false; float boost = -1; float negativeBoost = -1; @@ -59,8 +61,10 @@ public class BoostingQueryParser implements QueryParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("positive".equals(currentFieldName)) { positiveQuery = parseContext.parseInnerQuery(); + positiveQueryFound = true; } else if ("negative".equals(currentFieldName)) { negativeQuery = parseContext.parseInnerQuery(); + negativeQueryFound = true; } else { throw new QueryParsingException(parseContext.index(), "[boosting] query does not support [" + currentFieldName + "]"); } @@ -75,16 +79,21 @@ public class BoostingQueryParser implements QueryParser { } } - if (positiveQuery == null) { + if (positiveQuery == null && !positiveQueryFound) { throw new QueryParsingException(parseContext.index(), "[boosting] query requires 'positive' query to be set'"); } - if (negativeQuery == null) { + if (negativeQuery == null && !negativeQueryFound) { throw new QueryParsingException(parseContext.index(), "[boosting] query requires 'negative' query to be set'"); } if (negativeBoost == -1) { throw new QueryParsingException(parseContext.index(), "[boosting] query requires 'negative_boost' to be set'"); } + // parsers returned null + if (positiveQuery == null || negativeQuery == null) { + return null; + } + BoostingQuery boostingQuery = new BoostingQuery(positiveQuery, negativeQuery, negativeBoost); if (boost != -1) { boostingQuery.setBoost(boost); diff --git a/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryParser.java b/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryParser.java index a39d1b5afe5..6f54fd9e834 100644 --- a/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryParser.java @@ -51,7 +51,9 @@ public class ConstantScoreQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Filter filter = null; + boolean filterFound = false; Query query = null; + boolean queryFound = false; float boost = 1.0f; boolean cache = false; CacheKeyFilter.Key cacheKey = null; @@ -64,8 +66,10 @@ public class ConstantScoreQueryParser implements QueryParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("filter".equals(currentFieldName)) { filter = parseContext.parseInnerFilter(); + filterFound = true; } else if ("query".equals(currentFieldName)) { query = parseContext.parseInnerQuery(); + queryFound = true; } else { throw new QueryParsingException(parseContext.index(), "[constant_score] query does not support [" + currentFieldName + "]"); } @@ -81,10 +85,14 @@ public class ConstantScoreQueryParser implements QueryParser { } } } - if (filter == null && query == null) { + if (!filterFound && !queryFound) { throw new QueryParsingException(parseContext.index(), "[constant_score] requires either 'filter' or 'query' element"); } + if (query == null && filter == null) { + return null; + } + if (filter != null) { // cache the filter if possible needed if (cache) { diff --git a/src/main/java/org/elasticsearch/index/query/CustomBoostFactorQueryParser.java b/src/main/java/org/elasticsearch/index/query/CustomBoostFactorQueryParser.java index ba9e25fc3ef..9bd1b656fdb 100644 --- a/src/main/java/org/elasticsearch/index/query/CustomBoostFactorQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/CustomBoostFactorQueryParser.java @@ -49,6 +49,7 @@ public class CustomBoostFactorQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; float boost = 1.0f; float boostFactor = 1.0f; @@ -60,6 +61,7 @@ public class CustomBoostFactorQueryParser implements QueryParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { query = parseContext.parseInnerQuery(); + queryFound = true; } else { throw new QueryParsingException(parseContext.index(), "[custom_boost_factor] query does not support [" + currentFieldName + "]"); } @@ -73,9 +75,12 @@ public class CustomBoostFactorQueryParser implements QueryParser { } } } - if (query == null) { + if (!queryFound) { throw new QueryParsingException(parseContext.index(), "[constant_factor_query] requires 'query' element"); } + if (query == null) { + return null; + } FunctionScoreQuery functionScoreQuery = new FunctionScoreQuery(query, new BoostScoreFunction(boostFactor)); functionScoreQuery.setBoost(boost); return functionScoreQuery; diff --git a/src/main/java/org/elasticsearch/index/query/CustomFiltersScoreQueryParser.java b/src/main/java/org/elasticsearch/index/query/CustomFiltersScoreQueryParser.java index efedceb30e2..e0300d35fd2 100644 --- a/src/main/java/org/elasticsearch/index/query/CustomFiltersScoreQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/CustomFiltersScoreQueryParser.java @@ -55,12 +55,14 @@ public class CustomFiltersScoreQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; float boost = 1.0f; String scriptLang = null; Map vars = null; FiltersFunctionScoreQuery.ScoreMode scoreMode = FiltersFunctionScoreQuery.ScoreMode.First; ArrayList filters = new ArrayList(); + boolean filtersFound = false; ArrayList scripts = new ArrayList(); TFloatArrayList boosts = new TFloatArrayList(); @@ -72,6 +74,7 @@ public class CustomFiltersScoreQueryParser implements QueryParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { query = parseContext.parseInnerQuery(); + queryFound = true; } else if ("params".equals(currentFieldName)) { vars = parser.map(); } else { @@ -79,9 +82,11 @@ public class CustomFiltersScoreQueryParser implements QueryParser { } } else if (token == XContentParser.Token.START_ARRAY) { if ("filters".equals(currentFieldName)) { + filtersFound = true; while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { String script = null; Filter filter = null; + boolean filterFound = false; float fboost = Float.NaN; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { @@ -89,6 +94,7 @@ public class CustomFiltersScoreQueryParser implements QueryParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("filter".equals(currentFieldName)) { filter = parseContext.parseInnerFilter(); + filterFound = true; } } else if (token.isValue()) { if ("script".equals(currentFieldName)) { @@ -101,12 +107,14 @@ public class CustomFiltersScoreQueryParser implements QueryParser { if (script == null && fboost == -1) { throw new QueryParsingException(parseContext.index(), "[custom_filters_score] missing 'script' or 'boost' in filters array element"); } - if (filter == null) { + if (!filterFound) { throw new QueryParsingException(parseContext.index(), "[custom_filters_score] missing 'filter' in filters array element"); } - filters.add(filter); - scripts.add(script); - boosts.add(fboost); + if (filter != null) { + filters.add(filter); + scripts.add(script); + boosts.add(fboost); + } } } else { throw new QueryParsingException(parseContext.index(), "[custom_filters_score] query does not support [" + currentFieldName + "]"); @@ -138,12 +146,19 @@ public class CustomFiltersScoreQueryParser implements QueryParser { } } } - if (query == null) { + if (!queryFound) { throw new QueryParsingException(parseContext.index(), "[custom_filters_score] requires 'query' field"); } - if (filters.isEmpty()) { + if (query == null) { + return null; + } + if (!filtersFound) { throw new QueryParsingException(parseContext.index(), "[custom_filters_score] requires 'filters' field"); } + // if all filter elements returned null, just use the query + if (filters.isEmpty()) { + return query; + } FiltersFunctionScoreQuery.FilterFunction[] filterFunctions = new FiltersFunctionScoreQuery.FilterFunction[filters.size()]; for (int i = 0; i < filterFunctions.length; i++) { diff --git a/src/main/java/org/elasticsearch/index/query/CustomScoreQueryParser.java b/src/main/java/org/elasticsearch/index/query/CustomScoreQueryParser.java index 4822797c024..a8338536819 100644 --- a/src/main/java/org/elasticsearch/index/query/CustomScoreQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/CustomScoreQueryParser.java @@ -53,6 +53,7 @@ public class CustomScoreQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; float boost = 1.0f; String script = null; String scriptLang = null; @@ -66,6 +67,7 @@ public class CustomScoreQueryParser implements QueryParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { query = parseContext.parseInnerQuery(); + queryFound = true; } else if ("params".equals(currentFieldName)) { vars = parser.map(); } else { @@ -83,12 +85,15 @@ public class CustomScoreQueryParser implements QueryParser { } } } - if (query == null) { + if (!queryFound) { throw new QueryParsingException(parseContext.index(), "[custom_score] requires 'query' field"); } if (script == null) { throw new QueryParsingException(parseContext.index(), "[custom_score] requires 'script' field"); } + if (query == null) { + return null; + } SearchScript searchScript = parseContext.scriptService().search(parseContext.lookup(), scriptLang, script, vars); FunctionScoreQuery functionScoreQuery = new FunctionScoreQuery(query, new ScriptScoreFunction(script, vars, searchScript)); diff --git a/src/main/java/org/elasticsearch/index/query/DisMaxQueryParser.java b/src/main/java/org/elasticsearch/index/query/DisMaxQueryParser.java index b1fa2b0bcde..9252d3a11e1 100644 --- a/src/main/java/org/elasticsearch/index/query/DisMaxQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/DisMaxQueryParser.java @@ -54,6 +54,7 @@ public class DisMaxQueryParser implements QueryParser { float tieBreaker = 0.0f; List queries = newArrayList(); + boolean queriesFound = false; String currentFieldName = null; XContentParser.Token token; @@ -62,14 +63,22 @@ public class DisMaxQueryParser implements QueryParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_OBJECT) { if ("queries".equals(currentFieldName)) { - queries.add(parseContext.parseInnerQuery()); + queriesFound = true; + Query query = parseContext.parseInnerQuery(); + if (query != null) { + queries.add(query); + } } else { throw new QueryParsingException(parseContext.index(), "[dis_max] query does not support [" + currentFieldName + "]"); } } else if (token == XContentParser.Token.START_ARRAY) { if ("queries".equals(currentFieldName)) { + queriesFound = true; while (token != XContentParser.Token.END_ARRAY) { - queries.add(parseContext.parseInnerQuery()); + Query query = parseContext.parseInnerQuery(); + if (query != null) { + queries.add(query); + } token = parser.nextToken(); } } else { @@ -86,6 +95,14 @@ public class DisMaxQueryParser implements QueryParser { } } + if (!queriesFound) { + throw new QueryParsingException(parseContext.index(), "[dis_max] requires 'queries' field"); + } + + if (queries.isEmpty()) { + return null; + } + DisjunctionMaxQuery query = new DisjunctionMaxQuery(queries, tieBreaker); query.setBoost(boost); return query; diff --git a/src/main/java/org/elasticsearch/index/query/FQueryFilterParser.java b/src/main/java/org/elasticsearch/index/query/FQueryFilterParser.java index bea20aa2bef..7a9cfb56fa0 100644 --- a/src/main/java/org/elasticsearch/index/query/FQueryFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/FQueryFilterParser.java @@ -50,6 +50,7 @@ public class FQueryFilterParser implements FilterParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; boolean cache = false; CacheKeyFilter.Key cacheKey = null; @@ -61,6 +62,7 @@ public class FQueryFilterParser implements FilterParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { + queryFound = true; query = parseContext.parseInnerQuery(); } else { throw new QueryParsingException(parseContext.index(), "[fquery] filter does not support [" + currentFieldName + "]"); @@ -77,6 +79,12 @@ public class FQueryFilterParser implements FilterParser { } } } + if (!queryFound) { + throw new QueryParsingException(parseContext.index(), "[fquery] requires 'query' element"); + } + if (query == null) { + return null; + } Filter filter = new QueryWrapperFilter(query); if (cache) { filter = parseContext.cacheFilter(filter, cacheKey); diff --git a/src/main/java/org/elasticsearch/index/query/FilterParser.java b/src/main/java/org/elasticsearch/index/query/FilterParser.java index 2acc261dba6..16fe8c0f3d6 100644 --- a/src/main/java/org/elasticsearch/index/query/FilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/FilterParser.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.query; import org.apache.lucene.search.Filter; +import org.elasticsearch.common.Nullable; import java.io.IOException; @@ -37,5 +38,6 @@ public interface FilterParser { * Parses the into a filter from the current parser location. Will be at "START_OBJECT" location, * and should end when the token is at the matching "END_OBJECT". */ + @Nullable Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException; } \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/index/query/FilteredQueryParser.java b/src/main/java/org/elasticsearch/index/query/FilteredQueryParser.java index 5efa8eda57f..96abf15bb80 100644 --- a/src/main/java/org/elasticsearch/index/query/FilteredQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/FilteredQueryParser.java @@ -51,7 +51,9 @@ public class FilteredQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; Filter filter = null; + boolean filterFound = false; float boost = 1.0f; boolean cache = false; CacheKeyFilter.Key cacheKey = null; @@ -63,8 +65,10 @@ public class FilteredQueryParser implements QueryParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { + queryFound = true; query = parseContext.parseInnerQuery(); } else if ("filter".equals(currentFieldName)) { + filterFound = true; filter = parseContext.parseInnerFilter(); } else { throw new QueryParsingException(parseContext.index(), "[filtered] query does not support [" + currentFieldName + "]"); @@ -81,12 +85,21 @@ public class FilteredQueryParser implements QueryParser { } } } - if (query == null) { + if (!queryFound) { throw new QueryParsingException(parseContext.index(), "[filtered] requires 'query' element"); } - // we allow for null filter, so it makes compositions on the client side to be simpler + if (query == null) { + return null; + } if (filter == null) { - return query; + if (!filterFound) { + // we allow for null filter, so it makes compositions on the client side to be simpler + return query; + } else { + // the filter was provided, but returned null, meaning we should discard it, this means no + // matches for this query... + return Queries.NO_MATCH_QUERY; + } } // cache if required diff --git a/src/main/java/org/elasticsearch/index/query/HasChildFilterParser.java b/src/main/java/org/elasticsearch/index/query/HasChildFilterParser.java index cc8310c4ea1..2a356942015 100644 --- a/src/main/java/org/elasticsearch/index/query/HasChildFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/HasChildFilterParser.java @@ -52,6 +52,7 @@ public class HasChildFilterParser implements FilterParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; String childType = null; String scope = null; @@ -68,6 +69,7 @@ public class HasChildFilterParser implements FilterParser { String[] origTypes = QueryParseContext.setTypesWithPrevious(childType == null ? null : new String[]{childType}); try { query = parseContext.parseInnerQuery(); + queryFound = true; } finally { QueryParseContext.setTypes(origTypes); } @@ -86,9 +88,12 @@ public class HasChildFilterParser implements FilterParser { } } } - if (query == null) { + if (!queryFound) { throw new QueryParsingException(parseContext.index(), "[child] filter requires 'query' field"); } + if (query == null) { + return null; + } if (childType == null) { throw new QueryParsingException(parseContext.index(), "[child] filter requires 'type' field"); } diff --git a/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java b/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java index ebb42fcfc87..73dbe6ed2ee 100644 --- a/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/HasChildQueryParser.java @@ -52,6 +52,7 @@ public class HasChildQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; float boost = 1.0f; String childType = null; String scope = null; @@ -68,6 +69,7 @@ public class HasChildQueryParser implements QueryParser { String[] origTypes = QueryParseContext.setTypesWithPrevious(childType == null ? null : new String[]{childType}); try { query = parseContext.parseInnerQuery(); + queryFound = true; } finally { QueryParseContext.setTypes(origTypes); } @@ -86,9 +88,12 @@ public class HasChildQueryParser implements QueryParser { } } } - if (query == null) { + if (!queryFound) { throw new QueryParsingException(parseContext.index(), "[has_child] requires 'query' field"); } + if (query == null) { + return null; + } if (childType == null) { throw new QueryParsingException(parseContext.index(), "[has_child] requires 'type' field"); } diff --git a/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java b/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java index 98aac9e2dc6..97646b0dbd8 100644 --- a/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java +++ b/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java @@ -26,6 +26,7 @@ import org.elasticsearch.ElasticSearchException; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.io.BytesStream; +import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; @@ -232,12 +233,14 @@ public class IndexQueryParserService extends AbstractIndexComponent { } } + @Nullable public Filter parseInnerFilter(XContentParser parser) throws IOException { QueryParseContext context = cache.get(); context.reset(parser); return context.parseInnerFilter(); } + @Nullable public Query parseInnerQuery(XContentParser parser) throws IOException { QueryParseContext context = cache.get(); context.reset(parser); @@ -247,6 +250,9 @@ public class IndexQueryParserService extends AbstractIndexComponent { private ParsedQuery parse(QueryParseContext parseContext, XContentParser parser) throws IOException, QueryParsingException { parseContext.reset(parser); Query query = parseContext.parseInnerQuery(); + if (query == null) { + query = Queries.NO_MATCH_QUERY; + } return new ParsedQuery(query, parseContext.copyNamedFilters()); } diff --git a/src/main/java/org/elasticsearch/index/query/IndicesFilterParser.java b/src/main/java/org/elasticsearch/index/query/IndicesFilterParser.java index 409304bd82a..e8b5b05432b 100644 --- a/src/main/java/org/elasticsearch/index/query/IndicesFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/IndicesFilterParser.java @@ -56,6 +56,7 @@ public class IndicesFilterParser implements FilterParser { XContentParser parser = parseContext.parser(); Filter filter = null; + boolean filterFound = false; Set indices = Sets.newHashSet(); String currentFieldName = null; @@ -66,6 +67,7 @@ public class IndicesFilterParser implements FilterParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_OBJECT) { if ("filter".equals(currentFieldName)) { + filterFound = true; filter = parseContext.parseInnerFilter(); } else if ("no_match_filter".equals(currentFieldName)) { noMatchFilter = parseContext.parseInnerFilter(); @@ -99,13 +101,17 @@ public class IndicesFilterParser implements FilterParser { } } } - if (filter == null) { + if (!filterFound) { throw new QueryParsingException(parseContext.index(), "[indices] requires 'filter' element"); } if (indices.isEmpty()) { throw new QueryParsingException(parseContext.index(), "[indices] requires 'indices' element"); } + if (filter == null) { + return null; + } + String[] concreteIndices = indices.toArray(new String[indices.size()]); if (clusterService != null) { MetaData metaData = clusterService.state().metaData(); diff --git a/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java b/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java index 3750282e03d..048101e9137 100644 --- a/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/IndicesQueryParser.java @@ -57,6 +57,7 @@ public class IndicesQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; Set indices = Sets.newHashSet(); String currentFieldName = null; @@ -68,6 +69,7 @@ public class IndicesQueryParser implements QueryParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { query = parseContext.parseInnerQuery(); + queryFound = true; } else if ("no_match_query".equals(currentFieldName)) { noMatchQuery = parseContext.parseInnerQuery(); } else { @@ -100,9 +102,12 @@ public class IndicesQueryParser implements QueryParser { } } } - if (query == null) { + if (!queryFound) { throw new QueryParsingException(parseContext.index(), "[indices] requires 'query' element"); } + if (query == null) { + return null; + } if (indices.isEmpty()) { throw new QueryParsingException(parseContext.index(), "[indices] requires 'indices' element"); } diff --git a/src/main/java/org/elasticsearch/index/query/NestedFilterParser.java b/src/main/java/org/elasticsearch/index/query/NestedFilterParser.java index 30438c5c59a..19a6bf0808f 100644 --- a/src/main/java/org/elasticsearch/index/query/NestedFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/NestedFilterParser.java @@ -50,7 +50,9 @@ public class NestedFilterParser implements FilterParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; Filter filter = null; + boolean filterFound = false; float boost = 1.0f; String scope = null; String path = null; @@ -72,8 +74,10 @@ public class NestedFilterParser implements FilterParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { + queryFound = true; query = parseContext.parseInnerQuery(); } else if ("filter".equals(currentFieldName)) { + filterFound = true; filter = parseContext.parseInnerFilter(); } else { throw new QueryParsingException(parseContext.index(), "[nested] filter does not support [" + currentFieldName + "]"); @@ -96,13 +100,17 @@ public class NestedFilterParser implements FilterParser { } } } - if (query == null && filter == null) { + if (!queryFound && !filterFound) { throw new QueryParsingException(parseContext.index(), "[nested] requires either 'query' or 'filter' field"); } if (path == null) { throw new QueryParsingException(parseContext.index(), "[nested] requires 'path' field"); } + if (query == null && filter == null) { + return null; + } + if (filter != null) { query = new DeletionAwareConstantScoreQuery(filter); } diff --git a/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java b/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java index 899b7436e3b..98bd960bc46 100644 --- a/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/NestedQueryParser.java @@ -50,7 +50,9 @@ public class NestedQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; Filter filter = null; + boolean filterFound = false; float boost = 1.0f; String scope = null; String path = null; @@ -70,8 +72,10 @@ public class NestedQueryParser implements QueryParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { + queryFound = true; query = parseContext.parseInnerQuery(); } else if ("filter".equals(currentFieldName)) { + filterFound = true; filter = parseContext.parseInnerFilter(); } else { throw new QueryParsingException(parseContext.index(), "[nested] query does not support [" + currentFieldName + "]"); @@ -101,13 +105,17 @@ public class NestedQueryParser implements QueryParser { } } } - if (query == null && filter == null) { + if (!queryFound && !filterFound) { throw new QueryParsingException(parseContext.index(), "[nested] requires either 'query' or 'filter' field"); } if (path == null) { throw new QueryParsingException(parseContext.index(), "[nested] requires 'path' field"); } + if (query == null && filter == null) { + return null; + } + if (filter != null) { query = new DeletionAwareConstantScoreQuery(filter); } diff --git a/src/main/java/org/elasticsearch/index/query/NotFilterParser.java b/src/main/java/org/elasticsearch/index/query/NotFilterParser.java index bc05ba6e13f..1de6cd70a02 100644 --- a/src/main/java/org/elasticsearch/index/query/NotFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/NotFilterParser.java @@ -48,6 +48,7 @@ public class NotFilterParser implements FilterParser { XContentParser parser = parseContext.parser(); Filter filter = null; + boolean filterFound = false; boolean cache = false; CacheKeyFilter.Key cacheKey = null; @@ -60,7 +61,9 @@ public class NotFilterParser implements FilterParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("filter".equals(currentFieldName)) { filter = parseContext.parseInnerFilter(); + filterFound = true; } else { + filterFound = true; // its the filter, and the name is the field filter = parseContext.parseInnerFilter(currentFieldName); } @@ -77,10 +80,14 @@ public class NotFilterParser implements FilterParser { } } - if (filter == null) { + if (!filterFound) { throw new QueryParsingException(parseContext.index(), "filter is required when using `not` filter"); } + if (filter == null) { + return null; + } + Filter notFilter = new NotFilter(filter); if (cache) { notFilter = parseContext.cacheFilter(notFilter, cacheKey); diff --git a/src/main/java/org/elasticsearch/index/query/OrFilterParser.java b/src/main/java/org/elasticsearch/index/query/OrFilterParser.java index 892f3c5f3e3..0a2f00e4598 100644 --- a/src/main/java/org/elasticsearch/index/query/OrFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/OrFilterParser.java @@ -51,6 +51,7 @@ public class OrFilterParser implements FilterParser { XContentParser parser = parseContext.parser(); ArrayList filters = newArrayList(); + boolean filtersFound = false; boolean cache = false; CacheKeyFilter.Key cacheKey = null; @@ -60,7 +61,11 @@ public class OrFilterParser implements FilterParser { XContentParser.Token token = parser.currentToken(); if (token == XContentParser.Token.START_ARRAY) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - filters.add(parseContext.parseInnerFilter()); + filtersFound = true; + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + filters.add(filter); + } } } else { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { @@ -68,12 +73,20 @@ public class OrFilterParser implements FilterParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_ARRAY) { if ("filters".equals(currentFieldName)) { + filtersFound = true; while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - filters.add(parseContext.parseInnerFilter()); + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + filters.add(filter); + } } } else { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { - filters.add(parseContext.parseInnerFilter()); + filtersFound = true; + Filter filter = parseContext.parseInnerFilter(); + if (filter != null) { + filters.add(filter); + } } } } else if (token.isValue()) { @@ -90,10 +103,14 @@ public class OrFilterParser implements FilterParser { } } - if (filters.isEmpty()) { + if (!filtersFound) { throw new QueryParsingException(parseContext.index(), "[or] filter requires 'filters' to be set on it'"); } + if (filters.isEmpty()) { + return null; + } + // no need to cache this one Filter filter = new OrFilter(filters); if (cache) { diff --git a/src/main/java/org/elasticsearch/index/query/QueryFilterParser.java b/src/main/java/org/elasticsearch/index/query/QueryFilterParser.java index 0c97e78e21b..bd76318c8f8 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/QueryFilterParser.java @@ -45,6 +45,9 @@ public class QueryFilterParser implements FilterParser { @Override public Filter parse(QueryParseContext parseContext) throws IOException, QueryParsingException { Query query = parseContext.parseInnerQuery(); + if (query == null) { + return null; + } return new QueryWrapperFilter(query); } } \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/index/query/QueryParseContext.java b/src/main/java/org/elasticsearch/index/query/QueryParseContext.java index afc09d00926..309678a2000 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryParseContext.java +++ b/src/main/java/org/elasticsearch/index/query/QueryParseContext.java @@ -160,6 +160,7 @@ public class QueryParseContext { return ImmutableMap.copyOf(namedFilters); } + @Nullable public Query parseInnerQuery() throws IOException, QueryParsingException { // move to START object XContentParser.Token token; @@ -192,6 +193,7 @@ public class QueryParseContext { return result; } + @Nullable public Filter parseInnerFilter() throws IOException, QueryParsingException { // move to START object XContentParser.Token token; diff --git a/src/main/java/org/elasticsearch/index/query/QueryParser.java b/src/main/java/org/elasticsearch/index/query/QueryParser.java index c57e0b0d178..17617ba7e37 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/QueryParser.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.query; import org.apache.lucene.search.Query; +import org.elasticsearch.common.Nullable; import java.io.IOException; @@ -36,6 +37,9 @@ public interface QueryParser { /** * Parses the into a query from the current parser location. Will be at "START_OBJECT" location, * and should end when the token is at the matching "END_OBJECT". + *

+ * Returns null if this query should be ignored in the context of the DSL. */ + @Nullable Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException; } diff --git a/src/main/java/org/elasticsearch/index/query/TopChildrenQueryParser.java b/src/main/java/org/elasticsearch/index/query/TopChildrenQueryParser.java index f40c346fdfc..94e81115b7c 100644 --- a/src/main/java/org/elasticsearch/index/query/TopChildrenQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/TopChildrenQueryParser.java @@ -51,6 +51,7 @@ public class TopChildrenQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query query = null; + boolean queryFound = false; float boost = 1.0f; String childType = null; String scope = null; @@ -65,6 +66,7 @@ public class TopChildrenQueryParser implements QueryParser { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { + 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}); @@ -94,13 +96,17 @@ public class TopChildrenQueryParser implements QueryParser { } } } - if (query == null) { + if (!queryFound) { throw new QueryParsingException(parseContext.index(), "[child] requires 'query' field"); } if (childType == null) { throw new QueryParsingException(parseContext.index(), "[child] requires 'type' field"); } + 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 + "]");