Fix an NPE when requesting inner hits and _source is disabled. (#44836)

This PR makes two changes to FetchSourceSubPhase when _source is disabled and
we're in a nested context:
* If no source filters are provided, return early to avoid an NPE.
* If there are source filters, make sure to throw an exception.

The behavior was chosen to match what currently happens in a non-nested context.
This commit is contained in:
Julie Tibshirani 2019-07-25 10:34:37 -07:00
parent 6a60fd6d30
commit acb7f599a3
2 changed files with 28 additions and 7 deletions

View File

@ -42,17 +42,23 @@ public final class FetchSourceSubPhase implements FetchSubPhase {
SourceLookup source = context.lookup().source(); SourceLookup source = context.lookup().source();
FetchSourceContext fetchSourceContext = context.fetchSourceContext(); FetchSourceContext fetchSourceContext = context.fetchSourceContext();
assert fetchSourceContext.fetchSource(); assert fetchSourceContext.fetchSource();
if (nestedHit == false) {
if (fetchSourceContext.includes().length == 0 && fetchSourceContext.excludes().length == 0) { // If source is disabled in the mapping, then attempt to return early.
hitContext.hit().sourceRef(source.internalSourceRef()); if (source.source() == null && source.internalSourceRef() == null) {
return; if (containsFilters(fetchSourceContext)) {
}
if (source.internalSourceRef() == null) {
throw new IllegalArgumentException("unable to fetch fields from _source field: _source is disabled in the mappings " + throw new IllegalArgumentException("unable to fetch fields from _source field: _source is disabled in the mappings " +
"for index [" + context.indexShard().shardId().getIndexName() + "]"); "for index [" + context.indexShard().shardId().getIndexName() + "]");
} }
return;
} }
// If this is a parent document and there are no source filters, then add the source as-is.
if (nestedHit == false && containsFilters(fetchSourceContext) == false) {
hitContext.hit().sourceRef(source.internalSourceRef());
return;
}
// Otherwise, filter the source and add it to the hit.
Object value = source.filter(fetchSourceContext); Object value = source.filter(fetchSourceContext);
if (nestedHit) { if (nestedHit) {
value = getNestedSource((Map<String, Object>) value, hitContext); value = getNestedSource((Map<String, Object>) value, hitContext);
@ -79,6 +85,10 @@ public final class FetchSourceSubPhase implements FetchSubPhase {
} }
} }
private static boolean containsFilters(FetchSourceContext context) {
return context.includes().length != 0 || context.excludes().length != 0;
}
private Map<String, Object> getNestedSource(Map<String, Object> sourceAsMap, HitContext hitContext) { private Map<String, Object> getNestedSource(Map<String, Object> sourceAsMap, HitContext hitContext) {
for (SearchHit.NestedIdentity o = hitContext.hit().getNestedIdentity(); o != null; o = o.getChild()) { for (SearchHit.NestedIdentity o = hitContext.hit().getNestedIdentity(); o != null; o = o.getChild()) {
sourceAsMap = (Map<String, Object>) sourceAsMap.get(o.getField().string()); sourceAsMap = (Map<String, Object>) sourceAsMap.get(o.getField().string());

View File

@ -119,6 +119,17 @@ public class FetchSourceSubPhaseTests extends ESTestCase {
"for index [index]", exception.getMessage()); "for index [index]", exception.getMessage());
} }
public void testNestedSourceWithSourceDisabled() {
FetchSubPhase.HitContext hitContext = hitExecute(null, true, null, null,
new SearchHit.NestedIdentity("nested1", 0, null));
assertNull(hitContext.hit().getSourceAsMap());
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> hitExecute(null, true, "field1", null, new SearchHit.NestedIdentity("nested1", 0, null)));
assertEquals("unable to fetch fields from _source field: _source is disabled in the mappings " +
"for index [index]", e.getMessage());
}
private FetchSubPhase.HitContext hitExecute(XContentBuilder source, boolean fetchSource, String include, String exclude) { private FetchSubPhase.HitContext hitExecute(XContentBuilder source, boolean fetchSource, String include, String exclude) {
return hitExecute(source, fetchSource, include, exclude, null); return hitExecute(source, fetchSource, include, exclude, null);
} }