From 286334d7180475c8c99669ca392df8efa69ecfb6 Mon Sep 17 00:00:00 2001 From: Thor Date: Fri, 21 Aug 2026 09:11:14 -0500 Subject: [PATCH 1/2] fix(expr): coerce RunEndEncoded arguments the same way as Dictionary coerced_from() already special-cases Dictionary(_, value_type) so that functions declared with the old-style Exact/OneOf signature (which don't go through the newer Coercible/TypeSignatureClass system) match against the dictionary's value type. RunEndEncoded had no equivalent case, so any such function (e.g. date_bin) failed to plan when given a RunEndEncoded-wrapped column, even though the same column as a plain Dictionary would have matched fine. Add the symmetric RunEndEncoded case, unwrapping to the value field's type before recursing, mirroring the existing Dictionary handling. --- .../expr/src/type_coercion/functions.rs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/datafusion/expr/src/type_coercion/functions.rs b/datafusion/expr/src/type_coercion/functions.rs index ec3ab6f441827..663ae8c9f31f6 100644 --- a/datafusion/expr/src/type_coercion/functions.rs +++ b/datafusion/expr/src/type_coercion/functions.rs @@ -1158,6 +1158,19 @@ fn coerced_from<'a>( { Some(type_into.clone()) } + // coerced run-end encoded the same way as dictionary: callers of a + // signature built from `Exact`/`OneOf` only know the logical value + // type, not that the argument happens to be physically run-encoded. + (_, RunEndEncoded(_, value_type)) + if coerced_from(type_into, value_type.data_type()).is_some() => + { + Some(type_into.clone()) + } + (RunEndEncoded(_, value_type), _) + if coerced_from(value_type.data_type(), type_from).is_some() => + { + Some(type_into.clone()) + } // coerced into type_into (Int8, Null | Int8) => Some(type_into.clone()), (Int16, Null | Int8 | Int16 | UInt8) => Some(type_into.clone()), @@ -1617,6 +1630,38 @@ mod tests { ); } + #[test] + fn test_coerced_from_run_end_encoded() { + let run_end_encoded_of = |value_type: DataType| { + DataType::RunEndEncoded( + Field::new("run_ends", DataType::Int32, false).into(), + Field::new("values", value_type, true).into(), + ) + }; + + let type_into = run_end_encoded_of(DataType::UInt32); + let type_from = DataType::Int64; + assert_eq!(coerced_from(&type_into, &type_from), None); + + let type_from = run_end_encoded_of(DataType::UInt32); + let type_into = DataType::Int64; + assert_eq!( + coerced_from(&type_into, &type_from), + Some(type_into.clone()) + ); + + // Signature candidates for functions like `date_bin` are plain + // Timestamp, but a REE-encoded column (e.g. a segment written with + // REE-dict encoding for that field) should still coerce against + // them via the wrapped value type. + let type_from = run_end_encoded_of(DataType::Timestamp(TimeUnit::Nanosecond, None)); + let type_into = DataType::Timestamp(TimeUnit::Nanosecond, None); + assert_eq!( + coerced_from(&type_into, &type_from), + Some(type_into.clone()) + ); + } + #[test] fn test_get_valid_types_array_and_array() -> Result<()> { let function = "array_and_array"; From 823dbbeadc2f395adcc829c40434c7901498191b Mon Sep 17 00:00:00 2001 From: Thor Date: Fri, 21 Aug 2026 09:33:46 -0500 Subject: [PATCH 2/2] fix(optimizer): unwrap RunEndEncoded in window frame target type extract_window_frame_target_type() already recurses through Dictionary(_, value_type) to find the natural type for a RANGE window frame bound, but had no equivalent case for RunEndEncoded, so ordering a RANGE window by a REE-dict-encoded column (the same column as a plain Dictionary would work) failed with "Cannot run range queries on datatype: RunEndEncoded(...)". Add the symmetric RunEndEncoded case, mirroring the existing Dictionary handling. --- datafusion/optimizer/src/analyzer/type_coercion.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datafusion/optimizer/src/analyzer/type_coercion.rs b/datafusion/optimizer/src/analyzer/type_coercion.rs index feadc0370bfd5..2887486179f6c 100644 --- a/datafusion/optimizer/src/analyzer/type_coercion.rs +++ b/datafusion/optimizer/src/analyzer/type_coercion.rs @@ -1083,6 +1083,8 @@ fn extract_window_frame_target_type(col_type: &DataType) -> Result { Ok(DataType::Interval(IntervalUnit::MonthDayNano)) } else if let DataType::Dictionary(_, value_type) = col_type { extract_window_frame_target_type(value_type) + } else if let DataType::RunEndEncoded(_, value_type) = col_type { + extract_window_frame_target_type(value_type.data_type()) } else { internal_err!("Cannot run range queries on datatype: {col_type}") }