Skip to content
Merged
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
23 changes: 14 additions & 9 deletions spark/src/main/scala/org/apache/comet/expressions/CometCast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ import org.apache.comet.shims.CometExprShim

object CometCast extends CometExpressionSerde[Cast] with CometExprShim {

// Shared with CometCastSuite so the asserted reason cannot drift from production.
private[comet] val negativeScaleDecimalToStringReason: String =
"Negative-scale decimal requires spark.sql.legacy.allowNegativeScaleOfDecimal=true"

def supportedTypes: Seq[DataType] =
Seq(
DataTypes.BooleanType,
Expand All @@ -48,17 +52,17 @@ object CometCast extends CometExpressionSerde[Cast] with CometExprShim {
DataTypes.TimestampType,
DataTypes.TimestampNTZType)

override def getIncompatibleReasons(): Seq[String] = Seq(
"Some cast operations between specific type pairs may produce different results than Spark." +
" Refer to the compatibility guide for the full matrix of supported cast operations.")

override def getUnsupportedReasons(): Seq[String] = Seq(
"Not all cast type combinations are supported. Unsupported casts fall back to Spark.")
// Note: `getIncompatibleReasons` / `getUnsupportedReasons` are intentionally not
// overridden here. The per-pair `isSupported` matrix is the canonical source of cast
// reasons: `cast.md` is generated directly from it (see `GenerateDocs.writeCastMatrixForMode`)
// and EXPLAIN surfaces the per-pair reason via `getSupportLevel`. A single static sentence
// would only duplicate the matrix and risk drifting from it.

override def getSupportLevel(cast: Cast): SupportLevel = {
if (cast.child.isInstanceOf[Literal]) {
// casting from literal is compatible because we delegate to Spark
// further data type checks will be performed by CometLiteral
// A cast whose child is a literal is folded by Spark at planning time via `cast.eval()`
// (see `convert`), so the cast never executes natively and the result matches Spark by
// definition. `CometLiteral` then validates the resulting literal's data type.
Compatible()
} else {
isSupported(cast.child.dataType, cast.dataType, cast.timeZoneId, evalMode(cast))
Expand Down Expand Up @@ -254,7 +258,8 @@ object CometCast extends CometExpressionSerde[Cast] with CometExprShim {
val allowNegativeScale = SQLConf.get
.getConfString("spark.sql.legacy.allowNegativeScaleOfDecimal", "false")
.toBoolean
if (allowNegativeScale) Compatible() else Incompatible()
if (allowNegativeScale) Compatible()
else Incompatible(Some(negativeScaleDecimalToStringReason))
case _: DecimalType =>
// Compatible across all eval modes: LEGACY uses cast_decimal128_to_utf8 which
// replicates Java BigDecimal.toString() (scientific notation when adj_exp < -6);
Expand Down
7 changes: 2 additions & 5 deletions spark/src/test/scala/org/apache/comet/CometCastSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -787,11 +787,8 @@ class CometCastSuite extends CometTestBase with AdaptiveSparkPlanHelper {
}
withSQLConf("spark.sql.legacy.allowNegativeScaleOfDecimal" -> "false") {
assert(
CometCast.isSupported(
negScaleType,
DataTypes.StringType,
None,
CometEvalMode.LEGACY) == Incompatible())
CometCast.isSupported(negScaleType, DataTypes.StringType, None, CometEvalMode.LEGACY) ==
Incompatible(Some(CometCast.negativeScaleDecimalToStringReason)))
}
}

Expand Down
Loading