Skip to main content

arrow_array/
ffi_stream.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 Stream Interface](https://arrow.apache.org/docs/format/CStreamInterface.html).
19//!
20//! This module has two main interfaces:
21//! One interface maps C ABI to native Rust types, i.e. convert c-pointers, c_char, to native rust.
22//! This is handled by [FFI_ArrowArrayStream].
23//!
24//! The second interface is used to import `FFI_ArrowArrayStream` as a Rust reader. A stream
25//! whose top-level type is `Struct` can be read as `RecordBatch` by [`RecordBatchStreamReader`];
26//! a stream of any type can be read as `ArrayRef` by [`ArrayStreamReader`].
27//!
28//! ```ignore
29//! # use std::fs::File;
30//! # use std::sync::Arc;
31//! # use arrow::error::Result;
32//! # use arrow::ffi_stream::{export_reader_into_raw, RecordBatchStreamReader, FFI_ArrowArrayStream};
33//! # use arrow::ipc::reader::FileReader;
34//! # use arrow::record_batch::RecordBatchReader;
35//! # fn main() -> Result<()> {
36//! // create an record batch reader natively
37//! let file = File::open("arrow_file").unwrap();
38//! let reader = Box::new(FileReader::try_new(file).unwrap());
39//!
40//! // export it
41//! let mut stream = FFI_ArrowArrayStream::empty();
42//! unsafe { export_reader_into_raw(reader, &mut stream) };
43//!
44//! // consumed and used by something else...
45//!
46//! // import it
47//! let stream_reader = unsafe { RecordBatchStreamReader::from_raw(&mut stream).unwrap() };
48//! let imported_schema = stream_reader.schema();
49//!
50//! let mut produced_batches = vec![];
51//! for batch in stream_reader {
52//!      produced_batches.push(batch.unwrap());
53//! }
54//! Ok(())
55//! }
56//! ```
57
58use arrow_schema::DataType;
59use std::ffi::CStr;
60use std::ptr::addr_of;
61use std::{
62    ffi::CString,
63    os::raw::{c_char, c_int, c_void},
64    sync::Arc,
65};
66
67use arrow_data::ffi::FFI_ArrowArray;
68use arrow_schema::{ArrowError, Field, FieldRef, Schema, SchemaRef, ffi::FFI_ArrowSchema};
69
70use crate::RecordBatchOptions;
71use crate::array::Array;
72use crate::array::StructArray;
73use crate::array::{ArrayRef, make_array};
74use crate::array_reader::ArrayReader;
75use crate::ffi::from_ffi_and_data_type;
76use crate::record_batch::{RecordBatch, RecordBatchReader};
77
78type Result<T> = std::result::Result<T, ArrowError>;
79
80// Errno values returned through the C stream interface, taken from libc so they match
81// the platform the consumer interprets them against.
82#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
83use libc::{EINVAL, EIO, ENOMEM, ENOSYS};
84
85// wasm32-unknown-unknown has no libc, and no OS to interpret the codes either — any
86// non-zero value works there, so use Linux's.
87#[cfg(all(target_family = "wasm", target_os = "unknown"))]
88const ENOMEM: i32 = 12;
89#[cfg(all(target_family = "wasm", target_os = "unknown"))]
90const EIO: i32 = 5;
91#[cfg(all(target_family = "wasm", target_os = "unknown"))]
92const EINVAL: i32 = 22;
93#[cfg(all(target_family = "wasm", target_os = "unknown"))]
94const ENOSYS: i32 = 38;
95
96/// ABI-compatible struct for `ArrayStream` from C Stream Interface
97/// See <https://arrow.apache.org/docs/format/CStreamInterface.html#structure-definitions>
98/// This was created by bindgen
99#[repr(C)]
100#[derive(Debug)]
101pub struct FFI_ArrowArrayStream {
102    // Fields are intentionally private so safety guarantees can be upheld via
103    // explicit unsafe functions.
104    /// C function to get schema from the stream
105    get_schema: Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowSchema) -> c_int>,
106    /// C function to get next array from the stream
107    get_next: Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowArray) -> c_int>,
108    /// C function to get the error from last operation on the stream
109    get_last_error: Option<unsafe extern "C" fn(arg1: *mut Self) -> *const c_char>,
110    /// C function to release the stream
111    release: Option<unsafe extern "C" fn(arg1: *mut Self)>,
112    /// Private data used by the stream, owned by the release callback.
113    private_data: *mut c_void,
114}
115
116unsafe impl Send for FFI_ArrowArrayStream {}
117
118// callback used to drop [FFI_ArrowArrayStream] when it is exported.
119unsafe extern "C" fn release_stream(stream: *mut FFI_ArrowArrayStream) {
120    if stream.is_null() {
121        return;
122    }
123    let stream = unsafe { &mut *stream };
124
125    stream.get_schema = None;
126    stream.get_next = None;
127    stream.get_last_error = None;
128
129    let private_data = unsafe { Box::from_raw(stream.private_data.cast::<StreamPrivateData>()) };
130    drop(private_data);
131
132    stream.release = None;
133}
134
135struct StreamPrivateData {
136    batch_reader: Box<dyn RecordBatchReader + Send>,
137    last_error: Option<CString>,
138}
139
140// The callback used to get array schema
141unsafe extern "C" fn get_schema(
142    stream: *mut FFI_ArrowArrayStream,
143    schema: *mut FFI_ArrowSchema,
144) -> c_int {
145    ExportedArrayStream { stream }.get_schema(schema)
146}
147
148// The callback used to get next array
149unsafe extern "C" fn get_next(
150    stream: *mut FFI_ArrowArrayStream,
151    array: *mut FFI_ArrowArray,
152) -> c_int {
153    ExportedArrayStream { stream }.get_next(array)
154}
155
156// The callback used to get the error from last operation on the `FFI_ArrowArrayStream`
157unsafe extern "C" fn get_last_error(stream: *mut FFI_ArrowArrayStream) -> *const c_char {
158    let mut ffi_stream = ExportedArrayStream { stream };
159    // The consumer should not take ownership of this string, we should return
160    // a const pointer to it.
161    match ffi_stream.get_last_error() {
162        Some(err_string) => err_string.as_ptr(),
163        None => std::ptr::null(),
164    }
165}
166
167impl Drop for FFI_ArrowArrayStream {
168    fn drop(&mut self) {
169        match self.release {
170            None => (),
171            Some(release) => unsafe { release(self) },
172        }
173    }
174}
175
176impl FFI_ArrowArrayStream {
177    /// Creates a new [`FFI_ArrowArrayStream`].
178    pub fn new(batch_reader: Box<dyn RecordBatchReader + Send>) -> Self {
179        let private_data = Box::new(StreamPrivateData {
180            batch_reader,
181            last_error: None,
182        });
183
184        Self {
185            get_schema: Some(get_schema),
186            get_next: Some(get_next),
187            get_last_error: Some(get_last_error),
188            release: Some(release_stream),
189            private_data: Box::into_raw(private_data).cast::<c_void>(),
190        }
191    }
192
193    /// Creates a new [`FFI_ArrowArrayStream`] from its raw parts.
194    ///
195    /// # Safety
196    ///
197    /// The caller takes responsibility for the [C stream interface] contract:
198    ///
199    /// * Each callback that is `Some` must be sound to invoke with a pointer to
200    ///   this struct, and must implement the semantics the C stream interface
201    ///   specifies for it.
202    /// * `private_data` must own everything the callbacks rely on, and must
203    ///   remain valid until `release` is called.
204    /// * `release`, if `Some`, is invoked by [`Drop`] with a pointer to this
205    ///   struct. It must free `private_data` exactly once and then set the
206    ///   release callback to `None` to mark the stream released.
207    /// * If `release` is `None` the stream is already released, so dropping it
208    ///   must not leak: nothing may be left for the callback to free.
209    ///
210    /// [C stream interface]: https://arrow.apache.org/docs/format/CStreamInterface.html
211    pub unsafe fn new_unchecked(
212        get_schema: Option<
213            unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowSchema) -> c_int,
214        >,
215        get_next: Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowArray) -> c_int>,
216        get_last_error: Option<unsafe extern "C" fn(arg1: *mut Self) -> *const c_char>,
217        release: Option<unsafe extern "C" fn(arg1: *mut Self)>,
218        private_data: *mut c_void,
219    ) -> Self {
220        Self {
221            get_schema,
222            get_next,
223            get_last_error,
224            release,
225            private_data,
226        }
227    }
228
229    /// Takes ownership of the pointed to [`FFI_ArrowArrayStream`]
230    ///
231    /// This acts to [move] the data out of `raw_stream`, setting the release callback to NULL
232    ///
233    /// # Safety
234    ///
235    /// * `raw_stream` must be [valid] for reads and writes
236    /// * `raw_stream` must be properly aligned
237    /// * `raw_stream` must point to a properly initialized value of [`FFI_ArrowArrayStream`]
238    ///
239    /// [move]: https://arrow.apache.org/docs/format/CDataInterface.html#moving-an-array
240    /// [valid]: https://doc.rust-lang.org/std/ptr/index.html#safety
241    pub unsafe fn from_raw(raw_stream: *mut FFI_ArrowArrayStream) -> Self {
242        unsafe { std::ptr::replace(raw_stream, Self::empty()) }
243    }
244
245    /// Creates a new empty [FFI_ArrowArrayStream]. Used to import from the C Stream Interface.
246    pub fn empty() -> Self {
247        Self {
248            get_schema: None,
249            get_next: None,
250            get_last_error: None,
251            release: None,
252            private_data: std::ptr::null_mut(),
253        }
254    }
255
256    #[expect(clippy::unnecessary_safety_doc)]
257    /// Returns the producer-provided callback that writes this stream's schema, if any.
258    ///
259    /// # Safety
260    ///
261    /// The callback must be invoked with a pointer to the stream it was read from.
262    pub fn get_schema(
263        &self,
264    ) -> Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowSchema) -> c_int> {
265        self.get_schema
266    }
267
268    #[expect(clippy::unnecessary_safety_doc)]
269    /// Returns the producer-provided callback that yields the next array, if any.
270    ///
271    /// # Safety
272    ///
273    /// The callback must be invoked with a pointer to the stream it was read from.
274    pub fn get_next(
275        &self,
276    ) -> Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowArray) -> c_int> {
277        self.get_next
278    }
279
280    #[expect(clippy::unnecessary_safety_doc)]
281    /// Returns the producer-provided callback that describes the last error, if any.
282    ///
283    /// # Safety
284    ///
285    /// The callback must be invoked with a pointer to the stream it was read from.
286    /// The string it returns is owned by the producer and valid only until the next
287    /// call on the stream.
288    pub fn get_last_error(&self) -> Option<unsafe extern "C" fn(arg1: *mut Self) -> *const c_char> {
289        self.get_last_error
290    }
291
292    /// Returns the producer-provided release callback, if any.
293    pub fn release(&self) -> Option<unsafe extern "C" fn(arg1: *mut Self)> {
294        self.release
295    }
296
297    /// Returns the opaque producer-provided private data pointer.
298    pub fn private_data(&self) -> *mut c_void {
299        self.private_data
300    }
301
302    /// Replaces the release callback, returning the previous one.
303    ///
304    /// Lets a consumer wrap release: save the old callback, install its own, and
305    /// chain back on drop. See <https://github.com/apache/arrow-rs/issues/9771>.
306    ///
307    /// # Safety
308    ///
309    /// [`Drop`] calls this callback with a pointer to `self`. The new callback
310    /// must correctly release this stream (usually by chaining to the returned
311    /// one) and must match the [`FFI_ArrowArrayStream::private_data`] it reads.
312    /// A wrong callback is undefined behavior on drop.
313    pub unsafe fn set_release(
314        &mut self,
315        release: Option<unsafe extern "C" fn(arg1: *mut Self)>,
316    ) -> Option<unsafe extern "C" fn(arg1: *mut Self)> {
317        std::mem::replace(&mut self.release, release)
318    }
319
320    /// Replaces the private data pointer, returning the previous one.
321    ///
322    /// # Safety
323    ///
324    /// The old pointer is returned without being freed; the caller owns it from
325    /// here. The new pointer must match what the current
326    /// [`FFI_ArrowArrayStream::release`] callback expects.
327    pub unsafe fn set_private_data(&mut self, private_data: *mut c_void) -> *mut c_void {
328        std::mem::replace(&mut self.private_data, private_data)
329    }
330}
331
332struct ExportedArrayStream {
333    stream: *mut FFI_ArrowArrayStream,
334}
335
336impl ExportedArrayStream {
337    fn get_private_data(&mut self) -> &mut StreamPrivateData {
338        unsafe { &mut *(*self.stream).private_data.cast::<StreamPrivateData>() }
339    }
340
341    pub fn get_schema(&mut self, out: *mut FFI_ArrowSchema) -> i32 {
342        let private_data = self.get_private_data();
343        let reader = &private_data.batch_reader;
344
345        let schema = FFI_ArrowSchema::try_from(reader.schema().as_ref());
346
347        match schema {
348            Ok(schema) => {
349                unsafe { std::ptr::copy(addr_of!(schema), out, 1) };
350                std::mem::forget(schema);
351                0
352            }
353            Err(ref err) => {
354                private_data.last_error = Some(
355                    CString::new(err.to_string()).expect("Error string has a null byte in it."),
356                );
357                get_error_code(err)
358            }
359        }
360    }
361
362    pub fn get_next(&mut self, out: *mut FFI_ArrowArray) -> i32 {
363        let private_data = self.get_private_data();
364        let reader = &mut private_data.batch_reader;
365
366        match reader.next() {
367            None => {
368                // Marks ArrowArray released to indicate reaching the end of stream.
369                unsafe { std::ptr::write(out, FFI_ArrowArray::empty()) }
370                0
371            }
372            Some(next_batch) => {
373                if let Ok(batch) = next_batch {
374                    let struct_array = StructArray::from(batch);
375                    let array = FFI_ArrowArray::new(&struct_array.to_data());
376
377                    unsafe { std::ptr::write_unaligned(out, array) };
378                    0
379                } else {
380                    let err = &next_batch.unwrap_err();
381                    private_data.last_error = Some(
382                        CString::new(err.to_string()).expect("Error string has a null byte in it."),
383                    );
384                    get_error_code(err)
385                }
386            }
387        }
388    }
389
390    pub fn get_last_error(&mut self) -> Option<&CString> {
391        self.get_private_data().last_error.as_ref()
392    }
393}
394
395fn get_error_code(err: &ArrowError) -> i32 {
396    match err {
397        ArrowError::NotYetImplemented(_) => ENOSYS,
398        ArrowError::MemoryError(_) => ENOMEM,
399        ArrowError::IoError(_, _) => EIO,
400        _ => EINVAL,
401    }
402}
403
404/// A `RecordBatchReader` which imports Arrays from `FFI_ArrowArrayStream`.
405///
406/// Struct used to fetch `RecordBatch` from the C Stream Interface.
407/// Its main responsibility is to expose `RecordBatchReader` functionality
408/// that requires [FFI_ArrowArrayStream].
409///
410/// See [`ArrayStreamReader`] to read a stream whose arrays are not of type `Struct`.
411#[derive(Debug)]
412pub struct RecordBatchStreamReader {
413    stream: FFI_ArrowArrayStream,
414    schema: SchemaRef,
415}
416
417/// Renamed to [`RecordBatchStreamReader`], to distinguish it from [`ArrayStreamReader`].
418#[deprecated(since = "60.1.0", note = "Use `RecordBatchStreamReader` instead")]
419pub type ArrowArrayStreamReader = RecordBatchStreamReader;
420
421/// Returns the producer's message for the last failed call on a `FFI_ArrowArrayStream`.
422///
423/// Returns `None` when the producer supplies no message, either because it installs no
424/// `get_last_error` callback or because that callback returns NULL: the C Stream Interface
425/// lets `get_last_error` return NULL when no detailed description is available.
426///
427/// # Safety
428///
429/// `stream_ptr` must point to a valid, not yet released [`FFI_ArrowArrayStream`], and the last
430/// operation on it must have returned an error: the C Stream Interface forbids calling
431/// `get_last_error` in any other case.
432unsafe fn producer_error(stream_ptr: *mut FFI_ArrowArrayStream) -> Option<String> {
433    let get_last_error = unsafe { (*stream_ptr).get_last_error }?;
434
435    let error_str = unsafe { get_last_error(stream_ptr) };
436    if error_str.is_null() {
437        return None;
438    }
439
440    Some(
441        unsafe { CStr::from_ptr(error_str) }
442            .to_string_lossy()
443            .into_owned(),
444    )
445}
446
447/// Gets schema from a raw pointer of `FFI_ArrowArrayStream`. This is used when constructing
448/// `RecordBatchStreamReader` to cache schema.
449fn get_stream_schema(stream_ptr: *mut FFI_ArrowArrayStream) -> Result<SchemaRef> {
450    let field = get_stream_field(stream_ptr)?;
451    match field.data_type() {
452        DataType::Struct(fields) => Ok(Arc::new(
453            Schema::new(fields.clone()).with_metadata(field.metadata().clone()),
454        )),
455        _ => Err(ArrowError::CDataInterface(
456            "Unable to interpret C data struct as a Schema".to_string(),
457        )),
458    }
459}
460
461/// Gets the field from a raw pointer of `FFI_ArrowArrayStream`. This is used when constructing
462/// `ArrayStreamReader` to cache the field.
463fn get_stream_field(stream_ptr: *mut FFI_ArrowArrayStream) -> Result<FieldRef> {
464    let mut schema = FFI_ArrowSchema::empty();
465
466    let Some(get_schema) = (unsafe { (*stream_ptr).get_schema }) else {
467        return Err(ArrowError::CDataInterface(
468            "input stream does not provide the required get_schema callback".to_string(),
469        ));
470    };
471
472    let ret_code = unsafe { get_schema(stream_ptr, &raw mut schema) };
473
474    if ret_code == 0 {
475        Ok(Arc::new(Field::try_from(&schema)?))
476    } else {
477        let message = format!("Cannot get schema from input stream. Error code: {ret_code}");
478        // SAFETY: `stream_ptr` is valid and unreleased, and the `get_schema` call above
479        // returned a non-zero code.
480        let message = match unsafe { producer_error(stream_ptr) } {
481            Some(producer_message) => format!("{message}. Producer error: {producer_message}"),
482            None => message,
483        };
484        Err(ArrowError::CDataInterface(message))
485    }
486}
487
488impl RecordBatchStreamReader {
489    /// Creates a new `RecordBatchStreamReader` from a `FFI_ArrowArrayStream`.
490    /// This is used to import from the C Stream Interface.
491    pub fn try_new(mut stream: FFI_ArrowArrayStream) -> Result<Self> {
492        if stream.release.is_none() {
493            return Err(ArrowError::CDataInterface(
494                "input stream is already released".to_string(),
495            ));
496        }
497
498        let schema = get_stream_schema(&raw mut stream)?;
499
500        Ok(Self { stream, schema })
501    }
502
503    /// Creates a new `RecordBatchStreamReader` from a raw pointer of `FFI_ArrowArrayStream`.
504    ///
505    /// Assumes that the pointer represents valid C Stream Interfaces.
506    /// This function copies the content from the raw pointer and cleans up it to prevent
507    /// double-dropping. The caller is responsible for freeing up the memory allocated for
508    /// the pointer.
509    ///
510    /// # Safety
511    ///
512    /// See [`FFI_ArrowArrayStream::from_raw`]
513    pub unsafe fn from_raw(raw_stream: *mut FFI_ArrowArrayStream) -> Result<Self> {
514        Self::try_new(unsafe { FFI_ArrowArrayStream::from_raw(raw_stream) })
515    }
516}
517
518impl Iterator for RecordBatchStreamReader {
519    type Item = Result<RecordBatch>;
520
521    fn next(&mut self) -> Option<Self::Item> {
522        let mut array = FFI_ArrowArray::empty();
523
524        let Some(get_next) = self.stream.get_next else {
525            return Some(Err(ArrowError::CDataInterface(
526                "input stream does not provide the required get_next callback".to_string(),
527            )));
528        };
529
530        let ret_code = unsafe { get_next(&raw mut self.stream, &raw mut array) };
531
532        if ret_code == 0 {
533            // The end of stream has been reached
534            if array.is_released() {
535                return None;
536            }
537
538            let result = unsafe {
539                from_ffi_and_data_type(array, DataType::Struct(self.schema().fields().clone()))
540            };
541            Some(result.and_then(|data| {
542                let len = data.len();
543                RecordBatch::try_new_with_options(
544                    self.schema.clone(),
545                    StructArray::from(data).into_parts().1,
546                    &RecordBatchOptions::new().with_row_count(Some(len)),
547                )
548            }))
549        } else {
550            let message =
551                format!("Cannot get next batch from input stream. Error code: {ret_code}");
552            // SAFETY: `self.stream` is valid and unreleased by construction, and the
553            // `get_next` call above returned a non-zero code.
554            let message = match unsafe { producer_error(&raw mut self.stream) } {
555                Some(producer_message) => format!("{message}. Producer error: {producer_message}"),
556                None => message,
557            };
558            Some(Err(ArrowError::CDataInterface(message)))
559        }
560    }
561}
562
563impl RecordBatchReader for RecordBatchStreamReader {
564    fn schema(&self) -> SchemaRef {
565        self.schema.clone()
566    }
567}
568
569/// An [`ArrayReader`] which imports arrays from an [`FFI_ArrowArrayStream`].
570///
571/// [`RecordBatchStreamReader`] requires the stream's top-level type to be
572/// [`DataType::Struct`], which it unpacks into a [`RecordBatch`]. This reader has no such
573/// requirement: the stream's arrays may be of any data type.
574#[derive(Debug)]
575pub struct ArrayStreamReader {
576    stream: FFI_ArrowArrayStream,
577    field: FieldRef,
578}
579
580impl ArrayStreamReader {
581    /// Creates a new [`ArrayStreamReader`] from an [`FFI_ArrowArrayStream`].
582    /// This is used to import from the C Stream Interface.
583    pub fn try_new(mut stream: FFI_ArrowArrayStream) -> Result<Self> {
584        if stream.release.is_none() {
585            return Err(ArrowError::CDataInterface(
586                "input stream is already released".to_string(),
587            ));
588        }
589
590        let field = get_stream_field(&raw mut stream)?;
591
592        Ok(Self { stream, field })
593    }
594
595    /// Creates a new [`ArrayStreamReader`] from a raw pointer of [`FFI_ArrowArrayStream`].
596    ///
597    /// # Safety
598    ///
599    /// See [`FFI_ArrowArrayStream::from_raw`]
600    pub unsafe fn from_raw(raw_stream: *mut FFI_ArrowArrayStream) -> Result<Self> {
601        Self::try_new(unsafe { FFI_ArrowArrayStream::from_raw(raw_stream) })
602    }
603}
604
605impl Iterator for ArrayStreamReader {
606    type Item = Result<ArrayRef>;
607
608    fn next(&mut self) -> Option<Self::Item> {
609        let mut array = FFI_ArrowArray::empty();
610
611        let ret_code =
612            unsafe { self.stream.get_next.unwrap()(&raw mut self.stream, &raw mut array) };
613
614        if ret_code == 0 {
615            // The end of stream has been reached
616            if array.is_released() {
617                return None;
618            }
619
620            let result = unsafe { from_ffi_and_data_type(array, self.field.data_type().clone()) };
621            Some(result.map(make_array))
622        } else {
623            let message =
624                format!("Cannot get next array from input stream. Error code: {ret_code}");
625            // SAFETY: `self.stream` is valid and unreleased by construction, and the
626            // `get_next` call above returned a non-zero code.
627            let message = match unsafe { producer_error(&raw mut self.stream) } {
628                Some(producer_message) => format!("{message}. Producer error: {producer_message}"),
629                None => message,
630            };
631            Some(Err(ArrowError::CDataInterface(message)))
632        }
633    }
634}
635
636impl ArrayReader for ArrayStreamReader {
637    fn field(&self) -> &FieldRef {
638        &self.field
639    }
640}
641
642#[cfg(test)]
643mod tests {
644    use super::*;
645    use std::collections::HashMap;
646
647    use arrow_schema::Field;
648
649    use crate::array::Int32Array;
650    use crate::ffi::from_ffi;
651
652    struct TestRecordBatchReader {
653        schema: SchemaRef,
654        iter: Box<dyn Iterator<Item = Result<RecordBatch>> + Send>,
655    }
656
657    impl TestRecordBatchReader {
658        pub fn new(
659            schema: SchemaRef,
660            iter: Box<dyn Iterator<Item = Result<RecordBatch>> + Send>,
661        ) -> TestRecordBatchReader {
662            TestRecordBatchReader { schema, iter }
663        }
664    }
665
666    impl Iterator for TestRecordBatchReader {
667        type Item = Result<RecordBatch>;
668
669        fn next(&mut self) -> Option<Self::Item> {
670            self.iter.next()
671        }
672    }
673
674    impl RecordBatchReader for TestRecordBatchReader {
675        fn schema(&self) -> SchemaRef {
676            self.schema.clone()
677        }
678    }
679
680    fn _test_round_trip_export(batch: RecordBatch, schema: Arc<Schema>) -> Result<()> {
681        let iter = Box::new(vec![batch.clone(), batch.clone()].into_iter().map(Ok)) as _;
682
683        let reader = Box::new(TestRecordBatchReader::new(schema.clone(), iter));
684
685        // Export a `RecordBatchReader` through `FFI_ArrowArrayStream`
686        let mut ffi_stream = FFI_ArrowArrayStream::new(reader);
687
688        // Get schema from `FFI_ArrowArrayStream`
689        let mut ffi_schema = FFI_ArrowSchema::empty();
690        let ret_code = unsafe { get_schema(&raw mut ffi_stream, &raw mut ffi_schema) };
691        assert_eq!(ret_code, 0);
692
693        let exported_schema = Schema::try_from(&ffi_schema).unwrap();
694        assert_eq!(&exported_schema, schema.as_ref());
695
696        // Get array from `FFI_ArrowArrayStream`
697        let mut produced_batches = vec![];
698        loop {
699            let mut ffi_array = FFI_ArrowArray::empty();
700            let ret_code = unsafe { get_next(&raw mut ffi_stream, &raw mut ffi_array) };
701            assert_eq!(ret_code, 0);
702
703            // The end of stream has been reached
704            if ffi_array.is_released() {
705                break;
706            }
707
708            let array = unsafe { from_ffi(ffi_array, &ffi_schema) }.unwrap();
709            let len = array.len();
710
711            let record_batch = RecordBatch::try_new_with_options(
712                SchemaRef::from(exported_schema.clone()),
713                StructArray::from(array).into_parts().1,
714                &RecordBatchOptions::new().with_row_count(Some(len)),
715            )
716            .unwrap();
717            produced_batches.push(record_batch);
718        }
719
720        assert_eq!(produced_batches, vec![batch.clone(), batch]);
721
722        Ok(())
723    }
724
725    fn _test_round_trip_import(batch: RecordBatch, schema: Arc<Schema>) -> Result<()> {
726        let iter = Box::new(vec![batch.clone(), batch.clone()].into_iter().map(Ok)) as _;
727
728        let reader = Box::new(TestRecordBatchReader::new(schema.clone(), iter));
729
730        // Import through `FFI_ArrowArrayStream` as `RecordBatchStreamReader`
731        let stream = FFI_ArrowArrayStream::new(reader);
732        let stream_reader = RecordBatchStreamReader::try_new(stream).unwrap();
733
734        let imported_schema = stream_reader.schema();
735        assert_eq!(imported_schema, schema);
736
737        let mut produced_batches = vec![];
738        for batch in stream_reader {
739            produced_batches.push(batch.unwrap());
740        }
741
742        assert_eq!(produced_batches, vec![batch.clone(), batch]);
743
744        Ok(())
745    }
746
747    #[test]
748    fn test_stream_round_trip() {
749        let array = Int32Array::from(vec![Some(2), None, Some(1), None]);
750        let array: Arc<dyn Array> = Arc::new(array);
751        let metadata = HashMap::from([("foo".to_owned(), "bar".to_owned())]);
752
753        let schema = Arc::new(Schema::new_with_metadata(
754            vec![
755                Field::new("a", array.data_type().clone(), true).with_metadata(metadata.clone()),
756                Field::new("b", array.data_type().clone(), true).with_metadata(metadata.clone()),
757                Field::new("c", array.data_type().clone(), true).with_metadata(metadata.clone()),
758            ],
759            metadata,
760        ));
761        let batch = RecordBatch::try_new(schema.clone(), vec![array.clone(), array.clone(), array])
762            .unwrap();
763
764        _test_round_trip_export(batch.clone(), schema.clone()).unwrap();
765        _test_round_trip_import(batch, schema).unwrap();
766    }
767
768    #[test]
769    fn test_stream_round_trip_no_columns() {
770        let metadata = HashMap::from([("foo".to_owned(), "bar".to_owned())]);
771
772        let schema = Arc::new(Schema::new_with_metadata(Vec::<Field>::new(), metadata));
773        let batch = RecordBatch::try_new_with_options(
774            schema.clone(),
775            Vec::<Arc<dyn Array>>::new(),
776            &RecordBatchOptions::new().with_row_count(Some(10)),
777        )
778        .unwrap();
779
780        _test_round_trip_export(batch.clone(), schema.clone()).unwrap();
781        _test_round_trip_import(batch, schema).unwrap();
782    }
783
784    #[test]
785    fn test_error_import() -> Result<()> {
786        let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
787
788        let iter =
789            Box::new(vec![Err(ArrowError::MemoryError("out of memory".to_string()))].into_iter());
790
791        let reader = Box::new(TestRecordBatchReader::new(schema.clone(), iter));
792
793        // Import through `FFI_ArrowArrayStream` as `RecordBatchStreamReader`
794        let stream = FFI_ArrowArrayStream::new(reader);
795        let stream_reader = RecordBatchStreamReader::try_new(stream).unwrap();
796
797        let imported_schema = stream_reader.schema();
798        assert_eq!(imported_schema, schema);
799
800        let mut produced_batches = vec![];
801        for batch in stream_reader {
802            produced_batches.push(batch);
803        }
804
805        // The results should outlive the lifetime of the stream itself.
806        assert_eq!(produced_batches.len(), 1);
807        assert_eq!(
808            produced_batches[0].as_ref().unwrap_err().to_string(),
809            format!(
810                "C Data interface error: Cannot get next batch from input stream. \
811                 Error code: {ENOMEM}. Producer error: Memory error: out of memory"
812            )
813        );
814
815        Ok(())
816    }
817
818    unsafe extern "C" fn failing_get_schema(
819        _stream: *mut FFI_ArrowArrayStream,
820        _out: *mut FFI_ArrowSchema,
821    ) -> c_int {
822        EIO
823    }
824
825    unsafe extern "C" fn working_get_schema(
826        _stream: *mut FFI_ArrowArrayStream,
827        out: *mut FFI_ArrowSchema,
828    ) -> c_int {
829        let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
830        unsafe { std::ptr::write(out, FFI_ArrowSchema::try_from(&schema).unwrap()) };
831        0
832    }
833
834    unsafe extern "C" fn failing_get_next(
835        _stream: *mut FFI_ArrowArrayStream,
836        _out: *mut FFI_ArrowArray,
837    ) -> c_int {
838        EIO
839    }
840
841    unsafe extern "C" fn producer_last_error(_stream: *mut FFI_ArrowArrayStream) -> *const c_char {
842        c"the producer failed".as_ptr()
843    }
844
845    unsafe extern "C" fn null_last_error(_stream: *mut FFI_ArrowArrayStream) -> *const c_char {
846        std::ptr::null()
847    }
848
849    unsafe extern "C" fn mark_released(stream: *mut FFI_ArrowArrayStream) {
850        unsafe { (*stream).release = None };
851    }
852
853    fn failing_stream(
854        get_last_error: Option<unsafe extern "C" fn(*mut FFI_ArrowArrayStream) -> *const c_char>,
855    ) -> FFI_ArrowArrayStream {
856        let mut stream = FFI_ArrowArrayStream::empty();
857        stream.get_schema = Some(failing_get_schema);
858        stream.get_next = Some(failing_get_next);
859        stream.get_last_error = get_last_error;
860        stream.release = Some(mark_released);
861        stream
862    }
863
864    #[test]
865    fn test_import_stream_without_get_schema_callback() {
866        // The C Stream Interface requires get_schema for an unreleased stream.
867        let stream = unsafe {
868            FFI_ArrowArrayStream::new_unchecked(
869                None,
870                Some(failing_get_next),
871                None,
872                Some(mark_released),
873                std::ptr::null_mut(),
874            )
875        };
876
877        let err = RecordBatchStreamReader::try_new(stream).unwrap_err();
878        assert!(matches!(err, ArrowError::CDataInterface(_)));
879        assert!(err.to_string().contains("get_schema"));
880    }
881
882    #[test]
883    fn test_import_stream_without_get_next_callback() {
884        // The C Stream Interface requires get_next for an unreleased stream.
885        let stream = unsafe {
886            FFI_ArrowArrayStream::new_unchecked(
887                Some(working_get_schema),
888                None,
889                None,
890                Some(mark_released),
891                std::ptr::null_mut(),
892            )
893        };
894
895        let err = RecordBatchStreamReader::try_new(stream)
896            .unwrap()
897            .next()
898            .unwrap()
899            .unwrap_err();
900        assert!(matches!(err, ArrowError::CDataInterface(_)));
901        assert!(err.to_string().contains("get_next"));
902    }
903
904    #[test]
905    fn test_import_schema_error_reports_producer_message() {
906        let err = RecordBatchStreamReader::try_new(failing_stream(Some(producer_last_error)))
907            .unwrap_err();
908        assert_eq!(
909            err.to_string(),
910            format!(
911                "C Data interface error: Cannot get schema from input stream. \
912                 Error code: {EIO}. Producer error: the producer failed"
913            )
914        );
915    }
916
917    #[test]
918    fn test_import_schema_error_without_producer_message() {
919        // A producer need not supply a message: `get_last_error` may return NULL when no
920        // detailed description is available.
921        let err =
922            RecordBatchStreamReader::try_new(failing_stream(Some(null_last_error))).unwrap_err();
923        assert_eq!(
924            err.to_string(),
925            format!(
926                "C Data interface error: Cannot get schema from input stream. Error code: {EIO}"
927            )
928        );
929    }
930
931    #[test]
932    fn test_import_schema_error_without_error_callback() {
933        let err = RecordBatchStreamReader::try_new(failing_stream(None)).unwrap_err();
934        assert_eq!(
935            err.to_string(),
936            format!(
937                "C Data interface error: Cannot get schema from input stream. Error code: {EIO}"
938            )
939        );
940    }
941
942    #[test]
943    fn test_import_next_error_without_producer_message() {
944        // Previously panicked: the message was unwrapped without checking that the producer
945        // supplied one.
946        let mut stream = failing_stream(Some(null_last_error));
947        stream.get_schema = Some(working_get_schema);
948
949        let err = RecordBatchStreamReader::try_new(stream)
950            .unwrap()
951            .next()
952            .unwrap()
953            .unwrap_err();
954        assert_eq!(
955            err.to_string(),
956            format!(
957                "C Data interface error: Cannot get next batch from input stream. Error code: {EIO}"
958            )
959        );
960    }
961
962    // A consumer wraps the release callback with its own, then chains back to
963    // the original on drop. This is the same wrap-release pattern the
964    // release/private_data accessors exist for (#9771).
965    static STREAM_WRAPPER_RAN: std::sync::atomic::AtomicBool =
966        std::sync::atomic::AtomicBool::new(false);
967
968    struct StreamWrapperData {
969        original_release: Option<unsafe extern "C" fn(*mut FFI_ArrowArrayStream)>,
970        original_private_data: *mut c_void,
971    }
972
973    unsafe extern "C" fn wrapping_release(stream: *mut FFI_ArrowArrayStream) {
974        use std::sync::atomic::Ordering;
975        let stream = unsafe { &mut *stream };
976        let data = unsafe { Box::from_raw(stream.private_data().cast::<StreamWrapperData>()) };
977        STREAM_WRAPPER_RAN.store(true, Ordering::SeqCst);
978        unsafe { stream.set_release(data.original_release) };
979        unsafe { stream.set_private_data(data.original_private_data) };
980        if let Some(release) = stream.release() {
981            unsafe { release(stream) };
982        }
983    }
984
985    #[test]
986    fn test_wrap_release_callback() {
987        use std::sync::atomic::Ordering;
988
989        let batch_reader = Box::new(TestRecordBatchReader::new(
990            Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)])),
991            Box::new(std::iter::empty()),
992        ));
993        let mut stream = FFI_ArrowArrayStream::new(batch_reader);
994
995        let data = Box::new(StreamWrapperData {
996            original_release: stream.release(),
997            original_private_data: stream.private_data(),
998        });
999        unsafe { stream.set_release(Some(wrapping_release)) };
1000        unsafe { stream.set_private_data(Box::into_raw(data).cast::<c_void>()) };
1001
1002        drop(stream); // runs wrapping_release, which chains to the original
1003        assert!(STREAM_WRAPPER_RAN.load(Ordering::SeqCst));
1004    }
1005
1006    // A producer that exports a stream of plain arrays rather than record batches.
1007    // `FFI_ArrowArrayStream::new` cannot express this, since it takes a
1008    // `RecordBatchReader`. See <https://github.com/apache/arrow-rs/issues/6586>.
1009    struct ArrayStreamPrivateData {
1010        field: Field,
1011        arrays: std::vec::IntoIter<Int32Array>,
1012    }
1013
1014    unsafe extern "C" fn array_stream_get_schema(
1015        stream: *mut FFI_ArrowArrayStream,
1016        out: *mut FFI_ArrowSchema,
1017    ) -> c_int {
1018        let private_data = unsafe { &*(*stream).private_data().cast::<ArrayStreamPrivateData>() };
1019        let schema = FFI_ArrowSchema::try_from(&private_data.field).unwrap();
1020        unsafe { std::ptr::write(out, schema) };
1021        0
1022    }
1023
1024    unsafe extern "C" fn array_stream_get_next(
1025        stream: *mut FFI_ArrowArrayStream,
1026        out: *mut FFI_ArrowArray,
1027    ) -> c_int {
1028        let private_data =
1029            unsafe { &mut *(*stream).private_data().cast::<ArrayStreamPrivateData>() };
1030        match private_data.arrays.next() {
1031            // Marks ArrowArray released to indicate reaching the end of stream.
1032            None => unsafe { std::ptr::write(out, FFI_ArrowArray::empty()) },
1033            Some(array) => unsafe {
1034                std::ptr::write_unaligned(out, FFI_ArrowArray::new(&array.to_data()))
1035            },
1036        }
1037        0
1038    }
1039
1040    unsafe extern "C" fn array_stream_get_last_error(
1041        _stream: *mut FFI_ArrowArrayStream,
1042    ) -> *const c_char {
1043        std::ptr::null()
1044    }
1045
1046    unsafe extern "C" fn array_stream_release(stream: *mut FFI_ArrowArrayStream) {
1047        let private_data =
1048            unsafe { Box::from_raw((*stream).private_data().cast::<ArrayStreamPrivateData>()) };
1049        drop(private_data);
1050        // Clears every callback and marks the stream released, without dropping the
1051        // value being overwritten.
1052        unsafe { std::ptr::write(stream, FFI_ArrowArrayStream::empty()) };
1053    }
1054
1055    fn array_stream(field: Field, arrays: Vec<Int32Array>) -> FFI_ArrowArrayStream {
1056        let private_data = Box::new(ArrayStreamPrivateData {
1057            field,
1058            arrays: arrays.into_iter(),
1059        });
1060
1061        unsafe {
1062            FFI_ArrowArrayStream::new_unchecked(
1063                Some(array_stream_get_schema),
1064                Some(array_stream_get_next),
1065                Some(array_stream_get_last_error),
1066                Some(array_stream_release),
1067                Box::into_raw(private_data).cast::<c_void>(),
1068            )
1069        }
1070    }
1071
1072    #[test]
1073    fn test_new_unchecked_exports_stream_of_arrays() {
1074        let field = Field::new("a", DataType::Int32, true);
1075        let arrays = vec![
1076            Int32Array::from(vec![1, 2, 3]),
1077            Int32Array::from(vec![4, 5]),
1078        ];
1079        let mut stream = array_stream(field.clone(), arrays.clone());
1080
1081        // Drive the stream through the callbacks the constructor stored, so a
1082        // field the constructor put in the wrong place would surface here.
1083        let get_schema_fn = stream.get_schema().unwrap();
1084        let get_next_fn = stream.get_next().unwrap();
1085        let get_last_error_fn = stream.get_last_error().unwrap();
1086
1087        let mut ffi_schema = FFI_ArrowSchema::empty();
1088        let ret_code = unsafe { get_schema_fn(&raw mut stream, &raw mut ffi_schema) };
1089        assert_eq!(ret_code, 0);
1090        assert_eq!(Field::try_from(&ffi_schema).unwrap(), field);
1091
1092        let mut produced = vec![];
1093        loop {
1094            let mut ffi_array = FFI_ArrowArray::empty();
1095            let ret_code = unsafe { get_next_fn(&raw mut stream, &raw mut ffi_array) };
1096            assert_eq!(ret_code, 0);
1097
1098            // The end of stream has been reached
1099            if ffi_array.is_released() {
1100                break;
1101            }
1102
1103            let data = unsafe { from_ffi(ffi_array, &ffi_schema) }.unwrap();
1104            produced.push(Int32Array::from(data));
1105        }
1106
1107        assert_eq!(produced, arrays);
1108
1109        // This producer never fails, so it reports no error message.
1110        assert!(unsafe { get_last_error_fn(&raw mut stream) }.is_null());
1111
1112        // Runs the stored release callback, freeing the private data exactly once.
1113        drop(stream);
1114    }
1115
1116    #[test]
1117    fn test_accessors_report_an_empty_stream_as_released() {
1118        let stream = FFI_ArrowArrayStream::empty();
1119        assert!(stream.get_schema().is_none());
1120        assert!(stream.get_next().is_none());
1121        assert!(stream.get_last_error().is_none());
1122        assert!(stream.release().is_none());
1123        assert!(stream.private_data().is_null());
1124    }
1125    #[test]
1126    fn test_import_array_stream() {
1127        let field = Field::new("a", DataType::Int32, true);
1128        let arrays = vec![
1129            Int32Array::from(vec![1, 2, 3]),
1130            Int32Array::from(vec![4, 5]),
1131        ];
1132        let reader = ArrayStreamReader::try_new(array_stream(field, arrays.clone())).unwrap();
1133
1134        let produced = reader
1135            .map(|array| Int32Array::from(array.unwrap().to_data()))
1136            .collect::<Vec<_>>();
1137
1138        assert_eq!(produced, arrays);
1139    }
1140
1141    #[test]
1142    fn test_import_array_stream_reports_its_field() {
1143        let field = Field::new("a", DataType::Int32, true)
1144            .with_metadata(HashMap::from([("key".to_string(), "value".to_string())]));
1145        let reader = ArrayStreamReader::try_new(array_stream(field.clone(), vec![])).unwrap();
1146
1147        assert_eq!(reader.field().as_ref(), &field);
1148    }
1149
1150    #[test]
1151    fn test_import_array_stream_rejects_released_stream() {
1152        let err = ArrayStreamReader::try_new(FFI_ArrowArrayStream::empty()).unwrap_err();
1153
1154        assert_eq!(
1155            err.to_string(),
1156            "C Data interface error: input stream is already released"
1157        );
1158    }
1159
1160    #[test]
1161    fn test_import_array_schema_error_reports_producer_message() {
1162        let err =
1163            ArrayStreamReader::try_new(failing_stream(Some(producer_last_error))).unwrap_err();
1164
1165        assert_eq!(
1166            err.to_string(),
1167            format!(
1168                "C Data interface error: Cannot get schema from input stream. \
1169                 Error code: {EIO}. Producer error: the producer failed"
1170            )
1171        );
1172    }
1173
1174    #[test]
1175    fn test_import_array_next_error_reports_producer_message() {
1176        let mut stream = failing_stream(Some(producer_last_error));
1177        stream.get_schema = Some(working_get_schema);
1178
1179        let err = ArrayStreamReader::try_new(stream)
1180            .unwrap()
1181            .next()
1182            .unwrap()
1183            .unwrap_err();
1184
1185        assert_eq!(
1186            err.to_string(),
1187            format!(
1188                "C Data interface error: Cannot get next array from input stream. \
1189                 Error code: {EIO}. Producer error: the producer failed"
1190            )
1191        );
1192    }
1193    #[test]
1194    fn test_import_record_batch_stream_rejects_non_struct_stream() {
1195        // A stream of plain arrays cannot be read as record batches. Use
1196        // `ArrayStreamReader` for that.
1197        let field = Field::new("a", DataType::Int32, true);
1198
1199        let err = RecordBatchStreamReader::try_new(array_stream(field, vec![])).unwrap_err();
1200
1201        assert_eq!(
1202            err.to_string(),
1203            "C Data interface error: Unable to interpret C data struct as a Schema"
1204        );
1205    }
1206}