This is an automated email from the ASF dual-hosted git repository.

andygrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git


The following commit(s) were added to refs/heads/main by this push:
     new 7123be7b52 feat: support multiply_dt_interval with codegen dispatch 
(#4900)
7123be7b52 is described below

commit 7123be7b52251739c85259a8f92fff7bf961892c
Author: Peter Lee <[email protected]>
AuthorDate: Fri Jul 24 22:33:13 2026 +0800

    feat: support multiply_dt_interval with codegen dispatch (#4900)
---
 native/core/src/execution/planner.rs               |  8 ++-
 .../codegen/CometBatchKernelCodegenInput.scala     | 15 +++--
 .../org/apache/comet/serde/QueryPlanSerde.scala    |  1 +
 .../scala/org/apache/comet/serde/datetime.scala    |  4 +-
 .../scala/org/apache/comet/serde/literals.scala    |  9 ++-
 .../comet/udf/codegen/CometScalaUDFCodegen.scala   |  2 +-
 .../expressions/datetime/multiply_dt_interval.sql  | 67 ++++++++++++++++++++++
 7 files changed, 95 insertions(+), 11 deletions(-)

diff --git a/native/core/src/execution/planner.rs 
b/native/core/src/execution/planner.rs
index a4aedfc991..835dfcfde4 100644
--- a/native/core/src/execution/planner.rs
+++ b/native/core/src/execution/planner.rs
@@ -365,6 +365,9 @@ impl PhysicalPlanner {
                         DataType::Time64(TimeUnit::Nanosecond) => {
                             ScalarValue::Time64Nanosecond(None)
                         }
+                        DataType::Duration(TimeUnit::Microsecond) => {
+                            ScalarValue::DurationMicrosecond(None)
+                        }
                         dt => {
                             return Err(GeneralError(format!("{dt:?} is not 
supported in Comet")))
                         }
@@ -391,9 +394,12 @@ impl PhysicalPlanner {
                             DataType::Timestamp(TimeUnit::Microsecond, 
Some(tz)) => {
                                 
ScalarValue::TimestampMicrosecond(Some(*value), Some(tz))
                             }
+                            DataType::Duration(TimeUnit::Microsecond) => {
+                                ScalarValue::DurationMicrosecond(Some(*value))
+                            }
                             dt => {
                                 return Err(GeneralError(format!(
-                                    "Expected either 'Int64' or 'Timestamp' 
for LongVal, but found {dt:?}"
+                                    "Expected 'Int64', 'Timestamp', or 
'Duration(Microsecond)' for LongVal, but found {dt:?}"
                                 )))
                             }
                         },
diff --git 
a/spark/src/main/scala/org/apache/comet/codegen/CometBatchKernelCodegenInput.scala
 
b/spark/src/main/scala/org/apache/comet/codegen/CometBatchKernelCodegenInput.scala
index f31c712c62..1d94bdc8dc 100644
--- 
a/spark/src/main/scala/org/apache/comet/codegen/CometBatchKernelCodegenInput.scala
+++ 
b/spark/src/main/scala/org/apache/comet/codegen/CometBatchKernelCodegenInput.scala
@@ -62,6 +62,7 @@ private[codegen] object CometBatchKernelCodegenInput extends 
CometTypeShim {
     classOf[Float4Vector],
     classOf[Float8Vector],
     classOf[DateDayVector],
+    classOf[DurationVector],
     classOf[TimeNanoVector],
     classOf[TimeStampMicroVector],
     classOf[TimeStampMicroTZVector])
@@ -135,6 +136,7 @@ private[codegen] object CometBatchKernelCodegenInput 
extends CometTypeShim {
     val longCases = withOrd.collect {
       case (ArrowColumnSpec(cls, _), ord)
           if cls == classOf[BigIntVector] ||
+            cls == classOf[DurationVector] ||
             cls == classOf[TimeNanoVector] ||
             cls == classOf[TimeStampMicroVector] ||
             cls == classOf[TimeStampMicroTZVector] =>
@@ -594,7 +596,8 @@ private[codegen] object CometBatchKernelCodegenInput 
extends CometTypeShim {
     case ByteType => s"getByte($idx)"
     case ShortType => s"getShort($idx)"
     case IntegerType | DateType => s"getInt($idx)"
-    case LongType | TimestampType | TimestampNTZType => s"getLong($idx)"
+    case LongType | TimestampType | TimestampNTZType | _: DayTimeIntervalType 
=>
+      s"getLong($idx)"
     case dt if isTimeType(dt) => s"getLong($idx)"
     case FloatType => s"getFloat($idx)"
     case DoubleType => s"getDouble($idx)"
@@ -696,7 +699,7 @@ private[codegen] object CometBatchKernelCodegenInput 
extends CometTypeShim {
            |      public int getInt(int i) {
            |        return $childField.getInt(startIndex + i);
            |      }""".stripMargin
-      case LongType | TimestampType | TimestampNTZType =>
+      case LongType | TimestampType | TimestampNTZType | _: 
DayTimeIntervalType =>
         s"""      @Override
            |      public long getLong(int i) {
            |        return $childField.getLong(startIndex + i);
@@ -854,7 +857,7 @@ private[codegen] object CometBatchKernelCodegenInput 
extends CometTypeShim {
           s"        case $fi: return ${path}_f$fi.getShort(this.rowIdx);"
         case IntegerType | DateType =>
           s"        case $fi: return ${path}_f$fi.getInt(this.rowIdx);"
-        case LongType | TimestampType | TimestampNTZType =>
+        case LongType | TimestampType | TimestampNTZType | _: 
DayTimeIntervalType =>
           s"        case $fi: return ${path}_f$fi.getLong(this.rowIdx);"
         case dt if isTimeType(dt) =>
           s"        case $fi: return ${path}_f$fi.getLong(this.rowIdx);"
@@ -908,8 +911,10 @@ private[codegen] object CometBatchKernelCodegenInput 
extends CometTypeShim {
     val longCases = scalarOrd.collect {
       case (f, fi)
           if f.sparkType == LongType || f.sparkType == TimestampType ||
-            f.sparkType == TimestampNTZType || isTimeType(f.sparkType) =>
-        fieldReadScalar(fi, LongType, f.nullable)
+            f.sparkType == TimestampNTZType ||
+            f.sparkType.isInstanceOf[DayTimeIntervalType] ||
+            isTimeType(f.sparkType) =>
+        fieldReadScalar(fi, f.sparkType, f.nullable)
     }
     val floatCases =
       scalarOrd.collect {
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 f62db397cb..19ac36b040 100644
--- a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
@@ -301,6 +301,7 @@ object QueryPlanSerde extends Logging with CometExprShim 
with CometTypeShim {
       classOf[MakeTimestamp] -> CometMakeTimestamp,
       classOf[MakeYMInterval] -> CometMakeYMInterval,
       classOf[MakeDTInterval] -> CometMakeDTInterval,
+      classOf[MultiplyDTInterval] -> CometMultiplyDTInterval,
       classOf[MicrosToTimestamp] -> CometMicrosToTimestamp,
       classOf[MillisToTimestamp] -> CometMillisToTimestamp,
       classOf[MonthsBetween] -> CometMonthsBetween,
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 9f2533d85f..d968c00cd1 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, 
ConvertTimezone, DateAdd, DateDiff, DateFormatClass, DateFromUnixDate, DateSub, 
DayOfMonth, DayOfWeek, DayOfYear, Days, Expression, FromUTCTimestamp, 
GetDateField, GetTimestamp, Hour, Hours, LastDay, Literal, MakeDate, 
MakeDTInterval, MakeTimestamp, MakeYMInterval, MicrosToTimestamp, 
MillisToTimestamp, Minute, Month, MonthsBetween, NextDay, 
PreciseTimestampConversion, Quarter, Second, SecondsToTimestamp, ToUnixTimest 
[...]
+import org.apache.spark.sql.catalyst.expressions.{AddMonths, Attribute, 
ConvertTimezone, DateAdd, DateDiff, DateFormatClass, DateFromUnixDate, DateSub, 
DayOfMonth, DayOfWeek, DayOfYear, Days, Expression, FromUTCTimestamp, 
GetDateField, GetTimestamp, Hour, Hours, LastDay, Literal, MakeDate, 
MakeDTInterval, MakeTimestamp, MakeYMInterval, MicrosToTimestamp, 
MillisToTimestamp, Minute, Month, MonthsBetween, MultiplyDTInterval, NextDay, 
PreciseTimestampConversion, Quarter, Second, SecondsToTim [...]
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.types.{DataType, DateType, DoubleType, FloatType, 
IntegerType, LongType, StringType, TimestampNTZType, TimestampType}
 import org.apache.spark.unsafe.types.UTF8String
@@ -954,6 +954,8 @@ object CometMakeYMInterval extends 
CometCodegenDispatch[MakeYMInterval]
 
 object CometMakeDTInterval extends CometCodegenDispatch[MakeDTInterval]
 
+object CometMultiplyDTInterval extends CometCodegenDispatch[MultiplyDTInterval]
+
 /**
  * Spark's internal `PreciseTimestampConversion` reinterprets a value between 
the timestamp types
  * (`TimestampType` / `TimestampNTZType`) and `LongType` without losing 
microsecond precision. It
diff --git a/spark/src/main/scala/org/apache/comet/serde/literals.scala 
b/spark/src/main/scala/org/apache/comet/serde/literals.scala
index 4f2a5dfa5e..bac3631bab 100644
--- a/spark/src/main/scala/org/apache/comet/serde/literals.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/literals.scala
@@ -24,7 +24,7 @@ import java.lang
 import org.apache.spark.internal.Logging
 import org.apache.spark.sql.catalyst.expressions.{Attribute, Literal}
 import org.apache.spark.sql.catalyst.util.ArrayData
-import org.apache.spark.sql.types.{ArrayType, BinaryType, BooleanType, 
ByteType, DateType, Decimal, DecimalType, DoubleType, FloatType, IntegerType, 
LongType, NullType, ShortType, StringType, TimestampNTZType, TimestampType}
+import org.apache.spark.sql.types.{ArrayType, BinaryType, BooleanType, 
ByteType, DateType, DayTimeIntervalType, Decimal, DecimalType, DoubleType, 
FloatType, IntegerType, LongType, NullType, ShortType, StringType, 
TimestampNTZType, TimestampType}
 import org.apache.spark.unsafe.types.UTF8String
 
 import com.google.protobuf.ByteString
@@ -56,7 +56,10 @@ object CometLiteral extends CometExpressionSerde[Literal] 
with Logging {
             .isInstanceOf[ArrayType])))) {
       Compatible(None)
     } else {
-      Unsupported(Some(s"Unsupported data type ${expr.dataType}"))
+      expr.dataType match {
+        case _: DayTimeIntervalType => Compatible(None)
+        case _ => Unsupported(Some(s"Unsupported data type ${expr.dataType}"))
+      }
     }
   }
 
@@ -78,7 +81,7 @@ object CometLiteral extends CometExpressionSerde[Literal] 
with Logging {
         case _: ByteType => exprBuilder.setByteVal(value.asInstanceOf[Byte])
         case _: ShortType => exprBuilder.setShortVal(value.asInstanceOf[Short])
         case _: IntegerType | _: DateType => 
exprBuilder.setIntVal(value.asInstanceOf[Int])
-        case _: LongType | _: TimestampType | _: TimestampNTZType =>
+        case _: LongType | _: TimestampType | _: TimestampNTZType | _: 
DayTimeIntervalType =>
           exprBuilder.setLongVal(value.asInstanceOf[Long])
         case dt if isTimeType(dt) =>
           exprBuilder.setLongVal(value.asInstanceOf[Long])
diff --git 
a/spark/src/main/scala/org/apache/comet/udf/codegen/CometScalaUDFCodegen.scala 
b/spark/src/main/scala/org/apache/comet/udf/codegen/CometScalaUDFCodegen.scala
index f575dd5b53..145698102e 100644
--- 
a/spark/src/main/scala/org/apache/comet/udf/codegen/CometScalaUDFCodegen.scala
+++ 
b/spark/src/main/scala/org/apache/comet/udf/codegen/CometScalaUDFCodegen.scala
@@ -219,7 +219,7 @@ class CometScalaUDFCodegen extends CometUDF with Logging {
       StructColumnSpec(nullable = true, fieldSpecs)
     case _: BitVector | _: TinyIntVector | _: SmallIntVector | _: IntVector | 
_: BigIntVector |
         _: Float4Vector | _: Float8Vector | _: DecimalVector | _: 
VarCharVector |
-        _: VarBinaryVector | _: DateDayVector | _: TimeStampMicroVector |
+        _: VarBinaryVector | _: DateDayVector | _: DurationVector | _: 
TimeStampMicroVector |
         _: TimeStampMicroTZVector =>
       ScalarColumnSpec(v.getClass.asInstanceOf[Class[_ <: ValueVector]], 
nullable = true)
     case other =>
diff --git 
a/spark/src/test/resources/sql-tests/expressions/datetime/multiply_dt_interval.sql
 
b/spark/src/test/resources/sql-tests/expressions/datetime/multiply_dt_interval.sql
new file mode 100644
index 0000000000..65f7535a6d
--- /dev/null
+++ 
b/spark/src/test/resources/sql-tests/expressions/datetime/multiply_dt_interval.sql
@@ -0,0 +1,67 @@
+-- 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 multiply_dt_interval through the codegen dispatcher; produces 
DayTimeIntervalType.
+-- Config: spark.comet.exec.scalaUDF.codegen.enabled=true
+
+statement
+CREATE TABLE test_multiply_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_multiply_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(0 AS TINYINT), CAST(0 AS SMALLINT), 0, CAST(0 AS 
BIGINT), CAST(0.5 AS FLOAT), CAST(0.5 AS DOUBLE), CAST(0.50 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_multiply_dt_interval
+
+-- literal interval input
+query
+SELECT INTERVAL '1 02:03:04.500000' DAY TO SECOND * i FROM 
test_multiply_dt_interval
+
+-- numeric on the left is normalized by Spark to multiply_dt_interval.
+query
+SELECT i * make_dt_interval(days, hours, minutes, seconds),
+  2 * INTERVAL '1 02:03:04.500000' DAY TO SECOND
+FROM test_multiply_dt_interval
+
+-- literal multipliers, including half-up rounding to the nearest microsecond.
+query
+SELECT
+  make_dt_interval(1, 2, 3, 4.5) * 2,
+  INTERVAL '0.000001' SECOND * 0.5D,
+  INTERVAL '0.000001' SECOND * CAST(0.50 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_multiply_dt_interval
+
+-- 106751991 days is valid, but multiplying it by two exceeds the int64 
microsecond range.
+query expect_error(overflow)
+SELECT make_dt_interval(106751991) * 2


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to