Skip to main content

arrow_data/
ffi.rs

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