function_score: use query and filter together
Before, if filter and query was defined for function_score, then the filter was silently ignored. Now, if both is defined then function score query wraps this in a filtered_query. closes #8638 closes #8675
This commit is contained in:
parent
75e1500e68
commit
7230e605fe
|
@ -420,6 +420,26 @@ public abstract class QueryBuilders {
|
||||||
return new FunctionScoreQueryBuilder(filterBuilder);
|
return new FunctionScoreQueryBuilder(filterBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query that allows to define a custom scoring function.
|
||||||
|
*
|
||||||
|
* @param queryBuilder The query to custom score
|
||||||
|
* @param filterBuilder The filterBuilder to custom score
|
||||||
|
*/
|
||||||
|
public static FunctionScoreQueryBuilder functionScoreQuery(QueryBuilder queryBuilder, FilterBuilder filterBuilder) {
|
||||||
|
return new FunctionScoreQueryBuilder(queryBuilder, filterBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query that allows to define a custom scoring function.
|
||||||
|
*
|
||||||
|
* @param queryBuilder The query to custom score
|
||||||
|
* @param filterBuilder The filterBuilder to custom score
|
||||||
|
*/
|
||||||
|
public static FunctionScoreQueryBuilder functionScoreQuery(QueryBuilder queryBuilder, FilterBuilder filterBuilder, ScoreFunctionBuilder function) {
|
||||||
|
return (new FunctionScoreQueryBuilder(queryBuilder, filterBuilder)).add(function);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A more like this query that finds documents that are "like" the provided {@link MoreLikeThisQueryBuilder#likeText(String)}
|
* A more like this query that finds documents that are "like" the provided {@link MoreLikeThisQueryBuilder#likeText(String)}
|
||||||
* which is checked against the fields the query is constructed with.
|
* which is checked against the fields the query is constructed with.
|
||||||
|
|
|
@ -53,21 +53,50 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
|
||||||
private ArrayList<ScoreFunctionBuilder> scoreFunctions = new ArrayList<>();
|
private ArrayList<ScoreFunctionBuilder> scoreFunctions = new ArrayList<>();
|
||||||
private Float minScore = null;
|
private Float minScore = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a function_score query that executes on documents that match query a query.
|
||||||
|
* Query and filter will be wrapped into a filtered_query.
|
||||||
|
*
|
||||||
|
* @param queryBuilder the query that defines which documents the function_score query will be executed on.
|
||||||
|
*/
|
||||||
public FunctionScoreQueryBuilder(QueryBuilder queryBuilder) {
|
public FunctionScoreQueryBuilder(QueryBuilder queryBuilder) {
|
||||||
this.queryBuilder = queryBuilder;
|
this.queryBuilder = queryBuilder;
|
||||||
this.filterBuilder = null;
|
this.filterBuilder = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a function_score query that executes on documents that match query a query.
|
||||||
|
* Query and filter will be wrapped into a filtered_query.
|
||||||
|
*
|
||||||
|
* @param filterBuilder the filter that defines which documents the function_score query will be executed on.
|
||||||
|
*/
|
||||||
public FunctionScoreQueryBuilder(FilterBuilder filterBuilder) {
|
public FunctionScoreQueryBuilder(FilterBuilder filterBuilder) {
|
||||||
this.filterBuilder = filterBuilder;
|
this.filterBuilder = filterBuilder;
|
||||||
this.queryBuilder = null;
|
this.queryBuilder = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a function_score query that executes on documents that match query and filter.
|
||||||
|
* Query and filter will be wrapped into a filtered_query.
|
||||||
|
*
|
||||||
|
* @param queryBuilder a query that will; be wrapped in a filtered query.
|
||||||
|
* @param filterBuilder the filter for the filtered query.
|
||||||
|
*/
|
||||||
|
public FunctionScoreQueryBuilder(QueryBuilder queryBuilder, FilterBuilder filterBuilder) {
|
||||||
|
this.filterBuilder = filterBuilder;
|
||||||
|
this.queryBuilder = queryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
public FunctionScoreQueryBuilder() {
|
public FunctionScoreQueryBuilder() {
|
||||||
this.filterBuilder = null;
|
this.filterBuilder = null;
|
||||||
this.queryBuilder = null;
|
this.queryBuilder = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a function_score query that will execute the function scoreFunctionBuilder on all documents.
|
||||||
|
*
|
||||||
|
* @param scoreFunctionBuilder score function that is executed
|
||||||
|
*/
|
||||||
public FunctionScoreQueryBuilder(ScoreFunctionBuilder scoreFunctionBuilder) {
|
public FunctionScoreQueryBuilder(ScoreFunctionBuilder scoreFunctionBuilder) {
|
||||||
if (scoreFunctionBuilder == null) {
|
if (scoreFunctionBuilder == null) {
|
||||||
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
|
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
|
||||||
|
@ -78,6 +107,12 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
|
||||||
this.scoreFunctions.add(scoreFunctionBuilder);
|
this.scoreFunctions.add(scoreFunctionBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a score function that will will execute the function scoreFunctionBuilder on all documents matching the filter.
|
||||||
|
*
|
||||||
|
* @param filter the filter that defines which documents the function_score query will be executed on.
|
||||||
|
* @param scoreFunctionBuilder score function that is executed
|
||||||
|
*/
|
||||||
public FunctionScoreQueryBuilder add(FilterBuilder filter, ScoreFunctionBuilder scoreFunctionBuilder) {
|
public FunctionScoreQueryBuilder add(FilterBuilder filter, ScoreFunctionBuilder scoreFunctionBuilder) {
|
||||||
if (scoreFunctionBuilder == null) {
|
if (scoreFunctionBuilder == null) {
|
||||||
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
|
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
|
||||||
|
@ -87,6 +122,11 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a score function that will will execute the function scoreFunctionBuilder on all documents.
|
||||||
|
*
|
||||||
|
* @param scoreFunctionBuilder score function that is executed
|
||||||
|
*/
|
||||||
public FunctionScoreQueryBuilder add(ScoreFunctionBuilder scoreFunctionBuilder) {
|
public FunctionScoreQueryBuilder add(ScoreFunctionBuilder scoreFunctionBuilder) {
|
||||||
if (scoreFunctionBuilder == null) {
|
if (scoreFunctionBuilder == null) {
|
||||||
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
|
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
|
||||||
|
@ -96,21 +136,35 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Score mode defines how results of individual score functions will be aggregated.
|
||||||
|
* Can be first, avg, max, sum, min, multiply
|
||||||
|
*/
|
||||||
public FunctionScoreQueryBuilder scoreMode(String scoreMode) {
|
public FunctionScoreQueryBuilder scoreMode(String scoreMode) {
|
||||||
this.scoreMode = scoreMode;
|
this.scoreMode = scoreMode;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Score mode defines how the combined result of score functions will influence the final score together with the sub query score.
|
||||||
|
* Can be replace, avg, max, sum, min, multiply
|
||||||
|
*/
|
||||||
public FunctionScoreQueryBuilder boostMode(String boostMode) {
|
public FunctionScoreQueryBuilder boostMode(String boostMode) {
|
||||||
this.boostMode = boostMode;
|
this.boostMode = boostMode;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Score mode defines how the combined result of score functions will influence the final score together with the sub query score.
|
||||||
|
*/
|
||||||
public FunctionScoreQueryBuilder boostMode(CombineFunction combineFunction) {
|
public FunctionScoreQueryBuilder boostMode(CombineFunction combineFunction) {
|
||||||
this.boostMode = combineFunction.getName();
|
this.boostMode = combineFunction.getName();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tha maximum boost that will be applied by function score.
|
||||||
|
*/
|
||||||
public FunctionScoreQueryBuilder maxBoost(float maxBoost) {
|
public FunctionScoreQueryBuilder maxBoost(float maxBoost) {
|
||||||
this.maxBoost = maxBoost;
|
this.maxBoost = maxBoost;
|
||||||
return this;
|
return this;
|
||||||
|
@ -132,7 +186,8 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
|
||||||
if (queryBuilder != null) {
|
if (queryBuilder != null) {
|
||||||
builder.field("query");
|
builder.field("query");
|
||||||
queryBuilder.toXContent(builder, params);
|
queryBuilder.toXContent(builder, params);
|
||||||
} else if (filterBuilder != null) {
|
}
|
||||||
|
if (filterBuilder != null) {
|
||||||
builder.field("filter");
|
builder.field("filter");
|
||||||
filterBuilder.toXContent(builder, params);
|
filterBuilder.toXContent(builder, params);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableMap.Builder;
|
import com.google.common.collect.ImmutableMap.Builder;
|
||||||
import org.apache.lucene.search.ConstantScoreQuery;
|
import org.apache.lucene.search.ConstantScoreQuery;
|
||||||
import org.apache.lucene.search.Filter;
|
import org.apache.lucene.search.Filter;
|
||||||
|
import org.apache.lucene.search.FilteredQuery;
|
||||||
import org.apache.lucene.search.Query;
|
import org.apache.lucene.search.Query;
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
import org.elasticsearch.ElasticsearchParseException;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
|
@ -84,6 +85,7 @@ public class FunctionScoreQueryParser implements QueryParser {
|
||||||
XContentParser parser = parseContext.parser();
|
XContentParser parser = parseContext.parser();
|
||||||
|
|
||||||
Query query = null;
|
Query query = null;
|
||||||
|
Filter filter = null;
|
||||||
float boost = 1.0f;
|
float boost = 1.0f;
|
||||||
|
|
||||||
FiltersFunctionScoreQuery.ScoreMode scoreMode = FiltersFunctionScoreQuery.ScoreMode.Multiply;
|
FiltersFunctionScoreQuery.ScoreMode scoreMode = FiltersFunctionScoreQuery.ScoreMode.Multiply;
|
||||||
|
@ -105,7 +107,7 @@ public class FunctionScoreQueryParser implements QueryParser {
|
||||||
} else if ("query".equals(currentFieldName)) {
|
} else if ("query".equals(currentFieldName)) {
|
||||||
query = parseContext.parseInnerQuery();
|
query = parseContext.parseInnerQuery();
|
||||||
} else if ("filter".equals(currentFieldName)) {
|
} else if ("filter".equals(currentFieldName)) {
|
||||||
query = new ConstantScoreQuery(parseContext.parseInnerFilter());
|
filter = parseContext.parseInnerFilter();
|
||||||
} else if ("score_mode".equals(currentFieldName) || "scoreMode".equals(currentFieldName)) {
|
} else if ("score_mode".equals(currentFieldName) || "scoreMode".equals(currentFieldName)) {
|
||||||
scoreMode = parseScoreMode(parseContext, parser);
|
scoreMode = parseScoreMode(parseContext, parser);
|
||||||
} else if ("boost_mode".equals(currentFieldName) || "boostMode".equals(currentFieldName)) {
|
} else if ("boost_mode".equals(currentFieldName) || "boostMode".equals(currentFieldName)) {
|
||||||
|
@ -147,8 +149,12 @@ public class FunctionScoreQueryParser implements QueryParser {
|
||||||
singleFunctionName = currentFieldName;
|
singleFunctionName = currentFieldName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (query == null) {
|
if (query == null && filter == null) {
|
||||||
query = Queries.newMatchAllQuery();
|
query = Queries.newMatchAllQuery();
|
||||||
|
} else if (query == null && filter != null) {
|
||||||
|
query = new ConstantScoreQuery(filter);
|
||||||
|
} else if (query != null && filter != null) {
|
||||||
|
query = new FilteredQuery(query, filter);
|
||||||
}
|
}
|
||||||
// if all filter elements returned null, just use the query
|
// if all filter elements returned null, just use the query
|
||||||
if (filterFunctions.isEmpty()) {
|
if (filterFunctions.isEmpty()) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
|
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
|
||||||
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
|
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
|
||||||
import org.elasticsearch.index.query.functionscore.weight.WeightBuilder;
|
import org.elasticsearch.index.query.functionscore.weight.WeightBuilder;
|
||||||
|
import org.elasticsearch.search.SearchHit;
|
||||||
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
|
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
|
||||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -513,5 +514,40 @@ public class FunctionScoreTests extends ElasticsearchIntegrationTest {
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilterAndQueryGiven() throws IOException, ExecutionException, InterruptedException {
|
||||||
|
assertAcked(prepareCreate("test").addMapping(
|
||||||
|
"type",
|
||||||
|
jsonBuilder().startObject().startObject("type").startObject("properties")
|
||||||
|
.startObject("filter_field").field("type", "string").endObject()
|
||||||
|
.startObject("query_field").field("type", "string").endObject()
|
||||||
|
.startObject("num").field("type", "float").endObject().endObject().endObject().endObject()));
|
||||||
|
ensureYellow();
|
||||||
|
|
||||||
|
List<IndexRequestBuilder> indexRequests = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
indexRequests.add(
|
||||||
|
client().prepareIndex()
|
||||||
|
.setType("type")
|
||||||
|
.setId(Integer.toString(i))
|
||||||
|
.setIndex("test")
|
||||||
|
.setSource(
|
||||||
|
jsonBuilder().startObject().field("query_field", Integer.toString(i % 3)).field("filter_field", Integer.toString(i % 2)).field("num", i).endObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
indexRandom(true, true, indexRequests);
|
||||||
|
|
||||||
|
SearchResponse response = client().search(
|
||||||
|
searchRequest().source(
|
||||||
|
searchSource().query(
|
||||||
|
functionScoreQuery(termQuery("query_field", "0"), termFilter("filter_field", "0"), scriptFunction("doc['num'].value")).boostMode("replace")))).get();
|
||||||
|
|
||||||
|
assertSearchResponse(response);
|
||||||
|
assertThat(response.getHits().totalHits(), equalTo(4l));
|
||||||
|
for (SearchHit hit : response.getHits().getHits()) {
|
||||||
|
assertThat(Float.parseFloat(hit.getId()), equalTo(hit.getScore()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue