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}