From e220acc69361169d1c91bfa7d8087f1ae5806804 Mon Sep 17 00:00:00 2001 From: Gengliang Wang Date: Sat, 30 May 2026 10:46:36 +0000 Subject: [PATCH] [SPARK-57169][SQL] Simplify NextDay codegen under ANSI mode ### What changes were proposed in this pull request? Add `DateTimeExpressionUtils.getNextDateExact(int startDate, UTF8String dayOfWeek)` and route `NextDay`'s ANSI (`failOnError = true`) eval and codegen paths through it. In ANSI mode the previous codegen emitted `try { ... } catch (SparkIllegalArgumentException e) { throw e; }` -- a no-op catch that simply rethrew the same exception. The ANSI branch now emits a single `getNextDateExact(...)` call with no try/catch (the helper lets the `SparkIllegalArgumentException` from `getDayOfWeekFromString` propagate, which is exactly the ANSI behavior). The non-ANSI branch keeps the inline `try/catch -> isNull`. The `dayOfWeek.foldable` constant-folding fast path is unchanged. ### Why are the changes needed? Part of SPARK-56908 (umbrella). Dropping the dead try/catch wrapper (and the two chained `DateTimeUtils` calls) in the ANSI path shrinks the generated Java for `next_day`. ### Does this PR introduce _any_ user-facing change? No. The compiled behavior is identical; only the emitted Java source text changes. ### How was this patch tested? ``` build/sbt "catalyst/testOnly *DateExpressionsSuite" ``` 75/75 pass, including `next_day` (exercised both with and without whole-stage codegen). ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 4.8) Co-authored-by: Isaac --- .../expressions/DateTimeExpressionUtils.java | 14 ++++++ .../expressions/datetimeExpressions.scala | 46 ++++++++++--------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/DateTimeExpressionUtils.java b/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/DateTimeExpressionUtils.java index 0413278d0cb86..0e6b06bceba40 100644 --- a/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/DateTimeExpressionUtils.java +++ b/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/DateTimeExpressionUtils.java @@ -25,6 +25,7 @@ import org.apache.spark.sql.errors.QueryExecutionErrors; import org.apache.spark.sql.types.Decimal; import org.apache.spark.unsafe.types.CalendarInterval; +import org.apache.spark.unsafe.types.UTF8String; /** * Static helpers shared by date/time/interval expression {@code doGenCode} @@ -68,4 +69,17 @@ public static CalendarInterval makeIntervalExact( throw QueryExecutionErrors.arithmeticOverflowError(e.getMessage(), "", null); } } + + /** + * Computes the first date after {@code startDate} that falls on the weekday + * named by {@code dayOfWeek} for {@code NextDay} (next_day) in ANSI mode + * ({@code failOnError = true}). The {@code SparkIllegalArgumentException} + * thrown by {@code DateTimeUtils.getDayOfWeekFromString} for an invalid + * day-of-week string propagates to the caller unchanged (in ANSI mode the + * caller wants exactly that exception), so no try/catch wrapper is emitted. + */ + public static int getNextDateExact(int startDate, UTF8String dayOfWeek) { + int dow = DateTimeUtils.getDayOfWeekFromString(dayOfWeek); + return DateTimeUtils.getNextDateForDayOfWeek(startDate, dow); + } } diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala index a724f02cd107e..756cdc968d981 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala @@ -1588,17 +1588,16 @@ case class NextDay( override def nullable: Boolean = true override def nullSafeEval(start: Any, dayOfW: Any): Any = { - try { - val dow = DateTimeUtils.getDayOfWeekFromString(dayOfW.asInstanceOf[UTF8String]) - val sd = start.asInstanceOf[Int] - DateTimeUtils.getNextDateForDayOfWeek(sd, dow) - } catch { - case e: SparkIllegalArgumentException => - if (failOnError) { - throw e - } else { - null - } + if (failOnError) { + DateTimeExpressionUtils.getNextDateExact( + start.asInstanceOf[Int], dayOfW.asInstanceOf[UTF8String]) + } else { + try { + val dow = DateTimeUtils.getDayOfWeekFromString(dayOfW.asInstanceOf[UTF8String]) + DateTimeUtils.getNextDateForDayOfWeek(start.asInstanceOf[Int], dow) + } catch { + case _: SparkIllegalArgumentException => null + } } } @@ -1609,19 +1608,22 @@ case class NextDay( dayOfWeekTerm: String, sd: String, dowS: String): String = { - val failOnErrorBranch = if (failOnError) { - "throw e;" + if (failOnError) { + // In ANSI mode the only exception (SparkIllegalArgumentException for an + // invalid day-of-week) must propagate, so the previous catch merely + // rethrew it. Delegate to the helper and drop the no-op try/catch. + val utils = classOf[DateTimeExpressionUtils].getName + s"${ev.value} = $utils.getNextDateExact($sd, $dowS);" } else { - s"${ev.isNull} = true;" + s""" + |try { + | int $dayOfWeekTerm = $dateTimeUtilClass.getDayOfWeekFromString($dowS); + | ${ev.value} = $dateTimeUtilClass.getNextDateForDayOfWeek($sd, $dayOfWeekTerm); + |} catch (org.apache.spark.SparkIllegalArgumentException e) { + | ${ev.isNull} = true; + |} + |""".stripMargin } - s""" - |try { - | int $dayOfWeekTerm = $dateTimeUtilClass.getDayOfWeekFromString($dowS); - | ${ev.value} = $dateTimeUtilClass.getNextDateForDayOfWeek($sd, $dayOfWeekTerm); - |} catch (org.apache.spark.SparkIllegalArgumentException e) { - | $failOnErrorBranch - |} - |""".stripMargin } override protected def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {