From acbd7b686a42f0662e1378bd2576164c451ef8e5 Mon Sep 17 00:00:00 2001 From: Shay Banon Date: Thu, 10 May 2012 11:51:51 +0300 Subject: [PATCH] Allow to customize quote analyzer to be used when quoting text in a query_string, closes #1931. --- .../lucene/queryParser/MapperQueryParser.java | 37 +++++- .../queryParser/QueryParserSettings.java | 35 ++++++ .../index/analysis/AnalysisService.java | 9 ++ .../index/mapper/DocumentFieldMappers.java | 10 ++ .../index/mapper/DocumentMapper.java | 24 +++- .../index/mapper/DocumentMapperParser.java | 9 ++ .../index/mapper/FieldMapper.java | 5 + .../index/mapper/MapperService.java | 117 ++++++++++++++++++ .../mapper/core/AbstractFieldMapper.java | 5 + .../index/mapper/core/StringFieldMapper.java | 44 ++++++- .../index/query/FieldQueryParser.java | 9 ++ .../index/query/QueryStringQueryBuilder.java | 29 ++++- .../index/query/QueryStringQueryParser.java | 9 ++ 13 files changed, 333 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/apache/lucene/queryParser/MapperQueryParser.java b/src/main/java/org/apache/lucene/queryParser/MapperQueryParser.java index 977eba1d835..48adc2431ac 100644 --- a/src/main/java/org/apache/lucene/queryParser/MapperQueryParser.java +++ b/src/main/java/org/apache/lucene/queryParser/MapperQueryParser.java @@ -64,12 +64,17 @@ public class MapperQueryParser extends QueryParser { private final QueryParseContext parseContext; + private Analyzer quoteAnalyzer; + private boolean forcedAnalyzer; + private boolean forcedQuoteAnalyzer; private FieldMapper currentMapper; private boolean analyzeWildcard; + private String quoteFieldSuffix; + public MapperQueryParser(QueryParseContext parseContext) { super(Lucene.QUERYPARSER_VERSION, null, null); this.parseContext = parseContext; @@ -85,6 +90,17 @@ public class MapperQueryParser extends QueryParser { this.field = settings.defaultField(); this.forcedAnalyzer = settings.forcedAnalyzer() != null; this.analyzer = forcedAnalyzer ? settings.forcedAnalyzer() : settings.defaultAnalyzer(); + if (settings.forcedQuoteAnalyzer() != null) { + this.forcedQuoteAnalyzer = true; + this.quoteAnalyzer = settings.forcedQuoteAnalyzer(); + } else if (forcedAnalyzer) { + this.forcedQuoteAnalyzer = true; + this.quoteAnalyzer = settings.forcedAnalyzer(); + } else { + this.forcedAnalyzer = false; + this.quoteAnalyzer = settings.defaultQuoteAnalyzer(); + } + this.quoteFieldSuffix = settings.quoteFieldSuffix(); setMultiTermRewriteMethod(settings.rewriteMethod()); setEnablePositionIncrements(settings.enablePositionIncrements()); setAutoGeneratePhraseQueries(settings.autoGeneratePhraseQueries()); @@ -122,10 +138,25 @@ public class MapperQueryParser extends QueryParser { currentMapper = null; Analyzer oldAnalyzer = analyzer; try { - MapperService.SmartNameFieldMappers fieldMappers = parseContext.smartFieldMappers(field); + MapperService.SmartNameFieldMappers fieldMappers = null; + if (quoted) { + analyzer = quoteAnalyzer; + if (quoteFieldSuffix != null) { + fieldMappers = parseContext.smartFieldMappers(field + quoteFieldSuffix); + } + } + if (fieldMappers == null) { + fieldMappers = parseContext.smartFieldMappers(field); + } if (fieldMappers != null) { - if (!forcedAnalyzer) { - analyzer = fieldMappers.searchAnalyzer(); + if (quoted) { + if (!forcedQuoteAnalyzer) { + analyzer = fieldMappers.searchQuoteAnalyzer(); + } + } else { + if (!forcedAnalyzer) { + analyzer = fieldMappers.searchAnalyzer(); + } } currentMapper = fieldMappers.fieldMappers().mapper(); if (currentMapper != null) { diff --git a/src/main/java/org/apache/lucene/queryParser/QueryParserSettings.java b/src/main/java/org/apache/lucene/queryParser/QueryParserSettings.java index 110e02c9712..f9778655cd4 100644 --- a/src/main/java/org/apache/lucene/queryParser/QueryParserSettings.java +++ b/src/main/java/org/apache/lucene/queryParser/QueryParserSettings.java @@ -45,7 +45,10 @@ public class QueryParserSettings { private boolean analyzeWildcard = DEFAULT_ANALYZE_WILDCARD; private boolean escape = false; private Analyzer defaultAnalyzer = null; + private Analyzer defaultQuoteAnalyzer = null; private Analyzer forcedAnalyzer = null; + private Analyzer forcedQuoteAnalyzer = null; + private String quoteFieldSuffix = null; private MultiTermQuery.RewriteMethod rewriteMethod = MultiTermQuery.CONSTANT_SCORE_AUTO_REWRITE_DEFAULT; private String minimumShouldMatch; @@ -153,6 +156,14 @@ public class QueryParserSettings { this.defaultAnalyzer = defaultAnalyzer; } + public Analyzer defaultQuoteAnalyzer() { + return defaultQuoteAnalyzer; + } + + public void defaultQuoteAnalyzer(Analyzer defaultAnalyzer) { + this.defaultQuoteAnalyzer = defaultAnalyzer; + } + public Analyzer forcedAnalyzer() { return forcedAnalyzer; } @@ -161,6 +172,14 @@ public class QueryParserSettings { this.forcedAnalyzer = forcedAnalyzer; } + public Analyzer forcedQuoteAnalyzer() { + return forcedQuoteAnalyzer; + } + + public void forcedQuoteAnalyzer(Analyzer forcedAnalyzer) { + this.forcedQuoteAnalyzer = forcedAnalyzer; + } + public boolean analyzeWildcard() { return this.analyzeWildcard; } @@ -185,6 +204,14 @@ public class QueryParserSettings { this.minimumShouldMatch = minimumShouldMatch; } + public void quoteFieldSuffix(String quoteFieldSuffix) { + this.quoteFieldSuffix = quoteFieldSuffix; + } + + public String quoteFieldSuffix() { + return this.quoteFieldSuffix; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -204,8 +231,12 @@ public class QueryParserSettings { if (phraseSlop != that.phraseSlop) return false; if (defaultAnalyzer != null ? !defaultAnalyzer.equals(that.defaultAnalyzer) : that.defaultAnalyzer != null) return false; + if (defaultQuoteAnalyzer != null ? !defaultQuoteAnalyzer.equals(that.defaultQuoteAnalyzer) : that.defaultQuoteAnalyzer != null) + return false; if (forcedAnalyzer != null ? !forcedAnalyzer.equals(that.forcedAnalyzer) : that.forcedAnalyzer != null) return false; + if (forcedQuoteAnalyzer != null ? !forcedQuoteAnalyzer.equals(that.forcedQuoteAnalyzer) : that.forcedQuoteAnalyzer != null) + return false; if (defaultField != null ? !defaultField.equals(that.defaultField) : that.defaultField != null) return false; if (defaultOperator != that.defaultOperator) return false; if (queryString != null ? !queryString.equals(that.queryString) : that.queryString != null) return false; @@ -213,6 +244,8 @@ public class QueryParserSettings { return false; if (minimumShouldMatch != null ? !minimumShouldMatch.equals(that.minimumShouldMatch) : that.minimumShouldMatch != null) return false; + if (quoteFieldSuffix != null ? !quoteFieldSuffix.equals(that.quoteFieldSuffix) : that.quoteFieldSuffix != null) + return false; return true; } @@ -232,7 +265,9 @@ public class QueryParserSettings { result = 31 * result + fuzzyPrefixLength; result = 31 * result + (escape ? 1 : 0); result = 31 * result + (defaultAnalyzer != null ? defaultAnalyzer.hashCode() : 0); + result = 31 * result + (defaultQuoteAnalyzer != null ? defaultQuoteAnalyzer.hashCode() : 0); result = 31 * result + (forcedAnalyzer != null ? forcedAnalyzer.hashCode() : 0); + result = 31 * result + (forcedQuoteAnalyzer != null ? forcedQuoteAnalyzer.hashCode() : 0); result = 31 * result + (analyzeWildcard ? 1 : 0); return result; } diff --git a/src/main/java/org/elasticsearch/index/analysis/AnalysisService.java b/src/main/java/org/elasticsearch/index/analysis/AnalysisService.java index d7285edb0af..04f4d803bc0 100644 --- a/src/main/java/org/elasticsearch/index/analysis/AnalysisService.java +++ b/src/main/java/org/elasticsearch/index/analysis/AnalysisService.java @@ -51,6 +51,7 @@ public class AnalysisService extends AbstractIndexComponent implements Closeable private final NamedAnalyzer defaultAnalyzer; private final NamedAnalyzer defaultIndexAnalyzer; private final NamedAnalyzer defaultSearchAnalyzer; + private final NamedAnalyzer defaultSearchQuoteAnalyzer; public AnalysisService(Index index) { this(index, ImmutableSettings.Builder.EMPTY_SETTINGS, null, null, null, null, null); @@ -209,6 +210,9 @@ public class AnalysisService extends AbstractIndexComponent implements Closeable if (!analyzerProviders.containsKey("default_search")) { analyzerProviders.put("default_search", analyzerProviders.get("default")); } + if (!analyzerProviders.containsKey("default_search_quoted")) { + analyzerProviders.put("default_search_quoted", analyzerProviders.get("default_search")); + } Map analyzers = newHashMap(); for (AnalyzerProvider analyzerFactory : analyzerProviders.values()) { @@ -233,6 +237,7 @@ public class AnalysisService extends AbstractIndexComponent implements Closeable defaultAnalyzer = analyzers.get("default"); defaultIndexAnalyzer = analyzers.containsKey("default_index") ? analyzers.get("default_index") : analyzers.get("default"); defaultSearchAnalyzer = analyzers.containsKey("default_search") ? analyzers.get("default_search") : analyzers.get("default"); + defaultSearchQuoteAnalyzer = analyzers.containsKey("default_search_quote") ? analyzers.get("default_search_quote") : defaultSearchAnalyzer; this.analyzers = ImmutableMap.copyOf(analyzers); } @@ -268,6 +273,10 @@ public class AnalysisService extends AbstractIndexComponent implements Closeable return defaultSearchAnalyzer; } + public NamedAnalyzer defaultSearchQuoteAnalyzer() { + return defaultSearchQuoteAnalyzer; + } + public TokenizerFactory tokenizer(String name) { return tokenizers.get(name); } diff --git a/src/main/java/org/elasticsearch/index/mapper/DocumentFieldMappers.java b/src/main/java/org/elasticsearch/index/mapper/DocumentFieldMappers.java index 2087f33fbf9..1fc193946a2 100644 --- a/src/main/java/org/elasticsearch/index/mapper/DocumentFieldMappers.java +++ b/src/main/java/org/elasticsearch/index/mapper/DocumentFieldMappers.java @@ -42,6 +42,7 @@ public class DocumentFieldMappers implements Iterable { private final FieldNameAnalyzer indexAnalyzer; private final FieldNameAnalyzer searchAnalyzer; + private final FieldNameAnalyzer searchQuoteAnalyzer; public DocumentFieldMappers(DocumentMapper docMapper, Iterable fieldMappers) { final Map tempNameFieldMappers = newHashMap(); @@ -50,6 +51,7 @@ public class DocumentFieldMappers implements Iterable { final Map indexAnalyzers = newHashMap(); final Map searchAnalyzers = newHashMap(); + final Map searchQuoteAnalyzers = newHashMap(); for (FieldMapper fieldMapper : fieldMappers) { FieldMappers mappers = tempNameFieldMappers.get(fieldMapper.names().name()); @@ -82,6 +84,9 @@ public class DocumentFieldMappers implements Iterable { if (fieldMapper.searchAnalyzer() != null) { searchAnalyzers.put(fieldMapper.names().indexName(), fieldMapper.searchAnalyzer()); } + if (fieldMapper.searchQuoteAnalyzer() != null) { + searchQuoteAnalyzers.put(fieldMapper.names().indexName(), fieldMapper.searchQuoteAnalyzer()); + } } this.fieldMappers = ImmutableList.copyOf(fieldMappers); this.nameFieldMappers = ImmutableMap.copyOf(tempNameFieldMappers); @@ -90,6 +95,7 @@ public class DocumentFieldMappers implements Iterable { this.indexAnalyzer = new FieldNameAnalyzer(indexAnalyzers, docMapper.indexAnalyzer()); this.searchAnalyzer = new FieldNameAnalyzer(searchAnalyzers, docMapper.searchAnalyzer()); + this.searchQuoteAnalyzer = new FieldNameAnalyzer(searchQuoteAnalyzers, docMapper.searchQuotedAnalyzer()); } @Override @@ -179,6 +185,10 @@ public class DocumentFieldMappers implements Iterable { return this.searchAnalyzer; } + public Analyzer searchQuoteAnalyzer() { + return this.searchQuoteAnalyzer; + } + public DocumentFieldMappers concat(DocumentMapper docMapper, FieldMapper... fieldMappers) { return concat(docMapper, newArrayList(fieldMappers)); } diff --git a/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java b/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java index 41791d6dc78..b7c4c9ef629 100644 --- a/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java @@ -132,6 +132,8 @@ public class DocumentMapper implements ToXContent { private NamedAnalyzer searchAnalyzer; + private NamedAnalyzer searchQuoteAnalyzer; + private final String index; @Nullable @@ -193,6 +195,14 @@ public class DocumentMapper implements ToXContent { public Builder searchAnalyzer(NamedAnalyzer searchAnalyzer) { this.searchAnalyzer = searchAnalyzer; + if (this.searchQuoteAnalyzer == null) { + this.searchQuoteAnalyzer = searchAnalyzer; + } + return this; + } + + public Builder searchQuoteAnalyzer(NamedAnalyzer searchQuoteAnalyzer) { + this.searchQuoteAnalyzer = searchQuoteAnalyzer; return this; } @@ -200,10 +210,14 @@ public class DocumentMapper implements ToXContent { return searchAnalyzer != null; } + public boolean hasSearchQuoteAnalyzer() { + return searchQuoteAnalyzer != null; + } + public DocumentMapper build(DocumentMapperParser docMapperParser) { Preconditions.checkNotNull(rootObjectMapper, "Mapper builder must have the root object mapper set"); return new DocumentMapper(index, indexSettings, docMapperParser, rootObjectMapper, meta, - indexAnalyzer, searchAnalyzer, + indexAnalyzer, searchAnalyzer, searchQuoteAnalyzer, rootMappers); } } @@ -237,6 +251,7 @@ public class DocumentMapper implements ToXContent { private final NamedAnalyzer indexAnalyzer; private final NamedAnalyzer searchAnalyzer; + private final NamedAnalyzer searchQuoteAnalyzer; private volatile DocumentFieldMappers fieldMappers; @@ -257,7 +272,7 @@ public class DocumentMapper implements ToXContent { public DocumentMapper(String index, @Nullable Settings indexSettings, DocumentMapperParser docMapperParser, RootObjectMapper rootObjectMapper, ImmutableMap meta, - NamedAnalyzer indexAnalyzer, NamedAnalyzer searchAnalyzer, + NamedAnalyzer indexAnalyzer, NamedAnalyzer searchAnalyzer, NamedAnalyzer searchQuoteAnalyzer, Map, RootMapper> rootMappers) { this.index = index; this.indexSettings = indexSettings; @@ -278,6 +293,7 @@ public class DocumentMapper implements ToXContent { this.indexAnalyzer = indexAnalyzer; this.searchAnalyzer = searchAnalyzer; + this.searchQuoteAnalyzer = searchQuoteAnalyzer != null ? searchQuoteAnalyzer : searchAnalyzer; this.typeFilter = typeMapper().fieldFilter(type, null); @@ -389,6 +405,10 @@ public class DocumentMapper implements ToXContent { return this.searchAnalyzer; } + public Analyzer searchQuotedAnalyzer() { + return this.searchQuoteAnalyzer; + } + public Filter typeFilter() { return this.typeFilter; } diff --git a/src/main/java/org/elasticsearch/index/mapper/DocumentMapperParser.java b/src/main/java/org/elasticsearch/index/mapper/DocumentMapperParser.java index 4991a155470..84bb19b958d 100644 --- a/src/main/java/org/elasticsearch/index/mapper/DocumentMapperParser.java +++ b/src/main/java/org/elasticsearch/index/mapper/DocumentMapperParser.java @@ -177,6 +177,12 @@ public class DocumentMapperParser extends AbstractIndexComponent { throw new MapperParsingException("Analyzer [" + fieldNode.toString() + "] not found for search_analyzer setting on root type [" + type + "]"); } docBuilder.searchAnalyzer(analyzer); + } else if ("search_quote_analyzer".equals(fieldName)) { + NamedAnalyzer analyzer = analysisService.analyzer(fieldNode.toString()); + if (analyzer == null) { + throw new MapperParsingException("Analyzer [" + fieldNode.toString() + "] not found for search_analyzer setting on root type [" + type + "]"); + } + docBuilder.searchQuoteAnalyzer(analyzer); } else if ("analyzer".equals(fieldName)) { NamedAnalyzer analyzer = analysisService.analyzer(fieldNode.toString()); if (analyzer == null) { @@ -198,6 +204,9 @@ public class DocumentMapperParser extends AbstractIndexComponent { if (!docBuilder.hasSearchAnalyzer()) { docBuilder.searchAnalyzer(analysisService.defaultSearchAnalyzer()); } + if (!docBuilder.hasSearchQuoteAnalyzer()) { + docBuilder.searchAnalyzer(analysisService.defaultSearchQuoteAnalyzer()); + } ImmutableMap attributes = ImmutableMap.of(); if (mapping.containsKey("_meta")) { diff --git a/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java index 587aee3509f..705cf048964 100644 --- a/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java @@ -147,6 +147,11 @@ public interface FieldMapper { */ Analyzer searchAnalyzer(); + /** + * The analyzer that will be used for quoted search on the field. + */ + Analyzer searchQuoteAnalyzer(); + /** * Returns the value that will be used as a result for search. Can be only of specific types... . */ diff --git a/src/main/java/org/elasticsearch/index/mapper/MapperService.java b/src/main/java/org/elasticsearch/index/mapper/MapperService.java index 006e1264768..e270a4238fa 100644 --- a/src/main/java/org/elasticsearch/index/mapper/MapperService.java +++ b/src/main/java/org/elasticsearch/index/mapper/MapperService.java @@ -95,6 +95,7 @@ public class MapperService extends AbstractIndexComponent implements Iterable implements FieldMapper, Mapper { return this.searchAnalyzer; } + @Override + public Analyzer searchQuoteAnalyzer() { + return this.searchAnalyzer; + } + @Override public void parse(ParseContext context) throws IOException { try { diff --git a/src/main/java/org/elasticsearch/index/mapper/core/StringFieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/core/StringFieldMapper.java index 7fa4fb795fe..8302d0503d0 100644 --- a/src/main/java/org/elasticsearch/index/mapper/core/StringFieldMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/core/StringFieldMapper.java @@ -19,6 +19,7 @@ package org.elasticsearch.index.mapper.core; +import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Field; import org.apache.lucene.document.Fieldable; import org.elasticsearch.common.Strings; @@ -55,6 +56,8 @@ public class StringFieldMapper extends AbstractFieldMapper implements Al protected int positionOffsetGap = Defaults.POSITION_OFFSET_GAP; + protected NamedAnalyzer searchQuotedAnalyzer; + public Builder(String name) { super(name); builder = this; @@ -71,20 +74,35 @@ public class StringFieldMapper extends AbstractFieldMapper implements Al return this; } + @Override + public Builder searchAnalyzer(NamedAnalyzer searchAnalyzer) { + super.searchAnalyzer(searchAnalyzer); + if (searchQuotedAnalyzer == null) { + searchQuotedAnalyzer = searchAnalyzer; + } + return this; + } + public Builder positionOffsetGap(int positionOffsetGap) { this.positionOffsetGap = positionOffsetGap; return this; } + public Builder searchQuotedAnalyzer(NamedAnalyzer analyzer) { + this.searchQuotedAnalyzer = analyzer; + return builder; + } + @Override public StringFieldMapper build(BuilderContext context) { if (positionOffsetGap > 0) { indexAnalyzer = new NamedCustomAnalyzer(indexAnalyzer, positionOffsetGap); searchAnalyzer = new NamedCustomAnalyzer(searchAnalyzer, positionOffsetGap); + searchQuotedAnalyzer = new NamedCustomAnalyzer(searchQuotedAnalyzer, positionOffsetGap); } StringFieldMapper fieldMapper = new StringFieldMapper(buildNames(context), index, store, termVector, boost, omitNorms, omitTermFreqAndPositions, nullValue, - indexAnalyzer, searchAnalyzer, positionOffsetGap); + indexAnalyzer, searchAnalyzer, searchQuotedAnalyzer, positionOffsetGap); fieldMapper.includeInAll(includeInAll); return fieldMapper; } @@ -100,6 +118,12 @@ public class StringFieldMapper extends AbstractFieldMapper implements Al Object propNode = entry.getValue(); if (propName.equals("null_value")) { builder.nullValue(propNode.toString()); + } else if (propName.equals("search_quote_analyzer")) { + NamedAnalyzer analyzer = parserContext.analysisService().analyzer(propNode.toString()); + if (analyzer == null) { + throw new MapperParsingException("Analyzer [" + propNode.toString() + "] not found for field [" + name + "]"); + } + builder.searchQuotedAnalyzer(analyzer); } else if (propName.equals("position_offset_gap")) { builder.positionOffsetGap(XContentMapValues.nodeIntegerValue(propNode, -1)); // we need to update to actual analyzers if they are not set in this case... @@ -110,6 +134,9 @@ public class StringFieldMapper extends AbstractFieldMapper implements Al if (builder.searchAnalyzer == null) { builder.searchAnalyzer = parserContext.analysisService().defaultSearchAnalyzer(); } + if (builder.searchQuotedAnalyzer == null) { + builder.searchQuotedAnalyzer = parserContext.analysisService().defaultSearchQuoteAnalyzer(); + } } } return builder; @@ -122,18 +149,21 @@ public class StringFieldMapper extends AbstractFieldMapper implements Al private int positionOffsetGap; + private NamedAnalyzer searchQuotedAnalyzer; + protected StringFieldMapper(Names names, Field.Index index, Field.Store store, Field.TermVector termVector, float boost, boolean omitNorms, boolean omitTermFreqAndPositions, String nullValue, NamedAnalyzer indexAnalyzer, NamedAnalyzer searchAnalyzer) { - this(names, index, store, termVector, boost, omitNorms, omitTermFreqAndPositions, nullValue, indexAnalyzer, searchAnalyzer, Defaults.POSITION_OFFSET_GAP); + this(names, index, store, termVector, boost, omitNorms, omitTermFreqAndPositions, nullValue, indexAnalyzer, searchAnalyzer, searchAnalyzer, Defaults.POSITION_OFFSET_GAP); } protected StringFieldMapper(Names names, Field.Index index, Field.Store store, Field.TermVector termVector, float boost, boolean omitNorms, boolean omitTermFreqAndPositions, - String nullValue, NamedAnalyzer indexAnalyzer, NamedAnalyzer searchAnalyzer, int positionOffsetGap) { + String nullValue, NamedAnalyzer indexAnalyzer, NamedAnalyzer searchAnalyzer, NamedAnalyzer searchQuotedAnalyzer, int positionOffsetGap) { super(names, index, store, termVector, boost, omitNorms, omitTermFreqAndPositions, indexAnalyzer, searchAnalyzer); this.nullValue = nullValue; this.positionOffsetGap = positionOffsetGap; + this.searchQuotedAnalyzer = searchQuotedAnalyzer != null ? searchQuotedAnalyzer : searchAnalyzer; } @Override @@ -179,6 +209,11 @@ public class StringFieldMapper extends AbstractFieldMapper implements Al return this.positionOffsetGap; } + @Override + public Analyzer searchQuoteAnalyzer() { + return this.searchQuotedAnalyzer; + } + @Override protected Field parseCreateField(ParseContext context) throws IOException { String value = nullValue; @@ -266,5 +301,8 @@ public class StringFieldMapper extends AbstractFieldMapper implements Al if (positionOffsetGap != Defaults.POSITION_OFFSET_GAP) { builder.field("position_offset_gap", positionOffsetGap); } + if (searchQuotedAnalyzer != null && searchAnalyzer != searchQuotedAnalyzer) { + builder.field("search_quote_analyzer", searchQuotedAnalyzer.name()); + } } } diff --git a/src/main/java/org/elasticsearch/index/query/FieldQueryParser.java b/src/main/java/org/elasticsearch/index/query/FieldQueryParser.java index be37ad04595..d83ec165954 100644 --- a/src/main/java/org/elasticsearch/index/query/FieldQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/FieldQueryParser.java @@ -99,6 +99,12 @@ public class FieldQueryParser implements QueryParser { throw new QueryParsingException(parseContext.index(), "[query_string] analyzer [" + parser.text() + "] not found"); } qpSettings.forcedAnalyzer(analyzer); + } else if ("quote_analyzer".equals(currentFieldName) || "quoteAnalyzer".equals(currentFieldName)) { + NamedAnalyzer analyzer = parseContext.analysisService().analyzer(parser.text()); + if (analyzer == null) { + throw new QueryParsingException(parseContext.index(), "[query_string] analyzer [" + parser.text() + "] not found"); + } + qpSettings.forcedQuoteAnalyzer(analyzer); } else if ("default_operator".equals(currentFieldName) || "defaultOperator".equals(currentFieldName)) { String op = parser.text(); if ("or".equalsIgnoreCase(op)) { @@ -120,6 +126,8 @@ public class FieldQueryParser implements QueryParser { qpSettings.rewriteMethod(QueryParsers.parseRewriteMethod(parser.textOrNull())); } else if ("minimum_should_match".equals(currentFieldName) || "minimumShouldMatch".equals(currentFieldName)) { qpSettings.minimumShouldMatch(parser.textOrNull()); + } else if ("quote_field_suffix".equals(currentFieldName) || "quoteFieldSuffix".equals(currentFieldName)) { + qpSettings.quoteFieldSuffix(parser.textOrNull()); } else { throw new QueryParsingException(parseContext.index(), "[field] query does not support [" + currentFieldName + "]"); } @@ -133,6 +141,7 @@ public class FieldQueryParser implements QueryParser { } qpSettings.defaultAnalyzer(parseContext.mapperService().searchAnalyzer()); + qpSettings.defaultQuoteAnalyzer(parseContext.mapperService().searchQuoteAnalyzer()); if (qpSettings.queryString() == null) { throw new QueryParsingException(parseContext.index(), "No value specified for term query"); diff --git a/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java index ff58326317c..54f45c25f38 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java @@ -34,7 +34,7 @@ import static com.google.common.collect.Lists.newArrayList; * will use the {@link #defaultField(String)} set. The second, when one or more fields are added * (using {@link #field(String)}), will run the parsed query against the provided fields, and combine * them either using DisMax or a plain boolean query (see {@link #useDisMax(boolean)}). - * + *

* (shay.baon) */ public class QueryStringQueryBuilder extends BaseQueryBuilder { @@ -51,6 +51,9 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder { private Operator defaultOperator; private String analyzer; + private String quoteAnalyzer; + + private String quoteFieldSuffix; private Boolean autoGeneratePhraseQueries; @@ -163,6 +166,16 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder { return this; } + /** + * The optional analyzer used to analyze the query string for phrase searches. Note, if a field has search (quote) analyzer + * defined for it, then it will be used automatically. Defaults to the smart search analyzer. + */ + public QueryStringQueryBuilder quoteAnalyzer(String analyzer) { + this.quoteAnalyzer = analyzer; + return this; + } + + /** * Set to true if phrase queries will be automatically generated * when the analyzer returns more than one term from whitespace @@ -258,6 +271,14 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder { return this; } + /** + * An optional field name suffix to automatically try and add to the field searched when using quoted text. + */ + public QueryStringQueryBuilder quoteFieldSuffix(String quoteFieldSuffix) { + this.quoteFieldSuffix = quoteFieldSuffix; + return this; + } + @Override protected void doXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(QueryStringQueryParser.NAME); @@ -291,6 +312,9 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder { if (analyzer != null) { builder.field("analyzer", analyzer); } + if (quoteAnalyzer != null) { + builder.field("quote_analyzer", quoteAnalyzer); + } if (autoGeneratePhraseQueries != null) { builder.field("auto_generate_phrase_queries", autoGeneratePhraseQueries); } @@ -324,6 +348,9 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder { if (minimumShouldMatch != null) { builder.field("minimum_should_match", minimumShouldMatch); } + if (quoteFieldSuffix != null) { + builder.field("quote_field_suffix", quoteFieldSuffix); + } builder.endObject(); } } diff --git a/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java b/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java index 9e5ef29f808..60d92095358 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java @@ -142,6 +142,12 @@ public class QueryStringQueryParser implements QueryParser { throw new QueryParsingException(parseContext.index(), "[query_string] analyzer [" + parser.text() + "] not found"); } qpSettings.forcedAnalyzer(analyzer); + } else if ("quote_analyzer".equals(currentFieldName) || "quoteAnalyzer".equals(currentFieldName)) { + NamedAnalyzer analyzer = parseContext.analysisService().analyzer(parser.text()); + if (analyzer == null) { + throw new QueryParsingException(parseContext.index(), "[query_string] quote_analyzer [" + parser.text() + "] not found"); + } + qpSettings.forcedQuoteAnalyzer(analyzer); } else if ("allow_leading_wildcard".equals(currentFieldName) || "allowLeadingWildcard".equals(currentFieldName)) { qpSettings.allowLeadingWildcard(parser.booleanValue()); } else if ("auto_generate_phrase_queries".equals(currentFieldName) || "autoGeneratePhraseQueries".equals(currentFieldName)) { @@ -170,6 +176,8 @@ public class QueryStringQueryParser implements QueryParser { qpSettings.rewriteMethod(QueryParsers.parseRewriteMethod(parser.textOrNull())); } else if ("minimum_should_match".equals(currentFieldName) || "minimumShouldMatch".equals(currentFieldName)) { qpSettings.minimumShouldMatch(parser.textOrNull()); + } else if ("quote_field_suffix".equals(currentFieldName) || "quoteFieldSuffix".equals(currentFieldName)) { + qpSettings.quoteFieldSuffix(parser.textOrNull()); } else { throw new QueryParsingException(parseContext.index(), "[query_string] query does not support [" + currentFieldName + "]"); } @@ -179,6 +187,7 @@ public class QueryStringQueryParser implements QueryParser { throw new QueryParsingException(parseContext.index(), "query_string must be provided with a [query]"); } qpSettings.defaultAnalyzer(parseContext.mapperService().searchAnalyzer()); + qpSettings.defaultQuoteAnalyzer(parseContext.mapperService().searchQuoteAnalyzer()); if (qpSettings.escape()) { qpSettings.queryString(org.apache.lucene.queryParser.QueryParser.escape(qpSettings.queryString()));