Skip to content

Commit 5c21635

Browse files
astefanSivagurunathanV
authored andcommitted
SQL: handle NULL arithmetic operations with INTERVALs (elastic#49633)
1 parent 37c8487 commit 5c21635

File tree

15 files changed

+86
-26
lines changed

15 files changed

+86
-26
lines changed

docs/reference/sql/functions/date-time.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ s|Description
5555

5656
==== Operators
5757

58-
Basic arithmetic operators (`+`, `-`, etc) support date/time parameters as indicated below:
58+
Basic arithmetic operators (`+`, `-`, `*`) support date/time parameters as indicated below:
5959

6060
[source, sql]
6161
--------------------------------------------------

x-pack/plugin/sql/qa/src/main/resources/arithmetic.csv-spec

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ SELECT 5 - 2 x FROM test_emp LIMIT 5;
1818
3
1919
;
2020

21+
22+
nullArithmetics
23+
schema::a:i|b:d|c:s|d:s|e:l|f:i|g:i|h:i|i:i|j:i|k:d
24+
SELECT null + 2 AS a, null * 1.5 AS b, null + null AS c, null - null AS d, null - 1234567890123 AS e, 123 - null AS f, null / 5 AS g, 5 / null AS h, null % 5 AS i, 5 % null AS j, null + 5.5 - (null * (null * 3)) AS k;
25+
26+
a | b | c | d | e | f | g | h | i | j | k
27+
---------------+---------------+---------------+---------------+---------------+---------------+---------------+---------------+---------------+---------------+---------------
28+
null |null |null |null |null |null |null |null |null |null |null
29+
;

x-pack/plugin/sql/qa/src/main/resources/datetime-interval.csv-spec

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ SELECT 4 * -INTERVAL '2' HOURS AS result1, -5 * -INTERVAL '3' HOURS AS result2;
191191
-0 08:00:00.0 | +0 15:00:00.0
192192
;
193193

194+
intervalNullMath
195+
schema::null_multiply:string|null_sub1:string|null_sub2:string|null_add:string
196+
SELECT null * INTERVAL '1 23:45' DAY TO MINUTES AS null_multiply, INTERVAL '1' DAY - null AS null_sub1, null - INTERVAL '1' DAY AS null_sub2, INTERVAL 1 DAY + null AS null_add;
197+
198+
null_multiply | null_sub1 | null_sub2 | null_add
199+
-----------------+-------------+-------------+-------------
200+
null |null |null |null
201+
;
202+
194203
intervalAndFieldMultiply
195204
schema::languages:byte|result:string
196205
SELECT languages, CAST (languages * INTERVAL '1 10:30' DAY TO MINUTES AS string) AS result FROM test_emp ORDER BY emp_no LIMIT 5;

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/TypeResolutions.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@
55
*/
66
package org.elasticsearch.xpack.sql.expression;
77

8+
import org.elasticsearch.xpack.sql.expression.Expression.TypeResolution;
9+
import org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
810
import org.elasticsearch.xpack.sql.type.DataType;
9-
import org.elasticsearch.xpack.sql.type.DataTypes;
1011
import org.elasticsearch.xpack.sql.type.EsField;
1112

1213
import java.util.Locale;
1314
import java.util.StringJoiner;
1415
import java.util.function.Predicate;
1516

1617
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
17-
import static org.elasticsearch.xpack.sql.expression.Expression.TypeResolution;
18-
import static org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
1918
import static org.elasticsearch.xpack.sql.expression.Expressions.name;
2019
import static org.elasticsearch.xpack.sql.type.DataType.BOOLEAN;
2120

@@ -119,7 +118,7 @@ public static TypeResolution isType(Expression e,
119118
String operationName,
120119
ParamOrdinal paramOrd,
121120
String... acceptedTypes) {
122-
return predicate.test(e.dataType()) || DataTypes.isNull(e.dataType())?
121+
return predicate.test(e.dataType()) || e.dataType().isNull() ?
123122
TypeResolution.TYPE_RESOLVED :
124123
new TypeResolution(format(null, "{}argument of [{}] must be [{}], found value [{}] type [{}]",
125124
paramOrd == null || paramOrd == ParamOrdinal.DEFAULT ? "" : paramOrd.name().toLowerCase(Locale.ROOT) + " ",

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Cast.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.elasticsearch.xpack.sql.tree.Source;
1414
import org.elasticsearch.xpack.sql.type.DataType;
1515
import org.elasticsearch.xpack.sql.type.DataTypeConversion;
16-
import org.elasticsearch.xpack.sql.type.DataTypes;
1716

1817
import java.util.Objects;
1918

@@ -64,7 +63,7 @@ public Object fold() {
6463

6564
@Override
6665
public Nullability nullable() {
67-
if (DataTypes.isNull(from())) {
66+
if (from().isNull()) {
6867
return Nullability.TRUE;
6968
}
7069
return field().nullable();

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Case.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ protected NodeInfo<? extends Expression> info() {
7878
protected TypeResolution resolveType() {
7979
DataType expectedResultDataType = null;
8080
for (IfConditional ifConditional : conditions) {
81-
if (DataTypes.isNull(ifConditional.result().dataType()) == false) {
81+
if (ifConditional.result().dataType().isNull() == false) {
8282
expectedResultDataType = ifConditional.result().dataType();
8383
break;
8484
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/nulls/IsNotNull.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212
import org.elasticsearch.xpack.sql.expression.gen.script.Scripts;
1313
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
1414
import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
15-
import org.elasticsearch.xpack.sql.tree.Source;
1615
import org.elasticsearch.xpack.sql.tree.NodeInfo;
16+
import org.elasticsearch.xpack.sql.tree.Source;
1717
import org.elasticsearch.xpack.sql.type.DataType;
18-
import org.elasticsearch.xpack.sql.type.DataTypes;
1918

2019
public class IsNotNull extends UnaryScalarFunction implements Negatable<UnaryScalarFunction> {
2120

@@ -35,7 +34,7 @@ protected IsNotNull replaceChild(Expression newChild) {
3534

3635
@Override
3736
public Object fold() {
38-
return field().fold() != null && !DataTypes.isNull(field().dataType());
37+
return field().fold() != null && !field().dataType().isNull();
3938
}
4039

4140
@Override

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/nulls/IsNull.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212
import org.elasticsearch.xpack.sql.expression.gen.script.Scripts;
1313
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
1414
import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
15-
import org.elasticsearch.xpack.sql.tree.Source;
1615
import org.elasticsearch.xpack.sql.tree.NodeInfo;
16+
import org.elasticsearch.xpack.sql.tree.Source;
1717
import org.elasticsearch.xpack.sql.type.DataType;
18-
import org.elasticsearch.xpack.sql.type.DataTypes;
1918

2019
public class IsNull extends UnaryScalarFunction implements Negatable<UnaryScalarFunction> {
2120

@@ -35,7 +34,7 @@ protected IsNull replaceChild(Expression newChild) {
3534

3635
@Override
3736
public Object fold() {
38-
return field().fold() == null || DataTypes.isNull(field().dataType());
37+
return field().fold() == null || field().dataType().isNull();
3938
}
4039

4140
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ protected TypeResolution resolveWithIntervals() {
5656
DataType l = left().dataType();
5757
DataType r = right().dataType();
5858

59-
if (!(r.isDateOrTimeBased() || r.isInterval())|| !(l.isDateOrTimeBased() || l.isInterval())) {
59+
if (!(r.isDateOrTimeBased() || r.isInterval() || r.isNull())|| !(l.isDateOrTimeBased() || l.isInterval() || l.isNull())) {
6060
return new TypeResolution(format(null, "[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
6161
}
6262
return TypeResolution.TYPE_RESOLVED;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ protected TypeResolution resolveType() {
3434
DataType r = right().dataType();
3535

3636
// 1. both are numbers
37-
if (l.isNumeric() && r.isNumeric()) {
37+
if (l.isNullOrNumeric() && r.isNullOrNumeric()) {
3838
return TypeResolution.TYPE_RESOLVED;
3939
}
4040

41-
if (l.isInterval() && r.isInteger()) {
41+
if (l.isNullOrInterval() && (r.isInteger() || r.isNull())) {
4242
dataType = l;
4343
return TypeResolution.TYPE_RESOLVED;
44-
} else if (r.isInterval() && l.isInteger()) {
44+
} else if (r.isNullOrInterval() && (l.isInteger() || l.isNull())) {
4545
dataType = r;
4646
return TypeResolution.TYPE_RESOLVED;
4747
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/querydsl/query/TermsQuery.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import org.elasticsearch.xpack.sql.expression.Expression;
1010
import org.elasticsearch.xpack.sql.expression.Foldables;
1111
import org.elasticsearch.xpack.sql.tree.Source;
12-
import org.elasticsearch.xpack.sql.type.DataTypes;
1312

1413
import java.util.Collections;
1514
import java.util.LinkedHashSet;
@@ -27,7 +26,7 @@ public class TermsQuery extends LeafQuery {
2726
public TermsQuery(Source source, String term, List<Expression> values) {
2827
super(source);
2928
this.term = term;
30-
values.removeIf(e -> DataTypes.isNull(e.dataType()));
29+
values.removeIf(e -> e.dataType().isNull());
3130
if (values.isEmpty()) {
3231
this.values = Collections.emptySet();
3332
} else {

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,18 @@ public boolean isSigned() {
247247
return isNumeric();
248248
}
249249

250+
public boolean isNull() {
251+
return this == NULL;
252+
}
253+
254+
public boolean isNullOrNumeric() {
255+
return isNull() || isNumeric();
256+
}
257+
258+
public boolean isNullOrInterval() {
259+
return isNull() || isInterval();
260+
}
261+
250262
public boolean isString() {
251263
return this == KEYWORD || this == TEXT;
252264
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypeConversion.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ public static DataType commonType(DataType left, DataType right) {
4545
if (left == right) {
4646
return left;
4747
}
48-
if (DataTypes.isNull(left)) {
48+
if (left.isNull()) {
4949
return right;
5050
}
51-
if (DataTypes.isNull(right)) {
51+
if (right.isNull()) {
5252
return left;
5353
}
5454
if (left.isString() && right.isString()) {

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypes.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ public final class DataTypes {
3131

3232
private DataTypes() {}
3333

34-
public static boolean isNull(DataType from) {
35-
return from == NULL;
36-
}
37-
3834
public static boolean isUnsupported(DataType from) {
3935
return from == UNSUPPORTED;
4036
}

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,45 @@ public void testMulNumberInterval() {
227227
Period p = interval.interval();
228228
assertEquals(Period.ofYears(2).negated(), p);
229229
}
230+
231+
public void testMulNullInterval() {
232+
Literal literal = interval(Period.ofMonths(1), INTERVAL_MONTH);
233+
Mul result = new Mul(EMPTY, L(null), literal);
234+
assertTrue(result.foldable());
235+
assertNull(result.fold());
236+
assertEquals(INTERVAL_MONTH, result.dataType());
237+
238+
result = new Mul(EMPTY, literal, L(null));
239+
assertTrue(result.foldable());
240+
assertNull(result.fold());
241+
assertEquals(INTERVAL_MONTH, result.dataType());
242+
}
243+
244+
public void testAddNullInterval() {
245+
Literal literal = interval(Period.ofMonths(1), INTERVAL_MONTH);
246+
Add result = new Add(EMPTY, L(null), literal);
247+
assertTrue(result.foldable());
248+
assertNull(result.fold());
249+
assertEquals(INTERVAL_MONTH, result.dataType());
250+
251+
result = new Add(EMPTY, literal, L(null));
252+
assertTrue(result.foldable());
253+
assertNull(result.fold());
254+
assertEquals(INTERVAL_MONTH, result.dataType());
255+
}
256+
257+
public void testSubNullInterval() {
258+
Literal literal = interval(Period.ofMonths(1), INTERVAL_MONTH);
259+
Sub result = new Sub(EMPTY, L(null), literal);
260+
assertTrue(result.foldable());
261+
assertNull(result.fold());
262+
assertEquals(INTERVAL_MONTH, result.dataType());
263+
264+
result = new Sub(EMPTY, literal, L(null));
265+
assertTrue(result.foldable());
266+
assertNull(result.fold());
267+
assertEquals(INTERVAL_MONTH, result.dataType());
268+
}
230269

231270
@SuppressWarnings("unchecked")
232271
private static <T> T add(Object l, Object r) {

0 commit comments

Comments
 (0)