From a2de34eead5a7c1817414ec1cd2a6b48b4ac6fb3 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Wed, 12 Jun 2013 22:41:49 +0200 Subject: [PATCH] Added `filter` support to `custom_score` query. Closes #3167 --- .../index/query/CustomScoreQueryBuilder.java | 31 ++++++++++++++----- .../index/query/CustomScoreQueryParser.java | 19 ++++++++---- .../index/query/QueryBuilders.java | 10 ++++++ .../customscore/CustomScoreSearchTests.java | 13 ++++++++ 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/elasticsearch/index/query/CustomScoreQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/CustomScoreQueryBuilder.java index 359a245520b..fa9784af402 100644 --- a/src/main/java/org/elasticsearch/index/query/CustomScoreQueryBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/CustomScoreQueryBuilder.java @@ -26,14 +26,14 @@ import java.io.IOException; import java.util.Map; /** - * A query that uses a script to compute the score. - * - * + * A query that uses a script to compute or influence the score of documents that match with the inner query or filter. */ public class CustomScoreQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder { private final QueryBuilder queryBuilder; + private final FilterBuilder filterBuilder; + private String script; private String lang; @@ -43,12 +43,24 @@ public class CustomScoreQueryBuilder extends BaseQueryBuilder implements Boostab private Map params = null; /** - * A query that simply applies the boost factor to another query (multiply it). + * Constructs a query that defines how the scores are computed or influenced for documents that match with the + * specified query by a custom defined script. * - * @param queryBuilder The query to apply the boost factor to. + * @param queryBuilder The query that defines what documents are custom scored by this query */ public CustomScoreQueryBuilder(QueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; + this.filterBuilder = null; + } + + /** + * Constructs a query that defines how documents are scored that match with the specified filter. + * + * @param filterBuilder The filter that decides with documents are scored by this query. + */ + public CustomScoreQueryBuilder(FilterBuilder filterBuilder) { + this.filterBuilder = filterBuilder; + this.queryBuilder = null; } /** @@ -102,8 +114,13 @@ public class CustomScoreQueryBuilder extends BaseQueryBuilder implements Boostab @Override protected void doXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(CustomScoreQueryParser.NAME); - builder.field("query"); - queryBuilder.toXContent(builder, params); + if (queryBuilder != null) { + builder.field("query"); + queryBuilder.toXContent(builder, params); + } else if (filterBuilder != null) { + builder.field("filter"); + filterBuilder.toXContent(builder, params); + } builder.field("script", script); if (lang != null) { builder.field("lang", lang); diff --git a/src/main/java/org/elasticsearch/index/query/CustomScoreQueryParser.java b/src/main/java/org/elasticsearch/index/query/CustomScoreQueryParser.java index 81d0694579f..ca0dda21670 100644 --- a/src/main/java/org/elasticsearch/index/query/CustomScoreQueryParser.java +++ b/src/main/java/org/elasticsearch/index/query/CustomScoreQueryParser.java @@ -20,8 +20,9 @@ package org.elasticsearch.index.query; import org.apache.lucene.index.AtomicReaderContext; -import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.Explanation; +import org.apache.lucene.search.Filter; import org.apache.lucene.search.Query; import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; @@ -55,7 +56,8 @@ public class CustomScoreQueryParser implements QueryParser { XContentParser parser = parseContext.parser(); Query query = null; - boolean queryFound = false; + Filter filter = null; + boolean queryOrFilterFound = false; float boost = 1.0f; String script = null; String scriptLang = null; @@ -69,7 +71,10 @@ public class CustomScoreQueryParser implements QueryParser { } else if (token == XContentParser.Token.START_OBJECT) { if ("query".equals(currentFieldName)) { query = parseContext.parseInnerQuery(); - queryFound = true; + queryOrFilterFound = true; + } else if ("filter".equals(currentFieldName)) { + filter = parseContext.parseInnerFilter(); + queryOrFilterFound = true; } else if ("params".equals(currentFieldName)) { vars = parser.map(); } else { @@ -87,14 +92,16 @@ public class CustomScoreQueryParser implements QueryParser { } } } - if (!queryFound) { - throw new QueryParsingException(parseContext.index(), "[custom_score] requires 'query' field"); + if (!queryOrFilterFound) { + throw new QueryParsingException(parseContext.index(), "[custom_score] requires 'query' or 'filter' field"); } if (script == null) { throw new QueryParsingException(parseContext.index(), "[custom_score] requires 'script' field"); } - if (query == null) { + if (query == null && filter == null) { return null; + } else if (filter != null) { + query = new ConstantScoreQuery(filter); } SearchScript searchScript; diff --git a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java index d78c061e1ac..8413e3ef1ae 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java +++ b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java @@ -520,6 +520,16 @@ public abstract class QueryBuilders { return new CustomScoreQueryBuilder(queryBuilder); } + /** + * A query that allows to define a custom scoring script, that defines the score for each document that match + * with the specified filter. + * + * @param filterBuilder The filter that defines which documents are scored by a script. + */ + public static CustomScoreQueryBuilder customScoreQuery(FilterBuilder filterBuilder) { + return new CustomScoreQueryBuilder(filterBuilder); + } + public static CustomFiltersScoreQueryBuilder customFiltersScoreQuery(QueryBuilder queryBuilder) { return new CustomFiltersScoreQueryBuilder(queryBuilder); } diff --git a/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.java b/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.java index 0126e3b892e..50531f03fd8 100644 --- a/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.java +++ b/src/test/java/org/elasticsearch/test/integration/search/customscore/CustomScoreSearchTests.java @@ -309,6 +309,19 @@ public class CustomScoreSearchTests extends AbstractSharedClusterTest { logger.info("Hit[1] {} Explanation {}", response.getHits().getAt(1).id(), response.getHits().getAt(1).explanation()); assertThat(response.getHits().getAt(0).id(), equalTo("1")); assertThat(response.getHits().getAt(1).id(), equalTo("2")); + + logger.info("running param1 * param2 * _score with filter instead of query"); + response = client().search(searchRequest() + .searchType(SearchType.QUERY_THEN_FETCH) + .source(searchSource().explain(true).query(customScoreQuery(termFilter("test", "value")).script("param1 * param2 * _score").param("param1", 2).param("param2", 2))) + ).actionGet(); + + assertThat(response.getHits().totalHits(), equalTo(2l)); + logger.info("Hit[0] {} Explanation {}", response.getHits().getAt(0).id(), response.getHits().getAt(0).explanation()); + logger.info("Hit[1] {} Explanation {}", response.getHits().getAt(1).id(), response.getHits().getAt(1).explanation()); + assertThat(response.getHits().getAt(0).id(), equalTo("1")); + assertThat(response.getHits().getAt(0).score(), equalTo(4f)); // _score is always 1 + assertThat(response.getHits().getAt(1).score(), equalTo(4f)); // _score is always 1 } @Test