Просмотр исходного кода

SQL: handle NULL arithmetic operations with INTERVALs (#49633)

Andrei Stefan 5 лет назад
Родитель
Сommit
ce727615c0
15 измененных файлов с 86 добавлено и 26 удалено
  1. 1 1
      docs/reference/sql/functions/date-time.asciidoc
  2. 9 0
      x-pack/plugin/sql/qa/src/main/resources/arithmetic.csv-spec
  3. 9 0
      x-pack/plugin/sql/qa/src/main/resources/datetime-interval.csv-spec
  4. 3 4
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/TypeResolutions.java
  5. 1 2
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Cast.java
  6. 1 1
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/conditional/Case.java
  7. 2 3
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/nulls/IsNotNull.java
  8. 2 3
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/nulls/IsNull.java
  9. 1 1
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/arithmetic/DateTimeArithmeticOperation.java
  10. 3 3
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/predicate/operator/arithmetic/Mul.java
  11. 1 2
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/querydsl/query/TermsQuery.java
  12. 12 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataType.java
  13. 2 2
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypeConversion.java
  14. 0 4
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypes.java
  15. 39 0
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/predicate/operator/arithmetic/BinaryArithmeticTests.java

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

@@ -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]
 --------------------------------------------------

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

@@ -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           
+;

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

@@ -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;

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

@@ -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) + " ",

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

@@ -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();

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

@@ -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;
             }

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

@@ -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

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

@@ -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

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

@@ -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;

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

@@ -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;
         }

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

@@ -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 {

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

@@ -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;
     }

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

@@ -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()) {

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

@@ -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;
     }

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

@@ -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) {