From b6a83fd8b245b8ac40edb226e95fa68c5d61e01a Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Wed, 17 Oct 2012 10:22:38 +0200 Subject: [PATCH] #2332 support CustomScoreQuery highlighting and expose QueryBuilder on CustomScoreQuery via Java API --- .../query/ConstantScoreQueryBuilder.java | 24 ++++- .../index/query/QueryBuilders.java | 10 ++ .../search/highlight/CustomQueryScorer.java | 101 ++++++++++++++++++ .../search/highlight/HighlightPhase.java | 27 +---- .../highlight/HighlighterSearchTests.java | 25 ++++- 5 files changed, 158 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/elasticsearch/search/highlight/CustomQueryScorer.java diff --git a/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java b/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java index d4bf85da22b..0c1bb6c3482 100644 --- a/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java +++ b/src/main/java/org/elasticsearch/index/query/ConstantScoreQueryBuilder.java @@ -32,8 +32,10 @@ import java.io.IOException; public class ConstantScoreQueryBuilder extends BaseQueryBuilder implements BoostableQueryBuilder { private final FilterBuilder filterBuilder; + private final QueryBuilder queryBuilder; private float boost = -1; + /** * A query that wraps a filter and simply returns a constant score equal to the @@ -43,7 +45,18 @@ public class ConstantScoreQueryBuilder extends BaseQueryBuilder implements Boost */ public ConstantScoreQueryBuilder(FilterBuilder filterBuilder) { this.filterBuilder = filterBuilder; + this.queryBuilder = null; } + /** + * A query that wraps a query and simply returns a constant score equal to the + * query boost for every document in the query. + * + * @param queryBuilder The query to wrap in a constant score query + */ + public ConstantScoreQueryBuilder(QueryBuilder queryBuilder) { + this.filterBuilder = null; + this.queryBuilder = queryBuilder; + } /** * Sets the boost for this query. Documents matching this query will (in addition to the normal @@ -57,8 +70,15 @@ public class ConstantScoreQueryBuilder extends BaseQueryBuilder implements Boost @Override protected void doXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(ConstantScoreQueryParser.NAME); - builder.field("filter"); - filterBuilder.toXContent(builder, params); + if (queryBuilder != null) { + assert filterBuilder == null; + builder.field("query"); + queryBuilder.toXContent(builder, params); + } else { + builder.field("filter"); + filterBuilder.toXContent(builder, params); + } + if (boost != -1) { builder.field("boost", boost); } diff --git a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java index 7711d8c9d9f..9fbdd92eb83 100644 --- a/src/main/java/org/elasticsearch/index/query/QueryBuilders.java +++ b/src/main/java/org/elasticsearch/index/query/QueryBuilders.java @@ -454,6 +454,16 @@ public abstract class QueryBuilders { public static ConstantScoreQueryBuilder constantScoreQuery(FilterBuilder filterBuilder) { return new ConstantScoreQueryBuilder(filterBuilder); } + + /** + * A query that wraps another query and simply returns a constant score equal to the + * query boost for every document in the query. + * + * @param queryBuilder The query to wrap in a constant score query + */ + public static ConstantScoreQueryBuilder constantScoreQuery(QueryBuilder queryBuilder) { + return new ConstantScoreQueryBuilder(queryBuilder); + } /** * A query that simply applies the boost fact to the wrapped query (multiplies it). diff --git a/src/main/java/org/elasticsearch/search/highlight/CustomQueryScorer.java b/src/main/java/org/elasticsearch/search/highlight/CustomQueryScorer.java new file mode 100644 index 00000000000..5d3e2a8c23b --- /dev/null +++ b/src/main/java/org/elasticsearch/search/highlight/CustomQueryScorer.java @@ -0,0 +1,101 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.search.highlight; + +import java.io.IOException; +import java.util.Map; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.ConstantScoreQuery; +import org.apache.lucene.search.FilteredQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.highlight.QueryScorer; +import org.apache.lucene.search.highlight.WeightedSpanTerm; +import org.apache.lucene.search.highlight.WeightedSpanTermExtractor; +import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery; +import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; + +public final class CustomQueryScorer extends QueryScorer { + + public CustomQueryScorer(Query query, IndexReader reader, String field, + String defaultField) { + super(query, reader, field, defaultField); + } + + public CustomQueryScorer(Query query, IndexReader reader, String field) { + super(query, reader, field); + } + + public CustomQueryScorer(Query query, String field, String defaultField) { + super(query, field, defaultField); + } + + public CustomQueryScorer(Query query, String field) { + super(query, field); + } + + public CustomQueryScorer(Query query) { + super(query); + } + + public CustomQueryScorer(WeightedSpanTerm[] weightedTerms) { + super(weightedTerms); + } + + @Override + protected WeightedSpanTermExtractor newTermExtractor(String defaultField) { + return defaultField == null ? new CustomWeightedSpanTermExtractor() + : new CustomWeightedSpanTermExtractor(defaultField); + } + + private static class CustomWeightedSpanTermExtractor extends WeightedSpanTermExtractor { + + public CustomWeightedSpanTermExtractor() { + super(); + } + + public CustomWeightedSpanTermExtractor(String defaultField) { + super(defaultField); + } + + @Override + protected void extractUnknownQuery(Query query, + Map terms) throws IOException { + if (query instanceof FunctionScoreQuery) { + query = ((FunctionScoreQuery) query).getSubQuery(); + extract(query, terms); + } else if (query instanceof FiltersFunctionScoreQuery) { + query = ((FiltersFunctionScoreQuery) query).getSubQuery(); + extract(query, terms); + } else if (query instanceof ConstantScoreQuery) { + ConstantScoreQuery q = (ConstantScoreQuery) query; + if (q.getQuery() != null) { + query = q.getQuery(); + extract(query, terms); + } + } else if (query instanceof FilteredQuery) { + query = ((FilteredQuery) query).getQuery(); + extract(query, terms); + } + } + + } + +} diff --git a/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java b/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java index 082f9b9059e..932e8a398b6 100644 --- a/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java +++ b/src/main/java/org/elasticsearch/search/highlight/HighlightPhase.java @@ -138,33 +138,8 @@ public class HighlightPhase extends AbstractComponent implements FetchSubPhase { // Don't use the context.query() since it might be rewritten, and we need to pass the non rewritten queries to // let the highlighter handle MultiTerm ones - // QueryScorer uses WeightedSpanTermExtractor to extract terms, but we can't really plug into - // it, so, we hack here (and really only support top level queries) Query query = context.parsedQuery().query(); - while (true) { - boolean extracted = false; - if (query instanceof FunctionScoreQuery) { - query = ((FunctionScoreQuery) query).getSubQuery(); - extracted = true; - } else if (query instanceof FiltersFunctionScoreQuery) { - query = ((FiltersFunctionScoreQuery) query).getSubQuery(); - extracted = true; - } else if (query instanceof ConstantScoreQuery) { - ConstantScoreQuery q = (ConstantScoreQuery) query; - if (q.getQuery() != null) { - query = q.getQuery(); - extracted = true; - } - } else if (query instanceof FilteredQuery) { - query = ((FilteredQuery) query).getQuery(); - extracted = true; - } - if (!extracted) { - break; - } - } - - QueryScorer queryScorer = new QueryScorer(query, field.requireFieldMatch() ? mapper.names().indexName() : null); + QueryScorer queryScorer = new CustomQueryScorer(query, field.requireFieldMatch() ? mapper.names().indexName() : null); queryScorer.setExpandMultiTermQuery(true); Fragmenter fragmenter; if (field.numberOfFragments() == 0) { diff --git a/src/test/java/org/elasticsearch/test/integration/search/highlight/HighlighterSearchTests.java b/src/test/java/org/elasticsearch/test/integration/search/highlight/HighlighterSearchTests.java index 1dd6e11bfdf..11c69808711 100644 --- a/src/test/java/org/elasticsearch/test/integration/search/highlight/HighlighterSearchTests.java +++ b/src/test/java/org/elasticsearch/test/integration/search/highlight/HighlighterSearchTests.java @@ -312,8 +312,31 @@ public class HighlighterSearchTests extends AbstractNodesTests { assertThat(searchResponse.hits().totalHits(), equalTo(1l)); assertThat(searchResponse.hits().getAt(0).highlightFields().get("field2").fragments()[0].string(), equalTo("The quick brown fox jumps over the lazy dog")); - } + + logger.info("--> searching on _all with constant score, highlighting on field2"); + source = searchSource() + .query(constantScoreQuery(prefixQuery("_all", "qui"))) + .from(0).size(60).explain(true) + .highlight(highlight().field("field2").order("score").preTags("").postTags("")); + searchResponse = client.search(searchRequest("test").source(source).searchType(QUERY_THEN_FETCH).scroll(timeValueMinutes(10))).actionGet(); + assertThat("Failures " + Arrays.toString(searchResponse.shardFailures()), searchResponse.shardFailures().length, equalTo(0)); + assertThat(searchResponse.hits().totalHits(), equalTo(1l)); + + assertThat(searchResponse.hits().getAt(0).highlightFields().get("field2").fragments()[0].string(), equalTo("The quick brown fox jumps over the lazy dog")); + + logger.info("--> searching on _all with constant score, highlighting on field2"); + source = searchSource() + .query(boolQuery().should(constantScoreQuery(prefixQuery("_all", "qui")))) + .from(0).size(60).explain(true) + .highlight(highlight().field("field2").order("score").preTags("").postTags("")); + + searchResponse = client.search(searchRequest("test").source(source).searchType(QUERY_THEN_FETCH).scroll(timeValueMinutes(10))).actionGet(); + assertThat("Failures " + Arrays.toString(searchResponse.shardFailures()), searchResponse.shardFailures().length, equalTo(0)); + assertThat(searchResponse.hits().totalHits(), equalTo(1l)); + assertThat(searchResponse.hits().getAt(0).highlightFields().get("field2").fragments()[0].string(), equalTo("The quick brown fox jumps over the lazy dog")); + } + @Test public void testFastVectorHighlighter() throws Exception { try {