Merge branch 'master' into immutable_set_be_gone

This commit is contained in:
Nik Everett 2015-09-25 09:09:24 -04:00
commit 5949b83115
471 changed files with 25508 additions and 13852 deletions

4
Vagrantfile vendored
View File

@ -83,6 +83,10 @@ Vagrant.configure(2) do |config|
# the elasticsearch project called vagrant.... # the elasticsearch project called vagrant....
config.vm.synced_folder ".", "/vagrant", disabled: true config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.synced_folder ".", "/elasticsearch" config.vm.synced_folder ".", "/elasticsearch"
config.vm.provider "virtualbox" do |v|
# Give the boxes 2GB so they can run our tests if they have to.
v.memory = 2048
end
if Vagrant.has_plugin?("vagrant-cachier") if Vagrant.has_plugin?("vagrant-cachier")
config.cache.scope = :box config.cache.scope = :box
end end

View File

@ -76,6 +76,10 @@ public class ExtendedCommonTermsQuery extends CommonTermsQuery {
return lowFreqMinNumShouldMatchSpec; return lowFreqMinNumShouldMatchSpec;
} }
public float getMaxTermFrequency() {
return this.maxTermFrequency;
}
@Override @Override
protected Query newTermQuery(Term term, TermContext context) { protected Query newTermQuery(Term term, TermContext context) {
if (fieldType == null) { if (fieldType == null) {

View File

@ -21,8 +21,8 @@ package org.apache.lucene.queryparser.classic;
import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.index.query.ExistsQueryParser; import org.elasticsearch.index.query.ExistsQueryBuilder;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
/** /**
* *
@ -32,7 +32,7 @@ public class ExistsFieldQueryExtension implements FieldQueryExtension {
public static final String NAME = "_exists_"; public static final String NAME = "_exists_";
@Override @Override
public Query query(QueryParseContext parseContext, String queryText) { public Query query(QueryShardContext context, String queryText) {
return new ConstantScoreQuery(ExistsQueryParser.newFilter(parseContext, queryText, null)); return new ConstantScoreQuery(ExistsQueryBuilder.newFilter(context, queryText));
} }
} }

View File

@ -20,12 +20,12 @@
package org.apache.lucene.queryparser.classic; package org.apache.lucene.queryparser.classic;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
/** /**
* *
*/ */
public interface FieldQueryExtension { public interface FieldQueryExtension {
Query query(QueryParseContext parseContext, String queryText); Query query(QueryShardContext context, String queryText);
} }

View File

@ -19,31 +19,21 @@
package org.apache.lucene.queryparser.classic; package org.apache.lucene.queryparser.classic;
import com.google.common.collect.ImmutableMap;
import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.*;
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.util.automaton.RegExp; import org.apache.lucene.util.automaton.RegExp;
import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.core.DateFieldMapper; import org.elasticsearch.index.mapper.core.DateFieldMapper;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.support.QueryParsers; import org.elasticsearch.index.query.support.QueryParsers;
import com.google.common.collect.ImmutableMap;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -70,53 +60,27 @@ public class MapperQueryParser extends QueryParser {
.build(); .build();
} }
private final QueryParseContext parseContext; private final QueryShardContext context;
private QueryParserSettings settings; private QueryParserSettings settings;
private Analyzer quoteAnalyzer;
private boolean forcedAnalyzer;
private boolean forcedQuoteAnalyzer;
private MappedFieldType currentFieldType; private MappedFieldType currentFieldType;
private boolean analyzeWildcard; public MapperQueryParser(QueryShardContext context) {
private String quoteFieldSuffix;
public MapperQueryParser(QueryParseContext parseContext) {
super(null, null); super(null, null);
this.parseContext = parseContext; this.context = context;
} }
public void reset(QueryParserSettings settings) { public void reset(QueryParserSettings settings) {
this.settings = settings; this.settings = settings;
this.field = settings.defaultField(); if (settings.fieldsAndWeights().isEmpty()) {
this.field = settings.defaultField();
if (settings.fields() != null) { } else if (settings.fieldsAndWeights().size() == 1) {
if (settings.fields.size() == 1) { this.field = settings.fieldsAndWeights().keySet().iterator().next();
// 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();
} else { } else {
this.forcedAnalyzer = false; this.field = null;
this.quoteAnalyzer = settings.defaultQuoteAnalyzer();
} }
this.quoteFieldSuffix = settings.quoteFieldSuffix(); setAnalyzer(settings.analyzer());
setMultiTermRewriteMethod(settings.rewriteMethod()); setMultiTermRewriteMethod(settings.rewriteMethod());
setEnablePositionIncrements(settings.enablePositionIncrements()); setEnablePositionIncrements(settings.enablePositionIncrements());
setAutoGeneratePhraseQueries(settings.autoGeneratePhraseQueries()); setAutoGeneratePhraseQueries(settings.autoGeneratePhraseQueries());
@ -125,10 +89,9 @@ public class MapperQueryParser extends QueryParser {
setLowercaseExpandedTerms(settings.lowercaseExpandedTerms()); setLowercaseExpandedTerms(settings.lowercaseExpandedTerms());
setPhraseSlop(settings.phraseSlop()); setPhraseSlop(settings.phraseSlop());
setDefaultOperator(settings.defaultOperator()); setDefaultOperator(settings.defaultOperator());
setFuzzyMinSim(settings.getFuzziness().asFloat()); setFuzzyMinSim(settings.fuzziness().asFloat());
setFuzzyPrefixLength(settings.fuzzyPrefixLength()); setFuzzyPrefixLength(settings.fuzzyPrefixLength());
setLocale(settings.locale()); setLocale(settings.locale());
this.analyzeWildcard = settings.analyzeWildcard();
} }
/** /**
@ -162,7 +125,7 @@ public class MapperQueryParser extends QueryParser {
public Query getFieldQuery(String field, String queryText, boolean quoted) throws ParseException { public Query getFieldQuery(String field, String queryText, boolean quoted) throws ParseException {
FieldQueryExtension fieldQueryExtension = fieldQueryExtensions.get(field); FieldQueryExtension fieldQueryExtension = fieldQueryExtensions.get(field);
if (fieldQueryExtension != null) { if (fieldQueryExtension != null) {
return fieldQueryExtension.query(parseContext, queryText); return fieldQueryExtension.query(context, queryText);
} }
Collection<String> fields = extractMultiFields(field); Collection<String> fields = extractMultiFields(field);
if (fields != null) { if (fields != null) {
@ -224,29 +187,29 @@ public class MapperQueryParser extends QueryParser {
Analyzer oldAnalyzer = getAnalyzer(); Analyzer oldAnalyzer = getAnalyzer();
try { try {
if (quoted) { if (quoted) {
setAnalyzer(quoteAnalyzer); setAnalyzer(settings.quoteAnalyzer());
if (quoteFieldSuffix != null) { if (settings.quoteFieldSuffix() != null) {
currentFieldType = parseContext.fieldMapper(field + quoteFieldSuffix); currentFieldType = context.fieldMapper(field + settings.quoteFieldSuffix());
} }
} }
if (currentFieldType == null) { if (currentFieldType == null) {
currentFieldType = parseContext.fieldMapper(field); currentFieldType = context.fieldMapper(field);
} }
if (currentFieldType != null) { if (currentFieldType != null) {
if (quoted) { if (quoted) {
if (!forcedQuoteAnalyzer) { if (!settings.forceQuoteAnalyzer()) {
setAnalyzer(parseContext.getSearchQuoteAnalyzer(currentFieldType)); setAnalyzer(context.getSearchQuoteAnalyzer(currentFieldType));
} }
} else { } else {
if (!forcedAnalyzer) { if (!settings.forceAnalyzer()) {
setAnalyzer(parseContext.getSearchAnalyzer(currentFieldType)); setAnalyzer(context.getSearchAnalyzer(currentFieldType));
} }
} }
if (currentFieldType != null) { if (currentFieldType != null) {
Query query = null; Query query = null;
if (currentFieldType.useTermQueryWithQueryString()) { if (currentFieldType.useTermQueryWithQueryString()) {
try { try {
query = currentFieldType.termQuery(queryText, parseContext); query = currentFieldType.termQuery(queryText, context);
} catch (RuntimeException e) { } catch (RuntimeException e) {
if (settings.lenient()) { if (settings.lenient()) {
return null; return null;
@ -357,7 +320,7 @@ public class MapperQueryParser extends QueryParser {
} }
private Query getRangeQuerySingle(String field, String part1, String part2, boolean startInclusive, boolean endInclusive) { private Query getRangeQuerySingle(String field, String part1, String part2, boolean startInclusive, boolean endInclusive) {
currentFieldType = parseContext.fieldMapper(field); currentFieldType = context.fieldMapper(field);
if (currentFieldType != null) { if (currentFieldType != null) {
if (lowercaseExpandedTerms && !currentFieldType.isNumeric()) { if (lowercaseExpandedTerms && !currentFieldType.isNumeric()) {
part1 = part1 == null ? null : part1.toLowerCase(locale); part1 = part1 == null ? null : part1.toLowerCase(locale);
@ -422,7 +385,7 @@ public class MapperQueryParser extends QueryParser {
} }
private Query getFuzzyQuerySingle(String field, String termStr, String minSimilarity) throws ParseException { private Query getFuzzyQuerySingle(String field, String termStr, String minSimilarity) throws ParseException {
currentFieldType = parseContext.fieldMapper(field); currentFieldType = context.fieldMapper(field);
if (currentFieldType != null) { if (currentFieldType != null) {
try { try {
return currentFieldType.fuzzyQuery(termStr, Fuzziness.build(minSimilarity), fuzzyPrefixLength, settings.fuzzyMaxExpansions(), FuzzyQuery.defaultTranspositions); return currentFieldType.fuzzyQuery(termStr, Fuzziness.build(minSimilarity), fuzzyPrefixLength, settings.fuzzyMaxExpansions(), FuzzyQuery.defaultTranspositions);
@ -492,14 +455,14 @@ public class MapperQueryParser extends QueryParser {
currentFieldType = null; currentFieldType = null;
Analyzer oldAnalyzer = getAnalyzer(); Analyzer oldAnalyzer = getAnalyzer();
try { try {
currentFieldType = parseContext.fieldMapper(field); currentFieldType = context.fieldMapper(field);
if (currentFieldType != null) { if (currentFieldType != null) {
if (!forcedAnalyzer) { if (!settings.forceAnalyzer()) {
setAnalyzer(parseContext.getSearchAnalyzer(currentFieldType)); setAnalyzer(context.getSearchAnalyzer(currentFieldType));
} }
Query query = null; Query query = null;
if (currentFieldType.useTermQueryWithQueryString()) { if (currentFieldType.useTermQueryWithQueryString()) {
query = currentFieldType.prefixQuery(termStr, multiTermRewriteMethod, parseContext); query = currentFieldType.prefixQuery(termStr, multiTermRewriteMethod, context);
} }
if (query == null) { if (query == null) {
query = getPossiblyAnalyzedPrefixQuery(currentFieldType.names().indexName(), termStr); query = getPossiblyAnalyzedPrefixQuery(currentFieldType.names().indexName(), termStr);
@ -518,7 +481,7 @@ public class MapperQueryParser extends QueryParser {
} }
private Query getPossiblyAnalyzedPrefixQuery(String field, String termStr) throws ParseException { private Query getPossiblyAnalyzedPrefixQuery(String field, String termStr) throws ParseException {
if (!analyzeWildcard) { if (!settings.analyzeWildcard()) {
return super.getPrefixQuery(field, termStr); return super.getPrefixQuery(field, termStr);
} }
// get Analyzer from superclass and tokenize the term // 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)); clauses.add(new BooleanClause(super.getPrefixQuery(field, token), BooleanClause.Occur.SHOULD));
} }
return getBooleanQuery(clauses, true); 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 @Override
@ -584,7 +538,7 @@ public class MapperQueryParser extends QueryParser {
return newMatchAllDocsQuery(); return newMatchAllDocsQuery();
} }
// effectively, we check if a field exists or not // effectively, we check if a field exists or not
return fieldQueryExtensions.get(ExistsFieldQueryExtension.NAME).query(parseContext, actualField); return fieldQueryExtensions.get(ExistsFieldQueryExtension.NAME).query(context, actualField);
} }
} }
if (lowercaseExpandedTerms) { if (lowercaseExpandedTerms) {
@ -633,10 +587,10 @@ public class MapperQueryParser extends QueryParser {
currentFieldType = null; currentFieldType = null;
Analyzer oldAnalyzer = getAnalyzer(); Analyzer oldAnalyzer = getAnalyzer();
try { try {
currentFieldType = parseContext.fieldMapper(field); currentFieldType = context.fieldMapper(field);
if (currentFieldType != null) { if (currentFieldType != null) {
if (!forcedAnalyzer) { if (!settings.forceAnalyzer()) {
setAnalyzer(parseContext.getSearchAnalyzer(currentFieldType)); setAnalyzer(context.getSearchAnalyzer(currentFieldType));
} }
indexedNameField = currentFieldType.names().indexName(); indexedNameField = currentFieldType.names().indexName();
return getPossiblyAnalyzedWildcardQuery(indexedNameField, termStr); return getPossiblyAnalyzedWildcardQuery(indexedNameField, termStr);
@ -653,7 +607,7 @@ public class MapperQueryParser extends QueryParser {
} }
private Query getPossiblyAnalyzedWildcardQuery(String field, String termStr) throws ParseException { private Query getPossiblyAnalyzedWildcardQuery(String field, String termStr) throws ParseException {
if (!analyzeWildcard) { if (!settings.analyzeWildcard()) {
return super.getWildcardQuery(field, termStr); return super.getWildcardQuery(field, termStr);
} }
boolean isWithinToken = (!termStr.startsWith("?") && !termStr.startsWith("*")); boolean isWithinToken = (!termStr.startsWith("?") && !termStr.startsWith("*"));
@ -765,14 +719,14 @@ public class MapperQueryParser extends QueryParser {
currentFieldType = null; currentFieldType = null;
Analyzer oldAnalyzer = getAnalyzer(); Analyzer oldAnalyzer = getAnalyzer();
try { try {
currentFieldType = parseContext.fieldMapper(field); currentFieldType = context.fieldMapper(field);
if (currentFieldType != null) { if (currentFieldType != null) {
if (!forcedAnalyzer) { if (!settings.forceAnalyzer()) {
setAnalyzer(parseContext.getSearchAnalyzer(currentFieldType)); setAnalyzer(context.getSearchAnalyzer(currentFieldType));
} }
Query query = null; Query query = null;
if (currentFieldType.useTermQueryWithQueryString()) { if (currentFieldType.useTermQueryWithQueryString()) {
query = currentFieldType.regexpQuery(termStr, RegExp.ALL, maxDeterminizedStates, multiTermRewriteMethod, parseContext); query = currentFieldType.regexpQuery(termStr, RegExp.ALL, maxDeterminizedStates, multiTermRewriteMethod, context);
} }
if (query == null) { if (query == null) {
query = super.getRegexpQuery(field, termStr); query = super.getRegexpQuery(field, termStr);
@ -800,9 +754,9 @@ public class MapperQueryParser extends QueryParser {
} }
private void applyBoost(String field, Query q) { private void applyBoost(String field, Query q) {
if (settings.boosts() != null) { Float fieldBoost = settings.fieldsAndWeights().get(field);
float boost = settings.boosts().getOrDefault(field, 1f); if (fieldBoost != null) {
q.setBoost(boost); q.setBoost(fieldBoost);
} }
} }
@ -828,11 +782,11 @@ public class MapperQueryParser extends QueryParser {
} }
private Collection<String> extractMultiFields(String field) { private Collection<String> extractMultiFields(String field) {
Collection<String> fields = null; Collection<String> fields;
if (field != null) { if (field != null) {
fields = parseContext.simpleMatchToIndexNames(field); fields = context.simpleMatchToIndexNames(field);
} else { } else {
fields = settings.fields(); fields = settings.fieldsAndWeights().keySet();
} }
return fields; return fields;
} }

View File

@ -21,8 +21,8 @@ package org.apache.lucene.queryparser.classic;
import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.elasticsearch.index.query.MissingQueryParser; import org.elasticsearch.index.query.MissingQueryBuilder;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
/** /**
* *
@ -32,8 +32,11 @@ public class MissingFieldQueryExtension implements FieldQueryExtension {
public static final String NAME = "_missing_"; public static final String NAME = "_missing_";
@Override @Override
public Query query(QueryParseContext parseContext, String queryText) { public Query query(QueryShardContext context, String queryText) {
return new ConstantScoreQuery(MissingQueryParser.newFilter(parseContext, queryText, Query query = MissingQueryBuilder.newFilter(context, queryText, MissingQueryBuilder.DEFAULT_EXISTENCE_VALUE, MissingQueryBuilder.DEFAULT_NULL_VALUE);
MissingQueryParser.DEFAULT_EXISTENCE_VALUE, MissingQueryParser.DEFAULT_NULL_VALUE, null)); if (query != null) {
return new ConstantScoreQuery(query);
}
return null;
} }
} }

View File

@ -19,66 +19,74 @@
package org.apache.lucene.queryparser.classic; package org.apache.lucene.queryparser.classic;
import com.carrotsearch.hppc.ObjectFloatHashMap;
import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
/** /**
* * Encapsulates settings that affect query_string parsing via {@link MapperQueryParser}
*/ */
public class QueryParserSettings { public class QueryParserSettings {
public static final boolean DEFAULT_ALLOW_LEADING_WILDCARD = true; private final String queryString;
public static final boolean DEFAULT_ANALYZE_WILDCARD = false;
public static final float DEFAULT_BOOST = 1.f;
private String queryString;
private String defaultField; private String defaultField;
private float boost = DEFAULT_BOOST;
private MapperQueryParser.Operator defaultOperator = QueryParser.Operator.OR; private Map<String, Float> fieldsAndWeights;
private boolean autoGeneratePhraseQueries = false;
private boolean allowLeadingWildcard = DEFAULT_ALLOW_LEADING_WILDCARD; private QueryParser.Operator defaultOperator;
private boolean lowercaseExpandedTerms = true;
private boolean enablePositionIncrements = true; private Analyzer analyzer;
private int phraseSlop = 0; private boolean forceAnalyzer;
private Fuzziness fuzziness = Fuzziness.AUTO; private Analyzer quoteAnalyzer;
private int fuzzyPrefixLength = FuzzyQuery.defaultPrefixLength; private boolean forceQuoteAnalyzer;
private int fuzzyMaxExpansions = FuzzyQuery.defaultMaxExpansions;
private int maxDeterminizedStates = Operations.DEFAULT_MAX_DETERMINIZED_STATES; private String quoteFieldSuffix;
private MultiTermQuery.RewriteMethod fuzzyRewriteMethod = null;
private boolean analyzeWildcard = DEFAULT_ANALYZE_WILDCARD; private boolean autoGeneratePhraseQueries;
private boolean escape = false;
private Analyzer defaultAnalyzer = null; private boolean allowLeadingWildcard;
private Analyzer defaultQuoteAnalyzer = null;
private Analyzer forcedAnalyzer = null; private boolean analyzeWildcard;
private Analyzer forcedQuoteAnalyzer = null;
private String quoteFieldSuffix = null; private boolean lowercaseExpandedTerms;
private MultiTermQuery.RewriteMethod rewriteMethod = MultiTermQuery.CONSTANT_SCORE_FILTER_REWRITE;
private String minimumShouldMatch; private boolean enablePositionIncrements;
private boolean lenient;
private Locale locale; 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; private DateTimeZone timeZone;
List<String> fields = null; /** To limit effort spent determinizing regexp queries. */
ObjectFloatHashMap<String> boosts = null; private int maxDeterminizedStates;
float tieBreaker = 0.0f;
boolean useDisMax = true; public QueryParserSettings(String queryString) {
this.queryString = queryString;
}
public String queryString() { public String queryString() {
return queryString; return queryString;
} }
public void queryString(String queryString) {
this.queryString = queryString;
}
public String defaultField() { public String defaultField() {
return defaultField; return defaultField;
} }
@ -87,12 +95,12 @@ public class QueryParserSettings {
this.defaultField = defaultField; this.defaultField = defaultField;
} }
public float boost() { public Map<String, Float> fieldsAndWeights() {
return boost; return fieldsAndWeights;
} }
public void boost(float boost) { public void fieldsAndWeights(Map<String, Float> fieldsAndWeights) {
this.boost = boost; this.fieldsAndWeights = fieldsAndWeights;
} }
public QueryParser.Operator defaultOperator() { public QueryParser.Operator defaultOperator() {
@ -175,44 +183,40 @@ public class QueryParserSettings {
this.fuzzyRewriteMethod = fuzzyRewriteMethod; this.fuzzyRewriteMethod = fuzzyRewriteMethod;
} }
public boolean escape() { public void defaultAnalyzer(Analyzer analyzer) {
return escape; this.analyzer = analyzer;
this.forceAnalyzer = false;
} }
public void escape(boolean escape) { public void forceAnalyzer(Analyzer analyzer) {
this.escape = escape; this.analyzer = analyzer;
this.forceAnalyzer = true;
} }
public Analyzer defaultAnalyzer() { public Analyzer analyzer() {
return defaultAnalyzer; return analyzer;
} }
public void defaultAnalyzer(Analyzer defaultAnalyzer) { public boolean forceAnalyzer() {
this.defaultAnalyzer = defaultAnalyzer; return forceAnalyzer;
} }
public Analyzer defaultQuoteAnalyzer() { public void defaultQuoteAnalyzer(Analyzer quoteAnalyzer) {
return defaultQuoteAnalyzer; this.quoteAnalyzer = quoteAnalyzer;
this.forceQuoteAnalyzer = false;
} }
public void defaultQuoteAnalyzer(Analyzer defaultAnalyzer) { public void forceQuoteAnalyzer(Analyzer quoteAnalyzer) {
this.defaultQuoteAnalyzer = defaultAnalyzer; this.quoteAnalyzer = quoteAnalyzer;
this.forceQuoteAnalyzer = true;
} }
public Analyzer forcedAnalyzer() { public Analyzer quoteAnalyzer() {
return forcedAnalyzer; return quoteAnalyzer;
} }
public void forcedAnalyzer(Analyzer forcedAnalyzer) { public boolean forceQuoteAnalyzer() {
this.forcedAnalyzer = forcedAnalyzer; return forceQuoteAnalyzer;
}
public Analyzer forcedQuoteAnalyzer() {
return forcedQuoteAnalyzer;
}
public void forcedQuoteAnalyzer(Analyzer forcedAnalyzer) {
this.forcedQuoteAnalyzer = forcedAnalyzer;
} }
public boolean analyzeWildcard() { public boolean analyzeWildcard() {
@ -231,14 +235,6 @@ public class QueryParserSettings {
this.rewriteMethod = rewriteMethod; this.rewriteMethod = rewriteMethod;
} }
public String minimumShouldMatch() {
return this.minimumShouldMatch;
}
public void minimumShouldMatch(String minimumShouldMatch) {
this.minimumShouldMatch = minimumShouldMatch;
}
public void quoteFieldSuffix(String quoteFieldSuffix) { public void quoteFieldSuffix(String quoteFieldSuffix) {
this.quoteFieldSuffix = quoteFieldSuffix; this.quoteFieldSuffix = quoteFieldSuffix;
} }
@ -255,22 +251,6 @@ public class QueryParserSettings {
return this.lenient; 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() { public float tieBreaker() {
return tieBreaker; return tieBreaker;
} }
@ -303,97 +283,11 @@ public class QueryParserSettings {
return this.timeZone; return this.timeZone;
} }
@Override public void fuzziness(Fuzziness fuzziness) {
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) {
this.fuzziness = fuzziness; this.fuzziness = fuzziness;
} }
public Fuzziness getFuzziness() { public Fuzziness fuzziness() {
return fuzziness; return fuzziness;
} }
} }

View File

@ -463,153 +463,151 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
// change due to refactorings etc. like renaming we have to keep the ordinal <--> class mapping // change due to refactorings etc. like renaming we have to keep the ordinal <--> class mapping
// to deserialize the exception coming from another node or from an corruption marker on // to deserialize the exception coming from another node or from an corruption marker on
// a corrupted index. // a corrupted index.
// NOTE: ONLY APPEND TO THE END and NEVER REMOVE EXCEPTIONS IN MINOR VERSIONS
final Map<Class<? extends ElasticsearchException>, Integer> exceptions = new HashMap<>(); final Map<Class<? extends ElasticsearchException>, Integer> exceptions = new HashMap<>();
exceptions.put(org.elasticsearch.index.snapshots.IndexShardSnapshotFailedException.class, 0); exceptions.put(org.elasticsearch.index.snapshots.IndexShardSnapshotFailedException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.dfs.DfsPhaseExecutionException.class, 1); exceptions.put(org.elasticsearch.search.dfs.DfsPhaseExecutionException.class, exceptions.size());
exceptions.put(org.elasticsearch.common.util.CancellableThreads.ExecutionCancelledException.class, 2); exceptions.put(org.elasticsearch.common.util.CancellableThreads.ExecutionCancelledException.class, exceptions.size());
exceptions.put(org.elasticsearch.discovery.MasterNotDiscoveredException.class, 3); exceptions.put(org.elasticsearch.discovery.MasterNotDiscoveredException.class, exceptions.size());
exceptions.put(org.elasticsearch.ElasticsearchSecurityException.class, 4); exceptions.put(org.elasticsearch.ElasticsearchSecurityException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.snapshots.IndexShardRestoreException.class, 5); exceptions.put(org.elasticsearch.index.snapshots.IndexShardRestoreException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.IndexClosedException.class, 6); exceptions.put(org.elasticsearch.indices.IndexClosedException.class, exceptions.size());
exceptions.put(org.elasticsearch.http.BindHttpException.class, 7); exceptions.put(org.elasticsearch.http.BindHttpException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.search.ReduceSearchPhaseException.class, 8); exceptions.put(org.elasticsearch.action.search.ReduceSearchPhaseException.class, exceptions.size());
exceptions.put(org.elasticsearch.node.NodeClosedException.class, 9); exceptions.put(org.elasticsearch.node.NodeClosedException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.SnapshotFailedEngineException.class, 10); exceptions.put(org.elasticsearch.index.engine.SnapshotFailedEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.ShardNotFoundException.class, 11); exceptions.put(org.elasticsearch.index.shard.ShardNotFoundException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.ConnectTransportException.class, 12); exceptions.put(org.elasticsearch.transport.ConnectTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.NotSerializableTransportException.class, 13); exceptions.put(org.elasticsearch.transport.NotSerializableTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.ResponseHandlerFailureTransportException.class, 14); exceptions.put(org.elasticsearch.transport.ResponseHandlerFailureTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.IndexCreationException.class, 15); exceptions.put(org.elasticsearch.indices.IndexCreationException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.IndexNotFoundException.class, 16); exceptions.put(org.elasticsearch.index.IndexNotFoundException.class, exceptions.size());
exceptions.put(org.elasticsearch.cluster.routing.IllegalShardRoutingStateException.class, 17); exceptions.put(org.elasticsearch.cluster.routing.IllegalShardRoutingStateException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException.class, 18); exceptions.put(org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException.class, exceptions.size());
exceptions.put(org.elasticsearch.ResourceNotFoundException.class, 19); exceptions.put(org.elasticsearch.ResourceNotFoundException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.ActionTransportException.class, 20); exceptions.put(org.elasticsearch.transport.ActionTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.ElasticsearchGenerationException.class, 21); exceptions.put(org.elasticsearch.ElasticsearchGenerationException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.CreateFailedEngineException.class, 22); exceptions.put(org.elasticsearch.index.engine.CreateFailedEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.IndexShardStartedException.class, 23); exceptions.put(org.elasticsearch.index.shard.IndexShardStartedException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.SearchContextMissingException.class, 24); exceptions.put(org.elasticsearch.search.SearchContextMissingException.class, exceptions.size());
exceptions.put(org.elasticsearch.script.ScriptException.class, 25); exceptions.put(org.elasticsearch.script.ScriptException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.TranslogRecoveryPerformer.BatchOperationException.class, 26); exceptions.put(org.elasticsearch.index.shard.TranslogRecoveryPerformer.BatchOperationException.class, exceptions.size());
exceptions.put(org.elasticsearch.snapshots.SnapshotCreationException.class, 27); exceptions.put(org.elasticsearch.snapshots.SnapshotCreationException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.DeleteFailedEngineException.class, 28); exceptions.put(org.elasticsearch.index.engine.DeleteFailedEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.DocumentMissingException.class, 29); exceptions.put(org.elasticsearch.index.engine.DocumentMissingException.class, exceptions.size());
exceptions.put(org.elasticsearch.snapshots.SnapshotException.class, 30); exceptions.put(org.elasticsearch.snapshots.SnapshotException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.InvalidAliasNameException.class, 31); exceptions.put(org.elasticsearch.indices.InvalidAliasNameException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.InvalidIndexNameException.class, 32); exceptions.put(org.elasticsearch.indices.InvalidIndexNameException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.IndexPrimaryShardNotAllocatedException.class, 33); exceptions.put(org.elasticsearch.indices.IndexPrimaryShardNotAllocatedException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.TransportException.class, 34); exceptions.put(org.elasticsearch.transport.TransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.ElasticsearchParseException.class, 35); exceptions.put(org.elasticsearch.ElasticsearchParseException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.SearchException.class, 36); exceptions.put(org.elasticsearch.search.SearchException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.mapper.MapperException.class, 37); exceptions.put(org.elasticsearch.index.mapper.MapperException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.InvalidTypeNameException.class, 38); exceptions.put(org.elasticsearch.indices.InvalidTypeNameException.class, exceptions.size());
exceptions.put(org.elasticsearch.snapshots.SnapshotRestoreException.class, 39); exceptions.put(org.elasticsearch.snapshots.SnapshotRestoreException.class, exceptions.size());
exceptions.put(org.elasticsearch.common.ParsingException.class, 40); exceptions.put(org.elasticsearch.common.ParsingException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.IndexShardClosedException.class, 41); exceptions.put(org.elasticsearch.index.shard.IndexShardClosedException.class, exceptions.size());
exceptions.put(org.elasticsearch.script.expression.ExpressionScriptCompilationException.class, 42); exceptions.put(org.elasticsearch.indices.recovery.RecoverFilesRecoveryException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.recovery.RecoverFilesRecoveryException.class, 43); exceptions.put(org.elasticsearch.index.translog.TruncatedTranslogException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.translog.TruncatedTranslogException.class, 44); exceptions.put(org.elasticsearch.indices.recovery.RecoveryFailedException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.recovery.RecoveryFailedException.class, 45); exceptions.put(org.elasticsearch.index.shard.IndexShardRelocatedException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.IndexShardRelocatedException.class, 46); exceptions.put(org.elasticsearch.transport.NodeShouldNotConnectException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.NodeShouldNotConnectException.class, 47); exceptions.put(org.elasticsearch.indices.IndexTemplateAlreadyExistsException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.IndexTemplateAlreadyExistsException.class, 48); exceptions.put(org.elasticsearch.index.translog.TranslogCorruptedException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.translog.TranslogCorruptedException.class, 49); exceptions.put(org.elasticsearch.cluster.block.ClusterBlockException.class, exceptions.size());
exceptions.put(org.elasticsearch.cluster.block.ClusterBlockException.class, 50); exceptions.put(org.elasticsearch.search.fetch.FetchPhaseExecutionException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.fetch.FetchPhaseExecutionException.class, 51); exceptions.put(org.elasticsearch.index.IndexShardAlreadyExistsException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.IndexShardAlreadyExistsException.class, 52); exceptions.put(org.elasticsearch.index.engine.VersionConflictEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.VersionConflictEngineException.class, 53); exceptions.put(org.elasticsearch.index.engine.EngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.EngineException.class, 54); exceptions.put(org.elasticsearch.index.engine.DocumentAlreadyExistsException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.DocumentAlreadyExistsException.class, 55); exceptions.put(org.elasticsearch.action.NoSuchNodeException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.NoSuchNodeException.class, 56); exceptions.put(org.elasticsearch.common.settings.SettingsException.class, exceptions.size());
exceptions.put(org.elasticsearch.common.settings.SettingsException.class, 57); exceptions.put(org.elasticsearch.indices.IndexTemplateMissingException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.IndexTemplateMissingException.class, 58); exceptions.put(org.elasticsearch.transport.SendRequestTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.SendRequestTransportException.class, 59); exceptions.put(org.elasticsearch.common.util.concurrent.EsRejectedExecutionException.class, exceptions.size());
exceptions.put(org.elasticsearch.common.util.concurrent.EsRejectedExecutionException.class, 60); exceptions.put(org.elasticsearch.common.lucene.Lucene.EarlyTerminationException.class, exceptions.size());
exceptions.put(org.elasticsearch.common.lucene.Lucene.EarlyTerminationException.class, 61); exceptions.put(org.elasticsearch.cluster.routing.RoutingValidationException.class, exceptions.size());
exceptions.put(org.elasticsearch.cluster.routing.RoutingValidationException.class, 62); exceptions.put(org.elasticsearch.common.io.stream.NotSerializableExceptionWrapper.class, exceptions.size());
exceptions.put(org.elasticsearch.common.io.stream.NotSerializableExceptionWrapper.class, 63); exceptions.put(org.elasticsearch.indices.AliasFilterParsingException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.AliasFilterParsingException.class, 64); exceptions.put(org.elasticsearch.index.engine.DeleteByQueryFailedEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.DeleteByQueryFailedEngineException.class, 65); exceptions.put(org.elasticsearch.gateway.GatewayException.class, exceptions.size());
exceptions.put(org.elasticsearch.gateway.GatewayException.class, 66); exceptions.put(org.elasticsearch.index.shard.IndexShardNotRecoveringException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.IndexShardNotRecoveringException.class, 67); exceptions.put(org.elasticsearch.http.HttpException.class, exceptions.size());
exceptions.put(org.elasticsearch.http.HttpException.class, 68); exceptions.put(org.elasticsearch.ElasticsearchException.class, exceptions.size());
exceptions.put(org.elasticsearch.ElasticsearchException.class, 69); exceptions.put(org.elasticsearch.snapshots.SnapshotMissingException.class, exceptions.size());
exceptions.put(org.elasticsearch.snapshots.SnapshotMissingException.class, 70); exceptions.put(org.elasticsearch.action.PrimaryMissingActionException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.PrimaryMissingActionException.class, 71); exceptions.put(org.elasticsearch.action.FailedNodeException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.FailedNodeException.class, 72); exceptions.put(org.elasticsearch.search.SearchParseException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.SearchParseException.class, 73); exceptions.put(org.elasticsearch.snapshots.ConcurrentSnapshotExecutionException.class, exceptions.size());
exceptions.put(org.elasticsearch.snapshots.ConcurrentSnapshotExecutionException.class, 74); exceptions.put(org.elasticsearch.common.blobstore.BlobStoreException.class, exceptions.size());
exceptions.put(org.elasticsearch.common.blobstore.BlobStoreException.class, 75); exceptions.put(org.elasticsearch.cluster.IncompatibleClusterStateVersionException.class, exceptions.size());
exceptions.put(org.elasticsearch.cluster.IncompatibleClusterStateVersionException.class, 76); exceptions.put(org.elasticsearch.index.engine.RecoveryEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.RecoveryEngineException.class, 77); exceptions.put(org.elasticsearch.common.util.concurrent.UncategorizedExecutionException.class, exceptions.size());
exceptions.put(org.elasticsearch.common.util.concurrent.UncategorizedExecutionException.class, 78); exceptions.put(org.elasticsearch.action.TimestampParsingException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.TimestampParsingException.class, 79); exceptions.put(org.elasticsearch.action.RoutingMissingException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.RoutingMissingException.class, 80); exceptions.put(org.elasticsearch.index.engine.IndexFailedEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.IndexFailedEngineException.class, 81); exceptions.put(org.elasticsearch.index.snapshots.IndexShardRestoreFailedException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.snapshots.IndexShardRestoreFailedException.class, 82); exceptions.put(org.elasticsearch.repositories.RepositoryException.class, exceptions.size());
exceptions.put(org.elasticsearch.repositories.RepositoryException.class, 83); exceptions.put(org.elasticsearch.transport.ReceiveTimeoutTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.ReceiveTimeoutTransportException.class, 84); exceptions.put(org.elasticsearch.transport.NodeDisconnectedException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.NodeDisconnectedException.class, 85); exceptions.put(org.elasticsearch.index.AlreadyExpiredException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.AlreadyExpiredException.class, 86); exceptions.put(org.elasticsearch.search.aggregations.AggregationExecutionException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.aggregations.AggregationExecutionException.class, 87); exceptions.put(org.elasticsearch.index.mapper.MergeMappingException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.mapper.MergeMappingException.class, 88); exceptions.put(org.elasticsearch.indices.InvalidIndexTemplateException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.InvalidIndexTemplateException.class, 89); exceptions.put(org.elasticsearch.percolator.PercolateException.class, exceptions.size());
exceptions.put(org.elasticsearch.percolator.PercolateException.class, 90); exceptions.put(org.elasticsearch.index.engine.RefreshFailedEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.RefreshFailedEngineException.class, 91); exceptions.put(org.elasticsearch.search.aggregations.AggregationInitializationException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.aggregations.AggregationInitializationException.class, 92); exceptions.put(org.elasticsearch.indices.recovery.DelayRecoveryException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.recovery.DelayRecoveryException.class, 93); exceptions.put(org.elasticsearch.search.warmer.IndexWarmerMissingException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.warmer.IndexWarmerMissingException.class, 94); exceptions.put(org.elasticsearch.client.transport.NoNodeAvailableException.class, exceptions.size());
exceptions.put(org.elasticsearch.client.transport.NoNodeAvailableException.class, 95); exceptions.put(org.elasticsearch.script.groovy.GroovyScriptCompilationException.class, exceptions.size());
exceptions.put(org.elasticsearch.script.groovy.GroovyScriptCompilationException.class, 96); exceptions.put(org.elasticsearch.snapshots.InvalidSnapshotNameException.class, exceptions.size());
exceptions.put(org.elasticsearch.snapshots.InvalidSnapshotNameException.class, 97); exceptions.put(org.elasticsearch.index.shard.IllegalIndexShardStateException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.IllegalIndexShardStateException.class, 98); exceptions.put(org.elasticsearch.index.snapshots.IndexShardSnapshotException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.snapshots.IndexShardSnapshotException.class, 99); exceptions.put(org.elasticsearch.index.shard.IndexShardNotStartedException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.IndexShardNotStartedException.class, 100); exceptions.put(org.elasticsearch.action.search.SearchPhaseExecutionException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.search.SearchPhaseExecutionException.class, 101); exceptions.put(org.elasticsearch.transport.ActionNotFoundTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.ActionNotFoundTransportException.class, 102); exceptions.put(org.elasticsearch.transport.TransportSerializationException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.TransportSerializationException.class, 103); exceptions.put(org.elasticsearch.transport.RemoteTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.RemoteTransportException.class, 104); exceptions.put(org.elasticsearch.index.engine.EngineCreationFailureException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.EngineCreationFailureException.class, 105); exceptions.put(org.elasticsearch.cluster.routing.RoutingException.class, exceptions.size());
exceptions.put(org.elasticsearch.cluster.routing.RoutingException.class, 106); exceptions.put(org.elasticsearch.index.shard.IndexShardRecoveryException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.IndexShardRecoveryException.class, 107); exceptions.put(org.elasticsearch.repositories.RepositoryMissingException.class, exceptions.size());
exceptions.put(org.elasticsearch.repositories.RepositoryMissingException.class, 108); exceptions.put(org.elasticsearch.index.percolator.PercolatorException.class, exceptions.size());
exceptions.put(org.elasticsearch.script.expression.ExpressionScriptExecutionException.class, 109); exceptions.put(org.elasticsearch.index.engine.DocumentSourceMissingException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.percolator.PercolatorException.class, 110); exceptions.put(org.elasticsearch.index.engine.FlushNotAllowedEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.DocumentSourceMissingException.class, 111); exceptions.put(org.elasticsearch.common.settings.NoClassSettingsException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.FlushNotAllowedEngineException.class, 112); exceptions.put(org.elasticsearch.transport.BindTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.common.settings.NoClassSettingsException.class, 113); exceptions.put(org.elasticsearch.rest.action.admin.indices.alias.delete.AliasesNotFoundException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.BindTransportException.class, 114); exceptions.put(org.elasticsearch.index.shard.IndexShardRecoveringException.class, exceptions.size());
exceptions.put(org.elasticsearch.rest.action.admin.indices.alias.delete.AliasesNotFoundException.class, 115); exceptions.put(org.elasticsearch.index.translog.TranslogException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.shard.IndexShardRecoveringException.class, 116); exceptions.put(org.elasticsearch.cluster.metadata.ProcessClusterEventTimeoutException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.translog.TranslogException.class, 117); exceptions.put(org.elasticsearch.action.support.replication.TransportReplicationAction.RetryOnPrimaryException.class, exceptions.size());
exceptions.put(org.elasticsearch.cluster.metadata.ProcessClusterEventTimeoutException.class, 118); exceptions.put(org.elasticsearch.ElasticsearchTimeoutException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.support.replication.TransportReplicationAction.RetryOnPrimaryException.class, 119); exceptions.put(org.elasticsearch.search.query.QueryPhaseExecutionException.class, exceptions.size());
exceptions.put(org.elasticsearch.ElasticsearchTimeoutException.class, 120); exceptions.put(org.elasticsearch.repositories.RepositoryVerificationException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.query.QueryPhaseExecutionException.class, 121); exceptions.put(org.elasticsearch.search.aggregations.InvalidAggregationPathException.class, exceptions.size());
exceptions.put(org.elasticsearch.repositories.RepositoryVerificationException.class, 122); exceptions.put(org.elasticsearch.script.groovy.GroovyScriptExecutionException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.aggregations.InvalidAggregationPathException.class, 123); exceptions.put(org.elasticsearch.indices.IndexAlreadyExistsException.class, exceptions.size());
exceptions.put(org.elasticsearch.script.groovy.GroovyScriptExecutionException.class, 124); exceptions.put(org.elasticsearch.script.Script.ScriptParseException.class, exceptions.size());
exceptions.put(org.elasticsearch.indices.IndexAlreadyExistsException.class, 125); exceptions.put(org.elasticsearch.transport.netty.SizeHeaderFrameDecoder.HttpOnTransportException.class, exceptions.size());
exceptions.put(org.elasticsearch.script.Script.ScriptParseException.class, 126); exceptions.put(org.elasticsearch.index.mapper.MapperParsingException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.netty.SizeHeaderFrameDecoder.HttpOnTransportException.class, 127); exceptions.put(org.elasticsearch.search.SearchContextException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.mapper.MapperParsingException.class, 128); exceptions.put(org.elasticsearch.search.builder.SearchSourceBuilderException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.SearchContextException.class, 129); exceptions.put(org.elasticsearch.index.engine.EngineClosedException.class, exceptions.size());
exceptions.put(org.elasticsearch.search.builder.SearchSourceBuilderException.class, 130); exceptions.put(org.elasticsearch.action.NoShardAvailableActionException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.EngineClosedException.class, 131); exceptions.put(org.elasticsearch.action.UnavailableShardsException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.NoShardAvailableActionException.class, 132); exceptions.put(org.elasticsearch.index.engine.FlushFailedEngineException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.UnavailableShardsException.class, 133); exceptions.put(org.elasticsearch.common.breaker.CircuitBreakingException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.engine.FlushFailedEngineException.class, 134); exceptions.put(org.elasticsearch.transport.NodeNotConnectedException.class, exceptions.size());
exceptions.put(org.elasticsearch.common.breaker.CircuitBreakingException.class, 135); exceptions.put(org.elasticsearch.index.mapper.StrictDynamicMappingException.class, exceptions.size());
exceptions.put(org.elasticsearch.transport.NodeNotConnectedException.class, 136); exceptions.put(org.elasticsearch.action.support.replication.TransportReplicationAction.RetryOnReplicaException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.mapper.StrictDynamicMappingException.class, 137); exceptions.put(org.elasticsearch.indices.TypeMissingException.class, exceptions.size());
exceptions.put(org.elasticsearch.action.support.replication.TransportReplicationAction.RetryOnReplicaException.class, 138);
exceptions.put(org.elasticsearch.indices.TypeMissingException.class, 139);
// added in 3.x // added in 3.x
exceptions.put(org.elasticsearch.discovery.Discovery.FailedToCommitClusterStateException.class, 140); exceptions.put(org.elasticsearch.discovery.Discovery.FailedToCommitClusterStateException.class, exceptions.size());
exceptions.put(org.elasticsearch.index.query.QueryShardException.class, exceptions.size());
final int maxOrd = 140; // NOTE: ONLY APPEND TO THE END and NEVER REMOVE EXCEPTIONS IN MINOR VERSIONS
assert exceptions.size() == maxOrd + 1; Constructor<? extends ElasticsearchException>[] idToSupplier = new Constructor[exceptions.size()];
Constructor<? extends ElasticsearchException>[] idToSupplier = new Constructor[maxOrd + 1];
for (Map.Entry<Class<? extends ElasticsearchException>, Integer> e : exceptions.entrySet()) { for (Map.Entry<Class<? extends ElasticsearchException>, Integer> e : exceptions.entrySet()) {
try { try {
Constructor<? extends ElasticsearchException> constructor = e.getKey().getDeclaredConstructor(StreamInput.class); Constructor<? extends ElasticsearchException> constructor = e.getKey().getDeclaredConstructor(StreamInput.class);

View File

@ -73,7 +73,8 @@ public class TransportCreateIndexAction extends TransportMasterNodeAction<Create
cause = "api"; cause = "api";
} }
final CreateIndexClusterStateUpdateRequest updateRequest = new CreateIndexClusterStateUpdateRequest(request, cause, request.index(), request.updateAllTypes()) final String indexName = indexNameExpressionResolver.resolveDateMathExpression(request.index());
final CreateIndexClusterStateUpdateRequest updateRequest = new CreateIndexClusterStateUpdateRequest(request, cause, indexName, request.updateAllTypes())
.ackTimeout(request.timeout()).masterNodeTimeout(request.masterNodeTimeout()) .ackTimeout(request.timeout()).masterNodeTimeout(request.masterNodeTimeout())
.settings(request.settings()).mappings(request.mappings()) .settings(request.settings()).mappings(request.mappings())
.aliases(request.aliases()).customs(request.customs()); .aliases(request.aliases()).customs(request.customs());

View File

@ -42,6 +42,7 @@ import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.index.IndexService; import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.engine.Engine;
import org.elasticsearch.index.query.IndexQueryParserService; import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShard;
import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.IndicesService;
@ -188,8 +189,8 @@ public class TransportValidateQueryAction extends TransportBroadcastAction<Valid
} }
if (request.rewrite()) { if (request.rewrite()) {
explanation = getRewrittenQuery(searcher.searcher(), searchContext.query()); explanation = getRewrittenQuery(searcher.searcher(), searchContext.query());
} }
} catch (ParsingException e) { } catch (QueryShardException|ParsingException e) {
valid = false; valid = false;
error = e.getDetailedMessage(); error = e.getDetailedMessage();
} catch (AssertionError|IOException e) { } catch (AssertionError|IOException e) {

View File

@ -19,7 +19,6 @@
package org.elasticsearch.action.admin.indices.validate.query; package org.elasticsearch.action.admin.indices.validate.query;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.QuerySourceBuilder; import org.elasticsearch.action.support.QuerySourceBuilder;
import org.elasticsearch.action.support.broadcast.BroadcastOperationRequestBuilder; import org.elasticsearch.action.support.broadcast.BroadcastOperationRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.client.ElasticsearchClient;

View File

@ -41,7 +41,7 @@ import org.elasticsearch.common.lucene.Lucene;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.index.IndexService; import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShard;
import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptService;
@ -166,10 +166,10 @@ public class TransportExistsAction extends TransportBroadcastAction<ExistsReques
BytesReference source = request.querySource(); BytesReference source = request.querySource();
if (source != null && source.length() > 0) { if (source != null && source.length() > 0) {
try { try {
QueryParseContext.setTypes(request.types()); QueryShardContext.setTypes(request.types());
context.parsedQuery(indexService.queryParserService().parseQuery(source)); context.parsedQuery(indexService.queryParserService().parseQuery(source));
} finally { } finally {
QueryParseContext.removeTypes(); QueryShardContext.removeTypes();
} }
} }
context.preProcess(); context.preProcess();

View File

@ -715,8 +715,13 @@ public class SearchRequestBuilder extends ActionRequestBuilder<SearchRequest, Se
return this; return this;
} }
public SearchRequestBuilder addInnerHit(String name, InnerHitsBuilder.InnerHit innerHit) { public SearchRequestBuilder addParentChildInnerHits(String name, String type, InnerHitsBuilder.InnerHit innerHit) {
innerHitsBuilder().addInnerHit(name, innerHit); innerHitsBuilder().addParentChildInnerHits(name, type, innerHit);
return this;
}
public SearchRequestBuilder addNestedInnerHits(String name, String path, InnerHitsBuilder.InnerHit innerHit) {
innerHitsBuilder().addNestedInnerHits(name, path, innerHit);
return this; return this;
} }

View File

@ -20,7 +20,6 @@
package org.elasticsearch.action.termvectors; package org.elasticsearch.action.termvectors;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.TransportActions; import org.elasticsearch.action.support.TransportActions;
import org.elasticsearch.action.support.single.shard.TransportSingleShardAction; import org.elasticsearch.action.support.single.shard.TransportSingleShardAction;
@ -81,7 +80,7 @@ public class TransportShardMultiTermsVectorAction extends TransportSingleShardAc
try { try {
IndexService indexService = indicesService.indexServiceSafe(request.index()); IndexService indexService = indicesService.indexServiceSafe(request.index());
IndexShard indexShard = indexService.shardSafe(shardId.id()); IndexShard indexShard = indexService.shardSafe(shardId.id());
TermVectorsResponse termVectorsResponse = indexShard.termVectorsService().getTermVectors(termVectorsRequest, shardId.getIndex()); TermVectorsResponse termVectorsResponse = indexShard.getTermVectors(termVectorsRequest);
termVectorsResponse.updateTookInMillis(termVectorsRequest.startTime()); termVectorsResponse.updateTookInMillis(termVectorsRequest.startTime());
response.add(request.locations.get(i), termVectorsResponse); response.add(request.locations.get(i), termVectorsResponse);
} catch (Throwable t) { } catch (Throwable t) {

View File

@ -83,7 +83,7 @@ public class TransportTermVectorsAction extends TransportSingleShardAction<TermV
protected TermVectorsResponse shardOperation(TermVectorsRequest request, ShardId shardId) { protected TermVectorsResponse shardOperation(TermVectorsRequest request, ShardId shardId) {
IndexService indexService = indicesService.indexServiceSafe(shardId.getIndex()); IndexService indexService = indicesService.indexServiceSafe(shardId.getIndex());
IndexShard indexShard = indexService.shardSafe(shardId.id()); IndexShard indexShard = indexService.shardSafe(shardId.id());
TermVectorsResponse response = indexShard.termVectorsService().getTermVectors(request, shardId.getIndex()); TermVectorsResponse response = indexShard.getTermVectors(request);
response.updateTookInMillis(request.startTime()); response.updateTookInMillis(request.startTime());
return response; return response;
} }

View File

@ -79,7 +79,7 @@ final class Bootstrap {
} }
/** initialize native resources */ /** initialize native resources */
public static void initializeNatives(boolean mlockAll, boolean ctrlHandler) { public static void initializeNatives(boolean mlockAll, boolean seccomp, boolean ctrlHandler) {
final ESLogger logger = Loggers.getLogger(Bootstrap.class); final ESLogger logger = Loggers.getLogger(Bootstrap.class);
// check if the user is running as root, and bail // check if the user is running as root, and bail
@ -91,6 +91,11 @@ final class Bootstrap {
} }
} }
// enable secure computing mode
if (seccomp) {
Natives.trySeccomp();
}
// mlockall if requested // mlockall if requested
if (mlockAll) { if (mlockAll) {
if (Constants.WINDOWS) { if (Constants.WINDOWS) {
@ -134,7 +139,8 @@ final class Bootstrap {
private void setup(boolean addShutdownHook, Settings settings, Environment environment) throws Exception { private void setup(boolean addShutdownHook, Settings settings, Environment environment) throws Exception {
initializeNatives(settings.getAsBoolean("bootstrap.mlockall", false), initializeNatives(settings.getAsBoolean("bootstrap.mlockall", false),
settings.getAsBoolean("bootstrap.ctrlhandler", true)); settings.getAsBoolean("bootstrap.seccomp", true),
settings.getAsBoolean("bootstrap.ctrlhandler", true));
// initialize probes before the security manager is installed // initialize probes before the security manager is installed
initializeProbes(); initializeProbes();

View File

@ -43,4 +43,11 @@ public final class BootstrapInfo {
public static boolean isMemoryLocked() { public static boolean isMemoryLocked() {
return Natives.isMemoryLocked(); return Natives.isMemoryLocked();
} }
/**
* Returns true if secure computing mode is enabled (linux/amd64 only)
*/
public static boolean isSeccompInstalled() {
return Natives.isSeccompInstalled();
}
} }

View File

@ -41,6 +41,8 @@ class JNANatives {
// Set to true, in case native mlockall call was successful // Set to true, in case native mlockall call was successful
static boolean LOCAL_MLOCKALL = false; static boolean LOCAL_MLOCKALL = false;
// Set to true, in case native seccomp call was successful
static boolean LOCAL_SECCOMP = false;
static void tryMlockall() { static void tryMlockall() {
int errno = Integer.MIN_VALUE; int errno = Integer.MIN_VALUE;
@ -170,4 +172,19 @@ class JNANatives {
} }
} }
static void trySeccomp() {
if (Constants.LINUX && "amd64".equals(Constants.OS_ARCH)) {
try {
Seccomp.installFilter();
LOCAL_SECCOMP = true;
} catch (Exception e) {
// this is likely to happen unless the kernel is newish, its a best effort at the moment
// so we log stacktrace at debug for now...
if (logger.isDebugEnabled()) {
logger.debug("unable to install seccomp filter", e);
}
logger.warn("unable to install seccomp filter: " + e.getMessage());
}
}
}
} }

View File

@ -88,4 +88,19 @@ final class Natives {
} }
return JNANatives.LOCAL_MLOCKALL; return JNANatives.LOCAL_MLOCKALL;
} }
static void trySeccomp() {
if (!JNA_AVAILABLE) {
logger.warn("cannot install seccomp filters because JNA is not available");
return;
}
JNANatives.trySeccomp();
}
static boolean isSeccompInstalled() {
if (!JNA_AVAILABLE) {
return false;
}
return JNANatives.LOCAL_SECCOMP;
}
} }

View File

@ -0,0 +1,271 @@
/*
* 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.bootstrap;
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import org.apache.lucene.util.Constants;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.List;
/**
* Installs a limited form of Linux secure computing mode (filter mode).
* This filters system calls to block process execution.
* <p>
* This is only supported on the amd64 architecture, on Linux kernels 3.5 or above, and requires
* {@code CONFIG_SECCOMP} and {@code CONFIG_SECCOMP_FILTER} compiled into the kernel.
* <p>
* Filters are installed using either {@code seccomp(2)} (3.17+) or {@code prctl(2)} (3.5+). {@code seccomp(2)}
* is preferred, as it allows filters to be applied to any existing threads in the process, and one motivation
* here is to protect against bugs in the JVM. Otherwise, code will fall back to the {@code prctl(2)} method
* which will at least protect elasticsearch application threads.
* <p>
* The filters will return {@code EACCES} (Access Denied) for the following system calls:
* <ul>
* <li>{@code execve}</li>
* <li>{@code fork}</li>
* <li>{@code vfork}</li>
* </ul>
* <p>
* This is not intended as a sandbox. It is another level of security, mostly intended to annoy
* security researchers and make their lives more difficult in achieving "remote execution" exploits.
* @see <a href="http://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt">
* http://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt</a>
*/
// only supported on linux/amd64
// not an example of how to write code!!!
final class Seccomp {
private static final ESLogger logger = Loggers.getLogger(Seccomp.class);
/** we use an explicit interface for native methods, for varargs support */
static interface LinuxLibrary extends Library {
/**
* maps to prctl(2)
*/
int prctl(int option, long arg2, long arg3, long arg4, long arg5);
/**
* used to call seccomp(2), its too new...
* this is the only way, DONT use it on some other architecture unless you know wtf you are doing
*/
long syscall(long number, Object... args);
};
// null if something goes wrong.
static final LinuxLibrary libc;
static {
LinuxLibrary lib = null;
try {
lib = (LinuxLibrary) Native.loadLibrary("c", LinuxLibrary.class);
} catch (UnsatisfiedLinkError e) {
logger.warn("unable to link C library. native methods (seccomp) will be disabled.", e);
}
libc = lib;
}
/** the preferred method is seccomp(2), since we can apply to all threads of the process */
static final int SECCOMP_SYSCALL_NR = 317; // since Linux 3.17
static final int SECCOMP_SET_MODE_FILTER = 1; // since Linux 3.17
static final int SECCOMP_FILTER_FLAG_TSYNC = 1; // since Linux 3.17
/** otherwise, we can use prctl(2), which will at least protect ES application threads */
static final int PR_GET_NO_NEW_PRIVS = 39; // since Linux 3.5
static final int PR_SET_NO_NEW_PRIVS = 38; // since Linux 3.5
static final int PR_GET_SECCOMP = 21; // since Linux 2.6.23
static final int PR_SET_SECCOMP = 22; // since Linux 2.6.23
static final int SECCOMP_MODE_FILTER = 2; // since Linux Linux 3.5
/** corresponds to struct sock_filter */
static final class SockFilter {
short code; // insn
byte jt; // number of insn to jump (skip) if true
byte jf; // number of insn to jump (skip) if false
int k; // additional data
SockFilter(short code, byte jt, byte jf, int k) {
this.code = code;
this.jt = jt;
this.jf = jf;
this.k = k;
}
}
/** corresponds to struct sock_fprog */
public static final class SockFProg extends Structure implements Structure.ByReference {
public short len; // number of filters
public Pointer filter; // filters
public SockFProg(SockFilter filters[]) {
len = (short) filters.length;
// serialize struct sock_filter * explicitly, its less confusing than the JNA magic we would need
Memory filter = new Memory(len * 8);
ByteBuffer bbuf = filter.getByteBuffer(0, len * 8);
bbuf.order(ByteOrder.nativeOrder()); // little endian
for (SockFilter f : filters) {
bbuf.putShort(f.code);
bbuf.put(f.jt);
bbuf.put(f.jf);
bbuf.putInt(f.k);
}
this.filter = filter;
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList(new String[] { "len", "filter" });
}
}
// BPF "macros" and constants
static final int BPF_LD = 0x00;
static final int BPF_W = 0x00;
static final int BPF_ABS = 0x20;
static final int BPF_JMP = 0x05;
static final int BPF_JEQ = 0x10;
static final int BPF_JGE = 0x30;
static final int BPF_JGT = 0x20;
static final int BPF_RET = 0x06;
static final int BPF_K = 0x00;
static SockFilter BPF_STMT(int code, int k) {
return new SockFilter((short) code, (byte) 0, (byte) 0, k);
}
static SockFilter BPF_JUMP(int code, int k, int jt, int jf) {
return new SockFilter((short) code, (byte) jt, (byte) jf, k);
}
static final int AUDIT_ARCH_X86_64 = 0xC000003E;
static final int SECCOMP_RET_ERRNO = 0x00050000;
static final int SECCOMP_RET_DATA = 0x0000FFFF;
static final int SECCOMP_RET_ALLOW = 0x7FFF0000;
// some errno constants for error checking/handling
static final int EACCES = 0x0D;
static final int EFAULT = 0x0E;
static final int EINVAL = 0x16;
static final int ENOSYS = 0x26;
// offsets (arch dependent) that our BPF checks
static final int SECCOMP_DATA_NR_OFFSET = 0x00;
static final int SECCOMP_DATA_ARCH_OFFSET = 0x04;
// currently this range is blocked (inclusive):
// execve is really the only one needed but why let someone fork a 30G heap? (not really what happens)
// ...
// 57: fork
// 58: vfork
// 59: execve
// ...
static final int BLACKLIST_START = 57;
static final int BLACKLIST_END = 59;
// TODO: execveat()? its less of a risk since the jvm does not use it...
/** try to install our filters */
static void installFilter() {
// first be defensive: we can give nice errors this way, at the very least.
// also, some of these security features get backported to old versions, checking kernel version here is a big no-no!
boolean supported = Constants.LINUX && "amd64".equals(Constants.OS_ARCH);
if (supported == false) {
throw new IllegalStateException("bug: should not be trying to initialize seccomp for an unsupported architecture");
}
// we couldn't link methods, could be some really ancient kernel (e.g. < 2.1.57) or some bug
if (libc == null) {
throw new UnsupportedOperationException("seccomp unavailable: could not link methods. requires kernel 3.5+ with CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER compiled in");
}
// check for kernel version
if (libc.prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) < 0) {
int errno = Native.getLastError();
switch (errno) {
case ENOSYS: throw new UnsupportedOperationException("seccomp unavailable: requires kernel 3.5+ with CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER compiled in");
default: throw new UnsupportedOperationException("prctl(PR_GET_NO_NEW_PRIVS): " + JNACLibrary.strerror(errno));
}
}
// check for SECCOMP
if (libc.prctl(PR_GET_SECCOMP, 0, 0, 0, 0) < 0) {
int errno = Native.getLastError();
switch (errno) {
case EINVAL: throw new UnsupportedOperationException("seccomp unavailable: CONFIG_SECCOMP not compiled into kernel, CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER are needed");
default: throw new UnsupportedOperationException("prctl(PR_GET_SECCOMP): " + JNACLibrary.strerror(errno));
}
}
// check for SECCOMP_MODE_FILTER
if (libc.prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, 0, 0, 0) < 0) {
int errno = Native.getLastError();
switch (errno) {
case EFAULT: break; // available
case EINVAL: throw new UnsupportedOperationException("seccomp unavailable: CONFIG_SECCOMP_FILTER not compiled into kernel, CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER are needed");
default: throw new UnsupportedOperationException("prctl(PR_SET_SECCOMP): " + JNACLibrary.strerror(errno));
}
}
// ok, now set PR_SET_NO_NEW_PRIVS, needed to be able to set a seccomp filter as ordinary user
if (libc.prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
throw new UnsupportedOperationException("prctl(PR_SET_NO_NEW_PRIVS): " + JNACLibrary.strerror(Native.getLastError()));
}
// BPF installed to check arch, then syscall range. See https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt for details.
SockFilter insns[] = {
/* 1 */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, SECCOMP_DATA_ARCH_OFFSET), // if (arch != amd64) goto fail;
/* 2 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, AUDIT_ARCH_X86_64, 0, 3), //
/* 3 */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, SECCOMP_DATA_NR_OFFSET), // if (syscall < BLACKLIST_START) goto pass;
/* 4 */ BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, BLACKLIST_START, 0, 2), //
/* 5 */ BPF_JUMP(BPF_JMP + BPF_JGT + BPF_K, BLACKLIST_END, 1, 0), // if (syscall > BLACKLIST_END) goto pass;
/* 6 */ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (EACCES & SECCOMP_RET_DATA)), // fail: return EACCES;
/* 7 */ BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) // pass: return OK;
};
// seccomp takes a long, so we pass it one explicitly to keep the JNA simple
SockFProg prog = new SockFProg(insns);
prog.write();
long pointer = Pointer.nativeValue(prog.getPointer());
// install filter, if this works, after this there is no going back!
// first try it with seccomp(SECCOMP_SET_MODE_FILTER), falling back to prctl()
if (libc.syscall(SECCOMP_SYSCALL_NR, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, pointer) != 0) {
int errno1 = Native.getLastError();
if (logger.isDebugEnabled()) {
logger.debug("seccomp(SECCOMP_SET_MODE_FILTER): " + JNACLibrary.strerror(errno1) + ", falling back to prctl(PR_SET_SECCOMP)...");
}
if (libc.prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, pointer, 0, 0) < 0) {
int errno2 = Native.getLastError();
throw new UnsupportedOperationException("seccomp(SECCOMP_SET_MODE_FILTER): " + JNACLibrary.strerror(errno1) +
", prctl(PR_SET_SECCOMP): " + JNACLibrary.strerror(errno2));
}
}
// now check that the filter was really installed, we should be in filter mode.
if (libc.prctl(PR_GET_SECCOMP, 0, 0, 0, 0) != 2) {
throw new UnsupportedOperationException("seccomp filter installation did not really succeed. seccomp(PR_GET_SECCOMP): " + JNACLibrary.strerror(Native.getLastError()));
}
}
}

View File

@ -28,7 +28,7 @@ import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import org.elasticsearch.index.query.IndexQueryParserService; import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.indices.InvalidAliasNameException; import org.elasticsearch.indices.InvalidAliasNameException;
import java.io.IOException; import java.io.IOException;
@ -142,10 +142,10 @@ public class AliasValidator extends AbstractComponent {
} }
private void validateAliasFilter(XContentParser parser, IndexQueryParserService indexQueryParserService) throws IOException { private void validateAliasFilter(XContentParser parser, IndexQueryParserService indexQueryParserService) throws IOException {
QueryParseContext context = indexQueryParserService.getParseContext(); QueryShardContext context = indexQueryParserService.getShardContext();
try { try {
context.reset(parser); context.reset(parser);
context.parseInnerFilter(); context.parseContext().parseInnerQueryBuilder().toFilter(context);
} finally { } finally {
context.reset(null); context.reset(null);
parser.close(); parser.close();

View File

@ -219,6 +219,15 @@ public class IndexNameExpressionResolver extends AbstractComponent {
return state.metaData().getAliasAndIndexLookup().containsKey(resolvedAliasOrIndex); return state.metaData().getAliasAndIndexLookup().containsKey(resolvedAliasOrIndex);
} }
/**
* @return If the specified string is data math expression then this method returns the resolved expression.
*/
public String resolveDateMathExpression(String dateExpression) {
// The data math expression resolver doesn't rely on cluster state or indices options, because
// it just resolves the date math to an actual date.
return dateMathExpressionResolver.resolveExpression(dateExpression, new Context(null, null));
}
/** /**
* Iterates through the list of indices and selects the effective list of filtering aliases for the * Iterates through the list of indices and selects the effective list of filtering aliases for the
* given index. * given index.

View File

@ -171,4 +171,11 @@ public final class Numbers {
return longToBytes(Double.doubleToRawLongBits(val)); return longToBytes(Double.doubleToRawLongBits(val));
} }
/** Returns true if value is neither NaN nor infinite. */
public static boolean isValidDouble(double value) {
if (Double.isNaN(value) || Double.isInfinite(value)) {
return false;
}
return true;
}
} }

View File

@ -24,15 +24,14 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentLocation; import org.elasticsearch.common.xcontent.XContentLocation;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import java.io.IOException; import java.io.IOException;
/** /**
* * Exception that can be used when parsing queries with a given {@link QueryParseContext}.
* Can contain information about location of the error.
*/ */
public class ParsingException extends ElasticsearchException { public class ParsingException extends ElasticsearchException {
@ -40,25 +39,17 @@ public class ParsingException extends ElasticsearchException {
private final int lineNumber; private final int lineNumber;
private final int columnNumber; private final int columnNumber;
public ParsingException(QueryParseContext parseContext, String msg, Object... args) { public ParsingException(XContentLocation contentLocation, String msg, Object... args) {
this(parseContext, msg, null, args); this(contentLocation, msg, null, args);
} }
public ParsingException(QueryParseContext parseContext, String msg, Throwable cause, Object... args) { public ParsingException(XContentLocation contentLocation, String msg, Throwable cause, Object... args) {
this(parseContext.index(), parseContext.parser(), msg, cause, args);
}
public ParsingException(Index index, XContentParser parser, String msg, Throwable cause, Object... args) {
super(msg, cause, args); super(msg, cause, args);
setIndex(index);
int lineNumber = UNKNOWN_POSITION; int lineNumber = UNKNOWN_POSITION;
int columnNumber = UNKNOWN_POSITION; int columnNumber = UNKNOWN_POSITION;
if (parser != null) { if (contentLocation != null) {
XContentLocation location = parser.getTokenLocation(); lineNumber = contentLocation.lineNumber;
if (location != null) { columnNumber = contentLocation.columnNumber;
lineNumber = location.lineNumber;
columnNumber = location.columnNumber;
}
} }
this.columnNumber = columnNumber; this.columnNumber = columnNumber;
this.lineNumber = lineNumber; this.lineNumber = lineNumber;
@ -68,16 +59,21 @@ public class ParsingException extends ElasticsearchException {
* This constructor is provided for use in unit tests where a * This constructor is provided for use in unit tests where a
* {@link QueryParseContext} may not be available * {@link QueryParseContext} may not be available
*/ */
public ParsingException(Index index, int line, int col, String msg, Throwable cause) { public ParsingException(int line, int col, String msg, Throwable cause) {
super(msg, cause); super(msg, cause);
setIndex(index);
this.lineNumber = line; this.lineNumber = line;
this.columnNumber = col; this.columnNumber = col;
} }
public ParsingException(StreamInput in) throws IOException{
super(in);
lineNumber = in.readInt();
columnNumber = in.readInt();
}
/** /**
* Line number of the location of the error * Line number of the location of the error
* *
* @return the line number or -1 if unknown * @return the line number or -1 if unknown
*/ */
public int getLineNumber() { public int getLineNumber() {
@ -86,7 +82,7 @@ public class ParsingException extends ElasticsearchException {
/** /**
* Column number of the location of the error * Column number of the location of the error
* *
* @return the column number or -1 if unknown * @return the column number or -1 if unknown
*/ */
public int getColumnNumber() { public int getColumnNumber() {
@ -113,11 +109,4 @@ public class ParsingException extends ElasticsearchException {
out.writeInt(lineNumber); out.writeInt(lineNumber);
out.writeInt(columnNumber); out.writeInt(columnNumber);
} }
public ParsingException(StreamInput in) throws IOException{
super(in);
lineNumber = in.readInt();
columnNumber = in.readInt();
}
} }

View File

@ -570,7 +570,6 @@ public class Strings {
count++; count++;
} }
} }
// TODO (MvG): No push: hppc or jcf?
final Set<String> result = new HashSet<>(count); final Set<String> result = new HashSet<>(count);
final int len = chars.length; final int len = chars.length;
int start = 0; // starting index in chars of the current substring. int start = 0; // starting index in chars of the current substring.

View File

@ -21,6 +21,9 @@ package org.elasticsearch.common.geo;
import org.apache.lucene.util.Bits; import org.apache.lucene.util.Bits;
import org.apache.lucene.util.SloppyMath; import org.apache.lucene.util.SloppyMath;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.fielddata.FieldData; import org.elasticsearch.index.fielddata.FieldData;
import org.elasticsearch.index.fielddata.GeoPointValues; import org.elasticsearch.index.fielddata.GeoPointValues;
@ -28,17 +31,17 @@ import org.elasticsearch.index.fielddata.MultiGeoPointValues;
import org.elasticsearch.index.fielddata.NumericDoubleValues; import org.elasticsearch.index.fielddata.NumericDoubleValues;
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
import org.elasticsearch.index.fielddata.SortingNumericDoubleValues; import org.elasticsearch.index.fielddata.SortingNumericDoubleValues;
import java.io.IOException;
import java.util.Locale; import java.util.Locale;
/** /**
* Geo distance calculation. * Geo distance calculation.
*/ */
public enum GeoDistance { public enum GeoDistance implements Writeable<GeoDistance> {
/** /**
* Calculates distance as points on a plane. Faster, but less accurate than {@link #ARC}. * Calculates distance as points on a plane. Faster, but less accurate than {@link #ARC}.
*/ */
PLANE() { PLANE {
@Override @Override
public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) { public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) {
double px = targetLongitude - sourceLongitude; double px = targetLongitude - sourceLongitude;
@ -60,7 +63,7 @@ public enum GeoDistance {
/** /**
* Calculates distance factor. * Calculates distance factor.
*/ */
FACTOR() { FACTOR {
@Override @Override
public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) { public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) {
double longitudeDifference = targetLongitude - sourceLongitude; double longitudeDifference = targetLongitude - sourceLongitude;
@ -82,7 +85,7 @@ public enum GeoDistance {
/** /**
* Calculates distance as points on a globe. * Calculates distance as points on a globe.
*/ */
ARC() { ARC {
@Override @Override
public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) { public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) {
double x1 = sourceLatitude * Math.PI / 180D; double x1 = sourceLatitude * Math.PI / 180D;
@ -109,7 +112,7 @@ public enum GeoDistance {
* Calculates distance as points on a globe in a sloppy way. Close to the pole areas the accuracy * Calculates distance as points on a globe in a sloppy way. Close to the pole areas the accuracy
* of this function decreases. * of this function decreases.
*/ */
SLOPPY_ARC() { SLOPPY_ARC {
@Override @Override
public double normalize(double distance, DistanceUnit unit) { public double normalize(double distance, DistanceUnit unit) {
@ -127,12 +130,31 @@ public enum GeoDistance {
} }
}; };
/** Returns a GeoDistance object as read from the StreamInput. */
@Override
public GeoDistance readFrom(StreamInput in) throws IOException {
int ord = in.readVInt();
if (ord < 0 || ord >= values().length) {
throw new IOException("Unknown GeoDistance ordinal [" + ord + "]");
}
return GeoDistance.values()[ord];
}
public static GeoDistance readGeoDistanceFrom(StreamInput in) throws IOException {
return DEFAULT.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(this.ordinal());
}
/** /**
* Default {@link GeoDistance} function. This method should be used, If no specific function has been selected. * Default {@link GeoDistance} function. This method should be used, If no specific function has been selected.
* This is an alias for <code>SLOPPY_ARC</code> * This is an alias for <code>SLOPPY_ARC</code>
*/ */
public static final GeoDistance DEFAULT = SLOPPY_ARC; public static final GeoDistance DEFAULT = SLOPPY_ARC;
public abstract double normalize(double distance, DistanceUnit unit); public abstract double normalize(double distance, DistanceUnit unit);
public abstract double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit); public abstract double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit);
@ -180,14 +202,14 @@ public enum GeoDistance {
/** /**
* Get a {@link GeoDistance} according to a given name. Valid values are * Get a {@link GeoDistance} according to a given name. Valid values are
* *
* <ul> * <ul>
* <li><b>plane</b> for <code>GeoDistance.PLANE</code></li> * <li><b>plane</b> for <code>GeoDistance.PLANE</code></li>
* <li><b>sloppy_arc</b> for <code>GeoDistance.SLOPPY_ARC</code></li> * <li><b>sloppy_arc</b> for <code>GeoDistance.SLOPPY_ARC</code></li>
* <li><b>factor</b> for <code>GeoDistance.FACTOR</code></li> * <li><b>factor</b> for <code>GeoDistance.FACTOR</code></li>
* <li><b>arc</b> for <code>GeoDistance.ARC</code></li> * <li><b>arc</b> for <code>GeoDistance.ARC</code></li>
* </ul> * </ul>
* *
* @param name name of the {@link GeoDistance} * @param name name of the {@link GeoDistance}
* @return a {@link GeoDistance} * @return a {@link GeoDistance}
*/ */
@ -336,7 +358,7 @@ public enum GeoDistance {
/** /**
* Basic implementation of {@link FixedSourceDistance}. This class keeps the basic parameters for a distance * Basic implementation of {@link FixedSourceDistance}. This class keeps the basic parameters for a distance
* functions based on a fixed source. Namely latitude, longitude and unit. * functions based on a fixed source. Namely latitude, longitude and unit.
*/ */
public static abstract class FixedSourceDistanceBase implements FixedSourceDistance { public static abstract class FixedSourceDistanceBase implements FixedSourceDistance {
protected final double sourceLatitude; protected final double sourceLatitude;
@ -349,7 +371,7 @@ public enum GeoDistance {
this.unit = unit; this.unit = unit;
} }
} }
public static class ArcFixedSourceDistance extends FixedSourceDistanceBase { public static class ArcFixedSourceDistance extends FixedSourceDistanceBase {
public ArcFixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) { public ArcFixedSourceDistance(double sourceLatitude, double sourceLongitude, DistanceUnit unit) {

View File

@ -19,6 +19,11 @@
package org.elasticsearch.common.geo; package org.elasticsearch.common.geo;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import java.io.IOException;
import org.apache.lucene.util.BitUtil; import org.apache.lucene.util.BitUtil;
import org.apache.lucene.util.XGeoHashUtils; import org.apache.lucene.util.XGeoHashUtils;
@ -27,11 +32,14 @@ import org.apache.lucene.util.XGeoUtils;
/** /**
* *
*/ */
public final class GeoPoint { public final class GeoPoint implements Writeable<GeoPoint> {
private double lat; private double lat;
private double lon; private double lon;
private final static double TOLERANCE = XGeoUtils.TOLERANCE; private final static double TOLERANCE = XGeoUtils.TOLERANCE;
// for serialization purposes
private static final GeoPoint PROTOTYPE = new GeoPoint(Double.NaN, Double.NaN);
public GeoPoint() { public GeoPoint() {
} }
@ -51,6 +59,10 @@ public final class GeoPoint {
this.lon = lon; this.lon = lon;
} }
public GeoPoint(GeoPoint template) {
this(template.getLat(), template.getLon());
}
public GeoPoint reset(double lat, double lon) { public GeoPoint reset(double lat, double lon) {
this.lat = lat; this.lat = lat;
this.lon = lon; this.lon = lon;
@ -152,8 +164,7 @@ public final class GeoPoint {
} }
public static GeoPoint parseFromLatLon(String latLon) { public static GeoPoint parseFromLatLon(String latLon) {
GeoPoint point = new GeoPoint(); GeoPoint point = new GeoPoint(latLon);
point.resetFromString(latLon);
return point; return point;
} }
@ -168,4 +179,21 @@ public final class GeoPoint {
public static GeoPoint fromIndexLong(long indexLong) { public static GeoPoint fromIndexLong(long indexLong) {
return new GeoPoint().resetFromIndexHash(indexLong); return new GeoPoint().resetFromIndexHash(indexLong);
} }
}
@Override
public GeoPoint readFrom(StreamInput in) throws IOException {
double lat = in.readDouble();
double lon = in.readDouble();
return new GeoPoint(lat, lon);
}
public static GeoPoint readGeoPointFrom(StreamInput in) throws IOException {
return PROTOTYPE.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeDouble(lat);
out.writeDouble(lon);
}
}

View File

@ -23,6 +23,7 @@ import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree; import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.util.SloppyMath; import org.apache.lucene.util.SloppyMath;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Numbers;
import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token; import org.elasticsearch.common.xcontent.XContentParser.Token;
@ -34,10 +35,19 @@ import java.io.IOException;
*/ */
public class GeoUtils { public class GeoUtils {
/** Maximum valid latitude in degrees. */
public static final double MAX_LAT = 90.0;
/** Minimum valid latitude in degrees. */
public static final double MIN_LAT = -90.0;
/** Maximum valid longitude in degrees. */
public static final double MAX_LON = 180.0;
/** Minimum valid longitude in degrees. */
public static final double MIN_LON = -180.0;
public static final String LATITUDE = GeoPointFieldMapper.Names.LAT; public static final String LATITUDE = GeoPointFieldMapper.Names.LAT;
public static final String LONGITUDE = GeoPointFieldMapper.Names.LON; public static final String LONGITUDE = GeoPointFieldMapper.Names.LON;
public static final String GEOHASH = GeoPointFieldMapper.Names.GEOHASH; public static final String GEOHASH = GeoPointFieldMapper.Names.GEOHASH;
/** Earth ellipsoid major axis defined by WGS 84 in meters */ /** Earth ellipsoid major axis defined by WGS 84 in meters */
public static final double EARTH_SEMI_MAJOR_AXIS = 6378137.0; // meters (WGS 84) public static final double EARTH_SEMI_MAJOR_AXIS = 6378137.0; // meters (WGS 84)
@ -56,6 +66,22 @@ public class GeoUtils {
/** Earth ellipsoid polar distance in meters */ /** Earth ellipsoid polar distance in meters */
public static final double EARTH_POLAR_DISTANCE = Math.PI * EARTH_SEMI_MINOR_AXIS; public static final double EARTH_POLAR_DISTANCE = Math.PI * EARTH_SEMI_MINOR_AXIS;
/** Returns true if latitude is actually a valid latitude value.*/
public static boolean isValidLatitude(double latitude) {
if (Double.isNaN(latitude) || Double.isInfinite(latitude) || latitude < GeoUtils.MIN_LAT || latitude > GeoUtils.MAX_LAT) {
return false;
}
return true;
}
/** Returns true if longitude is actually a valid longitude value. */
public static boolean isValidLongitude(double longitude) {
if (Double.isNaN(longitude) || Double.isNaN(longitude) || longitude < GeoUtils.MIN_LON || longitude > GeoUtils.MAX_LON) {
return false;
}
return true;
}
/** /**
* Return an approximate value of the diameter of the earth (in meters) at the given latitude (in radians). * Return an approximate value of the diameter of the earth (in meters) at the given latitude (in radians).
*/ */

View File

@ -19,13 +19,18 @@
package org.elasticsearch.common.geo; package org.elasticsearch.common.geo;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import java.io.IOException;
import java.util.Locale; import java.util.Locale;
/** /**
* Enum representing the relationship between a Query / Filter Shape and indexed Shapes * Enum representing the relationship between a Query / Filter Shape and indexed Shapes
* that will be used to determine if a Document should be matched or not * that will be used to determine if a Document should be matched or not
*/ */
public enum ShapeRelation { public enum ShapeRelation implements Writeable<ShapeRelation>{
INTERSECTS("intersects"), INTERSECTS("intersects"),
DISJOINT("disjoint"), DISJOINT("disjoint"),
@ -37,6 +42,20 @@ public enum ShapeRelation {
this.relationName = relationName; this.relationName = relationName;
} }
@Override
public ShapeRelation readFrom(StreamInput in) throws IOException {
int ordinal = in.readVInt();
if (ordinal < 0 || ordinal >= values().length) {
throw new IOException("Unknown ShapeRelation ordinal [" + ordinal + "]");
}
return values()[ordinal];
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(ordinal());
}
public static ShapeRelation getRelationByName(String name) { public static ShapeRelation getRelationByName(String name) {
name = name.toLowerCase(Locale.ENGLISH); name = name.toLowerCase(Locale.ENGLISH);
for (ShapeRelation relation : ShapeRelation.values()) { for (ShapeRelation relation : ShapeRelation.values()) {

View File

@ -18,11 +18,16 @@
*/ */
package org.elasticsearch.common.geo; package org.elasticsearch.common.geo;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import java.io.IOException;
/** /**
* *
*/ */
public enum SpatialStrategy { public enum SpatialStrategy implements Writeable<SpatialStrategy> {
TERM("term"), TERM("term"),
RECURSIVE("recursive"); RECURSIVE("recursive");
@ -36,4 +41,27 @@ public enum SpatialStrategy {
public String getStrategyName() { public String getStrategyName() {
return strategyName; return strategyName;
} }
@Override
public SpatialStrategy readFrom(StreamInput in) throws IOException {
int ordinal = in.readVInt();
if (ordinal < 0 || ordinal >= values().length) {
throw new IOException("Unknown SpatialStrategy ordinal [" + ordinal + "]");
}
return values()[ordinal];
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(ordinal());
}
public static SpatialStrategy fromString(String strategyName) {
for (SpatialStrategy strategy : values()) {
if (strategy.strategyName.equals(strategyName)) {
return strategy;
}
}
return null;
}
} }

View File

@ -27,6 +27,7 @@ import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.GeometryFactory;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.action.support.ToXContentToBytes;
import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.unit.DistanceUnit.Distance; import org.elasticsearch.common.unit.DistanceUnit.Distance;
@ -34,7 +35,6 @@ import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContent; import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper; import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper;
import java.io.IOException; import java.io.IOException;
@ -43,7 +43,7 @@ import java.util.*;
/** /**
* Basic class for building GeoJSON shapes like Polygons, Linestrings, etc * Basic class for building GeoJSON shapes like Polygons, Linestrings, etc
*/ */
public abstract class ShapeBuilder implements ToXContent { public abstract class ShapeBuilder extends ToXContentToBytes {
protected static final ESLogger LOGGER = ESLoggerFactory.getLogger(ShapeBuilder.class.getName()); protected static final ESLogger LOGGER = ESLoggerFactory.getLogger(ShapeBuilder.class.getName());
@ -209,16 +209,6 @@ public abstract class ShapeBuilder implements ToXContent {
*/ */
public static EnvelopeBuilder newEnvelope(Orientation orientation) { return new EnvelopeBuilder(orientation); } public static EnvelopeBuilder newEnvelope(Orientation orientation) { return new EnvelopeBuilder(orientation); }
@Override
public String toString() {
try {
XContentBuilder xcontent = JsonXContent.contentBuilder();
return toXContent(xcontent, EMPTY_PARAMS).prettyPrint().string();
} catch (IOException e) {
return super.toString();
}
}
/** /**
* Create a new Shape from this builder. Since calling this method could change the * Create a new Shape from this builder. Since calling this method could change the
* defined shape. (by inserting new coordinates or change the position of points) * defined shape. (by inserting new coordinates or change the position of points)

View File

@ -68,4 +68,4 @@ public abstract class FilterStreamInput extends StreamInput {
public void setVersion(Version version) { public void setVersion(Version version) {
delegate.setVersion(version); delegate.setVersion(version);
} }
} }

View File

@ -33,6 +33,8 @@ import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.text.StringAndBytesText; import org.elasticsearch.common.text.StringAndBytesText;
import org.elasticsearch.common.text.Text; import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
@ -55,8 +57,18 @@ import static org.elasticsearch.ElasticsearchException.readStackTrace;
public abstract class StreamInput extends InputStream { public abstract class StreamInput extends InputStream {
private final NamedWriteableRegistry namedWriteableRegistry;
private Version version = Version.CURRENT; private Version version = Version.CURRENT;
protected StreamInput() {
this.namedWriteableRegistry = new NamedWriteableRegistry();
}
protected StreamInput(NamedWriteableRegistry namedWriteableRegistry) {
this.namedWriteableRegistry = namedWriteableRegistry;
}
public Version getVersion() { public Version getVersion() {
return this.version; return this.version;
} }
@ -349,6 +361,13 @@ public abstract class StreamInput extends InputStream {
return ret; return ret;
} }
public String[] readOptionalStringArray() throws IOException {
if (readBoolean()) {
return readStringArray();
}
return null;
}
@Nullable @Nullable
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Map<String, Object> readMap() throws IOException { public Map<String, Object> readMap() throws IOException {
@ -571,6 +590,20 @@ public abstract class StreamInput extends InputStream {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* Reads a {@link QueryBuilder} from the current stream
*/
public QueryBuilder readQuery() throws IOException {
return readNamedWriteable(QueryBuilder.class);
}
/**
* Reads a {@link org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder} from the current stream
*/
public ScoreFunctionBuilder<?> readScoreFunction() throws IOException {
return readNamedWriteable(ScoreFunctionBuilder.class);
}
public static StreamInput wrap(BytesReference reference) { public static StreamInput wrap(BytesReference reference) {
if (reference.hasArray() == false) { if (reference.hasArray() == false) {
reference = reference.toBytesArray(); reference = reference.toBytesArray();

View File

@ -31,6 +31,8 @@ import org.elasticsearch.Version;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.text.Text; import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.joda.time.ReadableInstant; import org.joda.time.ReadableInstant;
import java.io.EOFException; import java.io.EOFException;
@ -315,6 +317,18 @@ public abstract class StreamOutput extends OutputStream {
} }
} }
/**
* Writes a string array, for nullable string, writes false.
*/
public void writeOptionalStringArray(@Nullable String[] array) throws IOException {
if (array == null) {
writeBoolean(false);
} else {
writeBoolean(true);
writeStringArray(array);
}
}
public void writeMap(@Nullable Map<String, Object> map) throws IOException { public void writeMap(@Nullable Map<String, Object> map) throws IOException {
writeGenericValue(map); writeGenericValue(map);
} }
@ -568,4 +582,18 @@ public abstract class StreamOutput extends OutputStream {
writeString(namedWriteable.getWriteableName()); writeString(namedWriteable.getWriteableName());
namedWriteable.writeTo(this); namedWriteable.writeTo(this);
} }
/**
* Writes a {@link QueryBuilder} to the current stream
*/
public void writeQuery(QueryBuilder queryBuilder) throws IOException {
writeNamedWriteable(queryBuilder);
}
/**
* Writes a {@link ScoreFunctionBuilder} to the current stream
*/
public void writeScoreFunction(ScoreFunctionBuilder<?> scoreFunctionBuilder) throws IOException {
writeNamedWriteable(scoreFunctionBuilder);
}
} }

View File

@ -158,7 +158,7 @@ public class MoreLikeThisQuery extends Query {
if (this.unlikeText != null || this.unlikeFields != null) { if (this.unlikeText != null || this.unlikeFields != null) {
handleUnlike(mlt, this.unlikeText, this.unlikeFields); handleUnlike(mlt, this.unlikeText, this.unlikeFields);
} }
return createQuery(mlt); return createQuery(mlt);
} }
@ -182,7 +182,7 @@ public class MoreLikeThisQuery extends Query {
BooleanQuery bq = bqBuilder.build(); BooleanQuery bq = bqBuilder.build();
bq.setBoost(getBoost()); bq.setBoost(getBoost());
return bq; return bq;
} }
private void handleUnlike(XMoreLikeThis mlt, String[] unlikeText, Fields[] unlikeFields) throws IOException { private void handleUnlike(XMoreLikeThis mlt, String[] unlikeText, Fields[] unlikeFields) throws IOException {
@ -257,8 +257,8 @@ public class MoreLikeThisQuery extends Query {
this.unlikeFields = unlikeFields; this.unlikeFields = unlikeFields;
} }
public void setUnlikeText(List<String> unlikeText) { public void setUnlikeText(String[] unlikeText) {
this.unlikeText = unlikeText.toArray(Strings.EMPTY_ARRAY); this.unlikeText = unlikeText;
} }
public String[] getMoreLikeFields() { public String[] getMoreLikeFields() {

View File

@ -20,19 +20,20 @@
package org.elasticsearch.common.lucene.search.function; package org.elasticsearch.common.lucene.search.function;
import org.apache.lucene.search.Explanation; import org.apache.lucene.search.Explanation;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
public enum CombineFunction { import java.io.IOException;
MULT { import java.util.Locale;
public enum CombineFunction implements Writeable<CombineFunction> {
MULTIPLY {
@Override @Override
public float combine(double queryScore, double funcScore, double maxBoost) { public float combine(double queryScore, double funcScore, double maxBoost) {
return toFloat(queryScore * Math.min(funcScore, maxBoost)); return toFloat(queryScore * Math.min(funcScore, maxBoost));
} }
@Override
public String getName() {
return "multiply";
}
@Override @Override
public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) { public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) {
Explanation boostExpl = Explanation.match(maxBoost, "maxBoost"); Explanation boostExpl = Explanation.match(maxBoost, "maxBoost");
@ -50,11 +51,6 @@ public enum CombineFunction {
return toFloat(Math.min(funcScore, maxBoost)); return toFloat(Math.min(funcScore, maxBoost));
} }
@Override
public String getName() {
return "replace";
}
@Override @Override
public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) { public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) {
Explanation boostExpl = Explanation.match(maxBoost, "maxBoost"); Explanation boostExpl = Explanation.match(maxBoost, "maxBoost");
@ -71,11 +67,6 @@ public enum CombineFunction {
return toFloat(queryScore + Math.min(funcScore, maxBoost)); return toFloat(queryScore + Math.min(funcScore, maxBoost));
} }
@Override
public String getName() {
return "sum";
}
@Override @Override
public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) { public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) {
Explanation minExpl = Explanation.match(Math.min(funcExpl.getValue(), maxBoost), "min of:", Explanation minExpl = Explanation.match(Math.min(funcExpl.getValue(), maxBoost), "min of:",
@ -91,11 +82,6 @@ public enum CombineFunction {
return toFloat((Math.min(funcScore, maxBoost) + queryScore) / 2.0); return toFloat((Math.min(funcScore, maxBoost) + queryScore) / 2.0);
} }
@Override
public String getName() {
return "avg";
}
@Override @Override
public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) { public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) {
Explanation minExpl = Explanation.match(Math.min(funcExpl.getValue(), maxBoost), "min of:", Explanation minExpl = Explanation.match(Math.min(funcExpl.getValue(), maxBoost), "min of:",
@ -112,11 +98,6 @@ public enum CombineFunction {
return toFloat(Math.min(queryScore, Math.min(funcScore, maxBoost))); return toFloat(Math.min(queryScore, Math.min(funcScore, maxBoost)));
} }
@Override
public String getName() {
return "min";
}
@Override @Override
public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) { public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) {
Explanation innerMinExpl = Explanation.match( Explanation innerMinExpl = Explanation.match(
@ -134,11 +115,6 @@ public enum CombineFunction {
return toFloat(Math.max(queryScore, Math.min(funcScore, maxBoost))); return toFloat(Math.max(queryScore, Math.min(funcScore, maxBoost)));
} }
@Override
public String getName() {
return "max";
}
@Override @Override
public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) { public Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost) {
Explanation innerMinExpl = Explanation.match( Explanation innerMinExpl = Explanation.match(
@ -153,8 +129,6 @@ public enum CombineFunction {
public abstract float combine(double queryScore, double funcScore, double maxBoost); public abstract float combine(double queryScore, double funcScore, double maxBoost);
public abstract String getName();
public static float toFloat(double input) { public static float toFloat(double input) {
assert deviation(input) <= 0.001 : "input " + input + " out of float scope for function score deviation: " + deviation(input); assert deviation(input) <= 0.001 : "input " + input + " out of float scope for function score deviation: " + deviation(input);
return (float) input; return (float) input;
@ -166,4 +140,26 @@ public enum CombineFunction {
} }
public abstract Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost); public abstract Explanation explain(Explanation queryExpl, Explanation funcExpl, float maxBoost);
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(this.ordinal());
}
@Override
public CombineFunction readFrom(StreamInput in) throws IOException {
int ordinal = in.readVInt();
if (ordinal < 0 || ordinal >= values().length) {
throw new IOException("Unknown CombineFunction ordinal [" + ordinal + "]");
}
return values()[ordinal];
}
public static CombineFunction readCombineFunctionFrom(StreamInput in) throws IOException {
return CombineFunction.MULTIPLY.readFrom(in);
}
public static CombineFunction fromString(String combineFunction) {
return valueOf(combineFunction.toUpperCase(Locale.ROOT));
}
} }

View File

@ -22,11 +22,16 @@ package org.elasticsearch.common.lucene.search.function;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Explanation; import org.apache.lucene.search.Explanation;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.index.fielddata.FieldData; import org.elasticsearch.index.fielddata.FieldData;
import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;
import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
/** /**
* A function_score function that multiplies the score with the value of a * A function_score function that multiplies the score with the value of a
@ -45,7 +50,7 @@ public class FieldValueFactorFunction extends ScoreFunction {
public FieldValueFactorFunction(String field, float boostFactor, Modifier modifierType, Double missing, public FieldValueFactorFunction(String field, float boostFactor, Modifier modifierType, Double missing,
IndexNumericFieldData indexFieldData) { IndexNumericFieldData indexFieldData) {
super(CombineFunction.MULT); super(CombineFunction.MULTIPLY);
this.field = field; this.field = field;
this.boostFactor = boostFactor; this.boostFactor = boostFactor;
this.modifier = modifierType; this.modifier = modifierType;
@ -103,11 +108,19 @@ public class FieldValueFactorFunction extends ScoreFunction {
return false; return false;
} }
@Override
protected boolean doEquals(ScoreFunction other) {
FieldValueFactorFunction fieldValueFactorFunction = (FieldValueFactorFunction) other;
return this.boostFactor == fieldValueFactorFunction.boostFactor &&
Objects.equals(this.field, fieldValueFactorFunction.field) &&
Objects.equals(this.modifier, fieldValueFactorFunction.modifier);
}
/** /**
* The Type class encapsulates the modification types that can be applied * The Type class encapsulates the modification types that can be applied
* to the score/value product. * to the score/value product.
*/ */
public enum Modifier { public enum Modifier implements Writeable<Modifier> {
NONE { NONE {
@Override @Override
public double apply(double n) { public double apply(double n) {
@ -171,9 +184,31 @@ public class FieldValueFactorFunction extends ScoreFunction {
public abstract double apply(double n); public abstract double apply(double n);
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(this.ordinal());
}
public static Modifier readModifierFrom(StreamInput in) throws IOException {
return Modifier.NONE.readFrom(in);
}
@Override
public Modifier readFrom(StreamInput in) throws IOException {
int ordinal = in.readVInt();
if (ordinal < 0 || ordinal >= values().length) {
throw new IOException("Unknown Modifier ordinal [" + ordinal + "]");
}
return values()[ordinal];
}
@Override @Override
public String toString() { public String toString() {
return super.toString().toLowerCase(Locale.ROOT); return super.toString().toLowerCase(Locale.ROOT);
} }
public static Modifier fromString(String modifier) {
return valueOf(modifier.toUpperCase(Locale.ROOT));
}
} }
} }

View File

@ -29,14 +29,13 @@ import org.apache.lucene.search.Scorer;
import org.apache.lucene.search.Weight; import org.apache.lucene.search.Weight;
import org.apache.lucene.util.Bits; import org.apache.lucene.util.Bits;
import org.apache.lucene.util.ToStringUtils; import org.apache.lucene.util.ToStringUtils;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.Lucene;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/** /**
* A query that allows for a pluggable boost function / filter. If it matches * A query that allows for a pluggable boost function / filter. If it matches
@ -55,53 +54,63 @@ public class FiltersFunctionScoreQuery extends Query {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) if (this == o) {
return true; return true;
if (o == null || getClass() != o.getClass()) }
if (o == null || getClass() != o.getClass()) {
return false; return false;
}
FilterFunction that = (FilterFunction) o; FilterFunction that = (FilterFunction) o;
return Objects.equals(this.filter, that.filter) && Objects.equals(this.function, that.function);
if (filter != null ? !filter.equals(that.filter) : that.filter != null)
return false;
if (function != null ? !function.equals(that.function) : that.function != null)
return false;
return true;
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = filter != null ? filter.hashCode() : 0; return Objects.hash(super.hashCode(), filter, function);
result = 31 * result + (function != null ? function.hashCode() : 0);
return result;
} }
} }
public static enum ScoreMode { public enum ScoreMode implements Writeable<ScoreMode> {
First, Avg, Max, Sum, Min, Multiply FIRST, AVG, MAX, SUM, MIN, MULTIPLY;
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(this.ordinal());
}
@Override
public ScoreMode readFrom(StreamInput in) throws IOException {
int ordinal = in.readVInt();
if (ordinal < 0 || ordinal >= values().length) {
throw new IOException("Unknown ScoreMode ordinal [" + ordinal + "]");
}
return values()[ordinal];
}
public static ScoreMode readScoreModeFrom(StreamInput in) throws IOException {
return ScoreMode.MULTIPLY.readFrom(in);
}
public static ScoreMode fromString(String scoreMode) {
return valueOf(scoreMode.toUpperCase(Locale.ROOT));
}
} }
Query subQuery; Query subQuery;
final FilterFunction[] filterFunctions; final FilterFunction[] filterFunctions;
final ScoreMode scoreMode; final ScoreMode scoreMode;
final float maxBoost; final float maxBoost;
private Float minScore; private final Float minScore;
protected CombineFunction combineFunction; final protected CombineFunction combineFunction;
public FiltersFunctionScoreQuery(Query subQuery, ScoreMode scoreMode, FilterFunction[] filterFunctions, float maxBoost, Float minScore) { public FiltersFunctionScoreQuery(Query subQuery, ScoreMode scoreMode, FilterFunction[] filterFunctions, float maxBoost, Float minScore, CombineFunction combineFunction) {
this.subQuery = subQuery; this.subQuery = subQuery;
this.scoreMode = scoreMode; this.scoreMode = scoreMode;
this.filterFunctions = filterFunctions; this.filterFunctions = filterFunctions;
this.maxBoost = maxBoost; this.maxBoost = maxBoost;
combineFunction = CombineFunction.MULT;
this.minScore = minScore;
}
public FiltersFunctionScoreQuery setCombineFunction(CombineFunction combineFunction) {
this.combineFunction = combineFunction; this.combineFunction = combineFunction;
return this; this.minScore = minScore;
} }
public Query getSubQuery() { public Query getSubQuery() {
@ -227,35 +236,34 @@ public class FiltersFunctionScoreQuery extends Query {
// filters // filters
double factor = 1.0; double factor = 1.0;
switch (scoreMode) { switch (scoreMode) {
case First: case FIRST:
factor = filterExplanations.get(0).getValue(); factor = filterExplanations.get(0).getValue();
break; break;
case Max: case MAX:
factor = Double.NEGATIVE_INFINITY; factor = Double.NEGATIVE_INFINITY;
for (int i = 0; i < filterExplanations.size(); i++) { for (Explanation filterExplanation : filterExplanations) {
factor = Math.max(filterExplanations.get(i).getValue(), factor); factor = Math.max(filterExplanation.getValue(), factor);
} }
break; break;
case Min: case MIN:
factor = Double.POSITIVE_INFINITY; factor = Double.POSITIVE_INFINITY;
for (int i = 0; i < filterExplanations.size(); i++) { for (Explanation filterExplanation : filterExplanations) {
factor = Math.min(filterExplanations.get(i).getValue(), factor); factor = Math.min(filterExplanation.getValue(), factor);
} }
break; break;
case Multiply: case MULTIPLY:
for (int i = 0; i < filterExplanations.size(); i++) { for (Explanation filterExplanation : filterExplanations) {
factor *= filterExplanations.get(i).getValue(); factor *= filterExplanation.getValue();
} }
break; break;
default: // Avg / Total default:
double totalFactor = 0.0f; double totalFactor = 0.0f;
for (int i = 0; i < filterExplanations.size(); i++) { for (Explanation filterExplanation : filterExplanations) {
totalFactor += filterExplanations.get(i).getValue(); totalFactor += filterExplanation.getValue();
} }
if (weightSum != 0) { if (weightSum != 0) {
factor = totalFactor; factor = totalFactor;
if (scoreMode == ScoreMode.Avg) { if (scoreMode == ScoreMode.AVG) {
factor /= weightSum; factor /= weightSum;
} }
} }
@ -293,58 +301,64 @@ public class FiltersFunctionScoreQuery extends Query {
// be costly to call score(), so we explicitly check if scores // be costly to call score(), so we explicitly check if scores
// are needed // are needed
float subQueryScore = needsScores ? scorer.score() : 0f; float subQueryScore = needsScores ? scorer.score() : 0f;
if (scoreMode == ScoreMode.First) { switch(scoreMode) {
for (int i = 0; i < filterFunctions.length; i++) { case FIRST:
if (docSets[i].get(docId)) { for (int i = 0; i < filterFunctions.length; i++) {
factor = functions[i].score(docId, subQueryScore); if (docSets[i].get(docId)) {
break; factor = functions[i].score(docId, subQueryScore);
} break;
}
} else if (scoreMode == ScoreMode.Max) {
double maxFactor = Double.NEGATIVE_INFINITY;
for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) {
maxFactor = Math.max(functions[i].score(docId, subQueryScore), maxFactor);
}
}
if (maxFactor != Float.NEGATIVE_INFINITY) {
factor = maxFactor;
}
} else if (scoreMode == ScoreMode.Min) {
double minFactor = Double.POSITIVE_INFINITY;
for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) {
minFactor = Math.min(functions[i].score(docId, subQueryScore), minFactor);
}
}
if (minFactor != Float.POSITIVE_INFINITY) {
factor = minFactor;
}
} else if (scoreMode == ScoreMode.Multiply) {
for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) {
factor *= functions[i].score(docId, subQueryScore);
}
}
} else { // Avg / Total
double totalFactor = 0.0f;
float weightSum = 0;
for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) {
totalFactor += functions[i].score(docId, subQueryScore);
if (filterFunctions[i].function instanceof WeightFactorFunction) {
weightSum+= ((WeightFactorFunction)filterFunctions[i].function).getWeight();
} else {
weightSum++;
} }
} }
} break;
if (weightSum != 0) { case MAX:
factor = totalFactor; double maxFactor = Double.NEGATIVE_INFINITY;
if (scoreMode == ScoreMode.Avg) { for (int i = 0; i < filterFunctions.length; i++) {
factor /= weightSum; if (docSets[i].get(docId)) {
maxFactor = Math.max(functions[i].score(docId, subQueryScore), maxFactor);
}
} }
} if (maxFactor != Float.NEGATIVE_INFINITY) {
factor = maxFactor;
}
break;
case MIN:
double minFactor = Double.POSITIVE_INFINITY;
for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) {
minFactor = Math.min(functions[i].score(docId, subQueryScore), minFactor);
}
}
if (minFactor != Float.POSITIVE_INFINITY) {
factor = minFactor;
}
break;
case MULTIPLY:
for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) {
factor *= functions[i].score(docId, subQueryScore);
}
}
break;
default: // Avg / Total
double totalFactor = 0.0f;
float weightSum = 0;
for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) {
totalFactor += functions[i].score(docId, subQueryScore);
if (filterFunctions[i].function instanceof WeightFactorFunction) {
weightSum+= ((WeightFactorFunction)filterFunctions[i].function).getWeight();
} else {
weightSum++;
}
}
}
if (weightSum != 0) {
factor = totalFactor;
if (scoreMode == ScoreMode.AVG) {
factor /= weightSum;
}
}
break;
} }
return scoreCombiner.combine(subQueryScore, factor, maxBoost); return scoreCombiner.combine(subQueryScore, factor, maxBoost);
} }
@ -364,19 +378,20 @@ public class FiltersFunctionScoreQuery extends Query {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) if (this == o) {
return false; return true;
FiltersFunctionScoreQuery other = (FiltersFunctionScoreQuery) o; }
if (this.getBoost() != other.getBoost()) if (super.equals(o) == false) {
return false;
if (!this.subQuery.equals(other.subQuery)) {
return false; return false;
} }
return Arrays.equals(this.filterFunctions, other.filterFunctions); FiltersFunctionScoreQuery other = (FiltersFunctionScoreQuery) o;
return Objects.equals(this.subQuery, other.subQuery) && this.maxBoost == other.maxBoost &&
Objects.equals(this.combineFunction, other.combineFunction) && Objects.equals(this.minScore, other.minScore) &&
Arrays.equals(this.filterFunctions, other.filterFunctions);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return subQuery.hashCode() + 31 * Arrays.hashCode(filterFunctions) ^ Float.floatToIntBits(getBoost()); return Objects.hash(super.hashCode(), subQuery, maxBoost, combineFunction, minScore, filterFunctions);
} }
} }

View File

@ -23,7 +23,6 @@ import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.*; import org.apache.lucene.search.*;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.ToStringUtils; import org.apache.lucene.util.ToStringUtils;
import java.io.IOException; import java.io.IOException;
@ -35,31 +34,27 @@ import java.util.Set;
*/ */
public class FunctionScoreQuery extends Query { public class FunctionScoreQuery extends Query {
public static final float DEFAULT_MAX_BOOST = Float.MAX_VALUE;
Query subQuery; Query subQuery;
final ScoreFunction function; final ScoreFunction function;
float maxBoost = Float.MAX_VALUE; final float maxBoost;
CombineFunction combineFunction; final CombineFunction combineFunction;
private Float minScore = null; private Float minScore;
public FunctionScoreQuery(Query subQuery, ScoreFunction function, Float minScore) { public FunctionScoreQuery(Query subQuery, ScoreFunction function, Float minScore, CombineFunction combineFunction, float maxBoost) {
this.subQuery = subQuery; this.subQuery = subQuery;
this.function = function; this.function = function;
this.combineFunction = function == null? CombineFunction.MULT : function.getDefaultScoreCombiner(); this.combineFunction = combineFunction;
this.minScore = minScore; this.minScore = minScore;
this.maxBoost = maxBoost;
} }
public FunctionScoreQuery(Query subQuery, ScoreFunction function) { public FunctionScoreQuery(Query subQuery, ScoreFunction function) {
this.subQuery = subQuery; this.subQuery = subQuery;
this.function = function; this.function = function;
this.combineFunction = function.getDefaultScoreCombiner(); this.combineFunction = function.getDefaultScoreCombiner();
} this.maxBoost = DEFAULT_MAX_BOOST;
public void setCombineFunction(CombineFunction combineFunction) {
this.combineFunction = combineFunction;
}
public void setMaxBoost(float maxBoost) {
this.maxBoost = maxBoost;
} }
public float getMaxBoost() { public float getMaxBoost() {
@ -193,15 +188,20 @@ public class FunctionScoreQuery extends Query {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) if (this == o) {
return true;
}
if (super.equals(o) == false) {
return false; return false;
}
FunctionScoreQuery other = (FunctionScoreQuery) o; FunctionScoreQuery other = (FunctionScoreQuery) o;
return this.getBoost() == other.getBoost() && this.subQuery.equals(other.subQuery) && (this.function != null ? this.function.equals(other.function) : other.function == null) return Objects.equals(this.subQuery, other.subQuery) && Objects.equals(this.function, other.function)
&& this.maxBoost == other.maxBoost; && Objects.equals(this.combineFunction, other.combineFunction)
&& Objects.equals(this.minScore, other.minScore) && this.maxBoost == other.maxBoost;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return subQuery.hashCode() + 31 * Objects.hashCode(function) ^ Float.floatToIntBits(getBoost()); return Objects.hash(super.hashCode(), subQuery.hashCode(), function, combineFunction, minScore, maxBoost);
} }
} }

View File

@ -38,7 +38,7 @@ public class RandomScoreFunction extends ScoreFunction {
* Default constructor. Only useful for constructing as a placeholder, but should not be used for actual scoring. * Default constructor. Only useful for constructing as a placeholder, but should not be used for actual scoring.
*/ */
public RandomScoreFunction() { public RandomScoreFunction() {
super(CombineFunction.MULT); super(CombineFunction.MULTIPLY);
uidFieldData = null; uidFieldData = null;
} }
@ -50,7 +50,7 @@ public class RandomScoreFunction extends ScoreFunction {
* @param uidFieldData The field data for _uid to use for generating consistent random values for the same id * @param uidFieldData The field data for _uid to use for generating consistent random values for the same id
*/ */
public RandomScoreFunction(int seed, int salt, IndexFieldData<?> uidFieldData) { public RandomScoreFunction(int seed, int salt, IndexFieldData<?> uidFieldData) {
super(CombineFunction.MULT); super(CombineFunction.MULTIPLY);
this.originalSeed = seed; this.originalSeed = seed;
this.saltedSeed = seed ^ salt; this.saltedSeed = seed ^ salt;
this.uidFieldData = uidFieldData; this.uidFieldData = uidFieldData;
@ -85,4 +85,11 @@ public class RandomScoreFunction extends ScoreFunction {
public boolean needsScores() { public boolean needsScores() {
return false; return false;
} }
@Override
protected boolean doEquals(ScoreFunction other) {
RandomScoreFunction randomScoreFunction = (RandomScoreFunction) other;
return this.originalSeed == randomScoreFunction.originalSeed &&
this.saltedSeed == randomScoreFunction.saltedSeed;
}
} }

View File

@ -22,6 +22,7 @@ package org.elasticsearch.common.lucene.search.function;
import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.LeafReaderContext;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
/** /**
* *
@ -46,4 +47,23 @@ public abstract class ScoreFunction {
* @return {@code true} if scores are needed. * @return {@code true} if scores are needed.
*/ */
public abstract boolean needsScores(); public abstract boolean needsScores();
@Override
public final boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ScoreFunction other = (ScoreFunction) obj;
return Objects.equals(scoreCombiner, other.scoreCombiner) &&
doEquals(other);
}
/**
* Indicates whether some other {@link ScoreFunction} object of the same type is "equal to" this one.
*/
protected abstract boolean doEquals(ScoreFunction other);
} }

View File

@ -29,6 +29,7 @@ import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.SearchScript; import org.elasticsearch.script.SearchScript;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
public class ScriptScoreFunction extends ScoreFunction { public class ScriptScoreFunction extends ScoreFunction {
@ -136,4 +137,9 @@ public class ScriptScoreFunction extends ScoreFunction {
return "script" + sScript.toString(); return "script" + sScript.toString();
} }
@Override
protected boolean doEquals(ScoreFunction other) {
ScriptScoreFunction scriptScoreFunction = (ScriptScoreFunction) other;
return Objects.equals(this.sScript, scriptScoreFunction.sScript);
}
} }

View File

@ -23,18 +23,19 @@ import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Explanation; import org.apache.lucene.search.Explanation;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
/** /**
* *
*/ */
public class WeightFactorFunction extends ScoreFunction { public class WeightFactorFunction extends ScoreFunction {
private static final ScoreFunction SCORE_ONE = new ScoreOne(CombineFunction.MULT); private static final ScoreFunction SCORE_ONE = new ScoreOne(CombineFunction.MULTIPLY);
private final ScoreFunction scoreFunction; private final ScoreFunction scoreFunction;
private float weight = 1.0f; private float weight = 1.0f;
public WeightFactorFunction(float weight, ScoreFunction scoreFunction) { public WeightFactorFunction(float weight, ScoreFunction scoreFunction) {
super(CombineFunction.MULT); super(CombineFunction.MULTIPLY);
if (scoreFunction == null) { if (scoreFunction == null) {
this.scoreFunction = SCORE_ONE; this.scoreFunction = SCORE_ONE;
} else { } else {
@ -44,7 +45,7 @@ public class WeightFactorFunction extends ScoreFunction {
} }
public WeightFactorFunction(float weight) { public WeightFactorFunction(float weight) {
super(CombineFunction.MULT); super(CombineFunction.MULTIPLY);
this.scoreFunction = SCORE_ONE; this.scoreFunction = SCORE_ONE;
this.weight = weight; this.weight = weight;
} }
@ -81,6 +82,17 @@ public class WeightFactorFunction extends ScoreFunction {
return weight; return weight;
} }
public ScoreFunction getScoreFunction() {
return scoreFunction;
}
@Override
protected boolean doEquals(ScoreFunction other) {
WeightFactorFunction weightFactorFunction = (WeightFactorFunction) other;
return this.weight == weightFactorFunction.weight &&
Objects.equals(this.scoreFunction, weightFactorFunction.scoreFunction);
}
private static class ScoreOne extends ScoreFunction { private static class ScoreOne extends ScoreFunction {
protected ScoreOne(CombineFunction scoreCombiner) { protected ScoreOne(CombineFunction scoreCombiner) {
@ -106,5 +118,10 @@ public class WeightFactorFunction extends ScoreFunction {
public boolean needsScores() { public boolean needsScores() {
return false; return false;
} }
@Override
protected boolean doEquals(ScoreFunction other) {
return true;
}
} }
} }

View File

@ -22,6 +22,8 @@ package org.elasticsearch.common.path;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import static org.elasticsearch.common.collect.MapBuilder.newMapBuilder; import static org.elasticsearch.common.collect.MapBuilder.newMapBuilder;
@ -195,7 +197,7 @@ public class PathTrie<T> {
private void put(Map<String, String> params, TrieNode<T> node, String value) { private void put(Map<String, String> params, TrieNode<T> node, String value) {
if (params != null && node.isNamedWildcard()) { if (params != null && node.isNamedWildcard()) {
params.put(node.namedWildcard(), decoder.decode(value)); params.put(node.namedWildcard(), value);
} }
} }
} }
@ -222,7 +224,7 @@ public class PathTrie<T> {
if (path.length() == 0) { if (path.length() == 0) {
return rootValue; return rootValue;
} }
String[] strings = Strings.splitStringToArray(path, separator); String[] strings = splitPath(decoder.decode(path));
if (strings.length == 0) { if (strings.length == 0) {
return rootValue; return rootValue;
} }
@ -233,4 +235,50 @@ public class PathTrie<T> {
} }
return root.retrieve(strings, index, params); return root.retrieve(strings, index, params);
} }
/*
Splits up the url path up by '/' and is aware of
index name expressions that appear between '<' and '>'.
*/
String[] splitPath(final String path) {
if (path == null || path.length() == 0) {
return Strings.EMPTY_ARRAY;
}
int count = 1;
boolean splitAllowed = true;
for (int i = 0; i < path.length(); i++) {
final char currentC = path.charAt(i);
if ('<' == currentC) {
splitAllowed = false;
} else if (currentC == '>') {
splitAllowed = true;
} else if (splitAllowed && currentC == separator) {
count++;
}
}
final List<String> result = new ArrayList<>(count);
final StringBuilder builder = new StringBuilder();
splitAllowed = true;
for (int i = 0; i < path.length(); i++) {
final char currentC = path.charAt(i);
if ('<' == currentC) {
splitAllowed = false;
} else if (currentC == '>') {
splitAllowed = true;
} else if (splitAllowed && currentC == separator) {
if (builder.length() > 0) {
result.add(builder.toString());
builder.setLength(0);
}
continue;
}
builder.append(currentC);
}
if (builder.length() > 0) {
result.add(builder.toString());
}
return result.toArray(new String[result.size()]);
}
} }

View File

@ -19,19 +19,24 @@
package org.elasticsearch.common.unit; package org.elasticsearch.common.unit;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentBuilderString; import org.elasticsearch.common.xcontent.XContentBuilderString;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
/** /**
* A unit class that encapsulates all in-exact search * A unit class that encapsulates all in-exact search
* parsing and conversion from similarities to edit distances * parsing and conversion from similarities to edit distances
* etc. * etc.
*/ */
public final class Fuzziness implements ToXContent { public final class Fuzziness implements ToXContent, Writeable<Fuzziness> {
public static final XContentBuilderString X_FIELD_NAME = new XContentBuilderString("fuzziness"); public static final XContentBuilderString X_FIELD_NAME = new XContentBuilderString("fuzziness");
public static final Fuzziness ZERO = new Fuzziness(0); public static final Fuzziness ZERO = new Fuzziness(0);
@ -42,6 +47,10 @@ public final class Fuzziness implements ToXContent {
private final String fuzziness; private final String fuzziness;
/** the prototype constant is intended for deserialization when used with
* {@link org.elasticsearch.common.io.stream.StreamableReader#readFrom(StreamInput)} */
static final Fuzziness PROTOTYPE = AUTO;
private Fuzziness(int fuzziness) { private Fuzziness(int fuzziness) {
if (fuzziness != 0 && fuzziness != 1 && fuzziness != 2) { if (fuzziness != 0 && fuzziness != 1 && fuzziness != 2) {
throw new IllegalArgumentException("Valid edit distances are [0, 1, 2] but was [" + fuzziness + "]"); throw new IllegalArgumentException("Valid edit distances are [0, 1, 2] but was [" + fuzziness + "]");
@ -50,7 +59,10 @@ public final class Fuzziness implements ToXContent {
} }
private Fuzziness(String fuzziness) { private Fuzziness(String fuzziness) {
this.fuzziness = fuzziness; if (fuzziness == null) {
throw new IllegalArgumentException("fuzziness can't be null!");
}
this.fuzziness = fuzziness.toUpperCase(Locale.ROOT);
} }
/** /**
@ -120,7 +132,7 @@ public final class Fuzziness implements ToXContent {
} }
public int asDistance(String text) { public int asDistance(String text) {
if (this == AUTO) { //AUTO if (this.equals(AUTO)) { //AUTO
final int len = termLen(text); final int len = termLen(text);
if (len <= 2) { if (len <= 2) {
return 0; return 0;
@ -134,7 +146,7 @@ public final class Fuzziness implements ToXContent {
} }
public TimeValue asTimeValue() { public TimeValue asTimeValue() {
if (this == AUTO) { if (this.equals(AUTO)) {
return TimeValue.timeValueMillis(1); return TimeValue.timeValueMillis(1);
} else { } else {
return TimeValue.parseTimeValue(fuzziness.toString(), null, "fuzziness"); return TimeValue.parseTimeValue(fuzziness.toString(), null, "fuzziness");
@ -142,7 +154,7 @@ public final class Fuzziness implements ToXContent {
} }
public long asLong() { public long asLong() {
if (this == AUTO) { if (this.equals(AUTO)) {
return 1; return 1;
} }
try { try {
@ -153,7 +165,7 @@ public final class Fuzziness implements ToXContent {
} }
public int asInt() { public int asInt() {
if (this == AUTO) { if (this.equals(AUTO)) {
return 1; return 1;
} }
try { try {
@ -164,7 +176,7 @@ public final class Fuzziness implements ToXContent {
} }
public short asShort() { public short asShort() {
if (this == AUTO) { if (this.equals(AUTO)) {
return 1; return 1;
} }
try { try {
@ -175,7 +187,7 @@ public final class Fuzziness implements ToXContent {
} }
public byte asByte() { public byte asByte() {
if (this == AUTO) { if (this.equals(AUTO)) {
return 1; return 1;
} }
try { try {
@ -186,14 +198,14 @@ public final class Fuzziness implements ToXContent {
} }
public double asDouble() { public double asDouble() {
if (this == AUTO) { if (this.equals(AUTO)) {
return 1d; return 1d;
} }
return Double.parseDouble(fuzziness.toString()); return Double.parseDouble(fuzziness.toString());
} }
public float asFloat() { public float asFloat() {
if (this == AUTO) { if (this.equals(AUTO)) {
return 1f; return 1f;
} }
return Float.parseFloat(fuzziness.toString()); return Float.parseFloat(fuzziness.toString());
@ -206,4 +218,35 @@ public final class Fuzziness implements ToXContent {
public String asString() { public String asString() {
return fuzziness.toString(); return fuzziness.toString();
} }
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Fuzziness other = (Fuzziness) obj;
return Objects.equals(fuzziness, other.fuzziness);
}
@Override
public int hashCode() {
return fuzziness.hashCode();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(fuzziness);
}
@Override
public Fuzziness readFrom(StreamInput in) throws IOException {
return new Fuzziness(in.readString());
}
public static Fuzziness readFuzzinessFrom(StreamInput in) throws IOException {
return PROTOTYPE.readFrom(in);
}
} }

View File

@ -21,7 +21,6 @@ package org.elasticsearch.common.xcontent;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.index.Index;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@ -130,7 +129,7 @@ public final class ObjectParser<Value, Context> implements BiFunction<XContentPa
try { try {
fieldParser.parser.parse(parser, value, context); fieldParser.parser.parse(parser, value, context);
} catch (Exception ex) { } catch (Exception ex) {
throw new ParsingException(new Index("_na_"), parser, "[" + name + "] failed to parse field [" + currentFieldName + "]", ex); throw new ParsingException(parser.getTokenLocation(), "[" + name + "] failed to parse field [" + currentFieldName + "]", ex);
} }
} }
@ -172,7 +171,7 @@ public final class ObjectParser<Value, Context> implements BiFunction<XContentPa
try { try {
return parse(parser, valueSupplier.get(), context); return parse(parser, valueSupplier.get(), context);
} catch (IOException e) { } catch (IOException e) {
throw new ParsingException(new Index("_na_"), parser, "[" + name + "] failed to parse object", e); throw new ParsingException(parser.getTokenLocation(), "[" + name + "] failed to parse object", e);
} }
} }

View File

@ -18,12 +18,17 @@
*/ */
package org.elasticsearch.index; package org.elasticsearch.index;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.lucene.uid.Versions; import org.elasticsearch.common.lucene.uid.Versions;
import java.io.IOException;
/** /**
* *
*/ */
public enum VersionType { public enum VersionType implements Writeable<VersionType> {
INTERNAL((byte) 0) { INTERNAL((byte) 0) {
@Override @Override
public boolean isVersionConflictForWrites(long currentVersion, long expectedVersion) { public boolean isVersionConflictForWrites(long currentVersion, long expectedVersion) {
@ -219,6 +224,8 @@ public enum VersionType {
private final byte value; private final byte value;
private static final VersionType PROTOTYPE = INTERNAL;
VersionType(byte value) { VersionType(byte value) {
this.value = value; this.value = value;
} }
@ -304,4 +311,20 @@ public enum VersionType {
} }
throw new IllegalArgumentException("No version type match [" + value + "]"); throw new IllegalArgumentException("No version type match [" + value + "]");
} }
@Override
public VersionType readFrom(StreamInput in) throws IOException {
int ordinal = in.readVInt();
assert (ordinal == 0 || ordinal == 1 || ordinal == 2 || ordinal == 3);
return VersionType.values()[ordinal];
}
public static VersionType readVersionTypeFrom(StreamInput in) throws IOException {
return PROTOTYPE.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(ordinal());
}
} }

View File

@ -33,7 +33,7 @@ import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.fielddata.FieldDataType; import org.elasticsearch.index.fielddata.FieldDataType;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.similarity.SimilarityProvider; import org.elasticsearch.index.similarity.SimilarityProvider;
import java.io.IOException; import java.io.IOException;
@ -437,7 +437,7 @@ public abstract class MappedFieldType extends FieldType {
} }
/** /**
* Should the field query {@link #termQuery(Object, org.elasticsearch.index.query.QueryParseContext)} be used when detecting this * Should the field query {@link #termQuery(Object, org.elasticsearch.index.query.QueryShardContext)} be used when detecting this
* field in query string. * field in query string.
*/ */
public boolean useTermQueryWithQueryString() { public boolean useTermQueryWithQueryString() {
@ -449,11 +449,11 @@ public abstract class MappedFieldType extends FieldType {
return new Term(names().indexName(), indexedValueForSearch(value)); return new Term(names().indexName(), indexedValueForSearch(value));
} }
public Query termQuery(Object value, @Nullable QueryParseContext context) { public Query termQuery(Object value, @Nullable QueryShardContext context) {
return new TermQuery(createTerm(value)); return new TermQuery(createTerm(value));
} }
public Query termsQuery(List values, @Nullable QueryParseContext context) { public Query termsQuery(List values, @Nullable QueryShardContext context) {
BytesRef[] bytesRefs = new BytesRef[values.size()]; BytesRef[] bytesRefs = new BytesRef[values.size()];
for (int i = 0; i < bytesRefs.length; i++) { for (int i = 0; i < bytesRefs.length; i++) {
bytesRefs[i] = indexedValueForSearch(values.get(i)); bytesRefs[i] = indexedValueForSearch(values.get(i));
@ -472,7 +472,7 @@ public abstract class MappedFieldType extends FieldType {
return new FuzzyQuery(createTerm(value), fuzziness.asDistance(BytesRefs.toString(value)), prefixLength, maxExpansions, transpositions); return new FuzzyQuery(createTerm(value), fuzziness.asDistance(BytesRefs.toString(value)), prefixLength, maxExpansions, transpositions);
} }
public Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryParseContext context) { public Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryShardContext context) {
PrefixQuery query = new PrefixQuery(createTerm(value)); PrefixQuery query = new PrefixQuery(createTerm(value));
if (method != null) { if (method != null) {
query.setRewriteMethod(method); query.setRewriteMethod(method);
@ -480,7 +480,7 @@ public abstract class MappedFieldType extends FieldType {
return query; return query;
} }
public Query regexpQuery(String value, int flags, int maxDeterminizedStates, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryParseContext context) { public Query regexpQuery(String value, int flags, int maxDeterminizedStates, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryShardContext context) {
RegexpQuery query = new RegexpQuery(createTerm(value), flags, maxDeterminizedStates); RegexpQuery query = new RegexpQuery(createTerm(value), flags, maxDeterminizedStates);
if (method != null) { if (method != null) {
query.setRewriteMethod(method); query.setRewriteMethod(method);

View File

@ -125,6 +125,7 @@ public class GeoShapeFieldMapper extends FieldMapper {
super(name, Defaults.FIELD_TYPE); super(name, Defaults.FIELD_TYPE);
} }
@Override
public GeoShapeFieldType fieldType() { public GeoShapeFieldType fieldType() {
return (GeoShapeFieldType)fieldType; return (GeoShapeFieldType)fieldType;
} }
@ -400,6 +401,10 @@ public class GeoShapeFieldMapper extends FieldMapper {
return this.defaultStrategy; return this.defaultStrategy;
} }
public PrefixTreeStrategy resolveStrategy(SpatialStrategy strategy) {
return resolveStrategy(strategy.getStrategyName());
}
public PrefixTreeStrategy resolveStrategy(String strategyName) { public PrefixTreeStrategy resolveStrategy(String strategyName) {
if (SpatialStrategy.RECURSIVE.getStrategyName().equals(strategyName)) { if (SpatialStrategy.RECURSIVE.getStrategyName().equals(strategyName)) {
recursiveStrategy.setPointsOnly(pointsOnly()); recursiveStrategy.setPointsOnly(pointsOnly());

View File

@ -40,7 +40,7 @@ import org.elasticsearch.index.mapper.MergeMappingException;
import org.elasticsearch.index.mapper.MergeResult; import org.elasticsearch.index.mapper.MergeResult;
import org.elasticsearch.index.mapper.MetadataFieldMapper; import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.index.mapper.ParseContext; import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.similarity.SimilarityLookupService; import org.elasticsearch.index.similarity.SimilarityLookupService;
import java.io.IOException; import java.io.IOException;
@ -186,7 +186,7 @@ public class AllFieldMapper extends MetadataFieldMapper {
} }
@Override @Override
public Query termQuery(Object value, QueryParseContext context) { public Query termQuery(Object value, QueryShardContext context) {
return queryStringTermQuery(createTerm(value)); return queryStringTermQuery(createTerm(value));
} }
} }

View File

@ -24,7 +24,12 @@ import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.queries.TermsQuery; import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.*; import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RegexpQuery;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
@ -36,8 +41,15 @@ import org.elasticsearch.common.util.iterable.Iterables;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.FieldDataType; import org.elasticsearch.index.fielddata.FieldDataType;
import org.elasticsearch.index.mapper.*; import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.MergeMappingException;
import org.elasticsearch.index.mapper.MergeResult;
import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.query.QueryShardContext;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
@ -48,7 +60,7 @@ import java.util.Map;
import static org.elasticsearch.index.mapper.core.TypeParsers.parseField; import static org.elasticsearch.index.mapper.core.TypeParsers.parseField;
/** /**
* *
*/ */
public class IdFieldMapper extends MetadataFieldMapper { public class IdFieldMapper extends MetadataFieldMapper {
@ -155,7 +167,7 @@ public class IdFieldMapper extends MetadataFieldMapper {
} }
@Override @Override
public Query termQuery(Object value, @Nullable QueryParseContext context) { public Query termQuery(Object value, @Nullable QueryShardContext context) {
if (indexOptions() != IndexOptions.NONE || context == null) { if (indexOptions() != IndexOptions.NONE || context == null) {
return super.termQuery(value, context); return super.termQuery(value, context);
} }
@ -164,7 +176,7 @@ public class IdFieldMapper extends MetadataFieldMapper {
} }
@Override @Override
public Query termsQuery(List values, @Nullable QueryParseContext context) { public Query termsQuery(List values, @Nullable QueryShardContext context) {
if (indexOptions() != IndexOptions.NONE || context == null) { if (indexOptions() != IndexOptions.NONE || context == null) {
return super.termsQuery(values, context); return super.termsQuery(values, context);
} }
@ -172,7 +184,7 @@ public class IdFieldMapper extends MetadataFieldMapper {
} }
@Override @Override
public Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryParseContext context) { public Query prefixQuery(String value, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryShardContext context) {
if (indexOptions() != IndexOptions.NONE || context == null) { if (indexOptions() != IndexOptions.NONE || context == null) {
return super.prefixQuery(value, method, context); return super.prefixQuery(value, method, context);
} }
@ -189,7 +201,7 @@ public class IdFieldMapper extends MetadataFieldMapper {
} }
@Override @Override
public Query regexpQuery(String value, int flags, int maxDeterminizedStates, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryParseContext context) { public Query regexpQuery(String value, int flags, int maxDeterminizedStates, @Nullable MultiTermQuery.RewriteMethod method, @Nullable QueryShardContext context) {
if (indexOptions() != IndexOptions.NONE || context == null) { if (indexOptions() != IndexOptions.NONE || context == null) {
return super.regexpQuery(value, flags, maxDeterminizedStates, method, context); return super.regexpQuery(value, flags, maxDeterminizedStates, method, context);
} }
@ -224,7 +236,7 @@ public class IdFieldMapper extends MetadataFieldMapper {
super(NAME, fieldType, Defaults.FIELD_TYPE, indexSettings); super(NAME, fieldType, Defaults.FIELD_TYPE, indexSettings);
this.path = path; this.path = path;
} }
private static MappedFieldType idFieldType(Settings indexSettings, MappedFieldType existing) { private static MappedFieldType idFieldType(Settings indexSettings, MappedFieldType existing) {
if (existing != null) { if (existing != null) {
return existing.clone(); return existing.clone();

View File

@ -38,7 +38,7 @@ import org.elasticsearch.index.mapper.MergeMappingException;
import org.elasticsearch.index.mapper.MergeResult; import org.elasticsearch.index.mapper.MergeResult;
import org.elasticsearch.index.mapper.MetadataFieldMapper; import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.index.mapper.ParseContext; import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;
@ -157,7 +157,7 @@ public class IndexFieldMapper extends MetadataFieldMapper {
* indices * indices
*/ */
@Override @Override
public Query termQuery(Object value, @Nullable QueryParseContext context) { public Query termQuery(Object value, @Nullable QueryShardContext context) {
if (context == null) { if (context == null) {
return super.termQuery(value, context); return super.termQuery(value, context);
} }
@ -171,7 +171,7 @@ public class IndexFieldMapper extends MetadataFieldMapper {
@Override @Override
public Query termsQuery(List values, QueryParseContext context) { public Query termsQuery(List values, QueryShardContext context) {
if (context == null) { if (context == null) {
return super.termsQuery(values, context); return super.termsQuery(values, context);
} }

View File

@ -34,8 +34,16 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.loader.SettingsLoader; import org.elasticsearch.common.settings.loader.SettingsLoader;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.fielddata.FieldDataType; import org.elasticsearch.index.fielddata.FieldDataType;
import org.elasticsearch.index.mapper.*; import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.MergeMappingException;
import org.elasticsearch.index.mapper.MergeResult;
import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.query.QueryShardContext;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -202,12 +210,12 @@ public class ParentFieldMapper extends MetadataFieldMapper {
} }
@Override @Override
public Query termQuery(Object value, @Nullable QueryParseContext context) { public Query termQuery(Object value, @Nullable QueryShardContext context) {
return termsQuery(Collections.singletonList(value), context); return termsQuery(Collections.singletonList(value), context);
} }
@Override @Override
public Query termsQuery(List values, @Nullable QueryParseContext context) { public Query termsQuery(List values, @Nullable QueryShardContext context) {
if (context == null) { if (context == null) {
return super.termsQuery(values, context); return super.termsQuery(values, context);
} }

View File

@ -43,7 +43,7 @@ import org.elasticsearch.index.mapper.MergeResult;
import org.elasticsearch.index.mapper.MetadataFieldMapper; import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.index.mapper.ParseContext; import org.elasticsearch.index.mapper.ParseContext;
import org.elasticsearch.index.mapper.Uid; import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -137,7 +137,7 @@ public class TypeFieldMapper extends MetadataFieldMapper {
} }
@Override @Override
public Query termQuery(Object value, @Nullable QueryParseContext context) { public Query termQuery(Object value, @Nullable QueryShardContext context) {
if (indexOptions() == IndexOptions.NONE) { if (indexOptions() == IndexOptions.NONE) {
return new ConstantScoreQuery(new PrefixQuery(new Term(UidFieldMapper.NAME, Uid.typePrefixAsBytes(BytesRefs.toBytesRef(value))))); return new ConstantScoreQuery(new PrefixQuery(new Term(UidFieldMapper.NAME, Uid.typePrefixAsBytes(BytesRefs.toBytesRef(value)))));
} }

View File

@ -25,6 +25,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
@ -42,8 +43,7 @@ import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.internal.TypeFieldMapper; import org.elasticsearch.index.mapper.internal.TypeFieldMapper;
import org.elasticsearch.index.percolator.stats.ShardPercolateService; import org.elasticsearch.index.percolator.stats.ShardPercolateService;
import org.elasticsearch.index.query.IndexQueryParserService; import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.settings.IndexSettings;
import org.elasticsearch.index.shard.AbstractIndexShardComponent; import org.elasticsearch.index.shard.AbstractIndexShardComponent;
import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.IndexShard;
@ -187,9 +187,9 @@ public class PercolatorQueriesRegistry extends AbstractIndexShardComponent imple
private Query parseQuery(String type, XContentParser parser) { private Query parseQuery(String type, XContentParser parser) {
String[] previousTypes = null; String[] previousTypes = null;
if (type != null) { if (type != null) {
QueryParseContext.setTypesWithPrevious(new String[]{type}); QueryShardContext.setTypesWithPrevious(new String[]{type});
} }
QueryParseContext context = queryParserService.getParseContext(); QueryShardContext context = queryParserService.getShardContext();
try { try {
context.reset(parser); context.reset(parser);
// This means that fields in the query need to exist in the mapping prior to registering this query // This means that fields in the query need to exist in the mapping prior to registering this query
@ -205,13 +205,13 @@ public class PercolatorQueriesRegistry extends AbstractIndexShardComponent imple
// if index.percolator.map_unmapped_fields_as_string is set to true, query can contain unmapped fields which will be mapped // if index.percolator.map_unmapped_fields_as_string is set to true, query can contain unmapped fields which will be mapped
// as an analyzed string. // as an analyzed string.
context.setAllowUnmappedFields(false); context.setAllowUnmappedFields(false);
context.setMapUnmappedFieldAsString(mapUnmappedFieldsAsString ? true : false); context.setMapUnmappedFieldAsString(mapUnmappedFieldsAsString);
return queryParserService.parseInnerQuery(context); return queryParserService.parseInnerQuery(context);
} catch (IOException e) { } catch (IOException e) {
throw new ParsingException(context, "Failed to parse", e); throw new ParsingException(parser.getTokenLocation(), "Failed to parse", e);
} finally { } finally {
if (type != null) { if (type != null) {
QueryParseContext.setTypes(previousTypes); QueryShardContext.setTypes(previousTypes);
} }
context.reset(null); context.reset(null);
} }

View File

@ -0,0 +1,266 @@
/*
* 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.search.Query;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.action.support.ToXContentToBytes;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* Base class for all classes producing lucene queries.
* Supports conversion to BytesReference and creation of lucene Query objects.
*/
public abstract class AbstractQueryBuilder<QB extends AbstractQueryBuilder> extends ToXContentToBytes implements QueryBuilder<QB> {
/** Default for boost to apply to resulting Lucene query. Defaults to 1.0*/
public static final float DEFAULT_BOOST = 1.0f;
public static final ParseField NAME_FIELD = new ParseField("_name");
public static final ParseField BOOST_FIELD = new ParseField("boost");
protected String queryName;
protected float boost = DEFAULT_BOOST;
protected AbstractQueryBuilder() {
super(XContentType.JSON);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
doXContent(builder, params);
builder.endObject();
return builder;
}
protected abstract void doXContent(XContentBuilder builder, Params params) throws IOException;
protected void printBoostAndQueryName(XContentBuilder builder) throws IOException {
builder.field("boost", boost);
if (queryName != null) {
builder.field("_name", queryName);
}
}
@Override
public final Query toQuery(QueryShardContext context) throws IOException {
Query query = doToQuery(context);
if (query != null) {
setFinalBoost(query);
if (queryName != null) {
context.addNamedQuery(queryName, query);
}
}
return query;
}
/**
* Sets the main boost to the query obtained by converting the current query into a lucene query.
* The default behaviour is to set the main boost, after verifying that we are not overriding any non default boost
* value that was previously set to the lucene query. That case would require some manual decision on how to combine
* the main boost with the boost coming from lucene by overriding this method.
* @throws IllegalStateException if the lucene query boost has already been set
*/
protected void setFinalBoost(Query query) {
if (query.getBoost() != AbstractQueryBuilder.DEFAULT_BOOST) {
throw new IllegalStateException("lucene query boost is already set, override setFinalBoost to define how to combine lucene boost with main boost");
}
query.setBoost(boost);
}
@Override
public final Query toFilter(QueryShardContext context) throws IOException {
Query result = null;
final boolean originalIsFilter = context.isFilter;
try {
context.isFilter = true;
result = toQuery(context);
} finally {
context.isFilter = originalIsFilter;
}
return result;
}
protected abstract Query doToQuery(QueryShardContext context) throws IOException;
/**
* Returns the query name for the query.
*/
@SuppressWarnings("unchecked")
@Override
public final QB queryName(String queryName) {
this.queryName = queryName;
return (QB) this;
}
/**
* Sets the query name for the query.
*/
@Override
public final String queryName() {
return queryName;
}
/**
* Returns the boost for this query.
*/
@Override
public final float boost() {
return this.boost;
}
/**
* Sets the boost for this query. Documents matching this query will (in addition to the normal
* weightings) have their score multiplied by the boost provided.
*/
@SuppressWarnings("unchecked")
@Override
public final QB boost(float boost) {
this.boost = boost;
return (QB) this;
}
@Override
public final QB readFrom(StreamInput in) throws IOException {
QB queryBuilder = doReadFrom(in);
queryBuilder.boost = in.readFloat();
queryBuilder.queryName = in.readOptionalString();
return queryBuilder;
}
protected abstract QB doReadFrom(StreamInput in) throws IOException;
@Override
public final void writeTo(StreamOutput out) throws IOException {
doWriteTo(out);
out.writeFloat(boost);
out.writeOptionalString(queryName);
}
protected abstract void doWriteTo(StreamOutput out) throws IOException;
protected final QueryValidationException addValidationError(String validationError, QueryValidationException validationException) {
return QueryValidationException.addValidationError(getName(), validationError, validationException);
}
@Override
public final boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
@SuppressWarnings("unchecked")
QB other = (QB) obj;
return Objects.equals(queryName, other.queryName) &&
Objects.equals(boost, other.boost) &&
doEquals(other);
}
/**
* Indicates whether some other {@link QueryBuilder} object of the same type is "equal to" this one.
*/
protected abstract boolean doEquals(QB other);
@Override
public final int hashCode() {
return Objects.hash(getClass(), queryName, boost, doHashCode());
}
protected abstract int doHashCode();
/**
* This helper method checks if the object passed in is a string, if so it
* converts it to a {@link BytesRef}.
* @param obj the input object
* @return the same input object or a {@link BytesRef} representation if input was of type string
*/
protected static Object convertToBytesRefIfString(Object obj) {
if (obj instanceof String) {
return BytesRefs.toBytesRef(obj);
}
return obj;
}
/**
* This helper method checks if the object passed in is a {@link BytesRef}, if so it
* converts it to a utf8 string.
* @param obj the input object
* @return the same input object or a utf8 string if input was of type {@link BytesRef}
*/
protected static Object convertToStringIfBytesRef(Object obj) {
if (obj instanceof BytesRef) {
return ((BytesRef) obj).utf8ToString();
}
return obj;
}
/**
* Helper method to convert collection of {@link QueryBuilder} instances to lucene
* {@link Query} instances. {@link QueryBuilder} that return <tt>null</tt> calling
* their {@link QueryBuilder#toQuery(QueryShardContext)} method are not added to the
* resulting collection.
*/
protected static Collection<Query> toQueries(Collection<QueryBuilder> queryBuilders, QueryShardContext context) throws QueryShardException,
IOException {
List<Query> queries = new ArrayList<>(queryBuilders.size());
for (QueryBuilder queryBuilder : queryBuilders) {
Query query = queryBuilder.toQuery(context);
if (query != null) {
queries.add(query);
}
}
return queries;
}
@Override
public String getName() {
//default impl returns the same as writeable name, but we keep the distinction between the two just to make sure
return getWriteableName();
}
protected final void writeQueries(StreamOutput out, List<? extends QueryBuilder> queries) throws IOException {
out.writeVInt(queries.size());
for (QueryBuilder query : queries) {
out.writeQuery(query);
}
}
protected final List<QueryBuilder> readQueries(StreamInput in) throws IOException {
List<QueryBuilder> queries = new ArrayList<>();
int size = in.readVInt();
for (int i = 0; i < size; i++) {
queries.add(in.readQuery());
}
return queries;
}
}

View File

@ -0,0 +1,165 @@
/*
* 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.util.BytesRef;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Objects;
public abstract class BaseTermQueryBuilder<QB extends BaseTermQueryBuilder<QB>> extends AbstractQueryBuilder<QB> {
/** Name of field to match against. */
protected final String fieldName;
/** Value to find matches for. */
protected final Object value;
/**
* Constructs a new base term query.
*
* @param fieldName The name of the field
* @param value The value of the term
*/
public BaseTermQueryBuilder(String fieldName, String value) {
this(fieldName, (Object) value);
}
/**
* Constructs a new base term query.
*
* @param fieldName The name of the field
* @param value The value of the term
*/
public BaseTermQueryBuilder(String fieldName, int value) {
this(fieldName, (Object) value);
}
/**
* Constructs a new base term query.
*
* @param fieldName The name of the field
* @param value The value of the term
*/
public BaseTermQueryBuilder(String fieldName, long value) {
this(fieldName, (Object) value);
}
/**
* Constructs a new base term query.
*
* @param fieldName The name of the field
* @param value The value of the term
*/
public BaseTermQueryBuilder(String fieldName, float value) {
this(fieldName, (Object) value);
}
/**
* Constructs a new base term query.
*
* @param fieldName The name of the field
* @param value The value of the term
*/
public BaseTermQueryBuilder(String fieldName, double value) {
this(fieldName, (Object) value);
}
/**
* Constructs a new base term query.
*
* @param fieldName The name of the field
* @param value The value of the term
*/
public BaseTermQueryBuilder(String fieldName, boolean value) {
this(fieldName, (Object) value);
}
/**
* Constructs a new base term query.
* In case value is assigned to a string, we internally convert it to a {@link BytesRef}
* because in {@link TermQueryParser} and {@link SpanTermQueryParser} string values are parsed to {@link BytesRef}
* and we want internal representation of query to be equal regardless of whether it was created from XContent or via Java API.
*
* @param fieldName The name of the field
* @param value The value of the term
*/
public BaseTermQueryBuilder(String fieldName, Object value) {
if (Strings.isEmpty(fieldName)) {
throw new IllegalArgumentException("field name is null or empty");
}
if (value == null) {
throw new IllegalArgumentException("value cannot be null");
}
this.fieldName = fieldName;
this.value = convertToBytesRefIfString(value);
}
/** Returns the field name used in this query. */
public String fieldName() {
return this.fieldName;
}
/**
* Returns the value used in this query.
* If necessary, converts internal {@link BytesRef} representation back to string.
*/
public Object value() {
return convertToStringIfBytesRef(this.value);
}
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(getName());
builder.startObject(fieldName);
builder.field("value", convertToStringIfBytesRef(this.value));
printBoostAndQueryName(builder);
builder.endObject();
builder.endObject();
}
@Override
protected final int doHashCode() {
return Objects.hash(fieldName, value);
}
@Override
protected final boolean doEquals(BaseTermQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(value, other.value);
}
@Override
protected final QB doReadFrom(StreamInput in) throws IOException {
return createBuilder(in.readString(), in.readGenericValue());
}
protected abstract QB createBuilder(String fieldName, Object value);
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldName);
out.writeGenericValue(value);
}
}

View File

@ -19,17 +19,35 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
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.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import static org.elasticsearch.common.lucene.search.Queries.fixNegativeQueryIfNeeded;
/** /**
* A Query that matches documents matching boolean combinations of other queries. * A Query that matches documents matching boolean combinations of other queries.
*/ */
public class BoolQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<BoolQueryBuilder> { public class BoolQueryBuilder extends AbstractQueryBuilder<BoolQueryBuilder> {
public static final String NAME = "bool";
public static final boolean ADJUST_PURE_NEGATIVE_DEFAULT = true;
public static final boolean DISABLE_COORD_DEFAULT = false;
static final BoolQueryBuilder PROTOTYPE = new BoolQueryBuilder();
private final List<QueryBuilder> mustClauses = new ArrayList<>(); private final List<QueryBuilder> mustClauses = new ArrayList<>();
@ -39,63 +57,92 @@ public class BoolQueryBuilder extends QueryBuilder implements BoostableQueryBuil
private final List<QueryBuilder> shouldClauses = new ArrayList<>(); private final List<QueryBuilder> shouldClauses = new ArrayList<>();
private float boost = -1; private boolean disableCoord = DISABLE_COORD_DEFAULT;
private Boolean disableCoord; private boolean adjustPureNegative = ADJUST_PURE_NEGATIVE_DEFAULT;
private String minimumShouldMatch; private String minimumShouldMatch;
private Boolean adjustPureNegative;
private String queryName;
/** /**
* Adds a query that <b>must</b> appear in the matching documents and will * Adds a query that <b>must</b> appear in the matching documents and will
* contribute to scoring. * contribute to scoring. No <tt>null</tt> value allowed.
*/ */
public BoolQueryBuilder must(QueryBuilder queryBuilder) { public BoolQueryBuilder must(QueryBuilder queryBuilder) {
if (queryBuilder == null) {
throw new IllegalArgumentException("inner bool query clause cannot be null");
}
mustClauses.add(queryBuilder); mustClauses.add(queryBuilder);
return this; return this;
} }
/**
* Gets the queries that <b>must</b> appear in the matching documents.
*/
public List<QueryBuilder> must() {
return this.mustClauses;
}
/** /**
* Adds a query that <b>must</b> appear in the matching documents but will * Adds a query that <b>must</b> appear in the matching documents but will
* not contribute to scoring. * not contribute to scoring. No <tt>null</tt> value allowed.
*/ */
public BoolQueryBuilder filter(QueryBuilder queryBuilder) { public BoolQueryBuilder filter(QueryBuilder queryBuilder) {
if (queryBuilder == null) {
throw new IllegalArgumentException("inner bool query clause cannot be null");
}
filterClauses.add(queryBuilder); filterClauses.add(queryBuilder);
return this; return this;
} }
/** /**
* Adds a query that <b>must not</b> appear in the matching documents and * Gets the queries that <b>must</b> appear in the matching documents but don't conntribute to scoring
* will not contribute to scoring. */
public List<QueryBuilder> filter() {
return this.filterClauses;
}
/**
* Adds a query that <b>must not</b> appear in the matching documents.
* No <tt>null</tt> value allowed.
*/ */
public BoolQueryBuilder mustNot(QueryBuilder queryBuilder) { public BoolQueryBuilder mustNot(QueryBuilder queryBuilder) {
if (queryBuilder == null) {
throw new IllegalArgumentException("inner bool query clause cannot be null");
}
mustNotClauses.add(queryBuilder); mustNotClauses.add(queryBuilder);
return this; return this;
} }
/** /**
* Adds a query that <i>should</i> appear in the matching documents. For a boolean query with no * Gets the queries that <b>must not</b> appear in the matching documents.
*/
public List<QueryBuilder> mustNot() {
return this.mustNotClauses;
}
/**
* Adds a clause that <i>should</i> be matched by the returned documents. For a boolean query with no
* <tt>MUST</tt> clauses one or more <code>SHOULD</code> clauses must match a document * <tt>MUST</tt> clauses one or more <code>SHOULD</code> clauses must match a document
* for the BooleanQuery to match. * for the BooleanQuery to match. No <tt>null</tt> value allowed.
* *
* @see #minimumNumberShouldMatch(int) * @see #minimumNumberShouldMatch(int)
*/ */
public BoolQueryBuilder should(QueryBuilder queryBuilder) { public BoolQueryBuilder should(QueryBuilder queryBuilder) {
if (queryBuilder == null) {
throw new IllegalArgumentException("inner bool query clause cannot be null");
}
shouldClauses.add(queryBuilder); shouldClauses.add(queryBuilder);
return this; return this;
} }
/** /**
* Sets the boost for this query. Documents matching this query will (in addition to the normal * Gets the list of clauses that <b>should</b> be matched by the returned documents.
* weightings) have their score multiplied by the boost provided. *
* @see #should(QueryBuilder)
* @see #minimumNumberShouldMatch(int)
*/ */
@Override public List<QueryBuilder> should() {
public BoolQueryBuilder boost(float boost) { return this.shouldClauses;
this.boost = boost;
return this;
} }
/** /**
@ -106,6 +153,13 @@ public class BoolQueryBuilder extends QueryBuilder implements BoostableQueryBuil
return this; return this;
} }
/**
* @return whether the <tt>Similarity#coord(int,int)</tt> in scoring are disabled. Defaults to <tt>false</tt>.
*/
public boolean disableCoord() {
return this.disableCoord;
}
/** /**
* Specifies a minimum number of the optional (should) boolean clauses which must be satisfied. * Specifies a minimum number of the optional (should) boolean clauses which must be satisfied.
* <p> * <p>
@ -124,6 +178,23 @@ public class BoolQueryBuilder extends QueryBuilder implements BoostableQueryBuil
return this; return this;
} }
/**
* Specifies a minimum number of the optional (should) boolean clauses which must be satisfied.
* @see BoolQueryBuilder#minimumNumberShouldMatch(int)
*/
public BoolQueryBuilder minimumNumberShouldMatch(String minimumNumberShouldMatch) {
this.minimumShouldMatch = minimumNumberShouldMatch;
return this;
}
/**
* @return the string representation of the minimumShouldMatch settings for this query
*/
public String minimumShouldMatch() {
return this.minimumShouldMatch;
}
/** /**
* Sets the minimum should match using the special syntax (for example, supporting percentage). * Sets the minimum should match using the special syntax (for example, supporting percentage).
*/ */
@ -139,7 +210,7 @@ public class BoolQueryBuilder extends QueryBuilder implements BoostableQueryBuil
public boolean hasClauses() { public boolean hasClauses() {
return !(mustClauses.isEmpty() && shouldClauses.isEmpty() && mustNotClauses.isEmpty() && filterClauses.isEmpty()); return !(mustClauses.isEmpty() && shouldClauses.isEmpty() && mustNotClauses.isEmpty() && filterClauses.isEmpty());
} }
/** /**
* If a boolean query contains only negative ("must not") clauses should the * If a boolean query contains only negative ("must not") clauses should the
* BooleanQuery be enhanced with a {@link MatchAllDocsQuery} in order to act * BooleanQuery be enhanced with a {@link MatchAllDocsQuery} in order to act
@ -151,52 +222,126 @@ public class BoolQueryBuilder extends QueryBuilder implements BoostableQueryBuil
} }
/** /**
* Sets the query name for the filter that can be used when searching for matched_filters per hit. * @return the setting for the adjust_pure_negative setting in this query
*/ */
public BoolQueryBuilder queryName(String queryName) { public boolean adjustPureNegative() {
this.queryName = queryName; return this.adjustPureNegative;
return this;
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("bool"); builder.startObject(NAME);
doXArrayContent("must", mustClauses, builder, params); doXArrayContent("must", mustClauses, builder, params);
doXArrayContent("filter", filterClauses, builder, params); doXArrayContent("filter", filterClauses, builder, params);
doXArrayContent("must_not", mustNotClauses, builder, params); doXArrayContent("must_not", mustNotClauses, builder, params);
doXArrayContent("should", shouldClauses, builder, params); doXArrayContent("should", shouldClauses, builder, params);
if (boost != -1) { builder.field("disable_coord", disableCoord);
builder.field("boost", boost); builder.field("adjust_pure_negative", adjustPureNegative);
}
if (disableCoord != null) {
builder.field("disable_coord", disableCoord);
}
if (minimumShouldMatch != null) { if (minimumShouldMatch != null) {
builder.field("minimum_should_match", minimumShouldMatch); builder.field("minimum_should_match", minimumShouldMatch);
} }
if (adjustPureNegative != null) { printBoostAndQueryName(builder);
builder.field("adjust_pure_negative", adjustPureNegative);
}
if (queryName != null) {
builder.field("_name", queryName);
}
builder.endObject(); builder.endObject();
} }
private void doXArrayContent(String field, List<QueryBuilder> clauses, XContentBuilder builder, Params params) throws IOException { private static void doXArrayContent(String field, List<QueryBuilder> clauses, XContentBuilder builder, Params params) throws IOException {
if (clauses.isEmpty()) { if (clauses.isEmpty()) {
return; return;
} }
if (clauses.size() == 1) { builder.startArray(field);
builder.field(field); for (QueryBuilder clause : clauses) {
clauses.get(0).toXContent(builder, params); clause.toXContent(builder, params);
} else { }
builder.startArray(field); builder.endArray();
for (QueryBuilder clause : clauses) { }
clause.toXContent(builder, params);
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
BooleanQuery.Builder booleanQueryBuilder = new BooleanQuery.Builder();
booleanQueryBuilder.setDisableCoord(disableCoord);
addBooleanClauses(context, booleanQueryBuilder, mustClauses, BooleanClause.Occur.MUST);
addBooleanClauses(context, booleanQueryBuilder, mustNotClauses, BooleanClause.Occur.MUST_NOT);
addBooleanClauses(context, booleanQueryBuilder, shouldClauses, BooleanClause.Occur.SHOULD);
addBooleanClauses(context, booleanQueryBuilder, filterClauses, BooleanClause.Occur.FILTER);
BooleanQuery booleanQuery = booleanQueryBuilder.build();
if (booleanQuery.clauses().isEmpty()) {
return new MatchAllDocsQuery();
}
booleanQuery = Queries.applyMinimumShouldMatch(booleanQuery, minimumShouldMatch);
return adjustPureNegative ? fixNegativeQueryIfNeeded(booleanQuery) : booleanQuery;
}
private void addBooleanClauses(QueryShardContext context, BooleanQuery.Builder booleanQueryBuilder, List<QueryBuilder> clauses, Occur occurs) throws IOException {
for (QueryBuilder query : clauses) {
Query luceneQuery = null;
switch (occurs) {
case SHOULD:
if (context.isFilter() && minimumShouldMatch == null) {
minimumShouldMatch = "1";
}
luceneQuery = query.toQuery(context);
break;
case FILTER:
case MUST_NOT:
luceneQuery = query.toFilter(context);
break;
case MUST:
luceneQuery = query.toQuery(context);
}
if (luceneQuery != null) {
booleanQueryBuilder.add(new BooleanClause(luceneQuery, occurs));
} }
builder.endArray();
} }
} }
@Override
protected int doHashCode() {
return Objects.hash(adjustPureNegative, disableCoord,
minimumShouldMatch, mustClauses, shouldClauses, mustNotClauses, filterClauses);
}
@Override
protected boolean doEquals(BoolQueryBuilder other) {
return Objects.equals(adjustPureNegative, other.adjustPureNegative) &&
Objects.equals(disableCoord, other.disableCoord) &&
Objects.equals(minimumShouldMatch, other.minimumShouldMatch) &&
Objects.equals(mustClauses, other.mustClauses) &&
Objects.equals(shouldClauses, other.shouldClauses) &&
Objects.equals(mustNotClauses, other.mustNotClauses) &&
Objects.equals(filterClauses, other.filterClauses);
}
@Override
protected BoolQueryBuilder doReadFrom(StreamInput in) throws IOException {
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
List<QueryBuilder> queryBuilders = readQueries(in);
boolQueryBuilder.mustClauses.addAll(queryBuilders);
queryBuilders = readQueries(in);
boolQueryBuilder.mustNotClauses.addAll(queryBuilders);
queryBuilders = readQueries(in);
boolQueryBuilder.shouldClauses.addAll(queryBuilders);
queryBuilders = readQueries(in);
boolQueryBuilder.filterClauses.addAll(queryBuilders);
boolQueryBuilder.adjustPureNegative = in.readBoolean();
boolQueryBuilder.disableCoord = in.readBoolean();
boolQueryBuilder.minimumShouldMatch = in.readOptionalString();
return boolQueryBuilder;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
writeQueries(out, mustClauses);
writeQueries(out, mustNotClauses);
writeQueries(out, shouldClauses);
writeQueries(out, filterClauses);
out.writeBoolean(adjustPureNegative);
out.writeBoolean(disableCoord);
out.writeOptionalString(minimumShouldMatch);
}
} }

View File

@ -19,13 +19,9 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
@ -33,14 +29,10 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.elasticsearch.common.lucene.search.Queries.fixNegativeQueryIfNeeded;
/** /**
* * Parser for bool query
*/ */
public class BoolQueryParser implements QueryParser { public class BoolQueryParser implements QueryParser<BoolQueryBuilder> {
public static final String NAME = "bool";
@Inject @Inject
public BoolQueryParser(Settings settings) { public BoolQueryParser(Settings settings) {
@ -49,23 +41,27 @@ public class BoolQueryParser implements QueryParser {
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME}; return new String[]{BoolQueryBuilder.NAME};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public BoolQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, ParsingException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
boolean disableCoord = false; boolean disableCoord = BoolQueryBuilder.DISABLE_COORD_DEFAULT;
float boost = 1.0f; boolean adjustPureNegative = BoolQueryBuilder.ADJUST_PURE_NEGATIVE_DEFAULT;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String minimumShouldMatch = null; String minimumShouldMatch = null;
List<BooleanClause> clauses = new ArrayList<>(); final List<QueryBuilder> mustClauses = new ArrayList<>();
boolean adjustPureNegative = true; final List<QueryBuilder> mustNotClauses = new ArrayList<>();
final List<QueryBuilder> shouldClauses = new ArrayList<>();
final List<QueryBuilder> filterClauses = new ArrayList<>();
String queryName = null; String queryName = null;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
QueryBuilder query;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
@ -74,69 +70,47 @@ public class BoolQueryParser implements QueryParser {
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
switch (currentFieldName) { switch (currentFieldName) {
case "must": case "must":
Query query = parseContext.parseInnerQuery(); query = parseContext.parseInnerQueryBuilder();
if (query != null) { mustClauses.add(query);
clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST));
}
break; break;
case "should": case "should":
query = parseContext.parseInnerQuery(); query = parseContext.parseInnerQueryBuilder();
if (query != null) { shouldClauses.add(query);
clauses.add(new BooleanClause(query, BooleanClause.Occur.SHOULD));
if (parseContext.isFilter() && minimumShouldMatch == null) {
minimumShouldMatch = "1";
}
}
break; break;
case "filter": case "filter":
query = parseContext.parseInnerFilter(); query = parseContext.parseInnerQueryBuilder();
if (query != null) { filterClauses.add(query);
clauses.add(new BooleanClause(query, BooleanClause.Occur.FILTER));
}
break; break;
case "must_not": case "must_not":
case "mustNot": case "mustNot":
query = parseContext.parseInnerFilter(); query = parseContext.parseInnerQueryBuilder();
if (query != null) { mustNotClauses.add(query);
clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST_NOT));
}
break; break;
default: default:
throw new ParsingException(parseContext, "[bool] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[bool] query does not support [" + currentFieldName + "]");
} }
} else if (token == XContentParser.Token.START_ARRAY) { } else if (token == XContentParser.Token.START_ARRAY) {
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
switch (currentFieldName) { switch (currentFieldName) {
case "must": case "must":
Query query = parseContext.parseInnerQuery(); query = parseContext.parseInnerQueryBuilder();
if (query != null) { mustClauses.add(query);
clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST));
}
break; break;
case "should": case "should":
query = parseContext.parseInnerQuery(); query = parseContext.parseInnerQueryBuilder();
if (query != null) { shouldClauses.add(query);
clauses.add(new BooleanClause(query, BooleanClause.Occur.SHOULD));
if (parseContext.isFilter() && minimumShouldMatch == null) {
minimumShouldMatch = "1";
}
}
break; break;
case "filter": case "filter":
query = parseContext.parseInnerFilter(); query = parseContext.parseInnerQueryBuilder();
if (query != null) { filterClauses.add(query);
clauses.add(new BooleanClause(query, BooleanClause.Occur.FILTER));
}
break; break;
case "must_not": case "must_not":
case "mustNot": case "mustNot":
query = parseContext.parseInnerFilter(); query = parseContext.parseInnerQueryBuilder();
if (query != null) { mustNotClauses.add(query);
clauses.add(new BooleanClause(query, BooleanClause.Occur.MUST_NOT));
}
break; break;
default: default:
throw new ParsingException(parseContext, "bool query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "bool query does not support [" + currentFieldName + "]");
} }
} }
} else if (token.isValue()) { } else if (token.isValue()) {
@ -153,27 +127,33 @@ public class BoolQueryParser implements QueryParser {
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[bool] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[bool] query does not support [" + currentFieldName + "]");
} }
} }
} }
BoolQueryBuilder boolQuery = new BoolQueryBuilder();
for (QueryBuilder queryBuilder : mustClauses) {
boolQuery.must(queryBuilder);
}
for (QueryBuilder queryBuilder : mustNotClauses) {
boolQuery.mustNot(queryBuilder);
}
for (QueryBuilder queryBuilder : shouldClauses) {
boolQuery.should(queryBuilder);
}
for (QueryBuilder queryBuilder : filterClauses) {
boolQuery.filter(queryBuilder);
}
boolQuery.boost(boost);
boolQuery.disableCoord(disableCoord);
boolQuery.adjustPureNegative(adjustPureNegative);
boolQuery.minimumNumberShouldMatch(minimumShouldMatch);
boolQuery.queryName(queryName);
return boolQuery;
}
if (clauses.isEmpty()) { @Override
return new MatchAllDocsQuery(); public BoolQueryBuilder getBuilderPrototype() {
} return BoolQueryBuilder.PROTOTYPE;
BooleanQuery.Builder booleanQueryBuilder = new BooleanQuery.Builder();
booleanQueryBuilder.setDisableCoord(disableCoord);
for (BooleanClause clause : clauses) {
booleanQueryBuilder.add(clause);
}
BooleanQuery booleanQuery = booleanQueryBuilder.build();
booleanQuery.setBoost(boost);
booleanQuery = Queries.applyMinimumShouldMatch(booleanQuery, minimumShouldMatch);
Query query = adjustPureNegative ? fixNegativeQueryIfNeeded(booleanQuery) : booleanQuery;
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
return query;
} }
} }

View File

@ -1,33 +0,0 @@
/*
* 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;
/**
* Query builder which allow setting some boost
*/
public interface BoostableQueryBuilder<B extends BoostableQueryBuilder<B>> {
/**
* Sets the boost for this query. Documents matching this query will (in addition to the normal
* weightings) have their score multiplied by the boost provided.
*/
B boost(float boost);
}

View File

@ -19,9 +19,14 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.apache.lucene.queries.BoostingQuery;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
/** /**
* The BoostingQuery class can be used to effectively demote results that match a given query. * The BoostingQuery class can be used to effectively demote results that match a given query.
@ -35,63 +40,122 @@ import java.io.IOException;
* multiplied by the supplied "boost" parameter, so this should be less than 1 to achieve a * multiplied by the supplied "boost" parameter, so this should be less than 1 to achieve a
* demoting effect * demoting effect
*/ */
public class BoostingQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<BoostingQueryBuilder> { public class BoostingQueryBuilder extends AbstractQueryBuilder<BoostingQueryBuilder> {
private QueryBuilder positiveQuery; public static final String NAME = "boosting";
private QueryBuilder negativeQuery; private final QueryBuilder positiveQuery;
private final QueryBuilder negativeQuery;
private float negativeBoost = -1; private float negativeBoost = -1;
private float boost = -1; static final BoostingQueryBuilder PROTOTYPE = new BoostingQueryBuilder(EmptyQueryBuilder.PROTOTYPE, EmptyQueryBuilder.PROTOTYPE);
public BoostingQueryBuilder() { /**
* Create a new {@link BoostingQueryBuilder}
} *
* @param positiveQuery the positive query for this boosting query.
public BoostingQueryBuilder positive(QueryBuilder positiveQuery) { * @param negativeQuery the negative query for this boosting query.
*/
public BoostingQueryBuilder(QueryBuilder positiveQuery, QueryBuilder negativeQuery) {
if (positiveQuery == null) {
throw new IllegalArgumentException("inner clause [positive] cannot be null.");
}
if (negativeQuery == null) {
throw new IllegalArgumentException("inner clause [negative] cannot be null.");
}
this.positiveQuery = positiveQuery; this.positiveQuery = positiveQuery;
return this;
}
public BoostingQueryBuilder negative(QueryBuilder negativeQuery) {
this.negativeQuery = negativeQuery; this.negativeQuery = negativeQuery;
return this;
} }
/**
* Get the positive query for this boosting query.
*/
public QueryBuilder positiveQuery() {
return this.positiveQuery;
}
/**
* Get the negative query for this boosting query.
*/
public QueryBuilder negativeQuery() {
return this.negativeQuery;
}
/**
* Set the negative boost factor.
*/
public BoostingQueryBuilder negativeBoost(float negativeBoost) { public BoostingQueryBuilder negativeBoost(float negativeBoost) {
if (negativeBoost < 0) {
throw new IllegalArgumentException("query requires negativeBoost to be set to positive value");
}
this.negativeBoost = negativeBoost; this.negativeBoost = negativeBoost;
return this; return this;
} }
@Override /**
public BoostingQueryBuilder boost(float boost) { * Get the negative boost factor.
this.boost = boost; */
return this; public float negativeBoost() {
return this.negativeBoost;
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
if (positiveQuery == null) { builder.startObject(NAME);
throw new IllegalArgumentException("boosting query requires positive query to be set");
}
if (negativeQuery == null) {
throw new IllegalArgumentException("boosting query requires negative query to be set");
}
if (negativeBoost == -1) {
throw new IllegalArgumentException("boosting query requires negativeBoost to be set");
}
builder.startObject(BoostingQueryParser.NAME);
builder.field("positive"); builder.field("positive");
positiveQuery.toXContent(builder, params); positiveQuery.toXContent(builder, params);
builder.field("negative"); builder.field("negative");
negativeQuery.toXContent(builder, params); negativeQuery.toXContent(builder, params);
builder.field("negative_boost", negativeBoost); builder.field("negative_boost", negativeBoost);
printBoostAndQueryName(builder);
if (boost != -1) {
builder.field("boost", boost);
}
builder.endObject(); builder.endObject();
} }
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
Query positive = positiveQuery.toQuery(context);
Query negative = negativeQuery.toQuery(context);
// make upstream queries ignore this query by returning `null`
// if either inner query builder returns null
if (positive == null || negative == null) {
return null;
}
return new BoostingQuery(positive, negative, negativeBoost);
}
@Override
protected int doHashCode() {
return Objects.hash(negativeBoost, positiveQuery, negativeQuery);
}
@Override
protected boolean doEquals(BoostingQueryBuilder other) {
return Objects.equals(negativeBoost, other.negativeBoost) &&
Objects.equals(positiveQuery, other.positiveQuery) &&
Objects.equals(negativeQuery, other.negativeQuery);
}
@Override
protected BoostingQueryBuilder doReadFrom(StreamInput in) throws IOException {
QueryBuilder positiveQuery = in.readQuery();
QueryBuilder negativeQuery = in.readQuery();
BoostingQueryBuilder boostingQuery = new BoostingQueryBuilder(positiveQuery, negativeQuery);
boostingQuery.negativeBoost = in.readFloat();
return boostingQuery;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeQuery(positiveQuery);
out.writeQuery(negativeQuery);
out.writeFloat(negativeBoost);
}
} }

View File

@ -19,40 +19,32 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.queries.BoostingQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException; import java.io.IOException;
/** /**
* * Parser for boosting query
*/ */
public class BoostingQueryParser implements QueryParser { public class BoostingQueryParser implements QueryParser<BoostingQueryBuilder> {
public static final String NAME = "boosting";
@Inject
public BoostingQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME}; return new String[]{BoostingQueryBuilder.NAME};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public BoostingQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
Query positiveQuery = null; QueryBuilder positiveQuery = null;
boolean positiveQueryFound = false; boolean positiveQueryFound = false;
Query negativeQuery = null; QueryBuilder negativeQuery = null;
boolean negativeQueryFound = false; boolean negativeQueryFound = false;
float boost = -1; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
float negativeBoost = -1; float negativeBoost = -1;
String queryName = null;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
@ -61,44 +53,46 @@ public class BoostingQueryParser implements QueryParser {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
if ("positive".equals(currentFieldName)) { if ("positive".equals(currentFieldName)) {
positiveQuery = parseContext.parseInnerQuery(); positiveQuery = parseContext.parseInnerQueryBuilder();
positiveQueryFound = true; positiveQueryFound = true;
} else if ("negative".equals(currentFieldName)) { } else if ("negative".equals(currentFieldName)) {
negativeQuery = parseContext.parseInnerQuery(); negativeQuery = parseContext.parseInnerQueryBuilder();
negativeQueryFound = true; negativeQueryFound = true;
} else { } else {
throw new ParsingException(parseContext, "[boosting] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[boosting] query does not support [" + currentFieldName + "]");
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if ("negative_boost".equals(currentFieldName) || "negativeBoost".equals(currentFieldName)) { if ("negative_boost".equals(currentFieldName) || "negativeBoost".equals(currentFieldName)) {
negativeBoost = parser.floatValue(); negativeBoost = parser.floatValue();
} else if ("_name".equals(currentFieldName)) {
queryName = parser.text();
} else if ("boost".equals(currentFieldName)) { } else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue(); boost = parser.floatValue();
} else { } else {
throw new ParsingException(parseContext, "[boosting] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[boosting] query does not support [" + currentFieldName + "]");
} }
} }
} }
if (positiveQuery == null && !positiveQueryFound) { if (!positiveQueryFound) {
throw new ParsingException(parseContext, "[boosting] query requires 'positive' query to be set'"); throw new ParsingException(parser.getTokenLocation(), "[boosting] query requires 'positive' query to be set'");
} }
if (negativeQuery == null && !negativeQueryFound) { if (!negativeQueryFound) {
throw new ParsingException(parseContext, "[boosting] query requires 'negative' query to be set'"); throw new ParsingException(parser.getTokenLocation(), "[boosting] query requires 'negative' query to be set'");
} }
if (negativeBoost == -1) { if (negativeBoost < 0) {
throw new ParsingException(parseContext, "[boosting] query requires 'negative_boost' to be set'"); throw new ParsingException(parser.getTokenLocation(), "[boosting] query requires 'negative_boost' to be set to be a positive value'");
} }
// parsers returned null BoostingQueryBuilder boostingQuery = new BoostingQueryBuilder(positiveQuery, negativeQuery);
if (positiveQuery == null || negativeQuery == null) { boostingQuery.negativeBoost(negativeBoost);
return null; boostingQuery.boost(boost);
} boostingQuery.queryName(queryName);
BoostingQuery boostingQuery = new BoostingQuery(positiveQuery, negativeQuery, negativeBoost);
if (boost != -1) {
boostingQuery.setBoost(boost);
}
return boostingQuery; return boostingQuery;
} }
@Override
public BoostingQueryBuilder getBuilderPrototype() {
return BoostingQueryBuilder.PROTOTYPE;
}
} }

View File

@ -19,12 +19,24 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
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.index.Term;
import org.apache.lucene.queries.ExtendedCommonTermsQuery;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.similarities.Similarity; import org.apache.lucene.search.similarities.Similarity;
import org.apache.lucene.util.BytesRefBuilder;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.MappedFieldType;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
/** /**
* CommonTermsQuery query is a query that executes high-frequency terms in a * CommonTermsQuery query is a query that executes high-frequency terms in a
@ -41,46 +53,58 @@ import java.io.IOException;
* low-frequency terms are matched such that this query can improve query * low-frequency terms are matched such that this query can improve query
* execution times significantly if applicable. * execution times significantly if applicable.
*/ */
public class CommonTermsQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<CommonTermsQueryBuilder> { public class CommonTermsQueryBuilder extends AbstractQueryBuilder<CommonTermsQueryBuilder> {
public static enum Operator { public static final String NAME = "common";
OR, AND
}
private final String name; public static final float DEFAULT_CUTOFF_FREQ = 0.01f;
public static final Operator DEFAULT_HIGH_FREQ_OCCUR = Operator.OR;
public static final Operator DEFAULT_LOW_FREQ_OCCUR = Operator.OR;
public static final boolean DEFAULT_DISABLE_COORD = true;
private final String fieldName;
private final Object text; private final Object text;
private Operator highFreqOperator = null; private Operator highFreqOperator = DEFAULT_HIGH_FREQ_OCCUR;
private Operator lowFreqOperator = null; private Operator lowFreqOperator = DEFAULT_LOW_FREQ_OCCUR;
private String analyzer = null; private String analyzer = null;
private Float boost = null;
private String lowFreqMinimumShouldMatch = null; private String lowFreqMinimumShouldMatch = null;
private String highFreqMinimumShouldMatch = null; private String highFreqMinimumShouldMatch = null;
private Boolean disableCoord = null; private boolean disableCoord = DEFAULT_DISABLE_COORD;
private Float cutoffFrequency = null; private float cutoffFrequency = DEFAULT_CUTOFF_FREQ;
private String queryName; static final CommonTermsQueryBuilder PROTOTYPE = new CommonTermsQueryBuilder("field", "text");
/** /**
* Constructs a new common terms query. * Constructs a new common terms query.
*/ */
public CommonTermsQueryBuilder(String name, Object text) { public CommonTermsQueryBuilder(String fieldName, Object text) {
if (name == null) { if (Strings.isEmpty(fieldName)) {
throw new IllegalArgumentException("Field name must not be null"); throw new IllegalArgumentException("field name is null or empty");
} }
if (text == null) { if (text == null) {
throw new IllegalArgumentException("Query must not be null"); throw new IllegalArgumentException("text cannot be null.");
} }
this.fieldName = fieldName;
this.text = text; this.text = text;
this.name = name; }
public String fieldName() {
return this.fieldName;
}
public Object value() {
return this.text;
} }
/** /**
@ -89,19 +113,27 @@ public class CommonTermsQueryBuilder extends QueryBuilder implements BoostableQu
* <tt>AND</tt>. * <tt>AND</tt>.
*/ */
public CommonTermsQueryBuilder highFreqOperator(Operator operator) { public CommonTermsQueryBuilder highFreqOperator(Operator operator) {
this.highFreqOperator = operator; this.highFreqOperator = (operator == null) ? DEFAULT_HIGH_FREQ_OCCUR : operator;
return this; return this;
} }
public Operator highFreqOperator() {
return highFreqOperator;
}
/** /**
* Sets the operator to use for terms with a low document frequency (less * Sets the operator to use for terms with a low document frequency (less
* than {@link #cutoffFrequency(float)}. Defaults to <tt>AND</tt>. * than {@link #cutoffFrequency(float)}. Defaults to <tt>AND</tt>.
*/ */
public CommonTermsQueryBuilder lowFreqOperator(Operator operator) { public CommonTermsQueryBuilder lowFreqOperator(Operator operator) {
this.lowFreqOperator = operator; this.lowFreqOperator = (operator == null) ? DEFAULT_LOW_FREQ_OCCUR : operator;
return this; return this;
} }
public Operator lowFreqOperator() {
return lowFreqOperator;
}
/** /**
* Explicitly set the analyzer to use. Defaults to use explicit mapping * Explicitly set the analyzer to use. Defaults to use explicit mapping
* config for the field, or, if not set, the default search analyzer. * config for the field, or, if not set, the default search analyzer.
@ -111,13 +143,8 @@ public class CommonTermsQueryBuilder extends QueryBuilder implements BoostableQu
return this; return this;
} }
/** public String analyzer() {
* Set the boost to apply to the query. return this.analyzer;
*/
@Override
public CommonTermsQueryBuilder boost(float boost) {
this.boost = boost;
return this;
} }
/** /**
@ -125,13 +152,17 @@ public class CommonTermsQueryBuilder extends QueryBuilder implements BoostableQu
* in [0..1] (or absolute number &gt;=1) representing the maximum threshold of * in [0..1] (or absolute number &gt;=1) representing the maximum threshold of
* a terms document frequency to be considered a low frequency term. * a terms document frequency to be considered a low frequency term.
* Defaults to * Defaults to
* <tt>{@value CommonTermsQueryParser#DEFAULT_MAX_TERM_DOC_FREQ}</tt> * <tt>{@value #DEFAULT_CUTOFF_FREQ}</tt>
*/ */
public CommonTermsQueryBuilder cutoffFrequency(float cutoffFrequency) { public CommonTermsQueryBuilder cutoffFrequency(float cutoffFrequency) {
this.cutoffFrequency = cutoffFrequency; this.cutoffFrequency = cutoffFrequency;
return this; return this;
} }
public float cutoffFrequency() {
return this.cutoffFrequency;
}
/** /**
* Sets the minimum number of high frequent query terms that need to match in order to * Sets the minimum number of high frequent query terms that need to match in order to
* produce a hit when there are no low frequen terms. * produce a hit when there are no low frequen terms.
@ -141,6 +172,10 @@ public class CommonTermsQueryBuilder extends QueryBuilder implements BoostableQu
return this; return this;
} }
public String highFreqMinimumShouldMatch() {
return this.highFreqMinimumShouldMatch;
}
/** /**
* Sets the minimum number of low frequent query terms that need to match in order to * Sets the minimum number of low frequent query terms that need to match in order to
* produce a hit. * produce a hit.
@ -149,44 +184,32 @@ public class CommonTermsQueryBuilder extends QueryBuilder implements BoostableQu
this.lowFreqMinimumShouldMatch = lowFreqMinimumShouldMatch; this.lowFreqMinimumShouldMatch = lowFreqMinimumShouldMatch;
return this; return this;
} }
public String lowFreqMinimumShouldMatch() {
return this.lowFreqMinimumShouldMatch;
}
public CommonTermsQueryBuilder disableCoord(boolean disableCoord) { public CommonTermsQueryBuilder disableCoord(boolean disableCoord) {
this.disableCoord = disableCoord; this.disableCoord = disableCoord;
return this; return this;
} }
/** public boolean disableCoord() {
* Sets the query name for the filter that can be used when searching for matched_filters per hit. return this.disableCoord;
*/
public CommonTermsQueryBuilder queryName(String queryName) {
this.queryName = queryName;
return this;
} }
@Override @Override
public void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(CommonTermsQueryParser.NAME); builder.startObject(NAME);
builder.startObject(name); builder.startObject(fieldName);
builder.field("query", text); builder.field("query", text);
if (disableCoord != null) { builder.field("disable_coord", disableCoord);
builder.field("disable_coord", disableCoord); builder.field("high_freq_operator", highFreqOperator.toString());
} builder.field("low_freq_operator", lowFreqOperator.toString());
if (highFreqOperator != null) {
builder.field("high_freq_operator", highFreqOperator.toString());
}
if (lowFreqOperator != null) {
builder.field("low_freq_operator", lowFreqOperator.toString());
}
if (analyzer != null) { if (analyzer != null) {
builder.field("analyzer", analyzer); builder.field("analyzer", analyzer);
} }
if (boost != null) { builder.field("cutoff_frequency", cutoffFrequency);
builder.field("boost", boost);
}
if (cutoffFrequency != null) {
builder.field("cutoff_frequency", cutoffFrequency);
}
if (lowFreqMinimumShouldMatch != null || highFreqMinimumShouldMatch != null) { if (lowFreqMinimumShouldMatch != null || highFreqMinimumShouldMatch != null) {
builder.startObject("minimum_should_match"); builder.startObject("minimum_should_match");
if (lowFreqMinimumShouldMatch != null) { if (lowFreqMinimumShouldMatch != null) {
@ -197,11 +220,113 @@ public class CommonTermsQueryBuilder extends QueryBuilder implements BoostableQu
} }
builder.endObject(); builder.endObject();
} }
if (queryName != null) { printBoostAndQueryName(builder);
builder.field("_name", queryName);
}
builder.endObject(); builder.endObject();
builder.endObject(); builder.endObject();
} }
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
String field;
MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType != null) {
field = fieldType.names().indexName();
} else {
field = fieldName;
}
Analyzer analyzerObj;
if (analyzer == null) {
if (fieldType != null) {
analyzerObj = context.getSearchAnalyzer(fieldType);
} else {
analyzerObj = context.mapperService().searchAnalyzer();
}
} else {
analyzerObj = context.mapperService().analysisService().analyzer(analyzer);
if (analyzerObj == null) {
throw new QueryShardException(context, "[common] analyzer [" + analyzer + "] not found");
}
}
Occur highFreqOccur = highFreqOperator.toBooleanClauseOccur();
Occur lowFreqOccur = lowFreqOperator.toBooleanClauseOccur();
ExtendedCommonTermsQuery commonsQuery = new ExtendedCommonTermsQuery(highFreqOccur, lowFreqOccur, cutoffFrequency, disableCoord, fieldType);
return parseQueryString(commonsQuery, text, field, analyzerObj, lowFreqMinimumShouldMatch, highFreqMinimumShouldMatch);
}
static Query parseQueryString(ExtendedCommonTermsQuery query, Object queryString, String field, Analyzer analyzer,
String lowFreqMinimumShouldMatch, String highFreqMinimumShouldMatch) throws IOException {
// Logic similar to QueryParser#getFieldQuery
int count = 0;
try (TokenStream source = analyzer.tokenStream(field, queryString.toString())) {
source.reset();
CharTermAttribute termAtt = source.addAttribute(CharTermAttribute.class);
BytesRefBuilder builder = new BytesRefBuilder();
while (source.incrementToken()) {
// UTF-8
builder.copyChars(termAtt);
query.add(new Term(field, builder.toBytesRef()));
count++;
}
}
if (count == 0) {
return null;
}
query.setLowFreqMinimumNumberShouldMatch(lowFreqMinimumShouldMatch);
query.setHighFreqMinimumNumberShouldMatch(highFreqMinimumShouldMatch);
return query;
}
@Override
protected CommonTermsQueryBuilder doReadFrom(StreamInput in) throws IOException {
CommonTermsQueryBuilder commonTermsQueryBuilder = new CommonTermsQueryBuilder(in.readString(), in.readGenericValue());
commonTermsQueryBuilder.highFreqOperator = Operator.readOperatorFrom(in);
commonTermsQueryBuilder.lowFreqOperator = Operator.readOperatorFrom(in);
commonTermsQueryBuilder.analyzer = in.readOptionalString();
commonTermsQueryBuilder.lowFreqMinimumShouldMatch = in.readOptionalString();
commonTermsQueryBuilder.highFreqMinimumShouldMatch = in.readOptionalString();
commonTermsQueryBuilder.disableCoord = in.readBoolean();
commonTermsQueryBuilder.cutoffFrequency = in.readFloat();
return commonTermsQueryBuilder;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(this.fieldName);
out.writeGenericValue(this.text);
highFreqOperator.writeTo(out);
lowFreqOperator.writeTo(out);
out.writeOptionalString(analyzer);
out.writeOptionalString(lowFreqMinimumShouldMatch);
out.writeOptionalString(highFreqMinimumShouldMatch);
out.writeBoolean(disableCoord);
out.writeFloat(cutoffFrequency);
}
@Override
protected int doHashCode() {
return Objects.hash(fieldName, text, highFreqOperator, lowFreqOperator, analyzer,
lowFreqMinimumShouldMatch, highFreqMinimumShouldMatch, disableCoord, cutoffFrequency);
}
@Override
protected boolean doEquals(CommonTermsQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(text, other.text) &&
Objects.equals(highFreqOperator, other.highFreqOperator) &&
Objects.equals(lowFreqOperator, other.lowFreqOperator) &&
Objects.equals(analyzer, other.analyzer) &&
Objects.equals(lowFreqMinimumShouldMatch, other.lowFreqMinimumShouldMatch) &&
Objects.equals(highFreqMinimumShouldMatch, other.highFreqMinimumShouldMatch) &&
Objects.equals(disableCoord, other.disableCoord) &&
Objects.equals(cutoffFrequency, other.cutoffFrequency);
}
} }

View File

@ -19,64 +19,38 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
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.queries.ExtendedCommonTermsQuery;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRefBuilder;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.MappedFieldType;
import java.io.IOException; import java.io.IOException;
/** /**
* * Parser for common terms query
*/ */
public class CommonTermsQueryParser implements QueryParser { public class CommonTermsQueryParser implements QueryParser<CommonTermsQueryBuilder> {
public static final String NAME = "common";
static final float DEFAULT_MAX_TERM_DOC_FREQ = 0.01f;
static final Occur DEFAULT_HIGH_FREQ_OCCUR = Occur.SHOULD;
static final Occur DEFAULT_LOW_FREQ_OCCUR = Occur.SHOULD;
static final boolean DEFAULT_DISABLE_COORD = true;
@Inject
public CommonTermsQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[] { NAME }; return new String[] { CommonTermsQueryBuilder.NAME };
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public CommonTermsQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
XContentParser.Token token = parser.nextToken(); XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.FIELD_NAME) { if (token != XContentParser.Token.FIELD_NAME) {
throw new ParsingException(parseContext, "[common] query malformed, no field"); throw new ParsingException(parser.getTokenLocation(), "[common] query malformed, no field");
} }
String fieldName = parser.currentName(); String fieldName = parser.currentName();
Object value = null; Object text = null;
float boost = 1.0f; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryAnalyzer = null; String analyzer = null;
String lowFreqMinimumShouldMatch = null; String lowFreqMinimumShouldMatch = null;
String highFreqMinimumShouldMatch = null; String highFreqMinimumShouldMatch = null;
boolean disableCoord = DEFAULT_DISABLE_COORD; boolean disableCoord = CommonTermsQueryBuilder.DEFAULT_DISABLE_COORD;
Occur highFreqOccur = DEFAULT_HIGH_FREQ_OCCUR; Operator highFreqOperator = CommonTermsQueryBuilder.DEFAULT_HIGH_FREQ_OCCUR;
Occur lowFreqOccur = DEFAULT_LOW_FREQ_OCCUR; Operator lowFreqOperator = CommonTermsQueryBuilder.DEFAULT_LOW_FREQ_OCCUR;
float maxTermFrequency = DEFAULT_MAX_TERM_DOC_FREQ; float cutoffFrequency = CommonTermsQueryBuilder.DEFAULT_CUTOFF_FREQ;
String queryName = null; String queryName = null;
token = parser.nextToken(); token = parser.nextToken();
if (token == XContentParser.Token.START_OBJECT) { if (token == XContentParser.Token.START_OBJECT) {
@ -96,130 +70,66 @@ public class CommonTermsQueryParser implements QueryParser {
} else if ("high_freq".equals(innerFieldName) || "highFreq".equals(innerFieldName)) { } else if ("high_freq".equals(innerFieldName) || "highFreq".equals(innerFieldName)) {
highFreqMinimumShouldMatch = parser.text(); highFreqMinimumShouldMatch = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[common] query does not support [" + innerFieldName throw new ParsingException(parser.getTokenLocation(), "[common] query does not support [" + innerFieldName
+ "] for [" + currentFieldName + "]"); + "] for [" + currentFieldName + "]");
} }
} }
} }
} else { } else {
throw new ParsingException(parseContext, "[common] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[common] query does not support [" + currentFieldName + "]");
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if ("query".equals(currentFieldName)) { if ("query".equals(currentFieldName)) {
value = parser.objectText(); text = parser.objectText();
} else if ("analyzer".equals(currentFieldName)) { } else if ("analyzer".equals(currentFieldName)) {
String analyzer = parser.text(); analyzer = parser.text();
if (parseContext.analysisService().analyzer(analyzer) == null) {
throw new ParsingException(parseContext, "[common] analyzer [" + parser.text() + "] not found");
}
queryAnalyzer = analyzer;
} else if ("disable_coord".equals(currentFieldName) || "disableCoord".equals(currentFieldName)) { } else if ("disable_coord".equals(currentFieldName) || "disableCoord".equals(currentFieldName)) {
disableCoord = parser.booleanValue(); disableCoord = parser.booleanValue();
} else if ("boost".equals(currentFieldName)) { } else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue(); boost = parser.floatValue();
} else if ("high_freq_operator".equals(currentFieldName) || "highFreqOperator".equals(currentFieldName)) { } else if ("high_freq_operator".equals(currentFieldName) || "highFreqOperator".equals(currentFieldName)) {
String op = parser.text(); highFreqOperator = Operator.fromString(parser.text());
if ("or".equalsIgnoreCase(op)) {
highFreqOccur = BooleanClause.Occur.SHOULD;
} else if ("and".equalsIgnoreCase(op)) {
highFreqOccur = BooleanClause.Occur.MUST;
} else {
throw new ParsingException(parseContext,
"[common] query requires operator to be either 'and' or 'or', not [" + op + "]");
}
} else if ("low_freq_operator".equals(currentFieldName) || "lowFreqOperator".equals(currentFieldName)) { } else if ("low_freq_operator".equals(currentFieldName) || "lowFreqOperator".equals(currentFieldName)) {
String op = parser.text(); lowFreqOperator = Operator.fromString(parser.text());
if ("or".equalsIgnoreCase(op)) {
lowFreqOccur = BooleanClause.Occur.SHOULD;
} else if ("and".equalsIgnoreCase(op)) {
lowFreqOccur = BooleanClause.Occur.MUST;
} else {
throw new ParsingException(parseContext,
"[common] query requires operator to be either 'and' or 'or', not [" + op + "]");
}
} else if ("minimum_should_match".equals(currentFieldName) || "minimumShouldMatch".equals(currentFieldName)) { } else if ("minimum_should_match".equals(currentFieldName) || "minimumShouldMatch".equals(currentFieldName)) {
lowFreqMinimumShouldMatch = parser.text(); lowFreqMinimumShouldMatch = parser.text();
} else if ("cutoff_frequency".equals(currentFieldName)) { } else if ("cutoff_frequency".equals(currentFieldName)) {
maxTermFrequency = parser.floatValue(); cutoffFrequency = parser.floatValue();
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[common] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[common] query does not support [" + currentFieldName + "]");
} }
} }
} }
parser.nextToken(); parser.nextToken();
} else { } else {
value = parser.objectText(); text = parser.objectText();
// move to the next token // move to the next token
token = parser.nextToken(); token = parser.nextToken();
if (token != XContentParser.Token.END_OBJECT) { if (token != XContentParser.Token.END_OBJECT) {
throw new ParsingException( throw new ParsingException(parser.getTokenLocation(),
parseContext,
"[common] query parsed in simplified form, with direct field name, but included more options than just the field name, possibly use its 'options' form, with 'query' element?"); "[common] query parsed in simplified form, with direct field name, but included more options than just the field name, possibly use its 'options' form, with 'query' element?");
} }
} }
if (value == null) { if (text == null) {
throw new ParsingException(parseContext, "No text specified for text query"); throw new ParsingException(parser.getTokenLocation(), "No text specified for text query");
} }
String field; return new CommonTermsQueryBuilder(fieldName, text)
MappedFieldType fieldType = parseContext.fieldMapper(fieldName); .lowFreqMinimumShouldMatch(lowFreqMinimumShouldMatch)
if (fieldType != null) { .highFreqMinimumShouldMatch(highFreqMinimumShouldMatch)
field = fieldType.names().indexName(); .analyzer(analyzer)
} else { .highFreqOperator(highFreqOperator)
field = fieldName; .lowFreqOperator(lowFreqOperator)
} .disableCoord(disableCoord)
.cutoffFrequency(cutoffFrequency)
Analyzer analyzer = null; .boost(boost)
if (queryAnalyzer == null) { .queryName(queryName);
if (fieldType != null) {
analyzer = fieldType.searchAnalyzer();
}
if (analyzer == null && fieldType != null) {
analyzer = parseContext.getSearchAnalyzer(fieldType);
}
if (analyzer == null) {
analyzer = parseContext.mapperService().searchAnalyzer();
}
} else {
analyzer = parseContext.mapperService().analysisService().analyzer(queryAnalyzer);
if (analyzer == null) {
throw new IllegalArgumentException("No analyzer found for [" + queryAnalyzer + "]");
}
}
ExtendedCommonTermsQuery commonsQuery = new ExtendedCommonTermsQuery(highFreqOccur, lowFreqOccur, maxTermFrequency, disableCoord, fieldType);
commonsQuery.setBoost(boost);
Query query = parseQueryString(commonsQuery, value.toString(), field, parseContext, analyzer, lowFreqMinimumShouldMatch, highFreqMinimumShouldMatch);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
return query;
} }
@Override
private final Query parseQueryString(ExtendedCommonTermsQuery query, String queryString, String field, QueryParseContext parseContext, public CommonTermsQueryBuilder getBuilderPrototype() {
Analyzer analyzer, String lowFreqMinimumShouldMatch, String highFreqMinimumShouldMatch) throws IOException { return CommonTermsQueryBuilder.PROTOTYPE;
// Logic similar to QueryParser#getFieldQuery
int count = 0;
try (TokenStream source = analyzer.tokenStream(field, queryString.toString())) {
source.reset();
CharTermAttribute termAtt = source.addAttribute(CharTermAttribute.class);
BytesRefBuilder builder = new BytesRefBuilder();
while (source.incrementToken()) {
// UTF-8
builder.copyChars(termAtt);
query.add(new Term(field, builder.toBytesRef()));
count++;
}
}
if (count == 0) {
return null;
}
query.setLowFreqMinimumNumberShouldMatch(lowFreqMinimumShouldMatch);
query.setHighFreqMinimumNumberShouldMatch(highFreqMinimumShouldMatch);
return query;
} }
} }

View File

@ -19,6 +19,10 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
@ -28,41 +32,76 @@ import java.util.Objects;
* A query that wraps a filter and simply returns a constant score equal to the * A query that wraps a filter and simply returns a constant score equal to the
* query boost for every document in the filter. * query boost for every document in the filter.
*/ */
public class ConstantScoreQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<ConstantScoreQueryBuilder> { public class ConstantScoreQueryBuilder extends AbstractQueryBuilder<ConstantScoreQueryBuilder> {
public static final String NAME = "constant_score";
private final QueryBuilder filterBuilder; private final QueryBuilder filterBuilder;
private float boost = -1; static final ConstantScoreQueryBuilder PROTOTYPE = new ConstantScoreQueryBuilder(EmptyQueryBuilder.PROTOTYPE);
/** /**
* A query that wraps a query and simply returns a constant score equal to the * A query that wraps another query and simply returns a constant score equal to the
* query boost for every document in the query. * query boost for every document in the query.
* *
* @param filterBuilder The query to wrap in a constant score query * @param filterBuilder The query to wrap in a constant score query
*/ */
public ConstantScoreQueryBuilder(QueryBuilder filterBuilder) { public ConstantScoreQueryBuilder(QueryBuilder filterBuilder) {
this.filterBuilder = Objects.requireNonNull(filterBuilder); if (filterBuilder == null) {
throw new IllegalArgumentException("inner clause [filter] cannot be null.");
}
this.filterBuilder = filterBuilder;
} }
/** /**
* Sets the boost for this query. Documents matching this query will (in addition to the normal * @return the query that was wrapped in this constant score query
* weightings) have their score multiplied by the boost provided.
*/ */
@Override public QueryBuilder innerQuery() {
public ConstantScoreQueryBuilder boost(float boost) { return this.filterBuilder;
this.boost = boost;
return this;
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(ConstantScoreQueryParser.NAME); builder.startObject(NAME);
builder.field("filter"); builder.field("filter");
filterBuilder.toXContent(builder, params); filterBuilder.toXContent(builder, params);
printBoostAndQueryName(builder);
if (boost != -1) {
builder.field("boost", boost);
}
builder.endObject(); builder.endObject();
} }
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
Query innerFilter = filterBuilder.toFilter(context);
if (innerFilter == null ) {
// return null so that parent queries (e.g. bool) also ignore this
return null;
}
return new ConstantScoreQuery(innerFilter);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected int doHashCode() {
return Objects.hash(filterBuilder);
}
@Override
protected boolean doEquals(ConstantScoreQueryBuilder other) {
return Objects.equals(filterBuilder, other.filterBuilder);
}
@Override
protected ConstantScoreQueryBuilder doReadFrom(StreamInput in) throws IOException {
QueryBuilder innerFilterBuilder = in.readQuery();
return new ConstantScoreQueryBuilder(innerFilterBuilder);
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeQuery(filterBuilder);
}
}

View File

@ -19,40 +19,33 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException; import java.io.IOException;
/** /**
* * Parser for constant_score query
*/ */
public class ConstantScoreQueryParser implements QueryParser { public class ConstantScoreQueryParser implements QueryParser<ConstantScoreQueryBuilder> {
public static final String NAME = "constant_score";
private static final ParseField INNER_QUERY_FIELD = new ParseField("filter", "query"); private static final ParseField INNER_QUERY_FIELD = new ParseField("filter", "query");
@Inject
public ConstantScoreQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, Strings.toCamelCase(NAME)}; return new String[]{ConstantScoreQueryBuilder.NAME, Strings.toCamelCase(ConstantScoreQueryBuilder.NAME)};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public ConstantScoreQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
Query filter = null; QueryBuilder query = null;
boolean queryFound = false; boolean queryFound = false;
float boost = 1.0f; String queryName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
@ -63,29 +56,33 @@ public class ConstantScoreQueryParser implements QueryParser {
// skip // skip
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
if (parseContext.parseFieldMatcher().match(currentFieldName, INNER_QUERY_FIELD)) { if (parseContext.parseFieldMatcher().match(currentFieldName, INNER_QUERY_FIELD)) {
filter = parseContext.parseInnerFilter(); query = parseContext.parseInnerQueryBuilder();
queryFound = true; queryFound = true;
} else { } else {
throw new ParsingException(parseContext, "[constant_score] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[constant_score] query does not support [" + currentFieldName + "]");
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if ("boost".equals(currentFieldName)) { if ("_name".equals(currentFieldName)) {
queryName = parser.text();
} else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue(); boost = parser.floatValue();
} else { } else {
throw new ParsingException(parseContext, "[constant_score] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[constant_score] query does not support [" + currentFieldName + "]");
} }
} }
} }
if (!queryFound) { if (!queryFound) {
throw new ParsingException(parseContext, "[constant_score] requires a 'filter' element"); throw new ParsingException(parser.getTokenLocation(), "[constant_score] requires a 'filter' element");
} }
if (filter == null) { ConstantScoreQueryBuilder constantScoreBuilder = new ConstantScoreQueryBuilder(query);
return null; constantScoreBuilder.boost(boost);
} constantScoreBuilder.queryName(queryName);
return constantScoreBuilder;
filter = new ConstantScoreQuery(filter);
filter.setBoost(boost);
return filter;
} }
}
@Override
public ConstantScoreQueryBuilder getBuilderPrototype() {
return ConstantScoreQueryBuilder.PROTOTYPE;
}
}

View File

@ -19,42 +19,51 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.DisjunctionMaxQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/** /**
* A query that generates the union of documents produced by its sub-queries, and that scores each document * A query that generates the union of documents produced by its sub-queries, and that scores each document
* with the maximum score for that document as produced by any sub-query, plus a tie breaking increment for any * with the maximum score for that document as produced by any sub-query, plus a tie breaking increment for any
* additional matching sub-queries. * additional matching sub-queries.
*/ */
public class DisMaxQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<DisMaxQueryBuilder> { public class DisMaxQueryBuilder extends AbstractQueryBuilder<DisMaxQueryBuilder> {
private ArrayList<QueryBuilder> queries = new ArrayList<>(); public static final String NAME = "dis_max";
private float boost = -1; private final ArrayList<QueryBuilder> queries = new ArrayList<>();
private float tieBreaker = -1; /** Default multiplication factor for breaking ties in document scores.*/
public static float DEFAULT_TIE_BREAKER = 0.0f;
private float tieBreaker = DEFAULT_TIE_BREAKER;
private String queryName; static final DisMaxQueryBuilder PROTOTYPE = new DisMaxQueryBuilder();
/** /**
* Add a sub-query to this disjunction. * Add a sub-query to this disjunction.
*/ */
public DisMaxQueryBuilder add(QueryBuilder queryBuilder) { public DisMaxQueryBuilder add(QueryBuilder queryBuilder) {
if (queryBuilder == null) {
throw new IllegalArgumentException("inner dismax query clause cannot be null");
}
queries.add(queryBuilder); queries.add(queryBuilder);
return this; return this;
} }
/** /**
* Sets the boost for this query. Documents matching this query will (in addition to the normal * @return an immutable list copy of the current sub-queries of this disjunction
* weightings) have their score multiplied by the boost provided.
*/ */
@Override public List<QueryBuilder> innerQueries() {
public DisMaxQueryBuilder boost(float boost) { return this.queries;
this.boost = boost;
return this;
} }
/** /**
@ -69,30 +78,65 @@ public class DisMaxQueryBuilder extends QueryBuilder implements BoostableQueryBu
} }
/** /**
* Sets the query name for the filter that can be used when searching for matched_filters per hit. * @return the tie breaker score
* @see DisMaxQueryBuilder#tieBreaker(float)
*/ */
public DisMaxQueryBuilder queryName(String queryName) { public float tieBreaker() {
this.queryName = queryName; return this.tieBreaker;
return this;
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(DisMaxQueryParser.NAME); builder.startObject(NAME);
if (tieBreaker != -1) { builder.field("tie_breaker", tieBreaker);
builder.field("tie_breaker", tieBreaker);
}
if (boost != -1) {
builder.field("boost", boost);
}
if (queryName != null) {
builder.field("_name", queryName);
}
builder.startArray("queries"); builder.startArray("queries");
for (QueryBuilder queryBuilder : queries) { for (QueryBuilder queryBuilder : queries) {
queryBuilder.toXContent(builder, params); queryBuilder.toXContent(builder, params);
} }
builder.endArray(); builder.endArray();
printBoostAndQueryName(builder);
builder.endObject(); builder.endObject();
} }
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
// return null if there are no queries at all
Collection<Query> luceneQueries = toQueries(queries, context);
if (luceneQueries.isEmpty()) {
return null;
}
return new DisjunctionMaxQuery(luceneQueries, tieBreaker);
}
@Override
protected DisMaxQueryBuilder doReadFrom(StreamInput in) throws IOException {
DisMaxQueryBuilder disMax = new DisMaxQueryBuilder();
List<QueryBuilder> queryBuilders = readQueries(in);
disMax.queries.addAll(queryBuilders);
disMax.tieBreaker = in.readFloat();
return disMax;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
writeQueries(out, queries);
out.writeFloat(tieBreaker);
}
@Override
protected int doHashCode() {
return Objects.hash(queries, tieBreaker);
}
@Override
protected boolean doEquals(DisMaxQueryBuilder other) {
return Objects.equals(queries, other.queries) &&
Objects.equals(tieBreaker, other.tieBreaker);
}
@Override
public String getWriteableName() {
return NAME;
}
}

View File

@ -19,11 +19,8 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.DisjunctionMaxQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException; import java.io.IOException;
@ -31,29 +28,23 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* * Parser for dis_max query
*/ */
public class DisMaxQueryParser implements QueryParser { public class DisMaxQueryParser implements QueryParser<DisMaxQueryBuilder> {
public static final String NAME = "dis_max";
@Inject
public DisMaxQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, Strings.toCamelCase(NAME)}; return new String[]{DisMaxQueryBuilder.NAME, Strings.toCamelCase(DisMaxQueryBuilder.NAME)};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public DisMaxQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
float boost = 1.0f; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
float tieBreaker = 0.0f; float tieBreaker = DisMaxQueryBuilder.DEFAULT_TIE_BREAKER;
List<Query> queries = new ArrayList<>(); final List<QueryBuilder> queries = new ArrayList<>();
boolean queriesFound = false; boolean queriesFound = false;
String queryName = null; String queryName = null;
@ -65,25 +56,21 @@ public class DisMaxQueryParser implements QueryParser {
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
if ("queries".equals(currentFieldName)) { if ("queries".equals(currentFieldName)) {
queriesFound = true; queriesFound = true;
Query query = parseContext.parseInnerQuery(); QueryBuilder query = parseContext.parseInnerQueryBuilder();
if (query != null) { queries.add(query);
queries.add(query);
}
} else { } else {
throw new ParsingException(parseContext, "[dis_max] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[dis_max] query does not support [" + currentFieldName + "]");
} }
} else if (token == XContentParser.Token.START_ARRAY) { } else if (token == XContentParser.Token.START_ARRAY) {
if ("queries".equals(currentFieldName)) { if ("queries".equals(currentFieldName)) {
queriesFound = true; queriesFound = true;
while (token != XContentParser.Token.END_ARRAY) { while (token != XContentParser.Token.END_ARRAY) {
Query query = parseContext.parseInnerQuery(); QueryBuilder query = parseContext.parseInnerQueryBuilder();
if (query != null) { queries.add(query);
queries.add(query);
}
token = parser.nextToken(); token = parser.nextToken();
} }
} else { } else {
throw new ParsingException(parseContext, "[dis_max] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[dis_max] query does not support [" + currentFieldName + "]");
} }
} else { } else {
if ("boost".equals(currentFieldName)) { if ("boost".equals(currentFieldName)) {
@ -93,24 +80,27 @@ public class DisMaxQueryParser implements QueryParser {
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[dis_max] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[dis_max] query does not support [" + currentFieldName + "]");
} }
} }
} }
if (!queriesFound) { if (!queriesFound) {
throw new ParsingException(parseContext, "[dis_max] requires 'queries' field"); throw new ParsingException(parser.getTokenLocation(), "[dis_max] requires 'queries' field");
} }
if (queries.isEmpty()) { DisMaxQueryBuilder disMaxQuery = new DisMaxQueryBuilder();
return null; disMaxQuery.tieBreaker(tieBreaker);
disMaxQuery.queryName(queryName);
disMaxQuery.boost(boost);
for (QueryBuilder query : queries) {
disMaxQuery.add(query);
} }
return disMaxQuery;
DisjunctionMaxQuery query = new DisjunctionMaxQuery(queries, tieBreaker);
query.setBoost(boost);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
return query;
} }
}
@Override
public DisMaxQueryBuilder getBuilderPrototype() {
return DisMaxQueryBuilder.PROTOTYPE;
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.search.Query;
import org.elasticsearch.action.support.ToXContentToBytes;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
/**
* A {@link QueryBuilder} that is a stand in replacement for an empty query clause in the DSL.
* The current DSL allows parsing inner queries / filters like "{ }", in order to have a
* valid non-null representation of these clauses that actually do nothing we can use this class.
*
* This builder has no corresponding parser and it is not registered under the query name. It is
* intended to be used internally as a stand-in for nested queries that are left empty and should
* be ignored upstream.
*/
public class EmptyQueryBuilder extends ToXContentToBytes implements QueryBuilder<EmptyQueryBuilder> {
public static final String NAME = "empty_query";
/** the one and only empty query builder */
public static final EmptyQueryBuilder PROTOTYPE = new EmptyQueryBuilder();
// prevent instances other than prototype
private EmptyQueryBuilder() {
super(XContentType.JSON);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public String getName() {
return getWriteableName();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.endObject();
return builder;
}
@Override
public Query toQuery(QueryShardContext context) throws IOException {
// empty
return null;
}
@Override
public Query toFilter(QueryShardContext context) throws IOException {
// empty
return null;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
}
@Override
public EmptyQueryBuilder readFrom(StreamInput in) throws IOException {
return EmptyQueryBuilder.PROTOTYPE;
}
@Override
public EmptyQueryBuilder queryName(String queryName) {
//no-op
return this;
}
@Override
public String queryName() {
return null;
}
@Override
public float boost() {
return -1;
}
@Override
public EmptyQueryBuilder boost(float boost) {
//no-op
return this;
}
}

View File

@ -19,38 +19,124 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.*;
import org.elasticsearch.common.Strings;
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.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.object.ObjectMapper;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.Objects;
/** /**
* Constructs a query that only match on documents that the field has a value in them. * Constructs a query that only match on documents that the field has a value in them.
*/ */
public class ExistsQueryBuilder extends QueryBuilder { public class ExistsQueryBuilder extends AbstractQueryBuilder<ExistsQueryBuilder> {
private String name; public static final String NAME = "exists";
private String queryName; private final String fieldName;
public ExistsQueryBuilder(String name) { static final ExistsQueryBuilder PROTOTYPE = new ExistsQueryBuilder("field");
this.name = name;
public ExistsQueryBuilder(String fieldName) {
if (Strings.isEmpty(fieldName)) {
throw new IllegalArgumentException("field name is null or empty");
}
this.fieldName = fieldName;
} }
/** /**
* Sets the query name for the query that can be used when searching for matched_queries per hit. * @return the field name that has to exist for this query to match
*/ */
public ExistsQueryBuilder queryName(String queryName) { public String fieldName() {
this.queryName = queryName; return this.fieldName;
return this;
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(ExistsQueryParser.NAME); builder.startObject(NAME);
builder.field("field", name); builder.field("field", fieldName);
if (queryName != null) { printBoostAndQueryName(builder);
builder.field("_name", queryName);
}
builder.endObject(); builder.endObject();
} }
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
return newFilter(context, fieldName);
}
public static Query newFilter(QueryShardContext context, String fieldPattern) {
final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType = (FieldNamesFieldMapper.FieldNamesFieldType)context.mapperService().fullName(FieldNamesFieldMapper.NAME);
if (fieldNamesFieldType == null) {
// can only happen when no types exist, so no docs exist either
return Queries.newMatchNoDocsQuery();
}
ObjectMapper objectMapper = context.getObjectMapper(fieldPattern);
if (objectMapper != null) {
// automatic make the object mapper pattern
fieldPattern = fieldPattern + ".*";
}
Collection<String> fields = context.simpleMatchToIndexNames(fieldPattern);
if (fields.isEmpty()) {
// no fields exists, so we should not match anything
return Queries.newMatchNoDocsQuery();
}
BooleanQuery.Builder boolFilterBuilder = new BooleanQuery.Builder();
for (String field : fields) {
MappedFieldType fieldType = context.fieldMapper(field);
Query filter = null;
if (fieldNamesFieldType.isEnabled()) {
final String f;
if (fieldType != null) {
f = fieldType.names().indexName();
} else {
f = field;
}
filter = fieldNamesFieldType.termQuery(f, context);
}
// if _field_names are not indexed, we need to go the slow way
if (filter == null && fieldType != null) {
filter = fieldType.rangeQuery(null, null, true, true);
}
if (filter == null) {
filter = new TermRangeQuery(field, null, null, true, true);
}
boolFilterBuilder.add(filter, BooleanClause.Occur.SHOULD);
}
return new ConstantScoreQuery(boolFilterBuilder.build());
}
@Override
protected int doHashCode() {
return Objects.hash(fieldName);
}
@Override
protected boolean doEquals(ExistsQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName);
}
@Override
protected ExistsQueryBuilder doReadFrom(StreamInput in) throws IOException {
return new ExistsQueryBuilder(in.readString());
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldName);
}
@Override
public String getWriteableName() {
return NAME;
}
} }

View File

@ -19,40 +19,28 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.*;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.internal.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.object.ObjectMapper;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
/** /**
* * Parser for exists query
*/ */
public class ExistsQueryParser implements QueryParser { public class ExistsQueryParser implements QueryParser<ExistsQueryBuilder> {
public static final String NAME = "exists";
@Inject
public ExistsQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME}; return new String[]{ExistsQueryBuilder.NAME};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public ExistsQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
String fieldPattern = null; String fieldPattern = null;
String queryName = null; String queryName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
XContentParser.Token token; XContentParser.Token token;
String currentFieldName = null; String currentFieldName = null;
@ -64,66 +52,26 @@ public class ExistsQueryParser implements QueryParser {
fieldPattern = parser.text(); fieldPattern = parser.text();
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue();
} else { } else {
throw new ParsingException(parseContext, "[exists] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[exists] query does not support [" + currentFieldName + "]");
} }
} }
} }
if (fieldPattern == null) { if (fieldPattern == null) {
throw new ParsingException(parseContext, "exists must be provided with a [field]"); throw new ParsingException(parser.getTokenLocation(), "exists must be provided with a [field]");
} }
return newFilter(parseContext, fieldPattern, queryName); ExistsQueryBuilder builder = new ExistsQueryBuilder(fieldPattern);
builder.queryName(queryName);
builder.boost(boost);
return builder;
} }
public static Query newFilter(QueryParseContext parseContext, String fieldPattern, String queryName) { @Override
final FieldNamesFieldMapper.FieldNamesFieldType fieldNamesFieldType = (FieldNamesFieldMapper.FieldNamesFieldType)parseContext.mapperService().fullName(FieldNamesFieldMapper.NAME); public ExistsQueryBuilder getBuilderPrototype() {
if (fieldNamesFieldType == null) { return ExistsQueryBuilder.PROTOTYPE;
// can only happen when no types exist, so no docs exist either
return Queries.newMatchNoDocsQuery();
}
ObjectMapper objectMapper = parseContext.getObjectMapper(fieldPattern);
if (objectMapper != null) {
// automatic make the object mapper pattern
fieldPattern = fieldPattern + ".*";
}
Collection<String> fields = parseContext.simpleMatchToIndexNames(fieldPattern);
if (fields.isEmpty()) {
// no fields exists, so we should not match anything
return Queries.newMatchNoDocsQuery();
}
BooleanQuery.Builder boolFilterBuilder = new BooleanQuery.Builder();
for (String field : fields) {
MappedFieldType fieldType = parseContext.fieldMapper(field);
Query filter = null;
if (fieldNamesFieldType.isEnabled()) {
final String f;
if (fieldType != null) {
f = fieldType.names().indexName();
} else {
f = field;
}
filter = fieldNamesFieldType.termQuery(f, parseContext);
}
// if _field_names are not indexed, we need to go the slow way
if (filter == null && fieldType != null) {
filter = fieldType.rangeQuery(null, null, true, true);
}
if (filter == null) {
filter = new TermRangeQuery(field, null, null, true, true);
}
boolFilterBuilder.add(filter, BooleanClause.Occur.SHOULD);
}
BooleanQuery boolFilter = boolFilterBuilder.build();
if (queryName != null) {
parseContext.addNamedQuery(queryName, boolFilter);
}
return new ConstantScoreQuery(boolFilter);
} }
} }

View File

@ -19,52 +19,106 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.spans.FieldMaskingSpanQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.MappedFieldType;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
public class FieldMaskingSpanQueryBuilder extends SpanQueryBuilder implements BoostableQueryBuilder<FieldMaskingSpanQueryBuilder> { public class FieldMaskingSpanQueryBuilder extends AbstractQueryBuilder<FieldMaskingSpanQueryBuilder> implements SpanQueryBuilder<FieldMaskingSpanQueryBuilder>{
public static final String NAME = "field_masking_span";
private final SpanQueryBuilder queryBuilder; private final SpanQueryBuilder queryBuilder;
private final String field; private final String fieldName;
private float boost = -1; static final FieldMaskingSpanQueryBuilder PROTOTYPE = new FieldMaskingSpanQueryBuilder(new SpanTermQueryBuilder("field", "text"), "field");
private String queryName; /**
* Constructs a new {@link FieldMaskingSpanQueryBuilder} given an inner {@link SpanQueryBuilder} for
* a given field
public FieldMaskingSpanQueryBuilder(SpanQueryBuilder queryBuilder, String field) { * @param queryBuilder inner {@link SpanQueryBuilder}
* @param fieldName the field name
*/
public FieldMaskingSpanQueryBuilder(SpanQueryBuilder queryBuilder, String fieldName) {
if (Strings.isEmpty(fieldName)) {
throw new IllegalArgumentException("field name is null or empty");
}
if (queryBuilder == null) {
throw new IllegalArgumentException("inner clause [query] cannot be null.");
}
this.queryBuilder = queryBuilder; this.queryBuilder = queryBuilder;
this.field = field; this.fieldName = fieldName;
}
@Override
public FieldMaskingSpanQueryBuilder boost(float boost) {
this.boost = boost;
return this;
} }
/** /**
* Sets the query name for the filter that can be used when searching for matched_filters per hit. * @return the field name for this query
*/ */
public FieldMaskingSpanQueryBuilder queryName(String queryName) { public String fieldName() {
this.queryName = queryName; return this.fieldName;
return this; }
/**
* @return the inner {@link QueryBuilder}
*/
public SpanQueryBuilder innerQuery() {
return this.queryBuilder;
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(FieldMaskingSpanQueryParser.NAME); builder.startObject(NAME);
builder.field("query"); builder.field("query");
queryBuilder.toXContent(builder, params); queryBuilder.toXContent(builder, params);
builder.field("field", field); builder.field("field", fieldName);
if (boost != -1) { printBoostAndQueryName(builder);
builder.field("boost", boost);
}
if (queryName != null) {
builder.field("_name", queryName);
}
builder.endObject(); builder.endObject();
} }
@Override
protected SpanQuery doToQuery(QueryShardContext context) throws IOException {
String fieldInQuery = fieldName;
MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType != null) {
fieldInQuery = fieldType.names().indexName();
}
Query innerQuery = queryBuilder.toQuery(context);
assert innerQuery instanceof SpanQuery;
return new FieldMaskingSpanQuery((SpanQuery)innerQuery, fieldInQuery);
}
@Override
protected FieldMaskingSpanQueryBuilder doReadFrom(StreamInput in) throws IOException {
QueryBuilder innerQueryBuilder = in.readQuery();
return new FieldMaskingSpanQueryBuilder((SpanQueryBuilder) innerQueryBuilder, in.readString());
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeQuery(queryBuilder);
out.writeString(fieldName);
}
@Override
protected int doHashCode() {
return Objects.hash(queryBuilder, fieldName);
}
@Override
protected boolean doEquals(FieldMaskingSpanQueryBuilder other) {
return Objects.equals(queryBuilder, other.queryBuilder) &&
Objects.equals(fieldName, other.fieldName);
}
@Override
public String getWriteableName() {
return NAME;
}
} }

View File

@ -19,40 +19,28 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.spans.FieldMaskingSpanQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.MappedFieldType;
import java.io.IOException; import java.io.IOException;
/** /**
* * Parser for field_masking_span query
*/ */
public class FieldMaskingSpanQueryParser implements QueryParser { public class FieldMaskingSpanQueryParser implements QueryParser<FieldMaskingSpanQueryBuilder> {
public static final String NAME = "field_masking_span";
@Inject
public FieldMaskingSpanQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, Strings.toCamelCase(NAME)}; return new String[]{FieldMaskingSpanQueryBuilder.NAME, Strings.toCamelCase(FieldMaskingSpanQueryBuilder.NAME)};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public FieldMaskingSpanQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
float boost = 1.0f; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
SpanQuery inner = null; SpanQueryBuilder inner = null;
String field = null; String field = null;
String queryName = null; String queryName = null;
@ -63,13 +51,13 @@ public class FieldMaskingSpanQueryParser implements QueryParser {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
if ("query".equals(currentFieldName)) { if ("query".equals(currentFieldName)) {
Query query = parseContext.parseInnerQuery(); QueryBuilder query = parseContext.parseInnerQueryBuilder();
if (!(query instanceof SpanQuery)) { if (!(query instanceof SpanQueryBuilder)) {
throw new ParsingException(parseContext, "[field_masking_span] query] must be of type span query"); throw new ParsingException(parser.getTokenLocation(), "[field_masking_span] query must be of type span query");
} }
inner = (SpanQuery) query; inner = (SpanQueryBuilder) query;
} else { } else {
throw new ParsingException(parseContext, "[field_masking_span] query does not support [" throw new ParsingException(parser.getTokenLocation(), "[field_masking_span] query does not support ["
+ currentFieldName + "]"); + currentFieldName + "]");
} }
} else { } else {
@ -80,27 +68,25 @@ public class FieldMaskingSpanQueryParser implements QueryParser {
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[field_masking_span] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[field_masking_span] query does not support [" + currentFieldName + "]");
} }
} }
} }
if (inner == null) { if (inner == null) {
throw new ParsingException(parseContext, "field_masking_span must have [query] span query clause"); throw new ParsingException(parser.getTokenLocation(), "field_masking_span must have [query] span query clause");
} }
if (field == null) { if (field == null) {
throw new ParsingException(parseContext, "field_masking_span must have [field] set for it"); throw new ParsingException(parser.getTokenLocation(), "field_masking_span must have [field] set for it");
} }
MappedFieldType fieldType = parseContext.fieldMapper(field); FieldMaskingSpanQueryBuilder queryBuilder = new FieldMaskingSpanQueryBuilder(inner, field);
if (fieldType != null) { queryBuilder.boost(boost);
field = fieldType.names().indexName(); queryBuilder.queryName(queryName);
} return queryBuilder;
FieldMaskingSpanQuery query = new FieldMaskingSpanQuery(inner, field);
query.setBoost(boost);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
return query;
} }
}
@Override
public FieldMaskingSpanQueryBuilder getBuilderPrototype() {
return FieldMaskingSpanQueryBuilder.PROTOTYPE;
}
}

View File

@ -19,177 +19,273 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.support.QueryParsers;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
/** /**
* A Query that does fuzzy matching for a specific value. * A Query that does fuzzy matching for a specific value.
*/ */
public class FuzzyQueryBuilder extends MultiTermQueryBuilder implements BoostableQueryBuilder<FuzzyQueryBuilder> { public class FuzzyQueryBuilder extends AbstractQueryBuilder<FuzzyQueryBuilder> implements MultiTermQueryBuilder<FuzzyQueryBuilder> {
private final String name; public static final String NAME = "fuzzy";
/** Default maximum edit distance. Defaults to AUTO. */
public static final Fuzziness DEFAULT_FUZZINESS = Fuzziness.AUTO;
/** Default number of initial characters which will not be “fuzzified”. Defaults to 0. */
public static final int DEFAULT_PREFIX_LENGTH = FuzzyQuery.defaultPrefixLength;
/** Default maximum number of terms that the fuzzy query will expand to. Defaults to 50. */
public static final int DEFAULT_MAX_EXPANSIONS = FuzzyQuery.defaultMaxExpansions;
/** Default as to whether transpositions should be treated as a primitive edit operation,
* instead of classic Levenshtein algorithm. Defaults to false. */
public static final boolean DEFAULT_TRANSPOSITIONS = false;
private final String fieldName;
private final Object value; private final Object value;
private float boost = -1; private Fuzziness fuzziness = DEFAULT_FUZZINESS;
private Fuzziness fuzziness; private int prefixLength = DEFAULT_PREFIX_LENGTH;
private Integer prefixLength; private int maxExpansions = DEFAULT_MAX_EXPANSIONS;
private Integer maxExpansions;
//LUCENE 4 UPGRADE we need a testcase for this + documentation //LUCENE 4 UPGRADE we need a testcase for this + documentation
private Boolean transpositions; private boolean transpositions = DEFAULT_TRANSPOSITIONS;
private String rewrite; private String rewrite;
private String queryName; static final FuzzyQueryBuilder PROTOTYPE = new FuzzyQueryBuilder();
/** /**
* Constructs a new fuzzy query. * Constructs a new fuzzy query.
* *
* @param name The name of the field * @param fieldName The name of the field
* @param value The value of the text * @param value The value of the text
*/ */
public FuzzyQueryBuilder(String name, Object value) { public FuzzyQueryBuilder(String fieldName, String value) {
this.name = name; this(fieldName, (Object) value);
this.value = value;
} }
/** /**
* Constructs a new fuzzy query. * Constructs a new fuzzy query.
* *
* @param name The name of the field * @param fieldName The name of the field
* @param value The value of the text * @param value The value of the text
*/ */
public FuzzyQueryBuilder(String name, String value) { public FuzzyQueryBuilder(String fieldName, int value) {
this(name, (Object) value); this(fieldName, (Object) value);
} }
/** /**
* Constructs a new fuzzy query. * Constructs a new fuzzy query.
* *
* @param name The name of the field * @param fieldName The name of the field
* @param value The value of the text * @param value The value of the text
*/ */
public FuzzyQueryBuilder(String name, int value) { public FuzzyQueryBuilder(String fieldName, long value) {
this(name, (Object) value); this(fieldName, (Object) value);
} }
/** /**
* Constructs a new fuzzy query. * Constructs a new fuzzy query.
* *
* @param name The name of the field * @param fieldName The name of the field
* @param value The value of the text * @param value The value of the text
*/ */
public FuzzyQueryBuilder(String name, long value) { public FuzzyQueryBuilder(String fieldName, float value) {
this(name, (Object) value); this(fieldName, (Object) value);
} }
/** /**
* Constructs a new fuzzy query. * Constructs a new fuzzy query.
* *
* @param name The name of the field * @param fieldName The name of the field
* @param value The value of the text * @param value The value of the text
*/ */
public FuzzyQueryBuilder(String name, float value) { public FuzzyQueryBuilder(String fieldName, double value) {
this(name, (Object) value); this(fieldName, (Object) value);
} }
/** /**
* Constructs a new fuzzy query. * Constructs a new fuzzy query.
* *
* @param name The name of the field * @param fieldName The name of the field
* @param value The value of the text * @param value The value of the text
*/ */
public FuzzyQueryBuilder(String name, double value) { public FuzzyQueryBuilder(String fieldName, boolean value) {
this(name, (Object) value); this(fieldName, (Object) value);
} }
// NO COMMIT: not sure we should also allow boolean?
/** /**
* Constructs a new fuzzy query. * Constructs a new fuzzy query.
* *
* @param name The name of the field * @param fieldName The name of the field
* @param value The value of the text * @param value The value of the term
*/ */
public FuzzyQueryBuilder(String name, boolean value) { public FuzzyQueryBuilder(String fieldName, Object value) {
this(name, (Object) value); if (Strings.isEmpty(fieldName)) {
throw new IllegalArgumentException("field name cannot be null or empty.");
}
if (value == null) {
throw new IllegalArgumentException("query value cannot be null");
}
this.fieldName = fieldName;
this.value = convertToBytesRefIfString(value);
} }
/** private FuzzyQueryBuilder() {
* Sets the boost for this query. Documents matching this query will (in addition to the normal // for protoype
* weightings) have their score multiplied by the boost provided. this.fieldName = null;
*/ this.value = null;
@Override }
public FuzzyQueryBuilder boost(float boost) {
this.boost = boost; public String fieldName() {
return this; return this.fieldName;
}
public Object value() {
return convertToStringIfBytesRef(this.value);
} }
public FuzzyQueryBuilder fuzziness(Fuzziness fuzziness) { public FuzzyQueryBuilder fuzziness(Fuzziness fuzziness) {
this.fuzziness = fuzziness; this.fuzziness = (fuzziness == null) ? DEFAULT_FUZZINESS : fuzziness;
return this; return this;
} }
public Fuzziness fuzziness() {
return this.fuzziness;
}
public FuzzyQueryBuilder prefixLength(int prefixLength) { public FuzzyQueryBuilder prefixLength(int prefixLength) {
this.prefixLength = prefixLength; this.prefixLength = prefixLength;
return this; return this;
} }
public int prefixLength() {
return this.prefixLength;
}
public FuzzyQueryBuilder maxExpansions(int maxExpansions) { public FuzzyQueryBuilder maxExpansions(int maxExpansions) {
this.maxExpansions = maxExpansions; this.maxExpansions = maxExpansions;
return this; return this;
} }
public int maxExpansions() {
return this.maxExpansions;
}
public FuzzyQueryBuilder transpositions(boolean transpositions) { public FuzzyQueryBuilder transpositions(boolean transpositions) {
this.transpositions = transpositions; this.transpositions = transpositions;
return this; return this;
} }
public boolean transpositions() {
return this.transpositions;
}
public FuzzyQueryBuilder rewrite(String rewrite) { public FuzzyQueryBuilder rewrite(String rewrite) {
this.rewrite = rewrite; this.rewrite = rewrite;
return this; return this;
} }
/** public String rewrite() {
* Sets the query name for the filter that can be used when searching for matched_filters per hit. return this.rewrite;
*/
public FuzzyQueryBuilder queryName(String queryName) {
this.queryName = queryName;
return this;
} }
@Override @Override
public void doXContent(XContentBuilder builder, Params params) throws IOException { public void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(FuzzyQueryParser.NAME); builder.startObject(NAME);
builder.startObject(name); builder.startObject(fieldName);
builder.field("value", value); builder.field("value", convertToStringIfBytesRef(this.value));
if (boost != -1) { fuzziness.toXContent(builder, params);
builder.field("boost", boost); builder.field("prefix_length", prefixLength);
} builder.field("max_expansions", maxExpansions);
if (transpositions != null) { builder.field("transpositions", transpositions);
builder.field("transpositions", transpositions);
}
if (fuzziness != null) {
fuzziness.toXContent(builder, params);
}
if (prefixLength != null) {
builder.field("prefix_length", prefixLength);
}
if (maxExpansions != null) {
builder.field("max_expansions", maxExpansions);
}
if (rewrite != null) { if (rewrite != null) {
builder.field("rewrite", rewrite); builder.field("rewrite", rewrite);
} }
if (queryName != null) { printBoostAndQueryName(builder);
builder.field("_name", queryName);
}
builder.endObject(); builder.endObject();
builder.endObject(); builder.endObject();
} }
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public Query doToQuery(QueryShardContext context) throws IOException {
Query query = null;
if (rewrite == null && context.isFilter()) {
rewrite = QueryParsers.CONSTANT_SCORE.getPreferredName();
}
MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType != null) {
query = fieldType.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions);
}
if (query == null) {
int maxEdits = fuzziness.asDistance(BytesRefs.toString(value));
query = new FuzzyQuery(new Term(fieldName, BytesRefs.toBytesRef(value)), maxEdits, prefixLength, maxExpansions, transpositions);
}
if (query instanceof MultiTermQuery) {
MultiTermQuery.RewriteMethod rewriteMethod = QueryParsers.parseRewriteMethod(context.parseFieldMatcher(), rewrite, null);
QueryParsers.setRewriteMethod((MultiTermQuery) query, rewriteMethod);
}
return query;
}
@Override
public FuzzyQueryBuilder doReadFrom(StreamInput in) throws IOException {
FuzzyQueryBuilder fuzzyQueryBuilder = new FuzzyQueryBuilder(in.readString(), in.readGenericValue());
fuzzyQueryBuilder.fuzziness = Fuzziness.readFuzzinessFrom(in);
fuzzyQueryBuilder.prefixLength = in.readVInt();
fuzzyQueryBuilder.maxExpansions = in.readVInt();
fuzzyQueryBuilder.transpositions = in.readBoolean();
fuzzyQueryBuilder.rewrite = in.readOptionalString();
return fuzzyQueryBuilder;
}
@Override
public void doWriteTo(StreamOutput out) throws IOException {
out.writeString(this.fieldName);
out.writeGenericValue(this.value);
this.fuzziness.writeTo(out);
out.writeVInt(this.prefixLength);
out.writeVInt(this.maxExpansions);
out.writeBoolean(this.transpositions);
out.writeOptionalString(this.rewrite);
}
@Override
public int doHashCode() {
return Objects.hash(fieldName, value, fuzziness, prefixLength, maxExpansions, transpositions, rewrite);
}
@Override
public boolean doEquals(FuzzyQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) &&
Objects.equals(value, other.value) &&
Objects.equals(fuzziness, other.fuzziness) &&
Objects.equals(prefixLength, other.prefixLength) &&
Objects.equals(maxExpansions, other.maxExpansions) &&
Objects.equals(transpositions, other.transpositions) &&
Objects.equals(rewrite, other.rewrite);
}
}

View File

@ -19,61 +19,42 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.query.support.QueryParsers;
import java.io.IOException; import java.io.IOException;
/** public class FuzzyQueryParser implements QueryParser<FuzzyQueryBuilder> {
*
*/
public class FuzzyQueryParser implements QueryParser {
public static final String NAME = "fuzzy";
private static final Fuzziness DEFAULT_FUZZINESS = Fuzziness.AUTO;
private static final ParseField FUZZINESS = Fuzziness.FIELD.withDeprecation("min_similarity"); private static final ParseField FUZZINESS = Fuzziness.FIELD.withDeprecation("min_similarity");
@Inject
public FuzzyQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME}; return new String[]{ FuzzyQueryBuilder.NAME };
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public FuzzyQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
XContentParser.Token token = parser.nextToken(); XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.FIELD_NAME) { if (token != XContentParser.Token.FIELD_NAME) {
throw new ParsingException(parseContext, "[fuzzy] query malformed, no field"); throw new ParsingException(parser.getTokenLocation(), "[fuzzy] query malformed, no field");
} }
String fieldName = parser.currentName();
String fieldName = parser.currentName();
Object value = null; Object value = null;
float boost = 1.0f;
Fuzziness fuzziness = DEFAULT_FUZZINESS; Fuzziness fuzziness = FuzzyQueryBuilder.DEFAULT_FUZZINESS;
int prefixLength = FuzzyQuery.defaultPrefixLength; int prefixLength = FuzzyQueryBuilder.DEFAULT_PREFIX_LENGTH;
int maxExpansions = FuzzyQuery.defaultMaxExpansions; int maxExpansions = FuzzyQueryBuilder.DEFAULT_MAX_EXPANSIONS;
boolean transpositions = FuzzyQuery.defaultTranspositions; boolean transpositions = FuzzyQueryBuilder.DEFAULT_TRANSPOSITIONS;
String rewrite = null;
String queryName = null; String queryName = null;
MultiTermQuery.RewriteMethod rewriteMethod = null; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
if (parseContext.isFilter()) {
rewriteMethod = MultiTermQuery.CONSTANT_SCORE_REWRITE;
}
token = parser.nextToken(); token = parser.nextToken();
if (token == XContentParser.Token.START_OBJECT) { if (token == XContentParser.Token.START_OBJECT) {
String currentFieldName = null; String currentFieldName = null;
@ -94,13 +75,13 @@ public class FuzzyQueryParser implements QueryParser {
} else if ("max_expansions".equals(currentFieldName) || "maxExpansions".equals(currentFieldName)) { } else if ("max_expansions".equals(currentFieldName) || "maxExpansions".equals(currentFieldName)) {
maxExpansions = parser.intValue(); maxExpansions = parser.intValue();
} else if ("transpositions".equals(currentFieldName)) { } else if ("transpositions".equals(currentFieldName)) {
transpositions = parser.booleanValue(); transpositions = parser.booleanValue();
} else if ("rewrite".equals(currentFieldName)) { } else if ("rewrite".equals(currentFieldName)) {
rewriteMethod = QueryParsers.parseRewriteMethod(parseContext.parseFieldMatcher(), parser.textOrNull(), null); rewrite = parser.textOrNull();
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[fuzzy] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[fuzzy] query does not support [" + currentFieldName + "]");
} }
} }
} }
@ -112,26 +93,20 @@ public class FuzzyQueryParser implements QueryParser {
} }
if (value == null) { if (value == null) {
throw new ParsingException(parseContext, "No value specified for fuzzy query"); throw new ParsingException(parser.getTokenLocation(), "no value specified for fuzzy query");
} }
return new FuzzyQueryBuilder(fieldName, value)
Query query = null; .fuzziness(fuzziness)
MappedFieldType fieldType = parseContext.fieldMapper(fieldName); .prefixLength(prefixLength)
if (fieldType != null) { .maxExpansions(maxExpansions)
query = fieldType.fuzzyQuery(value, fuzziness, prefixLength, maxExpansions, transpositions); .transpositions(transpositions)
} .rewrite(rewrite)
if (query == null) { .boost(boost)
int maxEdits = fuzziness.asDistance(BytesRefs.toString(value)); .queryName(queryName);
query = new FuzzyQuery(new Term(fieldName, BytesRefs.toBytesRef(value)), maxEdits, prefixLength, maxExpansions, transpositions); }
}
if (query instanceof MultiTermQuery) {
QueryParsers.setRewriteMethod((MultiTermQuery) query, rewriteMethod);
}
query.setBoost(boost);
if (queryName != null) { @Override
parseContext.addNamedQuery(queryName, query); public FuzzyQueryBuilder getBuilderPrototype() {
} return FuzzyQueryBuilder.PROTOTYPE;
return query;
} }
} }

View File

@ -19,174 +19,319 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.Numbers;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.search.geo.InMemoryGeoBoundingBoxQuery;
import org.elasticsearch.index.search.geo.IndexedGeoBoundingBoxQuery;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
public class GeoBoundingBoxQueryBuilder extends QueryBuilder { /**
* Creates a Lucene query that will filter for all documents that lie within the specified
* bounding box.
*
* This query can only operate on fields of type geo_point that have latitude and longitude
* enabled.
* */
public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBoundingBoxQueryBuilder> {
/** Name of the query. */
public static final String NAME = "geo_bbox";
/** Default type for executing this query (memory as of this writing). */
public static final GeoExecType DEFAULT_TYPE = GeoExecType.MEMORY;
/** Needed for serialization. */
static final GeoBoundingBoxQueryBuilder PROTOTYPE = new GeoBoundingBoxQueryBuilder("");
public static final String TOP_LEFT = GeoBoundingBoxQueryParser.TOP_LEFT; /** Name of field holding geo coordinates to compute the bounding box on.*/
public static final String BOTTOM_RIGHT = GeoBoundingBoxQueryParser.BOTTOM_RIGHT; private final String fieldName;
/** Top left corner coordinates of bounding box. */
private GeoPoint topLeft = new GeoPoint(Double.NaN, Double.NaN);
/** Bottom right corner coordinates of bounding box.*/
private GeoPoint bottomRight = new GeoPoint(Double.NaN, Double.NaN);
/** How to deal with incorrect coordinates.*/
private GeoValidationMethod validationMethod = GeoValidationMethod.DEFAULT;
/** How the query should be run. */
private GeoExecType type = DEFAULT_TYPE;
private static final int TOP = 0; /**
private static final int LEFT = 1; * Create new bounding box query.
private static final int BOTTOM = 2; * @param fieldName name of index field containing geo coordinates to operate on.
private static final int RIGHT = 3; * */
public GeoBoundingBoxQueryBuilder(String fieldName) {
private final String name; if (fieldName == null) {
throw new IllegalArgumentException("Field name must not be empty.");
private double[] box = {Double.NaN, Double.NaN, Double.NaN, Double.NaN}; }
this.fieldName = fieldName;
private String queryName;
private String type;
private Boolean coerce;
private Boolean ignoreMalformed;
public GeoBoundingBoxQueryBuilder(String name) {
this.name = name;
} }
/** /**
* Adds top left point. * Adds top left point.
* * @param top The top latitude
* @param lat The latitude * @param left The left longitude
* @param lon The longitude * @param bottom The bottom latitude
* @param right The right longitude
*/ */
public GeoBoundingBoxQueryBuilder topLeft(double lat, double lon) { public GeoBoundingBoxQueryBuilder setCorners(double top, double left, double bottom, double right) {
box[TOP] = lat; if (GeoValidationMethod.isIgnoreMalformed(validationMethod) == false) {
box[LEFT] = lon; if (Numbers.isValidDouble(top) == false) {
throw new IllegalArgumentException("top latitude is invalid: " + top);
}
if (Numbers.isValidDouble(left) == false) {
throw new IllegalArgumentException("left longitude is invalid: " + left);
}
if (Numbers.isValidDouble(bottom) == false) {
throw new IllegalArgumentException("bottom latitude is invalid: " + bottom);
}
if (Numbers.isValidDouble(right) == false) {
throw new IllegalArgumentException("right longitude is invalid: " + right);
}
// all corners are valid after above checks - make sure they are in the right relation
if (top < bottom) {
throw new IllegalArgumentException("top is below bottom corner: " +
top + " vs. " + bottom);
}
// we do not check longitudes as the query generation code can deal with flipped left/right values
}
topLeft.reset(top, left);
bottomRight.reset(bottom, right);
return this; return this;
} }
public GeoBoundingBoxQueryBuilder topLeft(GeoPoint point) {
return topLeft(point.lat(), point.lon());
}
public GeoBoundingBoxQueryBuilder topLeft(String geohash) {
return topLeft(GeoPoint.fromGeohash(geohash));
}
/** /**
* Adds bottom right corner. * Adds points.
* * @param topLeft topLeft point to add.
* @param lat The latitude * @param bottomRight bottomRight point to add.
* @param lon The longitude * */
*/ public GeoBoundingBoxQueryBuilder setCorners(GeoPoint topLeft, GeoPoint bottomRight) {
public GeoBoundingBoxQueryBuilder bottomRight(double lat, double lon) { return setCorners(topLeft.getLat(), topLeft.getLon(), bottomRight.getLat(), bottomRight.getLon());
box[BOTTOM] = lat;
box[RIGHT] = lon;
return this;
}
public GeoBoundingBoxQueryBuilder bottomRight(GeoPoint point) {
return bottomRight(point.lat(), point.lon());
}
public GeoBoundingBoxQueryBuilder bottomRight(String geohash) {
return bottomRight(GeoPoint.fromGeohash(geohash));
} }
/** /**
* Adds bottom left corner. * Adds points.
* @param topLeft topLeft point to add as geohash.
* @param bottomRight bottomRight point to add as geohash.
* */
public GeoBoundingBoxQueryBuilder setCorners(String topLeft, String bottomRight) {
return setCorners(GeoPoint.fromGeohash(topLeft), GeoPoint.fromGeohash(bottomRight));
}
/** Returns the top left corner of the bounding box. */
public GeoPoint topLeft() {
return topLeft;
}
/** Returns the bottom right corner of the bounding box. */
public GeoPoint bottomRight() {
return bottomRight;
}
/**
* Adds corners in OGC standard bbox/ envelop format.
* *
* @param lat The latitude * @param bottomLeft bottom left corner of bounding box.
* @param lon The longitude * @param topRight top right corner of bounding box.
*/ */
public GeoBoundingBoxQueryBuilder bottomLeft(double lat, double lon) { public GeoBoundingBoxQueryBuilder setCornersOGC(GeoPoint bottomLeft, GeoPoint topRight) {
box[BOTTOM] = lat; return setCorners(topRight.getLat(), bottomLeft.getLon(), bottomLeft.getLat(), topRight.getLon());
box[LEFT] = lon; }
/**
* Adds corners in OGC standard bbox/ envelop format.
*
* @param bottomLeft bottom left corner geohash.
* @param topRight top right corner geohash.
*/
public GeoBoundingBoxQueryBuilder setCornersOGC(String bottomLeft, String topRight) {
return setCornersOGC(GeoPoint.fromGeohash(bottomLeft), GeoPoint.fromGeohash(topRight));
}
/**
* Specify whether or not to ignore validation errors of bounding boxes.
* Can only be set if coerce set to false, otherwise calling this
* method has no effect.
**/
public GeoBoundingBoxQueryBuilder setValidationMethod(GeoValidationMethod method) {
this.validationMethod = method;
return this; return this;
} }
public GeoBoundingBoxQueryBuilder bottomLeft(GeoPoint point) {
return bottomLeft(point.lat(), point.lon());
}
public GeoBoundingBoxQueryBuilder bottomLeft(String geohash) {
return bottomLeft(GeoPoint.fromGeohash(geohash));
}
/** /**
* Adds top right point. * Returns geo coordinate validation method to use.
* * */
* @param lat The latitude public GeoValidationMethod getValidationMethod() {
* @param lon The longitude return this.validationMethod;
*/
public GeoBoundingBoxQueryBuilder topRight(double lat, double lon) {
box[TOP] = lat;
box[RIGHT] = lon;
return this;
}
public GeoBoundingBoxQueryBuilder topRight(GeoPoint point) {
return topRight(point.lat(), point.lon());
}
public GeoBoundingBoxQueryBuilder topRight(String geohash) {
return topRight(GeoPoint.fromGeohash(geohash));
}
/**
* Sets the filter name for the filter that can be used when searching for matched_filters per hit.
*/
public GeoBoundingBoxQueryBuilder queryName(String queryName) {
this.queryName = queryName;
return this;
}
public GeoBoundingBoxQueryBuilder coerce(boolean coerce) {
this.coerce = coerce;
return this;
}
public GeoBoundingBoxQueryBuilder ignoreMalformed(boolean ignoreMalformed) {
this.ignoreMalformed = ignoreMalformed;
return this;
} }
/** /**
* Sets the type of executing of the geo bounding box. Can be either `memory` or `indexed`. Defaults * Sets the type of executing of the geo bounding box. Can be either `memory` or `indexed`. Defaults
* to `memory`. * to `memory`.
*/ */
public GeoBoundingBoxQueryBuilder type(String type) { public GeoBoundingBoxQueryBuilder type(GeoExecType type) {
if (type == null) {
throw new IllegalArgumentException("Type is not allowed to be null.");
}
this.type = type; this.type = type;
return this; return this;
} }
/**
* For BWC: Parse type from type name.
* */
public GeoBoundingBoxQueryBuilder type(String type) {
this.type = GeoExecType.fromString(type);
return this;
}
/** Returns the execution type of the geo bounding box.*/
public GeoExecType type() {
return type;
}
/** Returns the name of the field to base the bounding box computation on. */
public String fieldName() {
return this.fieldName;
}
QueryValidationException checkLatLon(boolean indexCreatedBeforeV2_0) {
// validation was not available prior to 2.x, so to support bwc percolation queries we only ignore_malformed on 2.x created indexes
if (GeoValidationMethod.isIgnoreMalformed(validationMethod) == true || indexCreatedBeforeV2_0) {
return null;
}
QueryValidationException validationException = null;
// For everything post 2.0 validate latitude and longitude unless validation was explicitly turned off
if (GeoUtils.isValidLatitude(topLeft.getLat()) == false) {
validationException = addValidationError("top latitude is invalid: " + topLeft.getLat(),
validationException);
}
if (GeoUtils.isValidLongitude(topLeft.getLon()) == false) {
validationException = addValidationError("left longitude is invalid: " + topLeft.getLon(),
validationException);
}
if (GeoUtils.isValidLatitude(bottomRight.getLat()) == false) {
validationException = addValidationError("bottom latitude is invalid: " + bottomRight.getLat(),
validationException);
}
if (GeoUtils.isValidLongitude(bottomRight.getLon()) == false) {
validationException = addValidationError("right longitude is invalid: " + bottomRight.getLon(),
validationException);
}
return validationException;
}
@Override
public Query doToQuery(QueryShardContext context) {
QueryValidationException exception = checkLatLon(context.indexVersionCreated().before(Version.V_2_0_0));
if (exception != null) {
throw new QueryShardException(context, "couldn't validate latitude/ longitude values", exception);
}
GeoPoint luceneTopLeft = new GeoPoint(topLeft);
GeoPoint luceneBottomRight = new GeoPoint(bottomRight);
if (GeoValidationMethod.isCoerce(validationMethod)) {
// Special case: if the difference between the left and right is 360 and the right is greater than the left, we are asking for
// the complete longitude range so need to set longitude to the complete longditude range
double right = luceneBottomRight.getLon();
double left = luceneTopLeft.getLon();
boolean completeLonRange = ((right - left) % 360 == 0 && right > left);
GeoUtils.normalizePoint(luceneTopLeft, true, !completeLonRange);
GeoUtils.normalizePoint(luceneBottomRight, true, !completeLonRange);
if (completeLonRange) {
luceneTopLeft.resetLon(-180);
luceneBottomRight.resetLon(180);
}
}
MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType == null) {
throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
}
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field");
}
GeoPointFieldMapper.GeoPointFieldType geoFieldType = ((GeoPointFieldMapper.GeoPointFieldType) fieldType);
Query result;
switch(type) {
case INDEXED:
result = IndexedGeoBoundingBoxQuery.create(luceneTopLeft, luceneBottomRight, geoFieldType);
break;
case MEMORY:
IndexGeoPointFieldData indexFieldData = context.getForField(fieldType);
result = new InMemoryGeoBoundingBoxQuery(luceneTopLeft, luceneBottomRight, indexFieldData);
break;
default:
// Someone extended the type enum w/o adjusting this switch statement.
throw new IllegalStateException("geo bounding box type [" + type + "] not supported.");
}
return result;
}
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
// check values builder.startObject(NAME);
if(Double.isNaN(box[TOP])) {
throw new IllegalArgumentException("geo_bounding_box requires top latitude to be set");
} else if(Double.isNaN(box[BOTTOM])) {
throw new IllegalArgumentException("geo_bounding_box requires bottom latitude to be set");
} else if(Double.isNaN(box[RIGHT])) {
throw new IllegalArgumentException("geo_bounding_box requires right longitude to be set");
} else if(Double.isNaN(box[LEFT])) {
throw new IllegalArgumentException("geo_bounding_box requires left longitude to be set");
}
builder.startObject(GeoBoundingBoxQueryParser.NAME);
builder.startObject(name); builder.startObject(fieldName);
builder.array(TOP_LEFT, box[LEFT], box[TOP]); builder.array(GeoBoundingBoxQueryParser.TOP_LEFT, topLeft.getLon(), topLeft.getLat());
builder.array(BOTTOM_RIGHT, box[RIGHT], box[BOTTOM]); builder.array(GeoBoundingBoxQueryParser.BOTTOM_RIGHT, bottomRight.getLon(), bottomRight.getLat());
builder.endObject(); builder.endObject();
builder.field("validation_method", validationMethod);
builder.field("type", type);
if (queryName != null) { printBoostAndQueryName(builder);
builder.field("_name", queryName);
}
if (type != null) {
builder.field("type", type);
}
if (coerce != null) {
builder.field("coerce", coerce);
}
if (ignoreMalformed != null) {
builder.field("ignore_malformed", ignoreMalformed);
}
builder.endObject(); builder.endObject();
} }
@Override
public boolean doEquals(GeoBoundingBoxQueryBuilder other) {
return Objects.equals(topLeft, other.topLeft) &&
Objects.equals(bottomRight, other.bottomRight) &&
Objects.equals(type, other.type) &&
Objects.equals(validationMethod, other.validationMethod) &&
Objects.equals(fieldName, other.fieldName);
}
@Override
public int doHashCode() {
return Objects.hash(topLeft, bottomRight, type, validationMethod, fieldName);
}
@Override
public GeoBoundingBoxQueryBuilder doReadFrom(StreamInput in) throws IOException {
String fieldName = in.readString();
GeoBoundingBoxQueryBuilder geo = new GeoBoundingBoxQueryBuilder(fieldName);
geo.topLeft = geo.topLeft.readFrom(in);
geo.bottomRight = geo.bottomRight.readFrom(in);
geo.type = GeoExecType.readTypeFrom(in);
geo.validationMethod = GeoValidationMethod.readGeoValidationMethodFrom(in);
return geo;
}
@Override
public void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldName);
topLeft.writeTo(out);
bottomRight.writeTo(out);
type.writeTo(out);
validationMethod.writeTo(out);
}
@Override
public String getWriteableName() {
return NAME;
}
} }

View File

@ -19,57 +19,54 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.search.geo.InMemoryGeoBoundingBoxQuery;
import org.elasticsearch.index.search.geo.IndexedGeoBoundingBoxQuery;
import java.io.IOException; import java.io.IOException;
/** public class GeoBoundingBoxQueryParser implements QueryParser<GeoBoundingBoxQueryBuilder> {
*
*/
public class GeoBoundingBoxQueryParser implements QueryParser {
public static final String NAME = "geo_bbox"; public static final String NAME = "geo_bbox";
/** Key to refer to the top of the bounding box. */
public static final String TOP = "top"; public static final String TOP = "top";
/** Key to refer to the left of the bounding box. */
public static final String LEFT = "left"; public static final String LEFT = "left";
/** Key to refer to the right of the bounding box. */
public static final String RIGHT = "right"; public static final String RIGHT = "right";
/** Key to refer to the bottom of the bounding box. */
public static final String BOTTOM = "bottom"; public static final String BOTTOM = "bottom";
/** Key to refer to top_left corner of bounding box. */
public static final String TOP_LEFT = TOP + "_" + LEFT; public static final String TOP_LEFT = TOP + "_" + LEFT;
public static final String TOP_RIGHT = TOP + "_" + RIGHT; /** Key to refer to bottom_right corner of bounding box. */
public static final String BOTTOM_LEFT = BOTTOM + "_" + LEFT;
public static final String BOTTOM_RIGHT = BOTTOM + "_" + RIGHT; public static final String BOTTOM_RIGHT = BOTTOM + "_" + RIGHT;
/** Key to refer to top_right corner of bounding box. */
public static final String TOP_RIGHT = TOP + "_" + RIGHT;
/** Key to refer to bottom left corner of bounding box. */
public static final String BOTTOM_LEFT = BOTTOM + "_" + LEFT;
/** Key to refer to top_left corner of bounding box. */
public static final String TOPLEFT = "topLeft"; public static final String TOPLEFT = "topLeft";
public static final String TOPRIGHT = "topRight"; /** Key to refer to bottom_right corner of bounding box. */
public static final String BOTTOMLEFT = "bottomLeft";
public static final String BOTTOMRIGHT = "bottomRight"; public static final String BOTTOMRIGHT = "bottomRight";
/** Key to refer to top_right corner of bounding box. */
public static final String TOPRIGHT = "topRight";
/** Key to refer to bottom left corner of bounding box. */
public static final String BOTTOMLEFT = "bottomLeft";
public static final String FIELD = "field"; public static final String FIELD = "field";
@Inject
public GeoBoundingBoxQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, "geoBbox", "geo_bounding_box", "geoBoundingBox"}; return new String[]{GeoBoundingBoxQueryBuilder.NAME, "geoBbox", "geo_bounding_box", "geoBoundingBox"};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public GeoBoundingBoxQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
String fieldName = null; String fieldName = null;
@ -78,16 +75,17 @@ public class GeoBoundingBoxQueryParser implements QueryParser {
double bottom = Double.NaN; double bottom = Double.NaN;
double left = Double.NaN; double left = Double.NaN;
double right = Double.NaN; double right = Double.NaN;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null; String queryName = null;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
final boolean indexCreatedBeforeV2_0 = parseContext.indexVersionCreated().before(Version.V_2_0_0); boolean coerce = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
boolean coerce = false; boolean ignoreMalformed = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
boolean ignoreMalformed = false; GeoValidationMethod validationMethod = null;
GeoPoint sparse = new GeoPoint(); GeoPoint sparse = new GeoPoint();
String type = "memory"; String type = "memory";
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -140,74 +138,43 @@ public class GeoBoundingBoxQueryParser implements QueryParser {
} else if (token.isValue()) { } else if (token.isValue()) {
if ("_name".equals(currentFieldName)) { if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else if ("coerce".equals(currentFieldName) || (indexCreatedBeforeV2_0 && "normalize".equals(currentFieldName))) { } else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue();
} else if ("coerce".equals(currentFieldName) || ("normalize".equals(currentFieldName))) {
coerce = parser.booleanValue(); coerce = parser.booleanValue();
if (coerce == true) { if (coerce) {
ignoreMalformed = true; ignoreMalformed = true;
} }
} else if ("validation_method".equals(currentFieldName)) {
validationMethod = GeoValidationMethod.fromString(parser.text());
} else if ("type".equals(currentFieldName)) { } else if ("type".equals(currentFieldName)) {
type = parser.text(); type = parser.text();
} else if ("ignore_malformed".equals(currentFieldName) && coerce == false) { } else if ("ignore_malformed".equals(currentFieldName)) {
ignoreMalformed = parser.booleanValue(); ignoreMalformed = parser.booleanValue();
} else { } else {
throw new ParsingException(parseContext, "failed to parse [{}] query. unexpected field [{}]", NAME, currentFieldName); throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. unexpected field [{}]", NAME, currentFieldName);
} }
} }
} }
final GeoPoint topLeft = sparse.reset(top, left); //just keep the object final GeoPoint topLeft = sparse.reset(top, left); //just keep the object
final GeoPoint bottomRight = new GeoPoint(bottom, right); final GeoPoint bottomRight = new GeoPoint(bottom, right);
GeoBoundingBoxQueryBuilder builder = new GeoBoundingBoxQueryBuilder(fieldName);
// validation was not available prior to 2.x, so to support bwc percolation queries we only ignore_malformed on 2.x created indexes builder.setCorners(topLeft, bottomRight);
if (!indexCreatedBeforeV2_0 && !ignoreMalformed) { builder.queryName(queryName);
if (topLeft.lat() > 90.0 || topLeft.lat() < -90.0) { builder.boost(boost);
throw new ParsingException(parseContext, "illegal latitude value [{}] for [{}]", topLeft.lat(), NAME); builder.type(GeoExecType.fromString(type));
} if (validationMethod != null) {
if (topLeft.lon() > 180.0 || topLeft.lon() < -180) { // ignore deprecated coerce/ignoreMalformed settings if validationMethod is set
throw new ParsingException(parseContext, "illegal longitude value [{}] for [{}]", topLeft.lon(), NAME); builder.setValidationMethod(validationMethod);
}
if (bottomRight.lat() > 90.0 || bottomRight.lat() < -90.0) {
throw new ParsingException(parseContext, "illegal latitude value [{}] for [{}]", bottomRight.lat(), NAME);
}
if (bottomRight.lon() > 180.0 || bottomRight.lon() < -180) {
throw new ParsingException(parseContext, "illegal longitude value [{}] for [{}]", bottomRight.lon(), NAME);
}
}
if (coerce) {
// Special case: if the difference between the left and right is 360 and the right is greater than the left, we are asking for
// the complete longitude range so need to set longitude to the complete longditude range
boolean completeLonRange = ((right - left) % 360 == 0 && right > left);
GeoUtils.normalizePoint(topLeft, true, !completeLonRange);
GeoUtils.normalizePoint(bottomRight, true, !completeLonRange);
if (completeLonRange) {
topLeft.resetLon(-180);
bottomRight.resetLon(180);
}
}
MappedFieldType fieldType = parseContext.fieldMapper(fieldName);
if (fieldType == null) {
throw new ParsingException(parseContext, "failed to parse [{}] query. could not find [{}] field [{}]", NAME, GeoPointFieldMapper.CONTENT_TYPE, fieldName);
}
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new ParsingException(parseContext, "failed to parse [{}] query. field [{}] is expected to be of type [{}], but is of [{}] type instead", NAME, fieldName, GeoPointFieldMapper.CONTENT_TYPE, fieldType.typeName());
}
GeoPointFieldMapper.GeoPointFieldType geoFieldType = ((GeoPointFieldMapper.GeoPointFieldType) fieldType);
Query filter;
if ("indexed".equals(type)) {
filter = IndexedGeoBoundingBoxQuery.create(topLeft, bottomRight, geoFieldType);
} else if ("memory".equals(type)) {
IndexGeoPointFieldData indexFieldData = parseContext.getForField(fieldType);
filter = new InMemoryGeoBoundingBoxQuery(topLeft, bottomRight, indexFieldData);
} else { } else {
throw new ParsingException(parseContext, "failed to parse [{}] query. geo bounding box type [{}] is not supported. either [indexed] or [memory] are allowed", NAME, type); builder.setValidationMethod(GeoValidationMethod.infer(coerce, ignoreMalformed));
} }
return builder;
}
if (queryName != null) { @Override
parseContext.addNamedQuery(queryName, filter); public GeoBoundingBoxQueryBuilder getBuilderPrototype() {
} return GeoBoundingBoxQueryBuilder.PROTOTYPE;
return filter; }
}
} }

View File

@ -19,122 +19,283 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.search.geo.GeoDistanceRangeQuery;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
public class GeoDistanceQueryBuilder extends QueryBuilder { /**
* Filter results of a query to include only those within a specific distance to some
* geo point.
* */
public class GeoDistanceQueryBuilder extends AbstractQueryBuilder<GeoDistanceQueryBuilder> {
private final String name; /** Name of the query in the query dsl. */
public static final String NAME = "geo_distance";
/** Default for latitude normalization (as of this writing true).*/
public static final boolean DEFAULT_NORMALIZE_LAT = true;
/** Default for longitude normalization (as of this writing true). */
public static final boolean DEFAULT_NORMALIZE_LON = true;
/** Default for distance unit computation. */
public static final DistanceUnit DEFAULT_DISTANCE_UNIT = DistanceUnit.DEFAULT;
/** Default for geo distance computation. */
public static final GeoDistance DEFAULT_GEO_DISTANCE = GeoDistance.DEFAULT;
/** Default for optimising query through pre computed bounding box query. */
public static final String DEFAULT_OPTIMIZE_BBOX = "memory";
private String distance; private final String fieldName;
/** Distance from center to cover. */
private double distance;
/** Point to use as center. */
private GeoPoint center = new GeoPoint(Double.NaN, Double.NaN);
/** Algorithm to use for distance computation. */
private GeoDistance geoDistance = DEFAULT_GEO_DISTANCE;
/** Whether or not to use a bbox for pre-filtering. TODO change to enum? */
private String optimizeBbox = DEFAULT_OPTIMIZE_BBOX;
/** How strict should geo coordinate validation be? */
private GeoValidationMethod validationMethod = GeoValidationMethod.DEFAULT;
private double lat; static final GeoDistanceQueryBuilder PROTOTYPE = new GeoDistanceQueryBuilder("_na_");
private double lon; /**
* Construct new GeoDistanceQueryBuilder.
private String geohash; * @param fieldName name of indexed geo field to operate distance computation on.
* */
private GeoDistance geoDistance; public GeoDistanceQueryBuilder(String fieldName) {
if (Strings.isEmpty(fieldName)) {
private String optimizeBbox; throw new IllegalArgumentException("fieldName must not be null or empty");
}
private String queryName; this.fieldName = fieldName;
private Boolean coerce;
private Boolean ignoreMalformed;
public GeoDistanceQueryBuilder(String name) {
this.name = name;
} }
/** Name of the field this query is operating on. */
public String fieldName() {
return this.fieldName;
}
/** Sets the center point for the query.
* @param point the center of the query
**/
public GeoDistanceQueryBuilder point(GeoPoint point) {
if (point == null) {
throw new IllegalArgumentException("center point must not be null");
}
this.center = point;
return this;
}
/**
* Sets the center point of the query.
* @param lat latitude of center
* @param lon longitude of center
* */
public GeoDistanceQueryBuilder point(double lat, double lon) { public GeoDistanceQueryBuilder point(double lat, double lon) {
this.lat = lat; this.center = new GeoPoint(lat, lon);
this.lon = lon;
return this; return this;
} }
public GeoDistanceQueryBuilder lat(double lat) { /** Returns the center point of the distance query. */
this.lat = lat; public GeoPoint point() {
return this; return this.center;
}
public GeoDistanceQueryBuilder lon(double lon) {
this.lon = lon;
return this;
} }
/** Sets the distance from the center using the default distance unit.*/
public GeoDistanceQueryBuilder distance(String distance) { public GeoDistanceQueryBuilder distance(String distance) {
this.distance = distance; return distance(distance, DistanceUnit.DEFAULT);
}
/** Sets the distance from the center for this query. */
public GeoDistanceQueryBuilder distance(String distance, DistanceUnit unit) {
if (Strings.isEmpty(distance)) {
throw new IllegalArgumentException("distance must not be null or empty");
}
if (unit == null) {
throw new IllegalArgumentException("distance unit must not be null");
}
this.distance = DistanceUnit.parse(distance, unit, DistanceUnit.DEFAULT);
return this; return this;
} }
/** Sets the distance from the center for this query. */
public GeoDistanceQueryBuilder distance(double distance, DistanceUnit unit) { public GeoDistanceQueryBuilder distance(double distance, DistanceUnit unit) {
this.distance = unit.toString(distance); return distance(Double.toString(distance), unit);
return this;
} }
/** Returns the distance configured as radius. */
public double distance() {
return distance;
}
/** Sets the center point for this query. */
public GeoDistanceQueryBuilder geohash(String geohash) { public GeoDistanceQueryBuilder geohash(String geohash) {
this.geohash = geohash; if (Strings.isEmpty(geohash)) {
throw new IllegalArgumentException("geohash must not be null or empty");
}
this.center.resetFromGeoHash(geohash);
return this; return this;
} }
/** Which type of geo distance calculation method to use. */
public GeoDistanceQueryBuilder geoDistance(GeoDistance geoDistance) { public GeoDistanceQueryBuilder geoDistance(GeoDistance geoDistance) {
if (geoDistance == null) {
throw new IllegalArgumentException("geoDistance must not be null");
}
this.geoDistance = geoDistance; this.geoDistance = geoDistance;
return this; return this;
} }
/** Returns geo distance calculation type to use. */
public GeoDistance geoDistance() {
return this.geoDistance;
}
/**
* Set this to memory or indexed if before running the distance
* calculation you want to limit the candidates to hits in the
* enclosing bounding box.
**/
public GeoDistanceQueryBuilder optimizeBbox(String optimizeBbox) { public GeoDistanceQueryBuilder optimizeBbox(String optimizeBbox) {
if (optimizeBbox == null) {
throw new IllegalArgumentException("optimizeBox must not be null");
}
switch (optimizeBbox) {
case "none":
case "memory":
case "indexed":
break;
default:
throw new IllegalArgumentException("optimizeBbox must be one of [none, memory, indexed]");
}
this.optimizeBbox = optimizeBbox; this.optimizeBbox = optimizeBbox;
return this; return this;
} }
/** /**
* Sets the filter name for the filter that can be used when searching for matched_filters per hit. * Returns whether or not to run a BoundingBox query prior to
*/ * distance query for optimization purposes.*/
public GeoDistanceQueryBuilder queryName(String queryName) { public String optimizeBbox() {
this.queryName = queryName; return this.optimizeBbox;
return this;
} }
public GeoDistanceQueryBuilder coerce(boolean coerce) { /** Set validaton method for geo coordinates. */
this.coerce = coerce; public void setValidationMethod(GeoValidationMethod method) {
return this; this.validationMethod = method;
} }
public GeoDistanceQueryBuilder ignoreMalformed(boolean ignoreMalformed) { /** Returns validation method for geo coordinates. */
this.ignoreMalformed = ignoreMalformed; public GeoValidationMethod getValidationMethod() {
return this; return this.validationMethod;
}
@Override
protected Query doToQuery(QueryShardContext shardContext) throws IOException {
QueryValidationException exception = checkLatLon(shardContext.indexVersionCreated().before(Version.V_2_0_0));
if (exception != null) {
throw new QueryShardException(shardContext, "couldn't validate latitude/ longitude values", exception);
}
if (GeoValidationMethod.isCoerce(validationMethod)) {
GeoUtils.normalizePoint(center, true, true);
}
double normDistance = geoDistance.normalize(this.distance, DistanceUnit.DEFAULT);
MappedFieldType fieldType = shardContext.fieldMapper(fieldName);
if (fieldType == null) {
throw new QueryShardException(shardContext, "failed to find geo_point field [" + fieldName + "]");
}
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new QueryShardException(shardContext, "field [" + fieldName + "] is not a geo_point field");
}
GeoPointFieldMapper.GeoPointFieldType geoFieldType = ((GeoPointFieldMapper.GeoPointFieldType) fieldType);
IndexGeoPointFieldData indexFieldData = shardContext.getForField(fieldType);
Query query = new GeoDistanceRangeQuery(center, null, normDistance, true, false, geoDistance, geoFieldType, indexFieldData, optimizeBbox);
return query;
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(GeoDistanceQueryParser.NAME); builder.startObject(NAME);
if (geohash != null) { builder.startArray(fieldName).value(center.lon()).value(center.lat()).endArray();
builder.field(name, geohash);
} else {
builder.startArray(name).value(lon).value(lat).endArray();
}
builder.field("distance", distance); builder.field("distance", distance);
if (geoDistance != null) { builder.field("distance_type", geoDistance.name().toLowerCase(Locale.ROOT));
builder.field("distance_type", geoDistance.name().toLowerCase(Locale.ROOT)); builder.field("optimize_bbox", optimizeBbox);
} builder.field("validation_method", validationMethod);
if (optimizeBbox != null) { printBoostAndQueryName(builder);
builder.field("optimize_bbox", optimizeBbox);
}
if (queryName != null) {
builder.field("_name", queryName);
}
if (coerce != null) {
builder.field("coerce", coerce);
}
if (ignoreMalformed != null) {
builder.field("ignore_malformed", ignoreMalformed);
}
builder.endObject(); builder.endObject();
} }
@Override
public int doHashCode() {
return Objects.hash(center, geoDistance, optimizeBbox, distance, validationMethod);
}
@Override
public boolean doEquals(GeoDistanceQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName) &&
(distance == other.distance) &&
Objects.equals(validationMethod, other.validationMethod) &&
Objects.equals(center, other.center) &&
Objects.equals(optimizeBbox, other.optimizeBbox) &&
Objects.equals(geoDistance, other.geoDistance);
}
@Override
protected GeoDistanceQueryBuilder doReadFrom(StreamInput in) throws IOException {
String fieldName = in.readString();
GeoDistanceQueryBuilder result = new GeoDistanceQueryBuilder(fieldName);
result.distance = in.readDouble();
result.validationMethod = GeoValidationMethod.readGeoValidationMethodFrom(in);
result.center = GeoPoint.readGeoPointFrom(in);
result.optimizeBbox = in.readString();
result.geoDistance = GeoDistance.readGeoDistanceFrom(in);
return result;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldName);
out.writeDouble(distance);
validationMethod.writeTo(out);
center.writeTo(out);
out.writeString(optimizeBbox);
geoDistance.writeTo(out);
}
private QueryValidationException checkLatLon(boolean indexCreatedBeforeV2_0) {
// validation was not available prior to 2.x, so to support bwc percolation queries we only ignore_malformed on 2.x created indexes
if (GeoValidationMethod.isIgnoreMalformed(validationMethod) || indexCreatedBeforeV2_0) {
return null;
}
QueryValidationException validationException = null;
// For everything post 2.0, validate latitude and longitude unless validation was explicitly turned off
if (GeoUtils.isValidLatitude(center.getLat()) == false) {
validationException = addValidationError("center point latitude is invalid: " + center.getLat(), validationException);
}
if (GeoUtils.isValidLongitude(center.getLon()) == false) {
validationException = addValidationError("center point longitude is invalid: " + center.getLon(), validationException);
}
return validationException;
}
@Override
public String getWriteableName() {
return NAME;
}
} }

View File

@ -19,23 +19,19 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper; import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.search.geo.GeoDistanceRangeQuery;
import java.io.IOException; import java.io.IOException;
/** /**
* Parses a GeoDistanceQuery. See also
*
* <pre> * <pre>
* { * {
* "name.lat" : 1.1, * "name.lat" : 1.1,
@ -43,37 +39,32 @@ import java.io.IOException;
* } * }
* </pre> * </pre>
*/ */
public class GeoDistanceQueryParser implements QueryParser { public class GeoDistanceQueryParser implements QueryParser<GeoDistanceQueryBuilder> {
public static final String NAME = "geo_distance";
@Inject
public GeoDistanceQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, "geoDistance"}; return new String[]{GeoDistanceQueryBuilder.NAME, "geoDistance"};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public GeoDistanceQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
XContentParser.Token token; XContentParser.Token token;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null; String queryName = null;
String currentFieldName = null; String currentFieldName = null;
GeoPoint point = new GeoPoint(); GeoPoint point = new GeoPoint(Double.NaN, Double.NaN);
String fieldName = null; String fieldName = null;
double distance = 0;
Object vDistance = null; Object vDistance = null;
DistanceUnit unit = DistanceUnit.DEFAULT; DistanceUnit unit = GeoDistanceQueryBuilder.DEFAULT_DISTANCE_UNIT;
GeoDistance geoDistance = GeoDistance.DEFAULT; GeoDistance geoDistance = GeoDistanceQueryBuilder.DEFAULT_GEO_DISTANCE;
String optimizeBbox = "memory"; String optimizeBbox = GeoDistanceQueryBuilder.DEFAULT_OPTIMIZE_BBOX;
final boolean indexCreatedBeforeV2_0 = parseContext.indexVersionCreated().before(Version.V_2_0_0); boolean coerce = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
boolean coerce = false; boolean ignoreMalformed = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
boolean ignoreMalformed = false; GeoValidationMethod validationMethod = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
@ -85,6 +76,7 @@ public class GeoDistanceQueryParser implements QueryParser {
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
// the json in the format of -> field : { lat : 30, lon : 12 } // the json in the format of -> field : { lat : 30, lon : 12 }
String currentName = parser.currentName(); String currentName = parser.currentName();
assert currentFieldName != null;
fieldName = currentFieldName; fieldName = currentFieldName;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
@ -97,21 +89,21 @@ public class GeoDistanceQueryParser implements QueryParser {
} else if (currentName.equals(GeoPointFieldMapper.Names.GEOHASH)) { } else if (currentName.equals(GeoPointFieldMapper.Names.GEOHASH)) {
point.resetFromGeoHash(parser.text()); point.resetFromGeoHash(parser.text());
} else { } else {
throw new ParsingException(parseContext, "[geo_distance] query does not support [" + currentFieldName throw new ParsingException(parser.getTokenLocation(), "[geo_distance] query does not support [" + currentFieldName
+ "]"); + "]");
} }
} }
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if (currentFieldName.equals("distance")) { if ("distance".equals(currentFieldName)) {
if (token == XContentParser.Token.VALUE_STRING) { if (token == XContentParser.Token.VALUE_STRING) {
vDistance = parser.text(); // a String vDistance = parser.text(); // a String
} else { } else {
vDistance = parser.numberValue(); // a Number vDistance = parser.numberValue(); // a Number
} }
} else if (currentFieldName.equals("unit")) { } else if ("unit".equals(currentFieldName)) {
unit = DistanceUnit.fromString(parser.text()); unit = DistanceUnit.fromString(parser.text());
} else if (currentFieldName.equals("distance_type") || currentFieldName.equals("distanceType")) { } else if ("distance_type".equals(currentFieldName) || "distanceType".equals(currentFieldName)) {
geoDistance = GeoDistance.fromString(parser.text()); geoDistance = GeoDistance.fromString(parser.text());
} else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.LAT_SUFFIX)) { } else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.LAT_SUFFIX)) {
point.resetLat(parser.doubleValue()); point.resetLat(parser.doubleValue());
@ -124,15 +116,19 @@ public class GeoDistanceQueryParser implements QueryParser {
fieldName = currentFieldName.substring(0, currentFieldName.length() - GeoPointFieldMapper.Names.GEOHASH_SUFFIX.length()); fieldName = currentFieldName.substring(0, currentFieldName.length() - GeoPointFieldMapper.Names.GEOHASH_SUFFIX.length());
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue();
} else if ("optimize_bbox".equals(currentFieldName) || "optimizeBbox".equals(currentFieldName)) { } else if ("optimize_bbox".equals(currentFieldName) || "optimizeBbox".equals(currentFieldName)) {
optimizeBbox = parser.textOrNull(); optimizeBbox = parser.textOrNull();
} else if ("coerce".equals(currentFieldName) || (indexCreatedBeforeV2_0 && "normalize".equals(currentFieldName))) { } else if ("coerce".equals(currentFieldName) || ("normalize".equals(currentFieldName))) {
coerce = parser.booleanValue(); coerce = parser.booleanValue();
if (coerce == true) { if (coerce == true) {
ignoreMalformed = true; ignoreMalformed = true;
} }
} else if ("ignore_malformed".equals(currentFieldName) && coerce == false) { } else if ("ignore_malformed".equals(currentFieldName)) {
ignoreMalformed = parser.booleanValue(); ignoreMalformed = parser.booleanValue();
} else if ("validation_method".equals(currentFieldName)) {
validationMethod = GeoValidationMethod.fromString(parser.text());
} else { } else {
point.resetFromString(parser.text()); point.resetFromString(parser.text());
fieldName = currentFieldName; fieldName = currentFieldName;
@ -140,44 +136,31 @@ public class GeoDistanceQueryParser implements QueryParser {
} }
} }
// validation was not available prior to 2.x, so to support bwc percolation queries we only ignore_malformed on 2.x created indexes
if (!indexCreatedBeforeV2_0 && !ignoreMalformed) {
if (point.lat() > 90.0 || point.lat() < -90.0) {
throw new ParsingException(parseContext, "illegal latitude value [{}] for [{}]", point.lat(), NAME);
}
if (point.lon() > 180.0 || point.lon() < -180) {
throw new ParsingException(parseContext, "illegal longitude value [{}] for [{}]", point.lon(), NAME);
}
}
if (coerce) {
GeoUtils.normalizePoint(point, coerce, coerce);
}
if (vDistance == null) { if (vDistance == null) {
throw new ParsingException(parseContext, "geo_distance requires 'distance' to be specified"); throw new ParsingException(parser.getTokenLocation(), "geo_distance requires 'distance' to be specified");
} else if (vDistance instanceof Number) { }
distance = DistanceUnit.DEFAULT.convert(((Number) vDistance).doubleValue(), unit);
GeoDistanceQueryBuilder qb = new GeoDistanceQueryBuilder(fieldName);
if (vDistance instanceof Number) {
qb.distance(((Number) vDistance).doubleValue(), unit);
} else { } else {
distance = DistanceUnit.parse((String) vDistance, unit, DistanceUnit.DEFAULT); qb.distance((String) vDistance, unit);
} }
distance = geoDistance.normalize(distance, DistanceUnit.DEFAULT); qb.point(point);
if (validationMethod != null) {
qb.setValidationMethod(validationMethod);
} else {
qb.setValidationMethod(GeoValidationMethod.infer(coerce, ignoreMalformed));
}
qb.optimizeBbox(optimizeBbox);
qb.geoDistance(geoDistance);
qb.boost(boost);
qb.queryName(queryName);
return qb;
}
MappedFieldType fieldType = parseContext.fieldMapper(fieldName); @Override
if (fieldType == null) { public GeoDistanceQueryBuilder getBuilderPrototype() {
throw new ParsingException(parseContext, "failed to find geo_point field [" + fieldName + "]"); return GeoDistanceQueryBuilder.PROTOTYPE;
}
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new ParsingException(parseContext, "field [" + fieldName + "] is not a geo_point field");
}
GeoPointFieldMapper.GeoPointFieldType geoFieldType = ((GeoPointFieldMapper.GeoPointFieldType) fieldType);
IndexGeoPointFieldData indexFieldData = parseContext.getForField(fieldType);
Query query = new GeoDistanceRangeQuery(point, null, distance, true, false, geoDistance, geoFieldType, indexFieldData, optimizeBbox);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
return query;
} }
} }

View File

@ -19,161 +19,309 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.search.geo.GeoDistanceRangeQuery;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
public class GeoDistanceRangeQueryBuilder extends QueryBuilder { public class GeoDistanceRangeQueryBuilder extends AbstractQueryBuilder<GeoDistanceRangeQueryBuilder> {
private final String name; public static final String NAME = "geo_distance_range";
public static final boolean DEFAULT_INCLUDE_LOWER = true;
public static final boolean DEFAULT_INCLUDE_UPPER = true;
public static final GeoDistance DEFAULT_GEO_DISTANCE = GeoDistance.DEFAULT;
public static final DistanceUnit DEFAULT_UNIT = DistanceUnit.DEFAULT;
public static final String DEFAULT_OPTIMIZE_BBOX = "memory";
private final String fieldName;
private Object from; private Object from;
private Object to; private Object to;
private boolean includeLower = true; private boolean includeLower = DEFAULT_INCLUDE_LOWER;
private boolean includeUpper = true; private boolean includeUpper = DEFAULT_INCLUDE_UPPER;
private double lat; private final GeoPoint point;
private double lon; private GeoDistance geoDistance = DEFAULT_GEO_DISTANCE;
private String geohash; private DistanceUnit unit = DEFAULT_UNIT;
private GeoDistance geoDistance; private String optimizeBbox = DEFAULT_OPTIMIZE_BBOX;
private String queryName; private GeoValidationMethod validationMethod = GeoValidationMethod.DEFAULT;
private String optimizeBbox; static final GeoDistanceRangeQueryBuilder PROTOTYPE = new GeoDistanceRangeQueryBuilder("_na_", new GeoPoint());
private Boolean coerce; public GeoDistanceRangeQueryBuilder(String fieldName, GeoPoint point) {
if (Strings.isEmpty(fieldName)) {
private Boolean ignoreMalformed; throw new IllegalArgumentException("fieldName must not be null");
}
public GeoDistanceRangeQueryBuilder(String name) { if (point == null) {
this.name = name; throw new IllegalArgumentException("point must not be null");
}
this.fieldName = fieldName;
this.point = point;
} }
public GeoDistanceRangeQueryBuilder point(double lat, double lon) { public GeoDistanceRangeQueryBuilder(String fieldName, double lat, double lon) {
this.lat = lat; this(fieldName, new GeoPoint(lat, lon));
this.lon = lon;
return this;
} }
public GeoDistanceRangeQueryBuilder lat(double lat) { public GeoDistanceRangeQueryBuilder(String fieldName, String geohash) {
this.lat = lat; this(fieldName, geohash == null ? null : new GeoPoint().resetFromGeoHash(geohash));
return this;
} }
public GeoDistanceRangeQueryBuilder lon(double lon) { public String fieldName() {
this.lon = lon; return fieldName;
return this;
} }
public GeoDistanceRangeQueryBuilder from(Object from) { public GeoPoint point() {
return point;
}
public GeoDistanceRangeQueryBuilder from(String from) {
if (from == null) {
throw new IllegalArgumentException("[from] must not be null");
}
this.from = from; this.from = from;
return this; return this;
} }
public GeoDistanceRangeQueryBuilder to(Object to) { public GeoDistanceRangeQueryBuilder from(Number from) {
this.to = to; if (from == null) {
return this; throw new IllegalArgumentException("[from] must not be null");
} }
public GeoDistanceRangeQueryBuilder gt(Object from) {
this.from = from; this.from = from;
this.includeLower = false;
return this; return this;
} }
public GeoDistanceRangeQueryBuilder gte(Object from) { public Object from() {
this.from = from; return from;
this.includeLower = true;
return this;
} }
public GeoDistanceRangeQueryBuilder lt(Object to) { public GeoDistanceRangeQueryBuilder to(String to) {
if (to == null) {
throw new IllegalArgumentException("[to] must not be null");
}
this.to = to; this.to = to;
this.includeUpper = false;
return this; return this;
} }
public GeoDistanceRangeQueryBuilder lte(Object to) { public GeoDistanceRangeQueryBuilder to(Number to) {
if (to == null) {
throw new IllegalArgumentException("[to] must not be null");
}
this.to = to; this.to = to;
this.includeUpper = true;
return this; return this;
} }
public Object to() {
return to;
}
public GeoDistanceRangeQueryBuilder includeLower(boolean includeLower) { public GeoDistanceRangeQueryBuilder includeLower(boolean includeLower) {
this.includeLower = includeLower; this.includeLower = includeLower;
return this; return this;
} }
public boolean includeLower() {
return includeLower;
}
public GeoDistanceRangeQueryBuilder includeUpper(boolean includeUpper) { public GeoDistanceRangeQueryBuilder includeUpper(boolean includeUpper) {
this.includeUpper = includeUpper; this.includeUpper = includeUpper;
return this; return this;
} }
public GeoDistanceRangeQueryBuilder geohash(String geohash) { public boolean includeUpper() {
this.geohash = geohash; return includeUpper;
return this;
} }
public GeoDistanceRangeQueryBuilder geoDistance(GeoDistance geoDistance) { public GeoDistanceRangeQueryBuilder geoDistance(GeoDistance geoDistance) {
if (geoDistance == null) {
throw new IllegalArgumentException("geoDistance calculation mode must not be null");
}
this.geoDistance = geoDistance; this.geoDistance = geoDistance;
return this; return this;
} }
public GeoDistance geoDistance() {
return geoDistance;
}
public GeoDistanceRangeQueryBuilder unit(DistanceUnit unit) {
if (unit == null) {
throw new IllegalArgumentException("distance unit must not be null");
}
this.unit = unit;
return this;
}
public DistanceUnit unit() {
return unit;
}
public GeoDistanceRangeQueryBuilder optimizeBbox(String optimizeBbox) { public GeoDistanceRangeQueryBuilder optimizeBbox(String optimizeBbox) {
if (optimizeBbox == null) {
throw new IllegalArgumentException("optimizeBox must not be null");
}
switch (optimizeBbox) {
case "none":
case "memory":
case "indexed":
break;
default:
throw new IllegalArgumentException("optimizeBbox must be one of [none, memory, indexed]");
}
this.optimizeBbox = optimizeBbox; this.optimizeBbox = optimizeBbox;
return this; return this;
} }
public GeoDistanceRangeQueryBuilder coerce(boolean coerce) { public String optimizeBbox() {
this.coerce = coerce; return optimizeBbox;
return this;
} }
public GeoDistanceRangeQueryBuilder ignoreMalformed(boolean ignoreMalformed) { /** Set validation method for coordinates. */
this.ignoreMalformed = ignoreMalformed; public GeoDistanceRangeQueryBuilder setValidationMethod(GeoValidationMethod method) {
this.validationMethod = method;
return this; return this;
} }
/** Returns validation method for coordinates. */
public GeoValidationMethod getValidationMethod(GeoValidationMethod method) {
return this.validationMethod;
}
/** @Override
* Sets the filter name for the filter that can be used when searching for matched_filters per hit. protected Query doToQuery(QueryShardContext context) throws IOException {
*/
public GeoDistanceRangeQueryBuilder queryName(String queryName) { final boolean indexCreatedBeforeV2_0 = context.indexVersionCreated().before(Version.V_2_0_0);
this.queryName = queryName; // validation was not available prior to 2.x, so to support bwc
return this; // percolation queries we only ignore_malformed on 2.x created indexes
if (!indexCreatedBeforeV2_0 && !GeoValidationMethod.isIgnoreMalformed(validationMethod)) {
if (!GeoUtils.isValidLatitude(point.lat())) {
throw new QueryShardException(context, "illegal latitude value [{}] for [{}]", point.lat(), NAME);
}
if (!GeoUtils.isValidLongitude(point.lon())) {
throw new QueryShardException(context, "illegal longitude value [{}] for [{}]", point.lon(), NAME);
}
}
if (GeoValidationMethod.isCoerce(validationMethod)) {
GeoUtils.normalizePoint(point, true, true);
}
Double fromValue = null;
Double toValue = null;
if (from != null) {
if (from instanceof Number) {
fromValue = unit.toMeters(((Number) from).doubleValue());
} else {
fromValue = DistanceUnit.parse((String) from, unit, DistanceUnit.DEFAULT);
}
fromValue = geoDistance.normalize(fromValue, DistanceUnit.DEFAULT);
}
if (to != null) {
if (to instanceof Number) {
toValue = unit.toMeters(((Number) to).doubleValue());
} else {
toValue = DistanceUnit.parse((String) to, unit, DistanceUnit.DEFAULT);
}
toValue = geoDistance.normalize(toValue, DistanceUnit.DEFAULT);
}
MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType == null) {
throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
}
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field");
}
GeoPointFieldMapper.GeoPointFieldType geoFieldType = ((GeoPointFieldMapper.GeoPointFieldType) fieldType);
IndexGeoPointFieldData indexFieldData = context.getForField(fieldType);
return new GeoDistanceRangeQuery(point, fromValue, toValue, includeLower, includeUpper, geoDistance, geoFieldType,
indexFieldData, optimizeBbox);
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(GeoDistanceRangeQueryParser.NAME); builder.startObject(NAME);
if (geohash != null) { builder.startArray(fieldName).value(point.lon()).value(point.lat()).endArray();
builder.field(name, geohash); builder.field(GeoDistanceRangeQueryParser.FROM_FIELD.getPreferredName(), from);
} else { builder.field(GeoDistanceRangeQueryParser.TO_FIELD.getPreferredName(), to);
builder.startArray(name).value(lon).value(lat).endArray(); builder.field(GeoDistanceRangeQueryParser.INCLUDE_LOWER_FIELD.getPreferredName(), includeLower);
} builder.field(GeoDistanceRangeQueryParser.INCLUDE_UPPER_FIELD.getPreferredName(), includeUpper);
builder.field("from", from); builder.field(GeoDistanceRangeQueryParser.UNIT_FIELD.getPreferredName(), unit);
builder.field("to", to); builder.field(GeoDistanceRangeQueryParser.DISTANCE_TYPE_FIELD.getPreferredName(), geoDistance.name().toLowerCase(Locale.ROOT));
builder.field("include_lower", includeLower); builder.field(GeoDistanceRangeQueryParser.OPTIMIZE_BBOX_FIELD.getPreferredName(), optimizeBbox);
builder.field("include_upper", includeUpper); builder.field(GeoDistanceRangeQueryParser.VALIDATION_METHOD.getPreferredName(), validationMethod);
if (geoDistance != null) { printBoostAndQueryName(builder);
builder.field("distance_type", geoDistance.name().toLowerCase(Locale.ROOT));
}
if (optimizeBbox != null) {
builder.field("optimize_bbox", optimizeBbox);
}
if (queryName != null) {
builder.field("_name", queryName);
}
if (coerce != null) {
builder.field("coerce", coerce);
}
if (ignoreMalformed != null) {
builder.field("ignore_malformed", ignoreMalformed);
}
builder.endObject(); builder.endObject();
} }
@Override
protected GeoDistanceRangeQueryBuilder doReadFrom(StreamInput in) throws IOException {
GeoDistanceRangeQueryBuilder queryBuilder = new GeoDistanceRangeQueryBuilder(in.readString(), GeoPoint.readGeoPointFrom(in));
queryBuilder.from = in.readGenericValue();
queryBuilder.to = in.readGenericValue();
queryBuilder.includeLower = in.readBoolean();
queryBuilder.includeUpper = in.readBoolean();
queryBuilder.unit = DistanceUnit.valueOf(in.readString());
queryBuilder.geoDistance = GeoDistance.readGeoDistanceFrom(in);
queryBuilder.optimizeBbox = in.readString();
queryBuilder.validationMethod = GeoValidationMethod.readGeoValidationMethodFrom(in);
return queryBuilder;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldName);
point.writeTo(out);
out.writeGenericValue(from);
out.writeGenericValue(to);
out.writeBoolean(includeLower);
out.writeBoolean(includeUpper);
out.writeString(unit.name());
geoDistance.writeTo(out);;
out.writeString(optimizeBbox);
validationMethod.writeTo(out);
}
@Override
protected boolean doEquals(GeoDistanceRangeQueryBuilder other) {
return ((Objects.equals(fieldName, other.fieldName)) &&
(Objects.equals(point, other.point)) &&
(Objects.equals(from, other.from)) &&
(Objects.equals(to, other.to)) &&
(Objects.equals(includeUpper, other.includeUpper)) &&
(Objects.equals(includeLower, other.includeLower)) &&
(Objects.equals(geoDistance, other.geoDistance)) &&
(Objects.equals(optimizeBbox, other.optimizeBbox)) &&
(Objects.equals(validationMethod, other.validationMethod)));
}
@Override
protected int doHashCode() {
return Objects.hash(fieldName, point, from, to, includeUpper, includeLower, geoDistance, optimizeBbox, validationMethod);
}
@Override
public String getWriteableName() {
return NAME;
}
} }

View File

@ -19,19 +19,13 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query; import org.elasticsearch.common.ParseField;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper; import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.search.geo.GeoDistanceRangeQuery;
import java.io.IOException; import java.io.IOException;
@ -43,71 +37,95 @@ import java.io.IOException;
* } * }
* </pre> * </pre>
*/ */
public class GeoDistanceRangeQueryParser implements QueryParser { public class GeoDistanceRangeQueryParser implements QueryParser<GeoDistanceRangeQueryBuilder> {
public static final String NAME = "geo_distance_range"; public static final ParseField FROM_FIELD = new ParseField("from");
public static final ParseField TO_FIELD = new ParseField("to");
@Inject public static final ParseField INCLUDE_LOWER_FIELD = new ParseField("include_lower");
public GeoDistanceRangeQueryParser() { public static final ParseField INCLUDE_UPPER_FIELD = new ParseField("include_upper");
} public static final ParseField GT_FIELD = new ParseField("gt");
public static final ParseField GTE_FIELD = new ParseField("gte", "ge");
public static final ParseField LT_FIELD = new ParseField("lt");
public static final ParseField LTE_FIELD = new ParseField("lte", "le");
public static final ParseField UNIT_FIELD = new ParseField("unit");
public static final ParseField DISTANCE_TYPE_FIELD = new ParseField("distance_type");
public static final ParseField NAME_FIELD = new ParseField("_name");
public static final ParseField BOOST_FIELD = new ParseField("boost");
public static final ParseField OPTIMIZE_BBOX_FIELD = new ParseField("optimize_bbox");
public static final ParseField COERCE_FIELD = new ParseField("coerce", "normalize");
public static final ParseField IGNORE_MALFORMED_FIELD = new ParseField("ignore_malformed");
public static final ParseField VALIDATION_METHOD = new ParseField("validation_method");
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, "geoDistanceRange"}; return new String[]{GeoDistanceRangeQueryBuilder.NAME, "geoDistanceRange"};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public GeoDistanceRangeQueryBuilder getBuilderPrototype() {
return GeoDistanceRangeQueryBuilder.PROTOTYPE;
}
@Override
public GeoDistanceRangeQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
XContentParser.Token token; XContentParser.Token token;
Float boost = null;
String queryName = null; String queryName = null;
String currentFieldName = null; String currentFieldName = null;
GeoPoint point = new GeoPoint(); GeoPoint point = null;
String fieldName = null; String fieldName = null;
Object vFrom = null; Object vFrom = null;
Object vTo = null; Object vTo = null;
boolean includeLower = true; Boolean includeLower = null;
boolean includeUpper = true; Boolean includeUpper = null;
DistanceUnit unit = DistanceUnit.DEFAULT; DistanceUnit unit = null;
GeoDistance geoDistance = GeoDistance.DEFAULT; GeoDistance geoDistance = null;
String optimizeBbox = "memory"; String optimizeBbox = null;
final boolean indexCreatedBeforeV2_0 = parseContext.indexVersionCreated().before(Version.V_2_0_0); boolean coerce = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
boolean coerce = false; boolean ignoreMalformed = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
boolean ignoreMalformed = false; GeoValidationMethod validationMethod = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (parseContext.isDeprecatedSetting(currentFieldName)) { } else if (parseContext.isDeprecatedSetting(currentFieldName)) {
// skip // skip
} else if (token == XContentParser.Token.START_ARRAY) { } else if (token == XContentParser.Token.START_ARRAY) {
if (point == null) {
point = new GeoPoint();
}
GeoUtils.parseGeoPoint(parser, point); GeoUtils.parseGeoPoint(parser, point);
fieldName = currentFieldName; fieldName = currentFieldName;
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
// the json in the format of -> field : { lat : 30, lon : 12 } // the json in the format of -> field : { lat : 30, lon : 12 }
fieldName = currentFieldName; fieldName = currentFieldName;
if (point == null) {
point = new GeoPoint();
}
GeoUtils.parseGeoPoint(parser, point); GeoUtils.parseGeoPoint(parser, point);
} else if (token.isValue()) { } else if (token.isValue()) {
if (currentFieldName.equals("from")) { if (parseContext.parseFieldMatcher().match(currentFieldName, FROM_FIELD)) {
if (token == XContentParser.Token.VALUE_NULL) { if (token == XContentParser.Token.VALUE_NULL) {
} else if (token == XContentParser.Token.VALUE_STRING) { } else if (token == XContentParser.Token.VALUE_STRING) {
vFrom = parser.text(); // a String vFrom = parser.text(); // a String
} else { } else {
vFrom = parser.numberValue(); // a Number vFrom = parser.numberValue(); // a Number
} }
} else if (currentFieldName.equals("to")) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, TO_FIELD)) {
if (token == XContentParser.Token.VALUE_NULL) { if (token == XContentParser.Token.VALUE_NULL) {
} else if (token == XContentParser.Token.VALUE_STRING) { } else if (token == XContentParser.Token.VALUE_STRING) {
vTo = parser.text(); // a String vTo = parser.text(); // a String
} else { } else {
vTo = parser.numberValue(); // a Number vTo = parser.numberValue(); // a Number
} }
} else if ("include_lower".equals(currentFieldName) || "includeLower".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, INCLUDE_LOWER_FIELD)) {
includeLower = parser.booleanValue(); includeLower = parser.booleanValue();
} else if ("include_upper".equals(currentFieldName) || "includeUpper".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, INCLUDE_UPPER_FIELD)) {
includeUpper = parser.booleanValue(); includeUpper = parser.booleanValue();
} else if ("gt".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, GT_FIELD)) {
if (token == XContentParser.Token.VALUE_NULL) { if (token == XContentParser.Token.VALUE_NULL) {
} else if (token == XContentParser.Token.VALUE_STRING) { } else if (token == XContentParser.Token.VALUE_STRING) {
vFrom = parser.text(); // a String vFrom = parser.text(); // a String
@ -115,7 +133,7 @@ public class GeoDistanceRangeQueryParser implements QueryParser {
vFrom = parser.numberValue(); // a Number vFrom = parser.numberValue(); // a Number
} }
includeLower = false; includeLower = false;
} else if ("gte".equals(currentFieldName) || "ge".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, GTE_FIELD)) {
if (token == XContentParser.Token.VALUE_NULL) { if (token == XContentParser.Token.VALUE_NULL) {
} else if (token == XContentParser.Token.VALUE_STRING) { } else if (token == XContentParser.Token.VALUE_STRING) {
vFrom = parser.text(); // a String vFrom = parser.text(); // a String
@ -123,7 +141,7 @@ public class GeoDistanceRangeQueryParser implements QueryParser {
vFrom = parser.numberValue(); // a Number vFrom = parser.numberValue(); // a Number
} }
includeLower = true; includeLower = true;
} else if ("lt".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, LT_FIELD)) {
if (token == XContentParser.Token.VALUE_NULL) { if (token == XContentParser.Token.VALUE_NULL) {
} else if (token == XContentParser.Token.VALUE_STRING) { } else if (token == XContentParser.Token.VALUE_STRING) {
vTo = parser.text(); // a String vTo = parser.text(); // a String
@ -131,7 +149,7 @@ public class GeoDistanceRangeQueryParser implements QueryParser {
vTo = parser.numberValue(); // a Number vTo = parser.numberValue(); // a Number
} }
includeUpper = false; includeUpper = false;
} else if ("lte".equals(currentFieldName) || "le".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, LTE_FIELD)) {
if (token == XContentParser.Token.VALUE_NULL) { if (token == XContentParser.Token.VALUE_NULL) {
} else if (token == XContentParser.Token.VALUE_STRING) { } else if (token == XContentParser.Token.VALUE_STRING) {
vTo = parser.text(); // a String vTo = parser.text(); // a String
@ -139,84 +157,98 @@ public class GeoDistanceRangeQueryParser implements QueryParser {
vTo = parser.numberValue(); // a Number vTo = parser.numberValue(); // a Number
} }
includeUpper = true; includeUpper = true;
} else if (currentFieldName.equals("unit")) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, UNIT_FIELD)) {
unit = DistanceUnit.fromString(parser.text()); unit = DistanceUnit.fromString(parser.text());
} else if (currentFieldName.equals("distance_type") || currentFieldName.equals("distanceType")) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, DISTANCE_TYPE_FIELD)) {
geoDistance = GeoDistance.fromString(parser.text()); geoDistance = GeoDistance.fromString(parser.text());
} else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.LAT_SUFFIX)) { } else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.LAT_SUFFIX)) {
if (point == null) {
point = new GeoPoint();
}
point.resetLat(parser.doubleValue()); point.resetLat(parser.doubleValue());
fieldName = currentFieldName.substring(0, currentFieldName.length() - GeoPointFieldMapper.Names.LAT_SUFFIX.length()); fieldName = currentFieldName.substring(0, currentFieldName.length() - GeoPointFieldMapper.Names.LAT_SUFFIX.length());
} else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.LON_SUFFIX)) { } else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.LON_SUFFIX)) {
if (point == null) {
point = new GeoPoint();
}
point.resetLon(parser.doubleValue()); point.resetLon(parser.doubleValue());
fieldName = currentFieldName.substring(0, currentFieldName.length() - GeoPointFieldMapper.Names.LON_SUFFIX.length()); fieldName = currentFieldName.substring(0, currentFieldName.length() - GeoPointFieldMapper.Names.LON_SUFFIX.length());
} else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.GEOHASH_SUFFIX)) { } else if (currentFieldName.endsWith(GeoPointFieldMapper.Names.GEOHASH_SUFFIX)) {
point.resetFromGeoHash(parser.text()); point = GeoPoint.fromGeohash(parser.text());
fieldName = currentFieldName.substring(0, currentFieldName.length() - GeoPointFieldMapper.Names.GEOHASH_SUFFIX.length()); fieldName = currentFieldName.substring(0, currentFieldName.length() - GeoPointFieldMapper.Names.GEOHASH_SUFFIX.length());
} else if ("_name".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, NAME_FIELD)) {
queryName = parser.text(); queryName = parser.text();
} else if ("optimize_bbox".equals(currentFieldName) || "optimizeBbox".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, BOOST_FIELD)) {
boost = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, OPTIMIZE_BBOX_FIELD)) {
optimizeBbox = parser.textOrNull(); optimizeBbox = parser.textOrNull();
} else if ("coerce".equals(currentFieldName) || (indexCreatedBeforeV2_0 && "normalize".equals(currentFieldName))) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, COERCE_FIELD)) {
coerce = parser.booleanValue(); coerce = parser.booleanValue();
if (coerce == true) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_MALFORMED_FIELD)) {
ignoreMalformed = true;
}
} else if ("ignore_malformed".equals(currentFieldName) && coerce == false) {
ignoreMalformed = parser.booleanValue(); ignoreMalformed = parser.booleanValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, VALIDATION_METHOD)) {
validationMethod = GeoValidationMethod.fromString(parser.text());
} else { } else {
if (point == null) {
point = new GeoPoint();
}
point.resetFromString(parser.text()); point.resetFromString(parser.text());
fieldName = currentFieldName; fieldName = currentFieldName;
} }
} }
} }
// validation was not available prior to 2.x, so to support bwc percolation queries we only ignore_malformed on 2.x created indexes GeoDistanceRangeQueryBuilder queryBuilder = new GeoDistanceRangeQueryBuilder(fieldName, point);
if (!indexCreatedBeforeV2_0 && !ignoreMalformed) { if (boost != null) {
if (point.lat() > 90.0 || point.lat() < -90.0) { queryBuilder.boost(boost);
throw new ParsingException(parseContext, "illegal latitude value [{}] for [{}]", point.lat(), NAME);
}
if (point.lon() > 180.0 || point.lon() < -180) {
throw new ParsingException(parseContext, "illegal longitude value [{}] for [{}]", point.lon(), NAME);
}
} }
if (coerce) { if (queryName != null) {
GeoUtils.normalizePoint(point, coerce, coerce); queryBuilder.queryName(queryName);
} }
Double from = null;
Double to = null;
if (vFrom != null) { if (vFrom != null) {
if (vFrom instanceof Number) { if (vFrom instanceof Number) {
from = unit.toMeters(((Number) vFrom).doubleValue()); queryBuilder.from((Number) vFrom);
} else { } else {
from = DistanceUnit.parse((String) vFrom, unit, DistanceUnit.DEFAULT); queryBuilder.from((String) vFrom);
} }
from = geoDistance.normalize(from, DistanceUnit.DEFAULT);
} }
if (vTo != null) { if (vTo != null) {
if (vTo instanceof Number) { if (vTo instanceof Number) {
to = unit.toMeters(((Number) vTo).doubleValue()); queryBuilder.to((Number) vTo);
} else { } else {
to = DistanceUnit.parse((String) vTo, unit, DistanceUnit.DEFAULT); queryBuilder.to((String) vTo);
} }
to = geoDistance.normalize(to, DistanceUnit.DEFAULT);
} }
MappedFieldType fieldType = parseContext.fieldMapper(fieldName); if (includeUpper != null) {
if (fieldType == null) { queryBuilder.includeUpper(includeUpper);
throw new ParsingException(parseContext, "failed to find geo_point field [" + fieldName + "]");
} }
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new ParsingException(parseContext, "field [" + fieldName + "] is not a geo_point field");
}
GeoPointFieldMapper.GeoPointFieldType geoFieldType = ((GeoPointFieldMapper.GeoPointFieldType) fieldType);
IndexGeoPointFieldData indexFieldData = parseContext.getForField(fieldType); if (includeLower != null) {
Query query = new GeoDistanceRangeQuery(point, from, to, includeLower, includeUpper, geoDistance, geoFieldType, indexFieldData, optimizeBbox); queryBuilder.includeLower(includeLower);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
} }
return query;
if (unit != null) {
queryBuilder.unit(unit);
}
if (geoDistance != null) {
queryBuilder.geoDistance(geoDistance);
}
if (optimizeBbox != null) {
queryBuilder.optimizeBbox(optimizeBbox);
}
if (validationMethod != null) {
// if validation method is set explicitly ignore deprecated coerce/ignore malformed fields if any
queryBuilder.setValidationMethod(validationMethod);
} else {
queryBuilder.setValidationMethod(GeoValidationMethod.infer(coerce, ignoreMalformed));
}
return queryBuilder;
} }
} }

View File

@ -0,0 +1,73 @@
/*
* 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.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import java.io.IOException;
/** Specifies how a geo query should be run. */
public enum GeoExecType implements Writeable<GeoExecType> {
MEMORY(0), INDEXED(1);
private final int ordinal;
private static final GeoExecType PROTOTYPE = MEMORY;
GeoExecType(int ordinal) {
this.ordinal = ordinal;
}
@Override
public GeoExecType readFrom(StreamInput in) throws IOException {
int ord = in.readVInt();
switch(ord) {
case(0): return MEMORY;
case(1): return INDEXED;
}
throw new ElasticsearchException("unknown serialized type [" + ord + "]");
}
public static GeoExecType readTypeFrom(StreamInput in) throws IOException {
return PROTOTYPE.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(this.ordinal);
}
public static GeoExecType fromString(String typeName) {
if (typeName == null) {
throw new IllegalArgumentException("cannot parse type from null string");
}
for (GeoExecType type : GeoExecType.values()) {
if (type.name().equalsIgnoreCase(typeName)) {
return type;
}
}
throw new IllegalArgumentException("no type can be parsed from ordinal " + typeName);
}
}

View File

@ -19,90 +19,178 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.search.geo.GeoPolygonQuery;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
public class GeoPolygonQueryBuilder extends QueryBuilder { public class GeoPolygonQueryBuilder extends AbstractQueryBuilder<GeoPolygonQueryBuilder> {
public static final String POINTS = GeoPolygonQueryParser.POINTS; public static final String NAME = "geo_polygon";
private final String name;
private final List<GeoPoint> shell = new ArrayList<>(); private static final List<GeoPoint> PROTO_SHAPE = Arrays.asList(new GeoPoint[] { new GeoPoint(1.0, 1.0), new GeoPoint(1.0, 2.0),
new GeoPoint(2.0, 1.0) });
private String queryName; static final GeoPolygonQueryBuilder PROTOTYPE = new GeoPolygonQueryBuilder("field", PROTO_SHAPE);
private Boolean coerce; private final String fieldName;
private Boolean ignoreMalformed; private final List<GeoPoint> shell;
public GeoPolygonQueryBuilder(String name) { private GeoValidationMethod validationMethod = GeoValidationMethod.DEFAULT;
this.name = name;
public GeoPolygonQueryBuilder(String fieldName, List<GeoPoint> points) {
if (Strings.isEmpty(fieldName)) {
throw new IllegalArgumentException("fieldName must not be null");
}
if (points == null || points.isEmpty()) {
throw new IllegalArgumentException("polygon must not be null or empty");
} else {
GeoPoint start = points.get(0);
if (start.equals(points.get(points.size() - 1))) {
if (points.size() < 4) {
throw new IllegalArgumentException("too few points defined for geo_polygon query");
}
} else {
if (points.size() < 3) {
throw new IllegalArgumentException("too few points defined for geo_polygon query");
}
}
}
this.fieldName = fieldName;
this.shell = points;
} }
/** public String fieldName() {
* Adds a point with lat and lon return fieldName;
*
* @param lat The latitude
* @param lon The longitude
*/
public GeoPolygonQueryBuilder addPoint(double lat, double lon) {
return addPoint(new GeoPoint(lat, lon));
} }
public GeoPolygonQueryBuilder addPoint(String geohash) { public List<GeoPoint> points() {
return addPoint(GeoPoint.fromGeohash(geohash)); return shell;
} }
public GeoPolygonQueryBuilder addPoint(GeoPoint point) { /** Sets the validation method to use for geo coordinates. */
shell.add(point); public GeoPolygonQueryBuilder setValidationMethod(GeoValidationMethod method) {
return this; this.validationMethod = method;
}
/**
* Sets the filter name for the filter that can be used when searching for matched_filters per hit.
*/
public GeoPolygonQueryBuilder queryName(String queryName) {
this.queryName = queryName;
return this; return this;
} }
public GeoPolygonQueryBuilder coerce(boolean coerce) { /** Returns the validation method to use for geo coordinates. */
this.coerce = coerce; public GeoValidationMethod getValidationMethod() {
return this; return this.validationMethod;
} }
public GeoPolygonQueryBuilder ignoreMalformed(boolean ignoreMalformed) { @Override
this.ignoreMalformed = ignoreMalformed; protected Query doToQuery(QueryShardContext context) throws IOException {
return this;
if (!shell.get(shell.size() - 1).equals(shell.get(0))) {
shell.add(shell.get(0));
}
final boolean indexCreatedBeforeV2_0 = context.indexVersionCreated().before(Version.V_2_0_0);
// validation was not available prior to 2.x, so to support bwc
// percolation queries we only ignore_malformed on 2.x created indexes
if (!indexCreatedBeforeV2_0 && !GeoValidationMethod.isIgnoreMalformed(validationMethod)) {
for (GeoPoint point : shell) {
if (!GeoUtils.isValidLatitude(point.lat())) {
throw new QueryShardException(context, "illegal latitude value [{}] for [{}]", point.lat(),
GeoPolygonQueryBuilder.NAME);
}
if (!GeoUtils.isValidLongitude(point.lat())) {
throw new QueryShardException(context, "illegal longitude value [{}] for [{}]", point.lon(),
GeoPolygonQueryBuilder.NAME);
}
}
}
if (GeoValidationMethod.isCoerce(validationMethod)) {
for (GeoPoint point : shell) {
GeoUtils.normalizePoint(point, true, true);
}
}
MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType == null) {
throw new QueryShardException(context, "failed to find geo_point field [" + fieldName + "]");
}
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new QueryShardException(context, "field [" + fieldName + "] is not a geo_point field");
}
IndexGeoPointFieldData indexFieldData = context.getForField(fieldType);
return new GeoPolygonQuery(indexFieldData, shell.toArray(new GeoPoint[shell.size()]));
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(GeoPolygonQueryParser.NAME); builder.startObject(NAME);
builder.startObject(name); builder.startObject(fieldName);
builder.startArray(POINTS); builder.startArray(GeoPolygonQueryParser.POINTS_FIELD.getPreferredName());
for (GeoPoint point : shell) { for (GeoPoint point : shell) {
builder.startArray().value(point.lon()).value(point.lat()).endArray(); builder.startArray().value(point.lon()).value(point.lat()).endArray();
} }
builder.endArray(); builder.endArray();
builder.endObject(); builder.endObject();
if (queryName != null) { builder.field(GeoPolygonQueryParser.COERCE_FIELD.getPreferredName(), GeoValidationMethod.isCoerce(validationMethod));
builder.field("_name", queryName); builder.field(GeoPolygonQueryParser.IGNORE_MALFORMED_FIELD.getPreferredName(), GeoValidationMethod.isIgnoreMalformed(validationMethod));
}
if (coerce != null) {
builder.field("coerce", coerce);
}
if (ignoreMalformed != null) {
builder.field("ignore_malformed", ignoreMalformed);
}
printBoostAndQueryName(builder);
builder.endObject(); builder.endObject();
} }
@Override
protected GeoPolygonQueryBuilder doReadFrom(StreamInput in) throws IOException {
String fieldName = in.readString();
List<GeoPoint> shell = new ArrayList<>();
int size = in.readVInt();
for (int i = 0; i < size; i++) {
shell.add(GeoPoint.readGeoPointFrom(in));
}
GeoPolygonQueryBuilder builder = new GeoPolygonQueryBuilder(fieldName, shell);
builder.validationMethod = GeoValidationMethod.readGeoValidationMethodFrom(in);
return builder;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldName);
out.writeVInt(shell.size());
for (GeoPoint point : shell) {
point.writeTo(out);
}
validationMethod.writeTo(out);
}
@Override
protected boolean doEquals(GeoPolygonQueryBuilder other) {
return Objects.equals(validationMethod, other.validationMethod)
&& Objects.equals(fieldName, other.fieldName)
&& Objects.equals(shell, other.shell);
}
@Override
protected int doHashCode() {
return Objects.hash(validationMethod, fieldName, shell);
}
@Override
public String getWriteableName() {
return NAME;
}
} }

View File

@ -19,18 +19,12 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query; import org.elasticsearch.common.ParseField;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token; import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import org.elasticsearch.index.search.geo.GeoPolygonQuery;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -48,31 +42,30 @@ import java.util.List;
* } * }
* </pre> * </pre>
*/ */
public class GeoPolygonQueryParser implements QueryParser { public class GeoPolygonQueryParser implements QueryParser<GeoPolygonQueryBuilder> {
public static final String NAME = "geo_polygon"; public static final ParseField COERCE_FIELD = new ParseField("coerce", "normalize");
public static final String POINTS = "points"; public static final ParseField IGNORE_MALFORMED_FIELD = new ParseField("ignore_malformed");
public static final ParseField VALIDATION_METHOD = new ParseField("validation_method");
@Inject public static final ParseField POINTS_FIELD = new ParseField("points");
public GeoPolygonQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, "geoPolygon"}; return new String[]{GeoPolygonQueryBuilder.NAME, "geoPolygon"};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public GeoPolygonQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
String fieldName = null; String fieldName = null;
List<GeoPoint> shell = new ArrayList<>(); List<GeoPoint> shell = null;
final boolean indexCreatedBeforeV2_0 = parseContext.indexVersionCreated().before(Version.V_2_0_0); Float boost = null;
boolean coerce = false; boolean coerce = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
boolean ignoreMalformed = false; boolean ignoreMalformed = GeoValidationMethod.DEFAULT_LENIENT_PARSING;
GeoValidationMethod validationMethod = null;
String queryName = null; String queryName = null;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
@ -89,86 +82,60 @@ public class GeoPolygonQueryParser implements QueryParser {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_ARRAY) { } else if (token == XContentParser.Token.START_ARRAY) {
if (POINTS.equals(currentFieldName)) { if (parseContext.parseFieldMatcher().match(currentFieldName, POINTS_FIELD)) {
shell = new ArrayList<GeoPoint>();
while ((token = parser.nextToken()) != Token.END_ARRAY) { while ((token = parser.nextToken()) != Token.END_ARRAY) {
shell.add(GeoUtils.parseGeoPoint(parser)); shell.add(GeoUtils.parseGeoPoint(parser));
} }
if (!shell.get(shell.size()-1).equals(shell.get(0))) {
shell.add(shell.get(0));
}
} else { } else {
throw new ParsingException(parseContext, "[geo_polygon] query does not support [" + currentFieldName throw new ParsingException(parser.getTokenLocation(), "[geo_polygon] query does not support [" + currentFieldName
+ "]"); + "]");
} }
} else { } else {
throw new ParsingException(parseContext, "[geo_polygon] query does not support token type [" + token.name() throw new ParsingException(parser.getTokenLocation(), "[geo_polygon] query does not support token type [" + token.name()
+ "] under [" + currentFieldName + "]"); + "] under [" + currentFieldName + "]");
} }
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if ("_name".equals(currentFieldName)) { if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else if ("coerce".equals(currentFieldName) || (indexCreatedBeforeV2_0 && "normalize".equals(currentFieldName))) { } else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, COERCE_FIELD)) {
coerce = parser.booleanValue(); coerce = parser.booleanValue();
if (coerce == true) { if (coerce == true) {
ignoreMalformed = true; ignoreMalformed = true;
} }
} else if ("ignore_malformed".equals(currentFieldName) && coerce == false) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, IGNORE_MALFORMED_FIELD)) {
ignoreMalformed = parser.booleanValue(); ignoreMalformed = parser.booleanValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, VALIDATION_METHOD)) {
validationMethod = GeoValidationMethod.fromString(parser.text());
} else { } else {
throw new ParsingException(parseContext, "[geo_polygon] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[geo_polygon] query does not support [" + currentFieldName + "]");
} }
} else { } else {
throw new ParsingException(parseContext, "[geo_polygon] unexpected token type [" + token.name() + "]"); throw new ParsingException(parser.getTokenLocation(), "[geo_polygon] unexpected token type [" + token.name() + "]");
} }
} }
GeoPolygonQueryBuilder builder = new GeoPolygonQueryBuilder(fieldName, shell);
if (shell.isEmpty()) { if (validationMethod != null) {
throw new ParsingException(parseContext, "no points defined for geo_polygon query"); // if GeoValidationMethod was explicitly set ignore deprecated coerce and ignoreMalformed settings
builder.setValidationMethod(validationMethod);
} else { } else {
if (shell.size() < 3) { builder.setValidationMethod(GeoValidationMethod.infer(coerce, ignoreMalformed));
throw new ParsingException(parseContext, "too few points defined for geo_polygon query");
}
GeoPoint start = shell.get(0);
if (!start.equals(shell.get(shell.size() - 1))) {
shell.add(start);
}
if (shell.size() < 4) {
throw new ParsingException(parseContext, "too few points defined for geo_polygon query");
}
} }
// validation was not available prior to 2.x, so to support bwc percolation queries we only ignore_malformed on 2.x created indexes
if (!indexCreatedBeforeV2_0 && !ignoreMalformed) {
for (GeoPoint point : shell) {
if (point.lat() > 90.0 || point.lat() < -90.0) {
throw new ParsingException(parseContext, "illegal latitude value [{}] for [{}]", point.lat(), NAME);
}
if (point.lon() > 180.0 || point.lon() < -180) {
throw new ParsingException(parseContext, "illegal longitude value [{}] for [{}]", point.lon(), NAME);
}
}
}
if (coerce) {
for (GeoPoint point : shell) {
GeoUtils.normalizePoint(point, coerce, coerce);
}
}
MappedFieldType fieldType = parseContext.fieldMapper(fieldName);
if (fieldType == null) {
throw new ParsingException(parseContext, "failed to find geo_point field [" + fieldName + "]");
}
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new ParsingException(parseContext, "field [" + fieldName + "] is not a geo_point field");
}
IndexGeoPointFieldData indexFieldData = parseContext.getForField(fieldType);
Query query = new GeoPolygonQuery(indexFieldData, shell.toArray(new GeoPoint[shell.size()]));
if (queryName != null) { if (queryName != null) {
parseContext.addNamedQuery(queryName, query); builder.queryName(queryName);
} }
return query; if (boost != null) {
builder.boost(boost);
}
return builder;
}
@Override
public GeoPolygonQueryBuilder getBuilderPrototype() {
return GeoPolygonQueryBuilder.PROTOTYPE;
} }
} }

View File

@ -19,100 +19,182 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.ShapesAvailability;
import org.elasticsearch.common.geo.SpatialStrategy; import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper;
import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
/** /**
* {@link QueryBuilder} that builds a GeoShape Filter * {@link QueryBuilder} that builds a GeoShape Query
*/ */
public class GeoShapeQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<GeoShapeQueryBuilder> { public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuilder> {
private final String name; public static final String NAME = "geo_shape";
public static final String DEFAULT_SHAPE_INDEX_NAME = "shapes";
public static final String DEFAULT_SHAPE_FIELD_NAME = "shape";
public static final ShapeRelation DEFAULT_SHAPE_RELATION = ShapeRelation.INTERSECTS;
private final ShapeBuilder shape; static final GeoShapeQueryBuilder PROTOTYPE = new GeoShapeQueryBuilder("field", new BytesArray(new byte[1]));
private final String fieldName;
// TODO make the ShapeBuilder and subclasses Writable and implement hashCode
// and Equals so ShapeBuilder can be used here
private BytesReference shapeBytes;
private SpatialStrategy strategy = null; private SpatialStrategy strategy = null;
private String queryName;
private final String indexedShapeId; private final String indexedShapeId;
private final String indexedShapeType; private final String indexedShapeType;
private String indexedShapeIndex; private String indexedShapeIndex = DEFAULT_SHAPE_INDEX_NAME;
private String indexedShapePath; private String indexedShapePath = DEFAULT_SHAPE_FIELD_NAME;
private ShapeRelation relation = null; private ShapeRelation relation = DEFAULT_SHAPE_RELATION;
private float boost = -1;
/** /**
* Creates a new GeoShapeQueryBuilder whose Filter will be against the * Creates a new GeoShapeQueryBuilder whose Query will be against the given
* given field name using the given Shape * field name using the given Shape
* *
* @param name Name of the field that will be filtered * @param fieldName
* @param shape Shape used in the filter * Name of the field that will be queried
* @param shape
* Shape used in the Query
*/ */
public GeoShapeQueryBuilder(String name, ShapeBuilder shape) { public GeoShapeQueryBuilder(String fieldName, ShapeBuilder shape) throws IOException {
this(name, shape, null, null, null); this(fieldName, shape, null, null);
} }
/** /**
* Creates a new GeoShapeQueryBuilder whose Filter will be against the * Creates a new GeoShapeQueryBuilder whose Query will be against the given
* given field name using the given Shape * field name and will use the Shape found with the given ID in the given
* type
* *
* @param name Name of the field that will be filtered * @param fieldName
* @param relation {@link ShapeRelation} of query and indexed shape * Name of the field that will be filtered
* @param shape Shape used in the filter * @param indexedShapeId
* ID of the indexed Shape that will be used in the Query
* @param indexedShapeType
* Index type of the indexed Shapes
*/ */
public GeoShapeQueryBuilder(String name, ShapeBuilder shape, ShapeRelation relation) { public GeoShapeQueryBuilder(String fieldName, String indexedShapeId, String indexedShapeType) {
this(name, shape, null, null, relation); this(fieldName, (BytesReference) null, indexedShapeId, indexedShapeType);
} }
/** GeoShapeQueryBuilder(String fieldName, BytesReference shapeBytes) {
* Creates a new GeoShapeQueryBuilder whose Filter will be against the given field name this(fieldName, shapeBytes, null, null);
* and will use the Shape found with the given ID in the given type
*
* @param name Name of the field that will be filtered
* @param indexedShapeId ID of the indexed Shape that will be used in the Filter
* @param indexedShapeType Index type of the indexed Shapes
*/
public GeoShapeQueryBuilder(String name, String indexedShapeId, String indexedShapeType, ShapeRelation relation) {
this(name, null, indexedShapeId, indexedShapeType, relation);
} }
private GeoShapeQueryBuilder(String name, ShapeBuilder shape, String indexedShapeId, String indexedShapeType, ShapeRelation relation) { private GeoShapeQueryBuilder(String fieldName, ShapeBuilder shape, String indexedShapeId, String indexedShapeType) throws IOException {
this.name = name; this(fieldName, new BytesArray(new byte[1]), indexedShapeId, indexedShapeType);
this.shape = shape; if (shape != null) {
XContentBuilder builder = XContentFactory.jsonBuilder();
shape.toXContent(builder, EMPTY_PARAMS);
this.shapeBytes = shape.buildAsBytes(XContentType.JSON);
if (this.shapeBytes.length() == 0) {
throw new IllegalArgumentException("shape must not be empty");
}
} else {
throw new IllegalArgumentException("shape must not be null");
}
}
private GeoShapeQueryBuilder(String fieldName, BytesReference shapeBytes, String indexedShapeId, String indexedShapeType) {
if (fieldName == null) {
throw new IllegalArgumentException("fieldName is required");
}
if ((shapeBytes == null || shapeBytes.length() == 0) && indexedShapeId == null) {
throw new IllegalArgumentException("either shapeBytes or indexedShapeId and indexedShapeType are required");
}
if (indexedShapeId != null && indexedShapeType == null) {
throw new IllegalArgumentException("indexedShapeType is required if indexedShapeId is specified");
}
this.fieldName = fieldName;
this.shapeBytes = shapeBytes;
this.indexedShapeId = indexedShapeId; this.indexedShapeId = indexedShapeId;
this.relation = relation;
this.indexedShapeType = indexedShapeType; this.indexedShapeType = indexedShapeType;
} }
/** /**
* Sets the name of the filter * @return the name of the field that will be queried
*/
public String fieldName() {
return fieldName;
}
/**
* @return the JSON bytes for the shape used in the Query
*/
public BytesReference shapeBytes() {
return shapeBytes;
}
/**
* @return the ID of the indexed Shape that will be used in the Query
*/
public String indexedShapeId() {
return indexedShapeId;
}
/**
* @return the document type of the indexed Shape that will be used in the
* Query
*/
public String indexedShapeType() {
return indexedShapeType;
}
/**
* Defines which spatial strategy will be used for building the geo shape
* Query. When not set, the strategy that will be used will be the one that
* is associated with the geo shape field in the mappings.
* *
* @param queryName Name of the filter * @param strategy
* The spatial strategy to use for building the geo shape Query
* @return this * @return this
*/ */
public GeoShapeQueryBuilder queryName(String queryName) { public GeoShapeQueryBuilder strategy(SpatialStrategy strategy) {
this.queryName = queryName; if (strategy != null && strategy == SpatialStrategy.TERM && relation != ShapeRelation.INTERSECTS) {
throw new IllegalArgumentException("strategy [" + strategy.getStrategyName() + "] only supports relation ["
+ ShapeRelation.INTERSECTS.getRelationName() + "] found relation [" + relation.getRelationName() + "]");
}
this.strategy = strategy;
return this; return this;
} }
/** /**
* Defines which spatial strategy will be used for building the geo shape filter. When not set, the strategy that * @return The spatial strategy to use for building the geo shape Query
* will be used will be the one that is associated with the geo shape field in the mappings.
*
* @param strategy The spatial strategy to use for building the geo shape filter
* @return this
*/ */
public GeoShapeQueryBuilder strategy(SpatialStrategy strategy) { public SpatialStrategy strategy() {
this.strategy = strategy; return strategy;
return this;
} }
/** /**
@ -126,6 +208,14 @@ public class GeoShapeQueryBuilder extends QueryBuilder implements BoostableQuery
return this; return this;
} }
/**
* @return the index name for the indexed Shape that will be used in the
* Query
*/
public String indexedShapeIndex() {
return indexedShapeIndex;
}
/** /**
* Sets the path of the field in the indexed Shape document that has the Shape itself * Sets the path of the field in the indexed Shape document that has the Shape itself
* *
@ -137,6 +227,13 @@ public class GeoShapeQueryBuilder extends QueryBuilder implements BoostableQuery
return this; return this;
} }
/**
* @return the path of the indexed Shape that will be used in the Query
*/
public String indexedShapePath() {
return indexedShapePath;
}
/** /**
* Sets the relation of query shape and indexed shape. * Sets the relation of query shape and indexed shape.
* *
@ -144,55 +241,235 @@ public class GeoShapeQueryBuilder extends QueryBuilder implements BoostableQuery
* @return this * @return this
*/ */
public GeoShapeQueryBuilder relation(ShapeRelation relation) { public GeoShapeQueryBuilder relation(ShapeRelation relation) {
if (relation == null) {
throw new IllegalArgumentException("No Shape Relation defined");
}
if (strategy != null && strategy == SpatialStrategy.TERM && relation != ShapeRelation.INTERSECTS) {
throw new IllegalArgumentException("current strategy [" + strategy.getStrategyName() + "] only supports relation ["
+ ShapeRelation.INTERSECTS.getRelationName() + "] found relation [" + relation.getRelationName() + "]");
}
this.relation = relation; this.relation = relation;
return this; return this;
} }
/**
* @return the relation of query shape and indexed shape to use in the Query
*/
public ShapeRelation relation() {
return relation;
}
@Override @Override
public GeoShapeQueryBuilder boost(float boost) { protected Query doToQuery(QueryShardContext context) throws IOException {
this.boost = boost; ShapeBuilder shape;
return this; if (shapeBytes == null) {
GetRequest getRequest = new GetRequest(indexedShapeIndex, indexedShapeType, indexedShapeId);
getRequest.copyContextAndHeadersFrom(SearchContext.current());
shape = fetch(context.getClient(), getRequest, indexedShapePath);
} else {
XContentParser shapeParser = XContentHelper.createParser(shapeBytes);
shapeParser.nextToken();
shape = ShapeBuilder.parse(shapeParser);
}
MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType == null) {
throw new QueryShardException(context, "Failed to find geo_shape field [" + fieldName + "]");
}
// TODO: This isn't the nicest way to check this
if (!(fieldType instanceof GeoShapeFieldMapper.GeoShapeFieldType)) {
throw new QueryShardException(context, "Field [" + fieldName + "] is not a geo_shape");
}
GeoShapeFieldMapper.GeoShapeFieldType shapeFieldType = (GeoShapeFieldMapper.GeoShapeFieldType) fieldType;
PrefixTreeStrategy strategy = shapeFieldType.defaultStrategy();
if (this.strategy != null) {
strategy = shapeFieldType.resolveStrategy(this.strategy);
}
Query query;
if (strategy instanceof RecursivePrefixTreeStrategy && relation == ShapeRelation.DISJOINT) {
// this strategy doesn't support disjoint anymore: but it did
// before, including creating lucene fieldcache (!)
// in this case, execute disjoint as exists && !intersects
BooleanQuery.Builder bool = new BooleanQuery.Builder();
Query exists = ExistsQueryBuilder.newFilter(context, fieldName);
Filter intersects = strategy.makeFilter(getArgs(shape, ShapeRelation.INTERSECTS));
bool.add(exists, BooleanClause.Occur.MUST);
bool.add(intersects, BooleanClause.Occur.MUST_NOT);
query = new ConstantScoreQuery(bool.build());
} else {
query = strategy.makeQuery(getArgs(shape, relation));
}
return query;
}
/**
* Fetches the Shape with the given ID in the given type and index.
*
* @param getRequest
* GetRequest containing index, type and id
* @param path
* Name or path of the field in the Shape Document where the
* Shape itself is located
* @return Shape with the given ID
* @throws IOException
* Can be thrown while parsing the Shape Document and extracting
* the Shape
*/
private ShapeBuilder fetch(Client client, GetRequest getRequest, String path) throws IOException {
if (ShapesAvailability.JTS_AVAILABLE == false) {
throw new IllegalStateException("JTS not available");
}
getRequest.preference("_local");
getRequest.operationThreaded(false);
GetResponse response = client.get(getRequest).actionGet();
if (!response.isExists()) {
throw new IllegalArgumentException("Shape with ID [" + getRequest.id() + "] in type [" + getRequest.type() + "] not found");
}
String[] pathElements = Strings.splitStringToArray(path, '.');
int currentPathSlot = 0;
XContentParser parser = null;
try {
parser = XContentHelper.createParser(response.getSourceAsBytesRef());
XContentParser.Token currentToken;
while ((currentToken = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (currentToken == XContentParser.Token.FIELD_NAME) {
if (pathElements[currentPathSlot].equals(parser.currentName())) {
parser.nextToken();
if (++currentPathSlot == pathElements.length) {
return ShapeBuilder.parse(parser);
}
} else {
parser.nextToken();
parser.skipChildren();
}
}
}
throw new IllegalStateException("Shape with name [" + getRequest.id() + "] found but missing " + path + " field");
} finally {
if (parser != null) {
parser.close();
}
}
}
public static SpatialArgs getArgs(ShapeBuilder shape, ShapeRelation relation) {
switch (relation) {
case DISJOINT:
return new SpatialArgs(SpatialOperation.IsDisjointTo, shape.build());
case INTERSECTS:
return new SpatialArgs(SpatialOperation.Intersects, shape.build());
case WITHIN:
return new SpatialArgs(SpatialOperation.IsWithin, shape.build());
default:
throw new IllegalArgumentException("invalid relation [" + relation + "]");
}
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(GeoShapeQueryParser.NAME); builder.startObject(NAME);
builder.startObject(name); builder.startObject(fieldName);
if (strategy != null) { if (strategy != null) {
builder.field("strategy", strategy.getStrategyName()); builder.field(GeoShapeQueryParser.STRATEGY_FIELD.getPreferredName(), strategy.getStrategyName());
} }
if (shape != null) { if (shapeBytes != null) {
builder.field("shape", shape); builder.field(GeoShapeQueryParser.SHAPE_FIELD.getPreferredName());
XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(shapeBytes);
parser.nextToken();
builder.copyCurrentStructure(parser);
} else { } else {
builder.startObject("indexed_shape") builder.startObject(GeoShapeQueryParser.INDEXED_SHAPE_FIELD.getPreferredName())
.field("id", indexedShapeId) .field(GeoShapeQueryParser.SHAPE_ID_FIELD.getPreferredName(), indexedShapeId)
.field("type", indexedShapeType); .field(GeoShapeQueryParser.SHAPE_TYPE_FIELD.getPreferredName(), indexedShapeType);
if (indexedShapeIndex != null) { if (indexedShapeIndex != null) {
builder.field("index", indexedShapeIndex); builder.field(GeoShapeQueryParser.SHAPE_INDEX_FIELD.getPreferredName(), indexedShapeIndex);
} }
if (indexedShapePath != null) { if (indexedShapePath != null) {
builder.field("path", indexedShapePath); builder.field(GeoShapeQueryParser.SHAPE_PATH_FIELD.getPreferredName(), indexedShapePath);
} }
builder.endObject(); builder.endObject();
} }
if(relation != null) { if(relation != null) {
builder.field("relation", relation.getRelationName()); builder.field(GeoShapeQueryParser.RELATION_FIELD.getPreferredName(), relation.getRelationName());
} }
builder.endObject(); builder.endObject();
if (boost != -1) { printBoostAndQueryName(builder);
builder.field("boost", boost);
}
if (name != null) {
builder.field("_name", queryName);
}
builder.endObject(); builder.endObject();
} }
@Override
protected GeoShapeQueryBuilder doReadFrom(StreamInput in) throws IOException {
String fieldName = in.readString();
GeoShapeQueryBuilder builder;
if (in.readBoolean()) {
BytesReference shapeBytes = in.readBytesReference();
builder = new GeoShapeQueryBuilder(fieldName, shapeBytes);
} else {
String indexedShapeId = in.readOptionalString();
String indexedShapeType = in.readOptionalString();
String indexedShapeIndex = in.readOptionalString();
String indexedShapePath = in.readOptionalString();
builder = new GeoShapeQueryBuilder(fieldName, indexedShapeId, indexedShapeType);
if (indexedShapeIndex != null) {
builder.indexedShapeIndex = indexedShapeIndex;
}
if (indexedShapePath != null) {
builder.indexedShapePath = indexedShapePath;
}
}
builder.relation = ShapeRelation.DISJOINT.readFrom(in);
builder.strategy = SpatialStrategy.RECURSIVE.readFrom(in);
return builder;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldName);
boolean hasShapeBytes = shapeBytes != null;
out.writeBoolean(hasShapeBytes);
if (hasShapeBytes) {
out.writeBytesReference(shapeBytes);
} else {
out.writeOptionalString(indexedShapeId);
out.writeOptionalString(indexedShapeType);
out.writeOptionalString(indexedShapeIndex);
out.writeOptionalString(indexedShapePath);
}
relation.writeTo(out);
strategy.writeTo(out);
}
@Override
protected boolean doEquals(GeoShapeQueryBuilder other) {
return Objects.equals(fieldName, other.fieldName)
&& Objects.equals(indexedShapeId, other.indexedShapeId)
&& Objects.equals(indexedShapeIndex, other.indexedShapeIndex)
&& Objects.equals(indexedShapePath, other.indexedShapePath)
&& Objects.equals(indexedShapeType, other.indexedShapeType)
&& Objects.equals(relation, other.relation)
&& Objects.equals(shapeBytes, other.shapeBytes)
&& Objects.equals(strategy, other.strategy);
}
@Override
protected int doHashCode() {
return Objects.hash(fieldName, indexedShapeId, indexedShapeIndex,
indexedShapePath, indexedShapeType, relation, shapeBytes, strategy);
}
@Override
public String getWriteableName() {
return NAME;
}
} }

View File

@ -19,59 +19,51 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.*; import org.elasticsearch.common.ParseField;
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper;
import org.elasticsearch.index.search.shape.ShapeFetchService;
import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
public class GeoShapeQueryParser implements QueryParser { public class GeoShapeQueryParser implements QueryParser<GeoShapeQueryBuilder> {
public static final String NAME = "geo_shape"; public static final ParseField SHAPE_FIELD = new ParseField("shape");
public static final ParseField STRATEGY_FIELD = new ParseField("strategy");
private ShapeFetchService fetchService; public static final ParseField RELATION_FIELD = new ParseField("relation");
public static final ParseField INDEXED_SHAPE_FIELD = new ParseField("indexed_shape");
public static class DEFAULTS { public static final ParseField SHAPE_ID_FIELD = new ParseField("id");
public static final String INDEX_NAME = "shapes"; public static final ParseField SHAPE_TYPE_FIELD = new ParseField("type");
public static final String SHAPE_FIELD_NAME = "shape"; public static final ParseField SHAPE_INDEX_FIELD = new ParseField("index");
} public static final ParseField SHAPE_PATH_FIELD = new ParseField("path");
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, Strings.toCamelCase(NAME)}; return new String[]{GeoShapeQueryBuilder.NAME, Strings.toCamelCase(GeoShapeQueryBuilder.NAME)};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public GeoShapeQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
String fieldName = null; String fieldName = null;
ShapeRelation shapeRelation = ShapeRelation.INTERSECTS; ShapeRelation shapeRelation = null;
String strategyName = null; SpatialStrategy strategy = null;
ShapeBuilder shape = null; BytesReference shape = null;
String id = null; String id = null;
String type = null; String type = null;
String index = DEFAULTS.INDEX_NAME; String index = null;
String shapePath = DEFAULTS.SHAPE_FIELD_NAME; String shapePath = null;
XContentParser.Token token; XContentParser.Token token;
String currentFieldName = null; String currentFieldName = null;
float boost = 1f; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null; String queryName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -84,113 +76,78 @@ public class GeoShapeQueryParser implements QueryParser {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
token = parser.nextToken(); token = parser.nextToken();
if ("shape".equals(currentFieldName)) { if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_FIELD)) {
shape = ShapeBuilder.parse(parser); XContentBuilder builder = XContentFactory.contentBuilder(parser.contentType()).copyCurrentStructure(parser);
} else if ("strategy".equals(currentFieldName)) { shape = builder.bytes();
strategyName = parser.text(); } else if (parseContext.parseFieldMatcher().match(currentFieldName, STRATEGY_FIELD)) {
} else if ("relation".equals(currentFieldName)) { String strategyName = parser.text();
strategy = SpatialStrategy.fromString(strategyName);
if (strategy == null) {
throw new ParsingException(parser.getTokenLocation(), "Unknown strategy [" + strategyName + " ]");
}
} else if (parseContext.parseFieldMatcher().match(currentFieldName, RELATION_FIELD)) {
shapeRelation = ShapeRelation.getRelationByName(parser.text()); shapeRelation = ShapeRelation.getRelationByName(parser.text());
if (shapeRelation == null) { if (shapeRelation == null) {
throw new ParsingException(parseContext, "Unknown shape operation [" + parser.text() + " ]"); throw new ParsingException(parser.getTokenLocation(), "Unknown shape operation [" + parser.text() + " ]");
} }
} else if ("indexed_shape".equals(currentFieldName) || "indexedShape".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, INDEXED_SHAPE_FIELD)) {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (token.isValue()) { } else if (token.isValue()) {
if ("id".equals(currentFieldName)) { if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_ID_FIELD)) {
id = parser.text(); id = parser.text();
} else if ("type".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_TYPE_FIELD)) {
type = parser.text(); type = parser.text();
} else if ("index".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_INDEX_FIELD)) {
index = parser.text(); index = parser.text();
} else if ("path".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, SHAPE_PATH_FIELD)) {
shapePath = parser.text(); shapePath = parser.text();
} }
} }
} }
if (id == null) {
throw new ParsingException(parseContext, "ID for indexed shape not provided");
} else if (type == null) {
throw new ParsingException(parseContext, "Type for indexed shape not provided");
}
GetRequest getRequest = new GetRequest(index, type, id);
getRequest.copyContextAndHeadersFrom(SearchContext.current());
shape = fetchService.fetch(getRequest, shapePath);
} else { } else {
throw new ParsingException(parseContext, "[geo_shape] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[geo_shape] query does not support [" + currentFieldName + "]");
} }
} }
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if ("boost".equals(currentFieldName)) { if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
boost = parser.floatValue(); boost = parser.floatValue();
} else if ("_name".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[geo_shape] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[geo_shape] query does not support [" + currentFieldName + "]");
} }
} }
} }
GeoShapeQueryBuilder builder;
if (shape == null) { if (shape != null) {
throw new ParsingException(parseContext, "No Shape defined"); builder = new GeoShapeQueryBuilder(fieldName, shape);
} else if (shapeRelation == null) {
throw new ParsingException(parseContext, "No Shape Relation defined");
}
MappedFieldType fieldType = parseContext.fieldMapper(fieldName);
if (fieldType == null) {
throw new ParsingException(parseContext, "Failed to find geo_shape field [" + fieldName + "]");
}
// TODO: This isn't the nicest way to check this
if (!(fieldType instanceof GeoShapeFieldMapper.GeoShapeFieldType)) {
throw new ParsingException(parseContext, "Field [" + fieldName + "] is not a geo_shape");
}
GeoShapeFieldMapper.GeoShapeFieldType shapeFieldType = (GeoShapeFieldMapper.GeoShapeFieldType) fieldType;
PrefixTreeStrategy strategy = shapeFieldType.defaultStrategy();
if (strategyName != null) {
strategy = shapeFieldType.resolveStrategy(strategyName);
}
Query query;
if (strategy instanceof RecursivePrefixTreeStrategy && shapeRelation == ShapeRelation.DISJOINT) {
// this strategy doesn't support disjoint anymore: but it did before, including creating lucene fieldcache (!)
// in this case, execute disjoint as exists && !intersects
BooleanQuery.Builder bool = new BooleanQuery.Builder();
Query exists = ExistsQueryParser.newFilter(parseContext, fieldName, null);
Filter intersects = strategy.makeFilter(getArgs(shape, ShapeRelation.INTERSECTS));
bool.add(exists, BooleanClause.Occur.MUST);
bool.add(intersects, BooleanClause.Occur.MUST_NOT);
query = new ConstantScoreQuery(bool.build());
} else { } else {
query = strategy.makeQuery(getArgs(shape, shapeRelation)); builder = new GeoShapeQueryBuilder(fieldName, id, type);
}
if (index != null) {
builder.indexedShapeIndex(index);
}
if (shapePath != null) {
builder.indexedShapePath(shapePath);
}
if (shapeRelation != null) {
builder.relation(shapeRelation);
}
if (strategy != null) {
builder.strategy(strategy);
} }
query.setBoost(boost);
if (queryName != null) { if (queryName != null) {
parseContext.addNamedQuery(queryName, query); builder.queryName(queryName);
} }
return query; builder.boost(boost);
return builder;
} }
@Inject(optional = true) @Override
public void setFetchService(@Nullable ShapeFetchService fetchService) { public GeoShapeQueryBuilder getBuilderPrototype() {
this.fetchService = fetchService; return GeoShapeQueryBuilder.PROTOTYPE;
}
public static SpatialArgs getArgs(ShapeBuilder shape, ShapeRelation relation) {
switch(relation) {
case DISJOINT:
return new SpatialArgs(SpatialOperation.IsDisjointTo, shape.build());
case INTERSECTS:
return new SpatialArgs(SpatialOperation.Intersects, shape.build());
case WITHIN:
return new SpatialArgs(SpatialOperation.IsWithin, shape.build());
default:
throw new IllegalArgumentException("");
}
} }
} }

View File

@ -0,0 +1,89 @@
/*
* 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.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.util.CollectionUtils;
import java.io.IOException;
/**
* This enum is used to determine how to deal with invalid geo coordinates in geo related
* queries:
*
* On STRICT validation invalid coordinates cause an exception to be thrown.
* On IGNORE_MALFORMED invalid coordinates are being accepted.
* On COERCE invalid coordinates are being corrected to the most likely valid coordinate.
* */
public enum GeoValidationMethod implements Writeable<GeoValidationMethod>{
COERCE, IGNORE_MALFORMED, STRICT;
public static final GeoValidationMethod DEFAULT = STRICT;
public static final boolean DEFAULT_LENIENT_PARSING = (DEFAULT != STRICT);
private static final GeoValidationMethod PROTOTYPE = DEFAULT;
@Override
public GeoValidationMethod readFrom(StreamInput in) throws IOException {
return GeoValidationMethod.values()[in.readVInt()];
}
public static GeoValidationMethod readGeoValidationMethodFrom(StreamInput in) throws IOException {
return PROTOTYPE.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(this.ordinal());
}
public static GeoValidationMethod fromString(String op) {
for (GeoValidationMethod method : GeoValidationMethod.values()) {
if (method.name().equalsIgnoreCase(op)) {
return method;
}
}
throw new IllegalArgumentException("operator needs to be either " + CollectionUtils.arrayAsArrayList(GeoValidationMethod.values())
+ ", but not [" + op + "]");
}
/** Returns whether or not to skip bounding box validation. */
public static boolean isIgnoreMalformed(GeoValidationMethod method) {
return (method == GeoValidationMethod.IGNORE_MALFORMED || method == GeoValidationMethod.COERCE);
}
/** Returns whether or not to try and fix broken/wrapping bounding boxes. */
public static boolean isCoerce(GeoValidationMethod method) {
return method == GeoValidationMethod.COERCE;
}
/** Returns validation method corresponding to given coerce and ignoreMalformed values. */
public static GeoValidationMethod infer(boolean coerce, boolean ignoreMalformed) {
if (coerce) {
return GeoValidationMethod.COERCE;
} else if (ignoreMalformed) {
return GeoValidationMethod.IGNORE_MALFORMED;
} else {
return GeoValidationMethod.STRICT;
}
}
}

View File

@ -23,11 +23,13 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.util.XGeoHashUtils; import org.apache.lucene.util.XGeoHashUtils;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
@ -38,6 +40,7 @@ import org.elasticsearch.index.mapper.geo.GeoPointFieldMapper;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* A geohash cell filter that filters {@link GeoPoint}s by their geohashes. Basically the a * A geohash cell filter that filters {@link GeoPoint}s by their geohashes. Basically the a
@ -57,8 +60,9 @@ import java.util.List;
public class GeohashCellQuery { public class GeohashCellQuery {
public static final String NAME = "geohash_cell"; public static final String NAME = "geohash_cell";
public static final String NEIGHBORS = "neighbors"; public static final ParseField NEIGHBORS_FIELD = new ParseField("neighbors");
public static final String PRECISION = "precision"; public static final ParseField PRECISION_FIELD = new ParseField("precision");
public static final boolean DEFAULT_NEIGHBORS = false;
/** /**
* Create a new geohash filter for a given set of geohashes. In general this method * Create a new geohash filter for a given set of geohashes. In general this method
@ -70,7 +74,7 @@ public class GeohashCellQuery {
* @param geohashes optional array of additional geohashes * @param geohashes optional array of additional geohashes
* @return a new GeoBoundinboxfilter * @return a new GeoBoundinboxfilter
*/ */
public static Query create(QueryParseContext context, GeoPointFieldMapper.GeoPointFieldType fieldType, String geohash, @Nullable List<CharSequence> geohashes) { public static Query create(QueryShardContext context, GeoPointFieldMapper.GeoPointFieldType fieldType, String geohash, @Nullable List<CharSequence> geohashes) {
MappedFieldType geoHashMapper = fieldType.geohashFieldType(); MappedFieldType geoHashMapper = fieldType.geohashFieldType();
if (geoHashMapper == null) { if (geoHashMapper == null) {
throw new IllegalArgumentException("geohash filter needs geohash_prefix to be enabled"); throw new IllegalArgumentException("geohash filter needs geohash_prefix to be enabled");
@ -89,23 +93,20 @@ public class GeohashCellQuery {
* <code>geohash</code> to be set. the default for a neighbor filteing is * <code>geohash</code> to be set. the default for a neighbor filteing is
* <code>false</code>. * <code>false</code>.
*/ */
public static class Builder extends QueryBuilder { public static class Builder extends AbstractQueryBuilder<Builder> {
// we need to store the geohash rather than the corresponding point, // we need to store the geohash rather than the corresponding point,
// because a transformation from a geohash to a point an back to the // because a transformation from a geohash to a point an back to the
// geohash will extend the accuracy of the hash to max precision // geohash will extend the accuracy of the hash to max precision
// i.e. by filing up with z's. // i.e. by filing up with z's.
private String field; private String fieldName;
private String geohash; private String geohash;
private int levels = -1; private Integer levels = null;
private boolean neighbors; private boolean neighbors = DEFAULT_NEIGHBORS;
private static final Builder PROTOTYPE = new Builder("field", new GeoPoint());
public Builder(String field) {
this(field, null, false);
}
public Builder(String field, GeoPoint point) { public Builder(String field, GeoPoint point) {
this(field, point.geohash(), false); this(field, point == null ? null : point.geohash(), false);
} }
public Builder(String field, String geohash) { public Builder(String field, String geohash) {
@ -113,8 +114,13 @@ public class GeohashCellQuery {
} }
public Builder(String field, String geohash, boolean neighbors) { public Builder(String field, String geohash, boolean neighbors) {
super(); if (Strings.isEmpty(field)) {
this.field = field; throw new IllegalArgumentException("fieldName must not be null");
}
if (Strings.isEmpty(geohash)) {
throw new IllegalArgumentException("geohash or point must be defined");
}
this.fieldName = field;
this.geohash = geohash; this.geohash = geohash;
this.neighbors = neighbors; this.neighbors = neighbors;
} }
@ -134,11 +140,22 @@ public class GeohashCellQuery {
return this; return this;
} }
public String geohash() {
return geohash;
}
public Builder precision(int levels) { public Builder precision(int levels) {
if (levels <= 0) {
throw new IllegalArgumentException("precision must be greater than 0. Found [" + levels + "]");
}
this.levels = levels; this.levels = levels;
return this; return this;
} }
public Integer precision() {
return levels;
}
public Builder precision(String precision) { public Builder precision(String precision) {
double meters = DistanceUnit.parse(precision, DistanceUnit.DEFAULT, DistanceUnit.METERS); double meters = DistanceUnit.parse(precision, DistanceUnit.DEFAULT, DistanceUnit.METERS);
return precision(GeoUtils.geoHashLevelsForPrecision(meters)); return precision(GeoUtils.geoHashLevelsForPrecision(meters));
@ -149,27 +166,107 @@ public class GeohashCellQuery {
return this; return this;
} }
public Builder field(String field) { public boolean neighbors() {
this.field = field; return neighbors;
}
public Builder fieldName(String fieldName) {
this.fieldName = fieldName;
return this; return this;
} }
public String fieldName() {
return fieldName;
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType == null) {
throw new QueryShardException(context, "failed to parse [{}] query. missing [{}] field [{}]", NAME,
GeoPointFieldMapper.CONTENT_TYPE, fieldName);
}
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) {
throw new QueryShardException(context, "failed to parse [{}] query. field [{}] is not a geo_point field", NAME, fieldName);
}
GeoPointFieldMapper.GeoPointFieldType geoFieldType = ((GeoPointFieldMapper.GeoPointFieldType) fieldType);
if (!geoFieldType.isGeohashPrefixEnabled()) {
throw new QueryShardException(context, "failed to parse [{}] query. [geohash_prefix] is not enabled for field [{}]", NAME,
fieldName);
}
if (levels != null) {
int len = Math.min(levels, geohash.length());
geohash = geohash.substring(0, len);
}
Query query;
if (neighbors) {
query = create(context, geoFieldType, geohash, XGeoHashUtils.addNeighbors(geohash, new ArrayList<CharSequence>(8)));
} else {
query = create(context, geoFieldType, geohash, null);
}
return query;
}
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME); builder.startObject(NAME);
if (neighbors) { builder.field(NEIGHBORS_FIELD.getPreferredName(), neighbors);
builder.field(NEIGHBORS, neighbors); if (levels != null) {
builder.field(PRECISION_FIELD.getPreferredName(), levels);
} }
if(levels > 0) { builder.field(fieldName, geohash);
builder.field(PRECISION, levels); printBoostAndQueryName(builder);
}
builder.field(field, geohash);
builder.endObject(); builder.endObject();
} }
@Override
protected Builder doReadFrom(StreamInput in) throws IOException {
String field = in.readString();
String geohash = in.readString();
Builder builder = new Builder(field, geohash);
if (in.readBoolean()) {
builder.precision(in.readVInt());
}
builder.neighbors(in.readBoolean());
return builder;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(fieldName);
out.writeString(geohash);
boolean hasLevels = levels != null;
out.writeBoolean(hasLevels);
if (hasLevels) {
out.writeVInt(levels);
}
out.writeBoolean(neighbors);
}
@Override
protected boolean doEquals(Builder other) {
return Objects.equals(fieldName, other.fieldName)
&& Objects.equals(geohash, other.geohash)
&& Objects.equals(levels, other.levels)
&& Objects.equals(neighbors, other.neighbors);
}
@Override
protected int doHashCode() {
return Objects.hash(fieldName, geohash, levels, neighbors);
}
@Override
public String getWriteableName() {
return NAME;
}
} }
public static class Parser implements QueryParser { public static class Parser implements QueryParser<Builder> {
@Inject @Inject
public Parser() { public Parser() {
@ -181,14 +278,15 @@ public class GeohashCellQuery {
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public Builder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
String fieldName = null; String fieldName = null;
String geohash = null; String geohash = null;
int levels = -1; Integer levels = null;
boolean neighbors = false; Boolean neighbors = null;
String queryName = null;
Float boost = null;
XContentParser.Token token; XContentParser.Token token;
if ((token = parser.currentToken()) != Token.START_OBJECT) { if ((token = parser.currentToken()) != Token.START_OBJECT) {
@ -201,24 +299,31 @@ public class GeohashCellQuery {
if (parseContext.isDeprecatedSetting(field)) { if (parseContext.isDeprecatedSetting(field)) {
// skip // skip
} else if (PRECISION.equals(field)) { } else if (parseContext.parseFieldMatcher().match(field, PRECISION_FIELD)) {
token = parser.nextToken(); token = parser.nextToken();
if(token == Token.VALUE_NUMBER) { if (token == Token.VALUE_NUMBER) {
levels = parser.intValue(); levels = parser.intValue();
} else if(token == Token.VALUE_STRING) { } else if (token == Token.VALUE_STRING) {
double meters = DistanceUnit.parse(parser.text(), DistanceUnit.DEFAULT, DistanceUnit.METERS); double meters = DistanceUnit.parse(parser.text(), DistanceUnit.DEFAULT, DistanceUnit.METERS);
levels = GeoUtils.geoHashLevelsForPrecision(meters); levels = GeoUtils.geoHashLevelsForPrecision(meters);
} }
} else if (NEIGHBORS.equals(field)) { } else if (parseContext.parseFieldMatcher().match(field, NEIGHBORS_FIELD)) {
parser.nextToken(); parser.nextToken();
neighbors = parser.booleanValue(); neighbors = parser.booleanValue();
} else if (parseContext.parseFieldMatcher().match(field, AbstractQueryBuilder.NAME_FIELD)) {
parser.nextToken();
queryName = parser.text();
} else if (parseContext.parseFieldMatcher().match(field, AbstractQueryBuilder.BOOST_FIELD)) {
parser.nextToken();
boost = parser.floatValue();
} else { } else {
fieldName = field; fieldName = field;
token = parser.nextToken(); token = parser.nextToken();
if(token == Token.VALUE_STRING) { if (token == Token.VALUE_STRING) {
// A string indicates either a gehash or a lat/lon string // A string indicates either a geohash or a lat/lon
// string
String location = parser.text(); String location = parser.text();
if(location.indexOf(",")>0) { if (location.indexOf(",") > 0) {
geohash = GeoUtils.parseGeoPoint(parser).geohash(); geohash = GeoUtils.parseGeoPoint(parser).geohash();
} else { } else {
geohash = location; geohash = location;
@ -231,38 +336,25 @@ public class GeohashCellQuery {
throw new ElasticsearchParseException("failed to parse [{}] query. unexpected token [{}]", NAME, token); throw new ElasticsearchParseException("failed to parse [{}] query. unexpected token [{}]", NAME, token);
} }
} }
Builder builder = new Builder(fieldName, geohash);
if (geohash == null) { if (levels != null) {
throw new ParsingException(parseContext, "failed to parse [{}] query. missing geohash value", NAME); builder.precision(levels);
} }
if (neighbors != null) {
MappedFieldType fieldType = parseContext.fieldMapper(fieldName); builder.neighbors(neighbors);
if (fieldType == null) {
throw new ParsingException(parseContext, "failed to parse [{}] query. missing [{}] field [{}]", NAME, GeoPointFieldMapper.CONTENT_TYPE, fieldName);
} }
if (queryName != null) {
if (!(fieldType instanceof GeoPointFieldMapper.GeoPointFieldType)) { builder.queryName(queryName);
throw new ParsingException(parseContext, "failed to parse [{}] query. field [{}] is not a geo_point field", NAME, fieldName);
} }
if (boost != null) {
GeoPointFieldMapper.GeoPointFieldType geoFieldType = ((GeoPointFieldMapper.GeoPointFieldType) fieldType); builder.boost(boost);
if (!geoFieldType.isGeohashPrefixEnabled()) {
throw new ParsingException(parseContext, "failed to parse [{}] query. [geohash_prefix] is not enabled for field [{}]", NAME, fieldName);
} }
return builder;
}
if(levels > 0) { @Override
int len = Math.min(levels, geohash.length()); public GeohashCellQuery.Builder getBuilderPrototype() {
geohash = geohash.substring(0, len); return Builder.PROTOTYPE;
}
Query filter;
if (neighbors) {
filter = create(parseContext, geoFieldType, geohash, XGeoHashUtils.addNeighbors(geohash, new ArrayList<>(8)));
} else {
filter = create(parseContext, geoFieldType, geohash, null);
}
return filter;
} }
} }
} }

View File

@ -18,48 +18,92 @@
*/ */
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultiDocValues;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.join.JoinUtil;
import org.apache.lucene.search.join.ScoreMode;
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.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.support.QueryInnerHitBuilder; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexParentChildFieldData;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
import org.elasticsearch.index.query.support.QueryInnerHits;
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
public class HasChildQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<HasChildQueryBuilder> { /**
* A query builder for <tt>has_child</tt> queries.
private final QueryBuilder queryBuilder; */
public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuilder> {
private String childType;
private float boost = 1.0f;
private String scoreMode;
private Integer minChildren;
private Integer maxChildren;
private String queryName;
private QueryInnerHitBuilder innerHit = null;
public HasChildQueryBuilder(String type, QueryBuilder queryBuilder) {
this.childType = type;
this.queryBuilder = queryBuilder;
}
/** /**
* Sets the boost for this query. Documents matching this query will (in addition to the normal * The queries name
* weightings) have their score multiplied by the boost provided.
*/ */
@Override public static final String NAME = "has_child";
public HasChildQueryBuilder boost(float boost) {
this.boost = boost; /**
return this; * The default maximum number of children that are required to match for the parent to be considered a match.
*/
public static final int DEFAULT_MAX_CHILDREN = Integer.MAX_VALUE;
/**
* The default minimum number of children that are required to match for the parent to be considered a match.
*/
public static final int DEFAULT_MIN_CHILDREN = 0;
/*
* The default score mode that is used to combine score coming from multiple parent documents.
*/
public static final ScoreMode DEFAULT_SCORE_MODE = ScoreMode.None;
private final QueryBuilder query;
private final String type;
private ScoreMode scoreMode = DEFAULT_SCORE_MODE;
private int minChildren = DEFAULT_MIN_CHILDREN;
private int maxChildren = DEFAULT_MAX_CHILDREN;
private QueryInnerHits queryInnerHits;
static final HasChildQueryBuilder PROTOTYPE = new HasChildQueryBuilder("", EmptyQueryBuilder.PROTOTYPE);
public HasChildQueryBuilder(String type, QueryBuilder query, int maxChildren, int minChildren, ScoreMode scoreMode, QueryInnerHits queryInnerHits) {
this(type, query);
scoreMode(scoreMode);
this.maxChildren = maxChildren;
this.minChildren = minChildren;
this.queryInnerHits = queryInnerHits;
}
public HasChildQueryBuilder(String type, QueryBuilder query) {
if (type == null) {
throw new IllegalArgumentException("[" + NAME + "] requires 'type' field");
}
if (query == null) {
throw new IllegalArgumentException("[" + NAME + "] requires 'query' field");
}
this.type = type;
this.query = query;
} }
/** /**
* Defines how the scores from the matching child documents are mapped into the parent document. * Defines how the scores from the matching child documents are mapped into the parent document.
*/ */
public HasChildQueryBuilder scoreMode(String scoreMode) { public HasChildQueryBuilder scoreMode(ScoreMode scoreMode) {
if (scoreMode == null) {
throw new IllegalArgumentException("[" + NAME + "] requires 'score_mode' field");
}
this.scoreMode = scoreMode; this.scoreMode = scoreMode;
return this; return this;
} }
@ -68,6 +112,9 @@ public class HasChildQueryBuilder extends QueryBuilder implements BoostableQuery
* Defines the minimum number of children that are required to match for the parent to be considered a match. * Defines the minimum number of children that are required to match for the parent to be considered a match.
*/ */
public HasChildQueryBuilder minChildren(int minChildren) { public HasChildQueryBuilder minChildren(int minChildren) {
if (minChildren < 0) {
throw new IllegalArgumentException("[" + NAME + "] requires non-negative 'min_children' field");
}
this.minChildren = minChildren; this.minChildren = minChildren;
return this; return this;
} }
@ -76,6 +123,9 @@ public class HasChildQueryBuilder extends QueryBuilder implements BoostableQuery
* Defines the maximum number of children that are required to match for the parent to be considered a match. * Defines the maximum number of children that are required to match for the parent to be considered a match.
*/ */
public HasChildQueryBuilder maxChildren(int maxChildren) { public HasChildQueryBuilder maxChildren(int maxChildren) {
if (maxChildren < 0) {
throw new IllegalArgumentException("[" + NAME + "] requires non-negative 'max_children' field");
}
this.maxChildren = maxChildren; this.maxChildren = maxChildren;
return this; return this;
} }
@ -83,45 +133,252 @@ public class HasChildQueryBuilder extends QueryBuilder implements BoostableQuery
/** /**
* Sets the query name for the filter that can be used when searching for matched_filters per hit. * Sets the query name for the filter that can be used when searching for matched_filters per hit.
*/ */
public HasChildQueryBuilder queryName(String queryName) { public HasChildQueryBuilder innerHit(QueryInnerHits queryInnerHits) {
this.queryName = queryName; this.queryInnerHits = queryInnerHits;
return this; return this;
} }
/** /**
* Sets inner hit definition in the scope of this query and reusing the defined type and query. * Returns inner hit definition in the scope of this query and reusing the defined type and query.
*/ */
public HasChildQueryBuilder innerHit(QueryInnerHitBuilder innerHit) { public QueryInnerHits innerHit() {
this.innerHit = innerHit; return queryInnerHits;
return this;
} }
/**
* Returns the children query to execute.
*/
public QueryBuilder query() {
return query;
}
/**
* Returns the child type
*/
public String childType() {
return type;
}
/**
* Returns how the scores from the matching child documents are mapped into the parent document.
*/
public ScoreMode scoreMode() {
return scoreMode;
}
/**
* Returns the minimum number of children that are required to match for the parent to be considered a match.
* The default is {@value #DEFAULT_MAX_CHILDREN}
*/
public int minChildren() {
return minChildren;
}
/**
* Returns the maximum number of children that are required to match for the parent to be considered a match.
* The default is {@value #DEFAULT_MIN_CHILDREN}
*/
public int maxChildren() { return maxChildren; }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(HasChildQueryParser.NAME); builder.startObject(NAME);
builder.field("query"); builder.field("query");
queryBuilder.toXContent(builder, params); query.toXContent(builder, params);
builder.field("child_type", childType); builder.field("child_type", type);
if (boost != 1.0f) { builder.field("score_mode", scoreMode.name().toLowerCase(Locale.ROOT));
builder.field("boost", boost); builder.field("min_children", minChildren);
} builder.field("max_children", maxChildren);
if (scoreMode != null) { printBoostAndQueryName(builder);
builder.field("score_mode", scoreMode); if (queryInnerHits != null) {
} queryInnerHits.toXContent(builder, params);
if (minChildren != null) {
builder.field("min_children", minChildren);
}
if (maxChildren != null) {
builder.field("max_children", maxChildren);
}
if (queryName != null) {
builder.field("_name", queryName);
}
if (innerHit != null) {
builder.startObject("inner_hits");
builder.value(innerHit);
builder.endObject();
} }
builder.endObject(); builder.endObject();
} }
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
Query innerQuery = query.toQuery(context);
if (innerQuery == null) {
return null;
}
innerQuery.setBoost(boost);
DocumentMapper childDocMapper = context.mapperService().documentMapper(type);
if (childDocMapper == null) {
throw new QueryShardException(context, "[" + NAME + "] no mapping found for type [" + type + "]");
}
ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper();
if (parentFieldMapper.active() == false) {
throw new QueryShardException(context, "[" + NAME + "] _parent field has no parent type configured");
}
if (queryInnerHits != null) {
try (XContentParser parser = queryInnerHits.getXcontentParser()) {
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.START_OBJECT) {
throw new IllegalStateException("start object expected but was: [" + token + "]");
}
InnerHitsSubSearchContext innerHits = context.indexQueryParserService().getInnerHitsQueryParserHelper().parse(parser);
if (innerHits != null) {
ParsedQuery parsedQuery = new ParsedQuery(innerQuery, context.copyNamedQueries());
InnerHitsContext.ParentChildInnerHits parentChildInnerHits = new InnerHitsContext.ParentChildInnerHits(innerHits.getSubSearchContext(), parsedQuery, null, context.mapperService(), childDocMapper);
String name = innerHits.getName() != null ? innerHits.getName() : type;
context.addInnerHits(name, parentChildInnerHits);
}
}
}
String parentType = parentFieldMapper.type();
DocumentMapper parentDocMapper = context.mapperService().documentMapper(parentType);
if (parentDocMapper == null) {
throw new QueryShardException(context, "[" + NAME + "] Type [" + type + "] points to a non existent parent type ["
+ parentType + "]");
}
if (maxChildren > 0 && maxChildren < minChildren) {
throw new QueryShardException(context, "[" + NAME + "] 'max_children' is less than 'min_children'");
}
// wrap the query with type query
innerQuery = Queries.filtered(innerQuery, childDocMapper.typeFilter());
final ParentChildIndexFieldData parentChildIndexFieldData = context.getForField(parentFieldMapper.fieldType());
int maxChildren = maxChildren();
// 0 in pre 2.x p/c impl means unbounded
if (maxChildren == 0) {
maxChildren = Integer.MAX_VALUE;
}
return new LateParsingQuery(parentDocMapper.typeFilter(), innerQuery, minChildren(), maxChildren, parentType, scoreMode, parentChildIndexFieldData);
}
final static class LateParsingQuery extends Query {
private final Query toQuery;
private final Query innerQuery;
private final int minChildren;
private final int maxChildren;
private final String parentType;
private final ScoreMode scoreMode;
private final ParentChildIndexFieldData parentChildIndexFieldData;
LateParsingQuery(Query toQuery, Query innerQuery, int minChildren, int maxChildren, String parentType, ScoreMode scoreMode, ParentChildIndexFieldData parentChildIndexFieldData) {
this.toQuery = toQuery;
this.innerQuery = innerQuery;
this.minChildren = minChildren;
this.maxChildren = maxChildren;
this.parentType = parentType;
this.scoreMode = scoreMode;
this.parentChildIndexFieldData = parentChildIndexFieldData;
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
if (getBoost() != 1.0F) {
return super.rewrite(reader);
}
String joinField = ParentFieldMapper.joinField(parentType);
IndexSearcher indexSearcher = new IndexSearcher(reader);
indexSearcher.setQueryCache(null);
IndexParentChildFieldData indexParentChildFieldData = parentChildIndexFieldData.loadGlobal(indexSearcher.getIndexReader());
MultiDocValues.OrdinalMap ordinalMap = ParentChildIndexFieldData.getOrdinalMap(indexParentChildFieldData, parentType);
return JoinUtil.createJoinQuery(joinField, innerQuery, toQuery, indexSearcher, scoreMode, ordinalMap, minChildren, maxChildren);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
LateParsingQuery that = (LateParsingQuery) o;
if (minChildren != that.minChildren) return false;
if (maxChildren != that.maxChildren) return false;
if (!toQuery.equals(that.toQuery)) return false;
if (!innerQuery.equals(that.innerQuery)) return false;
if (!parentType.equals(that.parentType)) return false;
return scoreMode == that.scoreMode;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + toQuery.hashCode();
result = 31 * result + innerQuery.hashCode();
result = 31 * result + minChildren;
result = 31 * result + maxChildren;
result = 31 * result + parentType.hashCode();
result = 31 * result + scoreMode.hashCode();
return result;
}
@Override
public String toString(String s) {
return "LateParsingQuery {parentType=" + parentType + "}";
}
public int getMinChildren() {
return minChildren;
}
public int getMaxChildren() {
return maxChildren;
}
public ScoreMode getScoreMode() {
return scoreMode;
}
}
@Override
protected boolean doEquals(HasChildQueryBuilder that) {
return Objects.equals(query, that.query)
&& Objects.equals(type, that.type)
&& Objects.equals(scoreMode, that.scoreMode)
&& Objects.equals(minChildren, that.minChildren)
&& Objects.equals(maxChildren, that.maxChildren)
&& Objects.equals(queryInnerHits, that.queryInnerHits);
}
@Override
protected int doHashCode() {
return Objects.hash(query, type, scoreMode, minChildren, maxChildren, queryInnerHits);
}
protected HasChildQueryBuilder(StreamInput in) throws IOException {
type = in.readString();
minChildren = in.readInt();
maxChildren = in.readInt();
final int ordinal = in.readVInt();
scoreMode = ScoreMode.values()[ordinal];
query = in.readQuery();
if (in.readBoolean()) {
queryInnerHits = new QueryInnerHits(in);
}
}
@Override
protected HasChildQueryBuilder doReadFrom(StreamInput in) throws IOException {
return new HasChildQueryBuilder(in);
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(type);
out.writeInt(minChildren());
out.writeInt(maxChildren());
out.writeVInt(scoreMode.ordinal());
out.writeQuery(query);
if (queryInnerHits != null) {
out.writeBoolean(true);
queryInnerHits.writeTo(out);
} else {
out.writeBoolean(false);
}
}
} }

View File

@ -19,82 +19,52 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.MultiDocValues;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.join.JoinUtil;
import org.apache.lucene.search.join.ScoreMode; import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexParentChildFieldData; import org.elasticsearch.index.query.support.QueryInnerHits;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
import org.elasticsearch.index.query.support.InnerHitsQueryParserHelper;
import org.elasticsearch.index.query.support.XContentStructure;
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
import java.io.IOException; import java.io.IOException;
/** /**
* * A query parser for <tt>has_child</tt> queries.
*/ */
public class HasChildQueryParser implements QueryParser { public class HasChildQueryParser implements QueryParser<HasChildQueryBuilder> {
public static final String NAME = "has_child";
private static final ParseField QUERY_FIELD = new ParseField("query", "filter"); private static final ParseField QUERY_FIELD = new ParseField("query", "filter");
private final InnerHitsQueryParserHelper innerHitsQueryParserHelper;
@Inject
public HasChildQueryParser(InnerHitsQueryParserHelper innerHitsQueryParserHelper) {
this.innerHitsQueryParserHelper = innerHitsQueryParserHelper;
}
@Override @Override
public String[] names() { public String[] names() {
return new String[] { NAME, Strings.toCamelCase(NAME) }; return new String[] { HasChildQueryBuilder.NAME, Strings.toCamelCase(HasChildQueryBuilder.NAME) };
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public HasChildQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
boolean queryFound = false;
float boost = 1.0f;
String childType = null; String childType = null;
ScoreMode scoreMode = ScoreMode.None; ScoreMode scoreMode = HasChildQueryBuilder.DEFAULT_SCORE_MODE;
int minChildren = 0; int minChildren = HasChildQueryBuilder.DEFAULT_MIN_CHILDREN;
int maxChildren = Integer.MAX_VALUE; int maxChildren = HasChildQueryBuilder.DEFAULT_MAX_CHILDREN;
String queryName = null; String queryName = null;
InnerHitsSubSearchContext innerHits = null; QueryInnerHits queryInnerHits = null;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
XContentStructure.InnerQuery iq = null; QueryBuilder iqb = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (parseContext.isDeprecatedSetting(currentFieldName)) { } else if (parseContext.isDeprecatedSetting(currentFieldName)) {
// skip // skip
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
// Usually, the query would be parsed here, but the child
// type may not have been extracted yet, so use the
// XContentStructure.<type> facade to parse if available,
// or delay parsing if not.
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) { if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
iq = new XContentStructure.InnerQuery(parseContext, childType == null ? null : new String[] { childType }); iqb = parseContext.parseInnerQueryBuilder();
queryFound = true;
} else if ("inner_hits".equals(currentFieldName)) { } else if ("inner_hits".equals(currentFieldName)) {
innerHits = innerHitsQueryParserHelper.parse(parseContext); queryInnerHits = new QueryInnerHits(parser);
} else { } else {
throw new ParsingException(parseContext, "[has_child] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[has_child] query does not support [" + currentFieldName + "]");
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if ("type".equals(currentFieldName) || "child_type".equals(currentFieldName) || "childType".equals(currentFieldName)) { if ("type".equals(currentFieldName) || "child_type".equals(currentFieldName) || "childType".equals(currentFieldName)) {
@ -110,66 +80,14 @@ public class HasChildQueryParser implements QueryParser {
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[has_child] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[has_child] query does not support [" + currentFieldName + "]");
} }
} }
} }
if (!queryFound) { HasChildQueryBuilder hasChildQueryBuilder = new HasChildQueryBuilder(childType, iqb, maxChildren, minChildren, scoreMode, queryInnerHits);
throw new ParsingException(parseContext, "[has_child] requires 'query' field"); hasChildQueryBuilder.queryName(queryName);
} hasChildQueryBuilder.boost(boost);
if (childType == null) { return hasChildQueryBuilder;
throw new ParsingException(parseContext, "[has_child] requires 'type' field");
}
Query innerQuery = iq.asQuery(childType);
if (innerQuery == null) {
return null;
}
innerQuery.setBoost(boost);
DocumentMapper childDocMapper = parseContext.mapperService().documentMapper(childType);
if (childDocMapper == null) {
throw new ParsingException(parseContext, "[has_child] No mapping for for type [" + childType + "]");
}
ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper();
if (parentFieldMapper.active() == false) {
throw new ParsingException(parseContext, "[has_child] _parent field has no parent type configured");
}
if (innerHits != null) {
ParsedQuery parsedQuery = new ParsedQuery(innerQuery, parseContext.copyNamedQueries());
InnerHitsContext.ParentChildInnerHits parentChildInnerHits = new InnerHitsContext.ParentChildInnerHits(innerHits.getSubSearchContext(), parsedQuery, null, parseContext.mapperService(), childDocMapper);
String name = innerHits.getName() != null ? innerHits.getName() : childType;
parseContext.addInnerHits(name, parentChildInnerHits);
}
String parentType = parentFieldMapper.type();
DocumentMapper parentDocMapper = parseContext.mapperService().documentMapper(parentType);
if (parentDocMapper == null) {
throw new ParsingException(parseContext, "[has_child] Type [" + childType + "] points to a non existent parent type ["
+ parentType + "]");
}
if (maxChildren > 0 && maxChildren < minChildren) {
throw new ParsingException(parseContext, "[has_child] 'max_children' is less than 'min_children'");
}
// wrap the query with type query
innerQuery = Queries.filtered(innerQuery, childDocMapper.typeFilter());
final Query query;
final ParentChildIndexFieldData parentChildIndexFieldData = parseContext.getForField(parentFieldMapper.fieldType());
query = joinUtilHelper(parentType, parentChildIndexFieldData, parentDocMapper.typeFilter(), scoreMode, innerQuery, minChildren, maxChildren);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
query.setBoost(boost);
return query;
}
public static Query joinUtilHelper(String parentType, ParentChildIndexFieldData parentChildIndexFieldData, Query toQuery, ScoreMode scoreMode, Query innerQuery, int minChildren, int maxChildren) throws IOException {
return new LateParsingQuery(toQuery, innerQuery, minChildren, maxChildren, parentType, scoreMode, parentChildIndexFieldData);
} }
public static ScoreMode parseScoreMode(String scoreModeString) { public static ScoreMode parseScoreMode(String scoreModeString) {
@ -187,64 +105,8 @@ public class HasChildQueryParser implements QueryParser {
throw new IllegalArgumentException("No score mode for child query [" + scoreModeString + "] found"); throw new IllegalArgumentException("No score mode for child query [" + scoreModeString + "] found");
} }
final static class LateParsingQuery extends Query { @Override
public HasChildQueryBuilder getBuilderPrototype() {
private final Query toQuery; return HasChildQueryBuilder.PROTOTYPE;
private final Query innerQuery;
private final int minChildren;
private final int maxChildren;
private final String parentType;
private final ScoreMode scoreMode;
private final ParentChildIndexFieldData parentChildIndexFieldData;
private final Object identity = new Object();
LateParsingQuery(Query toQuery, Query innerQuery, int minChildren, int maxChildren, String parentType, ScoreMode scoreMode, ParentChildIndexFieldData parentChildIndexFieldData) {
this.toQuery = toQuery;
this.innerQuery = innerQuery;
this.minChildren = minChildren;
this.maxChildren = maxChildren;
this.parentType = parentType;
this.scoreMode = scoreMode;
this.parentChildIndexFieldData = parentChildIndexFieldData;
}
@Override
public Query rewrite(IndexReader reader) throws IOException {
if (getBoost() != 1.0F) {
return super.rewrite(reader);
}
String joinField = ParentFieldMapper.joinField(parentType);
IndexSearcher indexSearcher = new IndexSearcher(reader);
indexSearcher.setQueryCache(null);
IndexParentChildFieldData indexParentChildFieldData = parentChildIndexFieldData.loadGlobal(indexSearcher.getIndexReader());
MultiDocValues.OrdinalMap ordinalMap = ParentChildIndexFieldData.getOrdinalMap(indexParentChildFieldData, parentType);
return JoinUtil.createJoinQuery(joinField, innerQuery, toQuery, indexSearcher, scoreMode, ordinalMap, minChildren, maxChildren);
}
// Even though we only cache rewritten queries it is good to let all queries implement hashCode() and equals():
// We can't check for actually equality here, since we need to IndexReader for this, but
// that isn't available on all cases during query parse time, so instead rely on identity:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
LateParsingQuery that = (LateParsingQuery) o;
return identity.equals(that.identity);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + identity.hashCode();
return result;
}
@Override
public String toString(String s) {
return "LateParsingQuery {parentType=" + parentType + "}";
}
} }
} }

View File

@ -18,83 +18,234 @@
*/ */
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.join.ScoreMode;
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.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.support.QueryInnerHitBuilder; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
import org.elasticsearch.index.query.support.QueryInnerHits;
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/** /**
* Builder for the 'has_parent' query. * Builder for the 'has_parent' query.
*/ */
public class HasParentQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<HasParentQueryBuilder> { public class HasParentQueryBuilder extends AbstractQueryBuilder<HasParentQueryBuilder> {
private final QueryBuilder queryBuilder; public static final String NAME = "has_parent";
private final String parentType; public static final boolean DEFAULT_SCORE = false;
private String scoreMode; private final QueryBuilder query;
private float boost = 1.0f; private final String type;
private String queryName; private boolean score = DEFAULT_SCORE;
private QueryInnerHitBuilder innerHit = null; private QueryInnerHits innerHit;
/** /**
* @param parentType The parent type * @param type The parent type
* @param parentQuery The query that will be matched with parent documents * @param query The query that will be matched with parent documents
*/ */
public HasParentQueryBuilder(String parentType, QueryBuilder parentQuery) { public HasParentQueryBuilder(String type, QueryBuilder query) {
this.parentType = parentType; if (type == null) {
this.queryBuilder = parentQuery; throw new IllegalArgumentException("[" + NAME + "] requires 'parent_type' field");
}
if (query == null) {
throw new IllegalArgumentException("[" + NAME + "] requires 'query' field");
}
this.type = type;
this.query = query;
} }
@Override public HasParentQueryBuilder(String type, QueryBuilder query, boolean score, QueryInnerHits innerHits) {
public HasParentQueryBuilder boost(float boost) { this(type, query);
this.boost = boost; this.score = score;
return this; this.innerHit = innerHits;
} }
/** /**
* Defines how the parent score is mapped into the child documents. * Defines if the parent score is mapped into the child documents.
*/ */
public HasParentQueryBuilder scoreMode(String scoreMode) { public HasParentQueryBuilder score(boolean score) {
this.scoreMode = scoreMode; this.score = score;
return this;
}
/**
* Sets the query name for the filter that can be used when searching for matched_filters per hit.
*/
public HasParentQueryBuilder queryName(String queryName) {
this.queryName = queryName;
return this; return this;
} }
/** /**
* Sets inner hit definition in the scope of this query and reusing the defined type and query. * Sets inner hit definition in the scope of this query and reusing the defined type and query.
*/ */
public HasParentQueryBuilder innerHit(QueryInnerHitBuilder innerHit) { public HasParentQueryBuilder innerHit(QueryInnerHits innerHit) {
this.innerHit = innerHit; this.innerHit = innerHit;
return this; return this;
} }
/**
* Returns the query to execute.
*/
public QueryBuilder query() {
return query;
}
/**
* Returns <code>true</code> if the parent score is mapped into the child documents
*/
public boolean score() {
return score;
}
/**
* Returns the parents type name
*/
public String type() {
return type;
}
/**
* Returns inner hit definition in the scope of this query and reusing the defined type and query.
*/
public QueryInnerHits innerHit() {
return innerHit;
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
Query innerQuery = query.toQuery(context);
if (innerQuery == null) {
return null;
}
innerQuery.setBoost(boost);
DocumentMapper parentDocMapper = context.mapperService().documentMapper(type);
if (parentDocMapper == null) {
throw new QueryShardException(context, "[has_parent] query configured 'parent_type' [" + type
+ "] is not a valid type");
}
if (innerHit != null) {
try (XContentParser parser = innerHit.getXcontentParser()) {
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.START_OBJECT) {
throw new IllegalStateException("start object expected but was: [" + token + "]");
}
InnerHitsSubSearchContext innerHits = context.indexQueryParserService().getInnerHitsQueryParserHelper().parse(parser);
if (innerHits != null) {
ParsedQuery parsedQuery = new ParsedQuery(innerQuery, context.copyNamedQueries());
InnerHitsContext.ParentChildInnerHits parentChildInnerHits = new InnerHitsContext.ParentChildInnerHits(innerHits.getSubSearchContext(), parsedQuery, null, context.mapperService(), parentDocMapper);
String name = innerHits.getName() != null ? innerHits.getName() : type;
context.addInnerHits(name, parentChildInnerHits);
}
}
}
Set<String> parentTypes = new HashSet<>(5);
parentTypes.add(parentDocMapper.type());
ParentChildIndexFieldData parentChildIndexFieldData = null;
for (DocumentMapper documentMapper : context.mapperService().docMappers(false)) {
ParentFieldMapper parentFieldMapper = documentMapper.parentFieldMapper();
if (parentFieldMapper.active()) {
DocumentMapper parentTypeDocumentMapper = context.mapperService().documentMapper(parentFieldMapper.type());
parentChildIndexFieldData = context.getForField(parentFieldMapper.fieldType());
if (parentTypeDocumentMapper == null) {
// Only add this, if this parentFieldMapper (also a parent) isn't a child of another parent.
parentTypes.add(parentFieldMapper.type());
}
}
}
if (parentChildIndexFieldData == null) {
throw new QueryShardException(context, "[has_parent] no _parent field configured");
}
Query parentTypeQuery = null;
if (parentTypes.size() == 1) {
DocumentMapper documentMapper = context.mapperService().documentMapper(parentTypes.iterator().next());
if (documentMapper != null) {
parentTypeQuery = documentMapper.typeFilter();
}
} else {
BooleanQuery.Builder parentsFilter = new BooleanQuery.Builder();
for (String parentTypeStr : parentTypes) {
DocumentMapper documentMapper = context.mapperService().documentMapper(parentTypeStr);
if (documentMapper != null) {
parentsFilter.add(documentMapper.typeFilter(), BooleanClause.Occur.SHOULD);
}
}
parentTypeQuery = parentsFilter.build();
}
if (parentTypeQuery == null) {
return null;
}
// wrap the query with type query
innerQuery = Queries.filtered(innerQuery, parentDocMapper.typeFilter());
Query childrenFilter = Queries.not(parentTypeQuery);
return new HasChildQueryBuilder.LateParsingQuery(childrenFilter, innerQuery, HasChildQueryBuilder.DEFAULT_MIN_CHILDREN, HasChildQueryBuilder.DEFAULT_MAX_CHILDREN, type, score ? ScoreMode.Max : ScoreMode.None, parentChildIndexFieldData);
}
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(HasParentQueryParser.NAME); builder.startObject(NAME);
builder.field("query"); builder.field("query");
queryBuilder.toXContent(builder, params); query.toXContent(builder, params);
builder.field("parent_type", parentType); builder.field("parent_type", type);
if (scoreMode != null) { builder.field("score", score);
builder.field("score_mode", scoreMode); printBoostAndQueryName(builder);
}
if (boost != 1.0f) {
builder.field("boost", boost);
}
if (queryName != null) {
builder.field("_name", queryName);
}
if (innerHit != null) { if (innerHit != null) {
builder.startObject("inner_hits"); innerHit.toXContent(builder, params);
builder.value(innerHit);
builder.endObject();
} }
builder.endObject(); builder.endObject();
} }
}
@Override
public String getWriteableName() {
return NAME;
}
protected HasParentQueryBuilder(StreamInput in) throws IOException {
type = in.readString();
score = in.readBoolean();
query = in.readQuery();
if (in.readBoolean()) {
innerHit = new QueryInnerHits(in);
}
}
@Override
protected HasParentQueryBuilder doReadFrom(StreamInput in) throws IOException {
return new HasParentQueryBuilder(in);
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeString(type);
out.writeBoolean(score);
out.writeQuery(query);
if (innerHit != null) {
out.writeBoolean(true);
innerHit.writeTo(out);
} else {
out.writeBoolean(false);
}
}
@Override
protected boolean doEquals(HasParentQueryBuilder that) {
return Objects.equals(query, that.query)
&& Objects.equals(type, that.type)
&& Objects.equals(score, that.score)
&& Objects.equals(innerHit, that.innerHit);
}
@Override
protected int doHashCode() {
return Objects.hash(query, type, score, innerHit);
}
}

View File

@ -18,178 +18,79 @@
*/ */
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.*;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData; import org.elasticsearch.index.query.support.QueryInnerHits;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.internal.ParentFieldMapper;
import org.elasticsearch.index.query.support.InnerHitsQueryParserHelper;
import org.elasticsearch.index.query.support.XContentStructure;
import org.elasticsearch.search.fetch.innerhits.InnerHitsContext;
import org.elasticsearch.search.fetch.innerhits.InnerHitsSubSearchContext;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import static org.elasticsearch.index.query.HasChildQueryParser.joinUtilHelper; public class HasParentQueryParser implements QueryParser<HasParentQueryBuilder> {
public class HasParentQueryParser implements QueryParser { private static final HasParentQueryBuilder PROTOTYPE = new HasParentQueryBuilder("", EmptyQueryBuilder.PROTOTYPE);
public static final String NAME = "has_parent";
private static final ParseField QUERY_FIELD = new ParseField("query", "filter"); private static final ParseField QUERY_FIELD = new ParseField("query", "filter");
private static final ParseField SCORE_FIELD = new ParseField("score_mode").withAllDeprecated("score");
private final InnerHitsQueryParserHelper innerHitsQueryParserHelper; private static final ParseField TYPE_FIELD = new ParseField("parent_type", "type");
@Inject
public HasParentQueryParser(InnerHitsQueryParserHelper innerHitsQueryParserHelper) {
this.innerHitsQueryParserHelper = innerHitsQueryParserHelper;
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, Strings.toCamelCase(NAME)}; return new String[]{HasParentQueryBuilder.NAME, Strings.toCamelCase(HasParentQueryBuilder.NAME)};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public HasParentQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
boolean queryFound = false; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
float boost = 1.0f;
String parentType = null; String parentType = null;
boolean score = false; boolean score = HasParentQueryBuilder.DEFAULT_SCORE;
String queryName = null; String queryName = null;
InnerHitsSubSearchContext innerHits = null; QueryInnerHits innerHits = null;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
XContentStructure.InnerQuery iq = null; QueryBuilder iqb = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
// Usually, the query would be parsed here, but the child
// type may not have been extracted yet, so use the
// XContentStructure.<type> facade to parse if available,
// or delay parsing if not.
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) { if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
iq = new XContentStructure.InnerQuery(parseContext, parentType == null ? null : new String[] {parentType}); iqb = parseContext.parseInnerQueryBuilder();
queryFound = true;
} else if ("inner_hits".equals(currentFieldName)) { } else if ("inner_hits".equals(currentFieldName)) {
innerHits = innerHitsQueryParserHelper.parse(parseContext); innerHits = new QueryInnerHits(parser);
} else { } else {
throw new ParsingException(parseContext, "[has_parent] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[has_parent] query does not support [" + currentFieldName + "]");
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if ("type".equals(currentFieldName) || "parent_type".equals(currentFieldName) || "parentType".equals(currentFieldName)) { if (parseContext.parseFieldMatcher().match(currentFieldName, TYPE_FIELD)) {
parentType = parser.text(); parentType = parser.text();
} else if ("score_mode".equals(currentFieldName) || "scoreMode".equals(currentFieldName)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, SCORE_FIELD)) {
String scoreModeValue = parser.text(); String scoreModeValue = parser.text();
if ("score".equals(scoreModeValue)) { if ("score".equals(scoreModeValue)) {
score = true; score = true;
} else if ("none".equals(scoreModeValue)) { } else if ("none".equals(scoreModeValue)) {
score = false; score = false;
} else {
throw new ParsingException(parser.getTokenLocation(), "[has_parent] query does not support [" + scoreModeValue + "] as an option for score_mode");
} }
} else if ("score".equals(currentFieldName)) {
score = parser.booleanValue();
} else if ("boost".equals(currentFieldName)) { } else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue(); boost = parser.floatValue();
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[has_parent] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[has_parent] query does not support [" + currentFieldName + "]");
} }
} }
} }
if (!queryFound) { return new HasParentQueryBuilder(parentType, iqb, score, innerHits).queryName(queryName).boost(boost);
throw new ParsingException(parseContext, "[has_parent] query requires 'query' field");
}
if (parentType == null) {
throw new ParsingException(parseContext, "[has_parent] query requires 'parent_type' field");
}
Query innerQuery = iq.asQuery(parentType);
if (innerQuery == null) {
return null;
}
innerQuery.setBoost(boost);
Query query = createParentQuery(innerQuery, parentType, score, parseContext, innerHits);
if (query == null) {
return null;
}
query.setBoost(boost);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
return query;
} }
static Query createParentQuery(Query innerQuery, String parentType, boolean score, QueryParseContext parseContext, InnerHitsSubSearchContext innerHits) throws IOException { @Override
DocumentMapper parentDocMapper = parseContext.mapperService().documentMapper(parentType); public HasParentQueryBuilder getBuilderPrototype() {
if (parentDocMapper == null) { return PROTOTYPE;
throw new ParsingException(parseContext, "[has_parent] query configured 'parent_type' [" + parentType
+ "] is not a valid type");
}
if (innerHits != null) {
ParsedQuery parsedQuery = new ParsedQuery(innerQuery, parseContext.copyNamedQueries());
InnerHitsContext.ParentChildInnerHits parentChildInnerHits = new InnerHitsContext.ParentChildInnerHits(innerHits.getSubSearchContext(), parsedQuery, null, parseContext.mapperService(), parentDocMapper);
String name = innerHits.getName() != null ? innerHits.getName() : parentType;
parseContext.addInnerHits(name, parentChildInnerHits);
}
Set<String> parentTypes = new HashSet<>(5);
parentTypes.add(parentDocMapper.type());
ParentChildIndexFieldData parentChildIndexFieldData = null;
for (DocumentMapper documentMapper : parseContext.mapperService().docMappers(false)) {
ParentFieldMapper parentFieldMapper = documentMapper.parentFieldMapper();
if (parentFieldMapper.active()) {
DocumentMapper parentTypeDocumentMapper = parseContext.mapperService().documentMapper(parentFieldMapper.type());
parentChildIndexFieldData = parseContext.getForField(parentFieldMapper.fieldType());
if (parentTypeDocumentMapper == null) {
// Only add this, if this parentFieldMapper (also a parent) isn't a child of another parent.
parentTypes.add(parentFieldMapper.type());
}
}
}
if (parentChildIndexFieldData == null) {
throw new ParsingException(parseContext, "[has_parent] no _parent field configured");
}
Query parentTypeQuery = null;
if (parentTypes.size() == 1) {
DocumentMapper documentMapper = parseContext.mapperService().documentMapper(parentTypes.iterator().next());
if (documentMapper != null) {
parentTypeQuery = documentMapper.typeFilter();
}
} else {
BooleanQuery.Builder parentsFilter = new BooleanQuery.Builder();
for (String parentTypeStr : parentTypes) {
DocumentMapper documentMapper = parseContext.mapperService().documentMapper(parentTypeStr);
if (documentMapper != null) {
parentsFilter.add(documentMapper.typeFilter(), BooleanClause.Occur.SHOULD);
}
}
parentTypeQuery = parentsFilter.build();
}
if (parentTypeQuery == null) {
return null;
}
// wrap the query with type query
innerQuery = Queries.filtered(innerQuery, parentDocMapper.typeFilter());
Query childrenFilter = Queries.not(parentTypeQuery);
ScoreMode scoreMode = score ? ScoreMode.Max : ScoreMode.None;
return joinUtilHelper(parentType, parentChildIndexFieldData, childrenFilter, scoreMode, innerQuery, 0, Integer.MAX_VALUE);
} }
} }

View File

@ -19,44 +19,60 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.Nullable;
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.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/** /**
* A query that will return only documents matching specific ids (and a type). * A query that will return only documents matching specific ids (and a type).
*/ */
public class IdsQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<IdsQueryBuilder> { public class IdsQueryBuilder extends AbstractQueryBuilder<IdsQueryBuilder> {
private final List<String> types; public static final String NAME = "ids";
private List<String> values = new ArrayList<>(); private final Set<String> ids = new HashSet<>();
private float boost = -1; private final String[] types;
private String queryName; static final IdsQueryBuilder PROTOTYPE = new IdsQueryBuilder();
public IdsQueryBuilder(String... types) { /**
this.types = types == null ? null : Arrays.asList(types); * Creates a new IdsQueryBuilder by optionally providing the types of the documents to look for
*/
public IdsQueryBuilder(@Nullable String... types) {
this.types = types;
} }
/** /**
* Adds ids to the filter. * Returns the types used in this query
*/
public String[] types() {
return this.types;
}
/**
* Adds ids to the query.
*/ */
public IdsQueryBuilder addIds(String... ids) { public IdsQueryBuilder addIds(String... ids) {
values.addAll(Arrays.asList(ids)); Collections.addAll(this.ids, ids);
return this; return this;
} }
/** /**
* Adds ids to the filter. * Adds ids to the query.
*/ */
public IdsQueryBuilder addIds(Collection<String> ids) { public IdsQueryBuilder addIds(Collection<String> ids) {
values.addAll(ids); this.ids.addAll(ids);
return this; return this;
} }
@ -75,48 +91,78 @@ public class IdsQueryBuilder extends QueryBuilder implements BoostableQueryBuild
} }
/** /**
* Sets the boost for this query. Documents matching this query will (in addition to the normal * Returns the ids for the query.
* weightings) have their score multiplied by the boost provided.
*/ */
@Override public Set<String> ids() {
public IdsQueryBuilder boost(float boost) { return this.ids;
this.boost = boost;
return this;
}
/**
* Sets the query name for the filter that can be used when searching for matched_filters per hit.
*/
public IdsQueryBuilder queryName(String queryName) {
this.queryName = queryName;
return this;
} }
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(IdsQueryParser.NAME); builder.startObject(NAME);
if (types != null) { if (types != null) {
if (types.size() == 1) { if (types.length == 1) {
builder.field("type", types.get(0)); builder.field("type", types[0]);
} else { } else {
builder.startArray("types"); builder.array("types", types);
for (Object type : types) {
builder.value(type);
}
builder.endArray();
} }
} }
builder.startArray("values"); builder.startArray("values");
for (Object value : values) { for (String value : ids) {
builder.value(value); builder.value(value);
} }
builder.endArray(); builder.endArray();
if (boost != -1) { printBoostAndQueryName(builder);
builder.field("boost", boost);
}
if (queryName != null) {
builder.field("_name", queryName);
}
builder.endObject(); builder.endObject();
} }
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
Query query;
if (this.ids.isEmpty()) {
query = Queries.newMatchNoDocsQuery();
} else {
Collection<String> typesForQuery;
if (types == null || types.length == 0) {
typesForQuery = context.queryTypes();
} else if (types.length == 1 && MetaData.ALL.equals(types[0])) {
typesForQuery = context.mapperService().types();
} else {
typesForQuery = new HashSet<>();
Collections.addAll(typesForQuery, types);
}
query = new TermsQuery(UidFieldMapper.NAME, Uid.createUidsForTypesAndIds(typesForQuery, ids));
}
return query;
}
@Override
protected IdsQueryBuilder doReadFrom(StreamInput in) throws IOException {
IdsQueryBuilder idsQueryBuilder = new IdsQueryBuilder(in.readStringArray());
idsQueryBuilder.addIds(in.readStringArray());
return idsQueryBuilder;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeStringArray(types);
out.writeStringArray(ids.toArray(new String[ids.size()]));
}
@Override
protected int doHashCode() {
return Objects.hash(ids, Arrays.hashCode(types));
}
@Override
protected boolean doEquals(IdsQueryBuilder other) {
return Objects.equals(ids, other.ids) &&
Arrays.equals(types, other.types);
}
}

View File

@ -19,48 +19,36 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.queries.TermsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.util.iterable.Iterables;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.Uid;
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* * Parser for ids query
*/ */
public class IdsQueryParser implements QueryParser { public class IdsQueryParser implements QueryParser<IdsQueryBuilder> {
public static final String NAME = "ids";
@Inject
public IdsQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME}; return new String[]{IdsQueryBuilder.NAME};
} }
/**
* @return a QueryBuilder representation of the query passed in as XContent in the parse context
*/
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public IdsQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
List<String> ids = new ArrayList<>();
List<BytesRef> ids = new ArrayList<>(); List<String> types = new ArrayList<>();
Collection<String> types = null; float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String currentFieldName = null;
float boost = 1.0f;
String queryName = null; String queryName = null;
String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
boolean idsProvided = false; boolean idsProvided = false;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -72,27 +60,26 @@ public class IdsQueryParser implements QueryParser {
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if ((token == XContentParser.Token.VALUE_STRING) || if ((token == XContentParser.Token.VALUE_STRING) ||
(token == XContentParser.Token.VALUE_NUMBER)) { (token == XContentParser.Token.VALUE_NUMBER)) {
BytesRef value = parser.utf8BytesOrNull(); String id = parser.textOrNull();
if (value == null) { if (id == null) {
throw new ParsingException(parseContext, "No value specified for term filter"); throw new ParsingException(parser.getTokenLocation(), "No value specified for term filter");
} }
ids.add(value); ids.add(id);
} else { } else {
throw new ParsingException(parseContext, "Illegal value for id, expecting a string or number, got: " throw new ParsingException(parser.getTokenLocation(), "Illegal value for id, expecting a string or number, got: "
+ token); + token);
} }
} }
} else if ("types".equals(currentFieldName) || "type".equals(currentFieldName)) { } else if ("types".equals(currentFieldName) || "type".equals(currentFieldName)) {
types = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
String value = parser.textOrNull(); String value = parser.textOrNull();
if (value == null) { if (value == null) {
throw new ParsingException(parseContext, "No type specified for term filter"); throw new ParsingException(parser.getTokenLocation(), "No type specified for term filter");
} }
types.add(value); types.add(value);
} }
} else { } else {
throw new ParsingException(parseContext, "[ids] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[ids] query does not support [" + currentFieldName + "]");
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if ("type".equals(currentFieldName) || "_type".equals(currentFieldName)) { if ("type".equals(currentFieldName) || "_type".equals(currentFieldName)) {
@ -102,30 +89,22 @@ public class IdsQueryParser implements QueryParser {
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else { } else {
throw new ParsingException(parseContext, "[ids] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[ids] query does not support [" + currentFieldName + "]");
} }
} }
} }
if (!idsProvided) { if (!idsProvided) {
throw new ParsingException(parseContext, "[ids] query, no ids values provided"); throw new ParsingException(parser.getTokenLocation(), "[ids] query, no ids values provided");
} }
if (ids.isEmpty()) { IdsQueryBuilder query = new IdsQueryBuilder(types.toArray(new String[types.size()]));
return Queries.newMatchNoDocsQuery(); query.addIds(ids.toArray(new String[ids.size()]));
} query.boost(boost).queryName(queryName);
if (types == null || types.isEmpty()) {
types = parseContext.queryTypes();
} else if (types.size() == 1 && Iterables.getFirst(types, null).equals("_all")) {
types = parseContext.mapperService().types();
}
TermsQuery query = new TermsQuery(UidFieldMapper.NAME, Uid.createUidsForTypesAndIds(types, ids));
query.setBoost(boost);
if (queryName != null) {
parseContext.addNamedQuery(queryName, query);
}
return query; return query;
} }
@Override
public IdsQueryBuilder getBuilderPrototype() {
return IdsQueryBuilder.PROTOTYPE;
}
} }

View File

@ -22,12 +22,17 @@ package org.elasticsearch.index.query;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.util.CloseableThreadLocal; import org.apache.lucene.util.CloseableThreadLocal;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries; import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
@ -40,6 +45,7 @@ import org.elasticsearch.index.cache.bitset.BitsetFilterCache;
import org.elasticsearch.index.fielddata.IndexFieldDataService; import org.elasticsearch.index.fielddata.IndexFieldDataService;
import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.internal.AllFieldMapper; import org.elasticsearch.index.mapper.internal.AllFieldMapper;
import org.elasticsearch.index.query.support.InnerHitsQueryParserHelper;
import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.settings.IndexSettings;
import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.index.similarity.SimilarityService;
import org.elasticsearch.indices.query.IndicesQueriesRegistry; import org.elasticsearch.indices.query.IndicesQueriesRegistry;
@ -51,13 +57,16 @@ public class IndexQueryParserService extends AbstractIndexComponent {
public static final String DEFAULT_FIELD = "index.query.default_field"; 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_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 PARSE_STRICT = "index.query.parse.strict";
public static final String ALLOW_UNMAPPED = "index.query.parse.allow_unmapped_fields"; public static final String ALLOW_UNMAPPED = "index.query.parse.allow_unmapped_fields";
private final InnerHitsQueryParserHelper innerHitsQueryParserHelper;
private CloseableThreadLocal<QueryParseContext> cache = new CloseableThreadLocal<QueryParseContext>() { private CloseableThreadLocal<QueryShardContext> cache = new CloseableThreadLocal<QueryShardContext>() {
@Override @Override
protected QueryParseContext initialValue() { protected QueryShardContext initialValue() {
return new QueryParseContext(index, IndexQueryParserService.this); return new QueryShardContext(index, IndexQueryParserService.this);
} }
}; };
@ -71,24 +80,33 @@ public class IndexQueryParserService extends AbstractIndexComponent {
final IndexCache indexCache; final IndexCache indexCache;
final IndexFieldDataService fieldDataService; protected IndexFieldDataService fieldDataService;
final ClusterService clusterService;
final IndexNameExpressionResolver indexNameExpressionResolver;
final BitsetFilterCache bitsetFilterCache; final BitsetFilterCache bitsetFilterCache;
private final IndicesQueriesRegistry indicesQueriesRegistry; private final IndicesQueriesRegistry indicesQueriesRegistry;
private String defaultField; private final String defaultField;
private boolean queryStringLenient; private final boolean queryStringLenient;
private final boolean queryStringAnalyzeWildcard;
private final boolean queryStringAllowLeadingWildcard;
private final ParseFieldMatcher parseFieldMatcher; private final ParseFieldMatcher parseFieldMatcher;
private final boolean defaultAllowUnmappedFields; private final boolean defaultAllowUnmappedFields;
private final Client client;
@Inject @Inject
public IndexQueryParserService(Index index, @IndexSettings Settings indexSettings, public IndexQueryParserService(Index index, @IndexSettings Settings indexSettings, Settings settings,
IndicesQueriesRegistry indicesQueriesRegistry, IndicesQueriesRegistry indicesQueriesRegistry,
ScriptService scriptService, AnalysisService analysisService, ScriptService scriptService, AnalysisService analysisService,
MapperService mapperService, IndexCache indexCache, IndexFieldDataService fieldDataService, MapperService mapperService, IndexCache indexCache, IndexFieldDataService fieldDataService,
BitsetFilterCache bitsetFilterCache, BitsetFilterCache bitsetFilterCache,
@Nullable SimilarityService similarityService) { @Nullable SimilarityService similarityService, ClusterService clusterService,
IndexNameExpressionResolver indexNameExpressionResolver,
InnerHitsQueryParserHelper innerHitsQueryParserHelper, Client client) {
super(index, indexSettings); super(index, indexSettings);
this.scriptService = scriptService; this.scriptService = scriptService;
this.analysisService = analysisService; this.analysisService = analysisService;
@ -97,12 +115,18 @@ public class IndexQueryParserService extends AbstractIndexComponent {
this.indexCache = indexCache; this.indexCache = indexCache;
this.fieldDataService = fieldDataService; this.fieldDataService = fieldDataService;
this.bitsetFilterCache = bitsetFilterCache; this.bitsetFilterCache = bitsetFilterCache;
this.clusterService = clusterService;
this.indexNameExpressionResolver = indexNameExpressionResolver;
this.defaultField = indexSettings.get(DEFAULT_FIELD, AllFieldMapper.NAME); this.defaultField = indexSettings.get(DEFAULT_FIELD, AllFieldMapper.NAME);
this.queryStringLenient = indexSettings.getAsBoolean(QUERY_STRING_LENIENT, false); 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.parseFieldMatcher = new ParseFieldMatcher(indexSettings);
this.defaultAllowUnmappedFields = indexSettings.getAsBoolean(ALLOW_UNMAPPED, true); this.defaultAllowUnmappedFields = indexSettings.getAsBoolean(ALLOW_UNMAPPED, true);
this.indicesQueriesRegistry = indicesQueriesRegistry; this.indicesQueriesRegistry = indicesQueriesRegistry;
this.innerHitsQueryParserHelper = innerHitsQueryParserHelper;
this.client = client;
} }
public void close() { public void close() {
@ -113,56 +137,24 @@ public class IndexQueryParserService extends AbstractIndexComponent {
return this.defaultField; return this.defaultField;
} }
public boolean queryStringAnalyzeWildcard() {
return this.queryStringAnalyzeWildcard;
}
public boolean queryStringAllowLeadingWildcard() {
return this.queryStringAllowLeadingWildcard;
}
public boolean queryStringLenient() { public boolean queryStringLenient() {
return this.queryStringLenient; return this.queryStringLenient;
} }
public QueryParser queryParser(String name) { IndicesQueriesRegistry indicesQueriesRegistry() {
return indicesQueriesRegistry.queryParsers().get(name); return indicesQueriesRegistry;
}
public ParsedQuery parse(QueryBuilder queryBuilder) {
XContentParser parser = null;
try {
BytesReference bytes = queryBuilder.buildAsBytes();
parser = XContentFactory.xContent(bytes).createParser(bytes);
return parse(cache.get(), parser);
} catch (ParsingException e) {
throw e;
} catch (Exception e) {
throw new ParsingException(getParseContext(), "Failed to parse", e);
} finally {
if (parser != null) {
parser.close();
}
}
}
public ParsedQuery parse(byte[] source) {
return parse(source, 0, source.length);
}
public ParsedQuery parse(byte[] source, int offset, int length) {
XContentParser parser = null;
try {
parser = XContentFactory.xContent(source, offset, length).createParser(source, offset, length);
return parse(cache.get(), parser);
} catch (ParsingException e) {
throw e;
} catch (Exception e) {
throw new ParsingException(getParseContext(), "Failed to parse", e);
} finally {
if (parser != null) {
parser.close();
}
}
} }
public ParsedQuery parse(BytesReference source) { public ParsedQuery parse(BytesReference source) {
return parse(cache.get(), source); QueryShardContext context = cache.get();
}
public ParsedQuery parse(QueryParseContext context, BytesReference source) {
XContentParser parser = null; XContentParser parser = null;
try { try {
parser = XContentFactory.xContent(source).createParser(source); parser = XContentFactory.xContent(source).createParser(source);
@ -170,23 +162,7 @@ public class IndexQueryParserService extends AbstractIndexComponent {
} catch (ParsingException e) { } catch (ParsingException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
throw new ParsingException(context, "Failed to parse", e); throw new ParsingException(parser == null ? null : parser.getTokenLocation(), "Failed to parse", e);
} finally {
if (parser != null) {
parser.close();
}
}
}
public ParsedQuery parse(String source) throws ParsingException {
XContentParser parser = null;
try {
parser = XContentFactory.xContent(source).createParser(source);
return innerParse(cache.get(), parser);
} catch (ParsingException e) {
throw e;
} catch (Exception e) {
throw new ParsingException(getParseContext(), "Failed to parse [" + source + "]", e);
} finally { } finally {
if (parser != null) { if (parser != null) {
parser.close(); parser.close();
@ -195,14 +171,10 @@ public class IndexQueryParserService extends AbstractIndexComponent {
} }
public ParsedQuery parse(XContentParser parser) { public ParsedQuery parse(XContentParser parser) {
return parse(cache.get(), parser);
}
public ParsedQuery parse(QueryParseContext context, XContentParser parser) {
try { try {
return innerParse(context, parser); return innerParse(cache.get(), parser);
} catch (IOException e) { } catch(IOException e) {
throw new ParsingException(context, "Failed to parse", e); throw new ParsingException(parser.getTokenLocation(), "Failed to parse", e);
} }
} }
@ -211,10 +183,11 @@ public class IndexQueryParserService extends AbstractIndexComponent {
*/ */
@Nullable @Nullable
public ParsedQuery parseInnerFilter(XContentParser parser) throws IOException { public ParsedQuery parseInnerFilter(XContentParser parser) throws IOException {
QueryParseContext context = cache.get(); QueryShardContext context = cache.get();
context.reset(parser); context.reset(parser);
try { try {
Query filter = context.parseInnerFilter(); context.parseFieldMatcher(parseFieldMatcher);
Query filter = context.parseContext().parseInnerQueryBuilder().toFilter(context);
if (filter == null) { if (filter == null) {
return null; return null;
} }
@ -225,27 +198,15 @@ public class IndexQueryParserService extends AbstractIndexComponent {
} }
@Nullable @Nullable
public Query parseInnerQuery(XContentParser parser) throws IOException { public Query parseInnerQuery(QueryShardContext context) throws IOException {
QueryParseContext context = cache.get(); Query query = context.parseContext().parseInnerQueryBuilder().toQuery(context);
context.reset(parser);
try {
return context.parseInnerQuery();
} finally {
context.reset(null);
}
}
@Nullable
public Query parseInnerQuery(QueryParseContext parseContext) throws IOException {
parseContext.parseFieldMatcher(parseFieldMatcher);
Query query = parseContext.parseInnerQuery();
if (query == null) { if (query == null) {
query = Queries.newMatchNoDocsQuery(); query = Queries.newMatchNoDocsQuery();
} }
return query; return query;
} }
public QueryParseContext getParseContext() { public QueryShardContext getShardContext() {
return cache.get(); return cache.get();
} }
@ -264,9 +225,10 @@ public class IndexQueryParserService extends AbstractIndexComponent {
* Selectively parses a query from a top level query or query_binary json field from the specified source. * Selectively parses a query from a top level query or query_binary json field from the specified source.
*/ */
public ParsedQuery parseQuery(BytesReference source) { public ParsedQuery parseQuery(BytesReference source) {
XContentParser parser = null;
try { try {
parser = XContentHelper.createParser(source);
ParsedQuery parsedQuery = null; ParsedQuery parsedQuery = null;
XContentParser parser = XContentHelper.createParser(source);
for (XContentParser.Token token = parser.nextToken(); token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) { for (XContentParser.Token token = parser.nextToken(); token != XContentParser.Token.END_OBJECT; token = parser.nextToken()) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
String fieldName = parser.currentName(); String fieldName = parser.currentName();
@ -277,37 +239,54 @@ public class IndexQueryParserService extends AbstractIndexComponent {
XContentParser qSourceParser = XContentFactory.xContent(querySource).createParser(querySource); XContentParser qSourceParser = XContentFactory.xContent(querySource).createParser(querySource);
parsedQuery = parse(qSourceParser); parsedQuery = parse(qSourceParser);
} else { } else {
throw new ParsingException(getParseContext(), "request does not support [" + fieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "request does not support [" + fieldName + "]");
} }
} }
} }
if (parsedQuery != null) { if (parsedQuery == null) {
return parsedQuery; throw new ParsingException(parser.getTokenLocation(), "Required query is missing");
} }
} catch (ParsingException e) { return parsedQuery;
} catch (ParsingException | QueryShardException e) {
throw e; throw e;
} catch (Throwable e) { } catch (Throwable e) {
throw new ParsingException(getParseContext(), "Failed to parse", e); throw new ParsingException(parser == null ? null : parser.getTokenLocation(), "Failed to parse", e);
} }
throw new ParsingException(getParseContext(), "Required query is missing");
} }
private ParsedQuery innerParse(QueryParseContext parseContext, XContentParser parser) throws IOException, ParsingException { private ParsedQuery innerParse(QueryShardContext context, XContentParser parser) throws IOException, QueryShardException {
parseContext.reset(parser); context.reset(parser);
try { try {
parseContext.parseFieldMatcher(parseFieldMatcher); context.parseFieldMatcher(parseFieldMatcher);
Query query = parseContext.parseInnerQuery(); Query query = context.parseContext().parseInnerQueryBuilder().toQuery(context);
if (query == null) { if (query == null) {
query = Queries.newMatchNoDocsQuery(); query = Queries.newMatchNoDocsQuery();
} }
return new ParsedQuery(query, parseContext.copyNamedQueries()); return new ParsedQuery(query, context.copyNamedQueries());
} finally { } finally {
parseContext.reset(null); context.reset(null);
} }
} }
public ParseFieldMatcher parseFieldMatcher() { public ParseFieldMatcher parseFieldMatcher() {
return parseFieldMatcher; return parseFieldMatcher;
} }
public boolean matchesIndices(String... indices) {
final String[] concreteIndices = indexNameExpressionResolver.concreteIndices(clusterService.state(), IndicesOptions.lenientExpandOpen(), indices);
for (String index : concreteIndices) {
if (Regex.simpleMatch(index, this.index.name())) {
return true;
}
}
return false;
}
public InnerHitsQueryParserHelper getInnerHitsQueryParserHelper() {
return innerHitsQueryParserHelper;
}
public Client getClient() {
return client;
}
} }

View File

@ -19,69 +19,133 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
/** /**
* A query that will execute the wrapped query only for the specified indices, and "match_all" when * A query that will execute the wrapped query only for the specified indices, and "match_all" when
* it does not match those indices (by default). * it does not match those indices (by default).
*/ */
public class IndicesQueryBuilder extends QueryBuilder { public class IndicesQueryBuilder extends AbstractQueryBuilder<IndicesQueryBuilder> {
private final QueryBuilder queryBuilder; public static final String NAME = "indices";
private final QueryBuilder innerQuery;
private final String[] indices; private final String[] indices;
private String sNoMatchQuery; private QueryBuilder noMatchQuery = defaultNoMatchQuery();
private QueryBuilder noMatchQuery;
private String queryName; static final IndicesQueryBuilder PROTOTYPE = new IndicesQueryBuilder(EmptyQueryBuilder.PROTOTYPE, "index");
public IndicesQueryBuilder(QueryBuilder queryBuilder, String... indices) { public IndicesQueryBuilder(QueryBuilder innerQuery, String... indices) {
this.queryBuilder = queryBuilder; if (innerQuery == null) {
throw new IllegalArgumentException("inner query cannot be null");
}
if (indices == null || indices.length == 0) {
throw new IllegalArgumentException("list of indices cannot be null or empty");
}
this.innerQuery = Objects.requireNonNull(innerQuery);
this.indices = indices; this.indices = indices;
} }
/** public QueryBuilder innerQuery() {
* Sets the no match query, can either be <tt>all</tt> or <tt>none</tt>. return this.innerQuery;
*/ }
public IndicesQueryBuilder noMatchQuery(String type) {
this.sNoMatchQuery = type; public String[] indices() {
return this; return this.indices;
} }
/** /**
* Sets the query to use when it executes on an index that does not match the indices provided. * Sets the query to use when it executes on an index that does not match the indices provided.
*/ */
public IndicesQueryBuilder noMatchQuery(QueryBuilder noMatchQuery) { public IndicesQueryBuilder noMatchQuery(QueryBuilder noMatchQuery) {
if (noMatchQuery == null) {
throw new IllegalArgumentException("noMatch query cannot be null");
}
this.noMatchQuery = noMatchQuery; this.noMatchQuery = noMatchQuery;
return this; return this;
} }
/** /**
* Sets the query name for the filter that can be used when searching for matched_filters per hit. * Sets the no match query, can either be <tt>all</tt> or <tt>none</tt>.
*/ */
public IndicesQueryBuilder queryName(String queryName) { public IndicesQueryBuilder noMatchQuery(String type) {
this.queryName = queryName; this.noMatchQuery = IndicesQueryParser.parseNoMatchQuery(type);
return this; return this;
} }
public QueryBuilder noMatchQuery() {
return this.noMatchQuery;
}
static QueryBuilder defaultNoMatchQuery() {
return QueryBuilders.matchAllQuery();
}
@Override @Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(IndicesQueryParser.NAME); builder.startObject(NAME);
builder.field("indices", indices); builder.field("indices", indices);
builder.field("query"); builder.field("query");
queryBuilder.toXContent(builder, params); innerQuery.toXContent(builder, params);
if (noMatchQuery != null) { builder.field("no_match_query");
builder.field("no_match_query"); noMatchQuery.toXContent(builder, params);
noMatchQuery.toXContent(builder, params); printBoostAndQueryName(builder);
} else if (sNoMatchQuery != null) {
builder.field("no_match_query", sNoMatchQuery);
}
if (queryName != null) {
builder.field("_name", queryName);
}
builder.endObject(); builder.endObject();
} }
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
if (context.matchesIndices(indices)) {
return innerQuery.toQuery(context);
}
return noMatchQuery.toQuery(context);
}
@Override
protected void setFinalBoost(Query query) {
if (boost != DEFAULT_BOOST) {
//if both the wrapped query and the wrapper hold a boost, the main one coming from the wrapper wins
query.setBoost(boost);
}
}
@Override
protected IndicesQueryBuilder doReadFrom(StreamInput in) throws IOException {
IndicesQueryBuilder indicesQueryBuilder = new IndicesQueryBuilder(in.readQuery(), in.readStringArray());
indicesQueryBuilder.noMatchQuery = in.readQuery();
return indicesQueryBuilder;
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
out.writeQuery(innerQuery);
out.writeStringArray(indices);
out.writeQuery(noMatchQuery);
}
@Override
public int doHashCode() {
return Objects.hash(innerQuery, noMatchQuery, Arrays.hashCode(indices));
}
@Override
protected boolean doEquals(IndicesQueryBuilder other) {
return Objects.equals(innerQuery, other.innerQuery) &&
Arrays.equals(indices, other.indices) && // otherwise we are comparing pointers
Objects.equals(noMatchQuery, other.noMatchQuery);
}
}

View File

@ -19,147 +19,107 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.support.XContentStructure;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
/** /**
* Parser for {@link IndicesQueryBuilder}.
*/ */
public class IndicesQueryParser implements QueryParser { public class IndicesQueryParser implements QueryParser {
public static final String NAME = "indices";
private static final ParseField QUERY_FIELD = new ParseField("query", "filter"); private static final ParseField QUERY_FIELD = new ParseField("query", "filter");
private static final ParseField NO_MATCH_QUERY = new ParseField("no_match_query", "no_match_filter"); private static final ParseField NO_MATCH_QUERY = new ParseField("no_match_query", "no_match_filter");
@Nullable
private final ClusterService clusterService;
private final IndexNameExpressionResolver indexNameExpressionResolver;
@Inject
public IndicesQueryParser(@Nullable ClusterService clusterService, IndexNameExpressionResolver indexNameExpressionResolver) {
this.clusterService = clusterService;
this.indexNameExpressionResolver = indexNameExpressionResolver;
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME}; return new String[]{IndicesQueryBuilder.NAME};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public QueryBuilder fromXContent(QueryParseContext parseContext) throws IOException, ParsingException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
Query noMatchQuery = null; QueryBuilder innerQuery = null;
boolean queryFound = false; Collection<String> indices = new ArrayList<>();
boolean indicesFound = false; QueryBuilder noMatchQuery = IndicesQueryBuilder.defaultNoMatchQuery();
boolean currentIndexMatchesIndices = false;
String queryName = null; String queryName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
XContentStructure.InnerQuery innerQuery = null;
XContentStructure.InnerQuery innerNoMatchQuery = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) { } else if (token == XContentParser.Token.START_OBJECT) {
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) { if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
innerQuery = new XContentStructure.InnerQuery(parseContext, (String[])null); innerQuery = parseContext.parseInnerQueryBuilder();
queryFound = true;
} else if (parseContext.parseFieldMatcher().match(currentFieldName, NO_MATCH_QUERY)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, NO_MATCH_QUERY)) {
innerNoMatchQuery = new XContentStructure.InnerQuery(parseContext, (String[])null); noMatchQuery = parseContext.parseInnerQueryBuilder();
} else { } else {
throw new ParsingException(parseContext, "[indices] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[indices] query does not support [" + currentFieldName + "]");
} }
} else if (token == XContentParser.Token.START_ARRAY) { } else if (token == XContentParser.Token.START_ARRAY) {
if ("indices".equals(currentFieldName)) { if ("indices".equals(currentFieldName)) {
if (indicesFound) { if (indices.isEmpty() == false) {
throw new ParsingException(parseContext, "[indices] indices or index already specified"); throw new ParsingException(parser.getTokenLocation(), "[indices] indices or index already specified");
} }
indicesFound = true;
Collection<String> indices = new ArrayList<>();
while (parser.nextToken() != XContentParser.Token.END_ARRAY) { while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
String value = parser.textOrNull(); String value = parser.textOrNull();
if (value == null) { if (value == null) {
throw new ParsingException(parseContext, "[indices] no value specified for 'indices' entry"); throw new ParsingException(parser.getTokenLocation(), "[indices] no value specified for 'indices' entry");
} }
indices.add(value); indices.add(value);
} }
currentIndexMatchesIndices = matchesIndices(parseContext.index().name(), indices.toArray(new String[indices.size()]));
} else { } else {
throw new ParsingException(parseContext, "[indices] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[indices] query does not support [" + currentFieldName + "]");
} }
} else if (token.isValue()) { } else if (token.isValue()) {
if ("index".equals(currentFieldName)) { if ("index".equals(currentFieldName)) {
if (indicesFound) { if (indices.isEmpty() == false) {
throw new ParsingException(parseContext, "[indices] indices or index already specified"); throw new ParsingException(parser.getTokenLocation(), "[indices] indices or index already specified");
} }
indicesFound = true; indices.add(parser.text());
currentIndexMatchesIndices = matchesIndices(parseContext.index().name(), parser.text());
} else if (parseContext.parseFieldMatcher().match(currentFieldName, NO_MATCH_QUERY)) { } else if (parseContext.parseFieldMatcher().match(currentFieldName, NO_MATCH_QUERY)) {
String type = parser.text(); noMatchQuery = parseNoMatchQuery(parser.text());
if ("all".equals(type)) {
noMatchQuery = Queries.newMatchAllQuery();
} else if ("none".equals(type)) {
noMatchQuery = Queries.newMatchNoDocsQuery();
}
} else if ("_name".equals(currentFieldName)) { } else if ("_name".equals(currentFieldName)) {
queryName = parser.text(); queryName = parser.text();
} else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue();
} else { } else {
throw new ParsingException(parseContext, "[indices] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[indices] query does not support [" + currentFieldName + "]");
} }
} }
} }
if (!queryFound) {
throw new ParsingException(parseContext, "[indices] requires 'query' element");
}
if (!indicesFound) {
throw new ParsingException(parseContext, "[indices] requires 'indices' or 'index' element");
}
Query chosenQuery; if (innerQuery == null) {
if (currentIndexMatchesIndices) { throw new ParsingException(parser.getTokenLocation(), "[indices] requires 'query' element");
chosenQuery = innerQuery.asQuery();
} else {
// If noMatchQuery is set, it means "no_match_query" was "all" or "none"
if (noMatchQuery != null) {
chosenQuery = noMatchQuery;
} else {
// There might be no "no_match_query" set, so default to the match_all if not set
if (innerNoMatchQuery == null) {
chosenQuery = Queries.newMatchAllQuery();
} else {
chosenQuery = innerNoMatchQuery.asQuery();
}
}
} }
if (queryName != null) { if (indices.isEmpty()) {
parseContext.addNamedQuery(queryName, chosenQuery); throw new ParsingException(parser.getTokenLocation(), "[indices] requires 'indices' or 'index' element");
} }
return chosenQuery; return new IndicesQueryBuilder(innerQuery, indices.toArray(new String[indices.size()]))
.noMatchQuery(noMatchQuery)
.boost(boost)
.queryName(queryName);
} }
protected boolean matchesIndices(String currentIndex, String... indices) { static QueryBuilder parseNoMatchQuery(String type) {
final String[] concreteIndices = indexNameExpressionResolver.concreteIndices(clusterService.state(), IndicesOptions.lenientExpandOpen(), indices); if ("all".equals(type)) {
for (String index : concreteIndices) { return QueryBuilders.matchAllQuery();
if (Regex.simpleMatch(index, currentIndex)) { } else if ("none".equals(type)) {
return true; return new MatchNoneQueryBuilder();
}
} }
return false; throw new IllegalArgumentException("query type can only be [all] or [none] but not " + "[" + type + "]");
}
@Override
public IndicesQueryBuilder getBuilderPrototype() {
return IndicesQueryBuilder.PROTOTYPE;
} }
} }

View File

@ -19,6 +19,10 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
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.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException; import java.io.IOException;
@ -26,26 +30,46 @@ import java.io.IOException;
/** /**
* A query that matches on all documents. * A query that matches on all documents.
*/ */
public class MatchAllQueryBuilder extends QueryBuilder implements BoostableQueryBuilder<MatchAllQueryBuilder> { public class MatchAllQueryBuilder extends AbstractQueryBuilder<MatchAllQueryBuilder> {
private float boost = -1; public static final String NAME = "match_all";
/** static final MatchAllQueryBuilder PROTOTYPE = new MatchAllQueryBuilder();
* Sets the boost for this query. Documents matching this query will (in addition to the normal
* weightings) have their score multiplied by the boost provided.
*/
@Override
public MatchAllQueryBuilder boost(float boost) {
this.boost = boost;
return this;
}
@Override @Override
public void doXContent(XContentBuilder builder, Params params) throws IOException { protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(MatchAllQueryParser.NAME); builder.startObject(NAME);
if (boost != -1) { printBoostAndQueryName(builder);
builder.field("boost", boost);
}
builder.endObject(); builder.endObject();
} }
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
return Queries.newMatchAllQuery();
}
@Override
protected boolean doEquals(MatchAllQueryBuilder other) {
return true;
}
@Override
protected int doHashCode() {
return 0;
}
@Override
protected MatchAllQueryBuilder doReadFrom(StreamInput in) throws IOException {
return new MatchAllQueryBuilder();
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
//nothing to write really
}
@Override
public String getWriteableName() {
return NAME;
}
}

View File

@ -19,58 +19,51 @@
package org.elasticsearch.index.query; package org.elasticsearch.index.query;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException; import java.io.IOException;
/** /**
* * Parser for match_all query
*/ */
public class MatchAllQueryParser implements QueryParser { public class MatchAllQueryParser implements QueryParser<MatchAllQueryBuilder> {
public static final String NAME = "match_all";
@Inject
public MatchAllQueryParser() {
}
@Override @Override
public String[] names() { public String[] names() {
return new String[]{NAME, Strings.toCamelCase(NAME)}; return new String[]{MatchAllQueryBuilder.NAME, Strings.toCamelCase(MatchAllQueryBuilder.NAME)};
} }
@Override @Override
public Query parse(QueryParseContext parseContext) throws IOException, ParsingException { public MatchAllQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser(); XContentParser parser = parseContext.parser();
float boost = 1.0f;
String currentFieldName = null; String currentFieldName = null;
XContentParser.Token token; XContentParser.Token token;
String queryName = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
while (((token = parser.nextToken()) != XContentParser.Token.END_OBJECT && token != XContentParser.Token.END_ARRAY)) { while (((token = parser.nextToken()) != XContentParser.Token.END_OBJECT && token != XContentParser.Token.END_ARRAY)) {
if (token == XContentParser.Token.FIELD_NAME) { if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
} else if (token.isValue()) { } else if (token.isValue()) {
if ("boost".equals(currentFieldName)) { if ("_name".equals(currentFieldName)) {
queryName = parser.text();
} else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue(); boost = parser.floatValue();
} else { } else {
throw new ParsingException(parseContext, "[match_all] query does not support [" + currentFieldName + "]"); throw new ParsingException(parser.getTokenLocation(), "[match_all] query does not support [" + currentFieldName + "]");
} }
} }
} }
MatchAllQueryBuilder queryBuilder = new MatchAllQueryBuilder();
if (boost == 1.0f) { queryBuilder.boost(boost);
return Queries.newMatchAllQuery(); queryBuilder.queryName(queryName);
} return queryBuilder;
MatchAllDocsQuery query = new MatchAllDocsQuery();
query.setBoost(boost);
return query;
} }
}
@Override
public MatchAllQueryBuilder getBuilderPrototype() {
return MatchAllQueryBuilder.PROTOTYPE;
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.search.Query;
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.xcontent.XContentBuilder;
import java.io.IOException;
/**
* A query that matches no document.
*/
public class MatchNoneQueryBuilder extends AbstractQueryBuilder<MatchNoneQueryBuilder> {
public static final String NAME = "match_none";
public static final MatchNoneQueryBuilder PROTOTYPE = new MatchNoneQueryBuilder();
@Override
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
builder.endObject();
}
@Override
protected Query doToQuery(QueryShardContext context) throws IOException {
return Queries.newMatchNoDocsQuery();
}
@Override
protected void setFinalBoost(Query query) {
//no-op this query doesn't support boost
}
@Override
protected boolean doEquals(MatchNoneQueryBuilder other) {
return true;
}
@Override
protected int doHashCode() {
return 0;
}
@Override
protected MatchNoneQueryBuilder doReadFrom(StreamInput in) throws IOException {
return new MatchNoneQueryBuilder();
}
@Override
protected void doWriteTo(StreamOutput out) throws IOException {
//nothing to write really
}
@Override
public String getWriteableName() {
return NAME;
}
}

Some files were not shown because too many files have changed in this diff Show More