From bc969b0d45c14163dfc05235d8743b83c644b334 Mon Sep 17 00:00:00 2001 From: Patrick Ribbsaeter Date: Wed, 7 Oct 2026 03:21:54 +0200 Subject: [PATCH] fix: downcast to FixedSizeBinaryArray in CASE literal lookup table Fixes #26012. In LiteralLookupTable, evaluation of CASE WHEN ... with byte-like literals delegates to BytesLikeIndexMap. In ry_get_bytes_iterator, the DataType::FixedSizeBinary(_) match arm erroneously attempted to downcast the array via s_binary::() (inherited from DataType::LargeBinary), rather than handling FixedSizeBinaryArray. When a query contained 2 or more FixedSizeBinary WHEN literals, LiteralLookupTable was constructed and panicked during evaluation when downcasting to GenericBinaryArray with inary array. This fix updates DataType::FixedSizeBinary(_) in ry_get_bytes_iterator to downcast via rray.as_fixed_size_binary().iter(), matching the dictionary downcasting path and enabling correct evaluation. Signed-off-by: Patrick Ribbsaeter --- .../physical-expr/src/expressions/case.rs | 46 ++++++++++++++ .../bytes_like_lookup_table.rs | 2 +- datafusion/sqllogictest/test_files/case.slt | 60 +++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/datafusion/physical-expr/src/expressions/case.rs b/datafusion/physical-expr/src/expressions/case.rs index dd98029c739a8..c4849ffcc2ec9 100644 --- a/datafusion/physical-expr/src/expressions/case.rs +++ b/datafusion/physical-expr/src/expressions/case.rs @@ -3500,6 +3500,52 @@ mod tests { ); } + #[test] + fn test_case_when_literal_lookup_fixed_size_binary_to_int32() { + let lookup_map = vec![ + ( + ScalarValue::FixedSizeBinary(1, Some(vec![1])), + ScalarValue::Int32(Some(10)), + ), + ( + ScalarValue::FixedSizeBinary(1, Some(vec![2])), + ScalarValue::Int32(Some(20)), + ), + ]; + + let input_values = ScalarValue::iter_to_array(vec![ + ScalarValue::FixedSizeBinary(1, Some(vec![1])), + ScalarValue::FixedSizeBinary(1, Some(vec![2])), + ScalarValue::FixedSizeBinary(1, Some(vec![3])), + ScalarValue::FixedSizeBinary(1, None), + ]) + .expect("valid fixed size binary array"); + + let expected_no_else = + Arc::new(Int32Array::from(vec![Some(10), Some(20), None, None])) as ArrayRef; + + test_case_when_literal_lookup( + Arc::clone(&input_values), + &lookup_map, + None, + expected_no_else, + ); + + let expected_with_else = Arc::new(Int32Array::from(vec![ + Some(10), + Some(20), + Some(99), + Some(99), + ])) as ArrayRef; + + test_case_when_literal_lookup( + input_values, + &lookup_map, + Some(ScalarValue::Int32(Some(99))), + expected_with_else, + ); + } + /// Reproduces https://github.com/apache/datafusion/issues/22173 /// /// Nested self-referential CASE chains (common in rewrite-style projections) diff --git a/datafusion/physical-expr/src/expressions/case/literal_lookup_table/bytes_like_lookup_table.rs b/datafusion/physical-expr/src/expressions/case/literal_lookup_table/bytes_like_lookup_table.rs index e5cf3f84fd919..658d9cdaeec69 100644 --- a/datafusion/physical-expr/src/expressions/case/literal_lookup_table/bytes_like_lookup_table.rs +++ b/datafusion/physical-expr/src/expressions/case/literal_lookup_table/bytes_like_lookup_table.rs @@ -106,7 +106,7 @@ fn try_get_bytes_iterator( DataType::LargeBinary => Box::new(array.as_binary::().into_iter()), - DataType::FixedSizeBinary(_) => Box::new(array.as_binary::().into_iter()), + DataType::FixedSizeBinary(_) => Box::new(array.as_fixed_size_binary().iter()), DataType::Utf8View => Box::new( array diff --git a/datafusion/sqllogictest/test_files/case.slt b/datafusion/sqllogictest/test_files/case.slt index 3f9058bce865b..712981ed8313b 100644 --- a/datafusion/sqllogictest/test_files/case.slt +++ b/datafusion/sqllogictest/test_files/case.slt @@ -846,6 +846,66 @@ drop table letters; statement ok drop table letters_binary; +# Regression test for https://github.com/apache/datafusion/issues/26012 +# CASE with two or more FixedSizeBinary WHEN literals +query I +SELECT CASE x + WHEN arrow_cast(X'01', 'FixedSizeBinary(1)') THEN 1 + WHEN arrow_cast(X'02', 'FixedSizeBinary(1)') THEN 2 + ELSE 3 + END AS r +FROM (SELECT arrow_cast(X'03', 'FixedSizeBinary(1)') AS x); +---- +3 + +query I +SELECT CASE x + WHEN arrow_cast(X'01', 'FixedSizeBinary(1)') THEN 1 + WHEN arrow_cast(X'02', 'FixedSizeBinary(1)') THEN 2 + ELSE 3 + END AS r +FROM (SELECT arrow_cast(X'02', 'FixedSizeBinary(1)') AS x); +---- +2 + +query I +SELECT CASE x + WHEN arrow_cast(X'01', 'FixedSizeBinary(1)') THEN 1 + WHEN arrow_cast(X'02', 'FixedSizeBinary(1)') THEN 2 + ELSE 3 + END AS r +FROM (SELECT arrow_cast(X'01', 'FixedSizeBinary(1)') AS x); +---- +1 + +query I +SELECT CASE x + WHEN arrow_cast(X'01', 'FixedSizeBinary(1)') THEN 1 + WHEN arrow_cast(X'02', 'FixedSizeBinary(1)') THEN 2 + ELSE 3 + END AS r +FROM (SELECT arrow_cast(NULL, 'FixedSizeBinary(1)') AS x); +---- +3 + +query I +SELECT CASE x + WHEN arrow_cast(X'01', 'FixedSizeBinary(1)') THEN 10 + WHEN arrow_cast(X'02', 'FixedSizeBinary(1)') THEN 20 + ELSE 99 + END AS r +FROM (VALUES + (arrow_cast(X'01', 'FixedSizeBinary(1)')), + (arrow_cast(X'02', 'FixedSizeBinary(1)')), + (arrow_cast(X'03', 'FixedSizeBinary(1)')), + (arrow_cast(NULL, 'FixedSizeBinary(1)')) +) t(x); +---- +10 +20 +99 +99 + # Tests for CASE with boolean expressions statement ok create table booleans (b boolean) as values (true), (false), (null), (true), (null), (false);