1
0
Эх сурвалжийг харах

SQL: make date/datetime and interval types compatible in conditional functions (#47595)

Andrei Stefan 6 жил өмнө
parent
commit
6ff953e639

+ 101 - 0
x-pack/plugin/sql/qa/src/main/resources/conditionals.csv-spec

@@ -348,3 +348,104 @@ SELECT CONVERT(IIF(languages > 1, IIF(languages = 3, '3')), SQL_BIGINT) AS cond
 3
 null
 ;
+
+ifNullWithCompatibleDateBasedValues
+schema::replacement:ts
+SELECT IFNULL(birth_date, {d '2110-04-12'}) AS replacement FROM test_emp GROUP BY 1 ORDER BY replacement DESC LIMIT 5;
+
+    replacement       
+------------------------
+2110-04-12T00:00:00.000Z
+1965-01-03T00:00:00.000Z
+1964-10-18T00:00:00.000Z
+1964-06-11T00:00:00.000Z
+1964-06-02T00:00:00.000Z
+;
+
+caseWithCompatibleIntervals_1
+schema::date_math:ts|c:l
+SELECT birth_date + (CASE WHEN gender='M' THEN INTERVAL 1 YEAR ELSE INTERVAL 6 MONTH END) AS date_math, COUNT(*) c FROM test_emp GROUP BY 1 ORDER BY 1 DESC LIMIT 5;
+
+      date_math         |       c      
+------------------------+---------------
+1966-01-03T00:00:00.000Z|1              
+1965-06-11T00:00:00.000Z|1              
+1965-04-18T00:00:00.000Z|2              
+1964-12-02T00:00:00.000Z|1              
+1964-11-26T00:00:00.000Z|1              
+;
+
+caseWithCompatibleIntervals_2
+SELECT hire_date, birth_date, (CASE WHEN birth_date > {d '1960-01-01'} THEN INTERVAL 1 YEAR ELSE INTERVAL 1 MONTH END) AS x FROM test_emp WHERE x + hire_date > {d '1995-01-01'} ORDER BY hire_date;
+
+       hire_date        |       birth_date       |       x       
+------------------------+------------------------+---------------
+1994-04-09T00:00:00.000Z|1962-11-07T00:00:00.000Z|+1-0           
+1995-01-27T00:00:00.000Z|1961-05-02T00:00:00.000Z|+1-0           
+1995-03-13T00:00:00.000Z|1957-04-04T00:00:00.000Z|+0-1           
+1995-03-20T00:00:00.000Z|1953-04-03T00:00:00.000Z|+0-1           
+1995-08-22T00:00:00.000Z|1952-07-08T00:00:00.000Z|+0-1           
+1995-12-15T00:00:00.000Z|1960-05-25T00:00:00.000Z|+1-0           
+1996-11-05T00:00:00.000Z|1964-06-11T00:00:00.000Z|+1-0           
+1997-05-19T00:00:00.000Z|1958-09-05T00:00:00.000Z|+0-1           
+1999-04-30T00:00:00.000Z|1953-01-23T00:00:00.000Z|+0-1    
+;       
+
+iifWithCompatibleIntervals
+schema::hire_date + IIF(salary > 70000, INTERVAL 2 HOURS, INTERVAL 2 DAYS):ts|salary:i
+SELECT hire_date + IIF(salary > 70000, INTERVAL 2 HOURS, INTERVAL 2 DAYS), salary FROM test_emp ORDER BY salary DESC LIMIT 10;
+
+hire_date + IIF(salary > 70000, INTERVAL 2 HOURS, INTERVAL 2 DAYS)|    salary     
+------------------------------------------------------------------+---------------
+1985-11-20T02:00:00.000Z                                          |74999          
+1989-09-02T02:00:00.000Z                                          |74970          
+1989-02-10T02:00:00.000Z                                          |74572          
+1989-07-07T02:00:00.000Z                                          |73851          
+1999-04-30T02:00:00.000Z                                          |73717          
+1988-10-18T02:00:00.000Z                                          |73578          
+1990-09-15T02:00:00.000Z                                          |71165          
+1987-03-18T02:00:00.000Z                                          |70011          
+1987-05-28T00:00:00.000Z                                          |69904          
+1990-02-18T00:00:00.000Z                                          |68547          
+;
+
+isNullWithIntervalMath
+SELECT ISNULL(birth_date, INTERVAL '23:45' HOUR TO MINUTES + {d '2019-09-17'}) AS c, salary, birth_date, hire_date FROM test_emp ORDER BY salary DESC LIMIT 5;
+
+         c:ts           |    salary:i     |      birth_date:ts     |       hire_date:ts        
+------------------------+-----------------+------------------------+------------------------
+1956-12-13T00:00:00.000Z|74999            |1956-12-13T00:00:00.000Z|1985-11-20T00:00:00.000Z
+2019-09-17T00:00:00.000Z|74970            |null                    |1989-09-02T00:00:00.000Z
+1957-05-23T00:00:00.000Z|74572            |1957-05-23T00:00:00.000Z|1989-02-10T00:00:00.000Z
+1962-07-10T00:00:00.000Z|73851            |1962-07-10T00:00:00.000Z|1989-07-07T00:00:00.000Z
+1953-01-23T00:00:00.000Z|73717            |1953-01-23T00:00:00.000Z|1999-04-30T00:00:00.000Z
+;
+
+coalesceWithCompatibleDateBasedTypes
+SELECT COALESCE(birth_date, CAST(birth_date AS DATE), CAST(hire_date AS DATETIME)) AS coalesce FROM test_emp ORDER BY 1 LIMIT 5;
+
+      coalesce:ts        
+------------------------
+1952-02-27T00:00:00.000Z
+1952-04-19T00:00:00.000Z
+1952-05-15T00:00:00.000Z
+1952-06-13T00:00:00.000Z
+1952-07-08T00:00:00.000Z
+;
+
+greatestWithCompatibleDateBasedTypes
+SELECT GREATEST(null, null, birth_date + INTERVAL 25 YEARS, hire_date + INTERVAL 2 DAYS, CAST(hire_date + INTERVAL 2 DAYS AS DATE)) AS greatest, birth_date, hire_date FROM test_emp ORDER BY 1 LIMIT 10;
+
+      greatest:ts       |      birth_date:ts     |      hire_date:ts        
+------------------------+------------------------+------------------------
+1985-02-20T00:00:00.000Z|1952-04-19T00:00:00.000Z|1985-02-18T00:00:00.000Z
+1985-02-26T00:00:00.000Z|null                    |1985-02-24T00:00:00.000Z
+1985-07-11T00:00:00.000Z|1952-06-13T00:00:00.000Z|1985-07-09T00:00:00.000Z
+1985-10-16T00:00:00.000Z|1955-08-20T00:00:00.000Z|1985-10-14T00:00:00.000Z
+1985-11-21T00:00:00.000Z|1957-12-03T00:00:00.000Z|1985-11-19T00:00:00.000Z
+1985-11-22T00:00:00.000Z|1956-12-13T00:00:00.000Z|1985-11-20T00:00:00.000Z
+1985-11-22T00:00:00.000Z|1959-04-07T00:00:00.000Z|1985-11-20T00:00:00.000Z
+1986-02-06T00:00:00.000Z|1954-09-13T00:00:00.000Z|1986-02-04T00:00:00.000Z
+1986-02-28T00:00:00.000Z|1952-11-13T00:00:00.000Z|1986-02-26T00:00:00.000Z
+1986-05-30T00:00:00.000Z|1961-05-30T00:00:00.000Z|1986-03-14T00:00:00.000Z
+;

+ 20 - 0
x-pack/plugin/sql/qa/src/main/resources/filter.csv-spec

@@ -119,3 +119,23 @@ SELECT COUNT(*), TRUNCATE(emp_no, -2) t FROM test_emp WHERE 'aaabbb' RLIKE 'a{2,
 99             |10000
 1              |10100
 ;
+
+inWithCompatibleDateTypes
+SELECT birth_date FROM test_emp WHERE birth_date IN ({d '1959-07-23'},CAST('1959-12-25T12:12:12' AS TIMESTAMP)) OR birth_date IS NULL ORDER BY birth_date;
+
+     birth_date:ts       
+------------------------
+1959-07-23T00:00:00.000Z
+1959-07-23T00:00:00.000Z
+1959-12-25T00:00:00.000Z
+null                    
+null                    
+null                    
+null                    
+null                    
+null                    
+null                    
+null                    
+null                    
+null                    
+;

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

@@ -12,7 +12,6 @@ import org.elasticsearch.xpack.sql.expression.Literal;
 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;
 
 import java.time.ZoneId;
 import java.util.Collections;
@@ -48,7 +47,7 @@ public class Histogram extends GroupingFunction {
         if (resolution == TypeResolution.TYPE_RESOLVED) {
             // interval must be Literal interval
             if (field().dataType().isDateBased()) {
-                resolution = isType(interval, DataTypes::isInterval, "(Date) HISTOGRAM", ParamOrdinal.SECOND, "interval");
+                resolution = isType(interval, DataType::isInterval, "(Date) HISTOGRAM", ParamOrdinal.SECOND, "interval");
             } else {
                 resolution = isNumeric(interval, "(Numeric) HISTOGRAM", ParamOrdinal.SECOND);
             }

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

@@ -11,7 +11,6 @@ import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.Bina
 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 static org.elasticsearch.common.logging.LoggerMessageFormat.format;
 
@@ -41,7 +40,7 @@ abstract class DateTimeArithmeticOperation extends ArithmeticOperation {
             return TypeResolution.TYPE_RESOLVED;
         }
         // 2. 3. 4. intervals
-        if ((DataTypes.isInterval(l) || DataTypes.isInterval(r))) {
+        if (l.isInterval() || r.isInterval()) {
             if (DataTypeConversion.commonType(l, r) == null) {
                 return new TypeResolution(format(null, "[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
             } else {
@@ -57,7 +56,7 @@ abstract class DateTimeArithmeticOperation extends ArithmeticOperation {
         DataType l = left().dataType();
         DataType r = right().dataType();
 
-        if (!(r.isDateOrTimeBased() || DataTypes.isInterval(r))|| !(l.isDateOrTimeBased() || DataTypes.isInterval(l))) {
+        if (!(r.isDateOrTimeBased() || r.isInterval())|| !(l.isDateOrTimeBased() || l.isInterval())) {
             return new TypeResolution(format(null, "[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
         }
         return TypeResolution.TYPE_RESOLVED;

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

@@ -7,10 +7,9 @@ package org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic;
 
 import org.elasticsearch.xpack.sql.expression.Expression;
 import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation;
-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;
 
 import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
 
@@ -39,10 +38,10 @@ public class Mul extends ArithmeticOperation {
             return TypeResolution.TYPE_RESOLVED;
         }
 
-        if (DataTypes.isInterval(l) && r.isInteger()) {
+        if (l.isInterval() && r.isInteger()) {
             dataType = l;
             return TypeResolution.TYPE_RESOLVED;
-        } else if (DataTypes.isInterval(r) && l.isInteger()) {
+        } else if (r.isInterval() && l.isInteger()) {
             dataType = r;
             return TypeResolution.TYPE_RESOLVED;
         }

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

@@ -9,7 +9,6 @@ import org.elasticsearch.xpack.sql.expression.Expression;
 import org.elasticsearch.xpack.sql.expression.predicate.operator.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation;
 import org.elasticsearch.xpack.sql.tree.NodeInfo;
 import org.elasticsearch.xpack.sql.tree.Source;
-import org.elasticsearch.xpack.sql.type.DataTypes;
 
 import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
 
@@ -38,7 +37,7 @@ public class Sub extends DateTimeArithmeticOperation {
         if (resolution.unresolved()) {
             return resolution;
         }
-        if ((right().dataType().isDateOrTimeBased()) && DataTypes.isInterval(left().dataType())) {
+        if ((right().dataType().isDateOrTimeBased()) && left().dataType().isInterval()) {
             return new TypeResolution(format(null, "Cannot subtract a {}[{}] from an interval[{}]; do you mean the reverse?",
                 right().dataType().typeName, right().source().text(), left().source().text()));
         }

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

@@ -271,6 +271,21 @@ public enum DataType {
         return isDateBased() || isTimeBased();
     }
 
+    public boolean isInterval() {
+        int ordinal = this.ordinal();
+        return ordinal >= INTERVAL_YEAR.ordinal() && ordinal <= INTERVAL_MINUTE_TO_SECOND.ordinal();
+    }
+
+    public boolean isYearMonthInterval() {
+        return this == INTERVAL_YEAR || this == INTERVAL_MONTH || this == INTERVAL_YEAR_TO_MONTH;
+    }
+
+    public boolean isDayTimeInterval() {
+        int ordinal = this.ordinal();
+        return (ordinal >= INTERVAL_DAY.ordinal() && ordinal <= INTERVAL_SECOND.ordinal())
+                || (ordinal >= INTERVAL_DAY_TO_HOUR.ordinal() && ordinal <= INTERVAL_MINUTE_TO_SECOND.ordinal());
+    }
+    
     // data type extract-able from _source or from docvalue_fields
     public boolean isFromDocValuesOnly() {
         return this == KEYWORD  // because of ignore_above. Extracting this from _source wouldn't make sense if it wasn't indexed at all.

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

@@ -87,12 +87,12 @@ public abstract class DataTypeConversion {
 
         // interval and dates
         if (left == DATE) {
-            if (DataTypes.isInterval(right)) {
+            if (right.isInterval()) {
                 return left;
             }
         }
         if (right == DATE) {
-            if (DataTypes.isInterval(left)) {
+            if (left.isInterval()) {
                 return right;
             }
         }
@@ -100,7 +100,7 @@ public abstract class DataTypeConversion {
             if (right == DATE) {
                 return DATETIME;
             }
-            if (DataTypes.isInterval(right)) {
+            if (right.isInterval()) {
                 return left;
             }
         }
@@ -108,7 +108,7 @@ public abstract class DataTypeConversion {
             if (left == DATE) {
                 return DATETIME;
             }
-            if (DataTypes.isInterval(left)) {
+            if (left.isInterval()) {
                 return right;
             }
         }
@@ -116,7 +116,7 @@ public abstract class DataTypeConversion {
             if (right == DATE || right == TIME) {
                 return left;
             }
-            if (DataTypes.isInterval(right)) {
+            if (right.isInterval()) {
                 return left;
             }
         }
@@ -124,24 +124,24 @@ public abstract class DataTypeConversion {
             if (left == DATE || left == TIME) {
                 return right;
             }
-            if (DataTypes.isInterval(left)) {
+            if (left.isInterval()) {
                 return right;
             }
         }
         // Interval * integer is a valid operation
-        if (DataTypes.isInterval(left)) {
+        if (left.isInterval()) {
             if (right.isInteger()) {
                 return left;
             }
         }
-        if (DataTypes.isInterval(right)) {
+        if (right.isInterval()) {
             if (left.isInteger()) {
                 return right;
             }
         }
-        if (DataTypes.isInterval(left)) {
+        if (left.isInterval()) {
             // intervals widening
-            if (DataTypes.isInterval(right)) {
+            if (right.isInterval()) {
                 // null returned for incompatible intervals
                 return DataTypes.compatibleInterval(left, right);
             }

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

@@ -18,12 +18,6 @@ import static org.elasticsearch.xpack.sql.type.DataType.DATETIME;
 import static org.elasticsearch.xpack.sql.type.DataType.DOUBLE;
 import static org.elasticsearch.xpack.sql.type.DataType.FLOAT;
 import static org.elasticsearch.xpack.sql.type.DataType.INTEGER;
-import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_DAY;
-import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_DAY_TO_HOUR;
-import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_MINUTE_TO_SECOND;
-import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_MONTH;
-import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_SECOND;
-import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_YEAR;
 import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_YEAR_TO_MONTH;
 import static org.elasticsearch.xpack.sql.type.DataType.KEYWORD;
 import static org.elasticsearch.xpack.sql.type.DataType.LONG;
@@ -88,18 +82,6 @@ public final class DataTypes {
         throw new SqlIllegalArgumentException("No idea what's the DataType for {}", value.getClass());
     }
 
-
-    //
-    // Interval utilities
-    //
-    // some of the methods below could have used an EnumSet however isDayTime would have required a large initialization block
-    // for this reason, these use the ordinal directly (and thus avoid the type check in EnumSet)
-
-    public static boolean isInterval(DataType type) {
-        int ordinal = type.ordinal();
-        return ordinal >= INTERVAL_YEAR.ordinal() && ordinal <= INTERVAL_MINUTE_TO_SECOND.ordinal();
-    }
-
     // return the compatible interval between the two - it is assumed the types are intervals
     // YEAR and MONTH -> YEAR_TO_MONTH
     // DAY... SECOND -> DAY_TIME
@@ -108,11 +90,11 @@ public final class DataTypes {
         if (left == right) {
             return left;
         }
-        if (isYearMonthInterval(left) && isYearMonthInterval(right)) {
+        if (left.isYearMonthInterval() && right.isYearMonthInterval()) {
             // no need to look at YEAR/YEAR or MONTH/MONTH as these are equal and already handled
             return INTERVAL_YEAR_TO_MONTH;
         }
-        if (isDayTimeInterval(left) && isDayTimeInterval(right)) {
+        if (left.isDayTimeInterval() && right.isDayTimeInterval()) {
             // to avoid specifying the combinations, extract the leading and trailing unit from the name
             // D > H > S > M which is also the alphabetical order
             String lName = left.name().substring(9);
@@ -141,16 +123,6 @@ public final class DataTypes {
         return null;
     }
 
-    private static boolean isYearMonthInterval(DataType type) {
-        return type == INTERVAL_YEAR || type == INTERVAL_MONTH || type == INTERVAL_YEAR_TO_MONTH;
-    }
-
-    private static boolean isDayTimeInterval(DataType type) {
-        int ordinal = type.ordinal();
-        return (ordinal >= INTERVAL_DAY.ordinal() && ordinal <= INTERVAL_SECOND.ordinal())
-                || (ordinal >= INTERVAL_DAY_TO_HOUR.ordinal() && ordinal <= INTERVAL_MINUTE_TO_SECOND.ordinal());
-    }
-
     private static String intervalUnit(char unitChar) {
         switch (unitChar) {
             case 'D':
@@ -240,9 +212,11 @@ public final class DataTypes {
             return true;
         } else {
             return
-                (left == DataType.NULL || right == DataType.NULL) ||
-                    (left.isString() && right.isString()) ||
-                    (left.isNumeric() && right.isNumeric());
+                (left == DataType.NULL || right == DataType.NULL)
+                    || (left.isString() && right.isString())
+                    || (left.isNumeric() && right.isNumeric())
+                    || (left.isDateBased() && right.isDateBased())
+                    || (left.isInterval() && right.isInterval() && compatibleInterval(left, right) != null);
         }
     }
 }

+ 1 - 1
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/type/DataTypeConversionTests.java

@@ -685,6 +685,6 @@ public class DataTypeConversionTests extends ESTestCase {
     }
 
     private DataType randomInterval() {
-        return randomFrom(Stream.of(DataType.values()).filter(DataTypes::isInterval).collect(Collectors.toList()));
+        return randomFrom(Stream.of(DataType.values()).filter(DataType::isInterval).collect(Collectors.toList()));
     }
 }

+ 1 - 2
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/type/DataTypesTests.java

@@ -32,7 +32,6 @@ import static org.elasticsearch.xpack.sql.type.DataType.INTERVAL_YEAR_TO_MONTH;
 import static org.elasticsearch.xpack.sql.type.DataType.KEYWORD;
 import static org.elasticsearch.xpack.sql.type.DataType.LONG;
 import static org.elasticsearch.xpack.sql.type.DataTypes.compatibleInterval;
-import static org.elasticsearch.xpack.sql.type.DataTypes.isInterval;
 import static org.elasticsearch.xpack.sql.type.DataTypes.metaSqlDataType;
 import static org.elasticsearch.xpack.sql.type.DataTypes.metaSqlDateTimeSub;
 import static org.elasticsearch.xpack.sql.type.DataTypes.metaSqlMaximumScale;
@@ -77,7 +76,7 @@ public class DataTypesTests extends ESTestCase {
     // type checks
     public void testIsInterval() throws Exception {
         for (DataType dataType : EnumSet.range(INTERVAL_YEAR, INTERVAL_MINUTE_TO_SECOND)) {
-            assertTrue(isInterval(dataType));
+            assertTrue(dataType.isInterval());
         }
     }