Highlighting shouldn't fail when the field to highlight is absent.
PlainHighlighter fails with a NPE when the field to highlight is marked as stored in the mapping but doesn't exist in a hit. This patch makes FieldsVisitor.fields less error-prone by returning an empty list instead of null when no matching stored field was found. Closes #3109
This commit is contained in:
parent
03a86604a4
commit
5ea6c77dad
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.fieldvisitor;
|
package org.elasticsearch.index.fieldvisitor;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import org.apache.lucene.index.FieldInfo;
|
import org.apache.lucene.index.FieldInfo;
|
||||||
import org.apache.lucene.index.StoredFieldVisitor;
|
import org.apache.lucene.index.StoredFieldVisitor;
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
|
@ -44,9 +45,6 @@ public abstract class FieldsVisitor extends StoredFieldVisitor {
|
||||||
protected Map<String, List<Object>> fieldsValues;
|
protected Map<String, List<Object>> fieldsValues;
|
||||||
|
|
||||||
public void postProcess(MapperService mapperService) {
|
public void postProcess(MapperService mapperService) {
|
||||||
if (fieldsValues == null || fieldsValues.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (uid != null) {
|
if (uid != null) {
|
||||||
DocumentMapper documentMapper = mapperService.documentMapper(uid.type());
|
DocumentMapper documentMapper = mapperService.documentMapper(uid.type());
|
||||||
if (documentMapper != null) {
|
if (documentMapper != null) {
|
||||||
|
@ -56,7 +54,7 @@ public abstract class FieldsVisitor extends StoredFieldVisitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// can't derive exact mapping type
|
// can't derive exact mapping type
|
||||||
for (Map.Entry<String, List<Object>> entry : fieldsValues.entrySet()) {
|
for (Map.Entry<String, List<Object>> entry : fields().entrySet()) {
|
||||||
FieldMappers fieldMappers = mapperService.indexName(entry.getKey());
|
FieldMappers fieldMappers = mapperService.indexName(entry.getKey());
|
||||||
if (fieldMappers == null) {
|
if (fieldMappers == null) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -69,8 +67,8 @@ public abstract class FieldsVisitor extends StoredFieldVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void postProcess(DocumentMapper documentMapper) {
|
public void postProcess(DocumentMapper documentMapper) {
|
||||||
for (Map.Entry<String, List<Object>> entry : fieldsValues.entrySet()) {
|
for (Map.Entry<String, List<Object>> entry : fields().entrySet()) {
|
||||||
FieldMapper fieldMapper = documentMapper.mappers().indexName(entry.getKey()).mapper();
|
FieldMapper<?> fieldMapper = documentMapper.mappers().indexName(entry.getKey()).mapper();
|
||||||
if (fieldMapper == null) {
|
if (fieldMapper == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -128,7 +126,9 @@ public abstract class FieldsVisitor extends StoredFieldVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, List<Object>> fields() {
|
public Map<String, List<Object>> fields() {
|
||||||
return fieldsValues;
|
return fieldsValues != null
|
||||||
|
? fieldsValues
|
||||||
|
: ImmutableMap.<String, List<Object>>of();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
|
|
@ -52,7 +52,6 @@ import org.elasticsearch.search.lookup.SearchLookup;
|
||||||
import org.elasticsearch.search.lookup.SourceLookup;
|
import org.elasticsearch.search.lookup.SourceLookup;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -322,7 +321,7 @@ public class ShardGetService extends AbstractIndexShardComponent {
|
||||||
}
|
}
|
||||||
source = fieldVisitor.source();
|
source = fieldVisitor.source();
|
||||||
|
|
||||||
if (fieldVisitor.fields() != null) {
|
if (!fieldVisitor.fields().isEmpty()) {
|
||||||
fieldVisitor.postProcess(docMapper);
|
fieldVisitor.postProcess(docMapper);
|
||||||
fields = new HashMap<String, GetField>(fieldVisitor.fields().size());
|
fields = new HashMap<String, GetField>(fieldVisitor.fields().size());
|
||||||
for (Map.Entry<String, List<Object>> entry : fieldVisitor.fields().entrySet()) {
|
for (Map.Entry<String, List<Object>> entry : fieldVisitor.fields().entrySet()) {
|
||||||
|
|
|
@ -145,7 +145,7 @@ public class FetchPhase implements SearchPhase {
|
||||||
fieldsVisitor.postProcess(context.mapperService());
|
fieldsVisitor.postProcess(context.mapperService());
|
||||||
|
|
||||||
Map<String, SearchHitField> searchFields = null;
|
Map<String, SearchHitField> searchFields = null;
|
||||||
if (fieldsVisitor.fields() != null) {
|
if (!fieldsVisitor.fields().isEmpty()) {
|
||||||
searchFields = new HashMap<String, SearchHitField>(fieldsVisitor.fields().size());
|
searchFields = new HashMap<String, SearchHitField>(fieldsVisitor.fields().size());
|
||||||
for (Map.Entry<String, List<Object>> entry : fieldsVisitor.fields().entrySet()) {
|
for (Map.Entry<String, List<Object>> entry : fieldsVisitor.fields().entrySet()) {
|
||||||
searchFields.put(entry.getKey(), new InternalSearchHitField(entry.getKey(), entry.getValue()));
|
searchFields.put(entry.getKey(), new InternalSearchHitField(entry.getKey(), entry.getValue()));
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.search.highlight;
|
package org.elasticsearch.search.highlight;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import org.apache.lucene.analysis.Analyzer;
|
import org.apache.lucene.analysis.Analyzer;
|
||||||
|
@ -58,10 +60,10 @@ public class PlainHighlighter implements Highlighter {
|
||||||
Encoder encoder = field.encoder().equals("html") ? Encoders.HTML : Encoders.DEFAULT;
|
Encoder encoder = field.encoder().equals("html") ? Encoders.HTML : Encoders.DEFAULT;
|
||||||
|
|
||||||
if (!hitContext.cache().containsKey(CACHE_KEY)) {
|
if (!hitContext.cache().containsKey(CACHE_KEY)) {
|
||||||
Map<FieldMapper, org.apache.lucene.search.highlight.Highlighter> mappers = Maps.newHashMap();
|
Map<FieldMapper<?>, org.apache.lucene.search.highlight.Highlighter> mappers = Maps.newHashMap();
|
||||||
hitContext.cache().put(CACHE_KEY, mappers);
|
hitContext.cache().put(CACHE_KEY, mappers);
|
||||||
}
|
}
|
||||||
Map<FieldMapper, org.apache.lucene.search.highlight.Highlighter> cache = (Map<FieldMapper, org.apache.lucene.search.highlight.Highlighter>) hitContext.cache().get(CACHE_KEY);
|
Map<FieldMapper<?>, org.apache.lucene.search.highlight.Highlighter> cache = (Map<FieldMapper<?>, org.apache.lucene.search.highlight.Highlighter>) hitContext.cache().get(CACHE_KEY);
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -98,6 +100,10 @@ public class PlainHighlighter implements Highlighter {
|
||||||
CustomFieldsVisitor fieldVisitor = new CustomFieldsVisitor(ImmutableSet.of(mapper.names().indexName()), false);
|
CustomFieldsVisitor fieldVisitor = new CustomFieldsVisitor(ImmutableSet.of(mapper.names().indexName()), false);
|
||||||
hitContext.reader().document(hitContext.docId(), fieldVisitor);
|
hitContext.reader().document(hitContext.docId(), fieldVisitor);
|
||||||
textsToHighlight = fieldVisitor.fields().get(mapper.names().indexName());
|
textsToHighlight = fieldVisitor.fields().get(mapper.names().indexName());
|
||||||
|
if (textsToHighlight == null) {
|
||||||
|
// Can happen if the document doesn't have the field to highlight
|
||||||
|
textsToHighlight = ImmutableList.of();
|
||||||
|
}
|
||||||
} 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);
|
||||||
}
|
}
|
||||||
|
@ -107,6 +113,7 @@ public class PlainHighlighter implements Highlighter {
|
||||||
lookup.setNextDocId(hitContext.docId());
|
lookup.setNextDocId(hitContext.docId());
|
||||||
textsToHighlight = lookup.source().extractRawValues(mapper.names().sourcePath());
|
textsToHighlight = lookup.source().extractRawValues(mapper.names().sourcePath());
|
||||||
}
|
}
|
||||||
|
assert textsToHighlight != null;
|
||||||
|
|
||||||
// a HACK to make highlighter do highlighting, even though its using the single frag list builder
|
// a HACK to make highlighter do highlighting, even though its using the single frag list builder
|
||||||
int numberOfFragments = field.numberOfFragments() == 0 ? 1 : field.numberOfFragments();
|
int numberOfFragments = field.numberOfFragments() == 0 ? 1 : field.numberOfFragments();
|
||||||
|
|
|
@ -1503,4 +1503,34 @@ public class HighlighterSearchTests extends AbstractNodesTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMissingStoredField() throws Exception {
|
||||||
|
try {
|
||||||
|
client.admin().indices().prepareDelete("test").execute().actionGet();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
client.admin().indices().prepareCreate("test").setSettings(ImmutableSettings.settingsBuilder()
|
||||||
|
.put("index.number_of_shards", 1).put("index.number_of_replicas", 0))
|
||||||
|
.addMapping("type1", jsonBuilder().startObject().startObject("type1").startObject("properties")
|
||||||
|
.startObject("highlight_field").field("type", "string").field("store", "yes").endObject()
|
||||||
|
.endObject().endObject().endObject())
|
||||||
|
.execute().actionGet();
|
||||||
|
|
||||||
|
client.prepareIndex("test", "type1", "1")
|
||||||
|
.setSource(jsonBuilder().startObject()
|
||||||
|
.field("field", "highlight")
|
||||||
|
.endObject())
|
||||||
|
.setRefresh(true).execute().actionGet();
|
||||||
|
|
||||||
|
// This query used to fail when the field to highlight was absent
|
||||||
|
SearchResponse response = client.prepareSearch("test")
|
||||||
|
.setQuery(QueryBuilders.matchQuery("field", "highlight").type(MatchQueryBuilder.Type.BOOLEAN))
|
||||||
|
.addHighlightedField(new HighlightBuilder.Field("highlight_field")
|
||||||
|
.fragmentSize(-1).numOfFragments(1).fragmenter("simple"))
|
||||||
|
.execute().actionGet();
|
||||||
|
assertThat(response.getHits().hits()[0].highlightFields().isEmpty(), equalTo(true));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue