Skip to main content

arrow_data/
ffi.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! Contains declarations to bind to the [C Data Interface](https://arrow.apache.org/docs/format/CDataInterface.html).
6
7use crate::bit_mask::set_bits;
8use crate::{ArrayData, layout};
9use arrow_buffer::buffer::NullBuffer;
10use arrow_buffer::{Buffer, MutableBuffer, ScalarBuffer};
11use arrow_schema::DataType;
12use std::ffi::c_void;
13
14/// ABI-compatible struct for ArrowArray from C Data Interface
15/// See <https://arrow.apache.org/docs/format/CDataInterface.html#the-arrowarray-structure>
16///
17/// ```
18/// # use arrow_data::ArrayData;
19/// # use arrow_data::ffi::FFI_ArrowArray;
20/// fn export_array(array: &ArrayData) -> FFI_ArrowArray {
21///     FFI_ArrowArray::new(array)
22/// }
23/// ```
24#[repr(C)]
25#[derive(Debug)]
26pub struct FFI_ArrowArray {
27    // Fields are intentionally private so safety guarantees can be upheld via
28    // explicit unsafe functions.
29    /// Logical length of the array
30    length: i64,
31    /// Number of null items in the array
32    null_count: i64,
33    /// logical offset inside the array
34    offset: i64,
35    /// Number of physical buffers backing this array
36    n_buffers: i64,
37    /// Number of children this array has
38    n_children: i64,
39    /// C array of pointers to the start of each physical buffer backing this array
40    buffers: *mut *const c_void,
41    /// C array of pointers to each child array of this array
42    children: *mut *mut FFI_ArrowArray,
43    /// Pointer to the underlying array of dictionary values
44    dictionary: *mut FFI_ArrowArray,
45    /// Producer-provided release callback.
46    release: Option<unsafe extern "C" fn(arg1: *mut FFI_ArrowArray)>,
47    /// Opaque pointer to producer-provided private data
48    /// When exported, this MUST contain everything that is owned by this array.
49    /// For example, any buffer pointed to in `buffers` must be here, as well
50    /// as the `buffers` pointer itself.
51    /// In other words, everything in [FFI_ArrowArray] must be owned by
52    /// `private_data` and can assume that they do not outlive `private_data`.
53    private_data: *mut c_void,
54}
55
56impl Drop for FFI_ArrowArray {
57    fn drop(&mut self) {
58        match self.release {
59            None => (),
60            Some(release) => unsafe { release(self) },
61        }
62    }
63}
64
65unsafe impl Send for FFI_ArrowArray {}
66unsafe impl Sync for FFI_ArrowArray {}
67
68// callback used to drop [FFI_ArrowArray] when it is exported
69unsafe extern "C" fn release_array(array: *mut FFI_ArrowArray) {
70    if array.is_null() {
71        return;
72    }
73    let array = unsafe { &mut *array };
74
75    // take ownership of `private_data`, therefore dropping it`
76    let private = unsafe { Box::from_raw(array.private_data.cast::<ArrayPrivateData>()) };
77    for child in &private.children {
78        let _ = unsafe { Box::from_raw(*child) };
79    }
80    if !private.dictionary.is_null() {
81        let _ = unsafe { Box::from_raw(private.dictionary) };
82    }
83
84    array.release = None;
85}
86
87/// Aligns the provided `nulls` to the provided `data_offset`
88///
89/// This is a temporary measure until offset is removed from ArrayData (#1799)
90fn align_nulls(data_offset: usize, nulls: Option<&NullBuffer>) -> Option<Buffer> {
91    let nulls = nulls?;
92    if data_offset == nulls.offset() {
93        // Underlying buffer is already aligned
94        return Some(nulls.buffer().clone());
95    }
96    if data_offset == 0 {
97        return Some(nulls.inner().sliced());
98    }
99    let mut builder = MutableBuffer::new_null(data_offset + nulls.len());
100    set_bits(
101        builder.as_slice_mut(),
102        nulls.validity(),
103        data_offset,
104        nulls.offset(),
105        nulls.len(),
106    );
107    Some(builder.into())
108}
109
110struct ArrayPrivateData {
111    #[expect(dead_code)]
112    buffers: Vec<Option<Buffer>>,
113    buffers_ptr: Box<[*const c_void]>,
114    children: Box<[*mut FFI_ArrowArray]>,
115    dictionary: *mut FFI_ArrowArray,
116}
117
118impl FFI_ArrowArray {
119    /// creates a new `FFI_ArrowArray` from existing data.
120    pub fn new(data: &ArrayData) -> Self {
121        let data_layout = layout(data.data_type());
122
123        let mut buffers = if data_layout.can_contain_null_mask {
124            // * insert the null buffer at the start
125            // * make all others `Option<Buffer>`.
126            std::iter::once(align_nulls(data.offset(), data.nulls()))
127                .chain(data.buffers().iter().map(|b| Some(b.clone())))
128                .collect::<Vec<_>>()
129        } else {
130            data.buffers().iter().map(|b| Some(b.clone())).collect()
131        };
132
133        // `n_buffers` is the number of buffers by the spec.
134        let mut n_buffers = {
135            data_layout.buffers.len() + {
136                // If the layout has a null buffer by Arrow spec.
137                // Note that even the array doesn't have a null buffer because it has
138                // no null value, we still need to count 1 here to follow the spec.
139                usize::from(data_layout.can_contain_null_mask)
140            }
141        } as i64;
142
143        if data_layout.variadic {
144            // Save the lengths of all variadic buffers into a new buffer.
145            // The first buffer is `views`, and the rest are variadic.
146            let mut data_buffers_lengths = Vec::new();
147            for buffer in data.buffers().iter().skip(1) {
148                data_buffers_lengths.push(buffer.len() as i64);
149                n_buffers += 1;
150            }
151
152            buffers.push(Some(ScalarBuffer::from(data_buffers_lengths).into_inner()));
153            n_buffers += 1;
154        }
155
156        let buffers_ptr = buffers
157            .iter()
158            .filter_map(|maybe_buffer| match maybe_buffer {
159                Some(b) => Some(b.as_ptr().cast::<c_void>()),
160                // This is for null buffer. We only put a null pointer for
161                // null buffer if by spec it can contain null mask.
162                None if data_layout.can_contain_null_mask => Some(std::ptr::null()),
163                None => None,
164            })
165            .collect::<Box<[_]>>();
166
167        let empty = vec![];
168        let (child_data, dictionary) = match data.data_type() {
169            DataType::Dictionary(_, _) => (
170                empty.as_slice(),
171                Box::into_raw(Box::new(FFI_ArrowArray::new(&data.child_data()[0]))),
172            ),
173            _ => (data.child_data(), std::ptr::null_mut()),
174        };
175
176        let children = child_data
177            .iter()
178            .map(|child| Box::into_raw(Box::new(FFI_ArrowArray::new(child))))
179            .collect::<Box<_>>();
180        let n_children = children.len() as i64;
181
182        // As in the IPC format, emit null_count = length for Null type
183        let null_count = match data.data_type() {
184            DataType::Null => data.len(),
185            _ => data.null_count(),
186        };
187
188        // create the private data owning everything.
189        // any other data must be added here, e.g. via a struct, to track lifetime.
190        let mut private_data = Box::new(ArrayPrivateData {
191            buffers,
192            buffers_ptr,
193            children,
194            dictionary,
195        });
196
197        Self {
198            length: data.len() as i64,
199            null_count: null_count as i64,
200            offset: data.offset() as i64,
201            n_buffers,
202            n_children,
203            buffers: private_data.buffers_ptr.as_mut_ptr(),
204            children: private_data.children.as_mut_ptr(),
205            dictionary,
206            release: Some(release_array),
207            private_data: Box::into_raw(private_data).cast::<c_void>(),
208        }
209    }
210
211    /// Takes ownership of the pointed to [`FFI_ArrowArray`]
212    ///
213    /// This acts to [move] the data out of `array`, setting the release callback to NULL
214    ///
215    /// # Safety
216    ///
217    /// * `array` must be [valid] for reads and writes
218    /// * `array` must be properly aligned
219    /// * `array` must point to a properly initialized value of [`FFI_ArrowArray`]
220    ///
221    /// [move]: https://arrow.apache.org/docs/format/CDataInterface.html#moving-an-array
222    /// [valid]: https://doc.rust-lang.org/std/ptr/index.html#safety
223    pub unsafe fn from_raw(array: *mut FFI_ArrowArray) -> Self {
224        unsafe { std::ptr::replace(array, Self::empty()) }
225    }
226
227    /// create an empty `FFI_ArrowArray`, which can be used to import data into
228    pub fn empty() -> Self {
229        Self {
230            length: 0,
231            null_count: 0,
232            offset: 0,
233            n_buffers: 0,
234            n_children: 0,
235            buffers: std::ptr::null_mut(),
236            children: std::ptr::null_mut(),
237            dictionary: std::ptr::null_mut(),
238            release: None,
239            private_data: std::ptr::null_mut(),
240        }
241    }
242
243    /// Returns the producer-provided release callback, if any.
244    pub fn release(&self) -> Option<unsafe extern "C" fn(arg1: *mut FFI_ArrowArray)> {
245        self.release
246    }
247
248    /// Returns the opaque producer-provided private data pointer.
249    pub fn private_data(&self) -> *mut c_void {
250        self.private_data
251    }
252
253    /// Replaces the release callback, returning the previous one.
254    ///
255    /// Lets a consumer wrap release: save the old callback, install its own, and
256    /// chain back on drop. See <https://github.com/apache/arrow-rs/issues/9771>.
257    ///
258    /// # Safety
259    ///
260    /// [`Drop`] calls this callback with a pointer to `self`. The new callback
261    /// must correctly release this array (usually by chaining to the returned
262    /// one) and must match the [`FFI_ArrowArray::private_data`] it reads. A
263    /// wrong callback is undefined behavior on drop.
264    pub unsafe fn set_release(
265        &mut self,
266        release: Option<unsafe extern "C" fn(arg1: *mut FFI_ArrowArray)>,
267    ) -> Option<unsafe extern "C" fn(arg1: *mut FFI_ArrowArray)> {
268        std::mem::replace(&mut self.release, release)
269    }
270
271    /// Replaces the private data pointer, returning the previous one.
272    ///
273    /// # Safety
274    ///
275    /// The old pointer is returned without being freed; the caller owns it from
276    /// here. The new pointer must match what the current
277    /// [`FFI_ArrowArray::release`] callback expects.
278    pub unsafe fn set_private_data(&mut self, private_data: *mut c_void) -> *mut c_void {
279        std::mem::replace(&mut self.private_data, private_data)
280    }
281
282    /// the length of the array
283    #[inline]
284    pub fn len(&self) -> usize {
285        self.length as usize
286    }
287
288    /// whether the array is empty
289    #[inline]
290    pub fn is_empty(&self) -> bool {
291        self.length == 0
292    }
293
294    /// Whether the array has been released
295    #[inline]
296    pub fn is_released(&self) -> bool {
297        self.release.is_none()
298    }
299
300    /// the offset of the array
301    #[inline]
302    pub fn offset(&self) -> usize {
303        self.offset as usize
304    }
305
306    /// the null count of the array
307    #[inline]
308    pub fn null_count(&self) -> usize {
309        self.null_count as usize
310    }
311
312    /// Returns the null count, checking for validity
313    #[inline]
314    pub fn null_count_opt(&self) -> Option<usize> {
315        usize::try_from(self.null_count).ok()
316    }
317
318    /// Set the null count of the array
319    ///
320    /// # Safety
321    /// Null count must match that of null buffer
322    #[inline]
323    pub unsafe fn set_null_count(&mut self, null_count: i64) {
324        self.null_count = null_count;
325    }
326
327    /// Returns the buffer at the provided index
328    ///
329    /// # Panics
330    /// Panics if index >= self.num_buffers() or the buffer is not correctly aligned
331    #[inline]
332    pub fn buffer(&self, index: usize) -> *const u8 {
333        assert!(!self.buffers.is_null());
334        assert!(index < self.num_buffers());
335        // SAFETY:
336        // If buffers is not null must be valid for reads up to num_buffers
337        unsafe { std::ptr::read_unaligned(self.buffers.cast::<*const u8>().add(index)) }
338    }
339
340    /// Returns the number of buffers
341    #[inline]
342    pub fn num_buffers(&self) -> usize {
343        self.n_buffers as _
344    }
345
346    /// Returns the child at the provided index
347    #[inline]
348    pub fn child(&self, index: usize) -> &FFI_ArrowArray {
349        assert!(!self.children.is_null());
350        assert!(index < self.num_children());
351        // Safety:
352        // If children is not null must be valid for reads up to num_children
353        unsafe {
354            let child = std::ptr::read_unaligned(self.children.add(index));
355            child.as_ref().unwrap()
356        }
357    }
358
359    /// Returns the number of children
360    #[inline]
361    pub fn num_children(&self) -> usize {
362        self.n_children as _
363    }
364
365    /// Returns the dictionary if any
366    #[inline]
367    pub fn dictionary(&self) -> Option<&Self> {
368        // Safety:
369        // If dictionary is not null should be valid for reads of `Self`
370        unsafe { self.dictionary.as_ref() }
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use super::*;
377
378    // More tests located in top-level arrow crate
379
380    #[test]
381    fn null_array_n_buffers() {
382        let data = ArrayData::new_null(&DataType::Null, 10);
383
384        let ffi_array = FFI_ArrowArray::new(&data);
385        assert_eq!(0, ffi_array.n_buffers);
386
387        let private_data =
388            unsafe { Box::from_raw(ffi_array.private_data.cast::<ArrayPrivateData>()) };
389
390        assert_eq!(0, private_data.buffers_ptr.len());
391
392        let _ = Box::into_raw(private_data);
393    }
394}