diff --git a/backends-velox/src/test/scala/org/apache/gluten/execution/VeloxInsertSuite.scala b/backends-velox/src/test/scala/org/apache/gluten/execution/VeloxInsertSuite.scala new file mode 100644 index 00000000000..f6e3a07888a --- /dev/null +++ b/backends-velox/src/test/scala/org/apache/gluten/execution/VeloxInsertSuite.scala @@ -0,0 +1,133 @@ +/* + * 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.gluten.execution + +import org.apache.spark.SparkConf +import org.apache.spark.sql.{AnalysisException, Row} +import org.apache.spark.sql.internal.SQLConf + +class VeloxInsertSuite extends VeloxWholeStageTransformerSuite { + override protected val resourcePath: String = "placeholder" + override protected val fileFormat: String = "parquet" + + override protected def sparkConf: SparkConf = { + super.sparkConf + .set("spark.shuffle.manager", "org.apache.spark.shuffle.sort.ColumnarShuffleManager") + .set("spark.sql.shuffle.partitions", "1") + .set("spark.memory.offHeap.size", "2g") + .set("spark.unsafe.exceptionOnMemoryLeak", "true") + } + + test("storeAssignmentPolicy default ANSI is independent from ANSI mode") { + withTable("store_assignment_ansi_src", "store_assignment_ansi") { + withSQLConf(SQLConf.ANSI_ENABLED.key -> "false") { + assert(SQLConf.get.storeAssignmentPolicy == SQLConf.StoreAssignmentPolicy.ANSI) + + createTableWithValue("store_assignment_ansi_src", "STRING", "'2147483648'") + createTable("store_assignment_ansi", "INT") + assertUnsafeCastAnalysisException("STRING", "INT") { + insertIntoFrom("store_assignment_ansi", "store_assignment_ansi_src").collect() + } + + withSQLConf( + SQLConf.STORE_ASSIGNMENT_POLICY.key -> SQLConf.StoreAssignmentPolicy.LEGACY.toString) { + val insert = insertIntoFrom("store_assignment_ansi", "store_assignment_ansi_src") + insert.collect() + checkGlutenPlan[ProjectExecTransformer](insert) + checkAnswer(spark.table("store_assignment_ansi"), Row(null)) + } + } + } + } + + test("storeAssignmentPolicy preserves configured cast modes") { + withSQLConf(SQLConf.ANSI_ENABLED.key -> "false") { + withTable("store_assignment_ansi_src", "store_assignment_ansi") { + createTableWithValue("store_assignment_ansi_src", "STRING", "'2147483648'") + createTable("store_assignment_ansi", "INT") + + withSQLConf( + SQLConf.STORE_ASSIGNMENT_POLICY.key -> SQLConf.StoreAssignmentPolicy.ANSI.toString) { + assertUnsafeCastAnalysisException("STRING", "INT") { + insertIntoFrom("store_assignment_ansi", "store_assignment_ansi_src").collect() + } + checkAnswer(spark.table("store_assignment_ansi"), Seq.empty[Row]) + } + } + } + + withSQLConf(SQLConf.ANSI_ENABLED.key -> "true") { + withTable("store_assignment_legacy_src", "store_assignment_legacy") { + createTableWithValue("store_assignment_legacy_src", "STRING", "'2147483648'") + createTable("store_assignment_legacy", "INT") + + withSQLConf( + SQLConf.STORE_ASSIGNMENT_POLICY.key -> SQLConf.StoreAssignmentPolicy.LEGACY.toString) { + val insert = insertIntoFrom("store_assignment_legacy", "store_assignment_legacy_src") + insert.collect() + checkGlutenPlan[ProjectExecTransformer](insert) + checkAnswer(spark.table("store_assignment_legacy"), Row(null)) + } + } + } + } + + test("storeAssignmentPolicy strict rejects unsafe insert casts") { + withTable("store_assignment_strict_src", "store_assignment_strict") { + withSQLConf( + SQLConf.STORE_ASSIGNMENT_POLICY.key -> SQLConf.StoreAssignmentPolicy.STRICT.toString) { + createTableWithValue("store_assignment_strict_src", "INT", "1") + createTable("store_assignment_strict", "TINYINT") + + assertUnsafeCastAnalysisException("INT", "TINYINT") { + insertIntoFrom("store_assignment_strict", "store_assignment_strict_src").collect() + } + checkAnswer(spark.table("store_assignment_strict"), Seq.empty[Row]) + } + } + } + + private def createTable(table: String, dataType: String): Unit = + spark.sql(s"CREATE TABLE $table (c $dataType) USING PARQUET") + + private def createTableWithValue(table: String, dataType: String, value: String): Unit = { + createTable(table, dataType) + spark.sql(s"INSERT INTO $table VALUES ($value)").collect() + } + + private def insertIntoFrom(target: String, source: String) = + spark.sql(s"INSERT INTO $target SELECT c FROM $source") + + private def assertUnsafeCastAnalysisException( + fromType: String, + toType: String)(f: => Unit): Unit = { + val exception = intercept[AnalysisException](f) + val message = exceptionMessages(exception) + assert(message.contains(fromType), message) + assert(message.contains(toType), message) + assert(message.contains("cast") || message.contains("Cast"), message) + } + + private def exceptionMessages(e: Throwable): String = { + val message = Option(e.getMessage).getOrElse("") + if (e.getCause == null) { + message + } else { + message + "\n" + exceptionMessages(e.getCause) + } + } +} diff --git a/cpp/velox/CMakeLists.txt b/cpp/velox/CMakeLists.txt index 5034c1601ab..2f7d3fa07b5 100644 --- a/cpp/velox/CMakeLists.txt +++ b/cpp/velox/CMakeLists.txt @@ -170,6 +170,7 @@ set(VELOX_SRCS memory/VeloxMemoryManager.cc operators/functions/RegistrationAllFunctions.cc operators/functions/RowConstructorWithNull.cc + operators/functions/SparkCastModeSpecialForms.cc operators/functions/SparkExprToSubfieldFilterParser.cc operators/plannodes/RowVectorStream.cc operators/hashjoin/HashTableBuilder.cc diff --git a/cpp/velox/operators/functions/RegistrationAllFunctions.cc b/cpp/velox/operators/functions/RegistrationAllFunctions.cc index dd1be7805c7..cd78778b24e 100644 --- a/cpp/velox/operators/functions/RegistrationAllFunctions.cc +++ b/cpp/velox/operators/functions/RegistrationAllFunctions.cc @@ -19,6 +19,7 @@ #include "operators/functions/Arithmetic.h" #include "operators/functions/RowConstructorWithNull.h" #include "operators/functions/RowFunctionWithNull.h" +#include "operators/functions/SparkCastModeSpecialForms.h" #include "velox/expression/SpecialFormRegistry.h" #include "velox/expression/VectorFunction.h" #include "velox/functions/iceberg/Register.h" @@ -83,6 +84,7 @@ void registerFunctionOverwrite() { void registerAllFunctions() { velox::functions::sparksql::registerFunctions(""); + registerSparkCastModeSpecialForms(); velox::aggregate::prestosql::registerAllAggregateFunctions( "", true /*registerCompanionFunctions*/, false /*onlyPrestoSignatures*/, true /*overwrite*/); velox::functions::aggregate::sparksql::registerAggregateFunctions( diff --git a/cpp/velox/operators/functions/SparkCastModeSpecialForms.cc b/cpp/velox/operators/functions/SparkCastModeSpecialForms.cc new file mode 100644 index 00000000000..9d0844b7199 --- /dev/null +++ b/cpp/velox/operators/functions/SparkCastModeSpecialForms.cc @@ -0,0 +1,118 @@ +/* + * 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. + */ + +#include "operators/functions/SparkCastModeSpecialForms.h" + +#include "velox/expression/SpecialFormRegistry.h" +#include "velox/functions/sparksql/specialforms/SparkCastExpr.h" +#include "velox/functions/sparksql/specialforms/SparkCastHooks.h" + +namespace gluten { +namespace { + +using namespace facebook::velox; +using facebook::velox::functions::sparksql::SparkCastExpr; +using facebook::velox::functions::sparksql::SparkCastHooks; + +bool isIntegralType(const TypePtr& type) { + return type == TINYINT() || type == SMALLINT() || type == INTEGER() || + type == BIGINT(); +} + +// Keep this in sync with Velox's SparkCastCallToSpecialForm::isAnsiSupported. +// Velox's helper is private today; this local copy is needed for expression-level +// ANSI and legacy cast modes. +bool isAnsiSupported(const TypePtr& fromType, const TypePtr& toType) { + if (fromType->isVarchar()) { + return toType->isBoolean() || toType->isDate() || isIntegralType(toType); + } + return false; +} + +exec::ExprPtr makeSparkCastExpr( + const TypePtr& type, + exec::ExprPtr&& input, + bool trackCpuUsage, + bool isTryCast, + bool allowOverflow, + const core::QueryConfig& config) { + return std::make_shared( + type, + std::move(input), + trackCpuUsage, + isTryCast, + std::make_shared(config, allowOverflow)); +} + +class SparkAnsiCastCallToSpecialForm : public exec::CastCallToSpecialForm { + public: + exec::ExprPtr constructSpecialForm( + const TypePtr& type, + std::vector&& compiledChildren, + bool trackCpuUsage, + const core::QueryConfig& config) override { + VELOX_CHECK_EQ( + compiledChildren.size(), + 1, + "ANSI CAST statements expect exactly 1 argument, received {}.", + compiledChildren.size()); + + const auto& fromType = compiledChildren[0]->type(); + const bool isTryCast = !isAnsiSupported(fromType, type); + return makeSparkCastExpr( + type, + std::move(compiledChildren[0]), + trackCpuUsage, + isTryCast, + isTryCast, + config); + } +}; + +class SparkLegacyCastCallToSpecialForm : public exec::CastCallToSpecialForm { + public: + exec::ExprPtr constructSpecialForm( + const TypePtr& type, + std::vector&& compiledChildren, + bool trackCpuUsage, + const core::QueryConfig& config) override { + VELOX_CHECK_EQ( + compiledChildren.size(), + 1, + "LEGACY CAST statements expect exactly 1 argument, received {}.", + compiledChildren.size()); + + return makeSparkCastExpr( + type, + std::move(compiledChildren[0]), + trackCpuUsage, + true, + true, + config); + } +}; + +} // namespace + +void registerSparkCastModeSpecialForms() { + exec::registerFunctionCallToSpecialForm( + kSparkAnsiCast, std::make_unique()); + exec::registerFunctionCallToSpecialForm( + kSparkLegacyCast, std::make_unique()); +} + +} // namespace gluten diff --git a/cpp/velox/operators/functions/SparkCastModeSpecialForms.h b/cpp/velox/operators/functions/SparkCastModeSpecialForms.h new file mode 100644 index 00000000000..75f0be6d77d --- /dev/null +++ b/cpp/velox/operators/functions/SparkCastModeSpecialForms.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#pragma once + +namespace gluten { + +constexpr const char* kSparkAnsiCast = "spark_ansi_cast"; +constexpr const char* kSparkLegacyCast = "spark_legacy_cast"; + +void registerSparkCastModeSpecialForms(); + +} // namespace gluten diff --git a/cpp/velox/substrait/SubstraitToVeloxExpr.cc b/cpp/velox/substrait/SubstraitToVeloxExpr.cc index 467df25ca88..652a317f459 100755 --- a/cpp/velox/substrait/SubstraitToVeloxExpr.cc +++ b/cpp/velox/substrait/SubstraitToVeloxExpr.cc @@ -17,6 +17,7 @@ #include "SubstraitToVeloxExpr.h" #include "TypeUtils.h" +#include "operators/functions/SparkCastModeSpecialForms.h" #include "velox/vector/FlatVector.h" #include "velox/vector/VariantToVector.h" @@ -146,16 +147,24 @@ TypePtr getScalarType(const ::substrait::Expression::Literal& literal) { } } -/// Whether is try cast. -bool isTryCast(::substrait::Expression::Cast::FailureBehavior failureBehavior) { +enum class SparkCastMode { + kLegacy, + kAnsi, + kTry, +}; + +SparkCastMode sparkCastMode( + ::substrait::Expression::Cast::FailureBehavior failureBehavior) { switch (failureBehavior) { case ::substrait::Expression_Cast_FailureBehavior_FAILURE_BEHAVIOR_UNSPECIFIED: + return SparkCastMode::kLegacy; case ::substrait::Expression_Cast_FailureBehavior_FAILURE_BEHAVIOR_THROW_EXCEPTION: - return false; + return SparkCastMode::kAnsi; case ::substrait::Expression_Cast_FailureBehavior_FAILURE_BEHAVIOR_RETURN_NULL: - return true; + return SparkCastMode::kTry; default: - VELOX_NYI("The given failure behavior is NOT supported: '{}'", std::to_string(failureBehavior)); + VELOX_NYI( + "The given failure behavior is NOT supported: '{}'", std::to_string(failureBehavior)); } } @@ -564,7 +573,18 @@ core::TypedExprPtr SubstraitVeloxExprConverter::toVeloxExpr( const RowTypePtr& inputType) { auto type = SubstraitParser::parseType(castExpr.type()); std::vector inputs{toVeloxExpr(castExpr.input(), inputType)}; - return std::make_shared(type, inputs, isTryCast(castExpr.failure_behavior())); + switch (sparkCastMode(castExpr.failure_behavior())) { + case SparkCastMode::kLegacy: + return std::make_shared( + type, std::move(inputs), kSparkLegacyCast); + case SparkCastMode::kAnsi: + return std::make_shared( + type, std::move(inputs), kSparkAnsiCast); + case SparkCastMode::kTry: + return std::make_shared(type, std::move(inputs), true); + default: + VELOX_UNREACHABLE(); + } } core::TypedExprPtr SubstraitVeloxExprConverter::toVeloxExpr( diff --git a/cpp/velox/tests/SparkFunctionTest.cc b/cpp/velox/tests/SparkFunctionTest.cc index ae5e7c48d8a..5454a2ba63d 100644 --- a/cpp/velox/tests/SparkFunctionTest.cc +++ b/cpp/velox/tests/SparkFunctionTest.cc @@ -18,6 +18,10 @@ #include #include "operators/functions/RegistrationAllFunctions.h" +#include "operators/functions/SparkCastModeSpecialForms.h" +#include "velox/common/base/tests/GTestUtils.h" +#include "velox/core/Expressions.h" +#include "velox/core/QueryConfig.h" #include "velox/functions/sparksql/tests/SparkFunctionBaseTest.h" using namespace facebook::velox::functions::sparksql::test; @@ -111,3 +115,32 @@ TEST_F(SparkFunctionTest, roundWithDecimal) { runRoundWithDecimalTest(testRoundWithDecIntegralData()); runRoundWithDecimalTest(testRoundWithDecIntegralData()); } + +TEST_F(SparkFunctionTest, expressionLevelAnsiCastIgnoresSessionAnsiOff) { + queryCtx_->testingOverrideConfigUnsafe( + {{core::QueryConfig::kSparkAnsiEnabled, "false"}}); + auto input = makeRowVector({makeFlatVector({"2147483648"})}); + core::TypedExprPtr field = + std::make_shared(VARCHAR(), "c0"); + auto ansiCast = std::make_shared( + INTEGER(), + std::vector{field}, + gluten::kSparkAnsiCast); + + VELOX_ASSERT_THROW(evaluate(ansiCast, input), "Cannot cast"); +} + +TEST_F(SparkFunctionTest, expressionLevelLegacyCastIgnoresSessionAnsiOn) { + queryCtx_->testingOverrideConfigUnsafe( + {{core::QueryConfig::kSparkAnsiEnabled, "true"}}); + auto input = makeRowVector({makeFlatVector({1234567})}); + core::TypedExprPtr field = + std::make_shared(INTEGER(), "c0"); + auto legacyCast = std::make_shared( + TINYINT(), + std::vector{field}, + gluten::kSparkLegacyCast); + + facebook::velox::test::assertEqualVectors( + makeFlatVector({-121}), evaluate(legacyCast, input)); +} diff --git a/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/CastNode.java b/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/CastNode.java index 1984c44d740..6a501c0b8c2 100644 --- a/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/CastNode.java +++ b/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/CastNode.java @@ -23,15 +23,25 @@ import java.io.Serializable; public class CastNode implements ExpressionNode, Serializable { + public enum CastMode { + LEGACY, + ANSI, + TRY + } + private final TypeNode typeNode; private final ExpressionNode expressionNode; - public final boolean isTryCast; + public final CastMode castMode; CastNode(TypeNode typeNode, ExpressionNode expressionNode, boolean isTryCast) { + this(typeNode, expressionNode, isTryCast ? CastMode.TRY : CastMode.ANSI); + } + + CastNode(TypeNode typeNode, ExpressionNode expressionNode, CastMode castMode) { this.typeNode = typeNode; this.expressionNode = expressionNode; - this.isTryCast = isTryCast; + this.castMode = castMode; } @Override @@ -39,12 +49,20 @@ public Expression toProtobuf() { Expression.Cast.Builder castBuilder = Expression.Cast.newBuilder(); castBuilder.setType(typeNode.toProtobuf()); castBuilder.setInput(expressionNode.toProtobuf()); - if (!isTryCast) { - // Throw exception on failure. - castBuilder.setFailureBehaviorValue(2); - } else { - // Return null on failure. - castBuilder.setFailureBehaviorValue(1); + switch (castMode) { + case ANSI: + // Throw exception on failure. + castBuilder.setFailureBehaviorValue(2); + break; + case TRY: + // Return null on failure. + castBuilder.setFailureBehaviorValue(1); + break; + case LEGACY: + // Leave failure behavior unspecified to preserve Spark legacy cast semantics. + break; + default: + throw new IllegalStateException("Unsupported cast mode: " + castMode); } Expression.Builder builder = Expression.newBuilder(); builder.setCast(castBuilder.build()); diff --git a/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/ExpressionBuilder.java b/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/ExpressionBuilder.java index 4bdef37878c..9d5b5c9a101 100644 --- a/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/ExpressionBuilder.java +++ b/gluten-substrait/src/main/java/org/apache/gluten/substrait/expression/ExpressionBuilder.java @@ -242,6 +242,11 @@ public static CastNode makeCast( return new CastNode(typeNode, expressionNode, isTryCast); } + public static CastNode makeCast( + TypeNode typeNode, ExpressionNode expressionNode, CastNode.CastMode castMode) { + return new CastNode(typeNode, expressionNode, castMode); + } + public static StringMapNode makeStringMap(Map values) { return new StringMapNode(values); } diff --git a/gluten-substrait/src/main/scala/org/apache/gluten/expression/UnaryExpressionTransformer.scala b/gluten-substrait/src/main/scala/org/apache/gluten/expression/UnaryExpressionTransformer.scala index b762ec95b64..536f51ac48b 100644 --- a/gluten-substrait/src/main/scala/org/apache/gluten/expression/UnaryExpressionTransformer.scala +++ b/gluten-substrait/src/main/scala/org/apache/gluten/expression/UnaryExpressionTransformer.scala @@ -22,7 +22,7 @@ import org.apache.gluten.sql.shims.SparkShimLoader import org.apache.gluten.substrait.`type`.ListNode import org.apache.gluten.substrait.`type`.MapNode import org.apache.gluten.substrait.SubstraitContext -import org.apache.gluten.substrait.expression.{ExpressionBuilder, ExpressionNode, StructLiteralNode} +import org.apache.gluten.substrait.expression.{CastNode, ExpressionBuilder, ExpressionNode, StructLiteralNode} import org.apache.spark.sql.catalyst.expressions._ import org.apache.spark.sql.types._ @@ -45,10 +45,19 @@ case class CastTransformer(substraitExprName: String, child: ExpressionTransform extends UnaryExpressionTransformer { override def doTransform(context: SubstraitContext): ExpressionNode = { val typeNode = ConverterUtils.getTypeNode(dataType, original.nullable) + val sparkShims = SparkShimLoader.getSparkShims + // Store-assignment casts can carry EvalMode.ANSI even when session ANSI is disabled. + val castMode = if (sparkShims.withTryEvalMode(original)) { + CastNode.CastMode.TRY + } else if (sparkShims.withAnsiEvalMode(original)) { + CastNode.CastMode.ANSI + } else { + CastNode.CastMode.LEGACY + } ExpressionBuilder.makeCast( typeNode, child.doTransform(context), - SparkShimLoader.getSparkShims.withTryEvalMode(original)) + castMode) } } diff --git a/shims/spark33/src/main/scala/org/apache/gluten/sql/shims/spark33/Spark33Shims.scala b/shims/spark33/src/main/scala/org/apache/gluten/sql/shims/spark33/Spark33Shims.scala index 52a9a4fd109..0d09b9e4b9d 100644 --- a/shims/spark33/src/main/scala/org/apache/gluten/sql/shims/spark33/Spark33Shims.scala +++ b/shims/spark33/src/main/scala/org/apache/gluten/sql/shims/spark33/Spark33Shims.scala @@ -209,6 +209,13 @@ class Spark33Shims extends SparkShims { } } + override def withAnsiEvalMode(expr: Expression): Boolean = { + expr match { + case _: Cast => SQLConf.get.ansiEnabled + case _ => false + } + } + override def createParquetFilters( conf: SQLConf, schema: MessageType, diff --git a/shims/spark34/src/main/scala/org/apache/gluten/sql/shims/spark34/Spark34Shims.scala b/shims/spark34/src/main/scala/org/apache/gluten/sql/shims/spark34/Spark34Shims.scala index 4f686271f1b..f42ee0901a0 100644 --- a/shims/spark34/src/main/scala/org/apache/gluten/sql/shims/spark34/Spark34Shims.scala +++ b/shims/spark34/src/main/scala/org/apache/gluten/sql/shims/spark34/Spark34Shims.scala @@ -434,6 +434,7 @@ class Spark34Shims extends SparkShims { case s: Subtract => s.evalMode == EvalMode.ANSI case d: Divide => d.evalMode == EvalMode.ANSI case m: Multiply => m.evalMode == EvalMode.ANSI + case c: Cast => c.evalMode == EvalMode.ANSI case i: IntegralDivide => i.evalMode == EvalMode.ANSI case _ => false } diff --git a/shims/spark35/src/main/scala/org/apache/gluten/sql/shims/spark35/Spark35Shims.scala b/shims/spark35/src/main/scala/org/apache/gluten/sql/shims/spark35/Spark35Shims.scala index 8be204816a8..74312e16b34 100644 --- a/shims/spark35/src/main/scala/org/apache/gluten/sql/shims/spark35/Spark35Shims.scala +++ b/shims/spark35/src/main/scala/org/apache/gluten/sql/shims/spark35/Spark35Shims.scala @@ -472,6 +472,7 @@ class Spark35Shims extends SparkShims { case s: Subtract => s.evalMode == EvalMode.ANSI case d: Divide => d.evalMode == EvalMode.ANSI case m: Multiply => m.evalMode == EvalMode.ANSI + case c: Cast => c.evalMode == EvalMode.ANSI case i: IntegralDivide => i.evalMode == EvalMode.ANSI case _ => false }