Added support for external query in postings highlighter

It is now possible to highlight an external query using the postings highlighter, relates to #3630

Closes #4121
This commit is contained in:
Luca Cavanna 2013-11-07 20:04:17 +01:00
parent 6f286c3382
commit 4aa59aff00
6 changed files with 74 additions and 49 deletions

View File

@ -80,13 +80,13 @@ public class FastVectorHighlighter implements Highlighter {
if (field.requireFieldMatch()) { if (field.requireFieldMatch()) {
if (cache.fieldMatchFieldQuery == null) { if (cache.fieldMatchFieldQuery == null) {
// we use top level reader to rewrite the query against all readers, with use caching it across hits (and across readers...) // we use top level reader to rewrite the query against all readers, with use caching it across hits (and across readers...)
cache.fieldMatchFieldQuery = new CustomFieldQuery(highlighterContext.highlightQuery, hitContext.topLevelReader(), true, field.requireFieldMatch()); cache.fieldMatchFieldQuery = new CustomFieldQuery(highlighterContext.query.originalQuery(), hitContext.topLevelReader(), true, field.requireFieldMatch());
} }
fieldQuery = cache.fieldMatchFieldQuery; fieldQuery = cache.fieldMatchFieldQuery;
} else { } else {
if (cache.noFieldMatchFieldQuery == null) { if (cache.noFieldMatchFieldQuery == null) {
// we use top level reader to rewrite the query against all readers, with use caching it across hits (and across readers...) // we use top level reader to rewrite the query against all readers, with use caching it across hits (and across readers...)
cache.noFieldMatchFieldQuery = new CustomFieldQuery(highlighterContext.highlightQuery, hitContext.topLevelReader(), true, field.requireFieldMatch()); cache.noFieldMatchFieldQuery = new CustomFieldQuery(highlighterContext.query.originalQuery(), hitContext.topLevelReader(), true, field.requireFieldMatch());
} }
fieldQuery = cache.noFieldMatchFieldQuery; fieldQuery = cache.noFieldMatchFieldQuery;
} }

View File

@ -21,7 +21,6 @@ package org.elasticsearch.search.highlight;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import org.apache.lucene.search.Query;
import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.FieldInfo;
import org.elasticsearch.ElasticSearchException; import org.elasticsearch.ElasticSearchException;
import org.elasticsearch.ElasticSearchIllegalArgumentException; import org.elasticsearch.ElasticSearchIllegalArgumentException;
@ -108,11 +107,11 @@ public class HighlightPhase extends AbstractComponent implements FetchSubPhase {
throw new ElasticSearchIllegalArgumentException("unknown highlighter type [" + field.highlighterType() + "] for the field [" + fieldName + "]"); throw new ElasticSearchIllegalArgumentException("unknown highlighter type [" + field.highlighterType() + "] for the field [" + fieldName + "]");
} }
Query highlightQuery = field.highlightQuery(); HighlighterContext.HighlightQuery highlightQuery;
if (highlightQuery == null) { if (field.highlightQuery() == null) {
// Don't use the context.query() since it might be rewritten, and we need to pass the non rewritten queries to highlightQuery = new HighlighterContext.HighlightQuery(context.parsedQuery().query(), context.query(), context.queryRewritten());
// let the highlighter handle MultiTerm ones } else {
highlightQuery = context.parsedQuery().query(); highlightQuery = new HighlighterContext.HighlightQuery(field.highlightQuery(), field.highlightQuery(), false);
} }
HighlighterContext highlighterContext = new HighlighterContext(fieldName, field, fieldMapper, context, hitContext, highlightQuery); HighlighterContext highlighterContext = new HighlighterContext(fieldName, field, fieldMapper, context, hitContext, highlightQuery);
HighlightField highlightField = highlighter.highlight(highlighterContext); HighlightField highlightField = highlighter.highlight(highlighterContext);

View File

@ -33,15 +33,39 @@ public class HighlighterContext {
public final FieldMapper<?> mapper; public final FieldMapper<?> mapper;
public final SearchContext context; public final SearchContext context;
public final FetchSubPhase.HitContext hitContext; public final FetchSubPhase.HitContext hitContext;
public final Query highlightQuery; public final HighlightQuery query;
public HighlighterContext(String fieldName, SearchContextHighlight.Field field, FieldMapper<?> mapper, SearchContext context, public HighlighterContext(String fieldName, SearchContextHighlight.Field field, FieldMapper<?> mapper, SearchContext context,
FetchSubPhase.HitContext hitContext, Query highlightQuery) { FetchSubPhase.HitContext hitContext, HighlightQuery query) {
this.fieldName = fieldName; this.fieldName = fieldName;
this.field = field; this.field = field;
this.mapper = mapper; this.mapper = mapper;
this.context = context; this.context = context;
this.hitContext = hitContext; this.hitContext = hitContext;
this.highlightQuery = highlightQuery; this.query = query;
}
static class HighlightQuery {
private final Query originalQuery;
private final Query query;
private final boolean queryRewritten;
HighlightQuery(Query originalQuery, Query query, boolean queryRewritten) {
this.originalQuery = originalQuery;
this.query = query;
this.queryRewritten = queryRewritten;
}
public boolean queryRewritten() {
return queryRewritten;
}
public Query originalQuery() {
return originalQuery;
}
public Query query() {
return query;
}
} }
} }

View File

@ -68,7 +68,7 @@ public class PlainHighlighter implements Highlighter {
org.apache.lucene.search.highlight.Highlighter entry = cache.get(mapper); org.apache.lucene.search.highlight.Highlighter entry = cache.get(mapper);
if (entry == null) { if (entry == null) {
Query query = highlighterContext.highlightQuery; Query query = highlighterContext.query.originalQuery();
QueryScorer queryScorer = new CustomQueryScorer(query, field.requireFieldMatch() ? mapper.names().indexName() : null); QueryScorer queryScorer = new CustomQueryScorer(query, field.requireFieldMatch() ? mapper.names().indexName() : null);
queryScorer.setExpandMultiTermQuery(true); queryScorer.setExpandMultiTermQuery(true);
Fragmenter fragmenter; Fragmenter fragmenter;

View File

@ -71,7 +71,7 @@ public class PostingsHighlighter implements Highlighter {
//get the non rewritten query and rewrite it //get the non rewritten query and rewrite it
Query query; Query query;
try { try {
query = rewrite(context, hitContext.topLevelReader()); query = rewrite(highlighterContext, hitContext.topLevelReader());
} catch (IOException e) { } catch (IOException e) {
throw new FetchPhaseExecutionException(context, "Failed to highlight field [" + highlighterContext.fieldName + "]", e); throw new FetchPhaseExecutionException(context, "Failed to highlight field [" + highlighterContext.fieldName + "]", e);
} }
@ -146,11 +146,11 @@ public class PostingsHighlighter implements Highlighter {
return null; return null;
} }
private static Query rewrite(SearchContext searchContext, IndexReader reader) throws IOException { private static Query rewrite(HighlighterContext highlighterContext, IndexReader reader) throws IOException {
//rewrite is expensive: if the query was already rewritten we try not to rewrite //rewrite is expensive: if the query was already rewritten we try not to rewrite
boolean mustRewrite = !searchContext.queryRewritten(); boolean mustRewrite = !highlighterContext.query.queryRewritten();
Query original = searchContext.parsedQuery().query(); Query original = highlighterContext.query.originalQuery();
MultiTermQuery originalMultiTermQuery = null; MultiTermQuery originalMultiTermQuery = null;
MultiTermQuery.RewriteMethod originalRewriteMethod = null; MultiTermQuery.RewriteMethod originalRewriteMethod = null;
@ -166,7 +166,7 @@ public class PostingsHighlighter implements Highlighter {
if (!mustRewrite) { if (!mustRewrite) {
//return the rewritten query //return the rewritten query
return searchContext.query(); return highlighterContext.query.query();
} }
Query query = original; Query query = original;

View File

@ -1301,55 +1301,57 @@ public class HighlighterSearchTests extends AbstractIntegrationTest {
@Test @Test
public void testHighlightUsesHighlightQuery() throws IOException { public void testHighlightUsesHighlightQuery() throws IOException {
assertAcked(prepareCreate("test") assertAcked(prepareCreate("test")
.addMapping("type1", "text", "type=string,store=yes,term_vector=with_positions_offsets")); .addMapping("type1", "text", "type=string," + randomStoreField() + "term_vector=with_positions_offsets,index_options=offsets"));
ensureGreen(); ensureGreen();
index("test", "type1", "1", "text", "some stuff stuff stuff stuff stuff to highlight against the stuff phrase"); index("test", "type1", "1", "text", "Testing the highlight query feature");
refresh(); refresh();
// Make sure the fvh doesn't highlight in the same way as we're going to do with a scoreQuery because HighlightBuilder.Field field = new HighlightBuilder.Field("text");
// that would invalidate the test results.
Matcher<String> highlightedMatcher = anyOf( SearchRequestBuilder search = client().prepareSearch("test").setQuery(QueryBuilders.matchQuery("text", "testing"))
containsString("<em>stuff phrase</em>"), //t FHV normally does this
containsString("<em>stuff</em> <em>phrase</em>")); // Plain normally does this
HighlightBuilder.Field field = new HighlightBuilder.Field("text")
.fragmentSize(20)
.numOfFragments(1)
.highlighterType("fvh");
SearchRequestBuilder search = client().prepareSearch("test")
.setQuery(QueryBuilders.matchQuery("text", "stuff"))
.setHighlighterOrder("score")
.addHighlightedField(field); .addHighlightedField(field);
Matcher<String> searchQueryMatcher = equalTo("<em>Testing</em> the highlight query feature");
field.highlighterType("plain");
SearchResponse response = search.get(); SearchResponse response = search.get();
assertHighlight(response, 0, "text", 0, not(highlightedMatcher)); assertHighlight(response, 0, "text", 0, searchQueryMatcher);
field.highlighterType("fvh");
response = search.get();
assertHighlight(response, 0, "text", 0, searchQueryMatcher);
field.highlighterType("postings");
response = search.get();
assertHighlight(response, 0, "text", 0, searchQueryMatcher);
Matcher<String> hlQueryMatcher = equalTo("Testing the highlight <em>query</em> feature");
field.highlightQuery(matchQuery("text", "query"));
field.highlighterType("fvh");
response = search.get();
assertHighlight(response, 0, "text", 0, hlQueryMatcher);
// And do the same for the plain highlighter
field.highlighterType("plain"); field.highlighterType("plain");
response = search.get(); response = search.get();
assertHighlight(response, 0, "text", 0, not(highlightedMatcher)); assertHighlight(response, 0, "text", 0, hlQueryMatcher);
// Make sure the fvh takes the highlightQuery into account field.highlighterType("postings");
field.highlighterType("fvh").highlightQuery(matchPhraseQuery("text", "stuff phrase"));
response = search.get(); response = search.get();
assertHighlight(response, 0, "text", 0, highlightedMatcher); assertHighlight(response, 0, "text", 0, hlQueryMatcher);
// And do the same for the plain highlighter // Make sure the the highlightQuery is taken into account when it is set on the highlight context instead of the field
field.highlighterType("plain"); search.setHighlighterQuery(matchQuery("text", "query"));
response = search.get();
assertHighlight(response, 0, "text", 0, highlightedMatcher);
// Note that the plain highlighter doesn't join the highlighted elements for us
// Make sure the fvh takes the highlightQuery into account when it is set on the highlight context instead of the field
search.setHighlighterQuery(matchPhraseQuery("text", "stuff phrase"));
field.highlighterType("fvh").highlightQuery(null); field.highlighterType("fvh").highlightQuery(null);
response = search.get(); response = search.get();
assertHighlight(response, 0, "text", 0, highlightedMatcher); assertHighlight(response, 0, "text", 0, hlQueryMatcher);
// And do the same for the plain highlighter
field.highlighterType("plain"); field.highlighterType("plain");
response = search.get(); response = search.get();
assertHighlight(response, 0, "text", 0, highlightedMatcher); assertHighlight(response, 0, "text", 0, hlQueryMatcher);
// Note that the plain highlighter doesn't join the highlighted elements for us
field.highlighterType("postings");
response = search.get();
assertHighlight(response, 0, "text", 0, hlQueryMatcher);
} }
private static String randomStoreField() { private static String randomStoreField() {