Skip to main content

lance_arrow/
list.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use std::sync::Arc;
5
6use arrow_array::{Array, BooleanArray, GenericListArray, OffsetSizeTrait};
7use arrow_buffer::{BooleanBufferBuilder, OffsetBuffer, ScalarBuffer};
8use arrow_schema::Field;
9
10pub trait ListArrayExt {
11    /// Filters out masked null items from the list array
12    ///
13    /// It is legal for a list array to have a null entry with a non-zero length.  The
14    /// values inside the entry are "garbage" and should be ignored.  This function
15    /// filters the values array to remove the garbage values.
16    ///
17    /// The output list will always have zero-length nulls.
18    fn filter_garbage_nulls(&self) -> Self;
19    /// Returns a copy of the list's values array that has been sliced to size
20    ///
21    /// It is legal for a list array's offsets to not start with zero.  It's also legal
22    /// for a list array's offsets to not extend to the entire values array.  This function
23    /// behaves similarly to `values()` except it slices the array so that it starts at
24    /// the first list offset and ends at the last list offset.
25    fn trimmed_values(&self) -> Arc<dyn Array>;
26    /// The offset type of the underlying list array.
27    type Offset: OffsetSizeTrait;
28    /// Returns offsets shifted so the first offset is zero, matching
29    /// [`Self::trimmed_values`].
30    ///
31    /// Sliced list arrays (e.g. a filtered batch) keep offsets that reference the
32    /// original values buffer, so combining them with trimmed values produces
33    /// offsets that exceed the values length. Use this together with
34    /// `trimmed_values` when constructing a new list array.
35    fn trimmed_offsets(&self) -> OffsetBuffer<Self::Offset>;
36}
37
38impl<OffsetSize: OffsetSizeTrait> ListArrayExt for GenericListArray<OffsetSize> {
39    fn filter_garbage_nulls(&self) -> Self {
40        if self.is_empty() {
41            return self.clone();
42        }
43        let Some(validity) = self.nulls().cloned() else {
44            return self.clone();
45        };
46
47        let mut should_keep = BooleanBufferBuilder::new(self.values().len());
48
49        // Handle case where offsets do not start at 0
50        let preamble_len = self.offsets().first().unwrap().to_usize().unwrap();
51        should_keep.append_n(preamble_len, false);
52
53        let mut new_offsets: Vec<OffsetSize> = Vec::with_capacity(self.len() + 1);
54        new_offsets.push(OffsetSize::zero());
55        let mut cur_len = OffsetSize::zero();
56        for (offset, is_valid) in self.offsets().windows(2).zip(validity.iter()) {
57            let len = offset[1] - offset[0];
58            if is_valid {
59                cur_len += len;
60                should_keep.append_n(len.to_usize().unwrap(), true);
61                new_offsets.push(cur_len);
62            } else {
63                should_keep.append_n(len.to_usize().unwrap(), false);
64                new_offsets.push(cur_len);
65            }
66        }
67
68        // Offsets may not reference entire values buffer
69        let trailer = self.values().len() - should_keep.len();
70        should_keep.append_n(trailer, false);
71
72        let should_keep = should_keep.finish();
73        let should_keep = BooleanArray::new(should_keep, None);
74        let new_values = arrow_select::filter::filter(self.values(), &should_keep).unwrap();
75        let new_offsets = ScalarBuffer::from(new_offsets);
76        let new_offsets = OffsetBuffer::new(new_offsets);
77
78        Self::new(
79            Arc::new(Field::new(
80                "item",
81                self.value_type(),
82                self.values().is_nullable(),
83            )),
84            new_offsets,
85            new_values,
86            Some(validity),
87        )
88    }
89
90    fn trimmed_values(&self) -> Arc<dyn Array> {
91        let first_value = self
92            .offsets()
93            .first()
94            .map(|v| v.to_usize().unwrap())
95            .unwrap_or(0);
96        let last_value = self
97            .offsets()
98            .last()
99            .map(|v| v.to_usize().unwrap())
100            .unwrap_or(0);
101        self.values().slice(first_value, last_value - first_value)
102    }
103
104    type Offset = OffsetSize;
105
106    fn trimmed_offsets(&self) -> OffsetBuffer<OffsetSize> {
107        let offsets = self.offsets();
108        let Some(&first) = offsets.first() else {
109            return offsets.clone();
110        };
111        if first == OffsetSize::zero() {
112            return offsets.clone();
113        }
114        let shifted: Vec<OffsetSize> = offsets.iter().map(|&o| o - first).collect();
115        OffsetBuffer::new(ScalarBuffer::from(shifted))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use std::sync::Arc;
122
123    use arrow_array::{ListArray, UInt64Array};
124    use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
125    use arrow_schema::{DataType, Field};
126
127    use super::ListArrayExt;
128
129    #[test]
130    fn test_filter_garbage_nulls() {
131        let items = UInt64Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
132        let offsets = ScalarBuffer::<i32>::from(vec![2, 5, 8, 9]);
133        let offsets = OffsetBuffer::new(offsets);
134        let list_validity = NullBuffer::new(BooleanBuffer::from(vec![true, false, true]));
135        let list_arr = ListArray::new(
136            Arc::new(Field::new("item", DataType::UInt64, true)),
137            offsets,
138            Arc::new(items),
139            Some(list_validity.clone()),
140        );
141
142        let filtered = list_arr.filter_garbage_nulls();
143
144        let expected_items = UInt64Array::from(vec![2, 3, 4, 8]);
145        let offsets = ScalarBuffer::<i32>::from(vec![0, 3, 3, 4]);
146        let expected = ListArray::new(
147            Arc::new(Field::new("item", DataType::UInt64, false)),
148            OffsetBuffer::new(offsets),
149            Arc::new(expected_items),
150            Some(list_validity),
151        );
152
153        assert_eq!(filtered, expected);
154    }
155
156    #[test]
157    fn test_trim_values() {
158        let items = UInt64Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
159        let offsets = ScalarBuffer::<i32>::from(vec![2, 5, 6, 8, 9]);
160        let offsets = OffsetBuffer::new(offsets);
161        let list_arr = ListArray::new(
162            Arc::new(Field::new("item", DataType::UInt64, true)),
163            offsets,
164            Arc::new(items),
165            None,
166        );
167        let list_arr = list_arr.slice(1, 2);
168
169        let trimmed = list_arr.trimmed_values();
170
171        let expected_items = UInt64Array::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
172        let expected_items = expected_items.slice(5, 3);
173
174        assert_eq!(trimmed.as_ref(), &expected_items);
175    }
176}