Highlighting can return excerpt with no highlights
You can configure the highlighting api to return an excerpt of a field even if there wasn't a match on the field. The FVH makes excerpts from the beginning of the string to the first boundary character after the requested length or the boundary_max_scan, whichever comes first. The Plain highlighter makes excerpts from the beginning of the string to the end of the last token before the requested length. Closes #1171
This commit is contained in:
parent
919720ab4f
commit
14a709f563
|
@ -164,6 +164,28 @@ is required. Note that `fragment_size` is ignored in this case.
|
||||||
When using `fast-vector-highlighter` one can use `fragment_offset`
|
When using `fast-vector-highlighter` one can use `fragment_offset`
|
||||||
parameter to control the margin to start highlighting from.
|
parameter to control the margin to start highlighting from.
|
||||||
|
|
||||||
|
coming[0.90.6]
|
||||||
|
It is also possible to ask Elasticsearch to return a fragment from the
|
||||||
|
beginning of the field in the case where there are no matches by setting
|
||||||
|
`no_match_size` to something greater than 0. The default is 0.
|
||||||
|
|
||||||
|
[source,js]
|
||||||
|
--------------------------------------------------
|
||||||
|
{
|
||||||
|
"query" : {...},
|
||||||
|
"highlight" : {
|
||||||
|
"fields" : {
|
||||||
|
"content" : {
|
||||||
|
"fragment_size" : 150,
|
||||||
|
"number_of_fragments" : 3,
|
||||||
|
"no_match_size": 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
==== Highlight query
|
==== Highlight query
|
||||||
|
|
||||||
It is also possible to highlight against a query other than the search
|
It is also possible to highlight against a query other than the search
|
||||||
|
|
|
@ -692,6 +692,17 @@ public class SearchRequestBuilder extends ActionRequestBuilder<SearchRequest, Se
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size of the fragment to return from the beginning of the field if there are no matches to
|
||||||
|
* highlight and the field doesn't also define noMatchSize.
|
||||||
|
* @param noMatchSize integer to set or null to leave out of request. default is null.
|
||||||
|
* @return this builder for chaining
|
||||||
|
*/
|
||||||
|
public SearchRequestBuilder setHighlighterNoMatchSize(Integer noMatchSize) {
|
||||||
|
highlightBuilder().noMatchSize(noMatchSize);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SearchRequestBuilder setHighlighterOptions(Map<String, Object> options) {
|
public SearchRequestBuilder setHighlighterOptions(Map<String, Object> options) {
|
||||||
highlightBuilder().options(options);
|
highlightBuilder().options(options);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.apache.lucene.search.highlight.DefaultEncoder;
|
||||||
import org.apache.lucene.search.highlight.Encoder;
|
import org.apache.lucene.search.highlight.Encoder;
|
||||||
import org.apache.lucene.search.highlight.SimpleHTMLEncoder;
|
import org.apache.lucene.search.highlight.SimpleHTMLEncoder;
|
||||||
import org.apache.lucene.search.vectorhighlight.*;
|
import org.apache.lucene.search.vectorhighlight.*;
|
||||||
|
import org.apache.lucene.search.vectorhighlight.FieldPhraseList.WeightedPhraseInfo;
|
||||||
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
@ -35,6 +36,7 @@ import org.elasticsearch.search.highlight.vectorhighlight.SourceScoreOrderFragme
|
||||||
import org.elasticsearch.search.highlight.vectorhighlight.SourceSimpleFragmentsBuilder;
|
import org.elasticsearch.search.highlight.vectorhighlight.SourceSimpleFragmentsBuilder;
|
||||||
import org.elasticsearch.search.internal.SearchContext;
|
import org.elasticsearch.search.internal.SearchContext;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -150,11 +152,21 @@ public class FastVectorHighlighter implements Highlighter {
|
||||||
if (fragments != null && fragments.length > 0) {
|
if (fragments != null && fragments.length > 0) {
|
||||||
return new HighlightField(field.field(), StringText.convertFromStringArray(fragments));
|
return new HighlightField(field.field(), StringText.convertFromStringArray(fragments));
|
||||||
}
|
}
|
||||||
|
int noMatchSize = highlighterContext.field.noMatchSize();
|
||||||
|
if (noMatchSize > 0) {
|
||||||
|
// Essentially we just request that a fragment is built from 0 to noMatchSize using the normal fragmentsBuilder
|
||||||
|
FieldFragList fieldFragList = new SimpleFieldFragList(-1 /*ignored*/);
|
||||||
|
fieldFragList.add(0, noMatchSize, Collections.<WeightedPhraseInfo>emptyList());
|
||||||
|
fragments = entry.fragmentsBuilder.createFragments(hitContext.reader(), hitContext.docId(), mapper.names().indexName(),
|
||||||
|
fieldFragList, 1, field.preTags(), field.postTags(), encoder);
|
||||||
|
if (fragments != null && fragments.length > 0) {
|
||||||
|
return new HighlightField(field.field(), StringText.convertFromStringArray(fragments));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new FetchPhaseExecutionException(context, "Failed to highlight field [" + highlighterContext.fieldName + "]", e);
|
throw new FetchPhaseExecutionException(context, "Failed to highlight field [" + highlighterContext.fieldName + "]", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MapperHighlightEntry {
|
private class MapperHighlightEntry {
|
||||||
|
|
|
@ -56,6 +56,8 @@ public class HighlightBuilder implements ToXContent {
|
||||||
|
|
||||||
private QueryBuilder highlightQuery;
|
private QueryBuilder highlightQuery;
|
||||||
|
|
||||||
|
private Integer noMatchSize;
|
||||||
|
|
||||||
private Map<String, Object> options;
|
private Map<String, Object> options;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -212,6 +214,17 @@ public class HighlightBuilder implements ToXContent {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size of the fragment to return from the beginning of the field if there are no matches to
|
||||||
|
* highlight and the field doesn't also define noMatchSize.
|
||||||
|
* @param noMatchSize integer to set or null to leave out of request. default is null.
|
||||||
|
* @return this for chaining
|
||||||
|
*/
|
||||||
|
public HighlightBuilder noMatchSize(Integer noMatchSize) {
|
||||||
|
this.noMatchSize = noMatchSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to set custom options for custom highlighters.
|
* Allows to set custom options for custom highlighters.
|
||||||
*/
|
*/
|
||||||
|
@ -250,6 +263,9 @@ public class HighlightBuilder implements ToXContent {
|
||||||
if (highlightQuery != null) {
|
if (highlightQuery != null) {
|
||||||
builder.field("highlight_query", highlightQuery);
|
builder.field("highlight_query", highlightQuery);
|
||||||
}
|
}
|
||||||
|
if (noMatchSize != null) {
|
||||||
|
builder.field("no_match_size", noMatchSize);
|
||||||
|
}
|
||||||
if (options != null && options.size() > 0) {
|
if (options != null && options.size() > 0) {
|
||||||
builder.field("options", options);
|
builder.field("options", options);
|
||||||
}
|
}
|
||||||
|
@ -296,6 +312,9 @@ public class HighlightBuilder implements ToXContent {
|
||||||
if (field.highlightQuery != null) {
|
if (field.highlightQuery != null) {
|
||||||
builder.field("highlight_query", field.highlightQuery);
|
builder.field("highlight_query", field.highlightQuery);
|
||||||
}
|
}
|
||||||
|
if (field.noMatchSize != null) {
|
||||||
|
builder.field("no_match_size", field.noMatchSize);
|
||||||
|
}
|
||||||
if (field.options != null && field.options.size() > 0) {
|
if (field.options != null && field.options.size() > 0) {
|
||||||
builder.field("options", field.options);
|
builder.field("options", field.options);
|
||||||
}
|
}
|
||||||
|
@ -324,6 +343,7 @@ public class HighlightBuilder implements ToXContent {
|
||||||
String highlighterType;
|
String highlighterType;
|
||||||
String fragmenter;
|
String fragmenter;
|
||||||
QueryBuilder highlightQuery;
|
QueryBuilder highlightQuery;
|
||||||
|
Integer noMatchSize;
|
||||||
Map<String, Object> options;
|
Map<String, Object> options;
|
||||||
|
|
||||||
public Field(String name) {
|
public Field(String name) {
|
||||||
|
@ -426,6 +446,17 @@ public class HighlightBuilder implements ToXContent {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size of the fragment to return from the beginning of the field if there are no matches to
|
||||||
|
* highlight.
|
||||||
|
* @param noMatchSize integer to set or null to leave out of request. default is null.
|
||||||
|
* @return this for chaining
|
||||||
|
*/
|
||||||
|
public Field noMatchSize(Integer noMatchSize) {
|
||||||
|
this.noMatchSize = noMatchSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to set custom options for custom highlighters.
|
* Allows to set custom options for custom highlighters.
|
||||||
* This overrides global settings set by {@link HighlightBuilder#options(Map<String, Object>)}.
|
* This overrides global settings set by {@link HighlightBuilder#options(Map<String, Object>)}.
|
||||||
|
|
|
@ -82,6 +82,7 @@ public class HighlighterParseElement implements SearchParseElement {
|
||||||
String globalFragmenter = null;
|
String globalFragmenter = null;
|
||||||
Map<String, Object> globalOptions = null;
|
Map<String, Object> globalOptions = null;
|
||||||
Query globalHighlightQuery = null;
|
Query globalHighlightQuery = null;
|
||||||
|
int globalNoMatchSize = 0;
|
||||||
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
if (token == XContentParser.Token.FIELD_NAME) {
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
|
@ -131,6 +132,8 @@ public class HighlighterParseElement implements SearchParseElement {
|
||||||
globalHighlighterType = parser.text();
|
globalHighlighterType = parser.text();
|
||||||
} else if ("fragmenter".equals(topLevelFieldName)) {
|
} else if ("fragmenter".equals(topLevelFieldName)) {
|
||||||
globalFragmenter = parser.text();
|
globalFragmenter = parser.text();
|
||||||
|
} else if ("no_match_size".equals(topLevelFieldName) || "noMatchSize".equals(topLevelFieldName)) {
|
||||||
|
globalNoMatchSize = parser.intValue();
|
||||||
}
|
}
|
||||||
} else if (token == XContentParser.Token.START_OBJECT && "options".equals(topLevelFieldName)) {
|
} else if (token == XContentParser.Token.START_OBJECT && "options".equals(topLevelFieldName)) {
|
||||||
globalOptions = parser.map();
|
globalOptions = parser.map();
|
||||||
|
@ -186,6 +189,8 @@ public class HighlighterParseElement implements SearchParseElement {
|
||||||
field.highlighterType(parser.text());
|
field.highlighterType(parser.text());
|
||||||
} else if ("fragmenter".equals(fieldName)) {
|
} else if ("fragmenter".equals(fieldName)) {
|
||||||
field.fragmenter(parser.text());
|
field.fragmenter(parser.text());
|
||||||
|
} else if ("no_match_size".equals(fieldName) || "noMatchSize".equals(fieldName)) {
|
||||||
|
field.noMatchSize(parser.intValue());
|
||||||
}
|
}
|
||||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||||
if ("highlight_query".equals(fieldName) || "highlightQuery".equals(fieldName)) {
|
if ("highlight_query".equals(fieldName) || "highlightQuery".equals(fieldName)) {
|
||||||
|
@ -251,6 +256,9 @@ public class HighlighterParseElement implements SearchParseElement {
|
||||||
if (field.highlightQuery() == null && globalHighlightQuery != null) {
|
if (field.highlightQuery() == null && globalHighlightQuery != null) {
|
||||||
field.highlightQuery(globalHighlightQuery);
|
field.highlightQuery(globalHighlightQuery);
|
||||||
}
|
}
|
||||||
|
if (field.noMatchSize() == -1) {
|
||||||
|
field.noMatchSize(globalNoMatchSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.highlight(new SearchContextHighlight(fields));
|
context.highlight(new SearchContextHighlight(fields));
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.apache.lucene.search.highlight.*;
|
||||||
import org.apache.lucene.util.CollectionUtil;
|
import org.apache.lucene.util.CollectionUtil;
|
||||||
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
||||||
import org.elasticsearch.common.text.StringText;
|
import org.elasticsearch.common.text.StringText;
|
||||||
|
import org.elasticsearch.common.text.Text;
|
||||||
import org.elasticsearch.index.fieldvisitor.CustomFieldsVisitor;
|
import org.elasticsearch.index.fieldvisitor.CustomFieldsVisitor;
|
||||||
import org.elasticsearch.index.mapper.FieldMapper;
|
import org.elasticsearch.index.mapper.FieldMapper;
|
||||||
import org.elasticsearch.search.fetch.FetchPhaseExecutionException;
|
import org.elasticsearch.search.fetch.FetchPhaseExecutionException;
|
||||||
|
@ -37,6 +38,7 @@ import org.elasticsearch.search.fetch.FetchSubPhase;
|
||||||
import org.elasticsearch.search.internal.SearchContext;
|
import org.elasticsearch.search.internal.SearchContext;
|
||||||
import org.elasticsearch.search.lookup.SearchLookup;
|
import org.elasticsearch.search.lookup.SearchLookup;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -164,10 +166,51 @@ public class PlainHighlighter implements Highlighter {
|
||||||
if (fragments != null && fragments.length > 0) {
|
if (fragments != null && fragments.length > 0) {
|
||||||
return new HighlightField(highlighterContext.fieldName, StringText.convertFromStringArray(fragments));
|
return new HighlightField(highlighterContext.fieldName, StringText.convertFromStringArray(fragments));
|
||||||
}
|
}
|
||||||
|
int noMatchSize = highlighterContext.field.noMatchSize();
|
||||||
|
if (noMatchSize > 0 && textsToHighlight.size() >= 1) {
|
||||||
|
// Pull an excerpt from the beginning of the string but make sure to split the string on a term boundary.
|
||||||
|
String fieldContents = textsToHighlight.get(0).toString();
|
||||||
|
Analyzer analyzer = context.mapperService().documentMapper(hitContext.hit().type()).mappers().indexAnalyzer();
|
||||||
|
int end;
|
||||||
|
try {
|
||||||
|
end = findGoodEndForNoHighlightExcerpt(noMatchSize, analyzer.tokenStream(mapper.names().indexName(), fieldContents));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FetchPhaseExecutionException(context, "Failed to highlight field [" + highlighterContext.fieldName + "]", e);
|
||||||
|
}
|
||||||
|
if (end > 0) {
|
||||||
|
return new HighlightField(highlighterContext.fieldName, new Text[] { new StringText(fieldContents.substring(0, end)) });
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int findGoodEndForNoHighlightExcerpt(int noMatchSize, TokenStream tokenStream) throws IOException {
|
||||||
|
try {
|
||||||
|
if (!tokenStream.hasAttribute(OffsetAttribute.class)) {
|
||||||
|
// Can't split on term boundaries without offsets
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int end = -1;
|
||||||
|
tokenStream.reset();
|
||||||
|
while (tokenStream.incrementToken()) {
|
||||||
|
OffsetAttribute attr = tokenStream.getAttribute(OffsetAttribute.class);
|
||||||
|
if (attr.endOffset() >= noMatchSize) {
|
||||||
|
// Jump to the end of this token if it wouldn't put us past the boundary
|
||||||
|
if (attr.endOffset() == noMatchSize) {
|
||||||
|
end = noMatchSize;
|
||||||
|
}
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
end = attr.endOffset();
|
||||||
|
}
|
||||||
|
// We've exhausted the token stream so we should just highlight everything.
|
||||||
|
return end;
|
||||||
|
} finally {
|
||||||
|
tokenStream.end();
|
||||||
|
tokenStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class Encoders {
|
private static class Encoders {
|
||||||
public static Encoder DEFAULT = new DefaultEncoder();
|
public static Encoder DEFAULT = new DefaultEncoder();
|
||||||
public static Encoder HTML = new SimpleHTMLEncoder();
|
public static Encoder HTML = new SimpleHTMLEncoder();
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class SearchContextHighlight {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Field {
|
public static class Field {
|
||||||
|
// Fields that default to null or -1 are often set to their real default in HighlighterParseElement#parse
|
||||||
private final String field;
|
private final String field;
|
||||||
|
|
||||||
private int fragmentCharSize = -1;
|
private int fragmentCharSize = -1;
|
||||||
|
@ -66,10 +66,13 @@ public class SearchContextHighlight {
|
||||||
private String fragmenter;
|
private String fragmenter;
|
||||||
|
|
||||||
private int boundaryMaxScan = -1;
|
private int boundaryMaxScan = -1;
|
||||||
|
|
||||||
private Character[] boundaryChars = null;
|
private Character[] boundaryChars = null;
|
||||||
|
|
||||||
private Query highlightQuery;
|
private Query highlightQuery;
|
||||||
|
|
||||||
|
private int noMatchSize = -1;
|
||||||
|
|
||||||
private Map<String, Object> options;
|
private Map<String, Object> options;
|
||||||
|
|
||||||
public Field(String field) {
|
public Field(String field) {
|
||||||
|
@ -192,6 +195,14 @@ public class SearchContextHighlight {
|
||||||
this.highlightQuery = highlightQuery;
|
this.highlightQuery = highlightQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int noMatchSize() {
|
||||||
|
return noMatchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void noMatchSize(int noMatchSize) {
|
||||||
|
this.noMatchSize = noMatchSize;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, Object> options() {
|
public Map<String, Object> options() {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,7 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||||
import static org.elasticsearch.index.query.QueryBuilders.*;
|
import static org.elasticsearch.index.query.QueryBuilders.*;
|
||||||
import static org.elasticsearch.search.builder.SearchSourceBuilder.highlight;
|
import static org.elasticsearch.search.builder.SearchSourceBuilder.highlight;
|
||||||
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
|
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHighlight;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1602,17 +1601,8 @@ public class HighlighterSearchTests extends AbstractIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testHighlightUsesHighlightQuery() throws IOException {
|
public void testHighlightUsesHighlightQuery() throws IOException {
|
||||||
prepareCreate("test")
|
prepareCreate("test")
|
||||||
.addMapping("type1", jsonBuilder().startObject()
|
.addMapping("type1", "text", "type=string,store=yes,term_vector=with_positions_offsets")
|
||||||
.startObject("type")
|
.get();
|
||||||
.startObject("properties")
|
|
||||||
.startObject("text")
|
|
||||||
.field("type", "string")
|
|
||||||
.field("store", "yes")
|
|
||||||
.field("term_vector", "with_positions_offsets")
|
|
||||||
.endObject()
|
|
||||||
.endObject()
|
|
||||||
.endObject()
|
|
||||||
.endObject()).get();
|
|
||||||
ensureGreen();
|
ensureGreen();
|
||||||
|
|
||||||
index("test", "type1", "1", "text", "some stuff stuff stuff stuff stuff to highlight against the stuff phrase");
|
index("test", "type1", "1", "text", "some stuff stuff stuff stuff stuff to highlight against the stuff phrase");
|
||||||
|
@ -1621,7 +1611,7 @@ public class HighlighterSearchTests extends AbstractIntegrationTest {
|
||||||
// Make sure the fvh doesn't highlight in the same way as we're going to do with a scoreQuery because
|
// Make sure the fvh doesn't highlight in the same way as we're going to do with a scoreQuery because
|
||||||
// that would invalidate the test results.
|
// that would invalidate the test results.
|
||||||
Matcher<String> highlightedMatcher = anyOf(
|
Matcher<String> highlightedMatcher = anyOf(
|
||||||
containsString("<em>stuff phrase</em>"), // FHV normally does this
|
containsString("<em>stuff phrase</em>"), //t FHV normally does this
|
||||||
containsString("<em>stuff</em> <em>phrase</em>")); // Plain normally does this
|
containsString("<em>stuff</em> <em>phrase</em>")); // Plain normally does this
|
||||||
HighlightBuilder.Field field = new HighlightBuilder.Field("text")
|
HighlightBuilder.Field field = new HighlightBuilder.Field("text")
|
||||||
.fragmentSize(20)
|
.fragmentSize(20)
|
||||||
|
@ -1662,4 +1652,181 @@ public class HighlighterSearchTests extends AbstractIntegrationTest {
|
||||||
assertHighlight(response, 0, "text", 0, highlightedMatcher);
|
assertHighlight(response, 0, "text", 0, highlightedMatcher);
|
||||||
// Note that the plain highlighter doesn't join the highlighted elements for us
|
// Note that the plain highlighter doesn't join the highlighted elements for us
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testHighlightNoMatchSize() throws IOException {
|
||||||
|
prepareCreate("test")
|
||||||
|
.addMapping("type1", "text", "type=string,store=yes,term_vector=with_positions_offsets")
|
||||||
|
.get();
|
||||||
|
ensureGreen();
|
||||||
|
|
||||||
|
String text = "I am pretty long so some of me should get cut off";
|
||||||
|
index("test", "type1", "1", "text", text);
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
// When you don't set noMatchSize you don't get any results if there isn't anything to highlight.
|
||||||
|
HighlightBuilder.Field field = new HighlightBuilder.Field("text")
|
||||||
|
.fragmentSize(21)
|
||||||
|
.numOfFragments(1)
|
||||||
|
.highlighterType("plain");
|
||||||
|
SearchResponse response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNotHighlighted(response, 0, "text");
|
||||||
|
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNotHighlighted(response, 0, "text");
|
||||||
|
|
||||||
|
// When noMatchSize is set to 0 you also shouldn't get any
|
||||||
|
field.highlighterType("plain").noMatchSize(0);
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNotHighlighted(response, 0, "text");
|
||||||
|
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNotHighlighted(response, 0, "text");
|
||||||
|
|
||||||
|
// When noMatchSize is between 0 and the size of the string
|
||||||
|
field.highlighterType("plain").noMatchSize(21);
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo("I am pretty long so"));
|
||||||
|
|
||||||
|
// The FVH also works but the fragment is longer than the plain highlighter because of boundary_max_scan
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo("I am pretty long so some"));
|
||||||
|
|
||||||
|
// We can also ask for a fragment longer than the input string and get the whole string
|
||||||
|
field.highlighterType("plain").noMatchSize(text.length() * 2);
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo(text));
|
||||||
|
|
||||||
|
// Same for the fvh
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo(text));
|
||||||
|
|
||||||
|
// We can also ask for a fragment exactly the size of the input field and get the whole field
|
||||||
|
field.highlighterType("plain").noMatchSize(text.length());
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo(text));
|
||||||
|
|
||||||
|
// Same for the fvh
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo(text));
|
||||||
|
|
||||||
|
// You can set noMatchSize globally in the highlighter as well
|
||||||
|
field.highlighterType("plain").noMatchSize(null);
|
||||||
|
response = client().prepareSearch("test").setHighlighterNoMatchSize(21).addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo("I am pretty long so"));
|
||||||
|
|
||||||
|
// Same for the fvh
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").setHighlighterNoMatchSize(21).addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo("I am pretty long so some"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHighlightNoMatchSizeWithMultivaluedFields() throws IOException {
|
||||||
|
prepareCreate("test")
|
||||||
|
.addMapping("type1", jsonBuilder()
|
||||||
|
.startObject()
|
||||||
|
.startObject("type1")
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("text")
|
||||||
|
.field("type", "string")
|
||||||
|
.field("store", "yes")
|
||||||
|
.field("term_vector", "with_positions_offsets")
|
||||||
|
.endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject())
|
||||||
|
.get();
|
||||||
|
ensureGreen();
|
||||||
|
|
||||||
|
String text1 = "I am pretty long so some of me should get cut off";
|
||||||
|
String text2 = "I am short";
|
||||||
|
index("test", "type1", "1", "text", new String[] {text1, text2});
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
// The no match fragment should come from the first field of a multi-valued field
|
||||||
|
HighlightBuilder.Field field = new HighlightBuilder.Field("text")
|
||||||
|
.fragmentSize(21)
|
||||||
|
.numOfFragments(1)
|
||||||
|
.highlighterType("plain")
|
||||||
|
.noMatchSize(21);
|
||||||
|
SearchResponse response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo("I am pretty long so"));
|
||||||
|
|
||||||
|
// And the fvh should work as well
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertHighlight(response, 0, "text", 0, equalTo("I am pretty long so some"));
|
||||||
|
|
||||||
|
// And noMatchSize returns nothing when the first entry is empty string!
|
||||||
|
assert(!client().prepareDelete("test", "type1", "1").get().isNotFound());
|
||||||
|
index("test", "type1", "1", "text", new String[] {"", text2});
|
||||||
|
refresh();
|
||||||
|
field.highlighterType("plain");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNoFailures(response);
|
||||||
|
assertThat("not enough hits", response.getHits().hits().length, greaterThan(0));
|
||||||
|
assertThat(response.getHits().hits()[0].getHighlightFields(), not(hasKey("text")));
|
||||||
|
|
||||||
|
// And the fvh should work as well
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNoFailures(response);
|
||||||
|
assertThat("not enough hits", response.getHits().hits().length, greaterThan(0));
|
||||||
|
assertThat(response.getHits().hits()[0].getHighlightFields(), not(hasKey("text")));
|
||||||
|
|
||||||
|
// But if the field was actually empty then you should get no highlighting field
|
||||||
|
assert(!client().prepareDelete("test", "type1", "1").get().isNotFound());
|
||||||
|
index("test", "type1", "1", "text", new String[] {});
|
||||||
|
refresh();
|
||||||
|
field.highlighterType("plain");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNoFailures(response);
|
||||||
|
assertThat("not enough hits", response.getHits().hits().length, greaterThan(0));
|
||||||
|
assertThat(response.getHits().hits()[0].getHighlightFields(), not(hasKey("text")));
|
||||||
|
|
||||||
|
// And the fvh should work as well
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNoFailures(response);
|
||||||
|
assertThat("not enough hits", response.getHits().hits().length, greaterThan(0));
|
||||||
|
assertThat(response.getHits().hits()[0].getHighlightFields(), not(hasKey("text")));
|
||||||
|
|
||||||
|
// Same for if the field doesn't even exist
|
||||||
|
assert(!client().prepareDelete("test", "type1", "1").get().isNotFound());
|
||||||
|
index("test", "type1", "1");
|
||||||
|
refresh();
|
||||||
|
field.highlighterType("plain");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNoFailures(response);
|
||||||
|
assertThat("not enough hits", response.getHits().hits().length, greaterThan(0));
|
||||||
|
assertThat(response.getHits().hits()[0].getHighlightFields(), not(hasKey("text")));
|
||||||
|
|
||||||
|
// And the fvh should work as well
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNoFailures(response);
|
||||||
|
assertThat("not enough hits", response.getHits().hits().length, greaterThan(0));
|
||||||
|
assertThat(response.getHits().hits()[0].getHighlightFields(), not(hasKey("text")));
|
||||||
|
|
||||||
|
// Again same if the field isn't mapped
|
||||||
|
field = new HighlightBuilder.Field("unmapped")
|
||||||
|
.highlighterType("plain")
|
||||||
|
.noMatchSize(21);
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNoFailures(response);
|
||||||
|
assertThat("not enough hits", response.getHits().hits().length, greaterThan(0));
|
||||||
|
assertThat(response.getHits().hits()[0].getHighlightFields(), not(hasKey("unmapped")));
|
||||||
|
|
||||||
|
// And the fvh should work as well
|
||||||
|
field.highlighterType("fvh");
|
||||||
|
response = client().prepareSearch("test").addHighlightedField(field).get();
|
||||||
|
assertNoFailures(response);
|
||||||
|
assertThat("not enough hits", response.getHits().hits().length, greaterThan(0));
|
||||||
|
assertThat(response.getHits().hits()[0].getHighlightFields(), not(hasKey("unmapped")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,12 +189,18 @@ public class ElasticsearchAssertions {
|
||||||
public static void assertHighlight(SearchResponse resp, int hit, String field, int fragment, Matcher<String> matcher) {
|
public static void assertHighlight(SearchResponse resp, int hit, String field, int fragment, Matcher<String> matcher) {
|
||||||
assertNoFailures(resp);
|
assertNoFailures(resp);
|
||||||
assertThat("not enough hits", resp.getHits().hits().length, greaterThan(hit));
|
assertThat("not enough hits", resp.getHits().hits().length, greaterThan(hit));
|
||||||
assertThat(resp.getHits().hits()[hit].getHighlightFields().get(field), notNullValue());
|
assertThat(resp.getHits().hits()[hit].getHighlightFields(), hasKey(field));
|
||||||
assertThat(resp.getHits().hits()[hit].getHighlightFields().get(field).fragments().length, greaterThan(fragment));
|
assertThat(resp.getHits().hits()[hit].getHighlightFields().get(field).fragments().length, greaterThan(fragment));
|
||||||
assertThat(resp.getHits().hits()[hit].highlightFields().get(field).fragments()[fragment].string(), matcher);
|
assertThat(resp.getHits().hits()[hit].highlightFields().get(field).fragments()[fragment].string(), matcher);
|
||||||
assertVersionSerializable(resp);
|
assertVersionSerializable(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void assertNotHighlighted(SearchResponse resp, int hit, String field) {
|
||||||
|
assertNoFailures(resp);
|
||||||
|
assertThat("not enough hits", resp.getHits().hits().length, greaterThan(hit));
|
||||||
|
assertThat(resp.getHits().hits()[hit].getHighlightFields(), not(hasKey(field)));
|
||||||
|
}
|
||||||
|
|
||||||
public static void assertSuggestionSize(Suggest searchSuggest, int entry, int size, String key) {
|
public static void assertSuggestionSize(Suggest searchSuggest, int entry, int size, String key) {
|
||||||
assertThat(searchSuggest, notNullValue());
|
assertThat(searchSuggest, notNullValue());
|
||||||
assertThat(searchSuggest.size(), greaterThanOrEqualTo(1));
|
assertThat(searchSuggest.size(), greaterThanOrEqualTo(1));
|
||||||
|
|
Loading…
Reference in New Issue