Skip to content

Commit 9edf9f6

Browse files
authored
Fix NPE in TimeSeriesExtractFieldOperator (#131497)
We miss checking whether a field exists when populating the dimension attributes. This issue occurs when a field exists in some, but not all target indices.
1 parent 654d1f4 commit 9edf9f6

File tree

2 files changed

+67
-4
lines changed

2 files changed

+67
-4
lines changed

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/read/TimeSeriesExtractFieldOperator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,8 @@ static final class ShardLevelFieldsReader implements Releasable {
254254
this.storedFieldsSpec = storedFieldsSpec;
255255
this.dimensions = new boolean[fields.size()];
256256
for (int i = 0; i < fields.size(); i++) {
257-
dimensions[i] = shardContext.fieldType(fields.get(i).name()).isDimension();
257+
final var mappedFieldType = shardContext.fieldType(fields.get(i).name());
258+
dimensions[i] = mappedFieldType != null && mappedFieldType.isDimension();
258259
}
259260
}
260261

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/TimeSeriesIT.java

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.elasticsearch.Build;
1111
import org.elasticsearch.common.Randomness;
1212
import org.elasticsearch.common.settings.Settings;
13+
import org.elasticsearch.common.unit.ByteSizeValue;
1314
import org.elasticsearch.compute.lucene.TimeSeriesSourceOperator;
1415
import org.elasticsearch.compute.operator.DriverProfile;
1516
import org.elasticsearch.compute.operator.TimeSeriesAggregationOperator;
@@ -56,7 +57,7 @@ public void testEmpty() {
5657
run("TS empty_index | LIMIT 1").close();
5758
}
5859

59-
record Doc(String host, String cluster, long timestamp, int requestCount, double cpu) {}
60+
record Doc(String host, String cluster, long timestamp, int requestCount, double cpu, ByteSizeValue memory) {}
6061

6162
final List<Doc> docs = new ArrayList<>();
6263

@@ -84,7 +85,6 @@ static Double computeRate(List<RequestCounter> values) {
8485

8586
@Before
8687
public void populateIndex() {
87-
// this can be expensive, do one
8888
Settings settings = Settings.builder().put("mode", "time_series").putList("routing_path", List.of("host", "cluster")).build();
8989
client().admin()
9090
.indices()
@@ -99,6 +99,8 @@ public void populateIndex() {
9999
"type=keyword,time_series_dimension=true",
100100
"cpu",
101101
"type=double,time_series_metric=gauge",
102+
"memory",
103+
"type=long,time_series_metric=gauge",
102104
"request_count",
103105
"type=integer,time_series_metric=counter"
104106
)
@@ -123,7 +125,8 @@ public void populateIndex() {
123125
}
124126
});
125127
int cpu = randomIntBetween(0, 100);
126-
docs.add(new Doc(host, hostToClusters.get(host), timestamp, requestCount, cpu));
128+
ByteSizeValue memory = ByteSizeValue.ofBytes(randomIntBetween(1024, 1024 * 1024));
129+
docs.add(new Doc(host, hostToClusters.get(host), timestamp, requestCount, cpu, memory));
127130
}
128131
}
129132
Randomness.shuffle(docs);
@@ -138,6 +141,8 @@ public void populateIndex() {
138141
doc.cluster,
139142
"cpu",
140143
doc.cpu,
144+
"memory",
145+
doc.memory.getBytes(),
141146
"request_count",
142147
doc.requestCount
143148
)
@@ -417,6 +422,63 @@ public void testIndexMode() {
417422
assertThat(failure.getMessage(), containsString("Unknown index [hosts-old]"));
418423
}
419424

425+
public void testFieldDoesNotExist() {
426+
// the old-hosts index doesn't have the cpu field
427+
Settings settings = Settings.builder().put("mode", "time_series").putList("routing_path", List.of("host", "cluster")).build();
428+
client().admin()
429+
.indices()
430+
.prepareCreate("old-hosts")
431+
.setSettings(settings)
432+
.setMapping(
433+
"@timestamp",
434+
"type=date",
435+
"host",
436+
"type=keyword,time_series_dimension=true",
437+
"cluster",
438+
"type=keyword,time_series_dimension=true",
439+
"memory",
440+
"type=long,time_series_metric=gauge",
441+
"request_count",
442+
"type=integer,time_series_metric=counter"
443+
)
444+
.get();
445+
Randomness.shuffle(docs);
446+
for (Doc doc : docs) {
447+
client().prepareIndex("old-hosts")
448+
.setSource(
449+
"@timestamp",
450+
doc.timestamp,
451+
"host",
452+
doc.host,
453+
"cluster",
454+
doc.cluster,
455+
"memory",
456+
doc.memory.getBytes(),
457+
"request_count",
458+
doc.requestCount
459+
)
460+
.get();
461+
}
462+
client().admin().indices().prepareRefresh("old-hosts").get();
463+
try (var resp1 = run("""
464+
TS hosts,old-hosts
465+
| STATS sum(rate(request_count)), max(last_over_time(cpu)), max(last_over_time(memory)) BY cluster, host
466+
| SORT cluster, host
467+
| DROP `sum(rate(request_count))`
468+
""")) {
469+
try (var resp2 = run("""
470+
TS hosts
471+
| STATS sum(rate(request_count)), max(last_over_time(cpu)), max(last_over_time(memory)) BY cluster, host
472+
| SORT cluster, host
473+
| DROP `sum(rate(request_count))`
474+
""")) {
475+
List<List<Object>> values1 = EsqlTestUtils.getValuesList(resp1);
476+
List<List<Object>> values2 = EsqlTestUtils.getValuesList(resp2);
477+
assertThat(values1, equalTo(values2));
478+
}
479+
}
480+
}
481+
420482
public void testProfile() {
421483
EsqlQueryRequest request = new EsqlQueryRequest();
422484
request.profile(true);

0 commit comments

Comments
 (0)