org.elasticsearch.index.query.functionscore.FunctionScoreQueryParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* 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 com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.lucene.search.Queries;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.common.lucene.search.function.ScoreFunction;
import org.elasticsearch.common.lucene.search.function.WeightFactorFunction;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParser;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.index.query.functionscore.factor.FactorParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
/**
*
*/
public class FunctionScoreQueryParser implements QueryParser {
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. ";
static final String MISPLACED_BOOST_FUNCTION_MESSAGE_SUFFIX = " did you mean [boost] instead?";
public static final ParseField WEIGHT_FIELD = new ParseField("weight");
private static final ParseField FILTER_FIELD = new ParseField("filter").withAllDeprecated("query");
ScoreFunctionParserMapper functionParserMapper;
@Inject
public FunctionScoreQueryParser(ScoreFunctionParserMapper functionParserMapper) {
this.functionParserMapper = functionParserMapper;
}
@Override
public String[] names() {
return new String[] { NAME };
}
private static final ImmutableMap combineFunctionsMap;
static {
CombineFunction[] values = CombineFunction.values();
Builder combineFunctionMapBuilder = ImmutableMap.builder();
for (CombineFunction combineFunction : values) {
combineFunctionMapBuilder.put(combineFunction.getName(), combineFunction);
}
combineFunctionsMap = combineFunctionMapBuilder.build();
}
@Override
public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
XContentParser parser = parseContext.parser();
Query query = null;
Query filter = null;
float boost = 1.0f;
FiltersFunctionScoreQuery.ScoreMode scoreMode = FiltersFunctionScoreQuery.ScoreMode.Multiply;
ArrayList filterFunctions = new ArrayList<>();
Float maxBoost = null;
Float minScore = null;
String currentFieldName = null;
XContentParser.Token token;
CombineFunction combineFunction = CombineFunction.MULT;
// Either define array of functions and filters or only one function
boolean functionArrayFound = false;
boolean singleFunctionFound = false;
String singleFunctionName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if ("query".equals(currentFieldName)) {
query = parseContext.parseInnerQuery();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, FILTER_FIELD)) {
filter = parseContext.parseInnerFilter();
} else if ("score_mode".equals(currentFieldName) || "scoreMode".equals(currentFieldName)) {
scoreMode = parseScoreMode(parseContext, parser);
} else if ("boost_mode".equals(currentFieldName) || "boostMode".equals(currentFieldName)) {
combineFunction = parseBoostMode(parseContext, parser);
} else if ("max_boost".equals(currentFieldName) || "maxBoost".equals(currentFieldName)) {
maxBoost = parser.floatValue();
} else if ("boost".equals(currentFieldName)) {
boost = parser.floatValue();
} else if ("min_score".equals(currentFieldName) || "minScore".equals(currentFieldName)) {
minScore = parser.floatValue();
} else if ("functions".equals(currentFieldName)) {
if (singleFunctionFound) {
String errorString = "already found [" + singleFunctionName + "], now encountering [functions].";
handleMisplacedFunctionsDeclaration(errorString, singleFunctionName);
}
currentFieldName = parseFiltersAndFunctions(parseContext, parser, filterFunctions, currentFieldName);
functionArrayFound = true;
} else {
ScoreFunction scoreFunction;
if (currentFieldName.equals("weight")) {
scoreFunction = new WeightFactorFunction(parser.floatValue());
} else {
// we try to parse a score function. If there is no score
// function for the current field name,
// functionParserMapper.get() will throw an Exception.
scoreFunction = functionParserMapper.get(parseContext, currentFieldName).parse(parseContext, parser);
}
if (functionArrayFound) {
String errorString = "already found [functions] array, now encountering [" + currentFieldName + "].";
handleMisplacedFunctionsDeclaration(errorString, currentFieldName);
}
if (filterFunctions.size() > 0) {
throw new ElasticsearchParseException("failed to parse [{}] query. already found function [{}], now encountering [{}]. use [functions] array if you want to define several functions.", NAME, singleFunctionName, currentFieldName);
}
filterFunctions.add(new FiltersFunctionScoreQuery.FilterFunction(null, scoreFunction));
singleFunctionFound = true;
singleFunctionName = currentFieldName;
}
}
if (query == null && filter == null) {
query = Queries.newMatchAllQuery();
} else if (query == null && filter != null) {
query = new ConstantScoreQuery(filter);
} else if (query != null && filter != null) {
final BooleanQuery.Builder filtered = new BooleanQuery.Builder();
filtered.add(query, Occur.MUST);
filtered.add(filter, Occur.FILTER);
query = filtered.build();
}
// if all filter elements returned null, just use the query
if (filterFunctions.isEmpty() && combineFunction == null) {
return query;
}
if (maxBoost == null) {
maxBoost = Float.MAX_VALUE;
}
// handle cases where only one score function and no filter was
// provided. In this case we create a FunctionScoreQuery.
if (filterFunctions.size() == 0 || filterFunctions.size() == 1 && (filterFunctions.get(0).filter == null || Queries.isConstantMatchAllQuery(filterFunctions.get(0).filter))) {
ScoreFunction function = filterFunctions.size() == 0 ? null : filterFunctions.get(0).function;
FunctionScoreQuery theQuery = new FunctionScoreQuery(query, function, minScore);
if (combineFunction != null) {
theQuery.setCombineFunction(combineFunction);
}
theQuery.setBoost(boost);
theQuery.setMaxBoost(maxBoost);
return theQuery;
// in all other cases we create a FiltersFunctionScoreQuery.
} else {
FiltersFunctionScoreQuery functionScoreQuery = new FiltersFunctionScoreQuery(query, scoreMode,
filterFunctions.toArray(new FiltersFunctionScoreQuery.FilterFunction[filterFunctions.size()]), maxBoost, minScore);
if (combineFunction != null) {
functionScoreQuery.setCombineFunction(combineFunction);
}
functionScoreQuery.setBoost(boost);
return functionScoreQuery;
}
}
private void handleMisplacedFunctionsDeclaration(String errorString, String functionName) {
errorString = MISPLACED_FUNCTION_MESSAGE_PREFIX + errorString;
if (Arrays.asList(FactorParser.NAMES).contains(functionName)) {
errorString = errorString + MISPLACED_BOOST_FUNCTION_MESSAGE_SUFFIX;
}
throw new ElasticsearchParseException("failed to parse [{}] query. [{}]", NAME, errorString);
}
private String parseFiltersAndFunctions(QueryParseContext parseContext, XContentParser parser,
ArrayList filterFunctions, String currentFieldName) throws IOException {
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
Query filter = null;
ScoreFunction scoreFunction = null;
Float functionWeight = null;
if (token != XContentParser.Token.START_OBJECT) {
throw new QueryParsingException(parseContext, "failed to parse [{}]. malformed query, expected a [{}] while parsing functions but got a [{}] instead", XContentParser.Token.START_OBJECT, token, NAME);
} else {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (parseContext.parseFieldMatcher().match(currentFieldName, WEIGHT_FIELD)) {
functionWeight = parser.floatValue();
} else {
if ("filter".equals(currentFieldName)) {
filter = parseContext.parseInnerFilter();
} else {
// do not need to check null here,
// functionParserMapper throws exception if parser
// non-existent
ScoreFunctionParser functionParser = functionParserMapper.get(parseContext, currentFieldName);
scoreFunction = functionParser.parse(parseContext, parser);
}
}
}
if (functionWeight != null) {
scoreFunction = new WeightFactorFunction(functionWeight, scoreFunction);
}
}
if (filter == null) {
filter = Queries.newMatchAllQuery();
}
if (scoreFunction == null) {
throw new ElasticsearchParseException("failed to parse [{}] query. an entry in functions list is missing a function.", NAME);
}
filterFunctions.add(new FiltersFunctionScoreQuery.FilterFunction(filter, scoreFunction));
}
return currentFieldName;
}
private FiltersFunctionScoreQuery.ScoreMode parseScoreMode(QueryParseContext parseContext, XContentParser parser) throws IOException {
String scoreMode = parser.text();
if ("avg".equals(scoreMode)) {
return FiltersFunctionScoreQuery.ScoreMode.Avg;
} else if ("max".equals(scoreMode)) {
return FiltersFunctionScoreQuery.ScoreMode.Max;
} else if ("min".equals(scoreMode)) {
return FiltersFunctionScoreQuery.ScoreMode.Min;
} else if ("sum".equals(scoreMode)) {
return FiltersFunctionScoreQuery.ScoreMode.Sum;
} else if ("multiply".equals(scoreMode)) {
return FiltersFunctionScoreQuery.ScoreMode.Multiply;
} else if ("first".equals(scoreMode)) {
return FiltersFunctionScoreQuery.ScoreMode.First;
} else {
throw new QueryParsingException(parseContext, "failed to parse [{}] query. illegal score_mode [{}]", NAME, scoreMode);
}
}
private CombineFunction parseBoostMode(QueryParseContext parseContext, XContentParser parser) throws IOException {
String boostMode = parser.text();
CombineFunction cf = combineFunctionsMap.get(boostMode);
if (cf == null) {
throw new QueryParsingException(parseContext, "failed to parse [{}] query. illegal boost_mode [{}]", NAME, boostMode);
}
return cf;
}
}