Skip to main content

lance_arrow/
deepcopy.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, RecordBatch, make_array};
7use arrow_buffer::{BooleanBuffer, Buffer, NullBuffer};
8use arrow_data::{ArrayData, ArrayDataBuilder, transform::MutableArrayData};
9use arrow_schema::DataType;
10
11pub fn deep_copy_buffer(buffer: &Buffer) -> Buffer {
12    Buffer::from(buffer.as_slice())
13}
14
15pub fn deep_copy_nulls(nulls: Option<&NullBuffer>) -> Option<NullBuffer> {
16    let nulls = nulls?;
17    let bit_buffer = deep_copy_buffer(nulls.inner().inner());
18    // SAFETY: `null_count` is taken from the source `NullBuffer`, which already
19    // upheld `NullBuffer::new_unchecked`'s invariant — the unset-bit count over
20    // the logical bit slice `[bit_offset, bit_offset + bit_len)`. `NullBuffer::slice`
21    // adjusts only `BooleanBuffer::bit_offset` / `bit_len` and never byte-advances
22    // the inner `Buffer`, so `deep_copy_buffer` (which copies the source `Buffer`'s
23    // `as_slice()` view from byte 0) reproduces the exact bit pattern at the same
24    // bit offsets; the unset-bit count is therefore preserved. `BooleanBuffer::new`
25    // panics (does not UB) if `bit_offset + bit_len > 8 * buffer.len()`, and the
26    // copy has the same length, so that check still passes.
27    Some(unsafe {
28        NullBuffer::new_unchecked(
29            BooleanBuffer::new(bit_buffer, nulls.offset(), nulls.len()),
30            nulls.null_count(),
31        )
32    })
33}
34
35pub fn deep_copy_array_data(data: &ArrayData) -> ArrayData {
36    let data_type = data.data_type().clone();
37    let len = data.len();
38    let nulls = deep_copy_nulls(data.nulls());
39    let offset = data.offset();
40    let buffers = data
41        .buffers()
42        .iter()
43        .map(deep_copy_buffer)
44        .collect::<Vec<_>>();
45    let child_data = data
46        .child_data()
47        .iter()
48        .map(deep_copy_array_data)
49        .collect::<Vec<_>>();
50    // SAFETY: `build_unchecked` inherits `ArrayData::new_unchecked`'s contract —
51    // `(data_type, len, offset, nulls, buffers, child_data)` must form a valid
52    // Arrow array. This call reproduces `data` structurally: `data_type`, `len`,
53    // and `offset` are forwarded unchanged; each buffer is replaced by a byte-
54    // identical copy of its offset-applied `as_slice()` view (the output buffer
55    // is `MutableBuffer`-allocated, at least as aligned as the source); `nulls`
56    // is deep-copied with the same bit offset/length and unset-bit count (see
57    // `deep_copy_nulls`); `child_data` is recursively cloned with the same
58    // guarantee. Every value-level invariant the source upheld — UTF-8 validity,
59    // monotonic offsets, in-bounds dictionary indices, run-end monotonicity,
60    // struct child-length matching — therefore transfers to the copy. If the
61    // source `ArrayData` was itself constructed via `new_unchecked` with an
62    // invalid payload, this function faithfully reproduces that invalidity.
63    unsafe {
64        ArrayDataBuilder::new(data_type)
65            .len(len)
66            .nulls(nulls)
67            .offset(offset)
68            .buffers(buffers)
69            .child_data(child_data)
70            .build_unchecked()
71    }
72}
73
74pub fn deep_copy_array(array: &dyn Array) -> Arc<dyn Array> {
75    let data = array.to_data();
76    let data = deep_copy_array_data(&data);
77    make_array(data)
78}
79
80pub fn deep_copy_batch(batch: &RecordBatch) -> crate::Result<RecordBatch> {
81    let arrays = batch
82        .columns()
83        .iter()
84        .map(|array| deep_copy_array(array))
85        .collect::<Vec<_>>();
86    RecordBatch::try_new(batch.schema(), arrays)
87}
88
89/// Deep copy array data, extracting only the sliced portion using MutableArrayData
90/// This is the most efficient and correct way to copy just the sliced data
91pub fn deep_copy_array_data_sliced(data: &ArrayData) -> ArrayData {
92    // Use MutableArrayData to efficiently copy just the slice
93    let mut mutable = MutableArrayData::new(vec![data], false, data.len());
94
95    // Which index this takes depends on the layout, because arrow's extenders
96    // disagree about who applies `ArrayData::offset()`. The bit-packed one adds
97    // it to the raw values buffer itself, so an offset-applied index there
98    // reads from twice the offset and runs off the end of the buffer. Every
99    // other layout either reads through an already-offset-applied view or
100    // forwards the index to children carrying their own offsets, and takes the
101    // absolute index it has always been given.
102    let start = match data.data_type() {
103        DataType::Boolean => 0,
104        _ => data.offset(),
105    };
106    mutable.extend(0, start, start + data.len());
107
108    // Freeze into immutable ArrayData
109    mutable.freeze()
110}
111
112/// Deep copy an array, extracting only the sliced portion using MutableArrayData
113pub fn deep_copy_array_sliced(array: &dyn Array) -> Arc<dyn Array> {
114    let data = array.to_data();
115    let data = deep_copy_array_data_sliced(&data);
116    make_array(data)
117}
118
119/// Deep copy a RecordBatch, extracting only the sliced portion using MutableArrayData
120pub fn deep_copy_batch_sliced(batch: &RecordBatch) -> crate::Result<RecordBatch> {
121    let arrays = batch
122        .columns()
123        .iter()
124        .map(|array| deep_copy_array_sliced(array))
125        .collect::<Vec<_>>();
126    RecordBatch::try_new(batch.schema(), arrays)
127}
128
129#[cfg(test)]
130mod tests {
131    use std::sync::Arc;
132
133    use arrow_array::{Array, BooleanArray, Int32Array, RecordBatch, StringArray};
134    use arrow_data::ArrayDataBuilder;
135    use arrow_schema::{DataType, Field, Schema};
136
137    #[test]
138    fn raw_sliced_fixed_size_list_data_keeps_its_child_offset() {
139        // `ArrayData::slice` records the offset on the parent and leaves the
140        // child whole -- a shape `FixedSizeListArray::slice` never produces,
141        // but a legal one this public helper can be handed directly. Arrow's
142        // extender forwards `start * size` to that unsliced child and adds
143        // nothing, so the index it gets has to be the absolute one; this is
144        // here to stop the boolean fix from being generalised over it.
145        let child = Int32Array::from(vec![10, 11, 20, 21]).to_data();
146        let field = Arc::new(Field::new_list_field(DataType::Int32, false));
147        let data = ArrayDataBuilder::new(DataType::FixedSizeList(field, 2))
148            .len(2)
149            .add_child_data(child)
150            .build()
151            .unwrap();
152        let sliced = data.slice(1, 1);
153
154        let copied = super::deep_copy_array_data_sliced(&sliced);
155        let copied_child = Int32Array::from(copied.child_data()[0].clone());
156        assert_eq!(copied_child.values().as_ref(), &[20, 21]);
157    }
158
159    #[test]
160    fn raw_sliced_struct_of_fixed_size_list_reaches_the_right_values() {
161        // Slicing raw struct data pushes the window into the struct's children,
162        // and a fixed-size-list child takes it as its own parent offset with
163        // its values left whole. Two levels of offset, and the absolute index
164        // has to remain right through both of them.
165        let values = Int32Array::from(vec![10, 11, 20, 21, 30, 31]).to_data();
166        let item = Arc::new(Field::new_list_field(DataType::Int32, false));
167        let list = ArrayDataBuilder::new(DataType::FixedSizeList(item, 2))
168            .len(3)
169            .add_child_data(values)
170            .build()
171            .unwrap();
172        let field = Arc::new(Field::new("l", list.data_type().clone(), false));
173        let data = ArrayDataBuilder::new(DataType::Struct(vec![field].into()))
174            .len(3)
175            .add_child_data(list)
176            .build()
177            .unwrap();
178        let sliced = data.slice(2, 1);
179
180        let copied = super::deep_copy_array_data_sliced(&sliced);
181        let copied_values = Int32Array::from(copied.child_data()[0].child_data()[0].clone());
182        assert_eq!(copied_values.values().as_ref(), &[30, 31]);
183    }
184
185    #[test]
186    fn raw_parent_offset_struct_keeps_the_selected_child_row() {
187        // A struct whose children are whole and whose window is the parent
188        // offset: arrow's struct extender forwards the index to those children
189        // untouched, so it has to be the absolute one.
190        let field = Arc::new(Field::new("a", DataType::Int32, false));
191        let data = ArrayDataBuilder::new(DataType::Struct(vec![field].into()))
192            .len(1)
193            .offset(1)
194            .add_child_data(Int32Array::from(vec![10, 20]).to_data())
195            .build()
196            .unwrap();
197
198        let copied = super::deep_copy_array_data_sliced(&data);
199        let copied_child = Int32Array::from(copied.child_data()[0].clone());
200        assert_eq!(copied_child.values().as_ref(), &[20]);
201    }
202
203    #[test]
204    fn sliced_boolean_deep_copy_reads_from_the_slice() {
205        // A boolean slice keeps its `ArrayData::offset()` -- the buffer cannot
206        // advance by a fraction of a byte -- so a copy that adds the offset on
207        // top of what `MutableArrayData` already applies reads past the end of
208        // the values buffer and panics.
209        let array = BooleanArray::from(vec![true, false, true, false, true, false, true, false]);
210        for (offset, len) in [(0usize, 8usize), (1, 7), (3, 5), (7, 1)] {
211            let sliced = array.slice(offset, len);
212            let copied = super::deep_copy_array_sliced(&sliced);
213            let copied = copied.as_any().downcast_ref::<BooleanArray>().unwrap();
214            let expected: Vec<bool> = (0..len).map(|i| sliced.value(i)).collect();
215            let actual: Vec<bool> = (0..len).map(|i| copied.value(i)).collect();
216            assert_eq!(actual, expected, "offset={offset} len={len}");
217        }
218    }
219
220    #[test]
221    fn sliced_boolean_deep_copy_keeps_its_nulls() {
222        let array = BooleanArray::from(vec![
223            Some(true),
224            None,
225            Some(false),
226            Some(true),
227            None,
228            Some(false),
229        ]);
230        let sliced = array.slice(1, 4);
231        let copied = super::deep_copy_array_sliced(&sliced);
232        let copied = copied.as_any().downcast_ref::<BooleanArray>().unwrap();
233        let expected: Vec<Option<bool>> = (0..sliced.len())
234            .map(|i| (!sliced.is_null(i)).then(|| sliced.value(i)))
235            .collect();
236        let actual: Vec<Option<bool>> = (0..copied.len())
237            .map(|i| (!copied.is_null(i)).then(|| copied.value(i)))
238            .collect();
239        assert_eq!(actual, expected);
240    }
241
242    #[test]
243    fn test_deep_copy_sliced_array_with_nulls() {
244        let array = Arc::new(Int32Array::from(vec![
245            Some(1),
246            None,
247            Some(3),
248            None,
249            Some(5),
250        ]));
251        let sliced_array = array.slice(1, 3);
252        let copied_array = super::deep_copy_array(&sliced_array);
253        assert_eq!(sliced_array.len(), copied_array.len());
254        assert_eq!(sliced_array.nulls(), copied_array.nulls());
255    }
256
257    #[test]
258    fn test_deep_copy_array_data_sliced() {
259        let array = Int32Array::from((0..1000).collect::<Vec<i32>>());
260        let sliced = array.slice(100, 10);
261
262        let sliced_data = sliced.to_data();
263        let copied_data = super::deep_copy_array_data_sliced(&sliced_data);
264
265        assert_eq!(copied_data.len(), 10);
266        assert_eq!(copied_data.offset(), 0);
267
268        // Verify data correctness
269        let copied_array = Int32Array::from(copied_data);
270        for i in 0..10 {
271            assert_eq!(copied_array.value(i), 100 + i as i32);
272        }
273    }
274
275    #[test]
276    fn test_deep_copy_array_sliced() {
277        let array = Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5]));
278        let sliced = array.slice(1, 3);
279
280        let copied = super::deep_copy_array_sliced(&sliced);
281
282        assert_eq!(copied.len(), 3);
283        let copied_int = copied.as_any().downcast_ref::<Int32Array>().unwrap();
284        assert_eq!(copied_int.value(0), 2);
285        assert_eq!(copied_int.value(1), 3);
286        assert_eq!(copied_int.value(2), 4);
287    }
288
289    #[test]
290    fn test_deep_copy_batch_sliced() {
291        let schema = Arc::new(Schema::new(vec![
292            Field::new("id", DataType::Int32, false),
293            Field::new("name", DataType::Utf8, false),
294        ]));
295
296        let id_array = Arc::new(Int32Array::from((0..100).collect::<Vec<i32>>()));
297        let name_array = Arc::new(StringArray::from(
298            (0..100)
299                .map(|i| format!("name_{}", i))
300                .collect::<Vec<String>>(),
301        ));
302
303        let batch = RecordBatch::try_new(
304            schema,
305            vec![id_array as Arc<dyn Array>, name_array as Arc<dyn Array>],
306        )
307        .unwrap();
308
309        let sliced = batch.slice(10, 5);
310        let copied = super::deep_copy_batch_sliced(&sliced).unwrap();
311
312        assert_eq!(copied.num_rows(), 5);
313        assert_eq!(copied.num_columns(), 2);
314
315        // Verify data correctness
316        let id_col = copied
317            .column(0)
318            .as_any()
319            .downcast_ref::<Int32Array>()
320            .unwrap();
321        let name_col = copied
322            .column(1)
323            .as_any()
324            .downcast_ref::<StringArray>()
325            .unwrap();
326
327        for i in 0..5 {
328            assert_eq!(id_col.value(i), 10 + i as i32);
329            assert_eq!(name_col.value(i), format!("name_{}", 10 + i));
330        }
331    }
332
333    #[test]
334    fn test_deep_copy_array_sliced_with_nulls() {
335        let array = Arc::new(Int32Array::from(vec![
336            Some(1),
337            None,
338            Some(3),
339            None,
340            Some(5),
341        ]));
342        let sliced = array.slice(1, 3); // [None, Some(3), None]
343
344        let copied = super::deep_copy_array_sliced(&sliced);
345
346        assert_eq!(copied.len(), 3);
347        assert_eq!(copied.null_count(), 2); // Two nulls in the slice
348
349        let copied_int = copied.as_any().downcast_ref::<Int32Array>().unwrap();
350        assert!(!copied_int.is_valid(0)); // None
351        assert!(copied_int.is_valid(1)); // Some(3)
352        assert!(!copied_int.is_valid(2)); // None
353        assert_eq!(copied_int.value(1), 3);
354    }
355}