Skip to content

Commit 7256954

Browse files
ESQL: Properly handle multi-values in fold() and date math (elastic#100766) (elastic#100804)
1 parent bf64b80 commit 7256954

File tree

7 files changed

+80
-13
lines changed

7 files changed

+80
-13
lines changed

docs/changelog/100766.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 100766
2+
summary: "ESQL: Properly handle multi-values in fold() and date math"
3+
area: ES|QL
4+
type: bug
5+
issues:
6+
- 100497

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,10 @@ Parto. |Parto.Bamford |Parto.BamfordParto. |Parto
199199
Chirstian. |Chirstian.Koblick|Chirstian.KoblickChirstian.|Chirstian
200200
Kyoichi. |Kyoichi.Maliniak |Kyoichi.MaliniakKyoichi. |Kyoichi
201201
;
202+
203+
roundArrays
204+
row a = [1.2], b = [2.4, 7.9] | eval c = round(a), d = round(b), e = round([1.2]), f = round([1.2, 4.6]), g = round([1.14], 1), h = round([1.14], [1, 2]);
205+
206+
a:double | b:double | c:double | d: double | e:double | f:double | g:double | h:double
207+
1.2 | [2.4, 7.9] | 1.0 | null | 1.0 | null | 1.1 | null
208+
;

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Round.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.ParamOrdinal.SECOND;
3535
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isInteger;
3636
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isNumeric;
37-
import static org.elasticsearch.xpack.ql.type.DataTypeConverter.safeToLong;
3837
import static org.elasticsearch.xpack.ql.util.NumericUtils.asLongUnsigned;
3938
import static org.elasticsearch.xpack.ql.util.NumericUtils.asUnsignedLong;
4039
import static org.elasticsearch.xpack.ql.util.NumericUtils.unsignedLongAsNumber;
@@ -70,15 +69,7 @@ public boolean foldable() {
7069

7170
@Override
7271
public Object fold() {
73-
if (field.dataType() == DataTypes.UNSIGNED_LONG) {
74-
return decimals == null
75-
? field.fold()
76-
: processUnsignedLong(safeToLong((Number) field.fold()), safeToLong((Number) decimals.fold()));
77-
}
78-
if (decimals == null) {
79-
return Maths.round((Number) field.fold(), 0L);
80-
}
81-
return Maths.round((Number) field.fold(), ((Number) decimals.fold()).longValue());
72+
return EvaluatorMapper.super.fold();
8273
}
8374

8475
@Evaluator(extraName = "DoubleNoDecimals")

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/DateTimeArithmeticOperation.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.time.Duration;
2020
import java.time.Period;
2121
import java.time.temporal.TemporalAmount;
22+
import java.util.Collection;
2223
import java.util.function.Function;
2324

2425
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
@@ -106,10 +107,13 @@ public final Object fold() {
106107
DataType rightDataType = right().dataType();
107108
if (leftDataType == DATE_PERIOD && rightDataType == DATE_PERIOD) {
108109
// Both left and right expressions are temporal amounts; we can assume they are both foldable.
109-
Period l = (Period) left().fold();
110-
Period r = (Period) right().fold();
110+
var l = left().fold();
111+
var r = right().fold();
112+
if (l instanceof Collection<?> || r instanceof Collection<?>) {
113+
return null;
114+
}
111115
try {
112-
return fold(l, r);
116+
return fold((Period) l, (Period) r);
113117
} catch (ArithmeticException e) {
114118
// Folding will be triggered before the plan is sent to the compute service, so we have to handle arithmetic exceptions
115119
// manually and provide a user-friendly error message.

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import static org.elasticsearch.compute.data.BlockUtils.toJavaObject;
2626
import static org.hamcrest.Matchers.equalTo;
27+
import static org.hamcrest.Matchers.is;
28+
import static org.hamcrest.Matchers.nullValue;
2729

2830
public class RoundTests extends AbstractScalarFunctionTestCase {
2931
public RoundTests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> testCaseSupplier) {
@@ -44,6 +46,31 @@ public static Iterable<Object[]> parameters() {
4446
DataTypes.DOUBLE,
4547
equalTo(Maths.round(number, precision))
4648
);
49+
}), new TestCaseSupplier("round([<double>], <int>)", () -> {
50+
double number = 1 / randomDouble();
51+
int precision = between(-30, 30);
52+
return new TestCaseSupplier.TestCase(
53+
List.of(
54+
new TestCaseSupplier.TypedData(List.of(number), DataTypes.DOUBLE, "number"),
55+
new TestCaseSupplier.TypedData(precision, DataTypes.INTEGER, "precision")
56+
),
57+
"RoundDoubleEvaluator[val=Attribute[channel=0], decimals=CastIntToLongEvaluator[v=Attribute[channel=1]]]",
58+
DataTypes.DOUBLE,
59+
equalTo(Maths.round(number, precision))
60+
);
61+
}), new TestCaseSupplier("round([<double>], <int>)", () -> {
62+
double number1 = 1 / randomDouble();
63+
double number2 = 1 / randomDouble();
64+
int precision = between(-30, 30);
65+
return new TestCaseSupplier.TestCase(
66+
List.of(
67+
new TestCaseSupplier.TypedData(List.of(number1, number2), DataTypes.DOUBLE, "number"),
68+
new TestCaseSupplier.TypedData(precision, DataTypes.INTEGER, "precision")
69+
),
70+
"RoundDoubleEvaluator[val=Attribute[channel=0], decimals=CastIntToLongEvaluator[v=Attribute[channel=1]]]",
71+
DataTypes.DOUBLE,
72+
is(nullValue())
73+
);
4774
})));
4875
}
4976

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/AddTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import static org.elasticsearch.xpack.ql.util.NumericUtils.asLongUnsigned;
3333
import static org.elasticsearch.xpack.ql.util.NumericUtils.unsignedLongAsBigInteger;
3434
import static org.hamcrest.Matchers.equalTo;
35+
import static org.hamcrest.Matchers.is;
36+
import static org.hamcrest.Matchers.nullValue;
3537

3638
public class AddTests extends AbstractDateTimeArithmeticTestCase {
3739
public AddTests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> testCaseSupplier) {
@@ -164,6 +166,20 @@ public static Iterable<Object[]> parameters() {
164166
EsqlDataTypes.TIME_DURATION,
165167
equalTo(lhs.plus(rhs))
166168
);
169+
}), new TestCaseSupplier("MV", () -> {
170+
// Ensure we don't have an overflow
171+
int rhs = randomIntBetween((Integer.MIN_VALUE >> 1) - 1, (Integer.MAX_VALUE >> 1) - 1);
172+
int lhs = randomIntBetween((Integer.MIN_VALUE >> 1) - 1, (Integer.MAX_VALUE >> 1) - 1);
173+
int lhs2 = randomIntBetween((Integer.MIN_VALUE >> 1) - 1, (Integer.MAX_VALUE >> 1) - 1);
174+
return new TestCaseSupplier.TestCase(
175+
List.of(
176+
new TestCaseSupplier.TypedData(List.of(lhs, lhs2), DataTypes.INTEGER, "lhs"),
177+
new TestCaseSupplier.TypedData(rhs, DataTypes.INTEGER, "rhs")
178+
),
179+
"AddIntsEvaluator[lhs=Attribute[channel=0], rhs=Attribute[channel=1]]",
180+
DataTypes.INTEGER,
181+
is(nullValue())
182+
);
167183
})));
168184
}
169185

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/SubTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import static org.elasticsearch.xpack.ql.util.NumericUtils.asLongUnsigned;
3333
import static org.elasticsearch.xpack.ql.util.NumericUtils.unsignedLongAsBigInteger;
3434
import static org.hamcrest.Matchers.equalTo;
35+
import static org.hamcrest.Matchers.is;
36+
import static org.hamcrest.Matchers.nullValue;
3537

3638
public class SubTests extends AbstractDateTimeArithmeticTestCase {
3739
public SubTests(@Name("TestCase") Supplier<TestCaseSupplier.TestCase> testCaseSupplier) {
@@ -140,6 +142,20 @@ public static Iterable<Object[]> parameters() {
140142
EsqlDataTypes.TIME_DURATION,
141143
equalTo(lhs.minus(rhs))
142144
);
145+
}), new TestCaseSupplier("MV", () -> {
146+
// Ensure we don't have an overflow
147+
int rhs = randomIntBetween((Integer.MIN_VALUE >> 1) - 1, (Integer.MAX_VALUE >> 1) - 1);
148+
int lhs = randomIntBetween((Integer.MIN_VALUE >> 1) - 1, (Integer.MAX_VALUE >> 1) - 1);
149+
int lhs2 = randomIntBetween((Integer.MIN_VALUE >> 1) - 1, (Integer.MAX_VALUE >> 1) - 1);
150+
return new TestCaseSupplier.TestCase(
151+
List.of(
152+
new TestCaseSupplier.TypedData(List.of(lhs, lhs2), DataTypes.INTEGER, "lhs"),
153+
new TestCaseSupplier.TypedData(rhs, DataTypes.INTEGER, "rhs")
154+
),
155+
"SubIntsEvaluator[lhs=Attribute[channel=0], rhs=Attribute[channel=1]]",
156+
DataTypes.INTEGER,
157+
is(nullValue())
158+
);
143159
})));
144160
}
145161

0 commit comments

Comments
 (0)