Skip to content

Commit 3b33f8c

Browse files
committed
Keep annotated untyped NULL neutral during CASE coercion
1 parent 90555f7 commit 3b33f8c

2 files changed

Lines changed: 65 additions & 1 deletion

File tree

‎datafusion/expr/src/expr_schema.rs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,23 @@ fn case_field_metadata(case: &Case, schema: &dyn ExprSchema) -> Result<FieldRef>
256256
let Some(source) = fields.pop() else {
257257
return internal_err!("Missing CASE cast input field");
258258
};
259+
let untyped_null =
260+
source.certainly_null && source.field.data_type().is_null();
261+
let mut field = cast_output_field(&source.field, target, force_nullable);
262+
// A type-only coercion must not make an untyped NULL constrain CASE metadata.
263+
if untyped_null
264+
&& target.metadata().is_empty()
265+
&& !field.metadata().is_empty()
266+
{
267+
field = Arc::new(
268+
field
269+
.as_ref()
270+
.clone()
271+
.with_metadata(arrow_schema::Metadata::default()),
272+
);
273+
}
259274
fields.push(BranchField {
260-
field: cast_output_field(&source.field, target, force_nullable),
275+
field,
261276
certainly_null: source.certainly_null,
262277
});
263278
}

‎datafusion/optimizer/src/analyzer/type_coercion.rs‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3081,6 +3081,55 @@ mod test {
30813081
Ok(())
30823082
}
30833083

3084+
#[test]
3085+
fn test_case_coercion_preserves_metadata_with_annotated_null() -> Result<()> {
3086+
let metadata = std::collections::HashMap::from([
3087+
(
3088+
"ARROW:extension:name".to_string(),
3089+
"example.case".to_string(),
3090+
),
3091+
("structured_type".to_string(), "ARRAY(NUMBER)".to_string()),
3092+
]);
3093+
let schema = DFSchema::from_unqualified_fields(
3094+
vec![
3095+
Field::new("structured", DataType::Int32, false)
3096+
.with_metadata(metadata.clone()),
3097+
]
3098+
.into(),
3099+
std::collections::HashMap::new(),
3100+
)?;
3101+
let conflicting = std::collections::HashMap::from([
3102+
(
3103+
"ARROW:extension:name".to_string(),
3104+
"example.other".to_string(),
3105+
),
3106+
("structured_type".to_string(), "OBJECT".to_string()),
3107+
]);
3108+
for null_metadata in [metadata.clone(), conflicting] {
3109+
let annotated_null = Expr::Literal(
3110+
ScalarValue::Null,
3111+
Some(expr::FieldMetadata::from(null_metadata)),
3112+
);
3113+
let case = Case {
3114+
expr: None,
3115+
when_then_expr: vec![(Box::new(lit(true)), Box::new(col("structured")))],
3116+
else_expr: Some(Box::new(annotated_null)),
3117+
};
3118+
assert_eq!(
3119+
Expr::Case(case.clone()).to_field(&schema)?.1.metadata(),
3120+
&metadata
3121+
);
3122+
3123+
let coerced = coerce_case_expression(case, &schema, None)?;
3124+
assert!(matches!(coerced.else_expr.as_deref(), Some(Expr::Cast(_))));
3125+
assert_eq!(
3126+
Expr::Case(coerced).to_field(&schema)?.1.metadata(),
3127+
&metadata
3128+
);
3129+
}
3130+
Ok(())
3131+
}
3132+
30843133
#[test]
30853134
fn test_case_expression_coercion() -> Result<()> {
30863135
let schema = Arc::new(DFSchema::from_unqualified_fields(

0 commit comments

Comments
 (0)