Skip to content

Commit 21cf60a

Browse files
authored
Add Null Type Coercions for Placeholders (#20543)
## Which issue does this PR close? At least this one: #19471 but might be others ## Rationale for this change This fixes a problem we have where placeholder types are `Null`, and we need to add explicit type casts in the query. I.e, you can't use placeholders for `date_bin` functions: ```sql select date_bin($resolution, timestamp) ``` This works though (currently): ```sql select date_bin($resolution::interval, timestamp) ``` ## What changes are included in this PR? Adds some extra type coercions ## Are these changes tested? Yes, new tests added ## Are there any user-facing changes? Not really?
1 parent 1efcbf5 commit 21cf60a

File tree

3 files changed

+45
-2
lines changed

3 files changed

+45
-2
lines changed

datafusion/expr/src/type_coercion/functions.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,13 @@ fn coerced_from<'a>(
928928
(Timestamp(_, Some(_)), Null | Timestamp(_, _) | Date32 | Utf8 | LargeUtf8) => {
929929
Some(type_into.clone())
930930
}
931+
// Null can be coerced to any target type, provided the cast is valid.
932+
// This mirrors null_coercion() in binary comparison coercion
933+
// (expr-common/src/type_coercion/binary.rs) and is the symmetric
934+
// counterpart of the (Null, _) arm above. Without this, untyped
935+
// placeholders ($1, $foo) inside function calls fail signature matching
936+
// because their Null type doesn't match any Exact(...) variant.
937+
(_, Null) if can_cast_types(type_from, type_into) => Some(type_into.clone()),
931938
_ => None,
932939
}
933940
}
@@ -937,7 +944,7 @@ mod tests {
937944
use crate::Volatility;
938945

939946
use super::*;
940-
use arrow::datatypes::Field;
947+
use arrow::datatypes::{Field, IntervalUnit};
941948
use datafusion_common::{
942949
assert_contains,
943950
types::{logical_binary, logical_int64},
@@ -956,6 +963,36 @@ mod tests {
956963
}
957964
}
958965

966+
#[test]
967+
fn test_coerced_from_null() {
968+
// Null should coerce to Interval (the motivating case)
969+
assert_eq!(
970+
coerced_from(
971+
&DataType::Interval(IntervalUnit::MonthDayNano),
972+
&DataType::Null
973+
),
974+
Some(DataType::Interval(IntervalUnit::MonthDayNano))
975+
);
976+
977+
// Null should coerce to Date32
978+
assert_eq!(
979+
coerced_from(&DataType::Date32, &DataType::Null),
980+
Some(DataType::Date32)
981+
);
982+
983+
// Null should coerce to Timestamp with timezone
984+
assert_eq!(
985+
coerced_from(
986+
&DataType::Timestamp(TimeUnit::Microsecond, Some("+00".into())),
987+
&DataType::Null
988+
),
989+
Some(DataType::Timestamp(
990+
TimeUnit::Microsecond,
991+
Some("+00".into())
992+
))
993+
);
994+
}
995+
959996
#[test]
960997
fn test_maybe_data_types() {
961998
// this vec contains: arg1, arg2, expected result

datafusion/sqllogictest/test_files/datetime/timestamps.slt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5385,3 +5385,9 @@ SELECT to_timestamp(arrow_cast(100.5, 'Float16'), name) FROM test_to_timestamp_s
53855385

53865386
statement ok
53875387
drop table test_to_timestamp_scalar
5388+
5389+
# date_bin with NULL interval should return NULL, not a planning error
5390+
query P
5391+
SELECT date_bin(NULL, TIMESTAMP '2023-01-01 12:30:00', TIMESTAMP '2023-01-01 12:00:00')
5392+
----
5393+
NULL

datafusion/sqllogictest/test_files/nvl.slt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ SELECT NVL(1, 3);
114114
----
115115
1
116116

117-
query I
117+
query B
118118
SELECT NVL(NULL, NULL);
119119
----
120120
NULL

0 commit comments

Comments
 (0)