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)]
98#[allow(non_camel_case_types)]
99pub struct FFI_ArrowArrayStream {
100 pub get_schema:
102 Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowSchema) -> c_int>,
103 pub get_next: Option<unsafe extern "C" fn(arg1: *mut Self, out: *mut FFI_ArrowArray) -> c_int>,
105 pub get_last_error: Option<unsafe extern "C" fn(arg1: *mut Self) -> *const c_char>,
107 pub release: Option<unsafe extern "C" fn(arg1: *mut Self)>,
109 pub 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 as *mut 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) as *mut 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
218struct ExportedArrayStream {
219 stream: *mut FFI_ArrowArrayStream,
220}
221
222impl ExportedArrayStream {
223 fn get_private_data(&mut self) -> &mut StreamPrivateData {
224 unsafe { &mut *((*self.stream).private_data as *mut StreamPrivateData) }
225 }
226
227 pub fn get_schema(&mut self, out: *mut FFI_ArrowSchema) -> i32 {
228 let private_data = self.get_private_data();
229 let reader = &private_data.batch_reader;
230
231 let schema = FFI_ArrowSchema::try_from(reader.schema().as_ref());
232
233 match schema {
234 Ok(schema) => {
235 unsafe { std::ptr::copy(addr_of!(schema), out, 1) };
236 std::mem::forget(schema);
237 0
238 }
239 Err(ref err) => {
240 private_data.last_error = Some(
241 CString::new(err.to_string()).expect("Error string has a null byte in it."),
242 );
243 get_error_code(err)
244 }
245 }
246 }
247
248 pub fn get_next(&mut self, out: *mut FFI_ArrowArray) -> i32 {
249 let private_data = self.get_private_data();
250 let reader = &mut private_data.batch_reader;
251
252 match reader.next() {
253 None => {
254 unsafe { std::ptr::write(out, FFI_ArrowArray::empty()) }
256 0
257 }
258 Some(next_batch) => {
259 if let Ok(batch) = next_batch {
260 let struct_array = StructArray::from(batch);
261 let array = FFI_ArrowArray::new(&struct_array.to_data());
262
263 unsafe { std::ptr::write_unaligned(out, array) };
264 0
265 } else {
266 let err = &next_batch.unwrap_err();
267 private_data.last_error = Some(
268 CString::new(err.to_string()).expect("Error string has a null byte in it."),
269 );
270 get_error_code(err)
271 }
272 }
273 }
274 }
275
276 pub fn get_last_error(&mut self) -> Option<&CString> {
277 self.get_private_data().last_error.as_ref()
278 }
279}
280
281fn get_error_code(err: &ArrowError) -> i32 {
282 match err {
283 ArrowError::NotYetImplemented(_) => ENOSYS,
284 ArrowError::MemoryError(_) => ENOMEM,
285 ArrowError::IoError(_, _) => EIO,
286 _ => EINVAL,
287 }
288}
289
290#[derive(Debug)]
296pub struct ArrowArrayStreamReader {
297 stream: FFI_ArrowArrayStream,
298 schema: SchemaRef,
299}
300
301fn get_stream_schema(stream_ptr: *mut FFI_ArrowArrayStream) -> Result<SchemaRef> {
304 let mut schema = FFI_ArrowSchema::empty();
305
306 let ret_code = unsafe { (*stream_ptr).get_schema.unwrap()(stream_ptr, &mut schema) };
307
308 if ret_code == 0 {
309 let schema = Schema::try_from(&schema)?;
310 Ok(Arc::new(schema))
311 } else {
312 Err(ArrowError::CDataInterface(format!(
313 "Cannot get schema from input stream. Error code: {ret_code:?}"
314 )))
315 }
316}
317
318impl ArrowArrayStreamReader {
319 #[allow(dead_code)]
322 pub fn try_new(mut stream: FFI_ArrowArrayStream) -> Result<Self> {
323 if stream.release.is_none() {
324 return Err(ArrowError::CDataInterface(
325 "input stream is already released".to_string(),
326 ));
327 }
328
329 let schema = get_stream_schema(&mut stream)?;
330
331 Ok(Self { stream, schema })
332 }
333
334 pub unsafe fn from_raw(raw_stream: *mut FFI_ArrowArrayStream) -> Result<Self> {
345 Self::try_new(unsafe { FFI_ArrowArrayStream::from_raw(raw_stream) })
346 }
347
348 fn get_stream_last_error(&mut self) -> Option<String> {
350 let get_last_error = self.stream.get_last_error?;
351
352 let error_str = unsafe { get_last_error(&mut self.stream) };
353 if error_str.is_null() {
354 return None;
355 }
356
357 let error_str = unsafe { CStr::from_ptr(error_str) };
358 Some(error_str.to_string_lossy().to_string())
359 }
360}
361
362impl Iterator for ArrowArrayStreamReader {
363 type Item = Result<RecordBatch>;
364
365 fn next(&mut self) -> Option<Self::Item> {
366 let mut array = FFI_ArrowArray::empty();
367
368 let ret_code = unsafe { self.stream.get_next.unwrap()(&mut self.stream, &mut array) };
369
370 if ret_code == 0 {
371 if array.is_released() {
373 return None;
374 }
375
376 let result = unsafe {
377 from_ffi_and_data_type(array, DataType::Struct(self.schema().fields().clone()))
378 };
379 Some(result.and_then(|data| {
380 let len = data.len();
381 RecordBatch::try_new_with_options(
382 self.schema.clone(),
383 StructArray::from(data).into_parts().1,
384 &RecordBatchOptions::new().with_row_count(Some(len)),
385 )
386 }))
387 } else {
388 let last_error = self.get_stream_last_error();
389 let err = ArrowError::CDataInterface(last_error.unwrap());
390 Some(Err(err))
391 }
392 }
393}
394
395impl RecordBatchReader for ArrowArrayStreamReader {
396 fn schema(&self) -> SchemaRef {
397 self.schema.clone()
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404 use std::collections::HashMap;
405
406 use arrow_schema::Field;
407
408 use crate::array::Int32Array;
409 use crate::ffi::from_ffi;
410
411 struct TestRecordBatchReader {
412 schema: SchemaRef,
413 iter: Box<dyn Iterator<Item = Result<RecordBatch>> + Send>,
414 }
415
416 impl TestRecordBatchReader {
417 pub fn new(
418 schema: SchemaRef,
419 iter: Box<dyn Iterator<Item = Result<RecordBatch>> + Send>,
420 ) -> Box<TestRecordBatchReader> {
421 Box::new(TestRecordBatchReader { schema, iter })
422 }
423 }
424
425 impl Iterator for TestRecordBatchReader {
426 type Item = Result<RecordBatch>;
427
428 fn next(&mut self) -> Option<Self::Item> {
429 self.iter.next()
430 }
431 }
432
433 impl RecordBatchReader for TestRecordBatchReader {
434 fn schema(&self) -> SchemaRef {
435 self.schema.clone()
436 }
437 }
438
439 fn _test_round_trip_export(batch: RecordBatch, schema: Arc<Schema>) -> Result<()> {
440 let iter = Box::new(vec![batch.clone(), batch.clone()].into_iter().map(Ok)) as _;
441
442 let reader = TestRecordBatchReader::new(schema.clone(), iter);
443
444 let mut ffi_stream = FFI_ArrowArrayStream::new(reader);
446
447 let mut ffi_schema = FFI_ArrowSchema::empty();
449 let ret_code = unsafe { get_schema(&mut ffi_stream, &mut ffi_schema) };
450 assert_eq!(ret_code, 0);
451
452 let exported_schema = Schema::try_from(&ffi_schema).unwrap();
453 assert_eq!(&exported_schema, schema.as_ref());
454
455 let mut produced_batches = vec![];
457 loop {
458 let mut ffi_array = FFI_ArrowArray::empty();
459 let ret_code = unsafe { get_next(&mut ffi_stream, &mut ffi_array) };
460 assert_eq!(ret_code, 0);
461
462 if ffi_array.is_released() {
464 break;
465 }
466
467 let array = unsafe { from_ffi(ffi_array, &ffi_schema) }.unwrap();
468 let len = array.len();
469
470 let record_batch = RecordBatch::try_new_with_options(
471 SchemaRef::from(exported_schema.clone()),
472 StructArray::from(array).into_parts().1,
473 &RecordBatchOptions::new().with_row_count(Some(len)),
474 )
475 .unwrap();
476 produced_batches.push(record_batch);
477 }
478
479 assert_eq!(produced_batches, vec![batch.clone(), batch]);
480
481 Ok(())
482 }
483
484 fn _test_round_trip_import(batch: RecordBatch, schema: Arc<Schema>) -> Result<()> {
485 let iter = Box::new(vec![batch.clone(), batch.clone()].into_iter().map(Ok)) as _;
486
487 let reader = TestRecordBatchReader::new(schema.clone(), iter);
488
489 let stream = FFI_ArrowArrayStream::new(reader);
491 let stream_reader = ArrowArrayStreamReader::try_new(stream).unwrap();
492
493 let imported_schema = stream_reader.schema();
494 assert_eq!(imported_schema, schema);
495
496 let mut produced_batches = vec![];
497 for batch in stream_reader {
498 produced_batches.push(batch.unwrap());
499 }
500
501 assert_eq!(produced_batches, vec![batch.clone(), batch]);
502
503 Ok(())
504 }
505
506 #[test]
507 fn test_stream_round_trip() {
508 let array = Int32Array::from(vec![Some(2), None, Some(1), None]);
509 let array: Arc<dyn Array> = Arc::new(array);
510 let metadata = HashMap::from([("foo".to_owned(), "bar".to_owned())]);
511
512 let schema = Arc::new(Schema::new_with_metadata(
513 vec![
514 Field::new("a", array.data_type().clone(), true).with_metadata(metadata.clone()),
515 Field::new("b", array.data_type().clone(), true).with_metadata(metadata.clone()),
516 Field::new("c", array.data_type().clone(), true).with_metadata(metadata.clone()),
517 ],
518 metadata,
519 ));
520 let batch = RecordBatch::try_new(schema.clone(), vec![array.clone(), array.clone(), array])
521 .unwrap();
522
523 _test_round_trip_export(batch.clone(), schema.clone()).unwrap();
524 _test_round_trip_import(batch, schema).unwrap();
525 }
526
527 #[test]
528 fn test_stream_round_trip_no_columns() {
529 let metadata = HashMap::from([("foo".to_owned(), "bar".to_owned())]);
530
531 let schema = Arc::new(Schema::new_with_metadata(Vec::<Field>::new(), metadata));
532 let batch = RecordBatch::try_new_with_options(
533 schema.clone(),
534 Vec::<Arc<dyn Array>>::new(),
535 &RecordBatchOptions::new().with_row_count(Some(10)),
536 )
537 .unwrap();
538
539 _test_round_trip_export(batch.clone(), schema.clone()).unwrap();
540 _test_round_trip_import(batch, schema).unwrap();
541 }
542
543 #[test]
544 fn test_error_import() -> Result<()> {
545 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, true)]));
546
547 let iter = Box::new(vec![Err(ArrowError::MemoryError("".to_string()))].into_iter());
548
549 let reader = TestRecordBatchReader::new(schema.clone(), iter);
550
551 let stream = FFI_ArrowArrayStream::new(reader);
553 let stream_reader = ArrowArrayStreamReader::try_new(stream).unwrap();
554
555 let imported_schema = stream_reader.schema();
556 assert_eq!(imported_schema, schema);
557
558 let mut produced_batches = vec![];
559 for batch in stream_reader {
560 produced_batches.push(batch);
561 }
562
563 assert_eq!(produced_batches.len(), 1);
565 assert!(produced_batches[0].is_err());
566
567 Ok(())
568 }
569}