diff --git a/core/src/main/java/org/apache/lucene/queryparser/classic/MapperQueryParser.java b/core/src/main/java/org/apache/lucene/queryparser/classic/MapperQueryParser.java index 6ed7f5236e1..34fdcfce6a4 100644 --- a/core/src/main/java/org/apache/lucene/queryparser/classic/MapperQueryParser.java +++ b/core/src/main/java/org/apache/lucene/queryparser/classic/MapperQueryParser.java @@ -19,20 +19,12 @@ package org.apache.lucene.queryparser.classic; +import com.google.common.collect.ImmutableMap; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.apache.lucene.index.Term; -import org.apache.lucene.search.BooleanClause; -import org.apache.lucene.search.DisjunctionMaxQuery; -import org.apache.lucene.search.FilteredQuery; -import org.apache.lucene.search.FuzzyQuery; -import org.apache.lucene.search.MatchNoDocsQuery; -import org.apache.lucene.search.MultiPhraseQuery; -import org.apache.lucene.search.PhraseQuery; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.WildcardQuery; -import org.apache.lucene.util.Version; +import org.apache.lucene.search.*; import org.apache.lucene.util.automaton.RegExp; import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.unit.Fuzziness; @@ -42,8 +34,6 @@ import org.elasticsearch.index.mapper.core.DateFieldMapper; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.support.QueryParsers; -import com.google.common.collect.ImmutableMap; - import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -74,17 +64,8 @@ public class MapperQueryParser extends QueryParser { private QueryParserSettings settings; - private Analyzer quoteAnalyzer; - - private boolean forcedAnalyzer; - private boolean forcedQuoteAnalyzer; - private MappedFieldType currentFieldType; - private boolean analyzeWildcard; - - private String quoteFieldSuffix; - public MapperQueryParser(QueryShardContext context) { super(null, null); this.context = context; @@ -92,31 +73,14 @@ public class MapperQueryParser extends QueryParser { public void reset(QueryParserSettings settings) { this.settings = settings; - this.field = settings.defaultField(); - - if (settings.fields() != null) { - if (settings.fields.size() == 1) { - // just mark it as the default field - this.field = settings.fields().get(0); - } else { - // otherwise, we need to have the default field being null... - this.field = null; - } - } - - this.forcedAnalyzer = settings.forcedAnalyzer() != null; - this.setAnalyzer(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(); + if (settings.fieldsAndWeights().isEmpty()) { + this.field = settings.defaultField(); + } else if (settings.fieldsAndWeights().size() == 1) { + this.field = settings.fieldsAndWeights().keySet().iterator().next(); } else { - this.forcedAnalyzer = false; - this.quoteAnalyzer = settings.defaultQuoteAnalyzer(); + this.field = null; } - this.quoteFieldSuffix = settings.quoteFieldSuffix(); + setAnalyzer(settings.analyzer()); setMultiTermRewriteMethod(settings.rewriteMethod()); setEnablePositionIncrements(settings.enablePositionIncrements()); setAutoGeneratePhraseQueries(settings.autoGeneratePhraseQueries()); @@ -125,10 +89,9 @@ public class MapperQueryParser extends QueryParser { setLowercaseExpandedTerms(settings.lowercaseExpandedTerms()); setPhraseSlop(settings.phraseSlop()); setDefaultOperator(settings.defaultOperator()); - setFuzzyMinSim(settings.getFuzziness().asFloat()); + setFuzzyMinSim(settings.fuzziness().asFloat()); setFuzzyPrefixLength(settings.fuzzyPrefixLength()); setLocale(settings.locale()); - this.analyzeWildcard = settings.analyzeWildcard(); } /** @@ -224,9 +187,9 @@ public class MapperQueryParser extends QueryParser { Analyzer oldAnalyzer = getAnalyzer(); try { if (quoted) { - setAnalyzer(quoteAnalyzer); - if (quoteFieldSuffix != null) { - currentFieldType = context.fieldMapper(field + quoteFieldSuffix); + setAnalyzer(settings.quoteAnalyzer()); + if (settings.quoteFieldSuffix() != null) { + currentFieldType = context.fieldMapper(field + settings.quoteFieldSuffix()); } } if (currentFieldType == null) { @@ -234,11 +197,11 @@ public class MapperQueryParser extends QueryParser { } if (currentFieldType != null) { if (quoted) { - if (!forcedQuoteAnalyzer) { + if (!settings.forceQuoteAnalyzer()) { setAnalyzer(context.getSearchQuoteAnalyzer(currentFieldType)); } } else { - if (!forcedAnalyzer) { + if (!settings.forceAnalyzer()) { setAnalyzer(context.getSearchAnalyzer(currentFieldType)); } } @@ -494,7 +457,7 @@ public class MapperQueryParser extends QueryParser { try { currentFieldType = context.fieldMapper(field); if (currentFieldType != null) { - if (!forcedAnalyzer) { + if (!settings.forceAnalyzer()) { setAnalyzer(context.getSearchAnalyzer(currentFieldType)); } Query query = null; @@ -518,7 +481,7 @@ public class MapperQueryParser extends QueryParser { } private Query getPossiblyAnalyzedPrefixQuery(String field, String termStr) throws ParseException { - if (!analyzeWildcard) { + if (!settings.analyzeWildcard()) { return super.getPrefixQuery(field, termStr); } // get Analyzer from superclass and tokenize the term @@ -556,16 +519,7 @@ public class MapperQueryParser extends QueryParser { clauses.add(new BooleanClause(super.getPrefixQuery(field, token), BooleanClause.Occur.SHOULD)); } return getBooleanQuery(clauses, true); - - //return super.getPrefixQuery(field, termStr); - - /* this means that the analyzer used either added or consumed -* (common for a stemmer) tokens, and we can't build a PrefixQuery */ -// throw new ParseException("Cannot build PrefixQuery with analyzer " -// + getAnalyzer().getClass() -// + (tlist.size() > 1 ? " - token(s) added" : " - token consumed")); } - } @Override @@ -635,7 +589,7 @@ public class MapperQueryParser extends QueryParser { try { currentFieldType = context.fieldMapper(field); if (currentFieldType != null) { - if (!forcedAnalyzer) { + if (!settings.forceAnalyzer()) { setAnalyzer(context.getSearchAnalyzer(currentFieldType)); } indexedNameField = currentFieldType.names().indexName(); @@ -653,7 +607,7 @@ public class MapperQueryParser extends QueryParser { } private Query getPossiblyAnalyzedWildcardQuery(String field, String termStr) throws ParseException { - if (!analyzeWildcard) { + if (!settings.analyzeWildcard()) { return super.getWildcardQuery(field, termStr); } boolean isWithinToken = (!termStr.startsWith("?") && !termStr.startsWith("*")); @@ -767,7 +721,7 @@ public class MapperQueryParser extends QueryParser { try { currentFieldType = context.fieldMapper(field); if (currentFieldType != null) { - if (!forcedAnalyzer) { + if (!settings.forceAnalyzer()) { setAnalyzer(context.getSearchAnalyzer(currentFieldType)); } Query query = null; @@ -800,9 +754,9 @@ public class MapperQueryParser extends QueryParser { } private void applyBoost(String field, Query q) { - if (settings.boosts() != null) { - float boost = settings.boosts().getOrDefault(field, 1f); - q.setBoost(boost); + Float fieldBoost = settings.fieldsAndWeights().get(field); + if (fieldBoost != null) { + q.setBoost(fieldBoost); } } @@ -828,11 +782,11 @@ public class MapperQueryParser extends QueryParser { } private Collection extractMultiFields(String field) { - Collection fields = null; + Collection fields; if (field != null) { fields = context.simpleMatchToIndexNames(field); } else { - fields = settings.fields(); + fields = settings.fieldsAndWeights().keySet(); } return fields; } diff --git a/core/src/main/java/org/apache/lucene/queryparser/classic/QueryParserSettings.java b/core/src/main/java/org/apache/lucene/queryparser/classic/QueryParserSettings.java index 76e8b4fccd9..c1fc2ae556e 100644 --- a/core/src/main/java/org/apache/lucene/queryparser/classic/QueryParserSettings.java +++ b/core/src/main/java/org/apache/lucene/queryparser/classic/QueryParserSettings.java @@ -19,66 +19,74 @@ package org.apache.lucene.queryparser.classic; -import com.carrotsearch.hppc.ObjectFloatHashMap; import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.search.FuzzyQuery; import org.apache.lucene.search.MultiTermQuery; -import org.apache.lucene.util.automaton.Operations; import org.elasticsearch.common.unit.Fuzziness; import org.joda.time.DateTimeZone; -import java.util.List; import java.util.Locale; +import java.util.Map; /** - * + * Encapsulates settings that affect query_string parsing via {@link MapperQueryParser} */ public class QueryParserSettings { - public static final boolean DEFAULT_ALLOW_LEADING_WILDCARD = true; - public static final boolean DEFAULT_ANALYZE_WILDCARD = false; - public static final float DEFAULT_BOOST = 1.f; + private final String queryString; - private String queryString; private String defaultField; - private float boost = DEFAULT_BOOST; - private MapperQueryParser.Operator defaultOperator = QueryParser.Operator.OR; - private boolean autoGeneratePhraseQueries = false; - private boolean allowLeadingWildcard = DEFAULT_ALLOW_LEADING_WILDCARD; - private boolean lowercaseExpandedTerms = true; - private boolean enablePositionIncrements = true; - private int phraseSlop = 0; - private Fuzziness fuzziness = Fuzziness.AUTO; - private int fuzzyPrefixLength = FuzzyQuery.defaultPrefixLength; - private int fuzzyMaxExpansions = FuzzyQuery.defaultMaxExpansions; - private int maxDeterminizedStates = Operations.DEFAULT_MAX_DETERMINIZED_STATES; - private MultiTermQuery.RewriteMethod fuzzyRewriteMethod = null; - 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_FILTER_REWRITE; - private String minimumShouldMatch; - private boolean lenient; + + private Map fieldsAndWeights; + + private QueryParser.Operator defaultOperator; + + private Analyzer analyzer; + private boolean forceAnalyzer; + private Analyzer quoteAnalyzer; + private boolean forceQuoteAnalyzer; + + private String quoteFieldSuffix; + + private boolean autoGeneratePhraseQueries; + + private boolean allowLeadingWildcard; + + private boolean analyzeWildcard; + + private boolean lowercaseExpandedTerms; + + private boolean enablePositionIncrements; + private Locale locale; + + private Fuzziness fuzziness; + private int fuzzyPrefixLength; + private int fuzzyMaxExpansions; + private MultiTermQuery.RewriteMethod fuzzyRewriteMethod; + + private int phraseSlop; + + private boolean useDisMax; + + private float tieBreaker; + + private MultiTermQuery.RewriteMethod rewriteMethod; + + private boolean lenient; + private DateTimeZone timeZone; - List fields = null; - ObjectFloatHashMap boosts = null; - float tieBreaker = 0.0f; - boolean useDisMax = true; + /** To limit effort spent determinizing regexp queries. */ + private int maxDeterminizedStates; + + public QueryParserSettings(String queryString) { + this.queryString = queryString; + } public String queryString() { return queryString; } - public void queryString(String queryString) { - this.queryString = queryString; - } - public String defaultField() { return defaultField; } @@ -87,12 +95,12 @@ public class QueryParserSettings { this.defaultField = defaultField; } - public float boost() { - return boost; + public Map fieldsAndWeights() { + return fieldsAndWeights; } - public void boost(float boost) { - this.boost = boost; + public void fieldsAndWeights(Map fieldsAndWeights) { + this.fieldsAndWeights = fieldsAndWeights; } public QueryParser.Operator defaultOperator() { @@ -175,44 +183,40 @@ public class QueryParserSettings { this.fuzzyRewriteMethod = fuzzyRewriteMethod; } - public boolean escape() { - return escape; + public void defaultAnalyzer(Analyzer analyzer) { + this.analyzer = analyzer; + this.forceAnalyzer = false; } - public void escape(boolean escape) { - this.escape = escape; + public void forceAnalyzer(Analyzer analyzer) { + this.analyzer = analyzer; + this.forceAnalyzer = true; } - public Analyzer defaultAnalyzer() { - return defaultAnalyzer; + public Analyzer analyzer() { + return analyzer; } - public void defaultAnalyzer(Analyzer defaultAnalyzer) { - this.defaultAnalyzer = defaultAnalyzer; + public boolean forceAnalyzer() { + return forceAnalyzer; } - public Analyzer defaultQuoteAnalyzer() { - return defaultQuoteAnalyzer; + public void defaultQuoteAnalyzer(Analyzer quoteAnalyzer) { + this.quoteAnalyzer = quoteAnalyzer; + this.forceQuoteAnalyzer = false; } - public void defaultQuoteAnalyzer(Analyzer defaultAnalyzer) { - this.defaultQuoteAnalyzer = defaultAnalyzer; + public void forceQuoteAnalyzer(Analyzer quoteAnalyzer) { + this.quoteAnalyzer = quoteAnalyzer; + this.forceQuoteAnalyzer = true; } - public Analyzer forcedAnalyzer() { - return forcedAnalyzer; + public Analyzer quoteAnalyzer() { + return quoteAnalyzer; } - public void forcedAnalyzer(Analyzer forcedAnalyzer) { - this.forcedAnalyzer = forcedAnalyzer; - } - - public Analyzer forcedQuoteAnalyzer() { - return forcedQuoteAnalyzer; - } - - public void forcedQuoteAnalyzer(Analyzer forcedAnalyzer) { - this.forcedQuoteAnalyzer = forcedAnalyzer; + public boolean forceQuoteAnalyzer() { + return forceQuoteAnalyzer; } public boolean analyzeWildcard() { @@ -231,14 +235,6 @@ public class QueryParserSettings { this.rewriteMethod = rewriteMethod; } - public String minimumShouldMatch() { - return this.minimumShouldMatch; - } - - public void minimumShouldMatch(String minimumShouldMatch) { - this.minimumShouldMatch = minimumShouldMatch; - } - public void quoteFieldSuffix(String quoteFieldSuffix) { this.quoteFieldSuffix = quoteFieldSuffix; } @@ -255,22 +251,6 @@ public class QueryParserSettings { return this.lenient; } - public List fields() { - return fields; - } - - public void fields(List fields) { - this.fields = fields; - } - - public ObjectFloatHashMap boosts() { - return boosts; - } - - public void boosts(ObjectFloatHashMap boosts) { - this.boosts = boosts; - } - public float tieBreaker() { return tieBreaker; } @@ -303,97 +283,11 @@ public class QueryParserSettings { return this.timeZone; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - QueryParserSettings that = (QueryParserSettings) o; - - if (autoGeneratePhraseQueries != that.autoGeneratePhraseQueries()) return false; - if (maxDeterminizedStates != that.maxDeterminizedStates()) return false; - if (allowLeadingWildcard != that.allowLeadingWildcard) return false; - if (Float.compare(that.boost, boost) != 0) return false; - if (enablePositionIncrements != that.enablePositionIncrements) return false; - if (escape != that.escape) return false; - if (analyzeWildcard != that.analyzeWildcard) return false; - if (fuzziness != null ? fuzziness.equals(that.fuzziness) == false : fuzziness != null) return false; - if (fuzzyPrefixLength != that.fuzzyPrefixLength) return false; - if (fuzzyMaxExpansions != that.fuzzyMaxExpansions) return false; - if (fuzzyRewriteMethod != null ? !fuzzyRewriteMethod.equals(that.fuzzyRewriteMethod) : that.fuzzyRewriteMethod != null) - return false; - if (lowercaseExpandedTerms != that.lowercaseExpandedTerms) return false; - 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; - if (rewriteMethod != null ? !rewriteMethod.equals(that.rewriteMethod) : that.rewriteMethod != null) - 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; - if (lenient != that.lenient) { - return false; - } - if (locale != null ? !locale.equals(that.locale) : that.locale != null) { - return false; - } - if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) { - return false; - } - - if (Float.compare(that.tieBreaker, tieBreaker) != 0) return false; - if (useDisMax != that.useDisMax) return false; - if (boosts != null ? !boosts.equals(that.boosts) : that.boosts != null) return false; - if (fields != null ? !fields.equals(that.fields) : that.fields != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = queryString != null ? queryString.hashCode() : 0; - result = 31 * result + (defaultField != null ? defaultField.hashCode() : 0); - result = 31 * result + (boost != +0.0f ? Float.floatToIntBits(boost) : 0); - result = 31 * result + (defaultOperator != null ? defaultOperator.hashCode() : 0); - result = 31 * result + (autoGeneratePhraseQueries ? 1 : 0); - result = 31 * result + maxDeterminizedStates; - result = 31 * result + (allowLeadingWildcard ? 1 : 0); - result = 31 * result + (lowercaseExpandedTerms ? 1 : 0); - result = 31 * result + (enablePositionIncrements ? 1 : 0); - result = 31 * result + phraseSlop; - result = 31 * result + (fuzziness.hashCode()); - 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); - - result = 31 * result + (fields != null ? fields.hashCode() : 0); - result = 31 * result + (boosts != null ? boosts.hashCode() : 0); - result = 31 * result + (tieBreaker != +0.0f ? Float.floatToIntBits(tieBreaker) : 0); - result = 31 * result + (useDisMax ? 1 : 0); - result = 31 * result + (locale != null ? locale.hashCode() : 0); - result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0); - return result; - } - - public void setFuzziness(Fuzziness fuzziness) { + public void fuzziness(Fuzziness fuzziness) { this.fuzziness = fuzziness; } - public Fuzziness getFuzziness() { + public Fuzziness fuzziness() { return fuzziness; } } diff --git a/core/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java b/core/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java index e2c8f67e7e9..f16edb0bbd1 100644 --- a/core/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java +++ b/core/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java @@ -58,6 +58,8 @@ public class IndexQueryParserService extends AbstractIndexComponent { public static final String DEFAULT_FIELD = "index.query.default_field"; public static final String QUERY_STRING_LENIENT = "index.query_string.lenient"; + public static final String QUERY_STRING_ANALYZE_WILDCARD = "indices.query.query_string.analyze_wildcard"; + public static final String QUERY_STRING_ALLOW_LEADING_WILDCARD = "indices.query.query_string.allowLeadingWildcard"; public static final String PARSE_STRICT = "index.query.parse.strict"; public static final String ALLOW_UNMAPPED = "index.query.parse.allow_unmapped_fields"; private final InnerHitsQueryParserHelper innerHitsQueryParserHelper; @@ -89,15 +91,17 @@ public class IndexQueryParserService extends AbstractIndexComponent { private final IndicesQueriesRegistry indicesQueriesRegistry; - private String defaultField; - private boolean queryStringLenient; + private final String defaultField; + private final boolean queryStringLenient; + private final boolean queryStringAnalyzeWildcard; + private final boolean queryStringAllowLeadingWildcard; private final ParseFieldMatcher parseFieldMatcher; private final boolean defaultAllowUnmappedFields; private TermsLookupFetchService termsLookupFetchService; @Inject - public IndexQueryParserService(Index index, @IndexSettings Settings indexSettings, + public IndexQueryParserService(Index index, @IndexSettings Settings indexSettings, Settings settings, IndicesQueriesRegistry indicesQueriesRegistry, ScriptService scriptService, AnalysisService analysisService, MapperService mapperService, IndexCache indexCache, IndexFieldDataService fieldDataService, @@ -118,6 +122,8 @@ public class IndexQueryParserService extends AbstractIndexComponent { this.defaultField = indexSettings.get(DEFAULT_FIELD, AllFieldMapper.NAME); this.queryStringLenient = indexSettings.getAsBoolean(QUERY_STRING_LENIENT, false); + this.queryStringAnalyzeWildcard = settings.getAsBoolean(QUERY_STRING_ANALYZE_WILDCARD, false); + this.queryStringAllowLeadingWildcard = settings.getAsBoolean(QUERY_STRING_ALLOW_LEADING_WILDCARD, true); this.parseFieldMatcher = new ParseFieldMatcher(indexSettings); this.defaultAllowUnmappedFields = indexSettings.getAsBoolean(ALLOW_UNMAPPED, true); this.indicesQueriesRegistry = indicesQueriesRegistry; @@ -137,6 +143,14 @@ public class IndexQueryParserService extends AbstractIndexComponent { return this.defaultField; } + public boolean queryStringAnalyzeWildcard() { + return this.queryStringAnalyzeWildcard; + } + + public boolean queryStringAllowLeadingWildcard() { + return this.queryStringAllowLeadingWildcard; + } + public boolean queryStringLenient() { return this.queryStringLenient; } diff --git a/core/src/main/java/org/elasticsearch/index/query/Operator.java b/core/src/main/java/org/elasticsearch/index/query/Operator.java index 14707371a9e..d114946aee7 100644 --- a/core/src/main/java/org/elasticsearch/index/query/Operator.java +++ b/core/src/main/java/org/elasticsearch/index/query/Operator.java @@ -18,6 +18,7 @@ */ package org.elasticsearch.index.query; +import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.BooleanClause; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.io.stream.StreamInput; @@ -34,7 +35,7 @@ public enum Operator implements Writeable { private static final Operator PROTOTYPE = OR; - private Operator(int ordinal) { + Operator(int ordinal) { this.ordinal = ordinal; } @@ -49,6 +50,17 @@ public enum Operator implements Writeable { } } + public QueryParser.Operator toQueryParserOperator() { + switch (this) { + case OR: + return QueryParser.Operator.OR; + case AND: + return QueryParser.Operator.AND; + default: + throw Operator.newOperatorException(this.toString()); + } + } + @Override public Operator readFrom(StreamInput in) throws IOException { int ord = in.readVInt(); diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java b/core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java index 025f24b36d2..8a04ca2006e 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryShardContext.java @@ -162,6 +162,14 @@ public class QueryShardContext { return indexQueryParser.queryStringLenient(); } + public boolean queryStringAnalyzeWildcard() { + return indexQueryParser.queryStringAnalyzeWildcard(); + } + + public boolean queryStringAllowLeadingWildcard() { + return indexQueryParser.queryStringAllowLeadingWildcard(); + } + public MapperQueryParser queryParser(QueryParserSettings settings) { queryParser.reset(settings); return queryParser; diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java b/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java index a6efa2f3492..fb13083b46a 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryBuilder.java @@ -19,14 +19,27 @@ package org.elasticsearch.index.query; -import com.carrotsearch.hppc.ObjectFloatHashMap; +import org.apache.lucene.queryparser.classic.MapperQueryParser; +import org.apache.lucene.queryparser.classic.QueryParserSettings; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.FuzzyQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.util.automaton.Operations; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.lucene.search.Queries; +import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.index.analysis.NamedAnalyzer; +import org.elasticsearch.index.query.support.QueryParsers; +import org.joda.time.DateTimeZone; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; /** * A query that parses a query string and runs it. There are two modes that this operates. The first, @@ -40,63 +53,89 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder fieldsAndWeights = new TreeMap<>(); - private Operator defaultOperator; + private Operator defaultOperator = DEFAULT_OPERATOR; private String analyzer; private String quoteAnalyzer; private String quoteFieldSuffix; - private Boolean autoGeneratePhraseQueries; + private boolean autoGeneratePhraseQueries = DEFAULT_AUTO_GENERATE_PHRASE_QUERIES; private Boolean allowLeadingWildcard; - private Boolean lowercaseExpandedTerms; - - private Boolean enablePositionIncrements; - private Boolean analyzeWildcard; - private Locale locale; + private boolean lowercaseExpandedTerms = DEFAULT_LOWERCASE_EXPANDED_TERMS; + + private boolean enablePositionIncrements = DEFAULT_ENABLE_POSITION_INCREMENTS; + + private Locale locale = DEFAULT_LOCALE; + + private Fuzziness fuzziness = DEFAULT_FUZZINESS; + + private int fuzzyPrefixLength = DEFAULT_FUZZY_PREFIX_LENGTH; + + private int fuzzyMaxExpansions = DEFAULT_FUZZY_MAX_EXPANSIONS; + + private String rewrite; - private Fuzziness fuzziness; - private int fuzzyPrefixLength = -1; - private int fuzzyMaxExpansions = -1; private String fuzzyRewrite; - private int phraseSlop = -1; + private boolean escape = DEFAULT_ESCAPE; - private List fields; + private int phraseSlop = DEFAULT_PHRASE_SLOP; - private ObjectFloatHashMap fieldsBoosts; + private boolean useDisMax = DEFAULT_USE_DIS_MAX; - private Boolean useDisMax; - - private float tieBreaker = -1; - - private String rewrite = null; + private float tieBreaker = DEFAULT_TIE_BREAKER; private String minimumShouldMatch; private Boolean lenient; - private String timeZone; - - private Boolean escape; + private DateTimeZone timeZone; /** To limit effort spent determinizing regexp queries. */ - private Integer maxDeterminizedStates; - - static final QueryStringQueryBuilder PROTOTYPE = new QueryStringQueryBuilder(null); - + private int maxDeterminizedStates = DEFAULT_MAX_DETERMINED_STATES; + public QueryStringQueryBuilder(String queryString) { this.queryString = queryString; } + public String queryString() { + return this.queryString; + } + /** * The default field to run against when no prefix field is specified. Only relevant when * not explicitly adding fields the query string will run against. @@ -106,14 +145,16 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder(); - } - fields.add(field); + this.fieldsAndWeights.put(field, AbstractQueryBuilder.DEFAULT_BOOST); return this; } @@ -121,17 +162,23 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder(); - } - fields.add(field); - if (fieldsBoosts == null) { - fieldsBoosts = new ObjectFloatHashMap<>(); - } - fieldsBoosts.put(field, boost); + this.fieldsAndWeights.put(field, boost); return this; } + /** + * Add several fields to run the query against with a specific boost. + */ + public QueryStringQueryBuilder fields(Map fields) { + this.fieldsAndWeights.putAll(fields); + return this; + } + + /** Returns the fields including their respective boosts to run the query against. */ + public Map fields() { + return this.fieldsAndWeights; + } + /** * When more than one field is used with the query string, should queries be combined using * dis max, or boolean query. Defaults to dis max (true). @@ -141,6 +188,10 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder @@ -161,10 +216,14 @@ public class QueryStringQueryBuilder extends AbstractQueryBuildercapital AND of AND Hungary */ public QueryStringQueryBuilder defaultOperator(Operator defaultOperator) { - this.defaultOperator = defaultOperator; + this.defaultOperator = defaultOperator == null ? DEFAULT_OPERATOR : defaultOperator; return this; } + public Operator defaultOperator() { + return this.defaultOperator; + } + /** * The optional analyzer used to analyze the query string. Note, if a field has search analyzer * defined for it, then it will be used automatically. Defaults to the smart search analyzer. @@ -178,12 +237,11 @@ public class QueryStringQueryBuilder extends AbstractQueryBuildertrue. */ - public QueryStringQueryBuilder allowLeadingWildcard(boolean allowLeadingWildcard) { + public QueryStringQueryBuilder allowLeadingWildcard(Boolean allowLeadingWildcard) { this.allowLeadingWildcard = allowLeadingWildcard; return this; } + public Boolean allowLeadingWildcard() { + return this.allowLeadingWildcard; + } + /** * Whether terms of wildcard, prefix, fuzzy and range queries are to be automatically * lower-cased or not. Default is true. @@ -223,6 +293,10 @@ public class QueryStringQueryBuilder extends AbstractQueryBuildertrue to enable position increments in result query. Defaults to * true. @@ -235,14 +309,22 @@ public class QueryStringQueryBuilder extends AbstractQueryBuildertrue to enable analysis on wildcard and prefix queries. */ - public QueryStringQueryBuilder analyzeWildcard(boolean analyzeWildcard) { + public QueryStringQueryBuilder analyzeWildcard(Boolean analyzeWildcard) { this.analyzeWildcard = analyzeWildcard; return this; } + public Boolean analyzeWildcard() { + return this.analyzeWildcard; + } + public QueryStringQueryBuilder rewrite(String rewrite) { this.rewrite = rewrite; return this; } + public String rewrite() { + return this.rewrite; + } + public QueryStringQueryBuilder minimumShouldMatch(String minimumShouldMatch) { this.minimumShouldMatch = minimumShouldMatch; return this; } + public String minimumShouldMatch() { + return this.minimumShouldMatch; + } + /** * An optional field name suffix to automatically try and add to the field searched when using quoted text. */ @@ -296,6 +406,10 @@ public class QueryStringQueryBuilder extends AbstractQueryBuildertrue to enable escaping of the query string */ @@ -326,92 +461,65 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder fieldEntry : this.fieldsAndWeights.entrySet()) { + builder.value(fieldEntry.getKey() + "^" + fieldEntry.getValue()); } - if (useDisMax != null) { - builder.field("use_dis_max", useDisMax); + builder.endArray(); + builder.field("use_dis_max", this.useDisMax); + builder.field("tie_breaker", this.tieBreaker); + builder.field("default_operator", this.defaultOperator.name().toLowerCase(Locale.ROOT)); + if (this.analyzer != null) { + builder.field("analyzer", this.analyzer); } - if (tieBreaker != -1) { - builder.field("tie_breaker", tieBreaker); + if (this.quoteAnalyzer != null) { + builder.field("quote_analyzer", this.quoteAnalyzer); } - if (defaultOperator != null) { - builder.field("default_operator", defaultOperator.name().toLowerCase(Locale.ROOT)); + builder.field("auto_generate_phrase_queries", this.autoGeneratePhraseQueries); + builder.field("max_determinized_states", this.maxDeterminizedStates); + if (this.allowLeadingWildcard != null) { + builder.field("allow_leading_wildcard", this.allowLeadingWildcard); } - if (analyzer != null) { - builder.field("analyzer", analyzer); + builder.field("lowercase_expanded_terms", this.lowercaseExpandedTerms); + builder.field("enable_position_increments", this.enablePositionIncrements); + this.fuzziness.toXContent(builder, params); + builder.field("fuzzy_prefix_length", this.fuzzyPrefixLength); + builder.field("fuzzy_max_expansions", this.fuzzyMaxExpansions); + if (this.fuzzyRewrite != null) { + builder.field("fuzzy_rewrite", this.fuzzyRewrite); } - if (quoteAnalyzer != null) { - builder.field("quote_analyzer", quoteAnalyzer); + builder.field("phrase_slop", this.phraseSlop); + if (this.analyzeWildcard != null) { + builder.field("analyze_wildcard", this.analyzeWildcard); } - if (autoGeneratePhraseQueries != null) { - builder.field("auto_generate_phrase_queries", autoGeneratePhraseQueries); + if (this.rewrite != null) { + builder.field("rewrite", this.rewrite); } - if (maxDeterminizedStates != null) { - builder.field("max_determinized_states", maxDeterminizedStates); + if (this.minimumShouldMatch != null) { + builder.field("minimum_should_match", this.minimumShouldMatch); } - if (allowLeadingWildcard != null) { - builder.field("allow_leading_wildcard", allowLeadingWildcard); + if (this.quoteFieldSuffix != null) { + builder.field("quote_field_suffix", this.quoteFieldSuffix); } - if (lowercaseExpandedTerms != null) { - builder.field("lowercase_expanded_terms", lowercaseExpandedTerms); + if (this.lenient != null) { + builder.field("lenient", this.lenient); } - if (enablePositionIncrements != null) { - builder.field("enable_position_increments", enablePositionIncrements); - } - if (fuzziness != null) { - fuzziness.toXContent(builder, params); - } - if (fuzzyPrefixLength != -1) { - builder.field("fuzzy_prefix_length", fuzzyPrefixLength); - } - if (fuzzyMaxExpansions != -1) { - builder.field("fuzzy_max_expansions", fuzzyMaxExpansions); - } - if (fuzzyRewrite != null) { - builder.field("fuzzy_rewrite", fuzzyRewrite); - } - if (phraseSlop != -1) { - builder.field("phrase_slop", phraseSlop); - } - if (analyzeWildcard != null) { - builder.field("analyze_wildcard", analyzeWildcard); - } - if (rewrite != null) { - builder.field("rewrite", rewrite); - } - if (minimumShouldMatch != null) { - builder.field("minimum_should_match", minimumShouldMatch); - } - if (quoteFieldSuffix != null) { - builder.field("quote_field_suffix", quoteFieldSuffix); - } - if (lenient != null) { - builder.field("lenient", lenient); - } - if (locale != null) { - builder.field("locale", locale.toString()); - } - if (timeZone != null) { - builder.field("time_zone", timeZone); - } - if (escape != null) { - builder.field("escape", escape); + builder.field("locale", this.locale.toLanguageTag()); + if (this.timeZone != null) { + builder.field("time_zone", this.timeZone.getID()); } + builder.field("escape", this.escape); printBoostAndQueryName(builder); builder.endObject(); } @@ -420,4 +528,217 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder fieldsEntry : this.fieldsAndWeights.entrySet()) { + out.writeString(fieldsEntry.getKey()); + out.writeFloat(fieldsEntry.getValue()); + } + this.defaultOperator.writeTo(out); + out.writeOptionalString(this.analyzer); + out.writeOptionalString(this.quoteAnalyzer); + out.writeOptionalString(this.quoteFieldSuffix); + out.writeBoolean(this.autoGeneratePhraseQueries); + out.writeOptionalBoolean(this.allowLeadingWildcard); + out.writeOptionalBoolean(this.analyzeWildcard); + out.writeBoolean(this.lowercaseExpandedTerms); + out.writeBoolean(this.enablePositionIncrements); + out.writeString(this.locale.toLanguageTag()); + this.fuzziness.writeTo(out); + out.writeVInt(this.fuzzyPrefixLength); + out.writeVInt(this.fuzzyMaxExpansions); + out.writeOptionalString(this.fuzzyRewrite); + out.writeVInt(this.phraseSlop); + out.writeBoolean(this.useDisMax); + out.writeFloat(this.tieBreaker); + out.writeOptionalString(this.rewrite); + out.writeOptionalString(this.minimumShouldMatch); + out.writeOptionalBoolean(this.lenient); + if (this.timeZone == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + out.writeString(this.timeZone.getID()); + } + out.writeBoolean(this.escape); + out.writeVInt(this.maxDeterminizedStates); + } + + @Override + protected boolean doEquals(QueryStringQueryBuilder other) { + return Objects.equals(queryString, other.queryString) && + Objects.equals(defaultField, other.defaultField) && + Objects.equals(fieldsAndWeights, other.fieldsAndWeights) && + Objects.equals(defaultOperator, other.defaultOperator) && + Objects.equals(analyzer, other.analyzer) && + Objects.equals(quoteAnalyzer, other.quoteAnalyzer) && + Objects.equals(quoteFieldSuffix, other.quoteFieldSuffix) && + Objects.equals(autoGeneratePhraseQueries, other.autoGeneratePhraseQueries) && + Objects.equals(allowLeadingWildcard, other.allowLeadingWildcard) && + Objects.equals(lowercaseExpandedTerms, other.lowercaseExpandedTerms) && + Objects.equals(enablePositionIncrements, other.enablePositionIncrements) && + Objects.equals(analyzeWildcard, other.analyzeWildcard) && + Objects.equals(locale.toLanguageTag(), other.locale.toLanguageTag()) && + Objects.equals(fuzziness, other.fuzziness) && + Objects.equals(fuzzyPrefixLength, other.fuzzyPrefixLength) && + Objects.equals(fuzzyMaxExpansions, other.fuzzyMaxExpansions) && + Objects.equals(fuzzyRewrite, other.fuzzyRewrite) && + Objects.equals(phraseSlop, other.phraseSlop) && + Objects.equals(useDisMax, other.useDisMax) && + Objects.equals(tieBreaker, other.tieBreaker) && + Objects.equals(rewrite, other.rewrite) && + Objects.equals(minimumShouldMatch, other.minimumShouldMatch) && + Objects.equals(lenient, other.lenient) && + timeZone == null ? other.timeZone == null : other.timeZone != null && Objects.equals(timeZone.getID(), other.timeZone.getID()) && + Objects.equals(escape, other.escape) && + Objects.equals(maxDeterminizedStates, other.maxDeterminizedStates); + } + + @Override + protected int doHashCode() { + return Objects.hash(queryString, defaultField, fieldsAndWeights, defaultOperator, analyzer, quoteAnalyzer, + quoteFieldSuffix, autoGeneratePhraseQueries, allowLeadingWildcard, lowercaseExpandedTerms, + enablePositionIncrements, analyzeWildcard, locale.toLanguageTag(), fuzziness, fuzzyPrefixLength, + fuzzyMaxExpansions, fuzzyRewrite, phraseSlop, useDisMax, tieBreaker, rewrite, minimumShouldMatch, lenient, + timeZone == null ? 0 : timeZone.getID(), escape, maxDeterminizedStates); + } + + @Override + protected Query doToQuery(QueryShardContext context) throws IOException { + //TODO would be nice to have all the settings in one place: some change though at query execution time + //e.g. field names get expanded to concrete names, defaults get resolved sometimes to settings values etc. + QueryParserSettings qpSettings; + if (this.escape) { + qpSettings = new QueryParserSettings(org.apache.lucene.queryparser.classic.QueryParser.escape(this.queryString)); + } else { + qpSettings = new QueryParserSettings(this.queryString); + } + qpSettings.defaultField(this.defaultField == null ? context.defaultField() : this.defaultField); + Map resolvedFields = new TreeMap<>(); + for (Map.Entry fieldsEntry : fieldsAndWeights.entrySet()) { + String fieldName = fieldsEntry.getKey(); + Float weight = fieldsEntry.getValue(); + if (Regex.isSimpleMatchPattern(fieldName)) { + for (String resolvedFieldName : context.mapperService().simpleMatchToIndexNames(fieldName)) { + resolvedFields.put(resolvedFieldName, weight); + } + } else { + resolvedFields.put(fieldName, weight); + } + } + qpSettings.fieldsAndWeights(resolvedFields); + qpSettings.defaultOperator(defaultOperator.toQueryParserOperator()); + + if (analyzer == null) { + qpSettings.defaultAnalyzer(context.mapperService().searchAnalyzer()); + } else { + NamedAnalyzer namedAnalyzer = context.analysisService().analyzer(analyzer); + if (namedAnalyzer == null) { + throw new QueryShardException(context, "[query_string] analyzer [" + analyzer + "] not found"); + } + qpSettings.forceAnalyzer(namedAnalyzer); + } + if (quoteAnalyzer != null) { + NamedAnalyzer namedAnalyzer = context.analysisService().analyzer(quoteAnalyzer); + if (namedAnalyzer == null) { + throw new QueryShardException(context, "[query_string] quote_analyzer [" + quoteAnalyzer + "] not found"); + } + qpSettings.forceQuoteAnalyzer(namedAnalyzer); + } else if (analyzer != null) { + qpSettings.forceQuoteAnalyzer(qpSettings.analyzer()); + } else { + qpSettings.defaultQuoteAnalyzer(context.mapperService().searchQuoteAnalyzer()); + } + + qpSettings.quoteFieldSuffix(quoteFieldSuffix); + qpSettings.autoGeneratePhraseQueries(autoGeneratePhraseQueries); + qpSettings.allowLeadingWildcard(allowLeadingWildcard == null ? context.queryStringAllowLeadingWildcard() : allowLeadingWildcard); + qpSettings.analyzeWildcard(analyzeWildcard == null ? context.queryStringAnalyzeWildcard() : analyzeWildcard); + qpSettings.lowercaseExpandedTerms(lowercaseExpandedTerms); + qpSettings.enablePositionIncrements(enablePositionIncrements); + qpSettings.locale(locale); + qpSettings.fuzziness(fuzziness); + qpSettings.fuzzyPrefixLength(fuzzyPrefixLength); + qpSettings.fuzzyMaxExpansions(fuzzyMaxExpansions); + qpSettings.fuzzyRewriteMethod(QueryParsers.parseRewriteMethod(context.parseFieldMatcher(), this.fuzzyRewrite)); + qpSettings.phraseSlop(phraseSlop); + qpSettings.useDisMax(useDisMax); + qpSettings.tieBreaker(tieBreaker); + qpSettings.rewriteMethod(QueryParsers.parseRewriteMethod(context.parseFieldMatcher(), this.rewrite)); + qpSettings.lenient(lenient == null ? context.queryStringLenient() : lenient); + qpSettings.timeZone(timeZone); + qpSettings.maxDeterminizedStates(maxDeterminizedStates); + + MapperQueryParser queryParser = context.queryParser(qpSettings); + Query query; + try { + query = queryParser.parse(queryString); + } catch (org.apache.lucene.queryparser.classic.ParseException e) { + throw new QueryShardException(context, "Failed to parse query [" + this.queryString + "]", e); + } + + if (query == null) { + return null; + } + query = Queries.fixNegativeQueryIfNeeded(query); + if (query instanceof BooleanQuery) { + query = Queries.applyMinimumShouldMatch((BooleanQuery) query, this.minimumShouldMatch()); + } + return query; + } + + @Override + protected void setFinalBoost(Query query) { + //we need to preserve the boost that came out of the parsing phase + query.setBoost(query.getBoost() * boost); + } } diff --git a/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java b/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java index dcca13309cd..fdf1a18029e 100644 --- a/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/QueryStringQueryParser.java @@ -19,44 +19,26 @@ package org.elasticsearch.index.query; -import com.carrotsearch.hppc.ObjectFloatHashMap; -import org.apache.lucene.queryparser.classic.MapperQueryParser; -import org.apache.lucene.queryparser.classic.QueryParserSettings; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.Query; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.lucene.search.Queries; -import org.elasticsearch.common.regex.Regex; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.Fuzziness; -import org.elasticsearch.common.util.LocaleUtils; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.index.analysis.NamedAnalyzer; -import org.elasticsearch.index.query.support.QueryParsers; -import org.joda.time.DateTimeZone; import java.io.IOException; -import java.util.ArrayList; +import java.util.HashMap; import java.util.Locale; - -import static org.elasticsearch.common.lucene.search.Queries.fixNegativeQueryIfNeeded; +import java.util.Map; /** - * + * Parser for query_string query */ -public class QueryStringQueryParser extends BaseQueryParserTemp { +public class QueryStringQueryParser extends BaseQueryParser { private static final ParseField FUZZINESS = Fuzziness.FIELD.withDeprecation("fuzzy_min_sim"); - private final boolean defaultAnalyzeWildcard; - private final boolean defaultAllowLeadingWildcard; - @Inject - public QueryStringQueryParser(Settings settings) { - this.defaultAnalyzeWildcard = settings.getAsBoolean("indices.query.query_string.analyze_wildcard", QueryParserSettings.DEFAULT_ANALYZE_WILDCARD); - this.defaultAllowLeadingWildcard = settings.getAsBoolean("indices.query.query_string.allowLeadingWildcard", QueryParserSettings.DEFAULT_ALLOW_LEADING_WILDCARD); + public QueryStringQueryParser() { } @Override @@ -65,20 +47,38 @@ public class QueryStringQueryParser extends BaseQueryParserTemp { } @Override - public Query parse(QueryShardContext context) throws IOException, QueryParsingException { - QueryParseContext parseContext = context.parseContext(); + public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, QueryParsingException { XContentParser parser = parseContext.parser(); - - String queryName = null; - QueryParserSettings qpSettings = new QueryParserSettings(); - qpSettings.defaultField(context.defaultField()); - qpSettings.lenient(context.queryStringLenient()); - qpSettings.analyzeWildcard(defaultAnalyzeWildcard); - qpSettings.allowLeadingWildcard(defaultAllowLeadingWildcard); - qpSettings.locale(Locale.ROOT); - String currentFieldName = null; XContentParser.Token token; + String queryString = null; + String defaultField = null; + String analyzer = null; + String quoteAnalyzer = null; + String queryName = null; + float boost = AbstractQueryBuilder.DEFAULT_BOOST; + boolean autoGeneratePhraseQueries = QueryStringQueryBuilder.DEFAULT_AUTO_GENERATE_PHRASE_QUERIES; + int maxDeterminizedStates = QueryStringQueryBuilder.DEFAULT_MAX_DETERMINED_STATES; + boolean lowercaseExpandedTerms = QueryStringQueryBuilder.DEFAULT_LOWERCASE_EXPANDED_TERMS; + boolean enablePositionIncrements = QueryStringQueryBuilder.DEFAULT_ENABLE_POSITION_INCREMENTS; + boolean escape = QueryStringQueryBuilder.DEFAULT_ESCAPE; + boolean useDisMax = QueryStringQueryBuilder.DEFAULT_USE_DIS_MAX; + int fuzzyPrefixLength = QueryStringQueryBuilder.DEFAULT_FUZZY_PREFIX_LENGTH; + int fuzzyMaxExpansions = QueryStringQueryBuilder.DEFAULT_FUZZY_MAX_EXPANSIONS; + int phraseSlop = QueryStringQueryBuilder.DEFAULT_PHRASE_SLOP; + float tieBreaker = QueryStringQueryBuilder.DEFAULT_TIE_BREAKER; + Boolean analyzeWildcard = null; + Boolean allowLeadingWildcard = null; + String minimumShouldMatch = null; + String quoteFieldSuffix = null; + Boolean lenient = null; + Operator defaultOperator = QueryStringQueryBuilder.DEFAULT_OPERATOR; + String timeZone = null; + Locale locale = QueryStringQueryBuilder.DEFAULT_LOCALE; + Fuzziness fuzziness = QueryStringQueryBuilder.DEFAULT_FUZZINESS; + String fuzzyRewrite = null; + String rewrite = null; + Map fieldsAndWeights = new HashMap<>(); while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); @@ -86,7 +86,7 @@ public class QueryStringQueryParser extends BaseQueryParserTemp { if ("fields".equals(currentFieldName)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { String fField = null; - float fBoost = -1; + float fBoost = AbstractQueryBuilder.DEFAULT_BOOST; char[] text = parser.textCharacters(); int end = parser.textOffset() + parser.textLength(); for (int i = parser.textOffset(); i < end; i++) { @@ -100,148 +100,109 @@ public class QueryStringQueryParser extends BaseQueryParserTemp { if (fField == null) { fField = parser.text(); } - if (qpSettings.fields() == null) { - qpSettings.fields(new ArrayList()); - } - - if (Regex.isSimpleMatchPattern(fField)) { - for (String field : context.mapperService().simpleMatchToIndexNames(fField)) { - qpSettings.fields().add(field); - if (fBoost != -1) { - if (qpSettings.boosts() == null) { - qpSettings.boosts(new ObjectFloatHashMap()); - } - qpSettings.boosts().put(field, fBoost); - } - } - } else { - qpSettings.fields().add(fField); - if (fBoost != -1) { - if (qpSettings.boosts() == null) { - qpSettings.boosts(new ObjectFloatHashMap()); - } - qpSettings.boosts().put(fField, fBoost); - } - } + fieldsAndWeights.put(fField, fBoost); } } else { - throw new QueryParsingException(parseContext, "[query_string] query does not support [" + currentFieldName - + "]"); + throw new QueryParsingException(parseContext, "[query_string] query does not support [" + currentFieldName + "]"); } } else if (token.isValue()) { if ("query".equals(currentFieldName)) { - qpSettings.queryString(parser.text()); + queryString = parser.text(); } else if ("default_field".equals(currentFieldName) || "defaultField".equals(currentFieldName)) { - qpSettings.defaultField(parser.text()); + defaultField = parser.text(); } else if ("default_operator".equals(currentFieldName) || "defaultOperator".equals(currentFieldName)) { - String op = parser.text(); - if ("or".equalsIgnoreCase(op)) { - qpSettings.defaultOperator(org.apache.lucene.queryparser.classic.QueryParser.Operator.OR); - } else if ("and".equalsIgnoreCase(op)) { - qpSettings.defaultOperator(org.apache.lucene.queryparser.classic.QueryParser.Operator.AND); - } else { - throw new QueryParsingException(parseContext, "Query default operator [" + op + "] is not allowed"); - } + defaultOperator = Operator.fromString(parser.text()); } else if ("analyzer".equals(currentFieldName)) { - NamedAnalyzer analyzer = context.analysisService().analyzer(parser.text()); - if (analyzer == null) { - throw new QueryParsingException(parseContext, "[query_string] analyzer [" + parser.text() + "] not found"); - } - qpSettings.forcedAnalyzer(analyzer); + analyzer = parser.text(); } else if ("quote_analyzer".equals(currentFieldName) || "quoteAnalyzer".equals(currentFieldName)) { - NamedAnalyzer analyzer = context.analysisService().analyzer(parser.text()); - if (analyzer == null) { - throw new QueryParsingException(parseContext, "[query_string] quote_analyzer [" + parser.text() - + "] not found"); - } - qpSettings.forcedQuoteAnalyzer(analyzer); + quoteAnalyzer = parser.text(); } else if ("allow_leading_wildcard".equals(currentFieldName) || "allowLeadingWildcard".equals(currentFieldName)) { - qpSettings.allowLeadingWildcard(parser.booleanValue()); + allowLeadingWildcard = parser.booleanValue(); } else if ("auto_generate_phrase_queries".equals(currentFieldName) || "autoGeneratePhraseQueries".equals(currentFieldName)) { - qpSettings.autoGeneratePhraseQueries(parser.booleanValue()); + autoGeneratePhraseQueries = parser.booleanValue(); } else if ("max_determinized_states".equals(currentFieldName) || "maxDeterminizedStates".equals(currentFieldName)) { - qpSettings.maxDeterminizedStates(parser.intValue()); + maxDeterminizedStates = parser.intValue(); } else if ("lowercase_expanded_terms".equals(currentFieldName) || "lowercaseExpandedTerms".equals(currentFieldName)) { - qpSettings.lowercaseExpandedTerms(parser.booleanValue()); + lowercaseExpandedTerms = parser.booleanValue(); } else if ("enable_position_increments".equals(currentFieldName) || "enablePositionIncrements".equals(currentFieldName)) { - qpSettings.enablePositionIncrements(parser.booleanValue()); + enablePositionIncrements = parser.booleanValue(); } else if ("escape".equals(currentFieldName)) { - qpSettings.escape(parser.booleanValue()); + escape = parser.booleanValue(); } else if ("use_dis_max".equals(currentFieldName) || "useDisMax".equals(currentFieldName)) { - qpSettings.useDisMax(parser.booleanValue()); + useDisMax = parser.booleanValue(); } else if ("fuzzy_prefix_length".equals(currentFieldName) || "fuzzyPrefixLength".equals(currentFieldName)) { - qpSettings.fuzzyPrefixLength(parser.intValue()); + fuzzyPrefixLength = parser.intValue(); } else if ("fuzzy_max_expansions".equals(currentFieldName) || "fuzzyMaxExpansions".equals(currentFieldName)) { - qpSettings.fuzzyMaxExpansions(parser.intValue()); + fuzzyMaxExpansions = parser.intValue(); } else if ("fuzzy_rewrite".equals(currentFieldName) || "fuzzyRewrite".equals(currentFieldName)) { - qpSettings.fuzzyRewriteMethod(QueryParsers.parseRewriteMethod(parseContext.parseFieldMatcher(), parser.textOrNull())); + fuzzyRewrite = parser.textOrNull(); } else if ("phrase_slop".equals(currentFieldName) || "phraseSlop".equals(currentFieldName)) { - qpSettings.phraseSlop(parser.intValue()); + phraseSlop = parser.intValue(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZINESS)) { - qpSettings.setFuzziness(Fuzziness.parse(parser)); + fuzziness = Fuzziness.parse(parser); } else if ("boost".equals(currentFieldName)) { - qpSettings.boost(parser.floatValue()); + boost = parser.floatValue(); } else if ("tie_breaker".equals(currentFieldName) || "tieBreaker".equals(currentFieldName)) { - qpSettings.tieBreaker(parser.floatValue()); + tieBreaker = parser.floatValue(); } else if ("analyze_wildcard".equals(currentFieldName) || "analyzeWildcard".equals(currentFieldName)) { - qpSettings.analyzeWildcard(parser.booleanValue()); + analyzeWildcard = parser.booleanValue(); } else if ("rewrite".equals(currentFieldName)) { - qpSettings.rewriteMethod(QueryParsers.parseRewriteMethod(parseContext.parseFieldMatcher(), parser.textOrNull())); + rewrite = parser.textOrNull(); } else if ("minimum_should_match".equals(currentFieldName) || "minimumShouldMatch".equals(currentFieldName)) { - qpSettings.minimumShouldMatch(parser.textOrNull()); + minimumShouldMatch = parser.textOrNull(); } else if ("quote_field_suffix".equals(currentFieldName) || "quoteFieldSuffix".equals(currentFieldName)) { - qpSettings.quoteFieldSuffix(parser.textOrNull()); + quoteFieldSuffix = parser.textOrNull(); } else if ("lenient".equalsIgnoreCase(currentFieldName)) { - qpSettings.lenient(parser.booleanValue()); + lenient = parser.booleanValue(); } else if ("locale".equals(currentFieldName)) { String localeStr = parser.text(); - qpSettings.locale(LocaleUtils.parse(localeStr)); - } else if ("time_zone".equals(currentFieldName)) { + locale = Locale.forLanguageTag(localeStr); + } else if ("time_zone".equals(currentFieldName) || "timeZone".equals(currentFieldName)) { try { - qpSettings.timeZone(DateTimeZone.forID(parser.text())); + timeZone = parser.text(); } catch (IllegalArgumentException e) { - throw new QueryParsingException(parseContext, - "[query_string] time_zone [" + parser.text() + "] is unknown"); + throw new QueryParsingException(parseContext, "[query_string] time_zone [" + parser.text() + "] is unknown"); } } else if ("_name".equals(currentFieldName)) { queryName = parser.text(); } else { - throw new QueryParsingException(parseContext, "[query_string] query does not support [" + currentFieldName - + "]"); + throw new QueryParsingException(parseContext, "[query_string] query does not support [" + currentFieldName + "]"); } } } - if (qpSettings.queryString() == null) { + if (queryString == null) { throw new QueryParsingException(parseContext, "query_string must be provided with a [query]"); } - qpSettings.defaultAnalyzer(context.mapperService().searchAnalyzer()); - qpSettings.defaultQuoteAnalyzer(context.mapperService().searchQuoteAnalyzer()); - if (qpSettings.escape()) { - qpSettings.queryString(org.apache.lucene.queryparser.classic.QueryParser.escape(qpSettings.queryString())); - } - - MapperQueryParser queryParser = context.queryParser(qpSettings); - - try { - Query query = queryParser.parse(qpSettings.queryString()); - if (query == null) { - return null; - } - if (qpSettings.boost() != QueryParserSettings.DEFAULT_BOOST) { - query.setBoost(query.getBoost() * qpSettings.boost()); - } - query = fixNegativeQueryIfNeeded(query); - if (query instanceof BooleanQuery) { - query = Queries.applyMinimumShouldMatch((BooleanQuery) query, qpSettings.minimumShouldMatch()); - } - if (queryName != null) { - context.addNamedQuery(queryName, query); - } - return query; - } catch (org.apache.lucene.queryparser.classic.ParseException e) { - throw new QueryParsingException(parseContext, "Failed to parse query [" + qpSettings.queryString() + "]", e); - } + QueryStringQueryBuilder queryStringQuery = new QueryStringQueryBuilder(queryString); + queryStringQuery.fields(fieldsAndWeights); + queryStringQuery.defaultField(defaultField); + queryStringQuery.defaultOperator(defaultOperator); + queryStringQuery.analyzer(analyzer); + queryStringQuery.quoteAnalyzer(quoteAnalyzer); + queryStringQuery.allowLeadingWildcard(allowLeadingWildcard); + queryStringQuery.autoGeneratePhraseQueries(autoGeneratePhraseQueries); + queryStringQuery.maxDeterminizedStates(maxDeterminizedStates); + queryStringQuery.lowercaseExpandedTerms(lowercaseExpandedTerms); + queryStringQuery.enablePositionIncrements(enablePositionIncrements); + queryStringQuery.escape(escape); + queryStringQuery.useDisMax(useDisMax); + queryStringQuery.fuzzyPrefixLength(fuzzyPrefixLength); + queryStringQuery.fuzzyMaxExpansions(fuzzyMaxExpansions); + queryStringQuery.fuzzyRewrite(fuzzyRewrite); + queryStringQuery.phraseSlop(phraseSlop); + queryStringQuery.fuzziness(fuzziness); + queryStringQuery.tieBreaker(tieBreaker); + queryStringQuery.analyzeWildcard(analyzeWildcard); + queryStringQuery.rewrite(rewrite); + queryStringQuery.minimumShouldMatch(minimumShouldMatch); + queryStringQuery.quoteFieldSuffix(quoteFieldSuffix); + queryStringQuery.lenient(lenient); + queryStringQuery.timeZone(timeZone); + queryStringQuery.locale(locale); + queryStringQuery.boost(boost); + queryStringQuery.queryName(queryName); + return queryStringQuery; } @Override diff --git a/core/src/test/java/org/elasticsearch/index/query/BaseQueryTestCase.java b/core/src/test/java/org/elasticsearch/index/query/BaseQueryTestCase.java index dc4ab22662e..87bd6f32e17 100644 --- a/core/src/test/java/org/elasticsearch/index/query/BaseQueryTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/query/BaseQueryTestCase.java @@ -79,7 +79,9 @@ import org.junit.BeforeClass; import org.junit.Test; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Map; import static org.hamcrest.Matchers.equalTo; @@ -91,6 +93,7 @@ public abstract class BaseQueryTestCase> ext private static final GeohashGenerator geohashGenerator = new GeohashGenerator(); protected static final String STRING_FIELD_NAME = "mapped_string"; + protected static final String STRING_FIELD_NAME_2 = "mapped_string_2"; protected static final String INT_FIELD_NAME = "mapped_int"; protected static final String DOUBLE_FIELD_NAME = "mapped_double"; protected static final String BOOLEAN_FIELD_NAME = "mapped_boolean"; @@ -176,6 +179,7 @@ public abstract class BaseQueryTestCase> ext String type = randomAsciiOfLengthBetween(1, 10); mapperService.merge(type, new CompressedXContent(PutMappingRequest.buildFromSimplifiedDef(type, STRING_FIELD_NAME, "type=string", + STRING_FIELD_NAME_2, "type=string", INT_FIELD_NAME, "type=integer", DOUBLE_FIELD_NAME, "type=double", BOOLEAN_FIELD_NAME, "type=boolean", @@ -546,25 +550,39 @@ public abstract class BaseQueryTestCase> ext } protected static Fuzziness randomFuzziness(String fieldName) { - Fuzziness fuzziness = Fuzziness.AUTO; - switch (fieldName) { - case INT_FIELD_NAME: - fuzziness = Fuzziness.build(randomIntBetween(3, 100)); - break; - case DOUBLE_FIELD_NAME: - fuzziness = Fuzziness.build(1 + randomFloat() * 10); - break; - case DATE_FIELD_NAME: - fuzziness = Fuzziness.build(randomTimeValue()); - break; + if (randomBoolean()) { + return Fuzziness.fromEdits(randomIntBetween(0, 2)); } if (randomBoolean()) { - fuzziness = Fuzziness.fromEdits(randomIntBetween(0, 2)); + return Fuzziness.AUTO; + } + switch (fieldName) { + case INT_FIELD_NAME: + return Fuzziness.build(randomIntBetween(3, 100)); + case DOUBLE_FIELD_NAME: + return Fuzziness.build(1 + randomFloat() * 10); + case DATE_FIELD_NAME: + return Fuzziness.build(randomTimeValue()); + default: + return Fuzziness.AUTO; } - return fuzziness; } protected static boolean isNumericFieldName(String fieldName) { return INT_FIELD_NAME.equals(fieldName) || DOUBLE_FIELD_NAME.equals(fieldName); } + + protected static String randomAnalyzer() { + return randomFrom("simple", "standard", "keyword", "whitespace"); + } + + protected static String randomMinimumShouldMatch() { + return randomFrom("1", "-1", "75%", "-25%", "2<75%", "2<-25%"); + } + + protected static String randomTimeZone() { + return randomFrom(TIMEZONE_IDS); + } + + private static final List TIMEZONE_IDS = new ArrayList<>(DateTimeZone.getAvailableIDs()); } diff --git a/core/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTests.java index cec4a7eb867..f11e82a808e 100644 --- a/core/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/BoolQueryBuilderTests.java @@ -43,7 +43,7 @@ public class BoolQueryBuilderTests extends BaseQueryTestCase { query.disableCoord(randomBoolean()); } if (randomBoolean()) { - query.minimumNumberShouldMatch(randomIntBetween(1, 10)); + query.minimumNumberShouldMatch(randomMinimumShouldMatch()); } int mustClauses = randomIntBetween(0, 3); for (int i = 0; i < mustClauses; i++) { diff --git a/core/src/test/java/org/elasticsearch/index/query/CommonTermsQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/CommonTermsQueryBuilderTests.java index 1d093add49b..7d5a927e258 100644 --- a/core/src/test/java/org/elasticsearch/index/query/CommonTermsQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/CommonTermsQueryBuilderTests.java @@ -66,7 +66,7 @@ public class CommonTermsQueryBuilderTests extends BaseQueryTestCase { + + @Override + protected QueryStringQueryBuilder doCreateTestQueryBuilder() { + int numTerms = randomIntBetween(0, 5); + String query = ""; + for (int i = 0; i < numTerms; i++) { + //min length 4 makes sure that the text is not an operator (AND/OR) so toQuery won't break + query += (randomBoolean() ? STRING_FIELD_NAME + ":" : "") + randomAsciiOfLengthBetween(4, 10) + " "; + } + QueryStringQueryBuilder queryStringQueryBuilder = new QueryStringQueryBuilder(query); + if (randomBoolean()) { + queryStringQueryBuilder.defaultField(randomBoolean() ? STRING_FIELD_NAME : randomAsciiOfLengthBetween(1, 10)); + } + if (randomBoolean()) { + int numFields = randomIntBetween(1, 5); + for (int i = 0; i < numFields; i++) { + String fieldName = randomBoolean() ? STRING_FIELD_NAME : randomAsciiOfLengthBetween(1, 10); + if (randomBoolean()) { + queryStringQueryBuilder.field(fieldName); + } else { + queryStringQueryBuilder.field(fieldName, randomFloat()); + } + } + } + if (randomBoolean()) { + queryStringQueryBuilder.defaultOperator(randomFrom(Operator.values())); + } + if (randomBoolean()) { + //we only use string fields (either mapped or unmapped) + queryStringQueryBuilder.fuzziness(randomFuzziness(STRING_FIELD_NAME)); + } + if (randomBoolean()) { + queryStringQueryBuilder.analyzer(randomAnalyzer()); + } + if (randomBoolean()) { + queryStringQueryBuilder.quoteAnalyzer(randomAnalyzer()); + } + if (randomBoolean()) { + queryStringQueryBuilder.allowLeadingWildcard(randomBoolean()); + } + if (randomBoolean()) { + queryStringQueryBuilder.analyzeWildcard(randomBoolean()); + } + if (randomBoolean()) { + queryStringQueryBuilder.maxDeterminizedStates(randomIntBetween(1, 100)); + } + if (randomBoolean()) { + queryStringQueryBuilder.lowercaseExpandedTerms(randomBoolean()); + } + if (randomBoolean()) { + queryStringQueryBuilder.autoGeneratePhraseQueries(randomBoolean()); + } + if (randomBoolean()) { + queryStringQueryBuilder.enablePositionIncrements(randomBoolean()); + } + if (randomBoolean()) { + queryStringQueryBuilder.lenient(randomBoolean()); + } + if (randomBoolean()) { + queryStringQueryBuilder.escape(randomBoolean()); + } + if (randomBoolean()) { + queryStringQueryBuilder.phraseSlop(randomIntBetween(0, 10)); + } + if (randomBoolean()) { + queryStringQueryBuilder.fuzzyMaxExpansions(randomIntBetween(0, 100)); + } + if (randomBoolean()) { + queryStringQueryBuilder.fuzzyPrefixLength(randomIntBetween(0, 10)); + } + if (randomBoolean()) { + queryStringQueryBuilder.fuzzyRewrite(getRandomRewriteMethod()); + } + if (randomBoolean()) { + queryStringQueryBuilder.rewrite(getRandomRewriteMethod()); + } + if (randomBoolean()) { + queryStringQueryBuilder.quoteFieldSuffix(randomAsciiOfLengthBetween(1, 3)); + } + if (randomBoolean()) { + queryStringQueryBuilder.tieBreaker(randomFloat()); + } + if (randomBoolean()) { + queryStringQueryBuilder.minimumShouldMatch(randomMinimumShouldMatch()); + } + if (randomBoolean()) { + queryStringQueryBuilder.useDisMax(randomBoolean()); + } + if (randomBoolean()) { + queryStringQueryBuilder.locale(randomLocale(getRandom())); + } + if (randomBoolean()) { + queryStringQueryBuilder.timeZone(randomTimeZone()); + } + return queryStringQueryBuilder; + } + + @Override + protected void doAssertLuceneQuery(QueryStringQueryBuilder queryBuilder, Query query, QueryShardContext context) throws IOException { + if ("".equals(queryBuilder.queryString())) { + assertThat(query, instanceOf(MatchNoDocsQuery.class)); + } else { + assertThat(query, either(instanceOf(TermQuery.class)).or(instanceOf(AllTermQuery.class)) + .or(instanceOf(BooleanQuery.class)).or(instanceOf(DisjunctionMaxQuery.class))); + } + } + + @Test + public void testValidate() { + QueryValidationException queryValidationException = createTestQueryBuilder().validate(); + assertNull(queryValidationException); + + queryValidationException = new QueryStringQueryBuilder(null).validate(); + assertNotNull(queryValidationException); + assertThat(queryValidationException.validationErrors().size(), equalTo(1)); + } + + @Test + public void testToQueryMatchAllQuery() throws Exception { + Query query = queryStringQuery("*:*").toQuery(createShardContext()); + assertThat(query, instanceOf(MatchAllDocsQuery.class)); + } + + @Test + public void testToQueryTermQuery() throws IOException { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + Query query = queryStringQuery("test").defaultField(STRING_FIELD_NAME).toQuery(createShardContext()); + assertThat(query, instanceOf(TermQuery.class)); + TermQuery termQuery = (TermQuery) query; + assertThat(termQuery.getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test"))); + } + + @Test + public void testToQueryPhraseQuery() throws IOException { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + Query query = queryStringQuery("\"term1 term2\"").defaultField(STRING_FIELD_NAME).phraseSlop(3).toQuery(createShardContext()); + assertThat(query, instanceOf(DisjunctionMaxQuery.class)); + DisjunctionMaxQuery disjunctionMaxQuery = (DisjunctionMaxQuery) query; + assertThat(disjunctionMaxQuery.getDisjuncts().size(), equalTo(1)); + assertThat(disjunctionMaxQuery.getDisjuncts().get(0), instanceOf(PhraseQuery.class)); + PhraseQuery phraseQuery = (PhraseQuery)disjunctionMaxQuery.getDisjuncts().get(0); + assertThat(phraseQuery.getTerms().length, equalTo(2)); + assertThat(phraseQuery.getTerms()[0], equalTo(new Term(STRING_FIELD_NAME, "term1"))); + assertThat(phraseQuery.getTerms()[1], equalTo(new Term(STRING_FIELD_NAME, "term2"))); + assertThat(phraseQuery.getSlop(), equalTo(3)); + } + + @Test + public void testToQueryBoosts() throws Exception { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + QueryShardContext shardContext = createShardContext(); + QueryStringQueryBuilder queryStringQuery = queryStringQuery(STRING_FIELD_NAME + ":boosted^2"); + Query query = queryStringQuery.toQuery(shardContext); + assertThat(query, instanceOf(TermQuery.class)); + assertThat(((TermQuery) query).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "boosted"))); + assertThat(query.getBoost(), equalTo(2.0f)); + queryStringQuery.boost(2.0f); + query = queryStringQuery.toQuery(shardContext); + assertThat(query.getBoost(), equalTo(4.0f)); + + queryStringQuery = queryStringQuery("((" + STRING_FIELD_NAME + ":boosted^2) AND (" + STRING_FIELD_NAME + ":foo^1.5))^3"); + query = queryStringQuery.toQuery(shardContext); + assertThat(query, instanceOf(BooleanQuery.class)); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 0).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "boosted"))); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 0).getBoost(), equalTo(2.0f)); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 1).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "foo"))); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 1).getBoost(), equalTo(1.5f)); + assertThat(query.getBoost(), equalTo(3.0f)); + queryStringQuery.boost(2.0f); + query = queryStringQuery.toQuery(shardContext); + assertThat(query.getBoost(), equalTo(6.0f)); + } + + @Test + public void testToQueryMultipleTermsBooleanQuery() throws Exception { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + Query query = queryStringQuery("test1 test2").field(STRING_FIELD_NAME).useDisMax(false).toQuery(createShardContext()); + assertThat(query, instanceOf(BooleanQuery.class)); + BooleanQuery bQuery = (BooleanQuery) query; + assertThat(bQuery.clauses().size(), equalTo(2)); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 0).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test1"))); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 1).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test2"))); + } + + @Test + public void testToQueryMultipleFieldsBooleanQuery() throws Exception { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + Query query = queryStringQuery("test").field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2).useDisMax(false).toQuery(createShardContext()); + assertThat(query, instanceOf(BooleanQuery.class)); + BooleanQuery bQuery = (BooleanQuery) query; + assertThat(bQuery.clauses().size(), equalTo(2)); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 0).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test"))); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 1).getTerm(), equalTo(new Term(STRING_FIELD_NAME_2, "test"))); + } + + @Test + public void testToQueryMultipleFieldsDisMaxQuery() throws Exception { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + Query query = queryStringQuery("test").field(STRING_FIELD_NAME).field(STRING_FIELD_NAME_2).useDisMax(true).toQuery(createShardContext()); + assertThat(query, instanceOf(DisjunctionMaxQuery.class)); + DisjunctionMaxQuery disMaxQuery = (DisjunctionMaxQuery) query; + List disjuncts = disMaxQuery.getDisjuncts(); + assertThat(((TermQuery) disjuncts.get(0)).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test"))); + assertThat(((TermQuery) disjuncts.get(1)).getTerm(), equalTo(new Term(STRING_FIELD_NAME_2, "test"))); + } + + @Test + public void testToQueryFieldsWildcard() throws Exception { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + Query query = queryStringQuery("test").field("mapped_str*").useDisMax(false).toQuery(createShardContext()); + assertThat(query, instanceOf(BooleanQuery.class)); + BooleanQuery bQuery = (BooleanQuery) query; + assertThat(bQuery.clauses().size(), equalTo(2)); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 0).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test"))); + assertThat(assertBooleanSubQuery(query, TermQuery.class, 1).getTerm(), equalTo(new Term(STRING_FIELD_NAME_2, "test"))); + } + + @Test + public void testToQueryDisMaxQuery() throws Exception { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + Query query = queryStringQuery("test").field(STRING_FIELD_NAME, 2.2f).field(STRING_FIELD_NAME_2).useDisMax(true).toQuery(createShardContext()); + assertThat(query, instanceOf(DisjunctionMaxQuery.class)); + DisjunctionMaxQuery disMaxQuery = (DisjunctionMaxQuery) query; + List disjuncts = disMaxQuery.getDisjuncts(); + assertThat(((TermQuery) disjuncts.get(0)).getTerm(), equalTo(new Term(STRING_FIELD_NAME, "test"))); + assertThat((double) disjuncts.get(0).getBoost(), closeTo(2.2, 0.01)); + assertThat(((TermQuery) disjuncts.get(1)).getTerm(), equalTo(new Term(STRING_FIELD_NAME_2, "test"))); + assertThat((double) disjuncts.get(1).getBoost(), closeTo(1, 0.01)); + } + + @Test + public void testToQueryRegExpQuery() throws Exception { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + Query query = queryStringQuery("/foo*bar/").defaultField(STRING_FIELD_NAME).maxDeterminizedStates(5000).toQuery(createShardContext()); + assertThat(query, instanceOf(RegexpQuery.class)); + RegexpQuery regexpQuery = (RegexpQuery) query; + assertTrue(regexpQuery.toString().contains("/foo*bar/")); + } + + @Test(expected = TooComplexToDeterminizeException.class) + public void testToQueryRegExpQueryTooComplex() throws Exception { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + queryStringQuery("/[ac]*a[ac]{50,200}/").defaultField(STRING_FIELD_NAME).toQuery(createShardContext()); + } + + @Test + public void testToQueryNumericRangeQuery() throws Exception { + assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0); + Query query = queryStringQuery("12~0.2").defaultField(INT_FIELD_NAME).toQuery(createShardContext()); + assertThat(query, instanceOf(NumericRangeQuery.class)); + + } +} diff --git a/core/src/test/java/org/elasticsearch/index/query/RangeQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/RangeQueryBuilderTests.java index fa0726eedd2..77b8c130ce0 100644 --- a/core/src/test/java/org/elasticsearch/index/query/RangeQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/RangeQueryBuilderTests.java @@ -27,16 +27,12 @@ import org.joda.time.DateTimeZone; import org.junit.Test; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; public class RangeQueryBuilderTests extends BaseQueryTestCase { - private static final List TIMEZONE_IDS = new ArrayList<>(DateTimeZone.getAvailableIDs()); - @Override protected RangeQueryBuilder doCreateTestQueryBuilder() { RangeQueryBuilder query; @@ -64,7 +60,7 @@ public class RangeQueryBuilderTests extends BaseQueryTestCase // otherwise we could trigger exception. if (createShardContext().mapperService().smartNameFieldType(DATE_FIELD_NAME) != null) { if (randomBoolean()) { - query.timeZone(TIMEZONE_IDS.get(randomIntBetween(0, TIMEZONE_IDS.size() - 1))); + query.timeZone(randomTimeZone()); } if (randomBoolean()) { query.format("yyyy-MM-dd'T'HH:mm:ss.SSSZZ"); diff --git a/core/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java index 129de588236..be0913184d4 100644 --- a/core/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java @@ -34,8 +34,6 @@ import static org.hamcrest.Matchers.*; public class SimpleQueryStringBuilderTests extends BaseQueryTestCase { - private static final String[] MINIMUM_SHOULD_MATCH = new String[] { "1", "-1", "75%", "-25%", "2<75%", "2<-25%" }; - @Override protected SimpleQueryStringBuilder doCreateTestQueryBuilder() { SimpleQueryStringBuilder result = new SimpleQueryStringBuilder(randomAsciiOfLengthBetween(1, 10)); @@ -52,13 +50,13 @@ public class SimpleQueryStringBuilderTests extends BaseQueryTestCase flagSet = new HashSet<>(); @@ -72,12 +70,12 @@ public class SimpleQueryStringBuilderTests extends BaseQueryTestCase fields = new TreeMap<>(); + Map fields = new HashMap<>(); for (int i = 0; i < fieldCount; i++) { if (randomBoolean()) { fields.put(randomAsciiOfLengthBetween(1, 10), AbstractQueryBuilder.DEFAULT_BOOST); } else { - fields.put(randomAsciiOfLengthBetween(1, 10), 2.0f / randomIntBetween(1, 20)); + fields.put(randomBoolean() ? STRING_FIELD_NAME : randomAsciiOfLengthBetween(1, 10), 2.0f / randomIntBetween(1, 20)); } } result.fields(fields); @@ -274,22 +272,12 @@ public class SimpleQueryStringBuilderTests extends BaseQueryTestCase minMatchAlways = Arrays.asList("1", "-1", "75%", "-25%"); - Collection minMatchLarger = Arrays.asList("2<75%", "2<-25%"); - - if (minMatchAlways.contains(queryBuilder.minimumShouldMatch())) { - assertThat(boolQuery.getMinimumNumberShouldMatch(), greaterThan(0)); - } else if (minMatchLarger.contains(queryBuilder.minimumShouldMatch())) { - if (shouldClauses(boolQuery) > 2) { - assertThat(boolQuery.getMinimumNumberShouldMatch(), greaterThan(0)); - } - } else { - assertEquals(0, boolQuery.getMinimumNumberShouldMatch()); - } + assertThat(boolQuery.getMinimumNumberShouldMatch(), greaterThan(0)); } } else if (queryBuilder.fields().size() <= 1) { assertTrue("Query should have been TermQuery but was " + query.getClass().getName(), query instanceof TermQuery); @@ -301,11 +289,8 @@ public class SimpleQueryStringBuilderTests extends BaseQueryTestCasecats junk junk")); // which can also be written by searching on the subfield - resp = req.setQuery(queryStringQuery("cats").field("foo").field("foo.plain^5")).get(); + resp = req.setQuery(queryStringQuery("cats").field("foo").field("foo.plain", 5)).get(); assertHighlight(resp, 0, "foo", 0, equalTo("junk junk cats junk junk")); // Speaking of two fields, you can have two fields, only one of which has matchedFields enabled - QueryBuilder twoFieldsQuery = queryStringQuery("cats").field("foo").field("foo.plain^5") - .field("bar").field("bar.plain^5"); + QueryBuilder twoFieldsQuery = queryStringQuery("cats").field("foo").field("foo.plain", 5) + .field("bar").field("bar.plain", 5); resp = req.setQuery(twoFieldsQuery).addHighlightedField(barField).get(); assertHighlight(resp, 0, "foo", 0, equalTo("junk junk cats junk junk")); assertHighlight(resp, 0, "bar", 0, equalTo("cat cat junk junk junk junk")); @@ -2610,7 +2610,7 @@ public class HighlighterSearchIT extends ESIntegTestCase { // Query string boosting the field phraseBoostTestCaseForClauses(highlighterType, 1f, queryStringQuery("highlight words together").field("field1"), - queryStringQuery("\"highlight words together\"").field("field1^100").autoGeneratePhraseQueries(true)); + queryStringQuery("\"highlight words together\"").field("field1", 100).autoGeneratePhraseQueries(true)); } private

> void diff --git a/docs/reference/migration/migrate_query_refactoring.asciidoc b/docs/reference/migration/migrate_query_refactoring.asciidoc index 3bac18ff3de..c46117ee337 100644 --- a/docs/reference/migration/migrate_query_refactoring.asciidoc +++ b/docs/reference/migration/migrate_query_refactoring.asciidoc @@ -59,6 +59,11 @@ Removed `wrapperQueryBuilder(byte[] source, int offset, int length)`. Instead si use `wrapperQueryBuilder(byte[] source)`. Updated the static factory methods in QueryBuilders accordingly. +==== QueryStringQueryBuilder + +Removed ability to pass in boost value using `field(String field)` method in form e.g. `field^2`. +Use the `field(String, float)` method instead. + ==== Operator Removed the enums called `Operator` from `MatchQueryBuilder`, `QueryStringQueryBuilder`,