1use arrow_schema::DataType;
58use std::ffi::CStr;
59use std::ptr::addr_of;
60use std::{
61 ffi::CString,
62 os::raw::{c_char, c_int, c_void},
63 sync::Arc,
64};
65
66use arrow_data::ffi::FFI_ArrowArray;
67use arrow_schema::{ArrowError, Schema, SchemaRef, ffi::FFI_ArrowSchema};
68
69use crate::RecordBatchOptions;
70use crate::array::Array;
71use crate::array::StructArray;
72use crate::ffi::from_ffi_and_data_type;
73use crate::record_batch::{RecordBatch, RecordBatchReader};
74
75type Result<T> = std::result::Result<T, ArrowError>;
76
77#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
80use libc::{EINVAL, EIO, ENOMEM, ENOSYS};
81
82#[cfg(all(target_family = "wasm", target_os = "unknown"))]
85const ENOMEM: i32 = 12;
86#[cfg(all(target_family = "wasm", target_os = "unknown"))]
87const EIO: i32 = 5;
88#[cfg(all(target_family = "wasm", target_os = "unknown"))]
89const EINVAL: i32 = 22;
90#[cfg(all(target_family = "wasm", target_os = "unknown"))]
91const ENOSYS: i32 = 38;
92
93#[repr(C)]
97#[derive(Debug)]
98pub struct FFI_ArrowArrayStream {
99 get_schema: Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowSchema) -> c_int>,
103 get_next: Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowArray) -> c_int>,
105 get_last_error: Option<unsafe extern "C" fn(arg1: *mut Self) -> *const c_char>,
107 release: Option<unsafe extern "C" fn(arg1: *mut Self)>,
109 private_data: *mut c_void,
111}
112
113unsafe impl Send for FFI_ArrowArrayStream {}
114
115unsafe extern "C" fn release_stream(stream: *mut FFI_ArrowArrayStream) {
117 if stream.is_null() {
118 return;
119 }
120 let stream = unsafe { &mut *stream };
121
122 stream.get_schema = None;
123 stream.get_next = None;
124 stream.get_last_error = None;
125
126 let private_data = unsafe { Box::from_raw(stream.private_data.cast::<StreamPrivateData>()) };
127 drop(private_data);
128
129 stream.release = None;
130}
131
132struct StreamPrivateData {
133 batch_reader: Box<dyn RecordBatchReader + Send>,
134 last_error: Option<CString>,
135}
136
137unsafe extern "C" fn get_schema(
139 stream: *mut FFI_ArrowArrayStream,
140 schema: *mut FFI_ArrowSchema,
141) -> c_int {
142 ExportedArrayStream { stream }.get_schema(schema)
143}
144
145unsafe extern "C" fn get_next(
147 stream: *mut FFI_ArrowArrayStream,
148 array: *mut FFI_ArrowArray,
149) -> c_int {
150 ExportedArrayStream { stream }.get_next(array)
151}
152
153unsafe extern "C" fn get_last_error(stream: *mut FFI_ArrowArrayStream) -> *const c_char {
155 let mut ffi_stream = ExportedArrayStream { stream };
156 match ffi_stream.get_last_error() {
159 Some(err_string) => err_string.as_ptr(),
160 None => std::ptr::null(),
161 }
162}
163
164impl Drop for FFI_ArrowArrayStream {
165 fn drop(&mut self) {
166 match self.release {
167 None => (),
168 Some(release) => unsafe { release(self) },
169 }
170 }
171}
172
173impl FFI_ArrowArrayStream {
174 pub fn new(batch_reader: Box<dyn RecordBatchReader + Send>) -> Self {
176 let private_data = Box::new(StreamPrivateData {
177 batch_reader,
178 last_error: None,
179 });
180
181 Self {
182 get_schema: Some(get_schema),
183 get_next: Some(get_next),
184 get_last_error: Some(get_last_error),
185 release: Some(release_stream),
186 private_data: Box::into_raw(private_data).cast::<c_void>(),
187 }
188 }
189
190 pub unsafe fn new_unchecked(
209 get_schema: Option<
210 unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowSchema) -> c_int,
211 >,
212 get_next: Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowArray) -> c_int>,
213 get_last_error: Option<unsafe extern "C" fn(arg1: *mut Self) -> *const c_char>,
214 release: Option<unsafe extern "C" fn(arg1: *mut Self)>,
215 private_data: *mut c_void,
216 ) -> Self {
217 Self {
218 get_schema,
219 get_next,
220 get_last_error,
221 release,
222 private_data,
223 }
224 }
225
226 pub unsafe fn from_raw(raw_stream: *mut FFI_ArrowArrayStream) -> Self {
239 unsafe { std::ptr::replace(raw_stream, Self::empty()) }
240 }
241
242 pub fn empty() -> Self {
244 Self {
245 get_schema: None,
246 get_next: None,
247 get_last_error: None,
248 release: None,
249 private_data: std::ptr::null_mut(),
250 }
251 }
252
253 #[expect(clippy::unnecessary_safety_doc)]
254 pub fn get_schema(
260 &self,
261 ) -> Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowSchema) -> c_int> {
262 self.get_schema
263 }
264
265 #[expect(clippy::unnecessary_safety_doc)]
266 pub fn get_next(
272 &self,
273 ) -> Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowArray) -> c_int> {
274 self.get_next
275 }
276
277 #[expect(clippy::unnecessary_safety_doc)]
278 pub fn get_last_error(&self) -> Option<unsafe extern "C" fn(arg1: *mut Self) -> *const c_char> {
286 self.get_last_error
287 }
288
289 pub fn release(&self) -> Option<unsafe extern "C" fn(arg1: *mut Self)> {
291 self.release
292 }
293
294 pub fn private_data(&self) -> *mut c_void {
296 self.private_data
297 }
298
299 pub unsafe fn set_release(
311 &mut self,
312 release: Option<unsafe extern "C" fn(arg1: *mut Self)>,
313 ) -> Option<unsafe extern "C" fn(arg1: *mut Self)> {
314 std::mem::replace(&mut self.release, release)
315 }
316
317 pub unsafe fn set_private_data(&mut self, private_data: *mut c_void) -> *mut c_void {
325 std::mem::replace(&mut self.private_data, private_data)
326 }
327}
328
329struct ExportedArrayStream {
330 stream: *mut FFI_ArrowArrayStream,
331}
332
333impl ExportedArrayStream {
334 fn get_private_data(&mut self) -> &mut StreamPrivateData {
335 unsafe { &mut *(*self.stream).private_data.cast::<StreamPrivateData>() }
336 }
337
338 pub fn get_schema(&mut self, out: *mut FFI_ArrowSchema) -> i32 {
339 let private_data = self.get_private_data();
340 let reader = &private_data.batch_reader;
341
342 let schema = FFI_ArrowSchema::try_from(reader.schema().as_ref());
343
344 match schema {
345 Ok(schema) => {
346 unsafe { std::ptr::copy(addr_of!(schema), out, 1) };
347 std::mem::forget(schema);
348 0
349 }
350 Err(ref err) => {
351 private_data.last_error = Some(
352 CString::new(err.to_string()).expect("Error string has a null byte in it."),
353 );
354 get_error_code(err)
355 }
356 }
357 }
358
359 pub fn get_next(&mut self, out: *mut FFI_ArrowArray) -> i32 {
360 let private_data = self.get_private_data();
361 let reader = &mut private_data.batch_reader;
362
363 match reader.next() {
364 None => {
365 unsafe { std::ptr::write(out, FFI_ArrowArray::empty()) }
367 0
368 }
369 Some(next_batch) => {
370 if let Ok(batch) = next_batch {
371 let struct_array = StructArray::from(batch);
372 let array = FFI_ArrowArray::new(&struct_array.to_data());
373
374 unsafe { std::ptr::write_unaligned(out, array) };
375 0
376 } else {
377 let err = &next_batch.unwrap_err();
378 private_data.last_error = Some(
379 CString::new(err.to_string()).expect("Error string has a null byte in it."),
380 );
381 get_error_code(err)
382 }
383 }
384 }
385 }
386
387 pub fn get_last_error(&mut self) -> Option<&CString> {
388 self.get_private_data().last_error.as_ref()
389 }
390}
391
392fn get_error_code(err: &ArrowError) -> i32 {
393 match err {
394 ArrowError::NotYetImplemented(_) => ENOSYS,
395 ArrowError::MemoryError(_) => ENOMEM,
396 ArrowError::IoError(_, _) => EIO,
397 _ => EINVAL,
398 }
399}
400
401#[derive(Debug)]
407pub struct ArrowArrayStreamReader {
408 stream: FFI_ArrowArrayStream,
409 schema: SchemaRef,
410}
411
412unsafe fn producer_error(stream_ptr: *mut FFI_ArrowArrayStream) -> Option<String> {
424 let get_last_error = unsafe { (*stream_ptr).get_last_error }?;
425
426 let error_str = unsafe { get_last_error(stream_ptr) };
427 if error_str.is_null() {
428 return None;
429 }
430
431 Some(
432 unsafe { CStr::from_ptr(error_str) }
433 .to_string_lossy()
434 .into_owned(),
435 )
436}
437
438fn get_stream_schema(stream_ptr: *mut FFI_ArrowArrayStream) -> Result<SchemaRef> {
441 let mut schema = FFI_ArrowSchema::empty();
442
443 let ret_code = unsafe { (*stream_ptr).get_schema.unwrap()(stream_ptr, &raw mut schema) };
444
445 if ret_code == 0 {
446 let schema = Schema::try_from(&schema)?;
447 Ok(Arc::new(schema))
448 } else {
449 let message = format!("Cannot get schema from input stream. Error code: {ret_code}");
450 let message = match unsafe { producer_error(stream_ptr) } {
453 Some(producer_message) => format!("{message}. Producer error: {producer_message}"),
454 None => message,
455 };
456 Err(ArrowError::CDataInterface(message))
457 }
458}
459
460impl ArrowArrayStreamReader {
461 pub fn try_new(mut stream: FFI_ArrowArrayStream) -> Result<Self> {
464 if stream.release.is_none() {
465 return Err(ArrowError::CDataInterface(
466 "input stream is already released".to_string(),
467 ));
468 }
469
470 let schema = get_stream_schema(&raw mut stream)?;
471
472 Ok(Self { stream, schema })
473 }
474
475 pub unsafe fn from_raw(raw_stream: *mut FFI_ArrowArrayStream) -> Result<Self> {
486 Self::try_new(unsafe { FFI_ArrowArrayStream::from_raw(raw_stream) })
487 }
488}
489
490impl Iterator for ArrowArrayStreamReader {
491 type Item = Result<RecordBatch>;
492
493 fn next(&mut self) -> Option<Self::Item> {
494 let mut array = FFI_ArrowArray::empty();
495
496 let ret_code =
497 unsafe { self.stream.get_next.unwrap()(&raw mut self.stream, &raw mut array) };
498
499 if ret_code == 0 {
500 if array.is_released() {
502 return None;
503 }
504
505 let result = unsafe {
506 from_ffi_and_data_type(array, DataType::Struct(self.schema().fields().clone()))
507 };
508 Some(result.and_then(|data| {
509 let len = data.len();
510 RecordBatch::try_new_with_options(
511 self.schema.clone(),
512 StructArray::from(data).into_parts().1,
513 &RecordBatchOptions::new().with_row_count(Some(len)),
514 )
515 }))
516 } else {
517 let message =
518 format!("Cannot get next batch from input stream. Error code: {ret_code}");
519 let message = match unsafe { producer_error(&raw mut self.stream) } {
522 Some(producer_message) => format!("{message}. Producer error: {producer_message}"),
523 None => message,
524 };
525 Some(Err(ArrowError::CDataInterface(message)))
526 }
527 }
528}
529
530impl RecordBatchReader for ArrowArrayStreamReader {
531 fn schema(&self) -> SchemaRef {
532 self.schema.clone()
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539 use std::collections::HashMap;
540
541 use arrow_schema::Field;
542
543 use crate::array::Int32Array;
544 use crate::ffi::from_ffi;
545
546 struct TestRecordBatchReader {
547 schema: SchemaRef,
548 iter: Box<dyn Iterator<Item = Result<RecordBatch>> + Send>,
549 }
550
551 impl TestRecordBatchReader {
552 pub fn new(
553 schema: SchemaRef,
554 iter: Box<dyn Iterator<Item = Result<RecordBatch>> + Send>,
555 ) -> TestRecordBatchReader {
556 TestRecordBatchReader { schema, iter }
557 }
558 }
559
560 impl Iterator for TestRecordBatchReader {
561 type Item = Result<RecordBatch>;
562
563 fn next(&mut self) -> Option<Self::Item> {
564 self.iter.next()
565 }
566 }
567
568 impl RecordBatchReader for TestRecordBatchReader {
569 fn schema(&self) -> SchemaRef {
570 self.schema.clone()
571 }
572 }
573
574 fn _test_round_trip_export(batch: RecordBatch, schema: Arc<Schema>) -> Result<()> {
575 let iter = Box::new(vec![batch.clone(), batch.clone()].into_iter().map(Ok)) as _;
576
577 let reader = Box::new(TestRecordBatchReader::new(schema.clone(), iter));
578
579 let mut ffi_stream = FFI_ArrowArrayStream::new(reader);
581
582 let mut ffi_schema = FFI_ArrowSchema::empty();
584 let ret_code = unsafe { get_schema(&raw mut ffi_stream, &raw mut ffi_schema) };
585 assert_eq!(ret_code, 0);
586
587 let exported_schema = Schema::try_from(&ffi_schema).unwrap();
588 assert_eq!(&exported_schema, schema.as_ref());
589
590 let mut produced_batches = vec![];
592 loop {
593 let mut ffi_array = FFI_ArrowArray::empty();
594 let ret_code = unsafe { get_next(&raw mut ffi_stream, &raw mut ffi_array) };
595 assert_eq!(ret_code, 0);
596
597 if ffi_array.is_released() {
599 break;
600 }
601
602 let array = unsafe { from_ffi(ffi_array, &ffi_schema) }.unwrap();
603 let len = array.len();
604
605 let record_batch = RecordBatch::try_new_with_options(
606 SchemaRef::from(exported_schema.clone()),
607 StructArray::from(array).into_parts().1,
608 &RecordBatchOptions::new().with_row_count(Some(len)),
609 )
610 .unwrap();
611 produced_batches.push(record_batch);
612 }
613
614 assert_eq!(produced_batches, vec![batch.clone(), batch]);
615
616 Ok(())
617 }
618
619 fn _test_round_trip_import(batch: RecordBatch, schema: Arc<Schema>) -> Result<()> {
620 let iter = Box::new(vec![batch.clone(), batch.clone()].into_iter().map(Ok)) as _;
621
622 let reader = Box::new(TestRecordBatchReader::new(schema.clone(), iter));
623
624 let stream = FFI_ArrowArrayStream::new(reader);
626 let stream_reader = ArrowArrayStreamReader::try_new(stream).unwrap();
627
628 let imported_schema = stream_reader.schema();
629 assert_eq!(imported_schema, schema);
630
631 let mut produced_batches = vec![];
632 for batch in stream_reader {
633 produced_batches.push(batch.unwrap());
634 }
635
636 assert_eq!(produced_batches, vec![batch.clone(), batch]);
637
638 Ok(())
639 }
640
641 #[test]
642 fn test_stream_round_trip() {
643 let array = Int32Array::from(vec![Some(2), None, Some(1), None]);
644 let array: Arc<dyn Array> = Arc::new(array);
645 let metadata = HashMap::from([("foo".to_owned(), "bar".to_owned())]);
646
647 let schema = Arc::new(Schema::new_with_metadata(
648 vec![
649 Field::new("a", array.data_type().clone(), true).with_metadata(metadata.clone()),
650 Field::new("b", array.data_type().clone(), true).with_metadata(metadata.clone()),
651 Field::new("c", array.data_type().clone(), true).with_metadata(metadata.clone()),
652 ],
653 metadata,
654 ));
655 let batch = RecordBatch::try_new(schema.clone(), vec![array.clone(), array.clone(), array])
656 .unwrap();
657
658 _test_round_trip_export(batch.clone(), schema.clone()).unwrap();
659 _test_round_trip_import(batch, schema).unwrap();
660 }
661
662 #[test]
663 fn test_stream_round_trip_no_columns() {
664 let metadata = HashMap::from([("foo".to_owned(), "bar".to_owned())]);
665
666 let schema = Arc::new(Schema::new_with_metadata(Vec::<Field>::new(), metadata));
667 let batch = RecordBatch::try_new_with_options(
668 schema.clone(),
669 Vec::<Arc<dyn Array>>::new(),
670 &RecordBatchOptions::new().with_row_count(Some(10)),
671 )
672 .unwrap();
673
674 _test_round_trip_export(batch.clone(), schema.clone()).unwrap();
675 _test_round_trip_import(batch, schema).unwrap();
676 }
677
678 #[test]
679 fn test_error_import() -> Result<()> {
680 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
681
682 let iter =
683 Box::new(vec![Err(ArrowError::MemoryError("out of memory".to_string()))].into_iter());
684
685 let reader = Box::new(TestRecordBatchReader::new(schema.clone(), iter));
686
687 let stream = FFI_ArrowArrayStream::new(reader);
689 let stream_reader = ArrowArrayStreamReader::try_new(stream).unwrap();
690
691 let imported_schema = stream_reader.schema();
692 assert_eq!(imported_schema, schema);
693
694 let mut produced_batches = vec![];
695 for batch in stream_reader {
696 produced_batches.push(batch);
697 }
698
699 assert_eq!(produced_batches.len(), 1);
701 assert_eq!(
702 produced_batches[0].as_ref().unwrap_err().to_string(),
703 format!(
704 "C Data interface error: Cannot get next batch from input stream. \
705 Error code: {ENOMEM}. Producer error: Memory error: out of memory"
706 )
707 );
708
709 Ok(())
710 }
711
712 unsafe extern "C" fn failing_get_schema(
713 _stream: *mut FFI_ArrowArrayStream,
714 _out: *mut FFI_ArrowSchema,
715 ) -> c_int {
716 EIO
717 }
718
719 unsafe extern "C" fn working_get_schema(
720 _stream: *mut FFI_ArrowArrayStream,
721 out: *mut FFI_ArrowSchema,
722 ) -> c_int {
723 let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
724 unsafe { std::ptr::write(out, FFI_ArrowSchema::try_from(&schema).unwrap()) };
725 0
726 }
727
728 unsafe extern "C" fn failing_get_next(
729 _stream: *mut FFI_ArrowArrayStream,
730 _out: *mut FFI_ArrowArray,
731 ) -> c_int {
732 EIO
733 }
734
735 unsafe extern "C" fn producer_last_error(_stream: *mut FFI_ArrowArrayStream) -> *const c_char {
736 c"the producer failed".as_ptr()
737 }
738
739 unsafe extern "C" fn null_last_error(_stream: *mut FFI_ArrowArrayStream) -> *const c_char {
740 std::ptr::null()
741 }
742
743 unsafe extern "C" fn mark_released(stream: *mut FFI_ArrowArrayStream) {
744 unsafe { (*stream).release = None };
745 }
746
747 fn failing_stream(
748 get_last_error: Option<unsafe extern "C" fn(*mut FFI_ArrowArrayStream) -> *const c_char>,
749 ) -> FFI_ArrowArrayStream {
750 let mut stream = FFI_ArrowArrayStream::empty();
751 stream.get_schema = Some(failing_get_schema);
752 stream.get_next = Some(failing_get_next);
753 stream.get_last_error = get_last_error;
754 stream.release = Some(mark_released);
755 stream
756 }
757
758 #[test]
759 fn test_import_schema_error_reports_producer_message() {
760 let err =
761 ArrowArrayStreamReader::try_new(failing_stream(Some(producer_last_error))).unwrap_err();
762 assert_eq!(
763 err.to_string(),
764 format!(
765 "C Data interface error: Cannot get schema from input stream. \
766 Error code: {EIO}. Producer error: the producer failed"
767 )
768 );
769 }
770
771 #[test]
772 fn test_import_schema_error_without_producer_message() {
773 let err =
776 ArrowArrayStreamReader::try_new(failing_stream(Some(null_last_error))).unwrap_err();
777 assert_eq!(
778 err.to_string(),
779 format!(
780 "C Data interface error: Cannot get schema from input stream. Error code: {EIO}"
781 )
782 );
783 }
784
785 #[test]
786 fn test_import_schema_error_without_error_callback() {
787 let err = ArrowArrayStreamReader::try_new(failing_stream(None)).unwrap_err();
788 assert_eq!(
789 err.to_string(),
790 format!(
791 "C Data interface error: Cannot get schema from input stream. Error code: {EIO}"
792 )
793 );
794 }
795
796 #[test]
797 fn test_import_next_error_without_producer_message() {
798 let mut stream = failing_stream(Some(null_last_error));
801 stream.get_schema = Some(working_get_schema);
802
803 let err = ArrowArrayStreamReader::try_new(stream)
804 .unwrap()
805 .next()
806 .unwrap()
807 .unwrap_err();
808 assert_eq!(
809 err.to_string(),
810 format!(
811 "C Data interface error: Cannot get next batch from input stream. Error code: {EIO}"
812 )
813 );
814 }
815
816 static STREAM_WRAPPER_RAN: std::sync::atomic::AtomicBool =
820 std::sync::atomic::AtomicBool::new(false);
821
822 struct StreamWrapperData {
823 original_release: Option<unsafe extern "C" fn(*mut FFI_ArrowArrayStream)>,
824 original_private_data: *mut c_void,
825 }
826
827 unsafe extern "C" fn wrapping_release(stream: *mut FFI_ArrowArrayStream) {
828 use std::sync::atomic::Ordering;
829 let stream = unsafe { &mut *stream };
830 let data = unsafe { Box::from_raw(stream.private_data().cast::<StreamWrapperData>()) };
831 STREAM_WRAPPER_RAN.store(true, Ordering::SeqCst);
832 unsafe { stream.set_release(data.original_release) };
833 unsafe { stream.set_private_data(data.original_private_data) };
834 if let Some(release) = stream.release() {
835 unsafe { release(stream) };
836 }
837 }
838
839 #[test]
840 fn test_wrap_release_callback() {
841 use std::sync::atomic::Ordering;
842
843 let batch_reader = Box::new(TestRecordBatchReader::new(
844 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)])),
845 Box::new(std::iter::empty()),
846 ));
847 let mut stream = FFI_ArrowArrayStream::new(batch_reader);
848
849 let data = Box::new(StreamWrapperData {
850 original_release: stream.release(),
851 original_private_data: stream.private_data(),
852 });
853 unsafe { stream.set_release(Some(wrapping_release)) };
854 unsafe { stream.set_private_data(Box::into_raw(data).cast::<c_void>()) };
855
856 drop(stream); assert!(STREAM_WRAPPER_RAN.load(Ordering::SeqCst));
858 }
859
860 struct ArrayStreamPrivateData {
864 field: Field,
865 arrays: std::vec::IntoIter<Int32Array>,
866 }
867
868 unsafe extern "C" fn array_stream_get_schema(
869 stream: *mut FFI_ArrowArrayStream,
870 out: *mut FFI_ArrowSchema,
871 ) -> c_int {
872 let private_data = unsafe { &*(*stream).private_data().cast::<ArrayStreamPrivateData>() };
873 let schema = FFI_ArrowSchema::try_from(&private_data.field).unwrap();
874 unsafe { std::ptr::write(out, schema) };
875 0
876 }
877
878 unsafe extern "C" fn array_stream_get_next(
879 stream: *mut FFI_ArrowArrayStream,
880 out: *mut FFI_ArrowArray,
881 ) -> c_int {
882 let private_data =
883 unsafe { &mut *(*stream).private_data().cast::<ArrayStreamPrivateData>() };
884 match private_data.arrays.next() {
885 None => unsafe { std::ptr::write(out, FFI_ArrowArray::empty()) },
887 Some(array) => unsafe {
888 std::ptr::write_unaligned(out, FFI_ArrowArray::new(&array.to_data()))
889 },
890 }
891 0
892 }
893
894 unsafe extern "C" fn array_stream_get_last_error(
895 _stream: *mut FFI_ArrowArrayStream,
896 ) -> *const c_char {
897 std::ptr::null()
898 }
899
900 unsafe extern "C" fn array_stream_release(stream: *mut FFI_ArrowArrayStream) {
901 let private_data =
902 unsafe { Box::from_raw((*stream).private_data().cast::<ArrayStreamPrivateData>()) };
903 drop(private_data);
904 unsafe { std::ptr::write(stream, FFI_ArrowArrayStream::empty()) };
907 }
908
909 fn array_stream(field: Field, arrays: Vec<Int32Array>) -> FFI_ArrowArrayStream {
910 let private_data = Box::new(ArrayStreamPrivateData {
911 field,
912 arrays: arrays.into_iter(),
913 });
914
915 unsafe {
916 FFI_ArrowArrayStream::new_unchecked(
917 Some(array_stream_get_schema),
918 Some(array_stream_get_next),
919 Some(array_stream_get_last_error),
920 Some(array_stream_release),
921 Box::into_raw(private_data).cast::<c_void>(),
922 )
923 }
924 }
925
926 #[test]
927 fn test_new_unchecked_exports_stream_of_arrays() {
928 let field = Field::new("a", DataType::Int32, true);
929 let arrays = vec![
930 Int32Array::from(vec![1, 2, 3]),
931 Int32Array::from(vec![4, 5]),
932 ];
933 let mut stream = array_stream(field.clone(), arrays.clone());
934
935 let get_schema_fn = stream.get_schema().unwrap();
938 let get_next_fn = stream.get_next().unwrap();
939 let get_last_error_fn = stream.get_last_error().unwrap();
940
941 let mut ffi_schema = FFI_ArrowSchema::empty();
942 let ret_code = unsafe { get_schema_fn(&raw mut stream, &raw mut ffi_schema) };
943 assert_eq!(ret_code, 0);
944 assert_eq!(Field::try_from(&ffi_schema).unwrap(), field);
945
946 let mut produced = vec![];
947 loop {
948 let mut ffi_array = FFI_ArrowArray::empty();
949 let ret_code = unsafe { get_next_fn(&raw mut stream, &raw mut ffi_array) };
950 assert_eq!(ret_code, 0);
951
952 if ffi_array.is_released() {
954 break;
955 }
956
957 let data = unsafe { from_ffi(ffi_array, &ffi_schema) }.unwrap();
958 produced.push(Int32Array::from(data));
959 }
960
961 assert_eq!(produced, arrays);
962
963 assert!(unsafe { get_last_error_fn(&raw mut stream) }.is_null());
965
966 drop(stream);
968 }
969
970 #[test]
971 fn test_accessors_report_an_empty_stream_as_released() {
972 let stream = FFI_ArrowArrayStream::empty();
973 assert!(stream.get_schema().is_none());
974 assert!(stream.get_next().is_none());
975 assert!(stream.get_last_error().is_none());
976 assert!(stream.release().is_none());
977 assert!(stream.private_data().is_null());
978 }
979}