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 e704ad4725 optimize(interleave): implement interleave for
FixedSizeList/Map type (#10046)
e704ad4725 is described below
commit e704ad47258134e3b61bc58e590b44426e852bb9
Author: mwish <[email protected]>
AuthorDate: Tue Jul 14 16:07:34 2026 +0800
optimize(interleave): implement interleave for FixedSizeList/Map type
(#10046)
# Which issue does this PR close?
- Closes #10045 .
# Rationale for this change
Implement interleave for Map/FSL type.
Note: This might a slow implementation for FSL. For map it's a struct
type, and at would have two childs. So even if it's `Map<i32, i32>`, it
wouldn't be slower than before.
For FSL, if it's fixed sized list with short i32 list, it may hit the
problem in https://github.com/apache/arrow-rs/pull/10025 , making them
use same code to handle list is better.
# What changes are included in this PR?
Implement interleave for Map/FSL type.
# Are these changes tested?
1. Test covered by existing
2. benchmark is provided
# Are there any user-facing changes?
No
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Jeffrey Vo <[email protected]>
---
arrow-select/src/interleave.rs | 336 +++++++++++++++++++++++++++++++++++++----
1 file changed, 307 insertions(+), 29 deletions(-)
diff --git a/arrow-select/src/interleave.rs b/arrow-select/src/interleave.rs
index 9d156534c9..8f53b4bc3c 100644
--- a/arrow-select/src/interleave.rs
+++ b/arrow-select/src/interleave.rs
@@ -19,6 +19,7 @@
use crate::concat::concat;
use crate::dictionary::{merge_dictionary_values,
should_merge_dictionary_values};
+use arrow_array::ListLikeArray;
use arrow_array::builder::{BooleanBufferBuilder, PrimitiveBuilder};
use arrow_array::cast::AsArray;
use arrow_array::types::*;
@@ -110,6 +111,8 @@ pub fn interleave(
DataType::Struct(fields) => interleave_struct(fields, values, indices),
DataType::List(field) => interleave_list::<i32>(values, indices,
field),
DataType::LargeList(field) => interleave_list::<i64>(values, indices,
field),
+ DataType::FixedSizeList(field, size) =>
interleave_fixed_size_list(values, indices, field, *size),
+ DataType::Map(field, ordered) => interleave_map(values, indices,
field, *ordered),
DataType::RunEndEncoded(r, _) => match r.data_type() {
DataType::Int16 => interleave_run_end::<Int16Type>(values,
indices),
DataType::Int32 => interleave_run_end::<Int32Type>(values,
indices),
@@ -375,8 +378,8 @@ fn interleave_struct(
Ok(Arc::new(struct_array))
}
-fn interleave_list_primitive_child<O: OffsetSizeTrait, T: ArrowPrimitiveType>(
- interleaved: &Interleave<'_, GenericListArray<O>>,
+fn interleave_list_like_primitive_child<L: ListLikeArray, T:
ArrowPrimitiveType>(
+ interleaved: &Interleave<'_, L>,
indices: &[(usize, usize)],
capacity: usize,
data_type: &DataType,
@@ -392,11 +395,9 @@ fn interleave_list_primitive_child<O: OffsetSizeTrait, T:
ArrowPrimitiveType>(
// Build values buffer by copying contiguous slices
let mut values: Vec<T::Native> = Vec::with_capacity(capacity);
for &(array, row) in indices {
- let o = interleaved.arrays[array].value_offsets();
- let start = o[row].as_usize();
- let end = o[row + 1].as_usize();
- if end > start {
-
values.extend_from_slice(&child_arrays[array].values()[start..end]);
+ let range = interleaved.arrays[array].element_range(row);
+ if !range.is_empty() {
+ values.extend_from_slice(&child_arrays[array].values()[range]);
}
}
@@ -410,10 +411,8 @@ fn interleave_list_primitive_child<O: OffsetSizeTrait, T:
ArrowPrimitiveType>(
let mut offset_write = 0;
let mut output_null_count = 0usize;
for &(array, row) in indices {
- let o = interleaved.arrays[array].value_offsets();
- let start = o[row].as_usize();
- let end = o[row + 1].as_usize();
- let len = end - start;
+ let range = interleaved.arrays[array].element_range(row);
+ let len = range.len();
if len > 0 {
match child_arrays[array].nulls() {
Some(null_buffer) => {
@@ -421,7 +420,7 @@ fn interleave_list_primitive_child<O: OffsetSizeTrait, T:
ArrowPrimitiveType>(
output_null_buf.as_slice_mut(),
null_buffer.validity(),
offset_write,
- null_buffer.offset() + start,
+ null_buffer.offset() + range.start,
len,
);
}
@@ -449,6 +448,26 @@ fn interleave_list_primitive_child<O: OffsetSizeTrait, T:
ArrowPrimitiveType>(
Arc::new(PrimitiveArray::<T>::new(values.into(),
nulls).with_data_type(data_type.clone()))
}
+/// Interleave child values for non-primitive child types, shared by List and
FixedSizeList.
+fn interleave_list_like_child<L: ListLikeArray>(
+ interleaved: &Interleave<'_, L>,
+ indices: &[(usize, usize)],
+ capacity: usize,
+) -> Result<ArrayRef, ArrowError> {
+ let mut child_indices = Vec::with_capacity(capacity);
+ for &(array, row) in indices {
+ let range = interleaved.arrays[array].element_range(row);
+ child_indices.extend(range.map(|i| (array, i)));
+ }
+
+ let child_arrays: Vec<&dyn Array> = interleaved
+ .arrays
+ .iter()
+ .map(|list| list.values().as_ref())
+ .collect();
+ interleave(&child_arrays, &child_indices)
+}
+
fn interleave_list<O: OffsetSizeTrait>(
values: &[&dyn Array],
indices: &[(usize, usize)],
@@ -472,7 +491,7 @@ fn interleave_list<O: OffsetSizeTrait>(
// Step 2: build child values.
macro_rules! list_primitive_helper {
($t:ty) => {
- interleave_list_primitive_child::<O, $t>(
+ interleave_list_like_primitive_child::<GenericListArray<O>, $t>(
&interleaved,
indices,
capacity,
@@ -487,22 +506,7 @@ fn interleave_list<O: OffsetSizeTrait>(
// MutableArrayData's function pointer indirection.
field.data_type() => (list_primitive_helper),
_ => {
- // For complex child types (nested lists, structs, views,
dictionaries, etc.),
- // use recursive interleave to benefit from type-specific
optimizations.
- let mut child_indices = Vec::with_capacity(capacity);
- for (array, row) in indices {
- let list = interleaved.arrays[*array];
- let start = list.value_offsets()[*row].as_usize();
- let end = list.value_offsets()[*row + 1].as_usize();
- child_indices.extend((start..end).map(|i| (*array, i)));
- }
-
- let child_arrays: Vec<&dyn Array> = interleaved
- .arrays
- .iter()
- .map(|list| list.values().as_ref())
- .collect();
- interleave(&child_arrays, &child_indices)?
+ interleave_list_like_child(&interleaved, indices, capacity)?
}
};
@@ -513,6 +517,77 @@ fn interleave_list<O: OffsetSizeTrait>(
Ok(Arc::new(list_array))
}
+fn interleave_fixed_size_list(
+ values: &[&dyn Array],
+ indices: &[(usize, usize)],
+ field: &FieldRef,
+ size: i32,
+) -> Result<ArrayRef, ArrowError> {
+ let interleaved = Interleave::<'_, FixedSizeListArray>::new(values,
indices);
+ let capacity = indices.len() * size as usize;
+
+ macro_rules! fsl_primitive_helper {
+ ($t:ty) => {
+ interleave_list_like_primitive_child::<FixedSizeListArray, $t>(
+ &interleaved,
+ indices,
+ capacity,
+ field.data_type(),
+ )
+ };
+ }
+
+ let interleaved_values = downcast_primitive! {
+ field.data_type() => (fsl_primitive_helper),
+ _ => {
+ interleave_list_like_child(&interleaved, indices, capacity)?
+ }
+ };
+
+ let array = FixedSizeListArray::new(field.clone(), size,
interleaved_values, interleaved.nulls);
+ Ok(Arc::new(array))
+}
+
+fn interleave_map(
+ values: &[&dyn Array],
+ indices: &[(usize, usize)],
+ field: &FieldRef,
+ ordered: bool,
+) -> Result<ArrayRef, ArrowError> {
+ let interleaved = Interleave::<'_, MapArray>::new(values, indices);
+
+ let mut capacity = 0usize;
+ let mut offsets = Vec::with_capacity(indices.len() + 1);
+ offsets.push(0i32);
+ for &(array, row) in indices {
+ let o = interleaved.arrays[array].value_offsets();
+ let element_len = (o[row + 1] - o[row]) as usize;
+ capacity += element_len;
+ offsets
+ .push(i32::try_from(capacity).map_err(|_|
ArrowError::OffsetOverflowError(capacity))?);
+ }
+
+ let mut child_indices = Vec::with_capacity(capacity);
+ for &(array, row) in indices {
+ let o = interleaved.arrays[array].value_offsets();
+ let start = o[row] as usize;
+ let end = o[row + 1] as usize;
+ child_indices.extend((start..end).map(|i| (array, i)));
+ }
+
+ let entries_arrays: Vec<&dyn Array> = interleaved
+ .arrays
+ .iter()
+ .map(|m| m.entries() as &dyn Array)
+ .collect();
+ let interleaved_entries = interleave(&entries_arrays, &child_indices)?;
+
+ let offsets = OffsetBuffer::new(offsets.into());
+ let entries = interleaved_entries.as_struct().clone();
+ let array = MapArray::new(field.clone(), offsets, entries,
interleaved.nulls, ordered);
+ Ok(Arc::new(array))
+}
+
/// Specialized [`interleave`] for [`RunArray`].
fn interleave_run_end<R: RunEndIndexType>(
values: &[&dyn Array],
@@ -2040,4 +2115,207 @@ mod tests {
&[3]
);
}
+
+ #[test]
+ fn test_interleave_fixed_size_list() {
+ // a: [[1, 2], [3, 4], [5, 6]]
+ let field = Arc::new(Field::new("item", DataType::Int32, false));
+ let a = FixedSizeListArray::new(
+ field.clone(),
+ 2,
+ Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5, 6])),
+ None,
+ );
+ // b: [[7, 8], [9, 10]]
+ let b = FixedSizeListArray::new(
+ field.clone(),
+ 2,
+ Arc::new(Int32Array::from(vec![7, 8, 9, 10])),
+ None,
+ );
+
+ let result = interleave(&[&a, &b], &[(0, 2), (1, 0), (0, 0), (1, 1),
(0, 1)]).unwrap();
+ let result = result.as_fixed_size_list();
+ assert_eq!(result.len(), 5);
+ assert_eq!(result.value_length(), 2);
+
+ let values = result.values().as_primitive::<Int32Type>();
+ // [[5,6], [7,8], [1,2], [9,10], [3,4]]
+ assert_eq!(values.values(), &[5, 6, 7, 8, 1, 2, 9, 10, 3, 4]);
+ }
+
+ #[test]
+ fn test_interleave_fixed_size_list_with_nulls() {
+ let field = Arc::new(Field::new("item", DataType::Int32, true));
+ // a: [[1, 2], null, [5, 6]]
+ let a = FixedSizeListArray::new(
+ field.clone(),
+ 2,
+ Arc::new(Int32Array::from(vec![1, 2, 0, 0, 5, 6])),
+ Some(NullBuffer::from(&[true, false, true])),
+ );
+ // b: [null, [9, 10]]
+ let b = FixedSizeListArray::new(
+ field.clone(),
+ 2,
+ Arc::new(Int32Array::from(vec![0, 0, 9, 10])),
+ Some(NullBuffer::from(&[false, true])),
+ );
+
+ let result = interleave(&[&a, &b], &[(0, 0), (0, 1), (1, 0), (1, 1),
(0, 2)]).unwrap();
+ let result = result.as_fixed_size_list();
+ assert_eq!(result.len(), 5);
+
+ let validity: Vec<bool> = result.nulls().unwrap().iter().collect();
+ assert_eq!(validity, &[true, false, false, true, true]);
+ }
+
+ fn run_fsl_string_child_test(
+ child_data_type: DataType,
+ create_child: impl Fn(Vec<&str>) -> ArrayRef,
+ extract_values: impl Fn(&ArrayRef) -> Vec<String>,
+ ) {
+ let field = Arc::new(Field::new("item", child_data_type, false));
+
+ // a: [["a", "b"], ["c", "d"]]
+ let a = FixedSizeListArray::new(
+ field.clone(),
+ 2,
+ create_child(vec!["a", "b", "c", "d"]),
+ None,
+ );
+ // b: [["x", "y"], ["z", "w"]]
+ let b = FixedSizeListArray::new(
+ field.clone(),
+ 2,
+ create_child(vec!["x", "y", "z", "w"]),
+ None,
+ );
+
+ let result = interleave(&[&a, &b], &[(0, 1), (1, 0), (0, 0)]).unwrap();
+ let result = result.as_fixed_size_list();
+ assert_eq!(result.len(), 3);
+ assert_eq!(result.value_length(), 2);
+
+ // Expected: [[c,d], [x,y], [a,b]]
+ let values = extract_values(result.values());
+ assert_eq!(values, vec!["c", "d", "x", "y", "a", "b"]);
+ }
+
+ #[test]
+ fn test_interleave_fixed_size_list_string_child() {
+ // FixedSizeList<Utf8> — exercises the non-primitive child path
+ run_fsl_string_child_test(
+ DataType::Utf8,
+ |v| Arc::new(StringArray::from(v)),
+ |a| {
+ a.as_string::<i32>()
+ .iter()
+ .map(|s| s.unwrap().to_owned())
+ .collect()
+ },
+ );
+ }
+
+ #[test]
+ fn test_interleave_fixed_size_list_string_view_child() {
+ // FixedSizeList<Utf8View> — exercises the non-primitive child path
+ run_fsl_string_child_test(
+ DataType::Utf8View,
+ |v| Arc::new(StringViewArray::from(v)),
+ |a| {
+ a.as_string_view()
+ .iter()
+ .map(|s| s.unwrap().to_owned())
+ .collect()
+ },
+ );
+ }
+
+ #[test]
+ fn test_interleave_map() {
+ use arrow_array::builder::MapBuilder;
+ use arrow_array::builder::StringBuilder;
+
+ // a: [{k1: 1, k2: 2}, {k3: 3}]
+ let mut a_builder = MapBuilder::new(None, StringBuilder::new(),
Int32Builder::new());
+ a_builder.keys().append_value("k1");
+ a_builder.values().append_value(1);
+ a_builder.keys().append_value("k2");
+ a_builder.values().append_value(2);
+ a_builder.append(true).unwrap();
+ a_builder.keys().append_value("k3");
+ a_builder.values().append_value(3);
+ a_builder.append(true).unwrap();
+ let a = a_builder.finish();
+
+ // b: [{k4: 4}, {k5: 5, k6: 6, k7: 7}]
+ let mut b_builder = MapBuilder::new(None, StringBuilder::new(),
Int32Builder::new());
+ b_builder.keys().append_value("k4");
+ b_builder.values().append_value(4);
+ b_builder.append(true).unwrap();
+ b_builder.keys().append_value("k5");
+ b_builder.values().append_value(5);
+ b_builder.keys().append_value("k6");
+ b_builder.values().append_value(6);
+ b_builder.keys().append_value("k7");
+ b_builder.values().append_value(7);
+ b_builder.append(true).unwrap();
+ let b = b_builder.finish();
+
+ let result = interleave(&[&a, &b], &[(1, 0), (0, 0), (0, 1), (1,
1)]).unwrap();
+ let result = result.as_map();
+ assert_eq!(result.len(), 4);
+
+ // Row 0: {k4: 4}
+ let row0 = result.value(0);
+ assert_eq!(row0.len(), 1);
+ assert_eq!(row0.column(0).as_string::<i32>().value(0), "k4");
+ assert_eq!(row0.column(1).as_primitive::<Int32Type>().value(0), 4);
+
+ // Row 1: {k1: 1, k2: 2}
+ let row1 = result.value(1);
+ assert_eq!(row1.len(), 2);
+
+ // Row 2: {k3: 3}
+ let row2 = result.value(2);
+ assert_eq!(row2.len(), 1);
+ assert_eq!(row2.column(0).as_string::<i32>().value(0), "k3");
+
+ // Row 3: {k5: 5, k6: 6, k7: 7}
+ let row3 = result.value(3);
+ assert_eq!(row3.len(), 3);
+ }
+
+ #[test]
+ fn test_interleave_map_with_nulls() {
+ use arrow_array::builder::MapBuilder;
+ use arrow_array::builder::StringBuilder;
+
+ // a: [{k1: 1}, null]
+ let mut a_builder = MapBuilder::new(None, StringBuilder::new(),
Int32Builder::new());
+ a_builder.keys().append_value("k1");
+ a_builder.values().append_value(1);
+ a_builder.append(true).unwrap();
+ a_builder.append(false).unwrap();
+ let a = a_builder.finish();
+
+ // b: [null, {k2: 2}]
+ let mut b_builder = MapBuilder::new(None, StringBuilder::new(),
Int32Builder::new());
+ b_builder.append(false).unwrap();
+ b_builder.keys().append_value("k2");
+ b_builder.values().append_value(2);
+ b_builder.append(true).unwrap();
+ let b = b_builder.finish();
+
+ let result = interleave(&[&a, &b], &[(0, 0), (1, 0), (0, 1), (1,
1)]).unwrap();
+ let result = result.as_map();
+ assert_eq!(result.len(), 4);
+
+ let validity: Vec<bool> = result.nulls().unwrap().iter().collect();
+ assert_eq!(validity, &[true, false, false, true]);
+
+ assert_eq!(result.value(0).len(), 1);
+ assert_eq!(result.value(3).len(), 1);
+ }
}