From a8306d68e57527a6b929a15cf046bd8550e995ea Mon Sep 17 00:00:00 2001 From: Patrick Ribbsaeter Date: Sun, 18 Oct 2026 02:23:57 +0000 Subject: [PATCH] Fix Fields::filter_leaves for ListView and LargeListView (#11657) # Which issue does this PR close? Closes #11612. # Rationale for this change `Fields::filter_leaves` and `Fields::try_filter_leaves` perform a depth-first traversal over nested `DataType` variants so that the filter predicate is invoked only on leaf fields (fields with no children). Currently, `try_filter_leaves` handles `DataType::List`, `DataType::LargeList`, and `DataType::FixedSizeList`, but omits `DataType::ListView` and `DataType::LargeListView`, causing `ListView` and `LargeListView` fields to be treated as leaf fields instead of descending into their element `FieldRef`. # What changes are included in this PR? - Add `DataType::ListView(child)` and `DataType::LargeListView(child)` match arms in `Fields::try_filter_leaves` (`arrow-schema/src/fields.rs`) to recursively filter their child element field, matching the behavior of `List` and `LargeList`. - Add `test_filter_list_view` covering leaf filtering and leaf index counting across nested and primitive `ListView` and `LargeListView` fields. # Are there any user-facing changes? Yes, `Fields::filter_leaves` and `Fields::try_filter_leaves` now properly descend into `DataType::ListView` and `DataType::LargeListView` child fields. --- arrow-schema/src/fields.rs | 86 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/arrow-schema/src/fields.rs b/arrow-schema/src/fields.rs index f7fa588..60e5602 100644 --- a/arrow-schema/src/fields.rs +++ b/arrow-schema/src/fields.rs @@ -177,6 +177,22 @@ impl Fields { return Ok(None); } } + ListView(child) => { + let fields = filter_field(child, filter)?; + if let Some(fields) = fields { + ListView(fields) + } else { + return Ok(None); + } + } + LargeListView(child) => { + let fields = filter_field(child, filter)?; + if let Some(fields) = fields { + LargeListView(fields) + } else { + return Ok(None); + } + } Map(child, ordered) => { let fields = filter_field(child, filter)?; if let Some(fields) = fields { @@ -797,6 +813,76 @@ mod tests { assert!(r.is_err()); } + #[test] + fn test_filter_list_view() { + let floats = Fields::from(vec![ + Field::new("a", DataType::Float32, false), + Field::new("b", DataType::Float32, false), + ]); + let fields = Fields::from(vec![ + Field::new( + "lv_struct", + DataType::ListView(Arc::new(Field::new( + "floats", + DataType::Struct(floats.clone()), + true, + ))), + true, + ), + Field::new( + "llv_struct", + DataType::LargeListView(Arc::new(Field::new( + "floats", + DataType::Struct(floats.clone()), + true, + ))), + false, + ), + Field::new( + "lv_prim", + DataType::ListView(Arc::new(Field::new_list_field(DataType::Int32, false))), + true, + ), + Field::new( + "llv_prim", + DataType::LargeListView(Arc::new(Field::new_list_field(DataType::Int64, true))), + false, + ), + ]); + + let floats_a = DataType::Struct(vec![floats[0].clone()].into()); + + // Filter by leaf field name "a" inside nested structs of ListView and LargeListView + let r = fields.filter_leaves(|_, f| f.name() == "a"); + assert_eq!(r.len(), 2); + assert_eq!( + r[0].as_ref(), + &Field::new( + "lv_struct", + DataType::ListView(Arc::new(Field::new("floats", floats_a.clone(), true))), + true, + ) + ); + assert_eq!( + r[1].as_ref(), + &Field::new( + "llv_struct", + DataType::LargeListView(Arc::new(Field::new("floats", floats_a, true))), + false, + ) + ); + + // Parent ListView/LargeListView fields are not leaves themselves + let r = fields.filter_leaves(|_, f| f.name() == "lv_struct" || f.name() == "llv_struct"); + assert_eq!(r.len(), 0); + + // Leaf indices: 0,1 (lv_struct.floats.a,b), 2,3 (llv_struct.floats.a,b), 4 (lv_prim.item), 5 (llv_prim.item) + let r = fields.filter_leaves(|idx, _| idx == 4 || idx == 5); + assert_eq!(r.len(), 2); + assert_eq!(r[0], fields[2]); + assert_eq!(r[1], fields[3]); + } + #[test] fn test_union_fields_try_new_valid() { let res = UnionFields::try_new( -- 2.55.0.windows.3