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
(cherry picked from commit 91039bab12d3ef27d6eac9cdc891a3b3ad0c694d)
This commit is contained in:
Marios Trivyzas 2019-05-15 16:06:03 -04:00
parent 9191b02213
commit 7473742e6e
No known key found for this signature in database
GPG Key ID: 8817B46B0CF36A3F
8 changed files with 71 additions and 5 deletions

View File

@ -182,6 +182,26 @@ SELECT -2 * INTERVAL '1 23:45' DAY TO MINUTES AS result;
-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
SELECT CAST('2018-05-13T12:34:56' AS DATETIME) - INTERVAL '2-8' YEAR TO MONTH AS result;

View File

@ -164,7 +164,7 @@ public class BinaryArithmeticProcessor extends FunctionalBinaryProcessor<Object,
return null;
}
if (f == BinaryArithmeticOperation.MUL || f == BinaryArithmeticOperation.DIV || f == BinaryArithmeticOperation.MOD) {
if (f == BinaryArithmeticOperation.DIV || f == BinaryArithmeticOperation.MOD) {
if (!(left instanceof Number)) {
throw new SqlIllegalArgumentException("A number is required; received {}", left);
}
@ -176,8 +176,8 @@ public class BinaryArithmeticProcessor extends FunctionalBinaryProcessor<Object,
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

View File

@ -43,7 +43,7 @@ abstract class DateTimeArithmeticOperation extends ArithmeticOperation {
// 2. 3. 4. intervals
if ((DataTypes.isInterval(l) || DataTypes.isInterval(r))) {
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 {
return resolveWithIntervals();
}
@ -54,6 +54,12 @@ abstract class DateTimeArithmeticOperation extends ArithmeticOperation {
}
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;
}
}

View File

@ -47,7 +47,7 @@ public class Mul extends ArithmeticOperation {
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

View File

@ -34,6 +34,10 @@ public class Sub extends DateTimeArithmeticOperation {
@Override
protected TypeResolution resolveWithIntervals() {
TypeResolution resolution = super.resolveWithIntervals();
if (resolution.unresolved()) {
return resolution;
}
if ((right().dataType().isDateOrTimeBased()) && DataTypes.isInterval(left().dataType())) {
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()));

View File

@ -121,6 +121,17 @@ public abstract class DataTypeConversion {
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)) {
// intervals widening
if (DataTypes.isInterval(right)) {

View File

@ -241,6 +241,27 @@ public class VerifierErrorMessagesTests extends ESTestCase {
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() {
assertEquals("1:43: Unknown column [xxx]\nline 1:8: Unknown column [xxx]",
error("SELECT xxx FROM test GROUP BY DAY_oF_YEAR(xxx)"));

View File

@ -627,6 +627,10 @@ public class DataTypeConversionTests extends ESTestCase {
assertEquals(FLOAT, commonType(FLOAT, INTEGER));
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
assertEquals(DATETIME, commonType(DATE, DATETIME));
assertEquals(DATETIME, commonType(DATETIME, DATE));