This is an automated email from the ASF dual-hosted git repository.
Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/main by this push:
new c9e83cc864 fix(arrow-ord): return an error for nested dictionary
comparisons (#11104)
c9e83cc864 is described below
commit c9e83cc86427fdbb33b058939adf334fae0f08d5
Author: Jaideep Pyne <[email protected]>
AuthorDate: Thu Sep 17 05:56:15 2026 +0530
fix(arrow-ord): return an error for nested dictionary comparisons (#11104)
Fixes #11094.
## Summary
- After unwrapping one dictionary/REE layer, `compare_op` now errors if
the leftover type is still `Dictionary` or `RunEndEncoded`.
- Add a regression: `eq` on `Dictionary<Int32, Dictionary<Int32, Utf8>>`
returns `InvalidArgumentError` instead of panicking.
## Why
`DataType::is_nested` is false for `Dictionary` and `RunEndEncoded`.
Nested dictionaries therefore passed the existing nested/type-mismatch
checks and fell into `downcast_primitive_array!`'s `unreachable!()` arm,
taking down the caller thread. Other unsupported inputs already return
`Err`.
The check is on the leftover type after the one-level unwrap so
primitive REE comparisons keep working.
## Validation
- `cargo test -p arrow-ord --lib cmp::`
---
arrow-ord/src/cmp.rs | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/arrow-ord/src/cmp.rs b/arrow-ord/src/cmp.rs
index 6c9d6115c6..e651c5f0b8 100644
--- a/arrow-ord/src/cmp.rs
+++ b/arrow-ord/src/cmp.rs
@@ -257,6 +257,15 @@ fn compare_op(op: Op, lhs: &dyn Datum, rhs: &dyn Datum) ->
Result<BooleanArray,
return Err(ArrowError::InvalidArgumentError(format!(
"Nested comparison: {l_t} {op} {r_t} (hint: use make_comparator
instead)"
)));
+ } else if matches!(l_t, Dictionary(_, _) | RunEndEncoded(_, _))
+ || matches!(r_t, Dictionary(_, _) | RunEndEncoded(_, _))
+ {
+ // One dictionary/REE layer is unwrapped above. A leftover dictionary
+ // (nested dict) or REE is not nested according to
`DataType::is_nested`,
+ // so without this check `downcast_primitive_array!` panics.
+ return Err(ArrowError::InvalidArgumentError(format!(
+ "Invalid comparison operation: {l_t} {op} {r_t}"
+ )));
} else if l_t != r_t {
return Err(ArrowError::InvalidArgumentError(format!(
"Invalid comparison operation: {l_t} {op} {r_t}"
@@ -1180,6 +1189,20 @@ mod tests {
neq(&col.slice(0, col.len() - 1), &col.slice(1, col.len() -
1)).unwrap();
}
+ #[test]
+ fn test_nested_dictionary_eq_returns_error() {
+ let inner = DictionaryArray::new(
+ Int32Array::from(vec![0, 1]),
+ Arc::new(StringArray::from(vec!["a", "b"])),
+ );
+ let outer = DictionaryArray::new(Int32Array::from(vec![0, 1]),
Arc::new(inner));
+ let err = eq(&outer, &outer).expect_err("nested dictionary must not
panic");
+ assert!(
+ matches!(err, ArrowError::InvalidArgumentError(_)),
+ "unexpected error: {err}"
+ );
+ }
+
#[test]
fn test_string_view_mixed_lt() {
let a = arrow_array::StringViewArray::from(vec![