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 from_raw(raw_stream: *mut FFI_ArrowArrayStream) -> Self {
203 unsafe { std::ptr::replace(raw_stream, Self::empty()) }
204 }
205
206 pub fn empty() -> Self {
208 Self {
209 get_schema: None,
210 get_next: None,
211 get_last_error: None,
212 release: None,
213 private_data: std::ptr::null_mut(),
214 }
215 }
216
217 pub fn release(&self) -> Option<unsafe extern "C" fn(arg1: *mut Self)> {
219 self.release
220 }
221
222 pub fn private_data(&self) -> *mut c_void {
224 self.private_data
225 }
226
227 pub unsafe fn set_release(
239 &mut self,
240 release: Option<unsafe extern "C" fn(arg1: *mut Self)>,
241 ) -> Option<unsafe extern "C" fn(arg1: *mut Self)> {
242 std::mem::replace(&mut self.release, release)
243 }
244
245 pub unsafe fn set_private_data(&mut self, private_data: *mut c_void) -> *mut c_void {
253 std::mem::replace(&mut self.private_data, private_data)
254 }
255}
256
257struct ExportedArrayStream {
258 stream: *mut FFI_ArrowArrayStream,
259}
260
261impl ExportedArrayStream {
262 fn get_private_data(&mut self) -> &mut StreamPrivateData {
263 unsafe { &mut *(*self.stream).private_data.cast::<StreamPrivateData>() }
264 }
265
266 pub fn get_schema(&mut self, out: *mut FFI_ArrowSchema) -> i32 {
267 let private_data = self.get_private_data();
268 let reader = &private_data.batch_reader;
269
270 let schema = FFI_ArrowSchema::try_from(reader.schema().as_ref());
271
272 match schema {
273 Ok(schema) => {
274 unsafe { std::ptr::copy(addr_of!(schema), out, 1) };
275 std::mem::forget(schema);
276 0
277 }
278 Err(ref err) => {
279 private_data.last_error = Some(
280 CString::new(err.to_string()).expect("Error string has a null byte in it."),
281 );
282 get_error_code(err)
283 }
284 }
285 }
286
287 pub fn get_next(&mut self, out: *mut FFI_ArrowArray) -> i32 {
288 let private_data = self.get_private_data();
289 let reader = &mut private_data.batch_reader;
290
291 match reader.next() {
292 None => {
293 unsafe { std::ptr::write(out, FFI_ArrowArray::empty()) }
295 0
296 }
297 Some(next_batch) => {
298 if let Ok(batch) = next_batch {
299 let struct_array = StructArray::from(batch);
300 let array = FFI_ArrowArray::new(&struct_array.to_data());
301
302 unsafe { std::ptr::write_unaligned(out, array) };
303 0
304 } else {
305 let err = &next_batch.unwrap_err();
306 private_data.last_error = Some(
307 CString::new(err.to_string()).expect("Error string has a null byte in it."),
308 );
309 get_error_code(err)
310 }
311 }
312 }
313 }
314
315 pub fn get_last_error(&mut self) -> Option<&CString> {
316 self.get_private_data().last_error.as_ref()
317 }
318}
319
320fn get_error_code(err: &ArrowError) -> i32 {
321 match err {
322 ArrowError::NotYetImplemented(_) => ENOSYS,
323 ArrowError::MemoryError(_) => ENOMEM,
324 ArrowError::IoError(_, _) => EIO,
325 _ => EINVAL,
326 }
327}
328
329#[derive(Debug)]
335pub struct ArrowArrayStreamReader {
336 stream: FFI_ArrowArrayStream,
337 schema: SchemaRef,
338}
339
340unsafe fn producer_error(stream_ptr: *mut FFI_ArrowArrayStream) -> Option<String> {
352 let get_last_error = unsafe { (*stream_ptr).get_last_error }?;
353
354 let error_str = unsafe { get_last_error(stream_ptr) };
355 if error_str.is_null() {
356 return None;
357 }
358
359 Some(
360 unsafe { CStr::from_ptr(error_str) }
361 .to_string_lossy()
362 .into_owned(),
363 )
364}
365
366fn get_stream_schema(stream_ptr: *mut FFI_ArrowArrayStream) -> Result<SchemaRef> {
369 let mut schema = FFI_ArrowSchema::empty();
370
371 let ret_code = unsafe { (*stream_ptr).get_schema.unwrap()(stream_ptr, &raw mut schema) };
372
373 if ret_code == 0 {
374 let schema = Schema::try_from(&schema)?;
375 Ok(Arc::new(schema))
376 } else {
377 let message = format!("Cannot get schema from input stream. Error code: {ret_code}");
378 let message = match unsafe { producer_error(stream_ptr) } {
381 Some(producer_message) => format!("{message}. Producer error: {producer_message}"),
382 None => message,
383 };
384 Err(ArrowError::CDataInterface(message))
385 }
386}
387
388impl ArrowArrayStreamReader {
389 pub fn try_new(mut stream: FFI_ArrowArrayStream) -> Result<Self> {
392 if stream.release.is_none() {
393 return Err(ArrowError::CDataInterface(
394 "input stream is already released".to_string(),
395 ));
396 }
397
398 let schema = get_stream_schema(&raw mut stream)?;
399
400 Ok(Self { stream, schema })
401 }
402
403 pub unsafe fn from_raw(raw_stream: *mut FFI_ArrowArrayStream) -> Result<Self> {
414 Self::try_new(unsafe { FFI_ArrowArrayStream::from_raw(raw_stream) })
415 }
416}
417
418impl Iterator for ArrowArrayStreamReader {
419 type Item = Result<RecordBatch>;
420
421 fn next(&mut self) -> Option<Self::Item> {
422 let mut array = FFI_ArrowArray::empty();
423
424 let ret_code =
425 unsafe { self.stream.get_next.unwrap()(&raw mut self.stream, &raw mut array) };
426
427 if ret_code == 0 {
428 if array.is_released() {
430 return None;
431 }
432
433 let result = unsafe {
434 from_ffi_and_data_type(array, DataType::Struct(self.schema().fields().clone()))
435 };
436 Some(result.and_then(|data| {
437 let len = data.len();
438 RecordBatch::try_new_with_options(
439 self.schema.clone(),
440 StructArray::from(data).into_parts().1,
441 &RecordBatchOptions::new().with_row_count(Some(len)),
442 )
443 }))
444 } else {
445 let message =
446 format!("Cannot get next batch from input stream. Error code: {ret_code}");
447 let message = match unsafe { producer_error(&raw mut self.stream) } {
450 Some(producer_message) => format!("{message}. Producer error: {producer_message}"),
451 None => message,
452 };
453 Some(Err(ArrowError::CDataInterface(message)))
454 }
455 }
456}
457
458impl RecordBatchReader for ArrowArrayStreamReader {
459 fn schema(&self) -> SchemaRef {
460 self.schema.clone()
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467 use std::collections::HashMap;
468
469 use arrow_schema::Field;
470
471 use crate::array::Int32Array;
472 use crate::ffi::from_ffi;
473
474 struct TestRecordBatchReader {
475 schema: SchemaRef,
476 iter: Box<dyn Iterator<Item = Result<RecordBatch>> + Send>,
477 }
478
479 impl TestRecordBatchReader {
480 pub fn new(
481 schema: SchemaRef,
482 iter: Box<dyn Iterator<Item = Result<RecordBatch>> + Send>,
483 ) -> TestRecordBatchReader {
484 TestRecordBatchReader { schema, iter }
485 }
486 }
487
488 impl Iterator for TestRecordBatchReader {
489 type Item = Result<RecordBatch>;
490
491 fn next(&mut self) -> Option<Self::Item> {
492 self.iter.next()
493 }
494 }
495
496 impl RecordBatchReader for TestRecordBatchReader {
497 fn schema(&self) -> SchemaRef {
498 self.schema.clone()
499 }
500 }
501
502 fn _test_round_trip_export(batch: RecordBatch, schema: Arc<Schema>) -> Result<()> {
503 let iter = Box::new(vec![batch.clone(), batch.clone()].into_iter().map(Ok)) as _;
504
505 let reader = Box::new(TestRecordBatchReader::new(schema.clone(), iter));
506
507 let mut ffi_stream = FFI_ArrowArrayStream::new(reader);
509
510 let mut ffi_schema = FFI_ArrowSchema::empty();
512 let ret_code = unsafe { get_schema(&raw mut ffi_stream, &raw mut ffi_schema) };
513 assert_eq!(ret_code, 0);
514
515 let exported_schema = Schema::try_from(&ffi_schema).unwrap();
516 assert_eq!(&exported_schema, schema.as_ref());
517
518 let mut produced_batches = vec![];
520 loop {
521 let mut ffi_array = FFI_ArrowArray::empty();
522 let ret_code = unsafe { get_next(&raw mut ffi_stream, &raw mut ffi_array) };
523 assert_eq!(ret_code, 0);
524
525 if ffi_array.is_released() {
527 break;
528 }
529
530 let array = unsafe { from_ffi(ffi_array, &ffi_schema) }.unwrap();
531 let len = array.len();
532
533 let record_batch = RecordBatch::try_new_with_options(
534 SchemaRef::from(exported_schema.clone()),
535 StructArray::from(array).into_parts().1,
536 &RecordBatchOptions::new().with_row_count(Some(len)),
537 )
538 .unwrap();
539 produced_batches.push(record_batch);
540 }
541
542 assert_eq!(produced_batches, vec![batch.clone(), batch]);
543
544 Ok(())
545 }
546
547 fn _test_round_trip_import(batch: RecordBatch, schema: Arc<Schema>) -> Result<()> {
548 let iter = Box::new(vec![batch.clone(), batch.clone()].into_iter().map(Ok)) as _;
549
550 let reader = Box::new(TestRecordBatchReader::new(schema.clone(), iter));
551
552 let stream = FFI_ArrowArrayStream::new(reader);
554 let stream_reader = ArrowArrayStreamReader::try_new(stream).unwrap();
555
556 let imported_schema = stream_reader.schema();
557 assert_eq!(imported_schema, schema);
558
559 let mut produced_batches = vec![];
560 for batch in stream_reader {
561 produced_batches.push(batch.unwrap());
562 }
563
564 assert_eq!(produced_batches, vec![batch.clone(), batch]);
565
566 Ok(())
567 }
568
569 #[test]
570 fn test_stream_round_trip() {
571 let array = Int32Array::from(vec![Some(2), None, Some(1), None]);
572 let array: Arc<dyn Array> = Arc::new(array);
573 let metadata = HashMap::from([("foo".to_owned(), "bar".to_owned())]);
574
575 let schema = Arc::new(Schema::new_with_metadata(
576 vec![
577 Field::new("a", array.data_type().clone(), true).with_metadata(metadata.clone()),
578 Field::new("b", array.data_type().clone(), true).with_metadata(metadata.clone()),
579 Field::new("c", array.data_type().clone(), true).with_metadata(metadata.clone()),
580 ],
581 metadata,
582 ));
583 let batch = RecordBatch::try_new(schema.clone(), vec![array.clone(), array.clone(), array])
584 .unwrap();
585
586 _test_round_trip_export(batch.clone(), schema.clone()).unwrap();
587 _test_round_trip_import(batch, schema).unwrap();
588 }
589
590 #[test]
591 fn test_stream_round_trip_no_columns() {
592 let metadata = HashMap::from([("foo".to_owned(), "bar".to_owned())]);
593
594 let schema = Arc::new(Schema::new_with_metadata(Vec::<Field>::new(), metadata));
595 let batch = RecordBatch::try_new_with_options(
596 schema.clone(),
597 Vec::<Arc<dyn Array>>::new(),
598 &RecordBatchOptions::new().with_row_count(Some(10)),
599 )
600 .unwrap();
601
602 _test_round_trip_export(batch.clone(), schema.clone()).unwrap();
603 _test_round_trip_import(batch, schema).unwrap();
604 }
605
606 #[test]
607 fn test_error_import() -> Result<()> {
608 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
609
610 let iter =
611 Box::new(vec![Err(ArrowError::MemoryError("out of memory".to_string()))].into_iter());
612
613 let reader = Box::new(TestRecordBatchReader::new(schema.clone(), iter));
614
615 let stream = FFI_ArrowArrayStream::new(reader);
617 let stream_reader = ArrowArrayStreamReader::try_new(stream).unwrap();
618
619 let imported_schema = stream_reader.schema();
620 assert_eq!(imported_schema, schema);
621
622 let mut produced_batches = vec![];
623 for batch in stream_reader {
624 produced_batches.push(batch);
625 }
626
627 assert_eq!(produced_batches.len(), 1);
629 assert_eq!(
630 produced_batches[0].as_ref().unwrap_err().to_string(),
631 format!(
632 "C Data interface error: Cannot get next batch from input stream. \
633 Error code: {ENOMEM}. Producer error: Memory error: out of memory"
634 )
635 );
636
637 Ok(())
638 }
639
640 unsafe extern "C" fn failing_get_schema(
641 _stream: *mut FFI_ArrowArrayStream,
642 _out: *mut FFI_ArrowSchema,
643 ) -> c_int {
644 EIO
645 }
646
647 unsafe extern "C" fn working_get_schema(
648 _stream: *mut FFI_ArrowArrayStream,
649 out: *mut FFI_ArrowSchema,
650 ) -> c_int {
651 let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
652 unsafe { std::ptr::write(out, FFI_ArrowSchema::try_from(&schema).unwrap()) };
653 0
654 }
655
656 unsafe extern "C" fn failing_get_next(
657 _stream: *mut FFI_ArrowArrayStream,
658 _out: *mut FFI_ArrowArray,
659 ) -> c_int {
660 EIO
661 }
662
663 unsafe extern "C" fn producer_last_error(_stream: *mut FFI_ArrowArrayStream) -> *const c_char {
664 c"the producer failed".as_ptr()
665 }
666
667 unsafe extern "C" fn null_last_error(_stream: *mut FFI_ArrowArrayStream) -> *const c_char {
668 std::ptr::null()
669 }
670
671 unsafe extern "C" fn mark_released(stream: *mut FFI_ArrowArrayStream) {
672 unsafe { (*stream).release = None };
673 }
674
675 fn failing_stream(
676 get_last_error: Option<unsafe extern "C" fn(*mut FFI_ArrowArrayStream) -> *const c_char>,
677 ) -> FFI_ArrowArrayStream {
678 let mut stream = FFI_ArrowArrayStream::empty();
679 stream.get_schema = Some(failing_get_schema);
680 stream.get_next = Some(failing_get_next);
681 stream.get_last_error = get_last_error;
682 stream.release = Some(mark_released);
683 stream
684 }
685
686 #[test]
687 fn test_import_schema_error_reports_producer_message() {
688 let err =
689 ArrowArrayStreamReader::try_new(failing_stream(Some(producer_last_error))).unwrap_err();
690 assert_eq!(
691 err.to_string(),
692 format!(
693 "C Data interface error: Cannot get schema from input stream. \
694 Error code: {EIO}. Producer error: the producer failed"
695 )
696 );
697 }
698
699 #[test]
700 fn test_import_schema_error_without_producer_message() {
701 let err =
704 ArrowArrayStreamReader::try_new(failing_stream(Some(null_last_error))).unwrap_err();
705 assert_eq!(
706 err.to_string(),
707 format!(
708 "C Data interface error: Cannot get schema from input stream. Error code: {EIO}"
709 )
710 );
711 }
712
713 #[test]
714 fn test_import_schema_error_without_error_callback() {
715 let err = ArrowArrayStreamReader::try_new(failing_stream(None)).unwrap_err();
716 assert_eq!(
717 err.to_string(),
718 format!(
719 "C Data interface error: Cannot get schema from input stream. Error code: {EIO}"
720 )
721 );
722 }
723
724 #[test]
725 fn test_import_next_error_without_producer_message() {
726 let mut stream = failing_stream(Some(null_last_error));
729 stream.get_schema = Some(working_get_schema);
730
731 let err = ArrowArrayStreamReader::try_new(stream)
732 .unwrap()
733 .next()
734 .unwrap()
735 .unwrap_err();
736 assert_eq!(
737 err.to_string(),
738 format!(
739 "C Data interface error: Cannot get next batch from input stream. Error code: {EIO}"
740 )
741 );
742 }
743
744 static STREAM_WRAPPER_RAN: std::sync::atomic::AtomicBool =
748 std::sync::atomic::AtomicBool::new(false);
749
750 struct StreamWrapperData {
751 original_release: Option<unsafe extern "C" fn(*mut FFI_ArrowArrayStream)>,
752 original_private_data: *mut c_void,
753 }
754
755 unsafe extern "C" fn wrapping_release(stream: *mut FFI_ArrowArrayStream) {
756 use std::sync::atomic::Ordering;
757 let stream = unsafe { &mut *stream };
758 let data = unsafe { Box::from_raw(stream.private_data().cast::<StreamWrapperData>()) };
759 STREAM_WRAPPER_RAN.store(true, Ordering::SeqCst);
760 unsafe { stream.set_release(data.original_release) };
761 unsafe { stream.set_private_data(data.original_private_data) };
762 if let Some(release) = stream.release() {
763 unsafe { release(stream) };
764 }
765 }
766
767 #[test]
768 fn test_wrap_release_callback() {
769 use std::sync::atomic::Ordering;
770
771 let batch_reader = Box::new(TestRecordBatchReader::new(
772 Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)])),
773 Box::new(std::iter::empty()),
774 ));
775 let mut stream = FFI_ArrowArrayStream::new(batch_reader);
776
777 let data = Box::new(StreamWrapperData {
778 original_release: stream.release(),
779 original_private_data: stream.private_data(),
780 });
781 unsafe { stream.set_release(Some(wrapping_release)) };
782 unsafe { stream.set_private_data(Box::into_raw(data).cast::<c_void>()) };
783
784 drop(stream); assert!(STREAM_WRAPPER_RAN.load(Ordering::SeqCst));
786 }
787}