From 49b6d70dfdeeac2a9a12647a83b9ef3613346faa Mon Sep 17 00:00:00 2001 From: Shay Banon Date: Sun, 29 Jan 2012 21:09:11 +0200 Subject: [PATCH] Query DSL: prefix query to support _id, closes #1648. --- .../lucene/queryParser/MapperQueryParser.java | 22 +++++++-- .../index/mapper/FieldMapper.java | 5 ++ .../mapper/core/AbstractFieldMapper.java | 14 ++++++ .../index/mapper/internal/IdFieldMapper.java | 46 +++++++++++++++++-- .../index/query/ExistsFilterParser.java | 6 +-- .../index/query/MissingFilterParser.java | 6 +-- .../index/query/PrefixFilterParser.java | 14 ++++-- .../index/query/PrefixQueryParser.java | 24 +++++++--- .../index/query/TermFilterParser.java | 18 ++++---- .../index/query/TermQueryParser.java | 18 ++++---- .../index/query/TermsQueryParser.java | 36 +++++++++------ .../index/query/WildcardQueryParser.java | 8 ++-- .../index/search/TextQueryParser.java | 10 ++-- .../search/simple/SimpleSearchTests.java | 13 ++++-- 14 files changed, 165 insertions(+), 75 deletions(-) diff --git a/src/main/java/org/apache/lucene/queryParser/MapperQueryParser.java b/src/main/java/org/apache/lucene/queryParser/MapperQueryParser.java index 4a5d98c4caa..6c3a51f45cd 100644 --- a/src/main/java/org/apache/lucene/queryParser/MapperQueryParser.java +++ b/src/main/java/org/apache/lucene/queryParser/MapperQueryParser.java @@ -190,7 +190,6 @@ public class MapperQueryParser extends QueryParser { @Override protected Query getPrefixQuery(String field, String termStr) throws ParseException { - String indexedNameField = field; currentMapper = null; Analyzer oldAnalyzer = analyzer; try { @@ -201,11 +200,26 @@ public class MapperQueryParser extends QueryParser { } currentMapper = fieldMappers.fieldMappers().mapper(); if (currentMapper != null) { - indexedNameField = currentMapper.names().indexName(); + Query query = null; + if (currentMapper.useFieldQueryWithQueryString()) { + if (fieldMappers.hasDocMapper()) { + String[] previousTypes = QueryParseContext.setTypesWithPrevious(new String[]{fieldMappers.docMapper().type()}); + try { + query = currentMapper.prefixQuery(termStr, multiTermRewriteMethod, parseContext); + } finally { + QueryParseContext.setTypes(previousTypes); + } + } else { + query = currentMapper.prefixQuery(termStr, multiTermRewriteMethod, parseContext); + } + } + if (query == null) { + query = super.getPrefixQuery(currentMapper.names().indexName(), termStr); + } + return wrapSmartNameQuery(query, fieldMappers, parseContext); } - return wrapSmartNameQuery(getPossiblyAnalyzedPrefixQuery(indexedNameField, termStr), fieldMappers, parseContext); } - return getPossiblyAnalyzedPrefixQuery(indexedNameField, termStr); + return super.getPrefixQuery(field, termStr); } finally { analyzer = oldAnalyzer; } diff --git a/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java index 38a1f29007b..ddec7dc6efe 100644 --- a/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java @@ -24,6 +24,7 @@ import org.apache.lucene.document.Field; import org.apache.lucene.document.Fieldable; import org.apache.lucene.index.Term; import org.apache.lucene.search.Filter; +import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.Query; import org.elasticsearch.common.Nullable; import org.elasticsearch.index.field.data.FieldDataType; @@ -173,6 +174,10 @@ public interface FieldMapper { Query fuzzyQuery(String value, double minSim, int prefixLength, int maxExpansions); + Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryParseContext context); + + Filter prefixFilter(String value, @Nullable QueryParseContext context); + /** * A term query to use when parsing a query string. Can return null. */ diff --git a/src/main/java/org/elasticsearch/index/mapper/core/AbstractFieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/core/AbstractFieldMapper.java index 37bec35b5e8..f2771a30b28 100644 --- a/src/main/java/org/elasticsearch/index/mapper/core/AbstractFieldMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/core/AbstractFieldMapper.java @@ -380,6 +380,20 @@ public abstract class AbstractFieldMapper implements FieldMapper, Mapper { return new FuzzyQuery(names().createIndexNameTerm(value), (float) minSim, prefixLength, maxExpansions); } + @Override + public Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryParseContext context) { + PrefixQuery query = new PrefixQuery(names().createIndexNameTerm(indexedValue(value))); + if (method != null) { + query.setRewriteMethod(method); + } + return query; + } + + @Override + public Filter prefixFilter(String value, @Nullable QueryParseContext context) { + return new PrefixFilter(names().createIndexNameTerm(indexedValue(value))); + } + @Override public Query rangeQuery(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) { return new TermRangeQuery(names.indexName(), diff --git a/src/main/java/org/elasticsearch/index/mapper/internal/IdFieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/internal/IdFieldMapper.java index 60b7f4b9bc6..e7e6c0a6933 100644 --- a/src/main/java/org/elasticsearch/index/mapper/internal/IdFieldMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/internal/IdFieldMapper.java @@ -20,15 +20,15 @@ package org.elasticsearch.index.mapper.internal; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.Fieldable; -import org.apache.lucene.search.ConstantScoreQuery; -import org.apache.lucene.search.Filter; -import org.apache.lucene.search.Query; +import org.apache.lucene.search.*; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.lucene.Lucene; +import org.elasticsearch.common.lucene.search.XBooleanFilter; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.mapper.*; @@ -37,6 +37,7 @@ import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.search.UidFilter; import java.io.IOException; +import java.util.Collection; import java.util.Map; import static org.elasticsearch.index.mapper.MapperBuilders.id; @@ -175,6 +176,45 @@ public class IdFieldMapper extends AbstractFieldMapper implements Intern return new UidFilter(context.queryTypes(), ImmutableList.of(value), context.indexCache().bloomCache()); } + @Override + public Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryParseContext context) { + if (indexed() || context == null) { + return super.prefixQuery(value, method, context); + } + Collection queryTypes = context.queryTypes(); + if (queryTypes.size() == 1) { + PrefixQuery prefixQuery = new PrefixQuery(UidFieldMapper.TERM_FACTORY.createTerm(Uid.createUid(Iterables.getFirst(queryTypes, null), value))); + if (method != null) { + prefixQuery.setRewriteMethod(method); + } + } + BooleanQuery query = new BooleanQuery(); + for (String queryType : queryTypes) { + PrefixQuery prefixQuery = new PrefixQuery(UidFieldMapper.TERM_FACTORY.createTerm(Uid.createUid(queryType, value))); + if (method != null) { + prefixQuery.setRewriteMethod(method); + } + query.add(prefixQuery, BooleanClause.Occur.SHOULD); + } + return query; + } + + @Override + public Filter prefixFilter(String value, @Nullable QueryParseContext context) { + if (indexed() || context == null) { + return super.prefixFilter(value, context); + } + Collection queryTypes = context.queryTypes(); + if (queryTypes.size() == 1) { + return new PrefixFilter(UidFieldMapper.TERM_FACTORY.createTerm(Uid.createUid(Iterables.getFirst(queryTypes, null), value))); + } + XBooleanFilter filter = new XBooleanFilter(); + for (String queryType : queryTypes) { + filter.addShould(new PrefixFilter(UidFieldMapper.TERM_FACTORY.createTerm(Uid.createUid(queryType, value)))); + } + return filter; + } + @Override public void preParse(ParseContext context) throws IOException { if (context.sourceToParse().id() != null) { diff --git a/src/main/java/org/elasticsearch/index/query/ExistsFilterParser.java b/src/main/java/org/elasticsearch/index/query/ExistsFilterParser.java index c6ef6825d5e..a2f1fe4305c 100644 --- a/src/main/java/org/elasticsearch/index/query/ExistsFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/ExistsFilterParser.java @@ -74,10 +74,8 @@ public class ExistsFilterParser implements FilterParser { Filter filter = null; MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - filter = smartNameFieldMappers.mapper().rangeFilter(null, null, true, true); - } + if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) { + filter = smartNameFieldMappers.mapper().rangeFilter(null, null, true, true); } if (filter == null) { filter = new TermRangeFilter(fieldName, null, null, true, true); diff --git a/src/main/java/org/elasticsearch/index/query/MissingFilterParser.java b/src/main/java/org/elasticsearch/index/query/MissingFilterParser.java index 17978ce5f67..9495f0be6cf 100644 --- a/src/main/java/org/elasticsearch/index/query/MissingFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/MissingFilterParser.java @@ -75,10 +75,8 @@ public class MissingFilterParser implements FilterParser { Filter filter = null; MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - filter = smartNameFieldMappers.mapper().rangeFilter(null, null, true, true); - } + if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) { + filter = smartNameFieldMappers.mapper().rangeFilter(null, null, true, true); } if (filter == null) { filter = new TermRangeFilter(fieldName, null, null, true, true); diff --git a/src/main/java/org/elasticsearch/index/query/PrefixFilterParser.java b/src/main/java/org/elasticsearch/index/query/PrefixFilterParser.java index 41492a0b197..5d2ad770d8b 100644 --- a/src/main/java/org/elasticsearch/index/query/PrefixFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/PrefixFilterParser.java @@ -83,10 +83,16 @@ public class PrefixFilterParser implements FilterParser { Filter filter = null; MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - value = smartNameFieldMappers.mapper().indexedValue(value); - filter = new PrefixFilter(smartNameFieldMappers.mapper().names().createIndexNameTerm(value)); + if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) { + if (smartNameFieldMappers.hasDocMapper()) { + String[] previousTypes = QueryParseContext.setTypesWithPrevious(new String[]{smartNameFieldMappers.docMapper().type()}); + try { + filter = smartNameFieldMappers.mapper().prefixFilter(value, parseContext); + } finally { + QueryParseContext.setTypes(previousTypes); + } + } else { + filter = smartNameFieldMappers.mapper().prefixFilter(value, parseContext); } } if (filter == null) { diff --git a/src/main/java/org/elasticsearch/index/query/PrefixQueryParser.java b/src/main/java/org/elasticsearch/index/query/PrefixQueryParser.java index ef17de274bb..07d780a4b60 100644 --- a/src/main/java/org/elasticsearch/index/query/PrefixQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/PrefixQueryParser.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.query; import org.apache.lucene.index.Term; +import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.elasticsearch.common.inject.Inject; @@ -90,18 +91,27 @@ public class PrefixQueryParser implements QueryParser { throw new QueryParsingException(parseContext.index(), "No value specified for prefix query"); } - PrefixQuery query = null; + MultiTermQuery.RewriteMethod method = QueryParsers.parseRewriteMethod(rewriteMethod); + + Query query = null; MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - value = smartNameFieldMappers.mapper().indexedValue(value); - query = new PrefixQuery(smartNameFieldMappers.mapper().names().createIndexNameTerm(value)); + if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) { + if (smartNameFieldMappers.hasDocMapper()) { + String[] previousTypes = QueryParseContext.setTypesWithPrevious(new String[]{smartNameFieldMappers.docMapper().type()}); + try { + query = smartNameFieldMappers.mapper().prefixQuery(value, method, parseContext); + } finally { + QueryParseContext.setTypes(previousTypes); + } + } else { + query = smartNameFieldMappers.mapper().prefixQuery(value, method, parseContext); } } if (query == null) { - query = new PrefixQuery(new Term(fieldName, value)); + PrefixQuery prefixQuery = new PrefixQuery(new Term(fieldName, value)); + prefixQuery.setRewriteMethod(method); + query = prefixQuery; } - query.setRewriteMethod(QueryParsers.parseRewriteMethod(rewriteMethod)); query.setBoost(boost); return wrapSmartNameQuery(query, smartNameFieldMappers, parseContext); } diff --git a/src/main/java/org/elasticsearch/index/query/TermFilterParser.java b/src/main/java/org/elasticsearch/index/query/TermFilterParser.java index 7c3257bd69e..2e0bf11b2ec 100644 --- a/src/main/java/org/elasticsearch/index/query/TermFilterParser.java +++ b/src/main/java/org/elasticsearch/index/query/TermFilterParser.java @@ -86,18 +86,16 @@ public class TermFilterParser implements FilterParser { Filter filter = null; MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - if (smartNameFieldMappers.hasDocMapper()) { - String[] previousTypes = QueryParseContext.setTypesWithPrevious(new String[]{smartNameFieldMappers.docMapper().type()}); - try { - filter = smartNameFieldMappers.mapper().fieldFilter(value, parseContext); - } finally { - QueryParseContext.setTypes(previousTypes); - } - } else { + if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) { + if (smartNameFieldMappers.hasDocMapper()) { + String[] previousTypes = QueryParseContext.setTypesWithPrevious(new String[]{smartNameFieldMappers.docMapper().type()}); + try { filter = smartNameFieldMappers.mapper().fieldFilter(value, parseContext); + } finally { + QueryParseContext.setTypes(previousTypes); } + } else { + filter = smartNameFieldMappers.mapper().fieldFilter(value, parseContext); } } if (filter == null) { diff --git a/src/main/java/org/elasticsearch/index/query/TermQueryParser.java b/src/main/java/org/elasticsearch/index/query/TermQueryParser.java index 8de9d6df915..6eddfeecb64 100644 --- a/src/main/java/org/elasticsearch/index/query/TermQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/TermQueryParser.java @@ -89,18 +89,16 @@ public class TermQueryParser implements QueryParser { Query query = null; MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - if (smartNameFieldMappers.hasDocMapper()) { - String[] previousTypes = QueryParseContext.setTypesWithPrevious(new String[]{smartNameFieldMappers.docMapper().type()}); - try { - query = smartNameFieldMappers.mapper().fieldQuery(value, parseContext); - } finally { - QueryParseContext.setTypes(previousTypes); - } - } else { + if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) { + if (smartNameFieldMappers.hasDocMapper()) { + String[] previousTypes = QueryParseContext.setTypesWithPrevious(new String[]{smartNameFieldMappers.docMapper().type()}); + try { query = smartNameFieldMappers.mapper().fieldQuery(value, parseContext); + } finally { + QueryParseContext.setTypes(previousTypes); } + } else { + query = smartNameFieldMappers.mapper().fieldQuery(value, parseContext); } } if (query == null) { diff --git a/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java b/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java index 721a919fd00..cf4dc310de8 100644 --- a/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/TermsQueryParser.java @@ -97,25 +97,33 @@ public class TermsQueryParser implements QueryParser { FieldMapper mapper = null; MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - mapper = smartNameFieldMappers.mapper(); + String[] previousTypes = null; + if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) { + mapper = smartNameFieldMappers.mapper(); + if (smartNameFieldMappers.hasDocMapper()) { + previousTypes = QueryParseContext.setTypesWithPrevious(new String[]{smartNameFieldMappers.docMapper().type()}); } } - BooleanQuery query = new BooleanQuery(disableCoord); - for (String value : values) { - if (mapper != null) { - query.add(new BooleanClause(mapper.fieldQuery(value, parseContext), BooleanClause.Occur.SHOULD)); - } else { - query.add(new TermQuery(new Term(fieldName, value)), BooleanClause.Occur.SHOULD); + try { + BooleanQuery query = new BooleanQuery(disableCoord); + for (String value : values) { + if (mapper != null) { + query.add(new BooleanClause(mapper.fieldQuery(value, parseContext), BooleanClause.Occur.SHOULD)); + } else { + query.add(new TermQuery(new Term(fieldName, value)), BooleanClause.Occur.SHOULD); + } + } + query.setBoost(boost); + if (minimumNumberShouldMatch != -1) { + query.setMinimumNumberShouldMatch(minimumNumberShouldMatch); + } + return wrapSmartNameQuery(optimizeQuery(fixNegativeQueryIfNeeded(query)), smartNameFieldMappers, parseContext); + } finally { + if (smartNameFieldMappers != null && smartNameFieldMappers.hasDocMapper()) { + QueryParseContext.setTypes(previousTypes); } } - query.setBoost(boost); - if (minimumNumberShouldMatch != -1) { - query.setMinimumNumberShouldMatch(minimumNumberShouldMatch); - } - return wrapSmartNameQuery(optimizeQuery(fixNegativeQueryIfNeeded(query)), smartNameFieldMappers, parseContext); } } diff --git a/src/main/java/org/elasticsearch/index/query/WildcardQueryParser.java b/src/main/java/org/elasticsearch/index/query/WildcardQueryParser.java index bdad45a6161..df54824194a 100644 --- a/src/main/java/org/elasticsearch/index/query/WildcardQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/WildcardQueryParser.java @@ -91,11 +91,9 @@ public class WildcardQueryParser implements QueryParser { } MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - fieldName = smartNameFieldMappers.mapper().names().indexName(); - value = smartNameFieldMappers.mapper().indexedValue(value); - } + if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) { + fieldName = smartNameFieldMappers.mapper().names().indexName(); + value = smartNameFieldMappers.mapper().indexedValue(value); } WildcardQuery query = new WildcardQuery(new Term(fieldName, value)); diff --git a/src/main/java/org/elasticsearch/index/search/TextQueryParser.java b/src/main/java/org/elasticsearch/index/search/TextQueryParser.java index 4ecdd59bd1e..c0a976da726 100644 --- a/src/main/java/org/elasticsearch/index/search/TextQueryParser.java +++ b/src/main/java/org/elasticsearch/index/search/TextQueryParser.java @@ -106,13 +106,9 @@ public class TextQueryParser { FieldMapper mapper = null; String field = fieldName; MapperService.SmartNameFieldMappers smartNameFieldMappers = parseContext.smartFieldMappers(fieldName); - if (smartNameFieldMappers != null) { - if (smartNameFieldMappers.hasMapper()) { - mapper = smartNameFieldMappers.mapper(); - if (mapper != null) { - field = mapper.names().indexName(); - } - } + if (smartNameFieldMappers != null && smartNameFieldMappers.hasMapper()) { + mapper = smartNameFieldMappers.mapper(); + field = mapper.names().indexName(); } if (mapper != null && mapper.useFieldQueryWithQueryString()) { diff --git a/src/test/java/org/elasticsearch/test/integration/search/simple/SimpleSearchTests.java b/src/test/java/org/elasticsearch/test/integration/search/simple/SimpleSearchTests.java index 4fea1082220..749735db270 100644 --- a/src/test/java/org/elasticsearch/test/integration/search/simple/SimpleSearchTests.java +++ b/src/test/java/org/elasticsearch/test/integration/search/simple/SimpleSearchTests.java @@ -81,12 +81,19 @@ public class SimpleSearchTests extends AbstractNodesTests { client.admin().indices().prepareDelete().execute().actionGet(); client.admin().indices().prepareCreate("test").setSettings(ImmutableSettings.settingsBuilder().put("number_of_shards", 1)).execute().actionGet(); - client.prepareIndex("test", "type", "1").setSource("field", "value").setRefresh(true).execute().actionGet(); + client.prepareIndex("test", "type", "XXX1").setSource("field", "value").setRefresh(true).execute().actionGet(); // id is not indexed, but lets see that we automatically convert to - SearchResponse searchResponse = client.prepareSearch().setQuery(QueryBuilders.termQuery("_id", "1")).execute().actionGet(); + SearchResponse searchResponse = client.prepareSearch().setQuery(QueryBuilders.termQuery("_id", "XXX1")).execute().actionGet(); assertThat(searchResponse.hits().totalHits(), equalTo(1l)); - searchResponse = client.prepareSearch().setQuery(QueryBuilders.queryString("_id:1")).execute().actionGet(); + searchResponse = client.prepareSearch().setQuery(QueryBuilders.queryString("_id:XXX1")).execute().actionGet(); + assertThat(searchResponse.hits().totalHits(), equalTo(1l)); + + // id is not index, but we can automatically support prefix as well + searchResponse = client.prepareSearch().setQuery(QueryBuilders.prefixQuery("_id", "XXX")).execute().actionGet(); + assertThat(searchResponse.hits().totalHits(), equalTo(1l)); + + searchResponse = client.prepareSearch().setQuery(QueryBuilders.queryString("_id:XXX*")).execute().actionGet(); assertThat(searchResponse.hits().totalHits(), equalTo(1l)); }