Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/user-guide/latest/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ The type-name conversion functions (`bigint`, `binary`, `boolean`, `date`, `deci
| `localtimestamp` | ✅ | — | |
| `make_date` | ✅ | Native | |
| `make_dt_interval` | ✅ | Codegen dispatch | |
| `make_interval` | 🔜 | | Produces legacy CalendarInterval; tracked by [#5061](https://github.com/apache/datafusion-comet/issues/5061) |
| `make_interval` | | Native | Falls back by default; opt-in via allowIncompatible ([details](compatibility/expressions/datetime.md)) |
| `make_time` | 🔜 | — | Spark 4.1 TIME type; tracked by [#4288](https://github.com/apache/datafusion-comet/issues/4288) |
| `make_timestamp` | ✅ | Hybrid | |
| `make_timestamp_ltz` | ✅ | — | 2-arg TIME form falls back |
Expand Down
1 change: 1 addition & 0 deletions native/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions native/core/src/execution/jni_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use datafusion::{
};
use datafusion_comet_proto::spark_operator::Operator;
use datafusion_comet_spark_expr::url_funcs::{CometParseUrl, CometTryParseUrl};
use datafusion_comet_spark_expr::SparkMakeInterval;
use datafusion_spark::function::array::array_contains::SparkArrayContains;
use datafusion_spark::function::array::repeat::SparkArrayRepeat;
use datafusion_spark::function::bitwise::bit_count::SparkBitCount;
Expand Down Expand Up @@ -623,6 +624,7 @@ fn register_datafusion_spark_function(session_ctx: &SessionContext) {
session_ctx.register_udf(ScalarUDF::new_from_impl(SparkDateSub::default()));
session_ctx.register_udf(ScalarUDF::new_from_impl(SparkFromUtcTimestamp::default()));
session_ctx.register_udf(ScalarUDF::new_from_impl(SparkLastDay::default()));
session_ctx.register_udf(ScalarUDF::new_from_impl(SparkMakeInterval::default()));

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.

I do not think this registration is reachable. Because the serde sets the return type, create_scalar_function_expr skips the session_ctx.udf(...) lookup (planner.rs:3337) and goes to create_comet_physical_fun, where the "make_interval" arm builds SparkMakeInterval::new(fail_on_error) directly.

If you want to keep a registry entry for safety, SparkMakeDate is the precedent and it lives in all_scalar_functions() in comet_scalar_funcs.rs. Putting a Comet wrapper here is a little misleading, since everything else in register_datafusion_spark_function is a raw upstream UDF, and this one hardcodes fail_on_error = false via Default. If it ever did get used it would silently ignore ANSI.

session_ctx.register_udf(ScalarUDF::new_from_impl(SparkToUtcTimestamp::default()));
session_ctx.register_udf(ScalarUDF::new_from_impl(SparkSha1::default()));
session_ctx.register_udf(ScalarUDF::new_from_impl(SparkConcat::default()));
Expand Down
1 change: 1 addition & 0 deletions native/spark-expr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ edition = { workspace = true }
arrow = { workspace = true }
chrono = { workspace = true }
datafusion = { workspace = true }
datafusion-spark = { workspace = true }
chrono-tz = { workspace = true }
num = { workspace = true }
regex = { workspace = true }
Expand Down
7 changes: 5 additions & 2 deletions native/spark-expr/src/comet_scalar_funcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use crate::{
spark_isnan, spark_lpad, spark_make_decimal, spark_month_name, spark_read_side_padding,
spark_round, spark_rpad, spark_to_time, spark_unhex, spark_unscaled_value, EvalMode,
SparkArrayPositionFunc, SparkArraySlice, SparkArraysOverlap, SparkContains, SparkDateDiff,
SparkDateFromUnixDate, SparkDateTrunc, SparkFlatten, SparkMakeDate, SparkMakeTime,
SparkNextDay, SparkSecondsToTimestamp, SparkSizeFunc,
SparkDateFromUnixDate, SparkDateTrunc, SparkFlatten, SparkMakeDate, SparkMakeInterval,
SparkMakeTime, SparkNextDay, SparkSecondsToTimestamp, SparkSizeFunc,
};
use arrow::datatypes::DataType;
use datafusion::common::{DataFusionError, Result as DataFusionResult};
Expand Down Expand Up @@ -250,6 +250,9 @@ pub fn create_comet_physical_fun_with_eval_mode(
"make_date" => Ok(Arc::new(ScalarUDF::new_from_impl(SparkMakeDate::new(
fail_on_error,
)))),
"make_interval" => Ok(Arc::new(ScalarUDF::new_from_impl(SparkMakeInterval::new(
fail_on_error,
)))),
"next_day" => Ok(Arc::new(ScalarUDF::new_from_impl(SparkNextDay::new(
fail_on_error,
)))),
Expand Down
91 changes: 91 additions & 0 deletions native/spark-expr/src/datetime_funcs/make_interval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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.

use crate::arithmetic_overflow_error;
use arrow::array::Array;
use arrow::datatypes::DataType;
use datafusion::common::Result;
use datafusion::logical_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature};
use datafusion_spark::function::datetime::make_interval::SparkMakeInterval as DataFusionMakeInterval;

#[derive(Debug, PartialEq, Eq, Hash)]
pub struct SparkMakeInterval {
inner: DataFusionMakeInterval,
fail_on_error: bool,
}

impl SparkMakeInterval {
pub fn new(fail_on_error: bool) -> Self {
Self {
inner: DataFusionMakeInterval::new(),
fail_on_error,
}
}
}

impl Default for SparkMakeInterval {
fn default() -> Self {
Self::new(false)
}
}

impl ScalarUDFImpl for SparkMakeInterval {
fn name(&self) -> &str {
self.inner.name()
}

fn signature(&self) -> &Signature {
self.inner.signature()
}

fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
self.inner.return_type(arg_types)
}

fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
let inputs = if self.fail_on_error {
Some(args.args.clone())
} else {
None
};
let result = self.inner.invoke_with_args(args)?;

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.

There's a compatibility concern I'd like to flag with the underlying DataFusion kernel. Two related issues:

Nanosecond vs microsecond overflow. Spark's IntervalUtils.makeInterval stores time components as int64 microseconds via secs.toUnscaledLong, so a Decimal(18, 6) seconds value fits comfortably (max ≈ 1e18 micros, well under Long.MaxValue). DataFusion's kernel accumulates in nanoseconds, so it overflows at roughly secs > 9_223_372_036 (~292 years). Any Decimal(18, 6) seconds value beyond that boundary silently returns null under this PR (or throws under ANSI) while Spark returns a valid interval. Spark's own sql-tests/inputs/interval.sql exercises exactly this range:

select make_interval(1, 2, 3, 4, 0, 0, 123456789012.123456);

Float64 coercion loses microsecond precision. DataFusion's SparkMakeInterval signature coerces secs to Float64, but Spark's MakeInterval.inputTypes is Decimal(18, 6) and preserves microseconds exactly. For secs = 999999999.999999, the Float64 round-trip yields frac * 1e9 ≈ 999999046 instead of 999999000 — a ~46 ns drift that translates into a wrong microsecond count on the JVM side. The small values currently in the fixture (7.123456, 100.000001, -1.5) happen to be exactly representable so they don't expose this.

Given both, would it make sense to mark this expression Incompatible(Some("...")) in getSupportLevel, and add a getIncompatibleReasons() string so the auto-generated compat page warns users? Marking it Native in expressions.md with no caveat currently overstates the compatibility.


if let Some(inputs) = inputs {
let inputs_are_valid = |i| {
inputs.iter().all(|input| match input {
ColumnarValue::Array(values) => values.is_valid(i),
ColumnarValue::Scalar(value) => !value.is_null(),
})
};
let overflow = match &result {
ColumnarValue::Array(values) => values.nulls().is_some_and(|nulls| {
nulls.null_count() != 0
&& nulls
.iter()
.enumerate()
.any(|(i, is_valid)| !is_valid && inputs_are_valid(i))
}),
ColumnarValue::Scalar(value) => value.is_null() && inputs_are_valid(0),
};
if overflow {
return Err(arithmetic_overflow_error("interval").into());
}
}

Ok(result)
}
}
2 changes: 2 additions & 0 deletions native/spark-expr/src/datetime_funcs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod day_month_name;
mod extract_date_part;
mod hours;
mod make_date;
mod make_interval;
mod make_time;
mod next_day;
mod seconds_to_timestamp;
Expand All @@ -38,6 +39,7 @@ pub use extract_date_part::SparkMinute;
pub use extract_date_part::SparkSecond;
pub use hours::SparkHoursTransform;
pub use make_date::SparkMakeDate;
pub use make_interval::SparkMakeInterval;
pub use make_time::SparkMakeTime;
pub use next_day::SparkNextDay;
pub use seconds_to_timestamp::SparkSecondsToTimestamp;
Expand Down
5 changes: 3 additions & 2 deletions native/spark-expr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ pub use comet_scalar_funcs::{
pub use csv_funcs::*;
pub use datetime_funcs::{
spark_day_name, spark_month_name, spark_to_time, SparkDateDiff, SparkDateFromUnixDate,
SparkDateTrunc, SparkHour, SparkHoursTransform, SparkMakeDate, SparkMakeTime, SparkMinute,
SparkNextDay, SparkSecond, SparkSecondsToTimestamp, SparkUnixTimestamp, TimestampTruncExpr,
SparkDateTrunc, SparkHour, SparkHoursTransform, SparkMakeDate, SparkMakeInterval,
SparkMakeTime, SparkMinute, SparkNextDay, SparkSecond, SparkSecondsToTimestamp,
SparkUnixTimestamp, TimestampTruncExpr,
};
pub use error::{decimal_overflow_error, SparkError, SparkErrorWithContext, SparkResult};
pub use hash_funcs::*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ object QueryPlanSerde extends Logging with CometExprShim with CometTypeShim {
classOf[MakeTimestamp] -> CometMakeTimestamp,
classOf[MakeYMInterval] -> CometMakeYMInterval,
classOf[MakeDTInterval] -> CometMakeDTInterval,
classOf[MakeInterval] -> CometMakeInterval,

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.

TryMakeInterval is RuntimeReplaceable and its replacement is MakeInterval(..., failOnError = false), so try_make_interval reaches this handler after ReplaceExpressions. That means this PR enables it too.

Two follow-ons. The try_make_interval row in expressions.md (line 308) still says 🔜 with the #5061 note, so it needs the same update as the make_interval row. And there is a combination neither fixture covers: failOnError = false with spark.sql.ansi.enabled = true, where try_make_interval(2147483647) must return NULL instead of throwing. Would you add a small fixture for it? It needs -- MinSparkVersion: 4.0, since try_make_interval is not registered in 3.5.

classOf[MultiplyDTInterval] -> CometMultiplyDTInterval,
classOf[MicrosToTimestamp] -> CometMicrosToTimestamp,
classOf[MillisToTimestamp] -> CometMillisToTimestamp,
Expand Down Expand Up @@ -1111,9 +1112,16 @@ object QueryPlanSerde extends Logging with CometExprShim with CometTypeShim {
}

def scalarFunctionExprToProto(funcName: String, args: Option[Expr]*): Option[Expr] = {
scalarFunctionExprToProto(funcName, false, args: _*)
}

def scalarFunctionExprToProto(
funcName: String,
failOnError: Boolean,
args: Option[Expr]*): Option[Expr] = {
val builder = ExprOuterClass.ScalarFunc.newBuilder()
builder.setFunc(funcName)
builder.setFailOnError(false)
builder.setFailOnError(failOnError)
scalarFunctionExprToProto0(builder, args: _*)
}

Expand Down
31 changes: 29 additions & 2 deletions spark/src/main/scala/org/apache/comet/serde/datetime.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ 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, MultiplyDTInterval, NextDay, PreciseTimestampConversion, Quarter, Second, SecondsToTimestamp, 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, Expression, FromUTCTimestamp, GetDateField, GetTimestamp, Hour, Hours, LastDay, Literal, MakeDate, MakeDTInterval, MakeInterval, MakeTimestamp, MakeYMInterval, MicrosToTimestamp, MillisToTimestamp, Minute, Month, MonthsBetween, MultiplyDTInterval, NextDay, PreciseTimestampConversion, Quarter, Second, SecondsToTimestamp, 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.{DataType, DateType, DoubleType, FloatType, IntegerType, LongType, StringType, TimestampNTZType, TimestampType}
import org.apache.spark.sql.types.{CalendarIntervalType, DataType, DateType, DoubleType, FloatType, IntegerType, LongType, StringType, TimestampNTZType, TimestampType}
import org.apache.spark.unsafe.types.UTF8String

import org.apache.comet.CometConf
Expand Down Expand Up @@ -968,6 +968,33 @@ object CometMakeYMInterval extends CometCodegenDispatch[MakeYMInterval]

object CometMakeDTInterval extends CometCodegenDispatch[MakeDTInterval]

object CometMakeInterval extends CometExpressionSerde[MakeInterval] {

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.

One structural thought. Since getSupportLevel is unconditionally Incompatible, the default path here is a full fallback to Spark, which is the same as the behavior today without this PR. Users only benefit if they flip allowIncompatible=true and take on the #5131 divergences.

Would you be open to mixing in CodegenDispatchFallback?

object CometMakeInterval
    extends CometExpressionSerde[MakeInterval]
    with CodegenDispatchFallback {

That routes the non-opt-in Incompatible case through the JVM codegen dispatcher (QueryPlanSerde.scala:874), so the projection stays in the Comet pipeline with exact Spark semantics by default, and your native kernel becomes the fast opt-in. It is the same shape as CometConvertTimezone and CometFromUTCTimestamp above.

This would also let #5260 and this PR land together rather than one replacing the other. I opened #5260 as the codegen-dispatch route before seeing how far this one had come. If you would rather keep them separate I am happy to close #5260 and let you carry the dispatch mixin here.

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 for the direction! I'd like to take the mixin approach. feel free to close #5260.

private val incompatReason =
"The native implementation converts seconds to `Float64`, which can lose microsecond" +
" precision, and stores time in nanoseconds, which overflows for large seconds values" +
" that Spark can represent."

Comment on lines +972 to +976

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.

The reason attributes the nanosecond overflow to seconds, but hours and minutes hit it too, and at much lower values. Spark's IntervalUtils.makeInterval accumulates microseconds while the DataFusion kernel accumulates nanoseconds, so every time component has a 1000x smaller range.

make_interval(0, 0, 0, 0, 2562048) is enough to show it. Spark computes 2562048 * 3_600_000_000 = 9_223_372_800_000_000 micros and returns a valid interval. The kernel computes 2562048 * 3_600_000_000_000 = 9_223_372_800_000_000_000 nanos, which exceeds i64::MAX, so checked_mul fails and it returns NULL, or throws under ANSI. The cutoffs are hours >= 2,562,048 and mins >= 153,722,868.

Could the reason say "time components (hours, minutes, seconds)" rather than just seconds? This string is what renders on the generated compat page, so it is the only warning a user gets. It would be good to widen #5131's description the same way.

override def getIncompatibleReasons(): Seq[String] = Seq(incompatReason)

override def getSupportLevel(expr: MakeInterval): SupportLevel =
Incompatible(Some(incompatReason))

override def convert(
expr: MakeInterval,
inputs: Seq[Attribute],
binding: Boolean): Option[Expr] = {
// The explicit return type skips DataFusion's registry coercion, but its kernel needs Float64.
val children = expr.children.updated(6, Cast(expr.children(6), DoubleType))
val childExprs = children.map(exprToProtoInternal(_, inputs, binding))
val optExpr = scalarFunctionExprToProtoWithReturnType(
"make_interval",
CalendarIntervalType,
expr.failOnError,
childExprs: _*)
optExprWithFallbackReason(optExpr, expr, children: _*)
}
}

object CometMultiplyDTInterval extends CometCodegenDispatch[MultiplyDTInterval]

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-- 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.

-- Config: spark.comet.expression.MakeInterval.allowIncompatible=true

statement
CREATE TABLE test_make_interval(
years int,
months int,
weeks int,
days int,
hours int,
mins int,
secs decimal(18, 6)) USING parquet

statement
INSERT INTO test_make_interval VALUES
(1, 2, 3, 4, 5, 6, 7.123456),
(0, 1, 0, 1, 0, 0, 100.000001),
(-1, -2, -1, -1, -1, -1, -1.500000),
(NULL, 1, 2, 3, 4, 5, 6.000000),
(2, NULL, 2, 3, 4, 5, 6.000000),
(3, 1, 2, 3, 4, 5, NULL),
(-2147483648, 0, 0, 0, 0, 0, 0.000000)

query
SELECT make_interval(years, months, weeks, days, hours, mins, secs)
FROM test_make_interval
ORDER BY years

query
SELECT make_interval(1, 2), make_interval(3), make_interval()

query
SELECT make_interval(0, 1, 0, 1, 0, 0, 100.000001)

query
SELECT make_interval(2147483647)

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.

It might be worth expanding coverage here. A few things Spark's own IntervalExpressionsSuite / interval.sql exercise that aren't covered yet:

  • Microsecond-precision seconds like Spark's docstring example make_interval(0, 1, 0, 1, 0, 0, 100.000001) asserted directly (it's currently only exercised via the column path where it can be hard to spot a per-row precision drift).
  • Nulls in components other than years in the column path (currently only the years=NULL row is tested).
  • Large-second cases from Spark's interval.sql: make_interval(1, 2, 3, 4, 0, 0, 123456789012.123456) and make_interval(0, 0, 0, 0, 0, 0, 1234567890123456789). If either is a known divergence (see the nanos-overflow comment on the Rust file), wrapping them in query ignore(<tracking issue>) would at least pin the behavior for future readers.
  • Int.MinValue for a signed-overflow smoke test on the years column.


query ignore(https://github.com/apache/datafusion-comet/issues/5131)
SELECT make_interval(1, 2, 3, 4, 0, 0, 123456789012.123456)

query
SELECT make_interval(0, 0, 0, 0, 0, 0, 999999999.999999)

query ignore(https://github.com/apache/datafusion-comet/issues/5131)
SELECT make_interval(0, 0, 0, 0, 0, 0, 999999999.000001)

query
SELECT make_interval(0, 0, 0, 0, 0, 0, 1234567890123456789)

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.

Could you add an hours case alongside the seconds ones? It is the same #5131 nanosecond overflow but on a component the fixture does not touch, and at a value a real query is much more likely to produce than a 12-digit seconds decimal.

query ignore(https://github.com/apache/datafusion-comet/issues/5131)
SELECT make_interval(0, 0, 0, 0, 2562048)

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- 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.

-- Native ANSI execution must preserve Spark's overflow exception.
-- Config: spark.sql.ansi.enabled=true
-- Config: spark.comet.expression.MakeInterval.allowIncompatible=true

statement
CREATE TABLE test_make_interval_ansi(years int) USING parquet

statement
INSERT INTO test_make_interval_ansi VALUES (NULL)

query
SELECT make_interval(1, 2, 3, 4, 5, 6, 7.123456)

query
SELECT make_interval(years) FROM test_make_interval_ansi

query expect_error(overflow)
SELECT make_interval(2147483647)

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.

Consider adding ANSI overflow tests for components other than years — the current fixture only exercises years = Int.MaxValue. Spark's IntervalExpressionsSuite ANSI mode block covers weeks = Int.MaxValue, and per-row overflow via hours/mins/seconds interactions. Something like:

query expect_error(overflow)
SELECT make_interval(0, 0, 2147483647)

would confirm the overflow detection path fires on non-years components too.


query expect_error(overflow)
SELECT make_interval(0, 0, 2147483647)
Comment on lines +34 to +38

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.

Would you tighten these to expect_error(ARITHMETIC_OVERFLOW)? Both engines produce that class on every supported profile. Spark 3.5 and 4.1 both route through QueryExecutionErrors.arithmeticOverflowError, and Comet's SparkError::ArithmeticOverflow renders [ARITHMETIC_OVERFLOW] interval overflow. .... The bare overflow would also match an unrelated failure, and ARITHMETIC_OVERFLOW is the more common convention in the existing fixtures.

Note the message bodies still differ. Spark says integer overflow or long overflow depending on which Math.*Exact tripped, Comet always says interval overflow. That is unavoidable given the wrapper only sees the result null mask, but a short comment in make_interval.rs noting it would save the next reader the investigation.

Loading