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:
Britta Weber 2014-11-26 17:45:39 +01:00
parent 75e1500e68
commit 7230e605fe
4 changed files with 120 additions and 3 deletions

View File

@ -420,6 +420,26 @@ public abstract class QueryBuilders {
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)}
* which is checked against the fields the query is constructed with.

View File

@ -53,21 +53,50 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
private ArrayList<ScoreFunctionBuilder> scoreFunctions = new ArrayList<>();
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) {
this.queryBuilder = queryBuilder;
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) {
this.filterBuilder = filterBuilder;
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() {
this.filterBuilder = 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) {
if (scoreFunctionBuilder == 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);
}
/**
* 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) {
if (scoreFunctionBuilder == null) {
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
@ -87,6 +122,11 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
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) {
if (scoreFunctionBuilder == null) {
throw new ElasticsearchIllegalArgumentException("function_score: function must not be null");
@ -96,21 +136,35 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
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) {
this.scoreMode = scoreMode;
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) {
this.boostMode = boostMode;
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) {
this.boostMode = combineFunction.getName();
return this;
}
/**
* Tha maximum boost that will be applied by function score.
*/
public FunctionScoreQueryBuilder maxBoost(float maxBoost) {
this.maxBoost = maxBoost;
return this;
@ -132,7 +186,8 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost
if (queryBuilder != null) {
builder.field("query");
queryBuilder.toXContent(builder, params);
} else if (filterBuilder != null) {
}
if (filterBuilder != null) {
builder.field("filter");
filterBuilder.toXContent(builder, params);
}

View File

@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
@ -84,6 +85,7 @@ public class FunctionScoreQueryParser implements QueryParser {
XContentParser parser = parseContext.parser();
Query query = null;
Filter filter = null;
float boost = 1.0f;
FiltersFunctionScoreQuery.ScoreMode scoreMode = FiltersFunctionScoreQuery.ScoreMode.Multiply;
@ -105,7 +107,7 @@ public class FunctionScoreQueryParser implements QueryParser {
} else if ("query".equals(currentFieldName)) {
query = parseContext.parseInnerQuery();
} else if ("filter".equals(currentFieldName)) {
query = new ConstantScoreQuery(parseContext.parseInnerFilter());
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)) {
@ -147,8 +149,12 @@ public class FunctionScoreQueryParser implements QueryParser {
singleFunctionName = currentFieldName;
}
}
if (query == null) {
if (query == null && filter == null) {
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 (filterFunctions.isEmpty()) {

View File

@ -29,6 +29,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.weight.WeightBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.junit.Test;
@ -513,5 +514,40 @@ public class FunctionScoreTests extends ElasticsearchIntegrationTest {
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()));
}
}
}