Start to rework query registration

Changes QueryParser into a @FunctionalInterface and provides a way to
register queries using that. Cuts match and function_score queries over
to that registration method as a proof of concept.

Once all queries have been cut over we can remove their PROTOTYPES.
This commit is contained in:
Nik Everett 2016-03-30 17:57:58 -04:00
parent f355bb2a9b
commit 75a9899813
13 changed files with 452 additions and 562 deletions

View File

@ -23,13 +23,17 @@ import org.apache.lucene.queries.ExtendedCommonTermsQuery;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
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.unit.Fuzziness;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.support.QueryParsers;
import org.elasticsearch.index.search.MatchQuery;
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
import java.io.IOException;
import java.util.Locale;
@ -40,6 +44,21 @@ import java.util.Objects;
* can construct different queries based on the type provided.
*/
public class MatchQueryBuilder extends AbstractQueryBuilder<MatchQueryBuilder> {
public static final ParseField MATCH_PHRASE_FIELD = new ParseField("match_phrase", "text_phrase");
public static final ParseField MATCH_PHRASE_PREFIX_FIELD = new ParseField("match_phrase_prefix", "text_phrase_prefix");
public static final ParseField SLOP_FIELD = new ParseField("slop", "phrase_slop");
public static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query");
public static final ParseField CUTOFF_FREQUENCY_FIELD = new ParseField("cutoff_frequency");
public static final ParseField LENIENT_FIELD = new ParseField("lenient");
public static final ParseField FUZZY_TRANSPOSITIONS_FIELD = new ParseField("fuzzy_transpositions");
public static final ParseField FUZZY_REWRITE_FIELD = new ParseField("fuzzy_rewrite");
public static final ParseField MINIMUM_SHOULD_MATCH_FIELD = new ParseField("minimum_should_match");
public static final ParseField OPERATOR_FIELD = new ParseField("operator");
public static final ParseField MAX_EXPANSIONS_FIELD = new ParseField("max_expansions");
public static final ParseField PREFIX_LENGTH_FIELD = new ParseField("prefix_length");
public static final ParseField ANALYZER_FIELD = new ParseField("analyzer");
public static final ParseField TYPE_FIELD = new ParseField("type");
public static final ParseField QUERY_FIELD = new ParseField("query");
/** The default name for the match query */
public static final String NAME = "match";
@ -80,7 +99,7 @@ public class MatchQueryBuilder extends AbstractQueryBuilder<MatchQueryBuilder> {
private Float cutoffFrequency = null;
static final MatchQueryBuilder PROTOTYPE = new MatchQueryBuilder("","");
public static final MatchQueryBuilder PROTOTYPE = new MatchQueryBuilder("","");
/**
* Constructs a new match query.
@ -317,30 +336,30 @@ public class MatchQueryBuilder extends AbstractQueryBuilder<MatchQueryBuilder> {
builder.startObject(NAME);
builder.startObject(fieldName);
builder.field(MatchQueryParser.QUERY_FIELD.getPreferredName(), value);
builder.field(MatchQueryParser.TYPE_FIELD.getPreferredName(), type.toString().toLowerCase(Locale.ENGLISH));
builder.field(MatchQueryParser.OPERATOR_FIELD.getPreferredName(), operator.toString());
builder.field(QUERY_FIELD.getPreferredName(), value);
builder.field(TYPE_FIELD.getPreferredName(), type.toString().toLowerCase(Locale.ENGLISH));
builder.field(OPERATOR_FIELD.getPreferredName(), operator.toString());
if (analyzer != null) {
builder.field(MatchQueryParser.ANALYZER_FIELD.getPreferredName(), analyzer);
builder.field(ANALYZER_FIELD.getPreferredName(), analyzer);
}
builder.field(MatchQueryParser.SLOP_FIELD.getPreferredName(), slop);
builder.field(SLOP_FIELD.getPreferredName(), slop);
if (fuzziness != null) {
fuzziness.toXContent(builder, params);
}
builder.field(MatchQueryParser.PREFIX_LENGTH_FIELD.getPreferredName(), prefixLength);
builder.field(MatchQueryParser.MAX_EXPANSIONS_FIELD.getPreferredName(), maxExpansions);
builder.field(PREFIX_LENGTH_FIELD.getPreferredName(), prefixLength);
builder.field(MAX_EXPANSIONS_FIELD.getPreferredName(), maxExpansions);
if (minimumShouldMatch != null) {
builder.field(MatchQueryParser.MINIMUM_SHOULD_MATCH_FIELD.getPreferredName(), minimumShouldMatch);
builder.field(MINIMUM_SHOULD_MATCH_FIELD.getPreferredName(), minimumShouldMatch);
}
if (fuzzyRewrite != null) {
builder.field(MatchQueryParser.FUZZY_REWRITE_FIELD.getPreferredName(), fuzzyRewrite);
builder.field(FUZZY_REWRITE_FIELD.getPreferredName(), fuzzyRewrite);
}
// LUCENE 4 UPGRADE we need to document this & test this
builder.field(MatchQueryParser.FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), fuzzyTranspositions);
builder.field(MatchQueryParser.LENIENT_FIELD.getPreferredName(), lenient);
builder.field(MatchQueryParser.ZERO_TERMS_QUERY_FIELD.getPreferredName(), zeroTermsQuery.toString());
builder.field(FUZZY_TRANSPOSITIONS_FIELD.getPreferredName(), fuzzyTranspositions);
builder.field(LENIENT_FIELD.getPreferredName(), lenient);
builder.field(ZERO_TERMS_QUERY_FIELD.getPreferredName(), zeroTermsQuery.toString());
if (cutoffFrequency != null) {
builder.field(MatchQueryParser.CUTOFF_FREQUENCY_FIELD.getPreferredName(), cutoffFrequency);
builder.field(CUTOFF_FREQUENCY_FIELD.getPreferredName(), cutoffFrequency);
}
printBoostAndQueryName(builder);
builder.endObject();
@ -467,4 +486,141 @@ public class MatchQueryBuilder extends AbstractQueryBuilder<MatchQueryBuilder> {
public String getWriteableName() {
return NAME;
}
public static MatchQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
MatchQuery.Type type = MatchQuery.Type.BOOLEAN;
if (parseContext.parseFieldMatcher().match(parser.currentName(), MATCH_PHRASE_FIELD)) {
type = MatchQuery.Type.PHRASE;
} else if (parseContext.parseFieldMatcher().match(parser.currentName(), MATCH_PHRASE_PREFIX_FIELD)) {
type = MatchQuery.Type.PHRASE_PREFIX;
}
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.FIELD_NAME) {
throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] query malformed, no field");
}
String fieldName = parser.currentName();
Object value = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String minimumShouldMatch = null;
String analyzer = null;
Operator operator = MatchQueryBuilder.DEFAULT_OPERATOR;
int slop = MatchQuery.DEFAULT_PHRASE_SLOP;
Fuzziness fuzziness = null;
int prefixLength = FuzzyQuery.defaultPrefixLength;
int maxExpansion = FuzzyQuery.defaultMaxExpansions;
boolean fuzzyTranspositions = FuzzyQuery.defaultTranspositions;
String fuzzyRewrite = null;
boolean lenient = MatchQuery.DEFAULT_LENIENCY;
Float cutOffFrequency = null;
ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY;
String queryName = null;
token = parser.nextToken();
if (token == XContentParser.Token.START_OBJECT) {
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token.isValue()) {
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
value = parser.objectText();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, TYPE_FIELD)) {
String tStr = parser.text();
if ("boolean".equals(tStr)) {
type = MatchQuery.Type.BOOLEAN;
} else if ("phrase".equals(tStr)) {
type = MatchQuery.Type.PHRASE;
} else if ("phrase_prefix".equals(tStr) || ("phrasePrefix".equals(tStr))) {
type = MatchQuery.Type.PHRASE_PREFIX;
} else {
throw new ParsingException(parser.getTokenLocation(),
"[" + MatchQueryBuilder.NAME + "] query does not support type " + tStr);
}
} else if (parseContext.parseFieldMatcher().match(currentFieldName, ANALYZER_FIELD)) {
analyzer = parser.text();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
boost = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, SLOP_FIELD)) {
slop = parser.intValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, Fuzziness.FIELD)) {
fuzziness = Fuzziness.parse(parser);
} else if (parseContext.parseFieldMatcher().match(currentFieldName, PREFIX_LENGTH_FIELD)) {
prefixLength = parser.intValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, MAX_EXPANSIONS_FIELD)) {
maxExpansion = parser.intValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, OPERATOR_FIELD)) {
operator = Operator.fromString(parser.text());
} else if (parseContext.parseFieldMatcher().match(currentFieldName, MINIMUM_SHOULD_MATCH_FIELD)) {
minimumShouldMatch = parser.textOrNull();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZY_REWRITE_FIELD)) {
fuzzyRewrite = parser.textOrNull();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZY_TRANSPOSITIONS_FIELD)) {
fuzzyTranspositions = parser.booleanValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, LENIENT_FIELD)) {
lenient = parser.booleanValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, CUTOFF_FREQUENCY_FIELD)) {
cutOffFrequency = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, ZERO_TERMS_QUERY_FIELD)) {
String zeroTermsDocs = parser.text();
if ("none".equalsIgnoreCase(zeroTermsDocs)) {
zeroTermsQuery = MatchQuery.ZeroTermsQuery.NONE;
} else if ("all".equalsIgnoreCase(zeroTermsDocs)) {
zeroTermsQuery = MatchQuery.ZeroTermsQuery.ALL;
} else {
throw new ParsingException(parser.getTokenLocation(),
"Unsupported zero_terms_docs value [" + zeroTermsDocs + "]");
}
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
queryName = parser.text();
} else {
throw new ParsingException(parser.getTokenLocation(),
"[" + MatchQueryBuilder.NAME + "] query does not support [" + currentFieldName + "]");
}
} else {
throw new ParsingException(parser.getTokenLocation(),
"[" + MatchQueryBuilder.NAME + "] unknown token [" + token + "] after [" + currentFieldName + "]");
}
}
parser.nextToken();
} else {
value = parser.objectText();
// move to the next token
token = parser.nextToken();
if (token != XContentParser.Token.END_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "[match] 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) {
throw new ParsingException(parser.getTokenLocation(), "No text specified for text query");
}
MatchQueryBuilder matchQuery = new MatchQueryBuilder(fieldName, value);
matchQuery.operator(operator);
matchQuery.type(type);
matchQuery.analyzer(analyzer);
matchQuery.slop(slop);
matchQuery.minimumShouldMatch(minimumShouldMatch);
if (fuzziness != null) {
matchQuery.fuzziness(fuzziness);
}
matchQuery.fuzzyRewrite(fuzzyRewrite);
matchQuery.prefixLength(prefixLength);
matchQuery.fuzzyTranspositions(fuzzyTranspositions);
matchQuery.maxExpansions(maxExpansion);
matchQuery.lenient(lenient);
if (cutOffFrequency != null) {
matchQuery.cutoffFrequency(cutOffFrequency);
}
matchQuery.zeroTermsQuery(zeroTermsQuery);
matchQuery.queryName(queryName);
matchQuery.boost(boost);
return matchQuery;
}
}

View File

@ -1,197 +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;
import org.apache.lucene.search.FuzzyQuery;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.search.MatchQuery;
import org.elasticsearch.index.search.MatchQuery.ZeroTermsQuery;
import java.io.IOException;
/**
*
*/
public class MatchQueryParser implements QueryParser<MatchQueryBuilder> {
public static final ParseField MATCH_PHRASE_FIELD = new ParseField("match_phrase", "text_phrase");
public static final ParseField MATCH_PHRASE_PREFIX_FIELD = new ParseField("match_phrase_prefix", "text_phrase_prefix");
public static final ParseField SLOP_FIELD = new ParseField("slop", "phrase_slop");
public static final ParseField ZERO_TERMS_QUERY_FIELD = new ParseField("zero_terms_query");
public static final ParseField CUTOFF_FREQUENCY_FIELD = new ParseField("cutoff_frequency");
public static final ParseField LENIENT_FIELD = new ParseField("lenient");
public static final ParseField FUZZY_TRANSPOSITIONS_FIELD = new ParseField("fuzzy_transpositions");
public static final ParseField FUZZY_REWRITE_FIELD = new ParseField("fuzzy_rewrite");
public static final ParseField MINIMUM_SHOULD_MATCH_FIELD = new ParseField("minimum_should_match");
public static final ParseField OPERATOR_FIELD = new ParseField("operator");
public static final ParseField MAX_EXPANSIONS_FIELD = new ParseField("max_expansions");
public static final ParseField PREFIX_LENGTH_FIELD = new ParseField("prefix_length");
public static final ParseField ANALYZER_FIELD = new ParseField("analyzer");
public static final ParseField TYPE_FIELD = new ParseField("type");
public static final ParseField QUERY_FIELD = new ParseField("query");
@Override
public String[] names() {
return new String[]{
MatchQueryBuilder.NAME, "match_phrase", "matchPhrase", "match_phrase_prefix", "matchPhrasePrefix", "matchFuzzy", "match_fuzzy", "fuzzy_match"
};
}
@Override
public MatchQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
MatchQuery.Type type = MatchQuery.Type.BOOLEAN;
if (parseContext.parseFieldMatcher().match(parser.currentName(), MATCH_PHRASE_FIELD)) {
type = MatchQuery.Type.PHRASE;
} else if (parseContext.parseFieldMatcher().match(parser.currentName(), MATCH_PHRASE_PREFIX_FIELD)) {
type = MatchQuery.Type.PHRASE_PREFIX;
}
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.FIELD_NAME) {
throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] query malformed, no field");
}
String fieldName = parser.currentName();
Object value = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String minimumShouldMatch = null;
String analyzer = null;
Operator operator = MatchQueryBuilder.DEFAULT_OPERATOR;
int slop = MatchQuery.DEFAULT_PHRASE_SLOP;
Fuzziness fuzziness = null;
int prefixLength = FuzzyQuery.defaultPrefixLength;
int maxExpansion = FuzzyQuery.defaultMaxExpansions;
boolean fuzzyTranspositions = FuzzyQuery.defaultTranspositions;
String fuzzyRewrite = null;
boolean lenient = MatchQuery.DEFAULT_LENIENCY;
Float cutOffFrequency = null;
ZeroTermsQuery zeroTermsQuery = MatchQuery.DEFAULT_ZERO_TERMS_QUERY;
String queryName = null;
token = parser.nextToken();
if (token == XContentParser.Token.START_OBJECT) {
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token.isValue()) {
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
value = parser.objectText();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, TYPE_FIELD)) {
String tStr = parser.text();
if ("boolean".equals(tStr)) {
type = MatchQuery.Type.BOOLEAN;
} else if ("phrase".equals(tStr)) {
type = MatchQuery.Type.PHRASE;
} else if ("phrase_prefix".equals(tStr) || ("phrasePrefix".equals(tStr))) {
type = MatchQuery.Type.PHRASE_PREFIX;
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] query does not support type " + tStr);
}
} else if (parseContext.parseFieldMatcher().match(currentFieldName, ANALYZER_FIELD)) {
analyzer = parser.text();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
boost = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, SLOP_FIELD)) {
slop = parser.intValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, Fuzziness.FIELD)) {
fuzziness = Fuzziness.parse(parser);
} else if (parseContext.parseFieldMatcher().match(currentFieldName, PREFIX_LENGTH_FIELD)) {
prefixLength = parser.intValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, MAX_EXPANSIONS_FIELD)) {
maxExpansion = parser.intValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, OPERATOR_FIELD)) {
operator = Operator.fromString(parser.text());
} else if (parseContext.parseFieldMatcher().match(currentFieldName, MINIMUM_SHOULD_MATCH_FIELD)) {
minimumShouldMatch = parser.textOrNull();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZY_REWRITE_FIELD)) {
fuzzyRewrite = parser.textOrNull();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, FUZZY_TRANSPOSITIONS_FIELD)) {
fuzzyTranspositions = parser.booleanValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, LENIENT_FIELD)) {
lenient = parser.booleanValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, CUTOFF_FREQUENCY_FIELD)) {
cutOffFrequency = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, ZERO_TERMS_QUERY_FIELD)) {
String zeroTermsDocs = parser.text();
if ("none".equalsIgnoreCase(zeroTermsDocs)) {
zeroTermsQuery = MatchQuery.ZeroTermsQuery.NONE;
} else if ("all".equalsIgnoreCase(zeroTermsDocs)) {
zeroTermsQuery = MatchQuery.ZeroTermsQuery.ALL;
} else {
throw new ParsingException(parser.getTokenLocation(), "Unsupported zero_terms_docs value [" + zeroTermsDocs + "]");
}
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
queryName = parser.text();
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] query does not support [" + currentFieldName + "]");
}
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + MatchQueryBuilder.NAME + "] unknown token [" + token + "] after [" + currentFieldName + "]");
}
}
parser.nextToken();
} else {
value = parser.objectText();
// move to the next token
token = parser.nextToken();
if (token != XContentParser.Token.END_OBJECT) {
throw new ParsingException(parser.getTokenLocation(),
"[match] 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) {
throw new ParsingException(parser.getTokenLocation(), "No text specified for text query");
}
MatchQueryBuilder matchQuery = new MatchQueryBuilder(fieldName, value);
matchQuery.operator(operator);
matchQuery.type(type);
matchQuery.analyzer(analyzer);
matchQuery.slop(slop);
matchQuery.minimumShouldMatch(minimumShouldMatch);
if (fuzziness != null) {
matchQuery.fuzziness(fuzziness);
}
matchQuery.fuzzyRewrite(fuzzyRewrite);
matchQuery.prefixLength(prefixLength);
matchQuery.fuzzyTranspositions(fuzzyTranspositions);
matchQuery.maxExpansions(maxExpansion);
matchQuery.lenient(lenient);
if (cutOffFrequency != null) {
matchQuery.cutoffFrequency(cutOffFrequency);
}
matchQuery.zeroTermsQuery(zeroTermsQuery);
matchQuery.queryName(queryName);
matchQuery.boost(boost);
return matchQuery;
}
@Override
public MatchQueryBuilder getBuilderPrototype() {
return MatchQueryBuilder.PROTOTYPE;
}
}

View File

@ -30,7 +30,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* Same as {@link MatchQueryParser} but has support for multiple fields.
* Same as {@link MatchQueryBuilder} but has support for multiple fields.
*/
public class MultiMatchQueryParser implements QueryParser<MultiMatchQueryBuilder> {

View File

@ -25,12 +25,15 @@ import java.io.IOException;
* Defines a query parser that is able to read and parse a query object in {@link org.elasticsearch.common.xcontent.XContent}
* format and create an internal object representing the query, implementing {@link QueryBuilder}, which can be streamed to other nodes.
*/
@FunctionalInterface
public interface QueryParser<QB extends QueryBuilder<QB>> {
/**
* The names this query parser is registered under.
*/
String[] names();
default String[] names() { // TODO remove this when nothing implements it
throw new UnsupportedOperationException();
}
/**
* Creates a new {@link QueryBuilder} from the query held by the {@link QueryParseContext}
@ -47,5 +50,7 @@ public interface QueryParser<QB extends QueryBuilder<QB>> {
/**
* @return an empty {@link QueryBuilder} instance for this parser that can be used for deserialization
*/
QB getBuilderPrototype();
default QB getBuilderPrototype() { // TODO remove this when nothing implements it
throw new UnsupportedOperationException();
}
}

View File

@ -21,6 +21,8 @@ package org.elasticsearch.index.query.functionscore;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
@ -30,18 +32,25 @@ import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.common.lucene.search.function.ScoreFunction;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentLocation;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.AbstractQueryBuilder;
import org.elasticsearch.index.query.EmptyQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryRewriteContext;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.index.query.functionscore.random.RandomScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.weight.WeightBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
/**
* A query that uses a filters with a script associated with them to compute the
@ -49,8 +58,23 @@ import java.util.Objects;
*/
public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScoreQueryBuilder> {
public static final FunctionScoreQueryBuilder PROTOTYPE = new FunctionScoreQueryBuilder(EmptyQueryBuilder.PROTOTYPE,
new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]);
public static final String NAME = "function_score";
// For better readability of error message
static final String MISPLACED_FUNCTION_MESSAGE_PREFIX = "you can either define [functions] array or a single function, not both. ";
public static final ParseField WEIGHT_FIELD = new ParseField("weight");
public static final ParseField QUERY_FIELD = new ParseField("query");
public static final ParseField FILTER_FIELD = new ParseField("filter");
public static final ParseField FUNCTIONS_FIELD = new ParseField("functions");
public static final ParseField SCORE_MODE_FIELD = new ParseField("score_mode");
public static final ParseField BOOST_MODE_FIELD = new ParseField("boost_mode");
public static final ParseField MAX_BOOST_FIELD = new ParseField("max_boost");
public static final ParseField MIN_SCORE_FIELD = new ParseField("min_score");
public static final CombineFunction DEFAULT_BOOST_MODE = CombineFunction.MULTIPLY;
public static final FiltersFunctionScoreQuery.ScoreMode DEFAULT_SCORE_MODE = FiltersFunctionScoreQuery.ScoreMode.MULTIPLY;
@ -198,22 +222,22 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(NAME);
if (query != null) {
builder.field(FunctionScoreQueryParser.QUERY_FIELD.getPreferredName());
builder.field(QUERY_FIELD.getPreferredName());
query.toXContent(builder, params);
}
builder.startArray(FunctionScoreQueryParser.FUNCTIONS_FIELD.getPreferredName());
builder.startArray(FUNCTIONS_FIELD.getPreferredName());
for (FilterFunctionBuilder filterFunctionBuilder : filterFunctionBuilders) {
filterFunctionBuilder.toXContent(builder, params);
}
builder.endArray();
builder.field(FunctionScoreQueryParser.SCORE_MODE_FIELD.getPreferredName(), scoreMode.name().toLowerCase(Locale.ROOT));
builder.field(SCORE_MODE_FIELD.getPreferredName(), scoreMode.name().toLowerCase(Locale.ROOT));
if (boostMode != null) {
builder.field(FunctionScoreQueryParser.BOOST_MODE_FIELD.getPreferredName(), boostMode.name().toLowerCase(Locale.ROOT));
builder.field(BOOST_MODE_FIELD.getPreferredName(), boostMode.name().toLowerCase(Locale.ROOT));
}
builder.field(FunctionScoreQueryParser.MAX_BOOST_FIELD.getPreferredName(), maxBoost);
builder.field(MAX_BOOST_FIELD.getPreferredName(), maxBoost);
if (minScore != null) {
builder.field(FunctionScoreQueryParser.MIN_SCORE_FIELD.getPreferredName(), minScore);
builder.field(MIN_SCORE_FIELD.getPreferredName(), minScore);
}
printBoostAndQueryName(builder);
builder.endObject();
@ -359,7 +383,7 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(FunctionScoreQueryParser.FILTER_FIELD.getPreferredName());
builder.field(FILTER_FIELD.getPreferredName());
filter.toXContent(builder, params);
scoreFunction.toXContent(builder, params);
builder.endObject();
@ -423,4 +447,179 @@ public class FunctionScoreQueryBuilder extends AbstractQueryBuilder<FunctionScor
}
return this;
}
public static FunctionScoreQueryBuilder fromXContent(Function<String, ScoreFunctionParser<?>> scoreFunctionLookup,
QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
QueryBuilder<?> query = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null;
FiltersFunctionScoreQuery.ScoreMode scoreMode = FunctionScoreQueryBuilder.DEFAULT_SCORE_MODE;
float maxBoost = FunctionScoreQuery.DEFAULT_MAX_BOOST;
Float minScore = null;
String currentFieldName = null;
XContentParser.Token token;
CombineFunction combineFunction = null;
// Either define array of functions and filters or only one function
boolean functionArrayFound = false;
boolean singleFunctionFound = false;
String singleFunctionName = null;
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
if (query != null) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. [query] is already defined.", FunctionScoreQueryBuilder.NAME);
}
query = parseContext.parseInnerQueryBuilder();
} else {
if (singleFunctionFound) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. already found function [{}], now encountering [{}]. use [functions] array if you want to define several functions.", FunctionScoreQueryBuilder.NAME, singleFunctionName, currentFieldName);
}
if (functionArrayFound) {
String errorString = "already found [functions] array, now encountering [" + currentFieldName + "].";
handleMisplacedFunctionsDeclaration(parser.getTokenLocation(), errorString);
}
singleFunctionFound = true;
singleFunctionName = currentFieldName;
// we try to parse a score function. If there is no score function for the current field name,
// functionParserMapper.get() may throw an Exception.
ScoreFunctionBuilder<?> scoreFunction = lookupFunctionParser(scoreFunctionLookup, parser.getTokenLocation(), currentFieldName).fromXContent(parseContext, parser);
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(scoreFunction));
}
} else if (token == XContentParser.Token.START_ARRAY) {
if (parseContext.parseFieldMatcher().match(currentFieldName, FUNCTIONS_FIELD)) {
if (singleFunctionFound) {
String errorString = "already found [" + singleFunctionName + "], now encountering [functions].";
handleMisplacedFunctionsDeclaration(parser.getTokenLocation(), errorString);
}
functionArrayFound = true;
currentFieldName = parseFiltersAndFunctions(scoreFunctionLookup, parseContext, parser, filterFunctionBuilders);
} else {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. array [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName);
}
} else if (token.isValue()) {
if (parseContext.parseFieldMatcher().match(currentFieldName, SCORE_MODE_FIELD)) {
scoreMode = FiltersFunctionScoreQuery.ScoreMode.fromString(parser.text());
} else if (parseContext.parseFieldMatcher().match(currentFieldName, BOOST_MODE_FIELD)) {
combineFunction = CombineFunction.fromString(parser.text());
} else if (parseContext.parseFieldMatcher().match(currentFieldName, MAX_BOOST_FIELD)) {
maxBoost = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
boost = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
queryName = parser.text();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, MIN_SCORE_FIELD)) {
minScore = parser.floatValue();
} else {
if (singleFunctionFound) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. already found function [{}], now encountering [{}]. use [functions] array if you want to define several functions.", FunctionScoreQueryBuilder.NAME, singleFunctionName, currentFieldName);
}
if (functionArrayFound) {
String errorString = "already found [functions] array, now encountering [" + currentFieldName + "].";
handleMisplacedFunctionsDeclaration(parser.getTokenLocation(), errorString);
}
if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) {
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(new WeightBuilder().setWeight(parser.floatValue())));
singleFunctionFound = true;
singleFunctionName = currentFieldName;
} else {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. field [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName);
}
}
}
}
if (query == null) {
query = new MatchAllQueryBuilder();
}
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(query,
filterFunctionBuilders.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()]));
if (combineFunction != null) {
functionScoreQueryBuilder.boostMode(combineFunction);
}
functionScoreQueryBuilder.scoreMode(scoreMode);
functionScoreQueryBuilder.maxBoost(maxBoost);
if (minScore != null) {
functionScoreQueryBuilder.setMinScore(minScore);
}
functionScoreQueryBuilder.boost(boost);
functionScoreQueryBuilder.queryName(queryName);
return functionScoreQueryBuilder;
}
private static void handleMisplacedFunctionsDeclaration(XContentLocation contentLocation, String errorString) {
throw new ParsingException(contentLocation, "failed to parse [{}] query. [{}]", FunctionScoreQueryBuilder.NAME, MISPLACED_FUNCTION_MESSAGE_PREFIX + errorString);
}
private static String parseFiltersAndFunctions(Function<String, ScoreFunctionParser<?>> scoreFunctionLookup,
QueryParseContext parseContext, XContentParser parser,
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders) throws IOException {
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
QueryBuilder filter = null;
ScoreFunctionBuilder<?> scoreFunction = null;
Float functionWeight = null;
if (token != XContentParser.Token.START_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}]. malformed query, expected a [{}] while parsing functions but got a [{}] instead", XContentParser.Token.START_OBJECT, token, FunctionScoreQueryBuilder.NAME);
} else {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
if (parseContext.parseFieldMatcher().match(currentFieldName, FILTER_FIELD)) {
filter = parseContext.parseInnerQueryBuilder();
} else {
if (scoreFunction != null) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse function_score functions. already found [{}], now encountering [{}].", scoreFunction.getName(), currentFieldName);
}
// do not need to check null here, functionParserMapper does it already
ScoreFunctionParser functionParser = lookupFunctionParser(scoreFunctionLookup, parser.getTokenLocation(), currentFieldName);
scoreFunction = functionParser.fromXContent(parseContext, parser);
}
} else if (token.isValue()) {
if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) {
functionWeight = parser.floatValue();
} else {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. field [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName);
}
}
}
if (functionWeight != null) {
if (scoreFunction == null) {
scoreFunction = new WeightBuilder().setWeight(functionWeight);
} else {
scoreFunction.setWeight(functionWeight);
}
}
}
if (filter == null) {
filter = new MatchAllQueryBuilder();
}
if (scoreFunction == null) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. an entry in functions list is missing a function.", FunctionScoreQueryBuilder.NAME);
}
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(filter, scoreFunction));
}
return currentFieldName;
}
private static ScoreFunctionParser<?> lookupFunctionParser(Function<String, ScoreFunctionParser<?>> scoreFunctionLookup,
XContentLocation contentLocation, String parserName) {
ScoreFunctionParser<?> functionParser = scoreFunctionLookup.apply(parserName);
if (functionParser == null) {
throw new ParsingException(contentLocation, "No function with the name [" + parserName + "] is registered.");
}
return functionParser;
}
}

View File

@ -1,240 +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.functionscore;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.common.xcontent.XContentLocation;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.AbstractQueryBuilder;
import org.elasticsearch.index.query.EmptyQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParser;
import org.elasticsearch.index.query.functionscore.weight.WeightBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Parser for function_score query
*/
public class FunctionScoreQueryParser implements QueryParser<FunctionScoreQueryBuilder> {
private static final FunctionScoreQueryBuilder PROTOTYPE = new FunctionScoreQueryBuilder(EmptyQueryBuilder.PROTOTYPE, new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]);
// For better readability of error message
static final String MISPLACED_FUNCTION_MESSAGE_PREFIX = "you can either define [functions] array or a single function, not both. ";
public static final ParseField WEIGHT_FIELD = new ParseField("weight");
public static final ParseField QUERY_FIELD = new ParseField("query");
public static final ParseField FILTER_FIELD = new ParseField("filter");
public static final ParseField FUNCTIONS_FIELD = new ParseField("functions");
public static final ParseField SCORE_MODE_FIELD = new ParseField("score_mode");
public static final ParseField BOOST_MODE_FIELD = new ParseField("boost_mode");
public static final ParseField MAX_BOOST_FIELD = new ParseField("max_boost");
public static final ParseField MIN_SCORE_FIELD = new ParseField("min_score");
private final ScoreFunctionParserMapper functionParserMapper;
public FunctionScoreQueryParser(ScoreFunctionParserMapper functionParserMapper) {
this.functionParserMapper = functionParserMapper;
}
@Override
public String[] names() {
return new String[] { FunctionScoreQueryBuilder.NAME, Strings.toCamelCase(FunctionScoreQueryBuilder.NAME) };
}
@Override
public FunctionScoreQueryBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
QueryBuilder query = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null;
FiltersFunctionScoreQuery.ScoreMode scoreMode = FunctionScoreQueryBuilder.DEFAULT_SCORE_MODE;
float maxBoost = FunctionScoreQuery.DEFAULT_MAX_BOOST;
Float minScore = null;
String currentFieldName = null;
XContentParser.Token token;
CombineFunction combineFunction = null;
// Either define array of functions and filters or only one function
boolean functionArrayFound = false;
boolean singleFunctionFound = false;
String singleFunctionName = null;
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
if (parseContext.parseFieldMatcher().match(currentFieldName, QUERY_FIELD)) {
if (query != null) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. [query] is already defined.", FunctionScoreQueryBuilder.NAME);
}
query = parseContext.parseInnerQueryBuilder();
} else {
if (singleFunctionFound) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. already found function [{}], now encountering [{}]. use [functions] array if you want to define several functions.", FunctionScoreQueryBuilder.NAME, singleFunctionName, currentFieldName);
}
if (functionArrayFound) {
String errorString = "already found [functions] array, now encountering [" + currentFieldName + "].";
handleMisplacedFunctionsDeclaration(parser.getTokenLocation(), errorString);
}
singleFunctionFound = true;
singleFunctionName = currentFieldName;
// we try to parse a score function. If there is no score function for the current field name,
// functionParserMapper.get() may throw an Exception.
ScoreFunctionBuilder<?> scoreFunction = functionParserMapper.get(parser.getTokenLocation(), currentFieldName).fromXContent(parseContext, parser);
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(scoreFunction));
}
} else if (token == XContentParser.Token.START_ARRAY) {
if (parseContext.parseFieldMatcher().match(currentFieldName, FUNCTIONS_FIELD)) {
if (singleFunctionFound) {
String errorString = "already found [" + singleFunctionName + "], now encountering [functions].";
handleMisplacedFunctionsDeclaration(parser.getTokenLocation(), errorString);
}
functionArrayFound = true;
currentFieldName = parseFiltersAndFunctions(parseContext, parser, filterFunctionBuilders);
} else {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. array [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName);
}
} else if (token.isValue()) {
if (parseContext.parseFieldMatcher().match(currentFieldName, SCORE_MODE_FIELD)) {
scoreMode = FiltersFunctionScoreQuery.ScoreMode.fromString(parser.text());
} else if (parseContext.parseFieldMatcher().match(currentFieldName, BOOST_MODE_FIELD)) {
combineFunction = CombineFunction.fromString(parser.text());
} else if (parseContext.parseFieldMatcher().match(currentFieldName, MAX_BOOST_FIELD)) {
maxBoost = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
boost = parser.floatValue();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.NAME_FIELD)) {
queryName = parser.text();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, MIN_SCORE_FIELD)) {
minScore = parser.floatValue();
} else {
if (singleFunctionFound) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. already found function [{}], now encountering [{}]. use [functions] array if you want to define several functions.", FunctionScoreQueryBuilder.NAME, singleFunctionName, currentFieldName);
}
if (functionArrayFound) {
String errorString = "already found [functions] array, now encountering [" + currentFieldName + "].";
handleMisplacedFunctionsDeclaration(parser.getTokenLocation(), errorString);
}
if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) {
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(new WeightBuilder().setWeight(parser.floatValue())));
singleFunctionFound = true;
singleFunctionName = currentFieldName;
} else {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. field [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName);
}
}
}
}
if (query == null) {
query = new MatchAllQueryBuilder();
}
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(query,
filterFunctionBuilders.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()]));
if (combineFunction != null) {
functionScoreQueryBuilder.boostMode(combineFunction);
}
functionScoreQueryBuilder.scoreMode(scoreMode);
functionScoreQueryBuilder.maxBoost(maxBoost);
if (minScore != null) {
functionScoreQueryBuilder.setMinScore(minScore);
}
functionScoreQueryBuilder.boost(boost);
functionScoreQueryBuilder.queryName(queryName);
return functionScoreQueryBuilder;
}
private static void handleMisplacedFunctionsDeclaration(XContentLocation contentLocation, String errorString) {
throw new ParsingException(contentLocation, "failed to parse [{}] query. [{}]", FunctionScoreQueryBuilder.NAME, MISPLACED_FUNCTION_MESSAGE_PREFIX + errorString);
}
private String parseFiltersAndFunctions(QueryParseContext parseContext, XContentParser parser, List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders) throws IOException {
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
QueryBuilder filter = null;
ScoreFunctionBuilder<?> scoreFunction = null;
Float functionWeight = null;
if (token != XContentParser.Token.START_OBJECT) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}]. malformed query, expected a [{}] while parsing functions but got a [{}] instead", XContentParser.Token.START_OBJECT, token, FunctionScoreQueryBuilder.NAME);
} else {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
if (parseContext.parseFieldMatcher().match(currentFieldName, FILTER_FIELD)) {
filter = parseContext.parseInnerQueryBuilder();
} else {
if (scoreFunction != null) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse function_score functions. already found [{}], now encountering [{}].", scoreFunction.getName(), currentFieldName);
}
// do not need to check null here, functionParserMapper does it already
ScoreFunctionParser functionParser = functionParserMapper.get(parser.getTokenLocation(), currentFieldName);
scoreFunction = functionParser.fromXContent(parseContext, parser);
}
} else if (token.isValue()) {
if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) {
functionWeight = parser.floatValue();
} else {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. field [{}] is not supported", FunctionScoreQueryBuilder.NAME, currentFieldName);
}
}
}
if (functionWeight != null) {
if (scoreFunction == null) {
scoreFunction = new WeightBuilder().setWeight(functionWeight);
} else {
scoreFunction.setWeight(functionWeight);
}
}
}
if (filter == null) {
filter = new MatchAllQueryBuilder();
}
if (scoreFunction == null) {
throw new ParsingException(parser.getTokenLocation(), "failed to parse [{}] query. an entry in functions list is missing a function.", FunctionScoreQueryBuilder.NAME);
}
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(filter, scoreFunction));
}
return currentFieldName;
}
@Override
public FunctionScoreQueryBuilder getBuilderPrototype() {
return PROTOTYPE;
}
}

View File

@ -48,7 +48,7 @@ public abstract class ScoreFunctionBuilder<FB extends ScoreFunctionBuilder> impl
protected void buildWeight(XContentBuilder builder) throws IOException {
if (weight != null) {
builder.field(FunctionScoreQueryParser.WEIGHT_FIELD.getPreferredName(), weight);
builder.field(FunctionScoreQueryBuilder.WEIGHT_FIELD.getPreferredName(), weight);
}
}

View File

@ -34,7 +34,7 @@ public interface ScoreFunctionParser<FB extends ScoreFunctionBuilder<FB>> {
/**
* Returns the name of the function, for example "linear", "gauss" etc. This
* name is used for registering the parser in
* {@link FunctionScoreQueryParser}.
* {@link FunctionScoreQueryBuilder}.
* */
String[] getNames();
}

View File

@ -1,48 +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.functionscore;
import java.util.Map;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.xcontent.XContentLocation;
import static java.util.Collections.unmodifiableMap;
public class ScoreFunctionParserMapper {
protected Map<String, ScoreFunctionParser<?>> functionParsers;
public ScoreFunctionParserMapper(Map<String, ScoreFunctionParser<?>> functionParsers) {
this.functionParsers = unmodifiableMap(functionParsers);
}
public ScoreFunctionParser<?> get(XContentLocation contentLocation, String parserName) {
ScoreFunctionParser<?> functionParser = get(parserName);
if (functionParser == null) {
throw new ParsingException(contentLocation, "No function with the name [" + parserName + "] is registered.");
}
return functionParser;
}
private ScoreFunctionParser<?> get(String parserName) {
return functionParsers.get(parserName);
}
}

View File

@ -20,12 +20,15 @@
package org.elasticsearch.search;
import org.apache.lucene.search.BooleanQuery;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.ShapesAvailability;
import org.elasticsearch.common.geo.builders.ShapeBuilders;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.multibindings.Multibinder;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.percolator.PercolatorHighlightSubFetchPhase;
import org.elasticsearch.index.query.BoolQueryParser;
@ -49,7 +52,7 @@ import org.elasticsearch.index.query.IdsQueryParser;
import org.elasticsearch.index.query.IndicesQueryParser;
import org.elasticsearch.index.query.MatchAllQueryParser;
import org.elasticsearch.index.query.MatchNoneQueryParser;
import org.elasticsearch.index.query.MatchQueryParser;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.MoreLikeThisQueryParser;
import org.elasticsearch.index.query.MultiMatchQueryParser;
import org.elasticsearch.index.query.NestedQueryParser;
@ -57,6 +60,7 @@ import org.elasticsearch.index.query.ParentIdQueryParser;
import org.elasticsearch.index.query.PercolatorQueryParser;
import org.elasticsearch.index.query.PrefixQueryParser;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParser;
import org.elasticsearch.index.query.QueryStringQueryParser;
import org.elasticsearch.index.query.RangeQueryParser;
@ -77,10 +81,9 @@ import org.elasticsearch.index.query.TermsQueryParser;
import org.elasticsearch.index.query.TypeQueryParser;
import org.elasticsearch.index.query.WildcardQueryParser;
import org.elasticsearch.index.query.WrapperQueryParser;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryParser;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionParser;
import org.elasticsearch.index.query.functionscore.ScoreFunctionParserMapper;
import org.elasticsearch.index.query.functionscore.exp.ExponentialDecayFunctionParser;
import org.elasticsearch.index.query.functionscore.fieldvaluefactor.FieldValueFactorFunctionParser;
import org.elasticsearch.index.query.functionscore.gauss.GaussDecayFunctionParser;
@ -224,14 +227,6 @@ import org.elasticsearch.search.sort.ScriptSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.suggest.Suggester;
import org.elasticsearch.search.suggest.Suggesters;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.Laplace;
import org.elasticsearch.search.suggest.phrase.LinearInterpolation;
import org.elasticsearch.search.suggest.phrase.PhraseSuggestionBuilder;
import org.elasticsearch.search.suggest.phrase.SmoothingModel;
import org.elasticsearch.search.suggest.phrase.StupidBackoff;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import java.util.ArrayList;
import java.util.HashMap;
@ -260,6 +255,7 @@ public class SearchModule extends AbstractModule {
* at configure time because they depend on things that are registered by
* plugins (function score parsers).
*/
private final List<QueryRegistration<?>> queries = new ArrayList<>();
private final List<Supplier<QueryParser<?>>> queryParsers = new ArrayList<>();
private final Set<Class<? extends FetchSubPhase>> fetchSubPhases = new HashSet<>();
private final Set<SignificanceHeuristicParser> heuristicParsers = new HashSet<>();
@ -304,6 +300,23 @@ public class SearchModule extends AbstractModule {
namedWriteableRegistry.registerPrototype(ScoreFunctionBuilder.class, sfb);
}
/**
* Register a query.
*
* @param reader the reader registered for this query's builder. Typically a reference to a constructor that takes a
* {@link org.elasticsearch.common.io.stream.StreamInput}
* @param parser the parser the reads the query builder from xcontent
* @param names all names by which this query might be parsed. The first name is special as it is the name by under which the reader is
* registered. So it is the name that the query should use as its {@link NamedWriteable#getWriteableName()}.
*/
public <QB extends QueryBuilder<QB>> void registerQuery(Writeable.Reader<QB> reader, QueryParser<QB> parser, String... names) {
queries.add(new QueryRegistration<QB>(names, reader, parser));
}
/**
* Register a query.
* TODO remove this in favor of registerQuery
*/
public void registerQueryParser(Supplier<QueryParser<?>> parser) {
queryParsers.add(parser);
}
@ -364,16 +377,28 @@ public class SearchModule extends AbstractModule {
public IndicesQueriesRegistry buildQueryParserRegistry() {
Map<String, QueryParser<?>> queryParsersMap = new HashMap<>();
// TODO remove this when we retire registerQueryParser
for (Supplier<QueryParser<?>> parserSupplier : queryParsers) {
QueryParser<? extends QueryBuilder> parser = parserSupplier.get();
QueryParser<? extends QueryBuilder<?>> parser = parserSupplier.get();
for (String name: parser.names()) {
Object oldValue = queryParsersMap.putIfAbsent(name, parser);
if (oldValue != null) {
throw new IllegalArgumentException("Query parser [" + oldValue + "] already registered for name [" + name + "] while trying to register [" + parser + "]");
}
}
@SuppressWarnings("unchecked") NamedWriteable<? extends QueryBuilder> qb = parser.getBuilderPrototype();
namedWriteableRegistry.registerPrototype(QueryBuilder.class, qb);
namedWriteableRegistry.registerPrototype(QueryBuilder.class, parser.getBuilderPrototype());
}
for (QueryRegistration<?> query : queries) {
QueryParser<? extends QueryBuilder<?>> parser = query.parser;
for (String name: query.names) {
Object oldValue = queryParsersMap.putIfAbsent(name, parser);
if (oldValue != null) {
throw new IllegalArgumentException("Query parser [" + oldValue + "] already registered for name [" + name + "] while trying to register [" + parser + "]");
}
}
namedWriteableRegistry.register(QueryBuilder.class, query.names[0], query.reader);
}
return new IndicesQueriesRegistry(settings, queryParsersMap);
}
@ -488,7 +513,8 @@ public class SearchModule extends AbstractModule {
}
private void registerBuiltinQueryParsers() {
registerQueryParser(MatchQueryParser::new);
registerQuery(MatchQueryBuilder.PROTOTYPE::readFrom, MatchQueryBuilder::fromXContent, MatchQueryBuilder.NAME,
"match_phrase", "matchPhrase", "match_phrase_prefix", "matchPhrasePrefix", "matchFuzzy", "match_fuzzy", "fuzzy_match");
registerQueryParser(MultiMatchQueryParser::new);
registerQueryParser(NestedQueryParser::new);
registerQueryParser(HasChildQueryParser::new);
@ -498,7 +524,8 @@ public class SearchModule extends AbstractModule {
registerQueryParser(MatchAllQueryParser::new);
registerQueryParser(QueryStringQueryParser::new);
registerQueryParser(BoostingQueryParser::new);
BooleanQuery.setMaxClauseCount(settings.getAsInt("index.query.bool.max_clause_count", settings.getAsInt("indices.query.bool.max_clause_count", BooleanQuery.getMaxClauseCount())));
BooleanQuery.setMaxClauseCount(settings.getAsInt("index.query.bool.max_clause_count",
settings.getAsInt("indices.query.bool.max_clause_count", BooleanQuery.getMaxClauseCount())));
registerQueryParser(BoolQueryParser::new);
registerQueryParser(TermQueryParser::new);
registerQueryParser(TermsQueryParser::new);
@ -521,8 +548,10 @@ public class SearchModule extends AbstractModule {
registerQueryParser(IndicesQueryParser::new);
registerQueryParser(CommonTermsQueryParser::new);
registerQueryParser(SpanMultiTermQueryParser::new);
// This is delayed until configure time to give plugins a chance to register parsers
registerQueryParser(() -> new FunctionScoreQueryParser(new ScoreFunctionParserMapper(functionScoreParsers)));
QueryParser<FunctionScoreQueryBuilder> functionScoreParser = (QueryParseContext c) -> FunctionScoreQueryBuilder
.fromXContent((String name) -> functionScoreParsers.get(name), c);
registerQuery(FunctionScoreQueryBuilder.PROTOTYPE::readFrom, functionScoreParser, FunctionScoreQueryBuilder.NAME,
Strings.toCamelCase(FunctionScoreQueryBuilder.NAME));
registerQueryParser(SimpleQueryStringParser::new);
registerQueryParser(TemplateQueryParser::new);
registerQueryParser(TypeQueryParser::new);
@ -609,4 +638,16 @@ public class SearchModule extends AbstractModule {
public Suggesters getSuggesters() {
return suggesters;
}
private static class QueryRegistration<QB extends QueryBuilder<QB>> {
private final String[] names;
private final Writeable.Reader<QB> reader;
private final QueryParser<QB> parser;
private QueryRegistration(String[] names, Reader<QB> reader, QueryParser<QB> parser) {
this.names = names;
this.reader = reader;
this.parser = parser;
}
}
}

View File

@ -637,12 +637,9 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
@SuppressWarnings("unchecked")
protected <QB extends QueryBuilder> QB assertSerialization(QB testQuery) throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
testQuery.writeTo(output);
output.writeQuery(testQuery);
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
QueryParser<?> queryParser = queryParser(testQuery.getName());
assertNotNull("queryparser not found for query: [" + testQuery.getName() + "]", queryParser);
QueryBuilder<?> prototype = queryParser.getBuilderPrototype();
QueryBuilder<?> deserializedQuery = prototype.readFrom(in);
QueryBuilder<?> deserializedQuery = in.readQuery();
assertEquals(deserializedQuery, testQuery);
assertEquals(deserializedQuery.hashCode(), testQuery.hashCode());
assertNotSame(deserializedQuery, testQuery);
@ -685,38 +682,13 @@ public abstract class AbstractQueryTestCase<QB extends AbstractQueryBuilder<QB>>
}
}
private QueryParser<?> queryParser(String queryId) {
QueryParser<?> queryParser = indicesQueriesRegistry.queryParsers().get(queryId);
if (queryParser == null && EmptyQueryBuilder.NAME.equals(queryId)) {
return new QueryParser() {
@Override
public String[] names() {
return new String[]{EmptyQueryBuilder.NAME};
}
@Override
public QueryBuilder<?> fromXContent(QueryParseContext parseContext) throws IOException {
return new EmptyQueryBuilder();
}
@Override
public QueryBuilder getBuilderPrototype() {
return EmptyQueryBuilder.PROTOTYPE;
}
};
}
return queryParser;
}
//we use the streaming infra to create a copy of the query provided as argument
@SuppressWarnings("unchecked")
protected QB copyQuery(QB query) throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
query.writeTo(output);
output.writeQuery(query);
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
QueryBuilder<?> prototype = queryParser(query.getName()).getBuilderPrototype();
@SuppressWarnings("unchecked")
QB secondQuery = (QB) prototype.readFrom(in);
return secondQuery;
return (QB) in.readQuery();
}
}
}

View File

@ -106,6 +106,7 @@ public abstract class AbstractSortTestCase<T extends SortBuilder<T>> extends EST
@AfterClass
public static void afterClass() throws Exception {
namedWriteableRegistry = null;
indicesQueriesRegistry = null;
}
/** Returns random sort that is put under test */

View File

@ -57,6 +57,7 @@ public class SortBuilderTests extends ESTestCase {
@AfterClass
public static void afterClass() throws Exception {
namedWriteableRegistry = null;
indicesQueriesRegistry = null;
}
/**