Skip to content

Commit 5ffdc46

Browse files
authored
Merge branch 'main' into fix-missing-index-handling
2 parents 39141cc + 4ea4a29 commit 5ffdc46

File tree

19 files changed

+287
-42
lines changed

19 files changed

+287
-42
lines changed

docs/changelog/126550.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 126550
2+
summary: Add leniency to missing array values in mustache
3+
area: Infra/Scripting
4+
type: bug
5+
issues:
6+
- 55200

modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/CustomReflectionObjectHandler.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ public Object get(Object key) {
111111
if ("size".equals(key)) {
112112
return size();
113113
} else if (key instanceof Number number) {
114-
return Array.get(array, number.intValue());
114+
return number.intValue() >= 0 && number.intValue() < length ? Array.get(array, number.intValue()) : null;
115115
}
116116
try {
117117
int index = Integer.parseInt(key.toString());
118-
return Array.get(array, index);
118+
return index >= 0 && index < length ? Array.get(array, index) : null;
119119
} catch (NumberFormatException nfe) {
120120
// if it's not a number it is as if the key doesn't exist
121121
return null;
@@ -169,11 +169,11 @@ public Object get(Object key) {
169169
if ("size".equals(key)) {
170170
return col.size();
171171
} else if (key instanceof Number number) {
172-
return Iterables.get(col, number.intValue());
172+
return number.intValue() >= 0 && number.intValue() < col.size() ? Iterables.get(col, number.intValue()) : null;
173173
}
174174
try {
175175
int index = Integer.parseInt(key.toString());
176-
return Iterables.get(col, index);
176+
return index >= 0 && index < col.size() ? Iterables.get(col, index) : null;
177177
} catch (NumberFormatException nfe) {
178178
// if it's not a number it is as if the key doesn't exist
179179
return null;

modules/lang-mustache/src/test/java/org/elasticsearch/script/mustache/MustacheTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,26 @@ public void testMapInArrayAccess() throws Exception {
141141
assertThat(output, both(containsString("foo")).and(containsString("bar")));
142142
}
143143

144+
public void testMapInArrayBadAccess() throws Exception {
145+
String template = "{{data.0.key}} {{data.2.key}}";
146+
TemplateScript.Factory factory = engine.compile(null, template, TemplateScript.CONTEXT, Collections.emptyMap());
147+
Map<String, Object> vars = new HashMap<>();
148+
vars.put("data", new Object[] { singletonMap("key", "foo"), singletonMap("key", "bar") });
149+
// assertThat(factory.newInstance(vars).execute(), equalTo("foo "));
150+
151+
vars.put("data", Arrays.asList(singletonMap("key", "foo"), singletonMap("key", "bar")));
152+
factory.newInstance(vars);
153+
assertThat(factory.newInstance(vars).execute(), equalTo("foo "));
154+
155+
// HashSet iteration order isn't fixed
156+
Set<Object> setData = new HashSet<>();
157+
setData.add(singletonMap("key", "foo"));
158+
setData.add(singletonMap("key", "bar"));
159+
vars.put("data", setData);
160+
String output = factory.newInstance(vars).execute();
161+
assertThat(output, both(containsString("foo")).and(not(containsString("bar"))));
162+
}
163+
144164
public void testSizeAccessForCollectionsAndArrays() throws Exception {
145165
String[] randomArrayValues = generateRandomStringArray(10, 20, false);
146166
List<String> randomList = Arrays.asList(generateRandomStringArray(10, 20, false));

muted-tests.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,21 @@ tests:
423423
- class: org.elasticsearch.smoketest.MlWithSecurityIT
424424
method: test {yaml=ml/get_trained_model_stats/Test get stats given trained models}
425425
issue: https://github.com/elastic/elasticsearch/issues/126510
426+
- class: org.elasticsearch.xpack.test.rest.XPackRestIT
427+
method: test {p0=transform/transforms_stats/Test get multiple transform stats}
428+
issue: https://github.com/elastic/elasticsearch/issues/126567
429+
- class: org.elasticsearch.xpack.test.rest.XPackRestIT
430+
method: test {p0=transform/transforms_stats/Test get single transform stats when it does not have a task}
431+
issue: https://github.com/elastic/elasticsearch/issues/126568
432+
- class: org.elasticsearch.repositories.blobstore.testkit.rest.SnapshotRepoTestKitClientYamlTestSuiteIT
433+
method: test {p0=/10_analyze/Analysis without details}
434+
issue: https://github.com/elastic/elasticsearch/issues/126569
435+
- class: org.elasticsearch.xpack.esql.qa.single_node.GenerativeIT
436+
method: test
437+
issue: https://github.com/elastic/elasticsearch/issues/126573
438+
- class: org.elasticsearch.repositories.blobstore.testkit.analyze.S3RepositoryAnalysisRestIT
439+
method: testRepositoryAnalysis
440+
issue: https://github.com/elastic/elasticsearch/issues/126576
426441

427442
# Examples:
428443
#

x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,22 @@ null | three | 2024-05-10T00:05:00.000
164164
null | three | 2024-05-10T00:02:00.000Z
165165
null | three | 2024-05-10T00:01:00.000Z
166166
;
167+
168+
169+
max_over_time
170+
required_capability: metrics_command
171+
required_capability: max_over_time
172+
TS k8s | STATS cost=sum(max_over_time(network.cost)) BY cluster, time_bucket = bucket(@timestamp,1minute) | SORT cost DESC, time_bucket DESC, cluster | LIMIT 10;
173+
174+
cost:double | cluster:keyword | time_bucket:datetime
175+
32.75 | qa | 2024-05-10T00:17:00.000Z
176+
32.25 | staging | 2024-05-10T00:09:00.000Z
177+
31.75 | qa | 2024-05-10T00:06:00.000Z
178+
29.0 | prod | 2024-05-10T00:19:00.000Z
179+
28.625 | qa | 2024-05-10T00:09:00.000Z
180+
24.625 | qa | 2024-05-10T00:18:00.000Z
181+
23.25 | qa | 2024-05-10T00:11:00.000Z
182+
23.125 | staging | 2024-05-10T00:08:00.000Z
183+
22.75 | prod | 2024-05-10T00:13:00.000Z
184+
22.75 | qa | 2024-05-10T00:08:00.000Z
185+
;

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,12 @@ public enum Cap {
959959
/**
960960
* Listing queries and getting information on a specific query.
961961
*/
962-
QUERY_MONITORING;
962+
QUERY_MONITORING,
963+
964+
/**
965+
* Support max_over_time aggregation
966+
*/
967+
MAX_OVER_TIME(Build.current().isSnapshot());
963968

964969
private final boolean enabled;
965970

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.elasticsearch.xpack.esql.expression.function.aggregate.Count;
2121
import org.elasticsearch.xpack.esql.expression.function.aggregate.CountDistinct;
2222
import org.elasticsearch.xpack.esql.expression.function.aggregate.Max;
23+
import org.elasticsearch.xpack.esql.expression.function.aggregate.MaxOverTime;
2324
import org.elasticsearch.xpack.esql.expression.function.aggregate.Median;
2425
import org.elasticsearch.xpack.esql.expression.function.aggregate.MedianAbsoluteDeviation;
2526
import org.elasticsearch.xpack.esql.expression.function.aggregate.Min;
@@ -432,6 +433,7 @@ private static FunctionDefinition[][] snapshotFunctions() {
432433
// This is an experimental function and can be removed without notice.
433434
def(Delay.class, Delay::new, "delay"),
434435
def(Rate.class, Rate::withUnresolvedTimestamp, "rate"),
436+
def(MaxOverTime.class, uni(MaxOverTime::new), "max_over_time"),
435437
def(Term.class, bi(Term::new), "term") } };
436438
}
437439

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AggregateWritables.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public static List<NamedWriteableRegistry.Entry> getNamedWriteables() {
3030
Sum.ENTRY,
3131
Top.ENTRY,
3232
Values.ENTRY,
33+
MaxOverTime.ENTRY,
3334
// internal functions
3435
ToPartial.ENTRY,
3536
FromPartial.ENTRY,
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.expression.function.aggregate;
9+
10+
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
11+
import org.elasticsearch.common.io.stream.StreamInput;
12+
import org.elasticsearch.xpack.esql.core.expression.Expression;
13+
import org.elasticsearch.xpack.esql.core.expression.Literal;
14+
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
15+
import org.elasticsearch.xpack.esql.core.tree.Source;
16+
import org.elasticsearch.xpack.esql.core.type.DataType;
17+
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
18+
import org.elasticsearch.xpack.esql.expression.function.FunctionType;
19+
import org.elasticsearch.xpack.esql.expression.function.Param;
20+
21+
import java.io.IOException;
22+
import java.util.List;
23+
24+
import static java.util.Collections.emptyList;
25+
26+
/**
27+
* Similar to {@link Max}, but it is used to calculate the maximum value over a time series of values from the given field.
28+
*/
29+
public class MaxOverTime extends TimeSeriesAggregateFunction {
30+
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
31+
Expression.class,
32+
"MaxOverTime",
33+
MaxOverTime::new
34+
);
35+
36+
@FunctionInfo(
37+
returnType = { "boolean", "double", "integer", "long", "date", "date_nanos", "ip", "keyword", "long", "version" },
38+
description = "The maximum over time value of a field.",
39+
type = FunctionType.AGGREGATE
40+
)
41+
public MaxOverTime(
42+
Source source,
43+
@Param(
44+
name = "field",
45+
type = {
46+
"aggregate_metric_double",
47+
"boolean",
48+
"double",
49+
"integer",
50+
"long",
51+
"date",
52+
"date_nanos",
53+
"ip",
54+
"keyword",
55+
"text",
56+
"long",
57+
"version" }
58+
) Expression field
59+
) {
60+
this(source, field, Literal.TRUE);
61+
}
62+
63+
public MaxOverTime(Source source, Expression field, Expression filter) {
64+
super(source, field, filter, emptyList());
65+
}
66+
67+
private MaxOverTime(StreamInput in) throws IOException {
68+
super(in);
69+
}
70+
71+
@Override
72+
public String getWriteableName() {
73+
return ENTRY.name;
74+
}
75+
76+
@Override
77+
public MaxOverTime withFilter(Expression filter) {
78+
return new MaxOverTime(source(), field(), filter);
79+
}
80+
81+
@Override
82+
protected NodeInfo<MaxOverTime> info() {
83+
return NodeInfo.create(this, MaxOverTime::new, field(), filter());
84+
}
85+
86+
@Override
87+
public MaxOverTime replaceChildren(List<Expression> newChildren) {
88+
return new MaxOverTime(source(), newChildren.get(0), newChildren.get(1));
89+
}
90+
91+
@Override
92+
protected TypeResolution resolveType() {
93+
return perTimeSeriesAggregation().resolveType();
94+
}
95+
96+
@Override
97+
public DataType dataType() {
98+
return perTimeSeriesAggregation().dataType();
99+
}
100+
101+
@Override
102+
public Max perTimeSeriesAggregation() {
103+
return new Max(source(), field(), filter());
104+
}
105+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Rate.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@
2525
import org.elasticsearch.xpack.esql.expression.function.OptionalArgument;
2626
import org.elasticsearch.xpack.esql.expression.function.Param;
2727
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
28-
import org.elasticsearch.xpack.esql.planner.ToTimeSeriesAggregator;
28+
import org.elasticsearch.xpack.esql.planner.ToAggregator;
2929

3030
import java.io.IOException;
3131
import java.util.List;
3232

3333
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
3434
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
3535

36-
public class Rate extends AggregateFunction implements OptionalArgument, ToTimeSeriesAggregator {
36+
public class Rate extends TimeSeriesAggregateFunction implements OptionalArgument, ToAggregator {
3737
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Rate", Rate::new);
3838

3939
private final Expression timestamp;
@@ -119,6 +119,11 @@ public AggregatorFunctionSupplier supplier() {
119119
};
120120
}
121121

122+
@Override
123+
public Rate perTimeSeriesAggregation() {
124+
return this;
125+
}
126+
122127
@Override
123128
public String toString() {
124129
return "rate(" + field() + ")";

0 commit comments

Comments
 (0)