in datafusion/optimizer/src/analyzer/type_coercion.rs [645:754]
fn coerce_case_expression(case: Case, schema: &DFSchemaRef) -> Result<Case> {
// Given expressions like:
//
// CASE a1
// WHEN a2 THEN b1
// WHEN a3 THEN b2
// ELSE b3
// END
//
// or:
//
// CASE
// WHEN x1 THEN b1
// WHEN x2 THEN b2
// ELSE b3
// END
//
// Then all aN (a1, a2, a3) must be converted to a common data type in the first example
// (case-when expression coercion)
//
// All xN (x1, x2) must be converted to a boolean data type in the second example
// (when-boolean expression coercion)
//
// And all bN (b1, b2, b3) must be converted to a common data type in both examples
// (then-else expression coercion)
//
// If any fail to find and cast to a common/specific data type, will return error
//
// Note that case-when and when-boolean expression coercions are mutually exclusive
// Only one or the other can occur for a case expression, whilst then-else expression coercion will always occur
// prepare types
let case_type = case
.expr
.as_ref()
.map(|expr| expr.get_type(&schema))
.transpose()?;
let then_types = case
.when_then_expr
.iter()
.map(|(_when, then)| then.get_type(&schema))
.collect::<Result<Vec<_>>>()?;
let else_type = case
.else_expr
.as_ref()
.map(|expr| expr.get_type(&schema))
.transpose()?;
// find common coercible types
let case_when_coerce_type = case_type
.as_ref()
.map(|case_type| {
let when_types = case
.when_then_expr
.iter()
.map(|(when, _then)| when.get_type(&schema))
.collect::<Result<Vec<_>>>()?;
let coerced_type =
get_coerce_type_for_case_expression(&when_types, Some(case_type));
coerced_type.ok_or_else(|| {
DataFusionError::Plan(format!(
"Failed to coerce case ({case_type:?}) and when ({when_types:?}) \
to common types in CASE WHEN expression"
))
})
})
.transpose()?;
let then_else_coerce_type =
get_coerce_type_for_case_expression(&then_types, else_type.as_ref()).ok_or_else(
|| {
DataFusionError::Plan(format!(
"Failed to coerce then ({then_types:?}) and else ({else_type:?}) \
to common types in CASE WHEN expression"
))
},
)?;
// do cast if found common coercible types
let case_expr = case
.expr
.zip(case_when_coerce_type.as_ref())
.map(|(case_expr, coercible_type)| case_expr.cast_to(coercible_type, &schema))
.transpose()?
.map(Box::new);
let when_then = case
.when_then_expr
.into_iter()
.map(|(when, then)| {
let when_type = case_when_coerce_type.as_ref().unwrap_or(&DataType::Boolean);
let when = when.cast_to(when_type, &schema).map_err(|e| {
DataFusionError::Context(
format!(
"WHEN expressions in CASE couldn't be \
converted to common type ({when_type})"
),
Box::new(e),
)
})?;
let then = then.cast_to(&then_else_coerce_type, &schema)?;
Ok((Box::new(when), Box::new(then)))
})
.collect::<Result<Vec<_>>>()?;
let else_expr = case
.else_expr
.map(|expr| expr.cast_to(&then_else_coerce_type, &schema))
.transpose()?
.map(Box::new);
Ok(Case::new(case_expr, when_then, else_expr))
}