diff --git a/datafusion/expr/src/type_coercion/functions.rs b/datafusion/expr/src/type_coercion/functions.rs index fe259fb8c972..e61c3fb6fd0a 100644 --- a/datafusion/expr/src/type_coercion/functions.rs +++ b/datafusion/expr/src/type_coercion/functions.rs @@ -928,6 +928,13 @@ fn coerced_from<'a>( (Timestamp(_, Some(_)), Null | Timestamp(_, _) | Date32 | Utf8 | LargeUtf8) => { Some(type_into.clone()) } + // Null can be coerced to any target type, provided the cast is valid. + // This mirrors null_coercion() in binary comparison coercion + // (expr-common/src/type_coercion/binary.rs) and is the symmetric + // counterpart of the (Null, _) arm above. Without this, untyped + // placeholders ($1, $foo) inside function calls fail signature matching + // because their Null type doesn't match any Exact(...) variant. + (_, Null) if can_cast_types(type_from, type_into) => Some(type_into.clone()), _ => None, } } @@ -937,7 +944,7 @@ mod tests { use crate::Volatility; use super::*; - use arrow::datatypes::Field; + use arrow::datatypes::{Field, IntervalUnit}; use datafusion_common::{ assert_contains, types::{logical_binary, logical_int64}, @@ -956,6 +963,36 @@ mod tests { } } + #[test] + fn test_coerced_from_null() { + // Null should coerce to Interval (the motivating case) + assert_eq!( + coerced_from( + &DataType::Interval(IntervalUnit::MonthDayNano), + &DataType::Null + ), + Some(DataType::Interval(IntervalUnit::MonthDayNano)) + ); + + // Null should coerce to Date32 + assert_eq!( + coerced_from(&DataType::Date32, &DataType::Null), + Some(DataType::Date32) + ); + + // Null should coerce to Timestamp with timezone + assert_eq!( + coerced_from( + &DataType::Timestamp(TimeUnit::Microsecond, Some("+00".into())), + &DataType::Null + ), + Some(DataType::Timestamp( + TimeUnit::Microsecond, + Some("+00".into()) + )) + ); + } + #[test] fn test_maybe_data_types() { // this vec contains: arg1, arg2, expected result diff --git a/datafusion/sqllogictest/test_files/datetime/timestamps.slt b/datafusion/sqllogictest/test_files/datetime/timestamps.slt index 9526ccebfd16..52d10aba95d7 100644 --- a/datafusion/sqllogictest/test_files/datetime/timestamps.slt +++ b/datafusion/sqllogictest/test_files/datetime/timestamps.slt @@ -5370,3 +5370,9 @@ SELECT to_timestamp(arrow_cast(100.5, 'Float16'), name) FROM test_to_timestamp_s statement ok drop table test_to_timestamp_scalar + +# date_bin with NULL interval should return NULL, not a planning error +query P +SELECT date_bin(NULL, TIMESTAMP '2023-01-01 12:30:00', TIMESTAMP '2023-01-01 12:00:00') +---- +NULL diff --git a/datafusion/sqllogictest/test_files/nvl.slt b/datafusion/sqllogictest/test_files/nvl.slt index f4225148ab78..7f78b02baccd 100644 --- a/datafusion/sqllogictest/test_files/nvl.slt +++ b/datafusion/sqllogictest/test_files/nvl.slt @@ -114,7 +114,7 @@ SELECT NVL(1, 3); ---- 1 -query I +query B SELECT NVL(NULL, NULL); ---- NULL