Skip to content

Commit 27af04d

Browse files
committed
Fix nested _source retrieval with includes/excludes (#33180)
If an exclude or an include clause removes an entry to a nested field in the original source at query time, the creation of nested hits fails with an NPE. This change fixes this exception and replaces the nested document source with an empty map. Closes #33163 Closes #33170
1 parent b28044c commit 27af04d

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchSourceSubPhase.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public void hitExecute(SearchContext context, HitContext hitContext) {
5757
if (nestedHit) {
5858
value = getNestedSource((Map<String, Object>) value, hitContext);
5959
}
60+
6061
try {
6162
final int initialCapacity = nestedHit ? 1024 : Math.min(1024, source.internalSourceRef().length());
6263
BytesStreamOutput streamOutput = new BytesStreamOutput(initialCapacity);
@@ -81,6 +82,9 @@ public void hitExecute(SearchContext context, HitContext hitContext) {
8182
private Map<String, Object> getNestedSource(Map<String, Object> sourceAsMap, HitContext hitContext) {
8283
for (SearchHit.NestedIdentity o = hitContext.hit().getNestedIdentity(); o != null; o = o.getChild()) {
8384
sourceAsMap = (Map<String, Object>) sourceAsMap.get(o.getField().string());
85+
if (sourceAsMap == null) {
86+
return null;
87+
}
8488
}
8589
return sourceAsMap;
8690
}

server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourceSubPhaseTests.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import java.io.IOException;
3636
import java.util.Collections;
37+
import java.util.Map;
3738

3839
import static org.mockito.Mockito.mock;
3940
import static org.mockito.Mockito.when;
@@ -78,6 +79,29 @@ public void testMultipleFiltering() throws IOException {
7879
assertEquals(Collections.singletonMap("field","value"), hitContext.hit().getSourceAsMap());
7980
}
8081

82+
public void testNestedSource() throws IOException {
83+
Map<String, Object> expectedNested = Collections.singletonMap("nested2", Collections.singletonMap("field", "value0"));
84+
XContentBuilder source = XContentFactory.jsonBuilder().startObject()
85+
.field("field", "value")
86+
.field("field2", "value2")
87+
.field("nested1", expectedNested)
88+
.endObject();
89+
FetchSubPhase.HitContext hitContext = hitExecuteMultiple(source, true, null, null,
90+
new SearchHit.NestedIdentity("nested1", 0,null));
91+
assertEquals(expectedNested, hitContext.hit().getSourceAsMap());
92+
hitContext = hitExecuteMultiple(source, true, new String[]{"invalid"}, null,
93+
new SearchHit.NestedIdentity("nested1", 0,null));
94+
assertEquals(Collections.emptyMap(), hitContext.hit().getSourceAsMap());
95+
96+
hitContext = hitExecuteMultiple(source, true, null, null,
97+
new SearchHit.NestedIdentity("nested1", 0, new SearchHit.NestedIdentity("nested2", 0, null)));
98+
assertEquals(Collections.singletonMap("field", "value0"), hitContext.hit().getSourceAsMap());
99+
100+
hitContext = hitExecuteMultiple(source, true, new String[]{"invalid"}, null,
101+
new SearchHit.NestedIdentity("nested1", 0, new SearchHit.NestedIdentity("nested2", 0, null)));
102+
assertEquals(Collections.emptyMap(), hitContext.hit().getSourceAsMap());
103+
}
104+
81105
public void testSourceDisabled() throws IOException {
82106
FetchSubPhase.HitContext hitContext = hitExecute(null, true, null, null);
83107
assertNull(hitContext.hit().getSourceAsMap());
@@ -96,17 +120,29 @@ public void testSourceDisabled() throws IOException {
96120
}
97121

98122
private FetchSubPhase.HitContext hitExecute(XContentBuilder source, boolean fetchSource, String include, String exclude) {
123+
return hitExecute(source, fetchSource, include, exclude, null);
124+
}
125+
126+
127+
private FetchSubPhase.HitContext hitExecute(XContentBuilder source, boolean fetchSource, String include, String exclude,
128+
SearchHit.NestedIdentity nestedIdentity) {
99129
return hitExecuteMultiple(source, fetchSource,
100130
include == null ? Strings.EMPTY_ARRAY : new String[]{include},
101-
exclude == null ? Strings.EMPTY_ARRAY : new String[]{exclude});
131+
exclude == null ? Strings.EMPTY_ARRAY : new String[]{exclude}, nestedIdentity);
102132
}
103133

104134
private FetchSubPhase.HitContext hitExecuteMultiple(XContentBuilder source, boolean fetchSource, String[] includes, String[] excludes) {
135+
return hitExecuteMultiple(source, fetchSource, includes, excludes, null);
136+
}
137+
138+
private FetchSubPhase.HitContext hitExecuteMultiple(XContentBuilder source, boolean fetchSource, String[] includes, String[] excludes,
139+
SearchHit.NestedIdentity nestedIdentity) {
105140
FetchSourceContext fetchSourceContext = new FetchSourceContext(fetchSource, includes, excludes);
106141
SearchContext searchContext = new FetchSourceSubPhaseTestSearchContext(fetchSourceContext,
107142
source == null ? null : BytesReference.bytes(source));
108143
FetchSubPhase.HitContext hitContext = new FetchSubPhase.HitContext();
109-
hitContext.reset(new SearchHit(1, null, null, null), null, 1, null);
144+
final SearchHit searchHit = new SearchHit(1, null, null, nestedIdentity, null);
145+
hitContext.reset(searchHit, null, 1, null);
110146
FetchSourceSubPhase phase = new FetchSourceSubPhase();
111147
phase.hitExecute(searchContext, hitContext);
112148
return hitContext;

0 commit comments

Comments
 (0)