From eae7162017be6e0879233c8faa18c1695a3801fc Mon Sep 17 00:00:00 2001 From: Huaijin Date: Thu, 3 Sep 2026 17:00:34 +0800 Subject: [PATCH 1/3] fix: include source field context in cast errors --- .../physical-expr/src/expressions/cast.rs | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/datafusion/physical-expr/src/expressions/cast.rs b/datafusion/physical-expr/src/expressions/cast.rs index 565d396dad1b..f2d5748227ef 100644 --- a/datafusion/physical-expr/src/expressions/cast.rs +++ b/datafusion/physical-expr/src/expressions/cast.rs @@ -330,7 +330,22 @@ impl PhysicalExpr for CastExpr { fn evaluate(&self, batch: &RecordBatch) -> Result { let value = self.expr.evaluate(batch)?; - value.cast_to(self.cast_type(), Some(&self.cast_options)) + value + .cast_to(self.cast_type(), Some(&self.cast_options)) + .map_err(|error| { + let source = self + .expr + .return_field(batch.schema().as_ref()) + .ok() + .filter(|field| !field.name().is_empty()) + .map(|field| format!("field '{}'", field.name())) + .unwrap_or_else(|| format!("expression '{}'", self.expr)); + error.context(format!( + "Failed to cast {source} from {} to {}", + value.data_type(), + self.cast_type() + )) + }) } fn return_field(&self, input_schema: &Schema) -> Result { @@ -1088,22 +1103,20 @@ mod tests { #[test] fn invalid_cast_with_options_error() -> Result<()> { - // Ensure a useful error happens at plan time if invalid casts are used + // Ensure a useful error happens at runtime if invalid casts are used let schema = Schema::new(vec![Field::new("a", Utf8, false)]); let a = StringArray::from(vec!["9.1"]); let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?; let expression = cast_with_options(col("a", &schema)?, &schema, Int32, None)?; let result = expression.evaluate(&batch); - match result { - Ok(_) => panic!("expected error"), - Err(e) => { - assert!( - e.to_string() - .contains("Cannot cast string '9.1' to value of Int32 type") - ) - } - } + let error = result.expect_err("expected error").strip_backtrace(); + assert_eq!( + error, + "Failed to cast field 'a' from Utf8 to Int32\n\ + caused by\n\ + Arrow error: Cast error: Cannot cast string '9.1' to value of Int32 type" + ); Ok(()) } From 6346aef18256d0b147b5f7e061c006405d1e12d7 Mon Sep 17 00:00:00 2001 From: haohuaijin Date: Thu, 3 Sep 2026 18:16:51 +0800 Subject: [PATCH 2/3] test: update cast error expectations --- datafusion/physical-expr/src/expressions/cast.rs | 6 +++++- datafusion/sqllogictest/test_files/errors.slt | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/datafusion/physical-expr/src/expressions/cast.rs b/datafusion/physical-expr/src/expressions/cast.rs index f2d5748227ef..1249fa52ae7f 100644 --- a/datafusion/physical-expr/src/expressions/cast.rs +++ b/datafusion/physical-expr/src/expressions/cast.rs @@ -784,7 +784,11 @@ mod tests { let expression = cast_with_options(col("a", &schema)?, &schema, Decimal128(6, 2), None)?; let e = expression.evaluate(&batch).unwrap_err().strip_backtrace(); // panics on OK - assert_snapshot!(e, @"Arrow error: Invalid argument error: 123456.79 is too large to store in a Decimal128 of precision 6. Max is 9999.99"); + assert_snapshot!(e, @r" + Failed to cast field 'a' from Decimal128(10, 3) to Decimal128(6, 2) + caused by + Arrow error: Invalid argument error: 123456.79 is too large to store in a Decimal128 of precision 6. Max is 9999.99 + "); // safe cast should return null let expression_safe = cast_with_options( col("a", &schema)?, diff --git a/datafusion/sqllogictest/test_files/errors.slt b/datafusion/sqllogictest/test_files/errors.slt index 5d3ddd43f086..55aa405ae3dd 100644 --- a/datafusion/sqllogictest/test_files/errors.slt +++ b/datafusion/sqllogictest/test_files/errors.slt @@ -42,7 +42,7 @@ statement error SELECT sin(c1) FROM aggregate_test_100 # cast_expressions_error -statement error DataFusion error: Arrow error: Cast error: Cannot cast string 'c' to value of Int32 type +statement error DataFusion error: Failed to cast field 'c1' from Utf8View to Int32\ncaused by\nArrow error: Cast error: Cannot cast string 'c' to value of Int32 type SELECT CAST(c1 AS INT) FROM aggregate_test_100 # aggregation_with_bad_arguments From 86926dcc35a663bfcd79c683990063c54a6aed4c Mon Sep 17 00:00:00 2001 From: haohuaijin Date: Thu, 3 Sep 2026 19:57:48 +0800 Subject: [PATCH 3/3] fix coverage --- .../physical-expr/src/expressions/cast.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/datafusion/physical-expr/src/expressions/cast.rs b/datafusion/physical-expr/src/expressions/cast.rs index 1249fa52ae7f..508c5de4ad4c 100644 --- a/datafusion/physical-expr/src/expressions/cast.rs +++ b/datafusion/physical-expr/src/expressions/cast.rs @@ -1124,6 +1124,28 @@ mod tests { Ok(()) } + #[test] + fn invalid_cast_with_empty_field_name_uses_expression() -> Result<()> { + let schema = Schema::new(vec![Field::new("", Utf8, false)]); + let batch = RecordBatch::try_new( + Arc::new(schema.clone()), + vec![Arc::new(StringArray::from(vec!["9.1"]))], + )?; + let expression = cast_with_options(col("", &schema)?, &schema, Int32, None)?; + + let error = expression + .evaluate(&batch) + .expect_err("expected error") + .strip_backtrace(); + assert_eq!( + error, + "Failed to cast expression '@0' from Utf8 to Int32\n\ + caused by\n\ + Arrow error: Cast error: Cannot cast string '9.1' to value of Int32 type" + ); + Ok(()) + } + #[test] fn field_aware_cast_preserves_target_field_semantics() -> Result<()> { // Target field metadata should be preserved exactly (no merging with source).