SQL: handle NULL arithmetic operations with INTERVALs (#49633)
(cherry picked from commit ce727615c08cf5ae422feb77f69ea24fb53cd9d1)
This commit is contained in:
parent
34311dd818
commit
e2982b2110
|
@ -55,7 +55,7 @@ s|Description
|
|||
|
||||
==== Operators
|
||||
|
||||
Basic arithmetic operators (`+`, `-`, etc) support date/time parameters as indicated below:
|
||||
Basic arithmetic operators (`+`, `-`, `*`) support date/time parameters as indicated below:
|
||||
|
||||
[source, sql]
|
||||
--------------------------------------------------
|
||||
|
|
|
@ -18,3 +18,12 @@ SELECT 5 - 2 x FROM test_emp LIMIT 5;
|
|||
3
|
||||
;
|
||||
|
||||
|
||||
nullArithmetics
|
||||
schema::a:i|b:d|c:s|d:s|e:l|f:i|g:i|h:i|i:i|j:i|k:d
|
||||
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;
|
||||
|
||||
a | b | c | d | e | f | g | h | i | j | k
|
||||
---------------+---------------+---------------+---------------+---------------+---------------+---------------+---------------+---------------+---------------+---------------
|
||||
null |null |null |null |null |null |null |null |null |null |null
|
||||
;
|
||||
|
|
|
@ -191,6 +191,15 @@ SELECT 4 * -INTERVAL '2' HOURS AS result1, -5 * -INTERVAL '3' HOURS AS result2;
|
|||
-0 08:00:00.0 | +0 15:00:00.0
|
||||
;
|
||||
|
||||
intervalNullMath
|
||||
schema::null_multiply:string|null_sub1:string|null_sub2:string|null_add:string
|
||||
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;
|
||||
|
||||
null_multiply | null_sub1 | null_sub2 | null_add
|
||||
-----------------+-------------+-------------+-------------
|
||||
null |null |null |null
|
||||
;
|
||||
|
||||
intervalAndFieldMultiply
|
||||
schema::languages:byte|result:string
|
||||
SELECT languages, CAST (languages * INTERVAL '1 10:30' DAY TO MINUTES AS string) AS result FROM test_emp ORDER BY emp_no LIMIT 5;
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.sql.expression;
|
||||
|
||||
import org.elasticsearch.xpack.sql.expression.Expression.TypeResolution;
|
||||
import org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
|
||||
import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import org.elasticsearch.xpack.sql.type.DataTypes;
|
||||
import org.elasticsearch.xpack.sql.type.EsField;
|
||||
|
||||
import java.util.Locale;
|
||||
|
@ -14,8 +15,6 @@ import java.util.StringJoiner;
|
|||
import java.util.function.Predicate;
|
||||
|
||||
import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
|
||||
import static org.elasticsearch.xpack.sql.expression.Expression.TypeResolution;
|
||||
import static org.elasticsearch.xpack.sql.expression.Expressions.ParamOrdinal;
|
||||
import static org.elasticsearch.xpack.sql.expression.Expressions.name;
|
||||
import static org.elasticsearch.xpack.sql.type.DataType.BOOLEAN;
|
||||
|
||||
|
@ -119,7 +118,7 @@ public final class TypeResolutions {
|
|||
String operationName,
|
||||
ParamOrdinal paramOrd,
|
||||
String... acceptedTypes) {
|
||||
return predicate.test(e.dataType()) || DataTypes.isNull(e.dataType())?
|
||||
return predicate.test(e.dataType()) || e.dataType().isNull() ?
|
||||
TypeResolution.TYPE_RESOLVED :
|
||||
new TypeResolution(format(null, "{}argument of [{}] must be [{}], found value [{}] type [{}]",
|
||||
paramOrd == null || paramOrd == ParamOrdinal.DEFAULT ? "" : paramOrd.name().toLowerCase(Locale.ROOT) + " ",
|
||||
|
|
|
@ -13,7 +13,6 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
|||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import org.elasticsearch.xpack.sql.type.DataTypeConversion;
|
||||
import org.elasticsearch.xpack.sql.type.DataTypes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
@ -64,7 +63,7 @@ public class Cast extends UnaryScalarFunction {
|
|||
|
||||
@Override
|
||||
public Nullability nullable() {
|
||||
if (DataTypes.isNull(from())) {
|
||||
if (from().isNull()) {
|
||||
return Nullability.TRUE;
|
||||
}
|
||||
return field().nullable();
|
||||
|
|
|
@ -78,7 +78,7 @@ public class Case extends ConditionalFunction {
|
|||
protected TypeResolution resolveType() {
|
||||
DataType expectedResultDataType = null;
|
||||
for (IfConditional ifConditional : conditions) {
|
||||
if (DataTypes.isNull(ifConditional.result().dataType()) == false) {
|
||||
if (ifConditional.result().dataType().isNull() == false) {
|
||||
expectedResultDataType = ifConditional.result().dataType();
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -12,10 +12,9 @@ import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
|
|||
import org.elasticsearch.xpack.sql.expression.gen.script.Scripts;
|
||||
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
|
||||
import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import org.elasticsearch.xpack.sql.type.DataTypes;
|
||||
|
||||
public class IsNotNull extends UnaryScalarFunction implements Negatable<UnaryScalarFunction> {
|
||||
|
||||
|
@ -35,7 +34,7 @@ public class IsNotNull extends UnaryScalarFunction implements Negatable<UnarySca
|
|||
|
||||
@Override
|
||||
public Object fold() {
|
||||
return field().fold() != null && !DataTypes.isNull(field().dataType());
|
||||
return field().fold() != null && !field().dataType().isNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -12,10 +12,9 @@ import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
|
|||
import org.elasticsearch.xpack.sql.expression.gen.script.Scripts;
|
||||
import org.elasticsearch.xpack.sql.expression.predicate.Negatable;
|
||||
import org.elasticsearch.xpack.sql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.tree.NodeInfo;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.type.DataType;
|
||||
import org.elasticsearch.xpack.sql.type.DataTypes;
|
||||
|
||||
public class IsNull extends UnaryScalarFunction implements Negatable<UnaryScalarFunction> {
|
||||
|
||||
|
@ -35,7 +34,7 @@ public class IsNull extends UnaryScalarFunction implements Negatable<UnaryScalar
|
|||
|
||||
@Override
|
||||
public Object fold() {
|
||||
return field().fold() == null || DataTypes.isNull(field().dataType());
|
||||
return field().fold() == null || field().dataType().isNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -56,7 +56,7 @@ abstract class DateTimeArithmeticOperation extends ArithmeticOperation {
|
|||
DataType l = left().dataType();
|
||||
DataType r = right().dataType();
|
||||
|
||||
if (!(r.isDateOrTimeBased() || r.isInterval())|| !(l.isDateOrTimeBased() || l.isInterval())) {
|
||||
if (!(r.isDateOrTimeBased() || r.isInterval() || r.isNull())|| !(l.isDateOrTimeBased() || l.isInterval() || l.isNull())) {
|
||||
return new TypeResolution(format(null, "[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
|
||||
}
|
||||
return TypeResolution.TYPE_RESOLVED;
|
||||
|
|
|
@ -34,14 +34,14 @@ public class Mul extends ArithmeticOperation {
|
|||
DataType r = right().dataType();
|
||||
|
||||
// 1. both are numbers
|
||||
if (l.isNumeric() && r.isNumeric()) {
|
||||
if (l.isNullOrNumeric() && r.isNullOrNumeric()) {
|
||||
return TypeResolution.TYPE_RESOLVED;
|
||||
}
|
||||
|
||||
if (l.isInterval() && r.isInteger()) {
|
||||
if (l.isNullOrInterval() && (r.isInteger() || r.isNull())) {
|
||||
dataType = l;
|
||||
return TypeResolution.TYPE_RESOLVED;
|
||||
} else if (r.isInterval() && l.isInteger()) {
|
||||
} else if (r.isNullOrInterval() && (l.isInteger() || l.isNull())) {
|
||||
dataType = r;
|
||||
return TypeResolution.TYPE_RESOLVED;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import org.elasticsearch.index.query.QueryBuilder;
|
|||
import org.elasticsearch.xpack.sql.expression.Expression;
|
||||
import org.elasticsearch.xpack.sql.expression.Foldables;
|
||||
import org.elasticsearch.xpack.sql.tree.Source;
|
||||
import org.elasticsearch.xpack.sql.type.DataTypes;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -27,7 +26,7 @@ public class TermsQuery extends LeafQuery {
|
|||
public TermsQuery(Source source, String term, List<Expression> values) {
|
||||
super(source);
|
||||
this.term = term;
|
||||
values.removeIf(e -> DataTypes.isNull(e.dataType()));
|
||||
values.removeIf(e -> e.dataType().isNull());
|
||||
if (values.isEmpty()) {
|
||||
this.values = Collections.emptySet();
|
||||
} else {
|
||||
|
|
|
@ -247,6 +247,18 @@ public enum DataType {
|
|||
return isNumeric();
|
||||
}
|
||||
|
||||
public boolean isNull() {
|
||||
return this == NULL;
|
||||
}
|
||||
|
||||
public boolean isNullOrNumeric() {
|
||||
return isNull() || isNumeric();
|
||||
}
|
||||
|
||||
public boolean isNullOrInterval() {
|
||||
return isNull() || isInterval();
|
||||
}
|
||||
|
||||
public boolean isString() {
|
||||
return this == KEYWORD || this == TEXT;
|
||||
}
|
||||
|
|
|
@ -45,10 +45,10 @@ public abstract class DataTypeConversion {
|
|||
if (left == right) {
|
||||
return left;
|
||||
}
|
||||
if (DataTypes.isNull(left)) {
|
||||
if (left.isNull()) {
|
||||
return right;
|
||||
}
|
||||
if (DataTypes.isNull(right)) {
|
||||
if (right.isNull()) {
|
||||
return left;
|
||||
}
|
||||
if (left.isString() && right.isString()) {
|
||||
|
|
|
@ -31,10 +31,6 @@ public final class DataTypes {
|
|||
|
||||
private DataTypes() {}
|
||||
|
||||
public static boolean isNull(DataType from) {
|
||||
return from == NULL;
|
||||
}
|
||||
|
||||
public static boolean isUnsupported(DataType from) {
|
||||
return from == UNSUPPORTED;
|
||||
}
|
||||
|
|
|
@ -227,6 +227,45 @@ public class BinaryArithmeticTests extends ESTestCase {
|
|||
Period p = interval.interval();
|
||||
assertEquals(Period.ofYears(2).negated(), p);
|
||||
}
|
||||
|
||||
public void testMulNullInterval() {
|
||||
Literal literal = interval(Period.ofMonths(1), INTERVAL_MONTH);
|
||||
Mul result = new Mul(EMPTY, L(null), literal);
|
||||
assertTrue(result.foldable());
|
||||
assertNull(result.fold());
|
||||
assertEquals(INTERVAL_MONTH, result.dataType());
|
||||
|
||||
result = new Mul(EMPTY, literal, L(null));
|
||||
assertTrue(result.foldable());
|
||||
assertNull(result.fold());
|
||||
assertEquals(INTERVAL_MONTH, result.dataType());
|
||||
}
|
||||
|
||||
public void testAddNullInterval() {
|
||||
Literal literal = interval(Period.ofMonths(1), INTERVAL_MONTH);
|
||||
Add result = new Add(EMPTY, L(null), literal);
|
||||
assertTrue(result.foldable());
|
||||
assertNull(result.fold());
|
||||
assertEquals(INTERVAL_MONTH, result.dataType());
|
||||
|
||||
result = new Add(EMPTY, literal, L(null));
|
||||
assertTrue(result.foldable());
|
||||
assertNull(result.fold());
|
||||
assertEquals(INTERVAL_MONTH, result.dataType());
|
||||
}
|
||||
|
||||
public void testSubNullInterval() {
|
||||
Literal literal = interval(Period.ofMonths(1), INTERVAL_MONTH);
|
||||
Sub result = new Sub(EMPTY, L(null), literal);
|
||||
assertTrue(result.foldable());
|
||||
assertNull(result.fold());
|
||||
assertEquals(INTERVAL_MONTH, result.dataType());
|
||||
|
||||
result = new Sub(EMPTY, literal, L(null));
|
||||
assertTrue(result.foldable());
|
||||
assertNull(result.fold());
|
||||
assertEquals(INTERVAL_MONTH, result.dataType());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T add(Object l, Object r) {
|
||||
|
|
Loading…
Reference in New Issue