Allow to customize quote analyzer to be used when quoting text in a query_string, closes #1931.

This commit is contained in:
Shay Banon 2012-05-10 11:51:51 +03:00
parent 379976b5ce
commit acbd7b686a
13 changed files with 333 additions and 9 deletions

View File

@ -64,12 +64,17 @@ public class MapperQueryParser extends QueryParser {
private final QueryParseContext parseContext; private final QueryParseContext parseContext;
private Analyzer quoteAnalyzer;
private boolean forcedAnalyzer; private boolean forcedAnalyzer;
private boolean forcedQuoteAnalyzer;
private FieldMapper currentMapper; private FieldMapper currentMapper;
private boolean analyzeWildcard; private boolean analyzeWildcard;
private String quoteFieldSuffix;
public MapperQueryParser(QueryParseContext parseContext) { public MapperQueryParser(QueryParseContext parseContext) {
super(Lucene.QUERYPARSER_VERSION, null, null); super(Lucene.QUERYPARSER_VERSION, null, null);
this.parseContext = parseContext; this.parseContext = parseContext;
@ -85,6 +90,17 @@ public class MapperQueryParser extends QueryParser {
this.field = settings.defaultField(); this.field = settings.defaultField();
this.forcedAnalyzer = settings.forcedAnalyzer() != null; this.forcedAnalyzer = settings.forcedAnalyzer() != null;
this.analyzer = forcedAnalyzer ? settings.forcedAnalyzer() : settings.defaultAnalyzer(); 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()); setMultiTermRewriteMethod(settings.rewriteMethod());
setEnablePositionIncrements(settings.enablePositionIncrements()); setEnablePositionIncrements(settings.enablePositionIncrements());
setAutoGeneratePhraseQueries(settings.autoGeneratePhraseQueries()); setAutoGeneratePhraseQueries(settings.autoGeneratePhraseQueries());
@ -122,10 +138,25 @@ public class MapperQueryParser extends QueryParser {
currentMapper = null; currentMapper = null;
Analyzer oldAnalyzer = analyzer; Analyzer oldAnalyzer = analyzer;
try { 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 (fieldMappers != null) {
if (!forcedAnalyzer) { if (quoted) {
analyzer = fieldMappers.searchAnalyzer(); if (!forcedQuoteAnalyzer) {
analyzer = fieldMappers.searchQuoteAnalyzer();
}
} else {
if (!forcedAnalyzer) {
analyzer = fieldMappers.searchAnalyzer();
}
} }
currentMapper = fieldMappers.fieldMappers().mapper(); currentMapper = fieldMappers.fieldMappers().mapper();
if (currentMapper != null) { if (currentMapper != null) {

View File

@ -45,7 +45,10 @@ public class QueryParserSettings {
private boolean analyzeWildcard = DEFAULT_ANALYZE_WILDCARD; private boolean analyzeWildcard = DEFAULT_ANALYZE_WILDCARD;
private boolean escape = false; private boolean escape = false;
private Analyzer defaultAnalyzer = null; private Analyzer defaultAnalyzer = null;
private Analyzer defaultQuoteAnalyzer = null;
private Analyzer forcedAnalyzer = null; private Analyzer forcedAnalyzer = null;
private Analyzer forcedQuoteAnalyzer = null;
private String quoteFieldSuffix = null;
private MultiTermQuery.RewriteMethod rewriteMethod = MultiTermQuery.CONSTANT_SCORE_AUTO_REWRITE_DEFAULT; private MultiTermQuery.RewriteMethod rewriteMethod = MultiTermQuery.CONSTANT_SCORE_AUTO_REWRITE_DEFAULT;
private String minimumShouldMatch; private String minimumShouldMatch;
@ -153,6 +156,14 @@ public class QueryParserSettings {
this.defaultAnalyzer = defaultAnalyzer; this.defaultAnalyzer = defaultAnalyzer;
} }
public Analyzer defaultQuoteAnalyzer() {
return defaultQuoteAnalyzer;
}
public void defaultQuoteAnalyzer(Analyzer defaultAnalyzer) {
this.defaultQuoteAnalyzer = defaultAnalyzer;
}
public Analyzer forcedAnalyzer() { public Analyzer forcedAnalyzer() {
return forcedAnalyzer; return forcedAnalyzer;
} }
@ -161,6 +172,14 @@ public class QueryParserSettings {
this.forcedAnalyzer = forcedAnalyzer; this.forcedAnalyzer = forcedAnalyzer;
} }
public Analyzer forcedQuoteAnalyzer() {
return forcedQuoteAnalyzer;
}
public void forcedQuoteAnalyzer(Analyzer forcedAnalyzer) {
this.forcedQuoteAnalyzer = forcedAnalyzer;
}
public boolean analyzeWildcard() { public boolean analyzeWildcard() {
return this.analyzeWildcard; return this.analyzeWildcard;
} }
@ -185,6 +204,14 @@ public class QueryParserSettings {
this.minimumShouldMatch = minimumShouldMatch; this.minimumShouldMatch = minimumShouldMatch;
} }
public void quoteFieldSuffix(String quoteFieldSuffix) {
this.quoteFieldSuffix = quoteFieldSuffix;
}
public String quoteFieldSuffix() {
return this.quoteFieldSuffix;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -204,8 +231,12 @@ public class QueryParserSettings {
if (phraseSlop != that.phraseSlop) return false; if (phraseSlop != that.phraseSlop) return false;
if (defaultAnalyzer != null ? !defaultAnalyzer.equals(that.defaultAnalyzer) : that.defaultAnalyzer != null) if (defaultAnalyzer != null ? !defaultAnalyzer.equals(that.defaultAnalyzer) : that.defaultAnalyzer != null)
return false; return false;
if (defaultQuoteAnalyzer != null ? !defaultQuoteAnalyzer.equals(that.defaultQuoteAnalyzer) : that.defaultQuoteAnalyzer != null)
return false;
if (forcedAnalyzer != null ? !forcedAnalyzer.equals(that.forcedAnalyzer) : that.forcedAnalyzer != null) if (forcedAnalyzer != null ? !forcedAnalyzer.equals(that.forcedAnalyzer) : that.forcedAnalyzer != null)
return false; 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 (defaultField != null ? !defaultField.equals(that.defaultField) : that.defaultField != null) return false;
if (defaultOperator != that.defaultOperator) return false; if (defaultOperator != that.defaultOperator) return false;
if (queryString != null ? !queryString.equals(that.queryString) : that.queryString != null) return false; if (queryString != null ? !queryString.equals(that.queryString) : that.queryString != null) return false;
@ -213,6 +244,8 @@ public class QueryParserSettings {
return false; return false;
if (minimumShouldMatch != null ? !minimumShouldMatch.equals(that.minimumShouldMatch) : that.minimumShouldMatch != null) if (minimumShouldMatch != null ? !minimumShouldMatch.equals(that.minimumShouldMatch) : that.minimumShouldMatch != null)
return false; return false;
if (quoteFieldSuffix != null ? !quoteFieldSuffix.equals(that.quoteFieldSuffix) : that.quoteFieldSuffix != null)
return false;
return true; return true;
} }
@ -232,7 +265,9 @@ public class QueryParserSettings {
result = 31 * result + fuzzyPrefixLength; result = 31 * result + fuzzyPrefixLength;
result = 31 * result + (escape ? 1 : 0); result = 31 * result + (escape ? 1 : 0);
result = 31 * result + (defaultAnalyzer != null ? defaultAnalyzer.hashCode() : 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 + (forcedAnalyzer != null ? forcedAnalyzer.hashCode() : 0);
result = 31 * result + (forcedQuoteAnalyzer != null ? forcedQuoteAnalyzer.hashCode() : 0);
result = 31 * result + (analyzeWildcard ? 1 : 0); result = 31 * result + (analyzeWildcard ? 1 : 0);
return result; return result;
} }

View File

@ -51,6 +51,7 @@ public class AnalysisService extends AbstractIndexComponent implements Closeable
private final NamedAnalyzer defaultAnalyzer; private final NamedAnalyzer defaultAnalyzer;
private final NamedAnalyzer defaultIndexAnalyzer; private final NamedAnalyzer defaultIndexAnalyzer;
private final NamedAnalyzer defaultSearchAnalyzer; private final NamedAnalyzer defaultSearchAnalyzer;
private final NamedAnalyzer defaultSearchQuoteAnalyzer;
public AnalysisService(Index index) { public AnalysisService(Index index) {
this(index, ImmutableSettings.Builder.EMPTY_SETTINGS, null, null, null, null, null); 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")) { if (!analyzerProviders.containsKey("default_search")) {
analyzerProviders.put("default_search", analyzerProviders.get("default")); analyzerProviders.put("default_search", analyzerProviders.get("default"));
} }
if (!analyzerProviders.containsKey("default_search_quoted")) {
analyzerProviders.put("default_search_quoted", analyzerProviders.get("default_search"));
}
Map<String, NamedAnalyzer> analyzers = newHashMap(); Map<String, NamedAnalyzer> analyzers = newHashMap();
for (AnalyzerProvider analyzerFactory : analyzerProviders.values()) { for (AnalyzerProvider analyzerFactory : analyzerProviders.values()) {
@ -233,6 +237,7 @@ public class AnalysisService extends AbstractIndexComponent implements Closeable
defaultAnalyzer = analyzers.get("default"); defaultAnalyzer = analyzers.get("default");
defaultIndexAnalyzer = analyzers.containsKey("default_index") ? analyzers.get("default_index") : 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"); 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); this.analyzers = ImmutableMap.copyOf(analyzers);
} }
@ -268,6 +273,10 @@ public class AnalysisService extends AbstractIndexComponent implements Closeable
return defaultSearchAnalyzer; return defaultSearchAnalyzer;
} }
public NamedAnalyzer defaultSearchQuoteAnalyzer() {
return defaultSearchQuoteAnalyzer;
}
public TokenizerFactory tokenizer(String name) { public TokenizerFactory tokenizer(String name) {
return tokenizers.get(name); return tokenizers.get(name);
} }

View File

@ -42,6 +42,7 @@ public class DocumentFieldMappers implements Iterable<FieldMapper> {
private final FieldNameAnalyzer indexAnalyzer; private final FieldNameAnalyzer indexAnalyzer;
private final FieldNameAnalyzer searchAnalyzer; private final FieldNameAnalyzer searchAnalyzer;
private final FieldNameAnalyzer searchQuoteAnalyzer;
public DocumentFieldMappers(DocumentMapper docMapper, Iterable<FieldMapper> fieldMappers) { public DocumentFieldMappers(DocumentMapper docMapper, Iterable<FieldMapper> fieldMappers) {
final Map<String, FieldMappers> tempNameFieldMappers = newHashMap(); final Map<String, FieldMappers> tempNameFieldMappers = newHashMap();
@ -50,6 +51,7 @@ public class DocumentFieldMappers implements Iterable<FieldMapper> {
final Map<String, Analyzer> indexAnalyzers = newHashMap(); final Map<String, Analyzer> indexAnalyzers = newHashMap();
final Map<String, Analyzer> searchAnalyzers = newHashMap(); final Map<String, Analyzer> searchAnalyzers = newHashMap();
final Map<String, Analyzer> searchQuoteAnalyzers = newHashMap();
for (FieldMapper fieldMapper : fieldMappers) { for (FieldMapper fieldMapper : fieldMappers) {
FieldMappers mappers = tempNameFieldMappers.get(fieldMapper.names().name()); FieldMappers mappers = tempNameFieldMappers.get(fieldMapper.names().name());
@ -82,6 +84,9 @@ public class DocumentFieldMappers implements Iterable<FieldMapper> {
if (fieldMapper.searchAnalyzer() != null) { if (fieldMapper.searchAnalyzer() != null) {
searchAnalyzers.put(fieldMapper.names().indexName(), fieldMapper.searchAnalyzer()); searchAnalyzers.put(fieldMapper.names().indexName(), fieldMapper.searchAnalyzer());
} }
if (fieldMapper.searchQuoteAnalyzer() != null) {
searchQuoteAnalyzers.put(fieldMapper.names().indexName(), fieldMapper.searchQuoteAnalyzer());
}
} }
this.fieldMappers = ImmutableList.copyOf(fieldMappers); this.fieldMappers = ImmutableList.copyOf(fieldMappers);
this.nameFieldMappers = ImmutableMap.copyOf(tempNameFieldMappers); this.nameFieldMappers = ImmutableMap.copyOf(tempNameFieldMappers);
@ -90,6 +95,7 @@ public class DocumentFieldMappers implements Iterable<FieldMapper> {
this.indexAnalyzer = new FieldNameAnalyzer(indexAnalyzers, docMapper.indexAnalyzer()); this.indexAnalyzer = new FieldNameAnalyzer(indexAnalyzers, docMapper.indexAnalyzer());
this.searchAnalyzer = new FieldNameAnalyzer(searchAnalyzers, docMapper.searchAnalyzer()); this.searchAnalyzer = new FieldNameAnalyzer(searchAnalyzers, docMapper.searchAnalyzer());
this.searchQuoteAnalyzer = new FieldNameAnalyzer(searchQuoteAnalyzers, docMapper.searchQuotedAnalyzer());
} }
@Override @Override
@ -179,6 +185,10 @@ public class DocumentFieldMappers implements Iterable<FieldMapper> {
return this.searchAnalyzer; return this.searchAnalyzer;
} }
public Analyzer searchQuoteAnalyzer() {
return this.searchQuoteAnalyzer;
}
public DocumentFieldMappers concat(DocumentMapper docMapper, FieldMapper... fieldMappers) { public DocumentFieldMappers concat(DocumentMapper docMapper, FieldMapper... fieldMappers) {
return concat(docMapper, newArrayList(fieldMappers)); return concat(docMapper, newArrayList(fieldMappers));
} }

View File

@ -132,6 +132,8 @@ public class DocumentMapper implements ToXContent {
private NamedAnalyzer searchAnalyzer; private NamedAnalyzer searchAnalyzer;
private NamedAnalyzer searchQuoteAnalyzer;
private final String index; private final String index;
@Nullable @Nullable
@ -193,6 +195,14 @@ public class DocumentMapper implements ToXContent {
public Builder searchAnalyzer(NamedAnalyzer searchAnalyzer) { public Builder searchAnalyzer(NamedAnalyzer searchAnalyzer) {
this.searchAnalyzer = searchAnalyzer; this.searchAnalyzer = searchAnalyzer;
if (this.searchQuoteAnalyzer == null) {
this.searchQuoteAnalyzer = searchAnalyzer;
}
return this;
}
public Builder searchQuoteAnalyzer(NamedAnalyzer searchQuoteAnalyzer) {
this.searchQuoteAnalyzer = searchQuoteAnalyzer;
return this; return this;
} }
@ -200,10 +210,14 @@ public class DocumentMapper implements ToXContent {
return searchAnalyzer != null; return searchAnalyzer != null;
} }
public boolean hasSearchQuoteAnalyzer() {
return searchQuoteAnalyzer != null;
}
public DocumentMapper build(DocumentMapperParser docMapperParser) { public DocumentMapper build(DocumentMapperParser docMapperParser) {
Preconditions.checkNotNull(rootObjectMapper, "Mapper builder must have the root object mapper set"); Preconditions.checkNotNull(rootObjectMapper, "Mapper builder must have the root object mapper set");
return new DocumentMapper(index, indexSettings, docMapperParser, rootObjectMapper, meta, return new DocumentMapper(index, indexSettings, docMapperParser, rootObjectMapper, meta,
indexAnalyzer, searchAnalyzer, indexAnalyzer, searchAnalyzer, searchQuoteAnalyzer,
rootMappers); rootMappers);
} }
} }
@ -237,6 +251,7 @@ public class DocumentMapper implements ToXContent {
private final NamedAnalyzer indexAnalyzer; private final NamedAnalyzer indexAnalyzer;
private final NamedAnalyzer searchAnalyzer; private final NamedAnalyzer searchAnalyzer;
private final NamedAnalyzer searchQuoteAnalyzer;
private volatile DocumentFieldMappers fieldMappers; private volatile DocumentFieldMappers fieldMappers;
@ -257,7 +272,7 @@ public class DocumentMapper implements ToXContent {
public DocumentMapper(String index, @Nullable Settings indexSettings, DocumentMapperParser docMapperParser, public DocumentMapper(String index, @Nullable Settings indexSettings, DocumentMapperParser docMapperParser,
RootObjectMapper rootObjectMapper, RootObjectMapper rootObjectMapper,
ImmutableMap<String, Object> meta, ImmutableMap<String, Object> meta,
NamedAnalyzer indexAnalyzer, NamedAnalyzer searchAnalyzer, NamedAnalyzer indexAnalyzer, NamedAnalyzer searchAnalyzer, NamedAnalyzer searchQuoteAnalyzer,
Map<Class<? extends RootMapper>, RootMapper> rootMappers) { Map<Class<? extends RootMapper>, RootMapper> rootMappers) {
this.index = index; this.index = index;
this.indexSettings = indexSettings; this.indexSettings = indexSettings;
@ -278,6 +293,7 @@ public class DocumentMapper implements ToXContent {
this.indexAnalyzer = indexAnalyzer; this.indexAnalyzer = indexAnalyzer;
this.searchAnalyzer = searchAnalyzer; this.searchAnalyzer = searchAnalyzer;
this.searchQuoteAnalyzer = searchQuoteAnalyzer != null ? searchQuoteAnalyzer : searchAnalyzer;
this.typeFilter = typeMapper().fieldFilter(type, null); this.typeFilter = typeMapper().fieldFilter(type, null);
@ -389,6 +405,10 @@ public class DocumentMapper implements ToXContent {
return this.searchAnalyzer; return this.searchAnalyzer;
} }
public Analyzer searchQuotedAnalyzer() {
return this.searchQuoteAnalyzer;
}
public Filter typeFilter() { public Filter typeFilter() {
return this.typeFilter; return this.typeFilter;
} }

View File

@ -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 + "]"); throw new MapperParsingException("Analyzer [" + fieldNode.toString() + "] not found for search_analyzer setting on root type [" + type + "]");
} }
docBuilder.searchAnalyzer(analyzer); 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)) { } else if ("analyzer".equals(fieldName)) {
NamedAnalyzer analyzer = analysisService.analyzer(fieldNode.toString()); NamedAnalyzer analyzer = analysisService.analyzer(fieldNode.toString());
if (analyzer == null) { if (analyzer == null) {
@ -198,6 +204,9 @@ public class DocumentMapperParser extends AbstractIndexComponent {
if (!docBuilder.hasSearchAnalyzer()) { if (!docBuilder.hasSearchAnalyzer()) {
docBuilder.searchAnalyzer(analysisService.defaultSearchAnalyzer()); docBuilder.searchAnalyzer(analysisService.defaultSearchAnalyzer());
} }
if (!docBuilder.hasSearchQuoteAnalyzer()) {
docBuilder.searchAnalyzer(analysisService.defaultSearchQuoteAnalyzer());
}
ImmutableMap<String, Object> attributes = ImmutableMap.of(); ImmutableMap<String, Object> attributes = ImmutableMap.of();
if (mapping.containsKey("_meta")) { if (mapping.containsKey("_meta")) {

View File

@ -147,6 +147,11 @@ public interface FieldMapper<T> {
*/ */
Analyzer searchAnalyzer(); 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... . * Returns the value that will be used as a result for search. Can be only of specific types... .
*/ */

View File

@ -95,6 +95,7 @@ public class MapperService extends AbstractIndexComponent implements Iterable<Do
private final InternalObjectMapperListener objectMapperListener = new InternalObjectMapperListener(); private final InternalObjectMapperListener objectMapperListener = new InternalObjectMapperListener();
private final SmartIndexNameSearchAnalyzer searchAnalyzer; private final SmartIndexNameSearchAnalyzer searchAnalyzer;
private final SmartIndexNameSearchQuoteAnalyzer searchQuoteAnalyzer;
@Inject @Inject
public MapperService(Index index, @IndexSettings Settings indexSettings, Environment environment, AnalysisService analysisService) { public MapperService(Index index, @IndexSettings Settings indexSettings, Environment environment, AnalysisService analysisService) {
@ -102,6 +103,7 @@ public class MapperService extends AbstractIndexComponent implements Iterable<Do
this.analysisService = analysisService; this.analysisService = analysisService;
this.documentParser = new DocumentMapperParser(index, indexSettings, analysisService); this.documentParser = new DocumentMapperParser(index, indexSettings, analysisService);
this.searchAnalyzer = new SmartIndexNameSearchAnalyzer(analysisService.defaultSearchAnalyzer()); this.searchAnalyzer = new SmartIndexNameSearchAnalyzer(analysisService.defaultSearchAnalyzer());
this.searchQuoteAnalyzer = new SmartIndexNameSearchQuoteAnalyzer(analysisService.defaultSearchQuoteAnalyzer());
this.dynamic = componentSettings.getAsBoolean("dynamic", true); this.dynamic = componentSettings.getAsBoolean("dynamic", true);
String defaultMappingLocation = componentSettings.get("default_mapping_location"); String defaultMappingLocation = componentSettings.get("default_mapping_location");
@ -665,6 +667,10 @@ public class MapperService extends AbstractIndexComponent implements Iterable<Do
return this.searchAnalyzer; return this.searchAnalyzer;
} }
public Analyzer searchQuoteAnalyzer() {
return this.searchQuoteAnalyzer;
}
public static class SmartNameObjectMapper { public static class SmartNameObjectMapper {
private final ObjectMapper mapper; private final ObjectMapper mapper;
private final DocumentMapper docMapper; private final DocumentMapper docMapper;
@ -767,6 +773,19 @@ public class MapperService extends AbstractIndexComponent implements Iterable<Do
} }
return mapperService.searchAnalyzer(); return mapperService.searchAnalyzer();
} }
public Analyzer searchQuoteAnalyzer() {
if (hasMapper()) {
Analyzer analyzer = mapper().searchQuoteAnalyzer();
if (analyzer != null) {
return analyzer;
}
}
if (docMapper != null && docMapper.searchQuotedAnalyzer() != null) {
return docMapper.searchQuotedAnalyzer();
}
return mapperService.searchQuoteAnalyzer();
}
} }
final class SmartIndexNameSearchAnalyzer extends Analyzer { final class SmartIndexNameSearchAnalyzer extends Analyzer {
@ -867,6 +886,104 @@ public class MapperService extends AbstractIndexComponent implements Iterable<Do
} }
} }
final class SmartIndexNameSearchQuoteAnalyzer extends Analyzer {
private final Analyzer defaultAnalyzer;
SmartIndexNameSearchQuoteAnalyzer(Analyzer defaultAnalyzer) {
this.defaultAnalyzer = defaultAnalyzer;
}
@Override
public int getPositionIncrementGap(String fieldName) {
int dotIndex = fieldName.indexOf('.');
if (dotIndex != -1) {
String possibleType = fieldName.substring(0, dotIndex);
DocumentMapper possibleDocMapper = mappers.get(possibleType);
if (possibleDocMapper != null) {
return possibleDocMapper.mappers().searchQuoteAnalyzer().getPositionIncrementGap(fieldName);
}
}
FieldMappers mappers = fullNameFieldMappers.get(fieldName);
if (mappers != null && mappers.mapper() != null && mappers.mapper().searchAnalyzer() != null) {
return mappers.mapper().searchQuoteAnalyzer().getPositionIncrementGap(fieldName);
}
mappers = indexNameFieldMappers.get(fieldName);
if (mappers != null && mappers.mapper() != null && mappers.mapper().searchAnalyzer() != null) {
return mappers.mapper().searchQuoteAnalyzer().getPositionIncrementGap(fieldName);
}
return defaultAnalyzer.getPositionIncrementGap(fieldName);
}
@Override
public int getOffsetGap(Fieldable field) {
String fieldName = field.name();
int dotIndex = fieldName.indexOf('.');
if (dotIndex != -1) {
String possibleType = fieldName.substring(0, dotIndex);
DocumentMapper possibleDocMapper = mappers.get(possibleType);
if (possibleDocMapper != null) {
return possibleDocMapper.mappers().searchQuoteAnalyzer().getOffsetGap(field);
}
}
FieldMappers mappers = fullNameFieldMappers.get(fieldName);
if (mappers != null && mappers.mapper() != null && mappers.mapper().searchAnalyzer() != null) {
return mappers.mapper().searchQuoteAnalyzer().getOffsetGap(field);
}
mappers = indexNameFieldMappers.get(fieldName);
if (mappers != null && mappers.mapper() != null && mappers.mapper().searchAnalyzer() != null) {
return mappers.mapper().searchQuoteAnalyzer().getOffsetGap(field);
}
return defaultAnalyzer.getOffsetGap(field);
}
@Override
public final TokenStream tokenStream(String fieldName, Reader reader) {
int dotIndex = fieldName.indexOf('.');
if (dotIndex != -1) {
String possibleType = fieldName.substring(0, dotIndex);
DocumentMapper possibleDocMapper = mappers.get(possibleType);
if (possibleDocMapper != null) {
return possibleDocMapper.mappers().searchQuoteAnalyzer().tokenStream(fieldName, reader);
}
}
FieldMappers mappers = fullNameFieldMappers.get(fieldName);
if (mappers != null && mappers.mapper() != null && mappers.mapper().searchAnalyzer() != null) {
return mappers.mapper().searchQuoteAnalyzer().tokenStream(fieldName, reader);
}
mappers = indexNameFieldMappers.get(fieldName);
if (mappers != null && mappers.mapper() != null && mappers.mapper().searchAnalyzer() != null) {
return mappers.mapper().searchQuoteAnalyzer().tokenStream(fieldName, reader);
}
return defaultAnalyzer.tokenStream(fieldName, reader);
}
@Override
public final TokenStream reusableTokenStream(String fieldName, Reader reader) throws IOException {
int dotIndex = fieldName.indexOf('.');
if (dotIndex != -1) {
String possibleType = fieldName.substring(0, dotIndex);
DocumentMapper possibleDocMapper = mappers.get(possibleType);
if (possibleDocMapper != null) {
return possibleDocMapper.mappers().searchQuoteAnalyzer().reusableTokenStream(fieldName, reader);
}
}
FieldMappers mappers = fullNameFieldMappers.get(fieldName);
if (mappers != null && mappers.mapper() != null && mappers.mapper().searchAnalyzer() != null) {
return mappers.mapper().searchQuoteAnalyzer().reusableTokenStream(fieldName, reader);
}
mappers = indexNameFieldMappers.get(fieldName);
if (mappers != null && mappers.mapper() != null && mappers.mapper().searchAnalyzer() != null) {
return mappers.mapper().searchQuoteAnalyzer().reusableTokenStream(fieldName, reader);
}
return defaultAnalyzer.reusableTokenStream(fieldName, reader);
}
}
class InternalFieldMapperListener implements FieldMapperListener { class InternalFieldMapperListener implements FieldMapperListener {
@Override @Override
public void fieldMapper(FieldMapper fieldMapper) { public void fieldMapper(FieldMapper fieldMapper) {

View File

@ -301,6 +301,11 @@ public abstract class AbstractFieldMapper<T> implements FieldMapper<T>, Mapper {
return this.searchAnalyzer; return this.searchAnalyzer;
} }
@Override
public Analyzer searchQuoteAnalyzer() {
return this.searchAnalyzer;
}
@Override @Override
public void parse(ParseContext context) throws IOException { public void parse(ParseContext context) throws IOException {
try { try {

View File

@ -19,6 +19,7 @@
package org.elasticsearch.index.mapper.core; package org.elasticsearch.index.mapper.core;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Field; import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable; import org.apache.lucene.document.Fieldable;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
@ -55,6 +56,8 @@ public class StringFieldMapper extends AbstractFieldMapper<String> implements Al
protected int positionOffsetGap = Defaults.POSITION_OFFSET_GAP; protected int positionOffsetGap = Defaults.POSITION_OFFSET_GAP;
protected NamedAnalyzer searchQuotedAnalyzer;
public Builder(String name) { public Builder(String name) {
super(name); super(name);
builder = this; builder = this;
@ -71,20 +74,35 @@ public class StringFieldMapper extends AbstractFieldMapper<String> implements Al
return this; return this;
} }
@Override
public Builder searchAnalyzer(NamedAnalyzer searchAnalyzer) {
super.searchAnalyzer(searchAnalyzer);
if (searchQuotedAnalyzer == null) {
searchQuotedAnalyzer = searchAnalyzer;
}
return this;
}
public Builder positionOffsetGap(int positionOffsetGap) { public Builder positionOffsetGap(int positionOffsetGap) {
this.positionOffsetGap = positionOffsetGap; this.positionOffsetGap = positionOffsetGap;
return this; return this;
} }
public Builder searchQuotedAnalyzer(NamedAnalyzer analyzer) {
this.searchQuotedAnalyzer = analyzer;
return builder;
}
@Override @Override
public StringFieldMapper build(BuilderContext context) { public StringFieldMapper build(BuilderContext context) {
if (positionOffsetGap > 0) { if (positionOffsetGap > 0) {
indexAnalyzer = new NamedCustomAnalyzer(indexAnalyzer, positionOffsetGap); indexAnalyzer = new NamedCustomAnalyzer(indexAnalyzer, positionOffsetGap);
searchAnalyzer = new NamedCustomAnalyzer(searchAnalyzer, positionOffsetGap); searchAnalyzer = new NamedCustomAnalyzer(searchAnalyzer, positionOffsetGap);
searchQuotedAnalyzer = new NamedCustomAnalyzer(searchQuotedAnalyzer, positionOffsetGap);
} }
StringFieldMapper fieldMapper = new StringFieldMapper(buildNames(context), StringFieldMapper fieldMapper = new StringFieldMapper(buildNames(context),
index, store, termVector, boost, omitNorms, omitTermFreqAndPositions, nullValue, index, store, termVector, boost, omitNorms, omitTermFreqAndPositions, nullValue,
indexAnalyzer, searchAnalyzer, positionOffsetGap); indexAnalyzer, searchAnalyzer, searchQuotedAnalyzer, positionOffsetGap);
fieldMapper.includeInAll(includeInAll); fieldMapper.includeInAll(includeInAll);
return fieldMapper; return fieldMapper;
} }
@ -100,6 +118,12 @@ public class StringFieldMapper extends AbstractFieldMapper<String> implements Al
Object propNode = entry.getValue(); Object propNode = entry.getValue();
if (propName.equals("null_value")) { if (propName.equals("null_value")) {
builder.nullValue(propNode.toString()); 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")) { } else if (propName.equals("position_offset_gap")) {
builder.positionOffsetGap(XContentMapValues.nodeIntegerValue(propNode, -1)); builder.positionOffsetGap(XContentMapValues.nodeIntegerValue(propNode, -1));
// we need to update to actual analyzers if they are not set in this case... // we need to update to actual analyzers if they are not set in this case...
@ -110,6 +134,9 @@ public class StringFieldMapper extends AbstractFieldMapper<String> implements Al
if (builder.searchAnalyzer == null) { if (builder.searchAnalyzer == null) {
builder.searchAnalyzer = parserContext.analysisService().defaultSearchAnalyzer(); builder.searchAnalyzer = parserContext.analysisService().defaultSearchAnalyzer();
} }
if (builder.searchQuotedAnalyzer == null) {
builder.searchQuotedAnalyzer = parserContext.analysisService().defaultSearchQuoteAnalyzer();
}
} }
} }
return builder; return builder;
@ -122,18 +149,21 @@ public class StringFieldMapper extends AbstractFieldMapper<String> implements Al
private int positionOffsetGap; private int positionOffsetGap;
private NamedAnalyzer searchQuotedAnalyzer;
protected StringFieldMapper(Names names, Field.Index index, Field.Store store, Field.TermVector termVector, protected StringFieldMapper(Names names, Field.Index index, Field.Store store, Field.TermVector termVector,
float boost, boolean omitNorms, boolean omitTermFreqAndPositions, float boost, boolean omitNorms, boolean omitTermFreqAndPositions,
String nullValue, NamedAnalyzer indexAnalyzer, NamedAnalyzer searchAnalyzer) { 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, protected StringFieldMapper(Names names, Field.Index index, Field.Store store, Field.TermVector termVector,
float boost, boolean omitNorms, boolean omitTermFreqAndPositions, 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); super(names, index, store, termVector, boost, omitNorms, omitTermFreqAndPositions, indexAnalyzer, searchAnalyzer);
this.nullValue = nullValue; this.nullValue = nullValue;
this.positionOffsetGap = positionOffsetGap; this.positionOffsetGap = positionOffsetGap;
this.searchQuotedAnalyzer = searchQuotedAnalyzer != null ? searchQuotedAnalyzer : searchAnalyzer;
} }
@Override @Override
@ -179,6 +209,11 @@ public class StringFieldMapper extends AbstractFieldMapper<String> implements Al
return this.positionOffsetGap; return this.positionOffsetGap;
} }
@Override
public Analyzer searchQuoteAnalyzer() {
return this.searchQuotedAnalyzer;
}
@Override @Override
protected Field parseCreateField(ParseContext context) throws IOException { protected Field parseCreateField(ParseContext context) throws IOException {
String value = nullValue; String value = nullValue;
@ -266,5 +301,8 @@ public class StringFieldMapper extends AbstractFieldMapper<String> implements Al
if (positionOffsetGap != Defaults.POSITION_OFFSET_GAP) { if (positionOffsetGap != Defaults.POSITION_OFFSET_GAP) {
builder.field("position_offset_gap", positionOffsetGap); builder.field("position_offset_gap", positionOffsetGap);
} }
if (searchQuotedAnalyzer != null && searchAnalyzer != searchQuotedAnalyzer) {
builder.field("search_quote_analyzer", searchQuotedAnalyzer.name());
}
} }
} }

View File

@ -99,6 +99,12 @@ public class FieldQueryParser implements QueryParser {
throw new QueryParsingException(parseContext.index(), "[query_string] analyzer [" + parser.text() + "] not found"); throw new QueryParsingException(parseContext.index(), "[query_string] analyzer [" + parser.text() + "] not found");
} }
qpSettings.forcedAnalyzer(analyzer); 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)) { } else if ("default_operator".equals(currentFieldName) || "defaultOperator".equals(currentFieldName)) {
String op = parser.text(); String op = parser.text();
if ("or".equalsIgnoreCase(op)) { if ("or".equalsIgnoreCase(op)) {
@ -120,6 +126,8 @@ public class FieldQueryParser implements QueryParser {
qpSettings.rewriteMethod(QueryParsers.parseRewriteMethod(parser.textOrNull())); qpSettings.rewriteMethod(QueryParsers.parseRewriteMethod(parser.textOrNull()));
} else if ("minimum_should_match".equals(currentFieldName) || "minimumShouldMatch".equals(currentFieldName)) { } else if ("minimum_should_match".equals(currentFieldName) || "minimumShouldMatch".equals(currentFieldName)) {
qpSettings.minimumShouldMatch(parser.textOrNull()); qpSettings.minimumShouldMatch(parser.textOrNull());
} else if ("quote_field_suffix".equals(currentFieldName) || "quoteFieldSuffix".equals(currentFieldName)) {
qpSettings.quoteFieldSuffix(parser.textOrNull());
} else { } else {
throw new QueryParsingException(parseContext.index(), "[field] query does not support [" + currentFieldName + "]"); 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.defaultAnalyzer(parseContext.mapperService().searchAnalyzer());
qpSettings.defaultQuoteAnalyzer(parseContext.mapperService().searchQuoteAnalyzer());
if (qpSettings.queryString() == null) { if (qpSettings.queryString() == null) {
throw new QueryParsingException(parseContext.index(), "No value specified for term query"); throw new QueryParsingException(parseContext.index(), "No value specified for term query");

View File

@ -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 * 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 * (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)}). * them either using DisMax or a plain boolean query (see {@link #useDisMax(boolean)}).
* * <p/>
* (shay.baon) * (shay.baon)
*/ */
public class QueryStringQueryBuilder extends BaseQueryBuilder { public class QueryStringQueryBuilder extends BaseQueryBuilder {
@ -51,6 +51,9 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder {
private Operator defaultOperator; private Operator defaultOperator;
private String analyzer; private String analyzer;
private String quoteAnalyzer;
private String quoteFieldSuffix;
private Boolean autoGeneratePhraseQueries; private Boolean autoGeneratePhraseQueries;
@ -163,6 +166,16 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder {
return this; 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 * Set to true if phrase queries will be automatically generated
* when the analyzer returns more than one term from whitespace * when the analyzer returns more than one term from whitespace
@ -258,6 +271,14 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder {
return this; 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 @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(QueryStringQueryParser.NAME); builder.startObject(QueryStringQueryParser.NAME);
@ -291,6 +312,9 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder {
if (analyzer != null) { if (analyzer != null) {
builder.field("analyzer", analyzer); builder.field("analyzer", analyzer);
} }
if (quoteAnalyzer != null) {
builder.field("quote_analyzer", quoteAnalyzer);
}
if (autoGeneratePhraseQueries != null) { if (autoGeneratePhraseQueries != null) {
builder.field("auto_generate_phrase_queries", autoGeneratePhraseQueries); builder.field("auto_generate_phrase_queries", autoGeneratePhraseQueries);
} }
@ -324,6 +348,9 @@ public class QueryStringQueryBuilder extends BaseQueryBuilder {
if (minimumShouldMatch != null) { if (minimumShouldMatch != null) {
builder.field("minimum_should_match", minimumShouldMatch); builder.field("minimum_should_match", minimumShouldMatch);
} }
if (quoteFieldSuffix != null) {
builder.field("quote_field_suffix", quoteFieldSuffix);
}
builder.endObject(); builder.endObject();
} }
} }

View File

@ -142,6 +142,12 @@ public class QueryStringQueryParser implements QueryParser {
throw new QueryParsingException(parseContext.index(), "[query_string] analyzer [" + parser.text() + "] not found"); throw new QueryParsingException(parseContext.index(), "[query_string] analyzer [" + parser.text() + "] not found");
} }
qpSettings.forcedAnalyzer(analyzer); 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)) { } else if ("allow_leading_wildcard".equals(currentFieldName) || "allowLeadingWildcard".equals(currentFieldName)) {
qpSettings.allowLeadingWildcard(parser.booleanValue()); qpSettings.allowLeadingWildcard(parser.booleanValue());
} else if ("auto_generate_phrase_queries".equals(currentFieldName) || "autoGeneratePhraseQueries".equals(currentFieldName)) { } 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())); qpSettings.rewriteMethod(QueryParsers.parseRewriteMethod(parser.textOrNull()));
} else if ("minimum_should_match".equals(currentFieldName) || "minimumShouldMatch".equals(currentFieldName)) { } else if ("minimum_should_match".equals(currentFieldName) || "minimumShouldMatch".equals(currentFieldName)) {
qpSettings.minimumShouldMatch(parser.textOrNull()); qpSettings.minimumShouldMatch(parser.textOrNull());
} else if ("quote_field_suffix".equals(currentFieldName) || "quoteFieldSuffix".equals(currentFieldName)) {
qpSettings.quoteFieldSuffix(parser.textOrNull());
} else { } else {
throw new QueryParsingException(parseContext.index(), "[query_string] query does not support [" + currentFieldName + "]"); 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]"); throw new QueryParsingException(parseContext.index(), "query_string must be provided with a [query]");
} }
qpSettings.defaultAnalyzer(parseContext.mapperService().searchAnalyzer()); qpSettings.defaultAnalyzer(parseContext.mapperService().searchAnalyzer());
qpSettings.defaultQuoteAnalyzer(parseContext.mapperService().searchQuoteAnalyzer());
if (qpSettings.escape()) { if (qpSettings.escape()) {
qpSettings.queryString(org.apache.lucene.queryParser.QueryParser.escape(qpSettings.queryString())); qpSettings.queryString(org.apache.lucene.queryParser.QueryParser.escape(qpSettings.queryString()));