diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/Average.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/Average.scala index 9bb048a9851e..996c548e1329 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/Average.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/Average.scala @@ -17,10 +17,9 @@ package org.apache.spark.sql.catalyst.expressions.aggregate -import org.apache.spark.sql.catalyst.analysis.{DecimalPrecision, FunctionRegistry, TypeCheckResult} +import org.apache.spark.sql.catalyst.analysis.{DecimalPrecision, FunctionRegistry} import org.apache.spark.sql.catalyst.dsl.expressions._ import org.apache.spark.sql.catalyst.expressions._ -import org.apache.spark.sql.catalyst.util.TypeUtils import org.apache.spark.sql.types._ @ExpressionDescription( @@ -81,7 +80,8 @@ case class Average(child: Expression) extends DeclarativeAggregate with Implicit case _: DecimalType => DecimalPrecision.decimalAndDecimal(sum / count.cast(DecimalType.LongDecimal)).cast(resultType) case CalendarIntervalType => - DivideInterval(sum.cast(resultType), count.cast(DoubleType)) + val newCount = If(EqualTo(count, Literal(0L)), Literal(null, LongType), count) + DivideInterval(sum.cast(resultType), newCount.cast(DoubleType)) case _ => sum.cast(resultType) / count.cast(resultType) } diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/arithmetic.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/arithmetic.scala index 82a8e6d80a0b..debd7c89adb9 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/arithmetic.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/arithmetic.scala @@ -75,12 +75,15 @@ case class UnaryMinus(child: Expression) extends UnaryExpression """}) case _: CalendarIntervalType => val iu = IntervalUtils.getClass.getCanonicalName.stripSuffix("$") - defineCodeGen(ctx, ev, c => s"$iu.negate($c)") + val method = if (checkOverflow) "negateExact" else "negate" + defineCodeGen(ctx, ev, c => s"$iu.$method($c)") } protected override def nullSafeEval(input: Any): Any = dataType match { + case CalendarIntervalType if checkOverflow => + IntervalUtils.negateExact(input.asInstanceOf[CalendarInterval]) case CalendarIntervalType => IntervalUtils.negate(input.asInstanceOf[CalendarInterval]) - case _ => numeric.negate(input) + case _ => numeric.negate(input) } override def sql: String = s"(- ${child.sql})" @@ -224,13 +227,17 @@ case class Add(left: Expression, right: Expression) extends BinaryArithmetic { override def decimalMethod: String = "$plus" - override def calendarIntervalMethod: String = "add" + override def calendarIntervalMethod: String = if (checkOverflow) "addExact" else "add" private lazy val numeric = TypeUtils.getNumeric(dataType, checkOverflow) protected override def nullSafeEval(input1: Any, input2: Any): Any = dataType match { - case CalendarIntervalType => IntervalUtils.add( - input1.asInstanceOf[CalendarInterval], input2.asInstanceOf[CalendarInterval]) + case CalendarIntervalType if checkOverflow => + IntervalUtils.addExact( + input1.asInstanceOf[CalendarInterval], input2.asInstanceOf[CalendarInterval]) + case CalendarIntervalType => + IntervalUtils.add( + input1.asInstanceOf[CalendarInterval], input2.asInstanceOf[CalendarInterval]) case _ => numeric.plus(input1, input2) } @@ -252,13 +259,17 @@ case class Subtract(left: Expression, right: Expression) extends BinaryArithmeti override def decimalMethod: String = "$minus" - override def calendarIntervalMethod: String = "subtract" + override def calendarIntervalMethod: String = if (checkOverflow) "subtractExact" else "subtract" private lazy val numeric = TypeUtils.getNumeric(dataType, checkOverflow) protected override def nullSafeEval(input1: Any, input2: Any): Any = dataType match { - case CalendarIntervalType => IntervalUtils.subtract( - input1.asInstanceOf[CalendarInterval], input2.asInstanceOf[CalendarInterval]) + case CalendarIntervalType if checkOverflow => + IntervalUtils.subtractExact( + input1.asInstanceOf[CalendarInterval], input2.asInstanceOf[CalendarInterval]) + case CalendarIntervalType => + IntervalUtils.subtract( + input1.asInstanceOf[CalendarInterval], input2.asInstanceOf[CalendarInterval]) case _ => numeric.minus(input1, input2) } diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/intervalExpressions.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/intervalExpressions.scala index 78314d6d8571..831510e7f0f3 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/intervalExpressions.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/intervalExpressions.scala @@ -125,34 +125,22 @@ abstract class IntervalNumOperation( override def nullable: Boolean = true override def nullSafeEval(interval: Any, num: Any): Any = { - try { - operation(interval.asInstanceOf[CalendarInterval], num.asInstanceOf[Double]) - } catch { - case _: java.lang.ArithmeticException => null - } + operation(interval.asInstanceOf[CalendarInterval], num.asInstanceOf[Double]) } override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = { - nullSafeCodeGen(ctx, ev, (interval, num) => { - val iu = IntervalUtils.getClass.getName.stripSuffix("$") - s""" - try { - ${ev.value} = $iu.$operationName($interval, $num); - } catch (java.lang.ArithmeticException e) { - ${ev.isNull} = true; - } - """ - }) + val iu = IntervalUtils.getClass.getName.stripSuffix("$") + defineCodeGen(ctx, ev, (interval, num) => s"$iu.$operationName($interval, $num)") } - override def prettyName: String = operationName + "_interval" + override def prettyName: String = operationName.stripSuffix("Exact") + "_interval" } case class MultiplyInterval(interval: Expression, num: Expression) - extends IntervalNumOperation(interval, num, multiply, "multiply") + extends IntervalNumOperation(interval, num, multiplyExact, "multiplyExact") case class DivideInterval(interval: Expression, num: Expression) - extends IntervalNumOperation(interval, num, divide, "divide") + extends IntervalNumOperation(interval, num, divideExact, "divideExact") // scalastyle:off line.size.limit @ExpressionDescription( diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala index b5bc3a571d6c..8763f24b05ed 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala @@ -138,7 +138,7 @@ object IntervalUtils { assert(input.length == input.trim.length) input match { case yearMonthPattern("-", yearStr, monthStr) => - negate(toInterval(yearStr, monthStr)) + negateExact(toInterval(yearStr, monthStr)) case yearMonthPattern(_, yearStr, monthStr) => toInterval(yearStr, monthStr) case _ => @@ -401,6 +401,8 @@ object IntervalUtils { /** * Makes an interval from months, days and micros with the fractional part by * adding the month fraction to days and the days fraction to micros. + * + * @throws ArithmeticException if the result overflows any field value */ private def fromDoubles( monthsWithFraction: Double, @@ -416,13 +418,34 @@ object IntervalUtils { /** * Unary minus, return the negated the calendar interval value. * - * @param interval the interval to be negated - * @return a new calendar interval instance with all it parameters negated from the origin one. + * @throws ArithmeticException if the result overflows any field value + */ + def negateExact(interval: CalendarInterval): CalendarInterval = { + val months = Math.negateExact(interval.months) + val days = Math.negateExact(interval.days) + val microseconds = Math.negateExact(interval.microseconds) + new CalendarInterval(months, days, microseconds) + } + + /** + * Unary minus, return the negated the calendar interval value. */ def negate(interval: CalendarInterval): CalendarInterval = { new CalendarInterval(-interval.months, -interval.days, -interval.microseconds) } + /** + * Return a new calendar interval instance of the sum of two intervals. + * + * @throws ArithmeticException if the result overflows any field value + */ + def addExact(left: CalendarInterval, right: CalendarInterval): CalendarInterval = { + val months = Math.addExact(left.months, right.months) + val days = Math.addExact(left.days, right.days) + val microseconds = Math.addExact(left.microseconds, right.microseconds) + new CalendarInterval(months, days, microseconds) + } + /** * Return a new calendar interval instance of the sum of two intervals. */ @@ -434,7 +457,19 @@ object IntervalUtils { } /** - * Return a new calendar interval instance of the left intervals minus the right one. + * Return a new calendar interval instance of the left interval minus the right one. + * + * @throws ArithmeticException if the result overflows any field value + */ + def subtractExact(left: CalendarInterval, right: CalendarInterval): CalendarInterval = { + val months = Math.subtractExact(left.months, right.months) + val days = Math.subtractExact(left.days, right.days) + val microseconds = Math.subtractExact(left.microseconds, right.microseconds) + new CalendarInterval(months, days, microseconds) + } + + /** + * Return a new calendar interval instance of the left interval minus the right one. */ def subtract(left: CalendarInterval, right: CalendarInterval): CalendarInterval = { val months = left.months - right.months @@ -443,12 +478,22 @@ object IntervalUtils { new CalendarInterval(months, days, microseconds) } - def multiply(interval: CalendarInterval, num: Double): CalendarInterval = { + /** + * Return a new calendar interval instance of the left interval times a multiplier. + * + * @throws ArithmeticException if the result overflows any field value + */ + def multiplyExact(interval: CalendarInterval, num: Double): CalendarInterval = { fromDoubles(num * interval.months, num * interval.days, num * interval.microseconds) } - def divide(interval: CalendarInterval, num: Double): CalendarInterval = { - if (num == 0) throw new java.lang.ArithmeticException("divide by zero") + /** + * Return a new calendar interval instance of the left interval divides by a dividend. + * + * @throws ArithmeticException if the result overflows any field value or divided by zero + */ + def divideExact(interval: CalendarInterval, num: Double): CalendarInterval = { + if (num == 0) throw new ArithmeticException("divide by zero") fromDoubles(interval.months / num, interval.days / num, interval.microseconds / num) } diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CollectionExpressionsSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CollectionExpressionsSuite.scala index cc9ebfe40942..9e98e146c7a0 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CollectionExpressionsSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CollectionExpressionsSuite.scala @@ -733,7 +733,7 @@ class CollectionExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper checkEvaluation(new Sequence( Literal(Timestamp.valueOf("2018-01-02 00:00:00")), Literal(Timestamp.valueOf("2018-01-01 00:00:00")), - Literal(negate(stringToInterval("interval 12 hours")))), + Literal(negateExact(stringToInterval("interval 12 hours")))), Seq( Timestamp.valueOf("2018-01-02 00:00:00"), Timestamp.valueOf("2018-01-01 12:00:00"), @@ -742,7 +742,7 @@ class CollectionExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper checkEvaluation(new Sequence( Literal(Timestamp.valueOf("2018-01-02 00:00:00")), Literal(Timestamp.valueOf("2017-12-31 23:59:59")), - Literal(negate(stringToInterval("interval 12 hours")))), + Literal(negateExact(stringToInterval("interval 12 hours")))), Seq( Timestamp.valueOf("2018-01-02 00:00:00"), Timestamp.valueOf("2018-01-01 12:00:00"), @@ -760,7 +760,7 @@ class CollectionExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper checkEvaluation(new Sequence( Literal(Timestamp.valueOf("2018-03-01 00:00:00")), Literal(Timestamp.valueOf("2018-01-01 00:00:00")), - Literal(negate(stringToInterval("interval 1 month")))), + Literal(negateExact(stringToInterval("interval 1 month")))), Seq( Timestamp.valueOf("2018-03-01 00:00:00"), Timestamp.valueOf("2018-02-01 00:00:00"), @@ -769,7 +769,7 @@ class CollectionExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper checkEvaluation(new Sequence( Literal(Timestamp.valueOf("2018-03-03 00:00:00")), Literal(Timestamp.valueOf("2018-01-01 00:00:00")), - Literal(negate(stringToInterval("interval 1 month 1 day")))), + Literal(negateExact(stringToInterval("interval 1 month 1 day")))), Seq( Timestamp.valueOf("2018-03-03 00:00:00"), Timestamp.valueOf("2018-02-02 00:00:00"), @@ -815,7 +815,7 @@ class CollectionExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper checkEvaluation(new Sequence( Literal(Timestamp.valueOf("2022-04-01 00:00:00")), Literal(Timestamp.valueOf("2017-01-01 00:00:00")), - Literal(negate(fromYearMonthString("1-5")))), + Literal(negateExact(fromYearMonthString("1-5")))), Seq( Timestamp.valueOf("2022-04-01 00:00:00.000"), Timestamp.valueOf("2020-11-01 00:00:00.000"), @@ -907,7 +907,7 @@ class CollectionExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper new Sequence( Literal(Date.valueOf("1970-01-01")), Literal(Date.valueOf("1970-02-01")), - Literal(negate(stringToInterval("interval 1 month")))), + Literal(negateExact(stringToInterval("interval 1 month")))), EmptyRow, s"sequence boundaries: 0 to 2678400000000 by -${28 * MICROS_PER_DAY}") } diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/IntervalExpressionsSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/IntervalExpressionsSuite.scala index ddcb6a66832a..d31a0e210552 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/IntervalExpressionsSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/IntervalExpressionsSuite.scala @@ -21,7 +21,8 @@ import scala.language.implicitConversions import org.apache.spark.SparkFunSuite import org.apache.spark.sql.catalyst.util.DateTimeConstants._ -import org.apache.spark.sql.catalyst.util.IntervalUtils.stringToInterval +import org.apache.spark.sql.catalyst.util.IntervalUtils.{safeStringToInterval, stringToInterval} +import org.apache.spark.sql.internal.SQLConf import org.apache.spark.sql.types.Decimal import org.apache.spark.unsafe.types.{CalendarInterval, UTF8String} @@ -198,9 +199,17 @@ class IntervalExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper { test("multiply") { def check(interval: String, num: Double, expected: String): Unit = { - checkEvaluation( - MultiplyInterval(Literal(stringToInterval(interval)), Literal(num)), - if (expected == null) null else stringToInterval(expected)) + val expr = MultiplyInterval(Literal(stringToInterval(interval)), Literal(num)) + val expectedRes = safeStringToInterval(expected) + Seq("true", "false").foreach { v => + withSQLConf(SQLConf.ANSI_ENABLED.key -> v) { + if (expectedRes == null) { + checkExceptionInExpression[ArithmeticException](expr, expected) + } else { + checkEvaluation(expr, expectedRes) + } + } + } } check("0 seconds", 10, "0 seconds") @@ -211,14 +220,22 @@ class IntervalExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper { check("-100 years -1 millisecond", 0.5, "-50 years -500 microseconds") check("2 months 4 seconds", -0.5, "-1 months -2 seconds") check("1 month 2 microseconds", 1.5, "1 months 15 days 3 microseconds") - check("2 months", Int.MaxValue, null) + check("2 months", Int.MaxValue, "integer overflow") } test("divide") { def check(interval: String, num: Double, expected: String): Unit = { - checkEvaluation( - DivideInterval(Literal(stringToInterval(interval)), Literal(num)), - if (expected == null) null else stringToInterval(expected)) + val expr = DivideInterval(Literal(stringToInterval(interval)), Literal(num)) + val expectedRes = safeStringToInterval(expected) + Seq("true", "false").foreach { v => + withSQLConf(SQLConf.ANSI_ENABLED.key -> v) { + if (expectedRes == null) { + checkExceptionInExpression[ArithmeticException](expr, expected) + } else { + checkEvaluation(expr, expectedRes) + } + } + } } check("0 seconds", 10, "0 seconds") @@ -228,7 +245,8 @@ class IntervalExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper { check("2 years -8 seconds", 0.5, "4 years -16 seconds") check("-1 month 2 microseconds", -0.25, "4 months -8 microseconds") check("1 month 3 microsecond", 1.5, "20 days 2 microseconds") - check("1 second", 0, null) + check("1 second", 0, "divide by zero") + check(s"${Int.MaxValue} months", 0.9, "integer overflow") } test("make interval") { diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/IntervalUtilsSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/IntervalUtilsSuite.scala index 4d2fbb6fbaba..47b7d402a202 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/IntervalUtilsSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/IntervalUtilsSuite.scala @@ -239,64 +239,68 @@ class IntervalUtilsSuite extends SparkFunSuite with SQLHelper { } test("negate") { + assert(negateExact(new CalendarInterval(1, 2, 3)) === new CalendarInterval(-1, -2, -3)) assert(negate(new CalendarInterval(1, 2, 3)) === new CalendarInterval(-1, -2, -3)) } test("subtract one interval by another") { val input1 = new CalendarInterval(3, 1, 1 * MICROS_PER_HOUR) val input2 = new CalendarInterval(2, 4, 100 * MICROS_PER_HOUR) - assert(new CalendarInterval(1, -3, -99 * MICROS_PER_HOUR) === subtract(input1, input2)) val input3 = new CalendarInterval(-10, -30, -81 * MICROS_PER_HOUR) val input4 = new CalendarInterval(75, 150, 200 * MICROS_PER_HOUR) - assert(new CalendarInterval(-85, -180, -281 * MICROS_PER_HOUR) === subtract(input3, input4)) + Seq[(CalendarInterval, CalendarInterval) => CalendarInterval](subtractExact, subtract) + .foreach { func => + assert(new CalendarInterval(1, -3, -99 * MICROS_PER_HOUR) === func(input1, input2)) + assert(new CalendarInterval(-85, -180, -281 * MICROS_PER_HOUR) === func(input3, input4)) + } } test("add two intervals") { val input1 = new CalendarInterval(3, 1, 1 * MICROS_PER_HOUR) val input2 = new CalendarInterval(2, 4, 100 * MICROS_PER_HOUR) - assert(new CalendarInterval(5, 5, 101 * MICROS_PER_HOUR) === add(input1, input2)) - val input3 = new CalendarInterval(-10, -30, -81 * MICROS_PER_HOUR) val input4 = new CalendarInterval(75, 150, 200 * MICROS_PER_HOUR) - assert(new CalendarInterval(65, 120, 119 * MICROS_PER_HOUR) === add(input3, input4)) + Seq[(CalendarInterval, CalendarInterval) => CalendarInterval](addExact, add).foreach { func => + assert(new CalendarInterval(5, 5, 101 * MICROS_PER_HOUR) === func(input1, input2)) + assert(new CalendarInterval(65, 120, 119 * MICROS_PER_HOUR) === func(input3, input4)) + } } test("multiply by num") { var interval = new CalendarInterval(0, 0, 0) - assert(interval === multiply(interval, 0)) + assert(interval === multiplyExact(interval, 0)) interval = new CalendarInterval(123, 456, 789) - assert(new CalendarInterval(123 * 42, 456 * 42, 789 * 42) === multiply(interval, 42)) + assert(new CalendarInterval(123 * 42, 456 * 42, 789 * 42) === multiplyExact(interval, 42)) interval = new CalendarInterval(-123, -456, -789) - assert(new CalendarInterval(-123 * 42, -456 * 42, -789 * 42) === multiply(interval, 42)) + assert(new CalendarInterval(-123 * 42, -456 * 42, -789 * 42) === multiplyExact(interval, 42)) assert(new CalendarInterval(1, 22, 12 * MICROS_PER_HOUR) === - multiply(new CalendarInterval(1, 5, 0), 1.5)) + multiplyExact(new CalendarInterval(1, 5, 0), 1.5)) assert(new CalendarInterval(2, 14, 12 * MICROS_PER_HOUR) === - multiply(new CalendarInterval(2, 2, 2 * MICROS_PER_HOUR), 1.2)) + multiplyExact(new CalendarInterval(2, 2, 2 * MICROS_PER_HOUR), 1.2)) + try { - multiply(new CalendarInterval(2, 0, 0), Integer.MAX_VALUE) + multiplyExact(new CalendarInterval(2, 0, 0), Integer.MAX_VALUE) fail("Expected to throw an exception on months overflow") } catch { - case e: ArithmeticException => - assert(e.getMessage.contains("overflow")) + case e: ArithmeticException => assert(e.getMessage.contains("overflow")) } } test("divide by num") { var interval = new CalendarInterval(0, 0, 0) - assert(interval === divide(interval, 10)) + assert(interval === divideExact(interval, 10)) interval = new CalendarInterval(1, 3, 30 * MICROS_PER_SECOND) assert(new CalendarInterval(0, 16, 12 * MICROS_PER_HOUR + 15 * MICROS_PER_SECOND) === - divide(interval, 2)) - assert(new CalendarInterval(2, 6, MICROS_PER_MINUTE) === divide(interval, 0.5)) + divideExact(interval, 2)) + assert(new CalendarInterval(2, 6, MICROS_PER_MINUTE) === divideExact(interval, 0.5)) interval = new CalendarInterval(-1, 0, -30 * MICROS_PER_SECOND) - assert(new CalendarInterval(0, -15, -15 * MICROS_PER_SECOND) === divide(interval, 2)) - assert(new CalendarInterval(-2, 0, -1 * MICROS_PER_MINUTE) === divide(interval, 0.5)) + assert(new CalendarInterval(0, -15, -15 * MICROS_PER_SECOND) === divideExact(interval, 2)) + assert(new CalendarInterval(-2, 0, -1 * MICROS_PER_MINUTE) === divideExact(interval, 0.5)) try { - divide(new CalendarInterval(123, 456, 789), 0) + divideExact(new CalendarInterval(123, 456, 789), 0) fail("Expected to throw an exception on divide by zero") } catch { - case e: ArithmeticException => - assert(e.getMessage.contains("divide by zero")) + case e: ArithmeticException => assert(e.getMessage.contains("divide by zero")) } } @@ -420,4 +424,40 @@ class IntervalUtilsSuite extends SparkFunSuite with SQLHelper { checkFail("5 30-12", DAY, SECOND, "must match day-time format") checkFail("5 1:12:20", HOUR, MICROSECOND, "Cannot support (interval") } + + test("interval overflow check") { + intercept[ArithmeticException](negateExact(new CalendarInterval(Int.MinValue, 0, 0))) + assert(negate(new CalendarInterval(Int.MinValue, 0, 0)) === + new CalendarInterval(Int.MinValue, 0, 0)) + intercept[ArithmeticException](negateExact(CalendarInterval.MIN_VALUE)) + assert(negate(CalendarInterval.MIN_VALUE) === CalendarInterval.MIN_VALUE) + intercept[ArithmeticException](addExact(CalendarInterval.MAX_VALUE, + new CalendarInterval(0, 0, 1))) + intercept[ArithmeticException](addExact(CalendarInterval.MAX_VALUE, + new CalendarInterval(0, 1, 0))) + intercept[ArithmeticException](addExact(CalendarInterval.MAX_VALUE, + new CalendarInterval(1, 0, 0))) + assert(add(CalendarInterval.MAX_VALUE, new CalendarInterval(0, 0, 1)) === + new CalendarInterval(Int.MaxValue, Int.MaxValue, Long.MinValue)) + assert(add(CalendarInterval.MAX_VALUE, new CalendarInterval(0, 1, 0)) === + new CalendarInterval(Int.MaxValue, Int.MinValue, Long.MaxValue)) + assert(add(CalendarInterval.MAX_VALUE, new CalendarInterval(1, 0, 0)) === + new CalendarInterval(Int.MinValue, Int.MaxValue, Long.MaxValue)) + + intercept[ArithmeticException](subtractExact(CalendarInterval.MAX_VALUE, + new CalendarInterval(0, 0, -1))) + intercept[ArithmeticException](subtractExact(CalendarInterval.MAX_VALUE, + new CalendarInterval(0, -1, 0))) + intercept[ArithmeticException](subtractExact(CalendarInterval.MAX_VALUE, + new CalendarInterval(-1, 0, 0))) + assert(subtract(CalendarInterval.MAX_VALUE, new CalendarInterval(0, 0, -1)) === + new CalendarInterval(Int.MaxValue, Int.MaxValue, Long.MinValue)) + assert(subtract(CalendarInterval.MAX_VALUE, new CalendarInterval(0, -1, 0)) === + new CalendarInterval(Int.MaxValue, Int.MinValue, Long.MaxValue)) + assert(subtract(CalendarInterval.MAX_VALUE, new CalendarInterval(-1, 0, 0)) === + new CalendarInterval(Int.MinValue, Int.MaxValue, Long.MaxValue)) + + intercept[ArithmeticException](multiplyExact(CalendarInterval.MAX_VALUE, 2)) + intercept[ArithmeticException](divideExact(CalendarInterval.MAX_VALUE, 0.5)) + } } diff --git a/sql/core/src/test/resources/sql-tests/inputs/interval.sql b/sql/core/src/test/resources/sql-tests/inputs/interval.sql index 9531a9575a9b..9d9fb0d030bc 100644 --- a/sql/core/src/test/resources/sql-tests/inputs/interval.sql +++ b/sql/core/src/test/resources/sql-tests/inputs/interval.sql @@ -255,3 +255,10 @@ select interval 'interval \t 1\tday'; select interval 'interval\t1\tday'; select interval '1\t' day; select interval '1 ' day; + +-- interval overflow if (ansi) exception else NULL +select -(a) from values (interval '-2147483648 months', interval '2147483647 months') t(a, b); +select a - b from values (interval '-2147483648 months', interval '2147483647 months') t(a, b); +select b + interval '1 month' from values (interval '-2147483648 months', interval '2147483647 months') t(a, b); +select a * 1.1 from values (interval '-2147483648 months', interval '2147483647 months') t(a, b); +select a / 0.5 from values (interval '-2147483648 months', interval '2147483647 months') t(a, b); diff --git a/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out b/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out index 3da83c1ff6a7..4fceb6b255b0 100644 --- a/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/ansi/interval.sql.out @@ -1,5 +1,5 @@ -- Automatically generated by SQLQueryTestSuite --- Number of queries: 126 +-- Number of queries: 131 -- !query 0 @@ -207,9 +207,10 @@ struct +struct<> -- !query 25 output -NULL +java.lang.ArithmeticException +divide by zero -- !query 26 @@ -1120,34 +1121,79 @@ struct -- !query 114 -select 1 year 2 days +select -(a) from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) -- !query 114 schema -struct +struct<> -- !query 114 output -1 years 2 days +java.lang.ArithmeticException +integer overflow -- !query 115 -select '10-9' year to month +select a - b from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) -- !query 115 schema -struct +struct<> -- !query 115 output -10 years 9 months +java.lang.ArithmeticException +integer overflow -- !query 116 -select '20 15:40:32.99899999' day to second +select b + interval '1 month' from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) -- !query 116 schema -struct +struct<> -- !query 116 output -20 days 15 hours 40 minutes 32.998999 seconds +java.lang.ArithmeticException +integer overflow -- !query 117 -select 30 day day +select a * 1.1 from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) -- !query 117 schema struct<> -- !query 117 output +java.lang.ArithmeticException +integer overflow + + +-- !query 118 +select a / 0.5 from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) +-- !query 118 schema +struct<> +-- !query 118 output +java.lang.ArithmeticException +integer overflow + + +-- !query 119 +select 1 year 2 days +-- !query 119 schema +struct +-- !query 119 output +1 years 2 days + + +-- !query 120 +select '10-9' year to month +-- !query 120 schema +struct +-- !query 120 output +10 years 9 months + + +-- !query 121 +select '20 15:40:32.99899999' day to second +-- !query 121 schema +struct +-- !query 121 output +20 days 15 hours 40 minutes 32.998999 seconds + + +-- !query 122 +select 30 day day +-- !query 122 schema +struct<> +-- !query 122 output org.apache.spark.sql.catalyst.parser.ParseException no viable alternative at input 'day'(line 1, pos 14) @@ -1157,27 +1203,27 @@ select 30 day day --------------^^^ --- !query 118 +-- !query 123 select date'2012-01-01' - '2-2' year to month --- !query 118 schema +-- !query 123 schema struct --- !query 118 output +-- !query 123 output 2009-11-01 --- !query 119 +-- !query 124 select 1 month - 1 day --- !query 119 schema +-- !query 124 schema struct --- !query 119 output +-- !query 124 output 1 months -1 days --- !query 120 +-- !query 125 select 1 year to month --- !query 120 schema +-- !query 125 schema struct<> --- !query 120 output +-- !query 125 output org.apache.spark.sql.catalyst.parser.ParseException The value of from-to unit must be a string(line 1, pos 7) @@ -1187,11 +1233,11 @@ select 1 year to month -------^^^ --- !query 121 +-- !query 126 select '1' year to second --- !query 121 schema +-- !query 126 schema struct<> --- !query 121 output +-- !query 126 output org.apache.spark.sql.catalyst.parser.ParseException Intervals FROM year TO second are not supported.(line 1, pos 7) @@ -1201,11 +1247,11 @@ select '1' year to second -------^^^ --- !query 122 +-- !query 127 select 1 year '2-1' year to month --- !query 122 schema +-- !query 127 schema struct<> --- !query 122 output +-- !query 127 output org.apache.spark.sql.catalyst.parser.ParseException Can only have a single from-to unit in the interval literal syntax(line 1, pos 14) @@ -1215,11 +1261,11 @@ select 1 year '2-1' year to month --------------^^^ --- !query 123 +-- !query 128 select (-30) day --- !query 123 schema +-- !query 128 schema struct<> --- !query 123 output +-- !query 128 output org.apache.spark.sql.catalyst.parser.ParseException no viable alternative at input 'day'(line 1, pos 13) @@ -1229,11 +1275,11 @@ select (-30) day -------------^^^ --- !query 124 +-- !query 129 select (a + 1) day --- !query 124 schema +-- !query 129 schema struct<> --- !query 124 output +-- !query 129 output org.apache.spark.sql.catalyst.parser.ParseException no viable alternative at input 'day'(line 1, pos 15) @@ -1243,11 +1289,11 @@ select (a + 1) day ---------------^^^ --- !query 125 +-- !query 130 select 30 day day day --- !query 125 schema +-- !query 130 schema struct<> --- !query 125 output +-- !query 130 output org.apache.spark.sql.catalyst.parser.ParseException no viable alternative at input 'day'(line 1, pos 14) diff --git a/sql/core/src/test/resources/sql-tests/results/interval.sql.out b/sql/core/src/test/resources/sql-tests/results/interval.sql.out index b178c18af77c..1c84bb4502f0 100644 --- a/sql/core/src/test/resources/sql-tests/results/interval.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/interval.sql.out @@ -1,5 +1,5 @@ -- Automatically generated by SQLQueryTestSuite --- Number of queries: 114 +-- Number of queries: 119 -- !query 0 @@ -207,9 +207,10 @@ struct +struct<> -- !query 25 output -NULL +java.lang.ArithmeticException +divide by zero -- !query 26 @@ -1101,3 +1102,45 @@ select interval '1 ' day struct -- !query 113 output 1 days + + +-- !query 114 +select -(a) from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) +-- !query 114 schema +struct<(- a):interval> +-- !query 114 output +-178956970 years -8 months + + +-- !query 115 +select a - b from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) +-- !query 115 schema +struct<(a - b):interval> +-- !query 115 output +1 months + + +-- !query 116 +select b + interval '1 month' from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) +-- !query 116 schema +struct<(b + INTERVAL '1 months'):interval> +-- !query 116 output +-178956970 years -8 months + + +-- !query 117 +select a * 1.1 from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) +-- !query 117 schema +struct<> +-- !query 117 output +java.lang.ArithmeticException +integer overflow + + +-- !query 118 +select a / 0.5 from values (interval '-2147483648 months', interval '2147483647 months') t(a, b) +-- !query 118 schema +struct<> +-- !query 118 output +java.lang.ArithmeticException +integer overflow