diff --git a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java index d48831f4437..9fec3f4326d 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java +++ b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java @@ -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. diff --git a/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java index 7d7e3d3c34d..499676f14f4 100644 --- a/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryBuilder.java @@ -53,21 +53,50 @@ public class FunctionScoreQueryBuilder extends BaseQueryBuilder implements Boost private ArrayList 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); } diff --git a/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java b/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java index eebf0b69979..bbfee97cb20 100644 --- a/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/functionscore/FunctionScoreQueryParser.java @@ -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()) { diff --git a/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreTests.java b/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreTests.java index 2a8efe5c8d6..79782450786 100644 --- a/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreTests.java +++ b/src/test/java/org/elasticsearch/search/functionscore/FunctionScoreTests.java @@ -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 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())); + } + } }