Skip to content

Commit d403922

Browse files
committed
Fix regressions around nested hits and disabled _source. (#66572)
This PR fixes two bugs that can arise when _source is disabled and we fetch nested documents: * Fix exception when highlighting `inner_hits` with disabled _source. * Fix exception in nested `top_hits` with disabled _source. * Add more tests for highlighting `inner_hits`.
1 parent df8c92c commit d403922

File tree

3 files changed

+196
-6
lines changed

3 files changed

+196
-6
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/200_top_hits_metric.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ setup:
5656
- do:
5757
search:
5858
rest_total_hits_as_int: true
59+
index: my-index
5960
body:
6061
aggs:
6162
to-users:
@@ -81,12 +82,62 @@ setup:
8182
- match: { aggregations.to-users.users.hits.hits.2._nested.field: users }
8283
- match: { aggregations.to-users.users.hits.hits.2._nested.offset: 1 }
8384

85+
---
86+
"top_hits aggregation with nested documents and disabled _source":
87+
- skip:
88+
version: " - 7.99.99"
89+
reason: "bug fix is not yet backported"
90+
- do:
91+
indices.create:
92+
index: disabled-source
93+
body:
94+
settings:
95+
number_of_shards: 1
96+
number_of_replicas: 0
97+
mappings:
98+
_source:
99+
enabled: false
100+
properties:
101+
users:
102+
type: nested
103+
104+
- do:
105+
index:
106+
index: disabled-source
107+
id: 1
108+
refresh: true
109+
body:
110+
users:
111+
- first: "John"
112+
last: "Smith"
113+
114+
- do:
115+
search:
116+
index: disabled-source
117+
rest_total_hits_as_int: true
118+
body:
119+
aggs:
120+
to-users:
121+
nested:
122+
path: users
123+
aggs:
124+
users:
125+
top_hits: {}
126+
127+
- match: { hits.total: 1 }
128+
- length: { aggregations.to-users.users.hits.hits: 1 }
129+
- match: { aggregations.to-users.users.hits.hits.0._id: "1" }
130+
- match: { aggregations.to-users.users.hits.hits.0._index: disabled-source }
131+
- match: { aggregations.to-users.users.hits.hits.0._nested.field: users }
132+
- match: { aggregations.to-users.users.hits.hits.0._nested.offset: 0 }
133+
- is_false: aggregations.to-users.users.hits.hits.0._source
84134

85135
---
86136
"top_hits aggregation with sequence numbers":
87137

88138
- do:
89139
search:
140+
index: my-index
90141
rest_total_hits_as_int: true
91142
body:
92143
aggs:
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
setup:
2+
- do:
3+
indices.create:
4+
index: test
5+
body:
6+
mappings:
7+
_source:
8+
excludes: ["nested.stored_only"]
9+
properties:
10+
nested:
11+
type: nested
12+
properties:
13+
field:
14+
type: text
15+
fields:
16+
vectors:
17+
type: text
18+
term_vector: "with_positions_offsets"
19+
postings:
20+
type: text
21+
index_options: "offsets"
22+
stored:
23+
type: text
24+
store: true
25+
stored_only:
26+
type: text
27+
store: true
28+
- do:
29+
index:
30+
index: test
31+
id: 1
32+
refresh: true
33+
body:
34+
nested:
35+
field : "The quick brown fox is brown."
36+
stored : "The quick brown fox is brown."
37+
stored_only : "The quick brown fox is brown."
38+
39+
---
40+
"Unified highlighter":
41+
- do:
42+
search:
43+
index: test
44+
body:
45+
query:
46+
nested:
47+
path: "nested"
48+
query:
49+
multi_match:
50+
query: "quick brown fox"
51+
fields: [ "nested.field", "nested.field.vectors", "nested.field.postings" ]
52+
inner_hits:
53+
highlight:
54+
type: "unified"
55+
fields:
56+
nested.field: {}
57+
nested.field.vectors: {}
58+
nested.field.postings: {}
59+
60+
- match: { hits.hits.0.inner_hits.nested.hits.hits.0.highlight.nested\.field.0: "The <em>quick</em> <em>brown</em> <em>fox</em> is <em>brown</em>." }
61+
- match: { hits.hits.0.inner_hits.nested.hits.hits.0.highlight.nested\.field\.vectors.0: "The <em>quick</em> <em>brown</em> <em>fox</em> is <em>brown</em>." }
62+
- match: { hits.hits.0.inner_hits.nested.hits.hits.0.highlight.nested\.field\.postings.0: "The <em>quick</em> <em>brown</em> <em>fox</em> is <em>brown</em>." }
63+
64+
---
65+
"Unified highlighter with stored fields":
66+
- do:
67+
search:
68+
index: test
69+
body:
70+
query:
71+
nested:
72+
path: "nested"
73+
query:
74+
multi_match:
75+
query: "quick brown fox"
76+
fields: [ "nested.stored", "nested.stored_only" ]
77+
inner_hits:
78+
highlight:
79+
type: "unified"
80+
fields:
81+
nested.stored: {}
82+
nested.stored_only: {}
83+
84+
- match: { hits.hits.0.inner_hits.nested.hits.hits.0.highlight.nested\.stored.0: "The <em>quick</em> <em>brown</em> <em>fox</em> is <em>brown</em>." }
85+
- match: { hits.hits.0.inner_hits.nested.hits.hits.0.highlight.nested\.stored_only.0: "The <em>quick</em> <em>brown</em> <em>fox</em> is <em>brown</em>." }
86+
87+
---
88+
"Unified highlighter with stored fields and disabled source":
89+
- skip:
90+
version: " - 7.99.99"
91+
reason: "bug fix is not yet backported"
92+
- do:
93+
indices.create:
94+
index: disabled_source
95+
body:
96+
mappings:
97+
_source:
98+
enabled: false
99+
properties:
100+
nested:
101+
type: nested
102+
properties:
103+
field:
104+
type: text
105+
stored_only:
106+
type: text
107+
store: true
108+
- do:
109+
index:
110+
index: disabled_source
111+
id: 1
112+
refresh: true
113+
body:
114+
nested:
115+
field: "The quick brown fox is brown."
116+
stored_only: "The quick brown fox is brown."
117+
118+
- do:
119+
search:
120+
index: disabled_source
121+
body:
122+
query:
123+
nested:
124+
path: "nested"
125+
query:
126+
multi_match:
127+
query: "quick brown fox"
128+
fields: ["nested.field", "nested.stored_only"]
129+
inner_hits:
130+
highlight:
131+
type: "unified"
132+
fields:
133+
nested.field: {}
134+
nested.stored_only: {}
135+
136+
- is_false: hits.hits.0.inner_hits.nested.hits.hits.0.highlight.nested\.field
137+
- match: { hits.hits.0.inner_hits.nested.hits.hits.0.highlight.nested\.stored_only.0: "The <em>quick</em> <em>brown</em> <em>fox</em> is <em>brown</em>."}

server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.apache.lucene.util.BitSet;
3333
import org.elasticsearch.Version;
3434
import org.elasticsearch.common.CheckedBiConsumer;
35-
import org.elasticsearch.common.bytes.BytesReference;
3635
import org.elasticsearch.common.collect.Tuple;
3736
import org.elasticsearch.common.document.DocumentField;
3837
import org.elasticsearch.common.lucene.index.SequentialStoredFieldsLeafReader;
@@ -378,10 +377,13 @@ private HitContext prepareNestedHitContext(SearchContext context,
378377
rootId = rootFieldsVisitor.uid();
379378

380379
if (needSource) {
381-
BytesReference rootSource = rootFieldsVisitor.source();
382-
Tuple<XContentType, Map<String, Object>> tuple = XContentHelper.convertToMap(rootSource, false);
383-
rootSourceAsMap = tuple.v2();
384-
rootSourceContentType = tuple.v1();
380+
if (rootFieldsVisitor.source() != null) {
381+
Tuple<XContentType, Map<String, Object>> tuple = XContentHelper.convertToMap(rootFieldsVisitor.source(), false);
382+
rootSourceAsMap = tuple.v2();
383+
rootSourceContentType = tuple.v1();
384+
} else {
385+
rootSourceAsMap = Collections.emptyMap();
386+
}
385387
}
386388
}
387389

@@ -413,7 +415,7 @@ private HitContext prepareNestedHitContext(SearchContext context,
413415
nestedDocId,
414416
new SourceLookup()); // Use a clean, fresh SourceLookup for the nested context
415417

416-
if (rootSourceAsMap != null) {
418+
if (rootSourceAsMap != null && rootSourceAsMap.isEmpty() == false) {
417419
// Isolate the nested json array object that matches with nested hit and wrap it back into the same json
418420
// structure with the nested json array object being the actual content. The latter is important, so that
419421
// features like source filtering and highlighting work consistent regardless of whether the field points

0 commit comments

Comments
 (0)