@@ -571,7 +571,18 @@ impl TreeNodeRewriter for ConstEvaluator<'_> {
571571 ConstSimplifyResult :: NotSimplified ( s, m) => {
572572 Ok ( Transformed :: no ( Expr :: Literal ( s, m) ) )
573573 }
574- ConstSimplifyResult :: SimplifyRuntimeError ( _, expr) => {
574+ ConstSimplifyResult :: SimplifyRuntimeError ( err, expr) => {
575+ // For CAST expressions with literal inputs, propagate the error at plan time rather than deferring to execution time.
576+ // This provides clearer error messages and fails fast.
577+ if let Expr :: Cast ( Cast { ref expr, .. } )
578+ | Expr :: TryCast ( TryCast { ref expr, .. } ) = expr
579+ {
580+ if matches ! ( expr. as_ref( ) , Expr :: Literal ( _, _) ) {
581+ return Err ( err) ;
582+ }
583+ }
584+ // For other expressions (like CASE, COALESCE), preserve the original
585+ // to allow short-circuit evaluation at execution time
575586 Ok ( Transformed :: yes ( expr) )
576587 }
577588 } ,
@@ -4968,6 +4979,56 @@ mod tests {
49684979 ) ;
49694980 }
49704981
4982+ #[ test]
4983+ fn simplify_cast_literal ( ) {
4984+ // Test that CAST(literal) expressions are evaluated at plan time
4985+
4986+ // CAST(123 AS Int64) should become 123i64
4987+ let expr = Expr :: Cast ( Cast :: new ( Box :: new ( lit ( 123i32 ) ) , DataType :: Int64 ) ) ;
4988+ let expected = lit ( 123i64 ) ;
4989+ assert_eq ! ( simplify( expr) , expected) ;
4990+
4991+ // CAST(1761630189642 AS Timestamp(Nanosecond, Some("+00:00")))
4992+ // Integer to timestamp cast
4993+ let expr = Expr :: Cast ( Cast :: new (
4994+ Box :: new ( lit ( 1761630189642i64 ) ) ,
4995+ DataType :: Timestamp (
4996+ arrow:: datatypes:: TimeUnit :: Nanosecond ,
4997+ Some ( "+00:00" . into ( ) ) ,
4998+ ) ,
4999+ ) ) ;
5000+ // Should evaluate to a timestamp literal
5001+ let result = simplify ( expr) ;
5002+ match result {
5003+ Expr :: Literal ( ScalarValue :: TimestampNanosecond ( Some ( val) , tz) , _) => {
5004+ assert_eq ! ( val, 1761630189642i64 ) ;
5005+ assert_eq ! ( tz. as_deref( ) , Some ( "+00:00" ) ) ;
5006+ }
5007+ other => panic ! ( "Expected TimestampNanosecond literal, got: {:?}" , other) ,
5008+ }
5009+
5010+ // Test CAST of invalid string to timestamp - should return an error at plan time
5011+ // This represents the case from the issue: CAST(Utf8("1761630189642") AS Timestamp)
5012+ // "1761630189642" is NOT a valid timestamp string format
5013+ let expr = Expr :: Cast ( Cast :: new (
5014+ Box :: new ( lit ( "1761630189642" ) ) ,
5015+ DataType :: Timestamp (
5016+ arrow:: datatypes:: TimeUnit :: Nanosecond ,
5017+ Some ( "+00:00" . into ( ) ) ,
5018+ ) ,
5019+ ) ) ;
5020+
5021+ // The simplification should now fail with an error at plan time
5022+ let schema = test_schema ( ) ;
5023+ let props = ExecutionProps :: new ( ) ;
5024+ let simplifier =
5025+ ExprSimplifier :: new ( SimplifyContext :: new ( & props) . with_schema ( schema) ) ;
5026+ let result = simplifier. simplify ( expr) ;
5027+ assert ! ( result. is_err( ) , "Expected error for invalid cast" ) ;
5028+ let err_msg = result. unwrap_err ( ) . to_string ( ) ;
5029+ assert_contains ! ( err_msg, "Error parsing timestamp" ) ;
5030+ }
5031+
49715032 fn if_not_null ( expr : Expr , then : bool ) -> Expr {
49725033 Expr :: Case ( Case {
49735034 expr : Some ( expr. is_not_null ( ) . into ( ) ) ,
0 commit comments