Query refactoring: query_string

Relates to #10217
This commit is contained in:
javanna 2015-08-31 15:16:53 +02:00 committed by Luca Cavanna
parent 274611c7c0
commit 484fcd49e5
15 changed files with 1017 additions and 558 deletions

View File

@ -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;
if (settings.fieldsAndWeights().isEmpty()) {
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 if (settings.fieldsAndWeights().size() == 1) {
this.field = settings.fieldsAndWeights().keySet().iterator().next();
} 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();
} else {
this.forcedAnalyzer = false;
this.quoteAnalyzer = settings.defaultQuoteAnalyzer();
}
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<String> extractMultiFields(String field) {
Collection<String> fields = null;
Collection<String> fields;
if (field != null) {
fields = context.simpleMatchToIndexNames(field);
} else {
fields = settings.fields();
fields = settings.fieldsAndWeights().keySet();
}
return fields;
}

View File

@ -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<String, Float> 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<String> fields = null;
ObjectFloatHashMap<String> 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<String, Float> fieldsAndWeights() {
return fieldsAndWeights;
}
public void boost(float boost) {
this.boost = boost;
public void fieldsAndWeights(Map<String, Float> 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<String> fields() {
return fields;
}
public void fields(List<String> fields) {
this.fields = fields;
}
public ObjectFloatHashMap<String> boosts() {
return boosts;
}
public void boosts(ObjectFloatHashMap<String> 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;
}
}

View File

@ -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;
}

View File

@ -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<Operator> {
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<Operator> {
}
}
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();

View File

@ -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;

View File

@ -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<QueryStringQue
public static final String NAME = "query_string";
public static final boolean DEFAULT_AUTO_GENERATE_PHRASE_QUERIES = false;
public static final int DEFAULT_MAX_DETERMINED_STATES = Operations.DEFAULT_MAX_DETERMINIZED_STATES;
public static final boolean DEFAULT_LOWERCASE_EXPANDED_TERMS = true;
public static final boolean DEFAULT_ENABLE_POSITION_INCREMENTS = true;
public static final boolean DEFAULT_ESCAPE = false;
public static final boolean DEFAULT_USE_DIS_MAX = true;
public static final int DEFAULT_FUZZY_PREFIX_LENGTH = FuzzyQuery.defaultPrefixLength;
public static final int DEFAULT_FUZZY_MAX_EXPANSIONS = FuzzyQuery.defaultMaxExpansions;
public static final int DEFAULT_PHRASE_SLOP = 0;
public static final float DEFAULT_TIE_BREAKER = 0.0f;
public static final Fuzziness DEFAULT_FUZZINESS = Fuzziness.AUTO;
public static final Operator DEFAULT_OPERATOR = Operator.OR;
public static final Locale DEFAULT_LOCALE = Locale.ROOT;
static final QueryStringQueryBuilder PROTOTYPE = new QueryStringQueryBuilder(null);
private final String queryString;
private String defaultField;
/**
* Fields to query against. If left empty will query default field,
* currently _ALL. Uses a TreeMap to hold the fields so boolean clauses are
* always sorted in same order for generated Lucene query for easier
* testing.
*
* Can be changed back to HashMap once https://issues.apache.org/jira/browse/LUCENE-6305 is fixed.
*/
private final Map<String, Float> 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<String> fields;
private int phraseSlop = DEFAULT_PHRASE_SLOP;
private ObjectFloatHashMap<String> 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<QueryStringQue
return this;
}
public String defaultField() {
return this.defaultField;
}
/**
* Adds a field to run the query string against.
* Adds a field to run the query string against. The field will be associated with the default boost of {@link AbstractQueryBuilder#DEFAULT_BOOST}.
* Use {@link #field(String, float)} to set a specific boost for the field.
*/
public QueryStringQueryBuilder field(String field) {
if (fields == null) {
fields = new ArrayList<>();
}
fields.add(field);
this.fieldsAndWeights.put(field, AbstractQueryBuilder.DEFAULT_BOOST);
return this;
}
@ -121,17 +162,23 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
* Adds a field to run the query string against with a specific boost.
*/
public QueryStringQueryBuilder field(String field, float boost) {
if (fields == null) {
fields = new ArrayList<>();
}
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<String, Float> fields) {
this.fieldsAndWeights.putAll(fields);
return this;
}
/** Returns the fields including their respective boosts to run the query against. */
public Map<String, Float> 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 (<tt>true</tt>).
@ -141,6 +188,10 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public boolean useDisMax() {
return this.useDisMax;
}
/**
* When more than one field is used with the query string, and combined queries are using
* dis max, control the tie breaker for it.
@ -150,6 +201,10 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public float tieBreaker() {
return this.tieBreaker;
}
/**
* Sets the boolean operator of the query parser used to parse the query string.
* <p/>
@ -161,10 +216,14 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
* above mentioned query is parsed as <code>capital AND of AND Hungary</code>
*/
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 AbstractQueryBuilder<QueryStringQue
* 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;
public QueryStringQueryBuilder quoteAnalyzer(String quoteAnalyzer) {
this.quoteAnalyzer = quoteAnalyzer;
return this;
}
/**
* Set to true if phrase queries will be automatically generated
* when the analyzer returns more than one term from whitespace
@ -198,6 +256,10 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public boolean autoGeneratePhraseQueries() {
return this.autoGeneratePhraseQueries;
}
/**
* Protects against too-difficult regular expression queries.
*/
@ -206,14 +268,22 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public int maxDeterminizedStates() {
return this.maxDeterminizedStates;
}
/**
* Should leading wildcards be allowed or not. Defaults to <tt>true</tt>.
*/
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 <tt>true</tt>.
@ -223,6 +293,10 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public boolean lowercaseExpandedTerms() {
return this.lowercaseExpandedTerms;
}
/**
* Set to <tt>true</tt> to enable position increments in result query. Defaults to
* <tt>true</tt>.
@ -235,14 +309,22 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public boolean enablePositionIncrements() {
return this.enablePositionIncrements;
}
/**
* Set the edit distance for fuzzy queries. Default is "AUTO".
*/
public QueryStringQueryBuilder fuzziness(Fuzziness fuzziness) {
this.fuzziness = fuzziness;
this.fuzziness = fuzziness == null ? DEFAULT_FUZZINESS : fuzziness;
return this;
}
public Fuzziness fuzziness() {
return this.fuzziness;
}
/**
* Set the minimum prefix length for fuzzy queries. Default is 1.
*/
@ -251,16 +333,28 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public int fuzzyPrefixLength() {
return fuzzyPrefixLength;
}
public QueryStringQueryBuilder fuzzyMaxExpansions(int fuzzyMaxExpansions) {
this.fuzzyMaxExpansions = fuzzyMaxExpansions;
return this;
}
public int fuzzyMaxExpansions() {
return fuzzyMaxExpansions;
}
public QueryStringQueryBuilder fuzzyRewrite(String fuzzyRewrite) {
this.fuzzyRewrite = fuzzyRewrite;
return this;
}
public String fuzzyRewrite() {
return fuzzyRewrite;
}
/**
* Sets the default slop for phrases. If zero, then exact phrase matches
* are required. Default value is zero.
@ -270,24 +364,40 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public int phraseSlop() {
return phraseSlop;
}
/**
* Set to <tt>true</tt> 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 AbstractQueryBuilder<QueryStringQue
return this;
}
public String quoteFieldSuffix() {
return this.quoteFieldSuffix;
}
/**
* Sets the query string parser to be lenient when parsing field values, defaults to the index
* setting and if not set, defaults to false.
@ -305,19 +419,40 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public Boolean lenient() {
return this.lenient;
}
public QueryStringQueryBuilder locale(Locale locale) {
this.locale = locale;
this.locale = locale == null ? DEFAULT_LOCALE : locale;
return this;
}
public Locale locale() {
return this.locale;
}
/**
* In case of date field, we can adjust the from/to fields using a timezone
*/
public QueryStringQueryBuilder timeZone(String timeZone) {
if (timeZone != null) {
this.timeZone = DateTimeZone.forID(timeZone);
} else {
this.timeZone = null;
}
return this;
}
public QueryStringQueryBuilder timeZone(DateTimeZone timeZone) {
this.timeZone = timeZone;
return this;
}
public DateTimeZone timeZone() {
return this.timeZone;
}
/**
* Set to <tt>true</tt> to enable escaping of the query string
*/
@ -326,92 +461,65 @@ public class QueryStringQueryBuilder extends AbstractQueryBuilder<QueryStringQue
return this;
}
public boolean escape() {
return this.escape;
}
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.field("query", queryString);
if (defaultField != null) {
builder.field("default_field", defaultField);
builder.field("query", this.queryString);
if (this.defaultField != null) {
builder.field("default_field", this.defaultField);
}
if (fields != null) {
builder.startArray("fields");
for (String field : fields) {
if (fieldsBoosts != null && fieldsBoosts.containsKey(field)) {
field += "^" + fieldsBoosts.get(field);
}
builder.value(field);
for (Map.Entry<String, Float> fieldEntry : this.fieldsAndWeights.entrySet()) {
builder.value(fieldEntry.getKey() + "^" + fieldEntry.getValue());
}
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 (useDisMax != null) {
builder.field("use_dis_max", useDisMax);
if (this.quoteAnalyzer != null) {
builder.field("quote_analyzer", this.quoteAnalyzer);
}
if (tieBreaker != -1) {
builder.field("tie_breaker", tieBreaker);
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 (defaultOperator != null) {
builder.field("default_operator", defaultOperator.name().toLowerCase(Locale.ROOT));
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 (analyzer != null) {
builder.field("analyzer", analyzer);
builder.field("phrase_slop", this.phraseSlop);
if (this.analyzeWildcard != null) {
builder.field("analyze_wildcard", this.analyzeWildcard);
}
if (quoteAnalyzer != null) {
builder.field("quote_analyzer", quoteAnalyzer);
if (this.rewrite != null) {
builder.field("rewrite", this.rewrite);
}
if (autoGeneratePhraseQueries != null) {
builder.field("auto_generate_phrase_queries", autoGeneratePhraseQueries);
if (this.minimumShouldMatch != null) {
builder.field("minimum_should_match", this.minimumShouldMatch);
}
if (maxDeterminizedStates != null) {
builder.field("max_determinized_states", maxDeterminizedStates);
if (this.quoteFieldSuffix != null) {
builder.field("quote_field_suffix", this.quoteFieldSuffix);
}
if (allowLeadingWildcard != null) {
builder.field("allow_leading_wildcard", allowLeadingWildcard);
if (this.lenient != null) {
builder.field("lenient", this.lenient);
}
if (lowercaseExpandedTerms != null) {
builder.field("lowercase_expanded_terms", lowercaseExpandedTerms);
}
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<QueryStringQue
public String getWriteableName() {
return NAME;
}
@Override
public QueryValidationException validate() {
QueryValidationException validationException = null;
if (queryString == null) {
validationException = addValidationError("query text missing", null);
}
return validationException;
}
@Override
protected QueryStringQueryBuilder doReadFrom(StreamInput in) throws IOException {
QueryStringQueryBuilder queryStringQueryBuilder = new QueryStringQueryBuilder(in.readString());
queryStringQueryBuilder.defaultField = in.readOptionalString();
int size = in.readVInt();
for (int i = 0; i < size; i++) {
queryStringQueryBuilder.fieldsAndWeights.put(in.readString(), in.readFloat());
}
queryStringQueryBuilder.defaultOperator = Operator.readOperatorFrom(in);
queryStringQueryBuilder.analyzer = in.readOptionalString();
queryStringQueryBuilder.quoteAnalyzer = in.readOptionalString();
queryStringQueryBuilder.quoteFieldSuffix = in.readOptionalString();
queryStringQueryBuilder.autoGeneratePhraseQueries = in.readBoolean();
queryStringQueryBuilder.allowLeadingWildcard = in.readOptionalBoolean();
queryStringQueryBuilder.analyzeWildcard = in.readOptionalBoolean();
queryStringQueryBuilder.lowercaseExpandedTerms = in.readBoolean();
queryStringQueryBuilder.enablePositionIncrements = in.readBoolean();
queryStringQueryBuilder.locale = Locale.forLanguageTag(in.readString());
queryStringQueryBuilder.fuzziness = Fuzziness.readFuzzinessFrom(in);
queryStringQueryBuilder.fuzzyPrefixLength = in.readVInt();
queryStringQueryBuilder.fuzzyMaxExpansions = in.readVInt();
queryStringQueryBuilder.fuzzyRewrite = in.readOptionalString();
queryStringQueryBuilder.phraseSlop = in.readVInt();
queryStringQueryBuilder.useDisMax = in.readBoolean();
queryStringQueryBuilder.tieBreaker = in.readFloat();
queryStringQueryBuilder.rewrite = in.readOptionalString();
queryStringQueryBuilder.minimumShouldMatch = in.readOptionalString();
queryStringQueryBuilder.lenient = in.readOptionalBoolean();
if (in.readBoolean()) {
queryStringQueryBuilder.timeZone = DateTimeZone.forID(in.readString());
}
queryStringQueryBuilder.escape = in.readBoolean();
queryStringQueryBuilder.maxDeterminizedStates = in.readVInt();
return queryStringQueryBuilder;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(this.queryString);
out.writeOptionalString(this.defaultField);
out.writeVInt(this.fieldsAndWeights.size());
for (Map.Entry<String, Float> 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<String, Float> resolvedFields = new TreeMap<>();
for (Map.Entry<String, Float> 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);
}
}

View File

@ -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<String, Float> 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<String>());
}
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<String>());
}
qpSettings.boosts().put(field, fBoost);
}
fieldsAndWeights.put(fField, fBoost);
}
} else {
qpSettings.fields().add(fField);
if (fBoost != -1) {
if (qpSettings.boosts() == null) {
qpSettings.boosts(new ObjectFloatHashMap<String>());
}
qpSettings.boosts().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

View File

@ -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<QB extends AbstractQueryBuilder<QB>> 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<QB extends AbstractQueryBuilder<QB>> 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<QB extends AbstractQueryBuilder<QB>> 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<String> TIMEZONE_IDS = new ArrayList<>(DateTimeZone.getAvailableIDs());
}

View File

@ -43,7 +43,7 @@ public class BoolQueryBuilderTests extends BaseQueryTestCase<BoolQueryBuilder> {
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++) {

View File

@ -66,7 +66,7 @@ public class CommonTermsQueryBuilderTests extends BaseQueryTestCase<CommonTermsQ
}
if (randomBoolean()) {
query.analyzer(randomFrom("simple", "keyword", "whitespace"));
query.analyzer(randomAnalyzer());
}
if (randomBoolean()) {

View File

@ -0,0 +1,291 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.index.query;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.apache.lucene.util.automaton.TooComplexToDeterminizeException;
import org.elasticsearch.common.lucene.all.AllTermQuery;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertBooleanSubQuery;
import static org.hamcrest.CoreMatchers.either;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.*;
public class QueryStringQueryBuilderTests extends BaseQueryTestCase<QueryStringQueryBuilder> {
@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<Query> 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<Query> 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));
}
}

View File

@ -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<RangeQueryBuilder> {
private static final List<String> TIMEZONE_IDS = new ArrayList<>(DateTimeZone.getAvailableIDs());
@Override
protected RangeQueryBuilder doCreateTestQueryBuilder() {
RangeQueryBuilder query;
@ -64,7 +60,7 @@ public class RangeQueryBuilderTests extends BaseQueryTestCase<RangeQueryBuilder>
// 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");

View File

@ -34,8 +34,6 @@ import static org.hamcrest.Matchers.*;
public class SimpleQueryStringBuilderTests extends BaseQueryTestCase<SimpleQueryStringBuilder> {
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<SimpleQuery
result.locale(randomLocale(getRandom()));
}
if (randomBoolean()) {
result.minimumShouldMatch(randomFrom(MINIMUM_SHOULD_MATCH));
result.minimumShouldMatch(randomMinimumShouldMatch());
}
if (randomBoolean()) {
result.analyzer("simple");
result.analyzer(randomAnalyzer());
}
if (randomBoolean()) {
result.defaultOperator(randomFrom(Operator.AND, Operator.OR));
result.defaultOperator(randomFrom(Operator.values()));
}
if (randomBoolean()) {
Set<SimpleQueryStringFlag> flagSet = new HashSet<>();
@ -72,12 +70,12 @@ public class SimpleQueryStringBuilderTests extends BaseQueryTestCase<SimpleQuery
}
int fieldCount = randomIntBetween(0, 10);
Map<String, Float> fields = new TreeMap<>();
Map<String, Float> 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<SimpleQuery
for (BooleanClause booleanClause : boolQuery) {
assertThat(booleanClause.getQuery(), instanceOf(TermQuery.class));
TermQuery termQuery = (TermQuery) booleanClause.getQuery();
assertThat(termQuery.getTerm(), equalTo(new Term(fields.next(), queryBuilder.value().toLowerCase(Locale.ROOT))));
assertThat(termQuery.getTerm().field(), equalTo(fields.next()));
assertThat(termQuery.getTerm().text().toLowerCase(Locale.ROOT), equalTo(queryBuilder.value().toLowerCase(Locale.ROOT)));
}
if (queryBuilder.minimumShouldMatch() != null) {
Collection<String> minMatchAlways = Arrays.asList("1", "-1", "75%", "-25%");
Collection<String> 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());
}
}
} 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 BaseQueryTestCase<SimpleQuery
} else {
field = queryBuilder.fields().keySet().iterator().next();
}
assertThat(termQuery.getTerm(), equalTo(new Term(field, queryBuilder.value().toLowerCase(Locale.ROOT))));
if (queryBuilder.lowercaseExpandedTerms()) {
assertThat(termQuery.getTerm().bytes().toString(), is(termQuery.getTerm().bytes().toString().toLowerCase(Locale.ROOT)));
}
assertThat(termQuery.getTerm().field(), equalTo(field));
assertThat(termQuery.getTerm().text().toLowerCase(Locale.ROOT), equalTo(queryBuilder.value().toLowerCase(Locale.ROOT)));
} else {
fail("Encountered lucene query type we do not have a validation implementation for in our " + SimpleQueryStringBuilderTests.class.getSimpleName());
}

View File

@ -964,12 +964,12 @@ public class HighlighterSearchIT extends ESIntegTestCase {
assertHighlight(resp, 0, "foo", 0, equalTo("junk junk <em>cats</em> 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 <em>cats</em> 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 <em>cats</em> junk junk"));
assertHighlight(resp, 0, "bar", 0, equalTo("<em>cat</em> <em>cat</em> 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 <P extends AbstractQueryBuilder<P>> void

View File

@ -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`,