Skip to content

Commit bf4655a

Browse files
authored
Fix StoredFieldsSpec#merge(...) issue (#138306)
If StoredFieldsSpec requires source and has no sourcePaths, then it requires the whole source. For example, in case of `SourceFieldBlockLoader`. When merging StoredFieldsSpec then omit the sourcePaths of the other StoredFieldsSpec. Closes #138188
1 parent e6c5dcc commit bf4655a

File tree

4 files changed

+120
-13
lines changed

4 files changed

+120
-13
lines changed

server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public class MapperFeatures implements FeatureSpecification {
6464
public static final NodeFeature GENERIC_VECTOR_FORMAT = new NodeFeature("mapper.vectors.generic_vector_format");
6565
public static final NodeFeature FIX_DENSE_VECTOR_WRONG_FIELDS = new NodeFeature("mapper.fix_dense_vector_wrong_fields");
6666
static final NodeFeature BBQ_DISK_STATS_SUPPORT = new NodeFeature("mapper.bbq_disk_stats_support");
67+
static final NodeFeature STORED_FIELDS_SPEC_MERGE_BUG = new NodeFeature("mapper.stored_fields_spec_merge_bug");
6768

6869
@Override
6970
public Set<NodeFeature> getTestFeatures() {
@@ -108,7 +109,8 @@ public Set<NodeFeature> getTestFeatures() {
108109
EXCLUDE_VECTORS_DOCVALUE_BUGFIX,
109110
BASE64_DENSE_VECTORS,
110111
FIX_DENSE_VECTOR_WRONG_FIELDS,
111-
BBQ_DISK_STATS_SUPPORT
112+
BBQ_DISK_STATS_SUPPORT,
113+
STORED_FIELDS_SPEC_MERGE_BUG
112114
);
113115
if (ES93GenericFlatVectorsFormat.GENERIC_VECTOR_FORMAT.isEnabled()) {
114116
features = new HashSet<>(features);

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

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,7 @@ public StoredFieldsSpec merge(StoredFieldsSpec other) {
7777
mergedFields = new HashSet<>(this.requiredStoredFields);
7878
mergedFields.addAll(other.requiredStoredFields);
7979
}
80-
Set<String> mergedSourcePaths;
81-
if (this.sourcePaths.isEmpty() == false && other.sourcePaths.isEmpty() == false) {
82-
mergedSourcePaths = new HashSet<>(this.sourcePaths);
83-
mergedSourcePaths.addAll(other.sourcePaths);
84-
} else if (this.sourcePaths.isEmpty() == false) {
85-
mergedSourcePaths = this.sourcePaths;
86-
} else if (other.sourcePaths.isEmpty() == false) {
87-
mergedSourcePaths = other.sourcePaths;
88-
} else {
89-
mergedSourcePaths = Set.of();
90-
}
80+
Set<String> mergedSourcePaths = mergeSourcePaths(other);
9181
IgnoredSourceFormat mergedFormat;
9282
if (this.ignoredSourceFormat == IgnoredSourceFormat.NO_IGNORED_SOURCE) {
9383
mergedFormat = other.ignoredSourceFormat;
@@ -114,6 +104,33 @@ public StoredFieldsSpec merge(StoredFieldsSpec other) {
114104
);
115105
}
116106

107+
/**
108+
* Returns the unique source paths that should be loaded from source. Other source paths may be filtered out.
109+
* If an empty set is returned, then all source paths need to be loaded.
110+
*/
111+
private Set<String> mergeSourcePaths(StoredFieldsSpec other) {
112+
Set<String> mergedSourcePaths;
113+
if (this.sourcePaths.isEmpty() == false && other.sourcePaths.isEmpty() == false) {
114+
mergedSourcePaths = new HashSet<>(this.sourcePaths);
115+
mergedSourcePaths.addAll(other.sourcePaths);
116+
} else if (this.sourcePaths.isEmpty() == false) {
117+
if (other.requiresSource) {
118+
mergedSourcePaths = Set.of();
119+
} else {
120+
mergedSourcePaths = this.sourcePaths;
121+
}
122+
} else if (other.sourcePaths.isEmpty() == false) {
123+
if (this.requiresSource) {
124+
mergedSourcePaths = Set.of();
125+
} else {
126+
mergedSourcePaths = other.sourcePaths;
127+
}
128+
} else {
129+
mergedSourcePaths = Set.of();
130+
}
131+
return mergedSourcePaths;
132+
}
133+
117134
public Set<String> requiredStoredFields() {
118135
if (sourcePaths.isEmpty() || ignoredSourceFormat == IgnoredSourceFormat.NO_IGNORED_SOURCE) {
119136
return requiredStoredFields;

server/src/test/java/org/elasticsearch/search/fetch/StoredFieldsSpecTests.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public void testMergeSourcePaths() {
120120

121121
spec = spec.merge(
122122
new StoredFieldsSpec(
123-
true,
123+
false, // if set to true, then the new spec will require complete and source and so source paths would then be empty
124124
false,
125125
Set.of("other_field"),
126126
IgnoredSourceFieldMapper.IgnoredSourceFormat.NO_IGNORED_SOURCE,
@@ -135,6 +135,24 @@ public void testMergeSourcePaths() {
135135
assertThat(spec.sourcePaths(), sameInstance(pref));
136136
}
137137

138+
public void testMergeSourcePathsRequireCompleteSource() {
139+
var ignoredSourceFormat = IgnoredSourceFieldMapper.IgnoredSourceFormat.NO_IGNORED_SOURCE;
140+
StoredFieldsSpec spec = new StoredFieldsSpec(true, false, Set.of(), ignoredSourceFormat, Set.of("field1", "field2"));
141+
assertThat(spec.ignoredSourceFormat(), equalTo(ignoredSourceFormat));
142+
assertThat(spec.requiresSource(), equalTo(true));
143+
assertThat(spec.requiresMetadata(), equalTo(false));
144+
assertThat(spec.requiredStoredFields(), empty());
145+
assertThat(spec.sourcePaths(), containsInAnyOrder("field1", "field2"));
146+
147+
// Clears source paths, because this spec requires complete source (since no source paths are defined)
148+
spec = spec.merge(new StoredFieldsSpec(true, false, Set.of(), ignoredSourceFormat, Set.of()));
149+
assertThat(spec.ignoredSourceFormat(), equalTo(ignoredSourceFormat));
150+
assertThat(spec.requiresSource(), equalTo(true));
151+
assertThat(spec.requiresMetadata(), equalTo(false));
152+
assertThat(spec.requiredStoredFields(), empty());
153+
assertThat(spec.sourcePaths(), empty());
154+
}
155+
138156
private static SearchContext searchContext(SearchSourceBuilder sourceBuilder) {
139157
SearchContext sc = mock(SearchContext.class);
140158
when(sc.fetchSourceContext()).thenReturn(sourceBuilder.fetchSource());

x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/140_metadata.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,73 @@ setup:
210210
"keyword" : "ok",
211211
"case" : "ok"
212212
}}
213+
214+
---
215+
"source meta field and dynamic false":
216+
- requires:
217+
cluster_features: [ "mapper.stored_fields_spec_merge_bug" ]
218+
reason: "Test can only run on clusters with the bug fix"
219+
- do:
220+
indices.create:
221+
index: my-index2
222+
body:
223+
mappings:
224+
dynamic: false
225+
properties:
226+
foo:
227+
type: text
228+
229+
- do:
230+
bulk:
231+
index: my-index2
232+
refresh: true
233+
body:
234+
- { "index": { } }
235+
- { "baz": "dasd" }
236+
- do:
237+
esql.query:
238+
body:
239+
query: 'FROM my-index2 METADATA _source | LIMIT 10'
240+
241+
- match: {columns.0.name: "foo"}
242+
- match: {columns.0.type: "text"}
243+
- match: {columns.1.name: "_source"}
244+
- match: {columns.1.type: "_source"}
245+
246+
- match: {values.0.0: null}
247+
- match: {values.0.1: { "baz": "dasd" }}
248+
249+
---
250+
"source meta field and keep":
251+
- requires:
252+
cluster_features: [ "mapper.stored_fields_spec_merge_bug" ]
253+
reason: "Test can only run on clusters with the bug fix"
254+
- do:
255+
indices.create:
256+
index: my-index2
257+
body:
258+
mappings:
259+
properties:
260+
foo:
261+
type: text
262+
263+
- do:
264+
bulk:
265+
index: my-index2
266+
refresh: true
267+
body:
268+
- { "index": { } }
269+
- { "baz": "dasd" }
270+
- do:
271+
esql.query:
272+
body:
273+
query: 'FROM my-index2 METADATA _source | KEEP foo, _source | LIMIT 10'
274+
275+
- match: {columns.0.name: "foo"}
276+
- match: {columns.0.type: "text"}
277+
- match: {columns.1.name: "_source"}
278+
- match: {columns.1.type: "_source"}
279+
280+
- match: {values.0.0: null}
281+
- match: {values.0.1: { "baz": "dasd" }}
282+

0 commit comments

Comments
 (0)