diff --git a/docs/source/user-guide/latest/expressions.md b/docs/source/user-guide/latest/expressions.md index 192eae807e..6f6d5af00c 100644 --- a/docs/source/user-guide/latest/expressions.md +++ b/docs/source/user-guide/latest/expressions.md @@ -417,7 +417,7 @@ The type-name conversion functions (`bigint`, `binary`, `boolean`, `date`, `deci | `*` | ✅ | Native | Interval multiplication falls back | | `+` | ✅ | Native | | | `-` | ✅ | Native | | -| `/` | ✅ | Native | | +| `/` | ✅ | Native | DayTime interval division routes through the JVM codegen dispatcher; YearMonth and Calendar interval division fall back | | `abs` | ✅ | Native | Interval types fall back | | `acos` | ✅ | Native | | | `acosh` | ✅ | Native | | diff --git a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala index 85601a9e0c..7d1c6c7542 100644 --- a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala +++ b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala @@ -287,6 +287,7 @@ object QueryPlanSerde extends Logging with CometExprShim with CometTypeShim { classOf[ConvertTimezone] -> CometConvertTimezone, classOf[DateAdd] -> CometDateAdd, classOf[DateDiff] -> CometDateDiff, + classOf[DivideDTInterval] -> CometDivideDTInterval, classOf[DateFormatClass] -> CometDateFormat, classOf[DateFromUnixDate] -> CometDateFromUnixDate, classOf[Days] -> CometDays, diff --git a/spark/src/main/scala/org/apache/comet/serde/datetime.scala b/spark/src/main/scala/org/apache/comet/serde/datetime.scala index 814dd6ca6f..a50029f9de 100644 --- a/spark/src/main/scala/org/apache/comet/serde/datetime.scala +++ b/spark/src/main/scala/org/apache/comet/serde/datetime.scala @@ -21,7 +21,7 @@ package org.apache.comet.serde import java.util.Locale -import org.apache.spark.sql.catalyst.expressions.{AddMonths, Attribute, Cast, ConvertTimezone, DateAdd, DateDiff, DateFormatClass, DateFromUnixDate, DateSub, DayOfMonth, DayOfWeek, DayOfYear, Days, Expression, FromUTCTimestamp, GetDateField, GetTimestamp, Hour, Hours, LastDay, Literal, MakeDate, MakeDTInterval, MakeInterval, MakeTimestamp, MakeYMInterval, MicrosToTimestamp, MillisToTimestamp, Minute, Month, MonthsBetween, MultiplyDTInterval, NextDay, PreciseTimestampConversion, Quarter, Second, SecondsToTimestamp, TimestampAdd, TimestampDiff, ToUnixTimestamp, ToUTCTimestamp, TruncDate, TruncTimestamp, UnixDate, UnixMicros, UnixMillis, UnixSeconds, UnixTimestamp, WeekDay, WeekOfYear, Year} +import org.apache.spark.sql.catalyst.expressions.{AddMonths, Attribute, Cast, ConvertTimezone, DateAdd, DateDiff, DateFormatClass, DateFromUnixDate, DateSub, DayOfMonth, DayOfWeek, DayOfYear, Days, DivideDTInterval, Expression, FromUTCTimestamp, GetDateField, GetTimestamp, Hour, Hours, LastDay, Literal, MakeDate, MakeDTInterval, MakeInterval, MakeTimestamp, MakeYMInterval, MicrosToTimestamp, MillisToTimestamp, Minute, Month, MonthsBetween, MultiplyDTInterval, NextDay, PreciseTimestampConversion, Quarter, Second, SecondsToTimestamp, TimestampAdd, TimestampDiff, ToUnixTimestamp, ToUTCTimestamp, TruncDate, TruncTimestamp, UnixDate, UnixMicros, UnixMillis, UnixSeconds, UnixTimestamp, WeekDay, WeekOfYear, Year} import org.apache.spark.sql.internal.SQLConf import org.apache.spark.sql.types.{CalendarIntervalType, DataType, DateType, DoubleType, FloatType, IntegerType, LongType, StringType, TimestampNTZType, TimestampType} import org.apache.spark.unsafe.types.UTF8String @@ -957,6 +957,8 @@ object CometMakeYMInterval extends CometCodegenDispatch[MakeYMInterval] object CometMakeDTInterval extends CometCodegenDispatch[MakeDTInterval] +object CometDivideDTInterval extends CometCodegenDispatch[DivideDTInterval] + object CometMakeInterval extends CometExpressionSerde[MakeInterval] with CodegenDispatchFallback { private val incompatReason = "The native implementation converts seconds to `Float64`, which can lose microsecond" + diff --git a/spark/src/test/resources/sql-tests/expressions/datetime/divide_dt_interval.sql b/spark/src/test/resources/sql-tests/expressions/datetime/divide_dt_interval.sql new file mode 100644 index 0000000000..c7b16f8aa2 --- /dev/null +++ b/spark/src/test/resources/sql-tests/expressions/datetime/divide_dt_interval.sql @@ -0,0 +1,65 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- Routes divide_dt_interval through the codegen dispatcher; produces DayTimeIntervalType. +-- Config: spark.comet.exec.scalaUDF.codegen.enabled=true + +statement +CREATE TABLE test_divide_dt_interval(days int, hours int, minutes int, seconds decimal(18,6), b tinyint, s smallint, i int, l long, f float, d double, dec decimal(10,2)) USING parquet + +statement +INSERT INTO test_divide_dt_interval VALUES + (1, 2, 3, 4.500000, CAST(2 AS TINYINT), CAST(3 AS SMALLINT), 2, CAST(3 AS BIGINT), CAST(1.5 AS FLOAT), CAST(2.5 AS DOUBLE), CAST(2.50 AS DECIMAL(10, 2))), + (-1, 0, 30, 15.250000, CAST(-2 AS TINYINT), CAST(-3 AS SMALLINT), -2, CAST(-3 AS BIGINT), CAST(-1.5 AS FLOAT), CAST(-2.5 AS DOUBLE), CAST(-2.50 AS DECIMAL(10, 2))), + (0, 0, 0, 0.000001, CAST(2 AS TINYINT), CAST(2 AS SMALLINT), 2, CAST(2 AS BIGINT), CAST(2.0 AS FLOAT), CAST(2.0 AS DOUBLE), CAST(2.00 AS DECIMAL(10, 2))), + (2, -6, 0, 0.000000, NULL, NULL, NULL, NULL, NULL, NULL, NULL) + +query +SELECT + make_dt_interval(days, hours, minutes, seconds) / b, + make_dt_interval(days, hours, minutes, seconds) / s, + make_dt_interval(days, hours, minutes, seconds) / i, + make_dt_interval(days, hours, minutes, seconds) / l, + make_dt_interval(days, hours, minutes, seconds) / f, + make_dt_interval(days, hours, minutes, seconds) / d, + make_dt_interval(days, hours, minutes, seconds) / dec +FROM test_divide_dt_interval + +-- literal interval input +query +SELECT INTERVAL '1 02:03:04.500000' DAY TO SECOND / i FROM test_divide_dt_interval + +-- literal divisors, including half-up rounding to the nearest microsecond. +query +SELECT + make_dt_interval(1, 2, 3, 4.5) / 2, + INTERVAL '0.000001' SECOND / 2, + INTERVAL '0.000001' SECOND / CAST(2.00 AS DECIMAL(10, 2)), + make_dt_interval(-1, 0, 30, 15.25) / 1.5D + +-- null interval input +query +SELECT make_dt_interval(NULL, hours, minutes, seconds) / 2 +FROM test_divide_dt_interval + +-- Division by zero fails regardless of ANSI mode. +query expect_error(zero) +SELECT make_dt_interval(1) / 0 + +-- This interval is Long.MinValue microseconds, which cannot be divided by -1. +query expect_error(overflow) +SELECT make_dt_interval(-106751991, -4, 0, -54.775808) / -1