Skip to content

Commit 2e03cca

Browse files
committed
Update cast handling in planner for field-aware types
Refactor logical Expr::Cast to use field-aware CastExpr, ensuring target FieldRef metadata is preserved. Enhance tests to confirm metadata retention, validate that same-type casts aren't elided for fields with semantics, and ensure existing TryCast rejection for extension types remains effective.
1 parent 7eccbb9 commit 2e03cca

1 file changed

Lines changed: 68 additions & 27 deletions

File tree

datafusion/physical-expr/src/planner.rs

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -289,23 +289,17 @@ pub fn create_physical_expr(
289289
Ok(expressions::case(expr, when_then_expr, else_expr)?)
290290
}
291291
Expr::Cast(Cast { expr, field }) => {
292-
if !field.metadata().is_empty() {
293-
let (_, src_field) = expr.to_field(input_dfschema)?;
294-
return plan_err!(
295-
"Cast from {} to {} is not supported",
296-
format_type_and_metadata(
297-
src_field.data_type(),
298-
Some(src_field.metadata()),
299-
),
300-
format_type_and_metadata(field.data_type(), Some(field.metadata()))
301-
);
302-
}
292+
let expr = create_physical_expr(expr, input_dfschema, execution_props)?;
303293

304-
expressions::cast(
305-
create_physical_expr(expr, input_dfschema, execution_props)?,
306-
input_schema,
307-
field.data_type().clone(),
308-
)
294+
// Reuse the standard CAST validation path, but preserve the logical
295+
// target field instead of lowering to a type-only physical cast.
296+
expressions::cast(Arc::clone(&expr), input_schema, field.data_type().clone())?;
297+
298+
Ok(Arc::new(expressions::CastExpr::new_with_target_field(
299+
expr,
300+
Arc::clone(field),
301+
None,
302+
)))
309303
}
310304
Expr::TryCast(TryCast { expr, field }) => {
311305
if !field.metadata().is_empty() {
@@ -476,7 +470,63 @@ mod tests {
476470
}
477471

478472
#[test]
479-
fn test_cast_to_extension_type() -> Result<()> {
473+
fn test_cast_lowering_preserves_target_field_metadata() -> Result<()> {
474+
let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
475+
let df_schema = DFSchema::try_from(schema.clone())?;
476+
let target_field = Arc::new(
477+
Field::new("cast_target", DataType::Int64, true).with_metadata(
478+
[("target_meta".to_string(), "1".to_string())].into(),
479+
),
480+
);
481+
let cast_expr = Expr::Cast(Cast::new_from_field(
482+
Box::new(col("a")),
483+
Arc::clone(&target_field),
484+
));
485+
486+
let physical =
487+
create_physical_expr(&cast_expr, &df_schema, &ExecutionProps::new())?;
488+
let cast = physical
489+
.as_any()
490+
.downcast_ref::<expressions::CastExpr>()
491+
.expect("planner should lower logical CAST to CastExpr");
492+
493+
assert_eq!(cast.target_field(), &target_field);
494+
assert_eq!(physical.return_field(&schema)?, target_field);
495+
assert!(physical.nullable(&schema)?);
496+
497+
Ok(())
498+
}
499+
500+
#[test]
501+
fn test_cast_lowering_preserves_same_type_field_semantics() -> Result<()> {
502+
let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
503+
let df_schema = DFSchema::try_from(schema.clone())?;
504+
let target_field = Arc::new(
505+
Field::new("same_type_cast", DataType::Int32, true).with_metadata(
506+
[("target_meta".to_string(), "same-type".to_string())].into(),
507+
),
508+
);
509+
let cast_expr = Expr::Cast(Cast::new_from_field(
510+
Box::new(col("a")),
511+
Arc::clone(&target_field),
512+
));
513+
514+
let physical =
515+
create_physical_expr(&cast_expr, &df_schema, &ExecutionProps::new())?;
516+
let cast = physical
517+
.as_any()
518+
.downcast_ref::<expressions::CastExpr>()
519+
.expect("same-type casts should not be elided when the target field carries semantics");
520+
521+
assert_eq!(cast.target_field(), &target_field);
522+
assert_eq!(physical.return_field(&schema)?, target_field);
523+
assert!(physical.nullable(&schema)?);
524+
525+
Ok(())
526+
}
527+
528+
#[test]
529+
fn test_try_cast_to_extension_type_is_rejected() -> Result<()> {
480530
let extension_field_type = Arc::new(
481531
DataType::FixedSizeBinary(16)
482532
.into_nullable_field()
@@ -486,17 +536,8 @@ mod tests {
486536
),
487537
);
488538
let expr = lit("3230e5d4-888e-408b-b09b-831f44aa0c58");
489-
let cast_expr = Expr::Cast(Cast::new_from_field(
490-
Box::new(expr.clone()),
491-
Arc::clone(&extension_field_type),
492-
));
493-
let err =
494-
create_physical_expr(&cast_expr, &DFSchema::empty(), &ExecutionProps::new())
495-
.unwrap_err();
496-
assert!(err.message().contains("arrow.uuid"));
497-
498539
let try_cast_expr = Expr::TryCast(TryCast::new_from_field(
499-
Box::new(expr.clone()),
540+
Box::new(expr),
500541
Arc::clone(&extension_field_type),
501542
));
502543
let err = create_physical_expr(

0 commit comments

Comments
 (0)