Pārlūkot izejas kodu

SQL: Fix issue regarding INTERVAL * number (#42014)

Interval * integer number is a valid operation which previously was
only supported for foldables (literals) and not when a field was
involved. That was because:

1. There was no common type returned for that combination
2. The `BinaryArithmeticOperation` was permitting the multiplication
(called by fold()) but the BinaryArithmeticProcessor didn't allow it

Moreover the error message for invalid arithmetic operations was wrong
because of the issue with the overloading methods of
`LoggerMessageFormat.format`.

Fixes: #41239
Fixes: #41200
Marios Trivyzas 6 gadi atpakaļ
vecāks
revīzija
91039bab12

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

@@ -182,6 +182,26 @@ SELECT -2 * INTERVAL '1 23:45' DAY TO MINUTES AS result;
 -3 23:30:00.0  
 -3 23:30:00.0  
 ;
 ;
 
 
+intervalHoursMultiply
+SELECT 4 * -INTERVAL '2' HOURS AS result1, -5 * -INTERVAL '3' HOURS AS result2;
+    result1    |  result2
+---------------+--------------
+-0 08:00:00.0  | +0 15:00:00.0
+;
+
+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;
+
+   languages   |  result
+---------------+---------------------------------------------
+2              |  +2 21:00:00.0
+5              |  +7 04:30:00.0
+4              |  +5 18:00:00.0
+5              |  +7 04:30:00.0
+1              |  +1 10:30:00.0
+;
+
 dateMinusInterval
 dateMinusInterval
 SELECT CAST('2018-05-13T12:34:56' AS DATETIME) - INTERVAL '2-8' YEAR TO MONTH AS result;
 SELECT CAST('2018-05-13T12:34:56' AS DATETIME) - INTERVAL '2-8' YEAR TO MONTH AS result;
 
 

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

@@ -164,7 +164,7 @@ public class BinaryArithmeticProcessor extends FunctionalBinaryProcessor<Object,
             return null;
             return null;
         }
         }
 
 
-        if (f == BinaryArithmeticOperation.MUL || f == BinaryArithmeticOperation.DIV || f == BinaryArithmeticOperation.MOD) {
+        if (f == BinaryArithmeticOperation.DIV || f == BinaryArithmeticOperation.MOD) {
             if (!(left instanceof Number)) {
             if (!(left instanceof Number)) {
                 throw new SqlIllegalArgumentException("A number is required; received {}", left);
                 throw new SqlIllegalArgumentException("A number is required; received {}", left);
             }
             }
@@ -176,8 +176,8 @@ public class BinaryArithmeticProcessor extends FunctionalBinaryProcessor<Object,
             return f.apply(left, right);
             return f.apply(left, right);
         }
         }
 
 
-        if (f == BinaryArithmeticOperation.ADD || f == BinaryArithmeticOperation.SUB) {
-                return f.apply(left, right);
+        if (f == BinaryArithmeticOperation.ADD || f == BinaryArithmeticOperation.SUB || f == BinaryArithmeticOperation.MUL) {
+            return f.apply(left, right);
         }
         }
 
 
         // this should not occur
         // this should not occur

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

@@ -43,7 +43,7 @@ abstract class DateTimeArithmeticOperation extends ArithmeticOperation {
         // 2. 3. 4. intervals
         // 2. 3. 4. intervals
         if ((DataTypes.isInterval(l) || DataTypes.isInterval(r))) {
         if ((DataTypes.isInterval(l) || DataTypes.isInterval(r))) {
             if (DataTypeConversion.commonType(l, r) == null) {
             if (DataTypeConversion.commonType(l, r) == null) {
-                return new TypeResolution(format("[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
+                return new TypeResolution(format(null, "[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
             } else {
             } else {
                 return resolveWithIntervals();
                 return resolveWithIntervals();
             }
             }
@@ -54,6 +54,12 @@ abstract class DateTimeArithmeticOperation extends ArithmeticOperation {
     }
     }
 
 
     protected TypeResolution resolveWithIntervals() {
     protected TypeResolution resolveWithIntervals() {
+        DataType l = left().dataType();
+        DataType r = right().dataType();
+
+        if (!(r.isDateOrTimeBased() || DataTypes.isInterval(r))|| !(l.isDateOrTimeBased() || DataTypes.isInterval(l))) {
+            return new TypeResolution(format(null, "[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
+        }
         return TypeResolution.TYPE_RESOLVED;
         return TypeResolution.TYPE_RESOLVED;
     }
     }
 }
 }

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

@@ -47,7 +47,7 @@ public class Mul extends ArithmeticOperation {
             return TypeResolution.TYPE_RESOLVED;
             return TypeResolution.TYPE_RESOLVED;
         }
         }
 
 
-        return new TypeResolution(format("[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
+        return new TypeResolution(format(null, "[{}] has arguments with incompatible types [{}] and [{}]", symbol(), l, r));
     }
     }
 
 
     @Override
     @Override

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

@@ -34,6 +34,10 @@ public class Sub extends DateTimeArithmeticOperation {
 
 
     @Override
     @Override
     protected TypeResolution resolveWithIntervals() {
     protected TypeResolution resolveWithIntervals() {
+        TypeResolution resolution = super.resolveWithIntervals();
+        if (resolution.unresolved()) {
+            return resolution;
+        }
         if ((right().dataType().isDateOrTimeBased()) && DataTypes.isInterval(left().dataType())) {
         if ((right().dataType().isDateOrTimeBased()) && DataTypes.isInterval(left().dataType())) {
             return new TypeResolution(format(null, "Cannot subtract a {}[{}] from an interval[{}]; do you mean the reverse?",
             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()));
                 right().dataType().typeName, right().source().text(), left().source().text()));

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

@@ -121,6 +121,17 @@ public abstract class DataTypeConversion {
                 return right;
                 return right;
             }
             }
         }
         }
+        // Interval * integer is a valid operation
+        if (DataTypes.isInterval(left)) {
+            if (right.isInteger()) {
+                return left;
+            }
+        }
+        if (DataTypes.isInterval(right)) {
+            if (left.isInteger()) {
+                return right;
+            }
+        }
         if (DataTypes.isInterval(left)) {
         if (DataTypes.isInterval(left)) {
             // intervals widening
             // intervals widening
             if (DataTypes.isInterval(right)) {
             if (DataTypes.isInterval(right)) {

+ 21 - 0
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java

@@ -241,6 +241,27 @@ public class VerifierErrorMessagesTests extends ESTestCase {
             error("SELECT INTERVAL 1 MONTH - CAST('12:23:56.789' AS TIME)"));
             error("SELECT INTERVAL 1 MONTH - CAST('12:23:56.789' AS TIME)"));
     }
     }
 
 
+    public void testAddIntervalAndNumberNotAllowed() {
+        assertEquals("1:8: [+] has arguments with incompatible types [INTERVAL_DAY] and [INTEGER]",
+            error("SELECT INTERVAL 1 DAY + 100"));
+        assertEquals("1:8: [+] has arguments with incompatible types [INTEGER] and [INTERVAL_DAY]",
+            error("SELECT 100 + INTERVAL 1 DAY"));
+    }
+
+    public void testSubtractIntervalAndNumberNotAllowed() {
+        assertEquals("1:8: [-] has arguments with incompatible types [INTERVAL_MINUTE] and [DOUBLE]",
+            error("SELECT INTERVAL 10 MINUTE - 100.0"));
+        assertEquals("1:8: [-] has arguments with incompatible types [DOUBLE] and [INTERVAL_MINUTE]",
+            error("SELECT 100.0 - INTERVAL 10 MINUTE"));
+    }
+
+    public void testMultiplyIntervalWithDecimalNotAllowed() {
+        assertEquals("1:8: [*] has arguments with incompatible types [INTERVAL_MONTH] and [DOUBLE]",
+            error("SELECT INTERVAL 1 MONTH * 1.234"));
+        assertEquals("1:8: [*] has arguments with incompatible types [DOUBLE] and [INTERVAL_MONTH]",
+            error("SELECT 1.234 * INTERVAL 1 MONTH"));
+    }
+
     public void testMultipleColumns() {
     public void testMultipleColumns() {
         assertEquals("1:43: Unknown column [xxx]\nline 1:8: Unknown column [xxx]",
         assertEquals("1:43: Unknown column [xxx]\nline 1:8: Unknown column [xxx]",
                 error("SELECT xxx FROM test GROUP BY DAY_oF_YEAR(xxx)"));
                 error("SELECT xxx FROM test GROUP BY DAY_oF_YEAR(xxx)"));

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

@@ -628,6 +628,10 @@ public class DataTypeConversionTests extends ESTestCase {
         assertEquals(FLOAT, commonType(FLOAT, INTEGER));
         assertEquals(FLOAT, commonType(FLOAT, INTEGER));
         assertEquals(DOUBLE, commonType(DOUBLE, FLOAT));
         assertEquals(DOUBLE, commonType(DOUBLE, FLOAT));
 
 
+        // numeric and intervals
+        assertEquals(INTERVAL_YEAR_TO_MONTH, commonType(INTERVAL_YEAR_TO_MONTH, LONG));
+        assertEquals(INTERVAL_HOUR_TO_MINUTE, commonType(INTEGER, INTERVAL_HOUR_TO_MINUTE));
+
         // dates/datetimes and intervals
         // dates/datetimes and intervals
         assertEquals(DATETIME, commonType(DATE, DATETIME));
         assertEquals(DATETIME, commonType(DATE, DATETIME));
         assertEquals(DATETIME, commonType(DATETIME, DATE));
         assertEquals(DATETIME, commonType(DATETIME, DATE));