1use 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#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
83use libc::{EINVAL, EIO, ENOMEM, ENOSYS};
84
85#[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#[repr(C)]
100#[derive(Debug)]
101pub struct FFI_ArrowArrayStream {
102 get_schema: Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowSchema) -> c_int>,
106 get_next: Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowArray) -> c_int>,
108 get_last_error: Option<unsafe extern "C" fn(arg1: *mut Self) -> *const c_char>,
110 release: Option<unsafe extern "C" fn(arg1: *mut Self)>,
112 private_data: *mut c_void,
114}
115
116unsafe impl Send for FFI_ArrowArrayStream {}
117
118unsafe 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
140unsafe 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
148unsafe 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
156unsafe extern "C" fn get_last_error(stream: *mut FFI_ArrowArrayStream) -> *const c_char {
158 let mut ffi_stream = ExportedArrayStream { stream };
159 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 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 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 pub unsafe fn from_raw(raw_stream: *mut FFI_ArrowArrayStream) -> Self {
242 unsafe { std::ptr::replace(raw_stream, Self::empty()) }
243 }
244
245 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 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 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 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 pub fn release(&self) -> Option<unsafe extern "C" fn(arg1: *mut Self)> {
294 self.release
295 }
296
297 pub fn private_data(&self) -> *mut c_void {
299 self.private_data
300 }
301
302 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 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 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#[derive(Debug)]
412pub struct RecordBatchStreamReader {
413 stream: FFI_ArrowArrayStream,
414 schema: SchemaRef,
415}
416
417#[deprecated(since = "60.1.0", note = "Use `RecordBatchStreamReader` instead")]
419pub type ArrowArrayStreamReader = RecordBatchStreamReader;
420
421unsafe 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
447fn 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
461fn 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 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 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 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 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 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#[derive(Debug)]
575pub struct ArrayStreamReader {
576 stream: FFI_ArrowArrayStream,
577 field: FieldRef,
578}
579
580impl ArrayStreamReader {
581 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 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 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 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 let mut ffi_stream = FFI_ArrowArrayStream::new(reader);
687
688 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 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 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 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 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 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 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 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 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 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 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); assert!(STREAM_WRAPPER_RAN.load(Ordering::SeqCst));
1004 }
1005
1006 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 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 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 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 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 assert!(unsafe { get_last_error_fn(&raw mut stream) }.is_null());
1111
1112 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 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}