Skip to content

feat: support interval types and make_ym_interval / make_dt_interval - #4541

Merged
andygrove merged 4 commits into
apache:mainfrom
andygrove:interval-type-support
Jul 1, 2026
Merged

feat: support interval types and make_ym_interval / make_dt_interval#4541
andygrove merged 4 commits into
apache:mainfrom
andygrove:interval-type-support

Conversation

@andygrove

Copy link
Copy Markdown
Member

Which issue does this PR close?

Part of #4540.

Rationale for this change

Comet had no support for Spark's interval data types, so every expression producing or consuming an interval forced a fallback to Spark, and the JVM codegen dispatcher (CometCodegenDispatch) could not cover them either because CometBatchKernelCodegen.isSupportedDataType rejected interval output. This PR adds the foundational type support and uses the existing codegen-dispatch mechanism to make the interval constructors run natively while matching Spark exactly.

What changes are included in this PR?

Type support for YearMonthIntervalType and DayTimeIntervalType:

  • native/proto/src/proto/types.proto: add YEAR_MONTH_INTERVAL (18) and DAY_TIME_INTERVAL (19) to DataTypeId.
  • native/core/src/execution/serde.rs: map them to Arrow Interval(YearMonth) and Duration(Microsecond).
  • Utils.toArrowType / fromArrowType: same mapping on the JVM side.
  • QueryPlanSerde.serializeDataType: emit the new type ids.
  • CometBatchKernelCodegen: accept both interval types in isSupportedDataType, resolve IntervalYearVector / DurationVector, and emit primitive set() writes.

Representation note: Spark stores DayTimeIntervalType as microseconds in an int64, which round-trips faithfully through Arrow Duration(Microsecond). Arrow Interval(DayTime) uses a {days, millis} layout that would lose microsecond precision, so it is intentionally not used.

Expressions (via CometCodegenDispatch, registered in temporalExpressions):

  • make_ym_interval -> CometMakeYMInterval
  • make_dt_interval -> CometMakeDTInterval

Out of scope (follow-ups tracked in #4540): CalendarIntervalType, and interval arithmetic / multiply_*_interval / extract of interval fields.

How are these changes tested?

SQL file tests under spark/src/test/resources/sql-tests/expressions/datetime/ (make_ym_interval.sql, make_dt_interval.sql) run by CometSqlFileTestSuite. The query mode uses checkSparkAnswerAndOperator, asserting both answer parity with Spark and native execution (a fallback fails the test). Cases cover column and literal inputs, default arguments, negatives, and nulls. The full SQL suite shows no new regressions.

…[skip ci]

Implements the type-support prerequisite from issue apache#4540: add Spark
YearMonthIntervalType and DayTimeIntervalType as physical types that round-trip
through Comet's Arrow FFI, and route the make_ym_interval / make_dt_interval
constructors through the JVM codegen dispatcher so they execute natively and
match Spark exactly.

Type plumbing:
- proto: add YEAR_MONTH_INTERVAL (18) and DAY_TIME_INTERVAL (19) to DataTypeId.
- native serde.rs: map them to Arrow Interval(YearMonth) and Duration(Microsecond)
  respectively. DayTime stores microseconds in an int64, which matches
  Duration(Microsecond) rather than the lossy Interval(DayTime) {days, millis}.
- Utils.toArrowType / fromArrowType: same mapping on the JVM side.
- QueryPlanSerde.serializeDataType: emit the new type ids.
- CometBatchKernelCodegen: accept the two interval types in isSupportedDataType
  and resolve IntervalYearVector / DurationVector; emit primitive set() writes.

Expressions:
- make_ym_interval -> CometMakeYMInterval, make_dt_interval -> CometMakeDTInterval,
  both via CometCodegenDispatch, registered in temporalExpressions.

CalendarIntervalType and interval arithmetic remain follow-ups under apache#4540.

Tests: SQL file tests for both constructors assert answer parity and native
execution (checkSparkAnswerAndOperator), covering column and literal inputs,
defaults, negatives, and nulls.
@andygrove
andygrove marked this pull request as ready for review June 4, 2026 13:45
# Conflicts:
#	spark/src/main/scala/org/apache/comet/codegen/CometBatchKernelCodegenOutput.scala
@andygrove andygrove added this to the 0.17.0 milestone Jun 4, 2026
@andygrove andygrove modified the milestones: 0.17.0, 1.0.0 Jun 19, 2026

@mbutrovich mbutrovich left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly just a test request, this is looking good @andygrove!

Comment on lines +155 to +158
case _: YearMonthIntervalType => new ArrowType.Interval(IntervalUnit.YEAR_MONTH)
// Spark stores DayTimeIntervalType as microseconds in an int64, matching Arrow
// Duration(Microsecond) rather than the lossy Interval(DayTime) {days, millis} layout.
case _: DayTimeIntervalType => new ArrowType.Duration(TimeUnit.MICROSECOND)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified correct, no change needed for this PR. The representation choices match Spark's internal storage exactly: YearMonthIntervalType is int32 months, which maps cleanly to Interval(YearMonth); DayTimeIntervalType is an int64 microsecond count, which round-trips faithfully through Duration(Microsecond). The comment correctly notes that Interval(DayTime)'s {days, millis} layout would lose microsecond precision, so avoiding it is right.

One forward-looking note, not a blocker. Both mappings drop Spark's interval field qualifiers. YearMonthIntervalType(YEAR, YEAR) and YearMonthIntervalType(YEAR, MONTH) both serialize to the same Interval(YearMonth), and on the way back fromArrowType produces the default YearMonthIntervalType() (YEAR TO MONTH); same story for DayTimeIntervalType start/end fields. This is harmless here because the only expressions wired are make_ym_interval and make_dt_interval, both of which Spark types with the default fields (YearMonthIntervalType(), DayTimeIntervalType()), and interval columns are not scanned yet. But once #4540 adds interval scans or field-qualified interval handling, this normalization will change a column's declared type. Worth a tracking note so it is not lost.

@@ -0,0 +1,35 @@
-- Licensed to the Apache Software Foundation (ASF) under one

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The coverage of column, literal, default, negative, and null inputs is good, and using checkSparkAnswerAndOperator (the default query mode) correctly asserts native execution and exercises the interval read-back path.

One gap worth filling: overflow. make_ym_interval computes years * 12 + months through IntervalUtils.makeYearMonthInterval, which uses exact arithmetic and throws ARITHMETIC_OVERFLOW unconditionally (not ANSI-gated); make_dt_interval overflows its int64 microsecond total similarly. Since these route through the codegen dispatcher (Spark's own doGenCode), an overflow case is a good way to confirm the dispatched path propagates Spark's exception identically rather than diverging. Something like:

query expect_error(ARITHMETIC_OVERFLOW)
SELECT make_ym_interval(178956971, 0)

Please confirm the exact error token the suite matches. The value 178956971 * 12 exceeds Int.MaxValue, so both engines should raise the same overflow.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mbutrovich. I added overflow tests

case yi: ArrowType.Interval if yi.getUnit == IntervalUnit.YEAR_MONTH =>
YearMonthIntervalType()
case di: ArrowType.Interval if di.getUnit == IntervalUnit.DAY_TIME => DayTimeIntervalType()
case d: ArrowType.Duration if d.getUnit == TimeUnit.MICROSECOND => DayTimeIntervalType()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation, no change requested. fromArrowType now maps both Interval(DayTime) (line 110) and Duration(Microsecond) (line 111) to DayTimeIntervalType, while toArrowType only ever emits Duration(Microsecond). So the Interval(DayTime) reverse case is dead for Comet-produced data and is presumably kept for externally-produced Arrow. That is fine and consistent with the precision rationale, just flagging the asymmetry in case it was unintentional.

andygrove added 2 commits July 1, 2026 10:52
Confirm the codegen-dispatch path propagates Spark's arithmetic-overflow
exception identically. The expect_error pattern uses the lowercase word
overflow so it matches every Spark version: 4.x raises
INTERVAL_ARITHMETIC_OVERFLOW while 3.x raises a raw ArithmeticException.
# Conflicts:
#	spark/src/main/scala/org/apache/comet/serde/datetime.scala

@mbutrovich mbutrovich left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @andygrove!

@andygrove
andygrove merged commit 464afe1 into apache:main Jul 1, 2026
69 checks passed
@andygrove
andygrove deleted the interval-type-support branch July 1, 2026 19:06
@andygrove

Copy link
Copy Markdown
Member Author

Merged. Thanks @mbutrovich

marvelshan pushed a commit to marvelshan/datafusion-comet that referenced this pull request Jul 2, 2026
…pache#4541)

* feat: support interval types and make_ym_interval / make_dt_interval [skip ci]

Implements the type-support prerequisite from issue apache#4540: add Spark
YearMonthIntervalType and DayTimeIntervalType as physical types that round-trip
through Comet's Arrow FFI, and route the make_ym_interval / make_dt_interval
constructors through the JVM codegen dispatcher so they execute natively and
match Spark exactly.

Type plumbing:
- proto: add YEAR_MONTH_INTERVAL (18) and DAY_TIME_INTERVAL (19) to DataTypeId.
- native serde.rs: map them to Arrow Interval(YearMonth) and Duration(Microsecond)
  respectively. DayTime stores microseconds in an int64, which matches
  Duration(Microsecond) rather than the lossy Interval(DayTime) {days, millis}.
- Utils.toArrowType / fromArrowType: same mapping on the JVM side.
- QueryPlanSerde.serializeDataType: emit the new type ids.
- CometBatchKernelCodegen: accept the two interval types in isSupportedDataType
  and resolve IntervalYearVector / DurationVector; emit primitive set() writes.

Expressions:
- make_ym_interval -> CometMakeYMInterval, make_dt_interval -> CometMakeDTInterval,
  both via CometCodegenDispatch, registered in temporalExpressions.

CalendarIntervalType and interval arithmetic remain follow-ups under apache#4540.

Tests: SQL file tests for both constructors assert answer parity and native
execution (checkSparkAnswerAndOperator), covering column and literal inputs,
defaults, negatives, and nulls.

* test: add overflow cases for make_ym_interval and make_dt_interval

Confirm the codegen-dispatch path propagates Spark's arithmetic-overflow
exception identically. The expect_error pattern uses the lowercase word
overflow so it matches every Spark version: 4.x raises
INTERVAL_ARITHMETIC_OVERFLOW while 3.x raises a raw ArithmeticException.
andygrove added a commit that referenced this pull request Jul 3, 2026
PR #4541 enabled the YearMonthIntervalType and DayTimeIntervalType
constructors via codegen dispatch. Update the expression support list to
reflect that make_dt_interval and make_ym_interval are now supported.

@peterxcli peterxcli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andygrove I notice that shuffle and array or nested support for YearMonthInterval and DayTimeInterval hasn't done yet.

26/07/20 17:26:13 WARN CometExecRule: Comet cannot execute some parts of this plan natively (set spark.comet.explainFallback.enabled=false to disable this logging):
CollectLimit
+- Project
   +-  Exchange [COMET: unsupported shuffle data type YearMonthIntervalType(0,1) for input i#11, Comet columnar shuffle not enabled]
      +- CometProject
         +- CometNativeScan parquet
+-  LocalTableScan [COMET: Unsupported array element of type YearMonthIntervalType(0,1)]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants