From 72092183f809539cd6b7fe9d75cda81c7f31a940 Mon Sep 17 00:00:00 2001 From: Gengliang Wang Date: Sun, 17 May 2026 22:52:03 +0000 Subject: [PATCH 1/7] [SPARK-56909][SQL] Refactor Cast to int/long codegen under ANSI mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What changes were proposed in this pull request? Introduce `CastUtils.java` and use it from `Cast.scala` to collapse the multi-line ANSI overflow-check codegen for casts that target `int` and `long` into one-line static-method calls. Source and target `DataType` constants used in the overflow error message live as `private static final` fields on the helper class, so the happy path performs no per-row `references[]` lookups. Helpers added: * `longToIntExact(long)` for narrowing `long -> int`. * `floatToIntExact(float)`, `doubleToIntExact(double)` for fractional -> int. * `floatToLongExact(float)`, `doubleToLongExact(double)` for fractional -> long. `Cast.scala` changes: * `castIntegralTypeToIntegralTypeExactCode` and `castFractionToIntegralTypeCode` dispatch on the target type: `int` (and `long` for the fraction case) emit a `CastUtils.<...>Exact` call; byte/short targets keep the inline body (refactored in SPARK-56910). * Eval paths for `castToInt` add ANSI `LongType` / `FloatType` / `DoubleType` cases, and `castToLong` adds `FloatType` / `DoubleType` cases, both delegating to the new helpers. ### Why are the changes needed? Part of SPARK-56908. The current ANSI cast codegen emits 5-line inline overflow blocks per call site. Multiplied across the many cast paths in a TPC-DS plan, this contributes meaningfully to the generated source size and to Janino compile time, and pushes whole-stage methods closer to the 64KB JVM method limit. ### 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 *CastSuite *CastWithAnsiOnSuite *CastWithAnsiOffSuite *AnsiCastSuite *TryCastSuite *ExpressionClassIdentitySuite"` — 312/312 pass. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Cursor 1.x --- .../sql/catalyst/expressions/CastUtils.java | 71 +++++++++++++++ .../spark/sql/catalyst/expressions/Cast.scala | 89 +++++++++++++------ 2 files changed, 133 insertions(+), 27 deletions(-) create mode 100644 sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/CastUtils.java diff --git a/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/CastUtils.java b/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/CastUtils.java new file mode 100644 index 0000000000000..1f6a0daf616e3 --- /dev/null +++ b/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/CastUtils.java @@ -0,0 +1,71 @@ +/* + * 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. + */ + +package org.apache.spark.sql.catalyst.expressions; + +import org.apache.spark.sql.errors.QueryExecutionErrors; +import org.apache.spark.sql.types.DataType; +import org.apache.spark.sql.types.DataTypes; + +/** + * Static helpers used by {@code Cast.doGenCode} (and corresponding eval + * paths) for ANSI overflow-checked narrowing conversions. The source and + * target {@link DataType} objects referenced by the overflow error message + * are held in {@code private static final} fields so the happy path + * performs no per-row {@code references[]} lookups. + */ +public final class CastUtils { + + private CastUtils() {} + + private static final DataType INT = DataTypes.IntegerType; + private static final DataType LONG = DataTypes.LongType; + private static final DataType FLOAT = DataTypes.FloatType; + private static final DataType DOUBLE = DataTypes.DoubleType; + + // ----- integral narrowing -> int (ANSI: throw on overflow) ----- + + public static int longToIntExact(long v) { + if (v == (int) v) return (int) v; + throw QueryExecutionErrors.castingCauseOverflowError(v, LONG, INT); + } + + // ----- fractional -> int (ANSI: throw on overflow) ----- + // Mirrors castFractionToIntegralTypeCode: floor(v) <= MAX && ceil(v) >= MIN. + + public static int floatToIntExact(float v) { + if (Math.floor(v) <= Integer.MAX_VALUE && Math.ceil(v) >= Integer.MIN_VALUE) return (int) v; + throw QueryExecutionErrors.castingCauseOverflowError(v, FLOAT, INT); + } + + public static int doubleToIntExact(double v) { + if (Math.floor(v) <= Integer.MAX_VALUE && Math.ceil(v) >= Integer.MIN_VALUE) return (int) v; + throw QueryExecutionErrors.castingCauseOverflowError(v, DOUBLE, INT); + } + + // ----- fractional -> long (ANSI: throw on overflow) ----- + + public static long floatToLongExact(float v) { + if (Math.floor(v) <= Long.MAX_VALUE && Math.ceil(v) >= Long.MIN_VALUE) return (long) v; + throw QueryExecutionErrors.castingCauseOverflowError(v, FLOAT, LONG); + } + + public static long doubleToLongExact(double v) { + if (Math.floor(v) <= Long.MAX_VALUE && Math.ceil(v) >= Long.MIN_VALUE) return (long) v; + throw QueryExecutionErrors.castingCauseOverflowError(v, DOUBLE, LONG); + } +} diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala index c51d3508d04a4..170d4953d830d 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala @@ -897,6 +897,10 @@ case class Cast( buildCast[Long](_, t => timestampToLong(t)) case _: TimeType => buildCast[Long](_, t => timeToLong(t)) + case FloatType if ansiEnabled => + b => CastUtils.floatToLongExact(b.asInstanceOf[Float]) + case DoubleType if ansiEnabled => + b => CastUtils.doubleToLongExact(b.asInstanceOf[Double]) case x: NumericType if ansiEnabled => val exactNumeric = PhysicalNumericType.exactNumeric(x) b => exactNumeric.toLong(b) @@ -939,6 +943,12 @@ case class Cast( }) case _: TimeType => buildCast[Long](_, t => timeToLong(t).toInt) + case LongType if ansiEnabled => + b => CastUtils.longToIntExact(b.asInstanceOf[Long]) + case FloatType if ansiEnabled => + b => CastUtils.floatToIntExact(b.asInstanceOf[Float]) + case DoubleType if ansiEnabled => + b => CastUtils.doubleToIntExact(b.asInstanceOf[Double]) case x: NumericType if ansiEnabled => val exactNumeric = PhysicalNumericType.exactNumeric(x) b => exactNumeric.toInt(b) @@ -1982,22 +1992,40 @@ case class Cast( } } + private[this] def integralPrefix(from: DataType): String = from match { + case ShortType => "short" + case IntegerType => "int" + case LongType => "long" + } + + private[this] def fractionalPrefix(from: DataType): String = from match { + case FloatType => "float" + case DoubleType => "double" + } + private[this] def castIntegralTypeToIntegralTypeExactCode( ctx: CodegenContext, integralType: String, from: DataType, to: DataType): CastFunction = { assert(ansiEnabled) - val fromDt = ctx.addReferenceObj("from", from, from.getClass.getName) - val toDt = ctx.addReferenceObj("to", to, to.getClass.getName) - (c, evPrim, _) => - code""" - if ($c == ($integralType) $c) { - $evPrim = ($integralType) $c; - } else { - throw QueryExecutionErrors.castingCauseOverflowError($c, $fromDt, $toDt); - } - """ + if (integralType == "int") { + val castUtils = classOf[CastUtils].getName + val method = s"${integralPrefix(from)}ToIntExact" + (c, evPrim, _) => code"$evPrim = $castUtils.$method($c);" + } else { + // Byte/short narrowing remains inline; refactored in a follow-up PR. + val fromDt = ctx.addReferenceObj("from", from, from.getClass.getName) + val toDt = ctx.addReferenceObj("to", to, to.getClass.getName) + (c, evPrim, _) => + code""" + if ($c == ($integralType) $c) { + $evPrim = ($integralType) $c; + } else { + throw QueryExecutionErrors.castingCauseOverflowError($c, $fromDt, $toDt); + } + """ + } } @@ -2017,23 +2045,30 @@ case class Cast( from: DataType, to: DataType): CastFunction = { assert(ansiEnabled) - val (min, max) = lowerAndUpperBound(integralType) - val mathClass = classOf[Math].getName - val fromDt = ctx.addReferenceObj("from", from, from.getClass.getName) - val toDt = ctx.addReferenceObj("to", to, to.getClass.getName) - // When casting floating values to integral types, Spark uses the method `Numeric.toInt` - // Or `Numeric.toLong` directly. For positive floating values, it is equivalent to `Math.floor`; - // for negative floating values, it is equivalent to `Math.ceil`. - // So, we can use the condition `Math.floor(x) <= upperBound && Math.ceil(x) >= lowerBound` - // to check if the floating value x is in the range of an integral type after rounding. - (c, evPrim, _) => - code""" - if ($mathClass.floor($c) <= $max && $mathClass.ceil($c) >= $min) { - $evPrim = ($integralType) $c; - } else { - throw QueryExecutionErrors.castingCauseOverflowError($c, $fromDt, $toDt); - } - """ + if (integralType == "int" || integralType == "long") { + val castUtils = classOf[CastUtils].getName + val method = s"${fractionalPrefix(from)}To${integralType.capitalize}Exact" + (c, evPrim, _) => code"$evPrim = $castUtils.$method($c);" + } else { + // Byte/short narrowing remains inline; refactored in a follow-up PR. + val (min, max) = lowerAndUpperBound(integralType) + val mathClass = classOf[Math].getName + val fromDt = ctx.addReferenceObj("from", from, from.getClass.getName) + val toDt = ctx.addReferenceObj("to", to, to.getClass.getName) + // When casting floating values to integral types, Spark uses the method `Numeric.toInt` + // Or `Numeric.toLong` directly. For positive floating values, it is equivalent to + // `Math.floor`; for negative floating values, it is equivalent to `Math.ceil`. + // So, we can use the condition `Math.floor(x) <= upperBound && Math.ceil(x) >= lowerBound` + // to check if the floating value x is in the range of an integral type after rounding. + (c, evPrim, _) => + code""" + if ($mathClass.floor($c) <= $max && $mathClass.ceil($c) >= $min) { + $evPrim = ($integralType) $c; + } else { + throw QueryExecutionErrors.castingCauseOverflowError($c, $fromDt, $toDt); + } + """ + } } private[this] def castToByteCode(from: DataType, ctx: CodegenContext): CastFunction = from match { From 7029605d691c82bbed0001f8b033840466aafd58 Mon Sep 17 00:00:00 2001 From: Gengliang Wang Date: Thu, 21 May 2026 18:27:48 +0000 Subject: [PATCH 2/7] address review lesson from PR 55938: call LongExactNumeric/FloatExactNumeric/DoubleExactNumeric directly, remove CastUtils.java --- .../sql/catalyst/expressions/CastUtils.java | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/CastUtils.java diff --git a/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/CastUtils.java b/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/CastUtils.java deleted file mode 100644 index 1f6a0daf616e3..0000000000000 --- a/sql/catalyst/src/main/java/org/apache/spark/sql/catalyst/expressions/CastUtils.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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. - */ - -package org.apache.spark.sql.catalyst.expressions; - -import org.apache.spark.sql.errors.QueryExecutionErrors; -import org.apache.spark.sql.types.DataType; -import org.apache.spark.sql.types.DataTypes; - -/** - * Static helpers used by {@code Cast.doGenCode} (and corresponding eval - * paths) for ANSI overflow-checked narrowing conversions. The source and - * target {@link DataType} objects referenced by the overflow error message - * are held in {@code private static final} fields so the happy path - * performs no per-row {@code references[]} lookups. - */ -public final class CastUtils { - - private CastUtils() {} - - private static final DataType INT = DataTypes.IntegerType; - private static final DataType LONG = DataTypes.LongType; - private static final DataType FLOAT = DataTypes.FloatType; - private static final DataType DOUBLE = DataTypes.DoubleType; - - // ----- integral narrowing -> int (ANSI: throw on overflow) ----- - - public static int longToIntExact(long v) { - if (v == (int) v) return (int) v; - throw QueryExecutionErrors.castingCauseOverflowError(v, LONG, INT); - } - - // ----- fractional -> int (ANSI: throw on overflow) ----- - // Mirrors castFractionToIntegralTypeCode: floor(v) <= MAX && ceil(v) >= MIN. - - public static int floatToIntExact(float v) { - if (Math.floor(v) <= Integer.MAX_VALUE && Math.ceil(v) >= Integer.MIN_VALUE) return (int) v; - throw QueryExecutionErrors.castingCauseOverflowError(v, FLOAT, INT); - } - - public static int doubleToIntExact(double v) { - if (Math.floor(v) <= Integer.MAX_VALUE && Math.ceil(v) >= Integer.MIN_VALUE) return (int) v; - throw QueryExecutionErrors.castingCauseOverflowError(v, DOUBLE, INT); - } - - // ----- fractional -> long (ANSI: throw on overflow) ----- - - public static long floatToLongExact(float v) { - if (Math.floor(v) <= Long.MAX_VALUE && Math.ceil(v) >= Long.MIN_VALUE) return (long) v; - throw QueryExecutionErrors.castingCauseOverflowError(v, FLOAT, LONG); - } - - public static long doubleToLongExact(double v) { - if (Math.floor(v) <= Long.MAX_VALUE && Math.ceil(v) >= Long.MIN_VALUE) return (long) v; - throw QueryExecutionErrors.castingCauseOverflowError(v, DOUBLE, LONG); - } -} From d5def9c21c92fe6f7560d0c76f0086ae36859aba Mon Sep 17 00:00:00 2001 From: Gengliang Wang Date: Thu, 21 May 2026 18:27:58 +0000 Subject: [PATCH 3/7] remove CastUtils refs and call LongExactNumeric/FloatExactNumeric/DoubleExactNumeric in codegen --- .../spark/sql/catalyst/expressions/Cast.scala | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala index 170d4953d830d..a8df3bd0c08a1 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala @@ -897,10 +897,6 @@ case class Cast( buildCast[Long](_, t => timestampToLong(t)) case _: TimeType => buildCast[Long](_, t => timeToLong(t)) - case FloatType if ansiEnabled => - b => CastUtils.floatToLongExact(b.asInstanceOf[Float]) - case DoubleType if ansiEnabled => - b => CastUtils.doubleToLongExact(b.asInstanceOf[Double]) case x: NumericType if ansiEnabled => val exactNumeric = PhysicalNumericType.exactNumeric(x) b => exactNumeric.toLong(b) @@ -943,12 +939,6 @@ case class Cast( }) case _: TimeType => buildCast[Long](_, t => timeToLong(t).toInt) - case LongType if ansiEnabled => - b => CastUtils.longToIntExact(b.asInstanceOf[Long]) - case FloatType if ansiEnabled => - b => CastUtils.floatToIntExact(b.asInstanceOf[Float]) - case DoubleType if ansiEnabled => - b => CastUtils.doubleToIntExact(b.asInstanceOf[Double]) case x: NumericType if ansiEnabled => val exactNumeric = PhysicalNumericType.exactNumeric(x) b => exactNumeric.toInt(b) @@ -1992,17 +1982,6 @@ case class Cast( } } - private[this] def integralPrefix(from: DataType): String = from match { - case ShortType => "short" - case IntegerType => "int" - case LongType => "long" - } - - private[this] def fractionalPrefix(from: DataType): String = from match { - case FloatType => "float" - case DoubleType => "double" - } - private[this] def castIntegralTypeToIntegralTypeExactCode( ctx: CodegenContext, integralType: String, @@ -2010,9 +1989,10 @@ case class Cast( to: DataType): CastFunction = { assert(ansiEnabled) if (integralType == "int") { - val castUtils = classOf[CastUtils].getName - val method = s"${integralPrefix(from)}ToIntExact" - (c, evPrim, _) => code"$evPrim = $castUtils.$method($c);" + // Long -> Int: call LongExactNumeric.toInt directly. It already does the + // bounds check and throws castingCauseOverflowError -- same as the inline body. + val numericObj = LongExactNumeric.getClass.getCanonicalName.stripSuffix("$") + (c, evPrim, _) => code"$evPrim = $numericObj.toInt($c);" } else { // Byte/short narrowing remains inline; refactored in a follow-up PR. val fromDt = ctx.addReferenceObj("from", from, from.getClass.getName) @@ -2046,9 +2026,15 @@ case class Cast( to: DataType): CastFunction = { assert(ansiEnabled) if (integralType == "int" || integralType == "long") { - val castUtils = classOf[CastUtils].getName - val method = s"${fractionalPrefix(from)}To${integralType.capitalize}Exact" - (c, evPrim, _) => code"$evPrim = $castUtils.$method($c);" + // Float/Double -> Int/Long: call FloatExactNumeric/DoubleExactNumeric.toInt/toLong + // directly. Each already does the floor/ceil bounds check and throws + // castingCauseOverflowError -- same as the inline body. + val numericObj = (from match { + case FloatType => FloatExactNumeric + case DoubleType => DoubleExactNumeric + }).getClass.getCanonicalName.stripSuffix("$") + val method = s"to${integralType.capitalize}" + (c, evPrim, _) => code"$evPrim = $numericObj.$method($c);" } else { // Byte/short narrowing remains inline; refactored in a follow-up PR. val (min, max) = lowerAndUpperBound(integralType) From ff9b4cb3c7011a3a1012ac09ab68d56f993b4577 Mon Sep 17 00:00:00 2001 From: Gengliang Wang Date: Fri, 22 May 2026 06:00:33 -0700 Subject: [PATCH 4/7] Apply suggestion from @gengliangwang --- .../scala/org/apache/spark/sql/catalyst/expressions/Cast.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala index a8df3bd0c08a1..23260c4fe15b2 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala @@ -2036,7 +2036,6 @@ case class Cast( val method = s"to${integralType.capitalize}" (c, evPrim, _) => code"$evPrim = $numericObj.$method($c);" } else { - // Byte/short narrowing remains inline; refactored in a follow-up PR. val (min, max) = lowerAndUpperBound(integralType) val mathClass = classOf[Math].getName val fromDt = ctx.addReferenceObj("from", from, from.getClass.getName) From 6945cc44faf674e5dd399fba4ad41d7fbd0669f4 Mon Sep 17 00:00:00 2001 From: Gengliang Wang Date: Fri, 22 May 2026 09:46:17 -0700 Subject: [PATCH 5/7] Update sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala Co-authored-by: Wenchen Fan --- .../scala/org/apache/spark/sql/catalyst/expressions/Cast.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala index 23260c4fe15b2..8f730a83a4f4d 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala @@ -1994,7 +1994,7 @@ case class Cast( val numericObj = LongExactNumeric.getClass.getCanonicalName.stripSuffix("$") (c, evPrim, _) => code"$evPrim = $numericObj.toInt($c);" } else { - // Byte/short narrowing remains inline; refactored in a follow-up PR. + } else { val fromDt = ctx.addReferenceObj("from", from, from.getClass.getName) val toDt = ctx.addReferenceObj("to", to, to.getClass.getName) (c, evPrim, _) => From a7ba4e62e4a4cfa94115d10bbe89006be1cb7f79 Mon Sep 17 00:00:00 2001 From: Gengliang Wang Date: Fri, 22 May 2026 16:55:37 +0000 Subject: [PATCH 6/7] address viirya readability nits: explicit default arms in from match --- .../spark/sql/catalyst/expressions/Cast.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala index 8f730a83a4f4d..dde784e2838a1 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala @@ -21,7 +21,7 @@ import java.time.{ZoneId, ZoneOffset} import java.util.Locale import java.util.concurrent.TimeUnit._ -import org.apache.spark.{QueryContext, SparkArithmeticException, SparkIllegalArgumentException} +import org.apache.spark.{QueryContext, SparkArithmeticException, SparkException, SparkIllegalArgumentException} import org.apache.spark.sql.catalyst.InternalRow import org.apache.spark.sql.catalyst.analysis.TypeCheckResult import org.apache.spark.sql.catalyst.analysis.TypeCheckResult.DataTypeMismatch @@ -1989,9 +1989,14 @@ case class Cast( to: DataType): CastFunction = { assert(ansiEnabled) if (integralType == "int") { - // Long -> Int: call LongExactNumeric.toInt directly. It already does the + // Integral -> Int: call the existing *ExactNumeric.toInt directly. It already does the // bounds check and throws castingCauseOverflowError -- same as the inline body. - val numericObj = LongExactNumeric.getClass.getCanonicalName.stripSuffix("$") + // Only LongType reaches this branch today (`castToIntCode` gates on `case LongType`). + val numericObj = (from match { + case LongType => LongExactNumeric + case _ => throw SparkException.internalError( + s"Unexpected source type $from for castIntegralTypeToIntegralTypeExactCode int branch") + }).getClass.getCanonicalName.stripSuffix("$") (c, evPrim, _) => code"$evPrim = $numericObj.toInt($c);" } else { } else { @@ -2032,6 +2037,8 @@ case class Cast( val numericObj = (from match { case FloatType => FloatExactNumeric case DoubleType => DoubleExactNumeric + case _ => throw SparkException.internalError( + s"Unexpected source type $from for castFractionToIntegralTypeCode") }).getClass.getCanonicalName.stripSuffix("$") val method = s"to${integralType.capitalize}" (c, evPrim, _) => code"$evPrim = $numericObj.$method($c);" From c966bd9c05721140f0477497e6c6b22d9b3729ef Mon Sep 17 00:00:00 2001 From: Gengliang Wang Date: Fri, 22 May 2026 17:00:11 +0000 Subject: [PATCH 7/7] fix duplicate `} else {` introduced by the github-ui suggestion --- .../scala/org/apache/spark/sql/catalyst/expressions/Cast.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala index dde784e2838a1..419ca3f32d888 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala @@ -1998,7 +1998,6 @@ case class Cast( s"Unexpected source type $from for castIntegralTypeToIntegralTypeExactCode int branch") }).getClass.getCanonicalName.stripSuffix("$") (c, evPrim, _) => code"$evPrim = $numericObj.toInt($c);" - } else { } else { val fromDt = ctx.addReferenceObj("from", from, from.getClass.getName) val toDt = ctx.addReferenceObj("to", to, to.getClass.getName)