use eyre::{Context, Result};
use crate::DoraArray;
const _: () = assert!(
size_of::<arrow58::ffi::FFI_ArrowArray>() == size_of::<arrow::ffi::FFI_ArrowArray>(),
"FFI_ArrowArray size differs between Arrow 58 and Arrow 59"
);
const _: () = assert!(
align_of::<arrow58::ffi::FFI_ArrowArray>() == align_of::<arrow::ffi::FFI_ArrowArray>(),
"FFI_ArrowArray alignment differs between Arrow 58 and Arrow 59"
);
const _: () = assert!(
size_of::<arrow58::ffi::FFI_ArrowSchema>() == size_of::<arrow::ffi::FFI_ArrowSchema>(),
"FFI_ArrowSchema size differs between Arrow 58 and Arrow 59"
);
const _: () = assert!(
align_of::<arrow58::ffi::FFI_ArrowSchema>() == align_of::<arrow::ffi::FFI_ArrowSchema>(),
"FFI_ArrowSchema alignment differs between Arrow 58 and Arrow 59"
);
fn v58_to_internal(array: &dyn arrow58::array::Array) -> Result<DoraArray> {
let (ffi_array, ffi_schema) = arrow58::ffi::to_ffi(&array.to_data())
.context("failed to export Arrow 58 array over FFI")?;
let ffi_array: arrow::ffi::FFI_ArrowArray = unsafe { std::mem::transmute(ffi_array) };
let ffi_schema: arrow::ffi::FFI_ArrowSchema = unsafe { std::mem::transmute(ffi_schema) };
let data = unsafe { arrow::ffi::from_ffi(ffi_array, &ffi_schema) }
.context("failed to import array")?;
Ok(crate::internal::from_array_data(data))
}
fn internal_to_v58(data: &DoraArray) -> Result<arrow58::array::ArrayRef> {
let (ffi_array, ffi_schema) = arrow::ffi::to_ffi(&crate::internal::array_ref(data).to_data())
.context("failed to export dora payload over FFI")?;
let ffi_array: arrow58::ffi::FFI_ArrowArray = unsafe { std::mem::transmute(ffi_array) };
let ffi_schema: arrow58::ffi::FFI_ArrowSchema = unsafe { std::mem::transmute(ffi_schema) };
let imported = unsafe { arrow58::ffi::from_ffi(ffi_array, &ffi_schema) }
.context("failed to import array into Arrow 58")?;
Ok(arrow58::array::make_array(imported))
}
impl TryFrom<&dyn arrow58::array::Array> for DoraArray {
type Error = eyre::Report;
fn try_from(array: &dyn arrow58::array::Array) -> Result<Self> {
v58_to_internal(array)
}
}
impl TryFrom<&arrow58::array::ArrayRef> for DoraArray {
type Error = eyre::Report;
fn try_from(array: &arrow58::array::ArrayRef) -> Result<Self> {
v58_to_internal(array.as_ref())
}
}
impl TryFrom<&DoraArray> for arrow58::array::ArrayRef {
type Error = eyre::Report;
fn try_from(data: &DoraArray) -> Result<Self> {
internal_to_v58(data)
}
}
#[cfg(test)]
mod tests {
use super::*;
use arrow58::array::{Array, ArrayRef};
#[test]
fn round_trip_58_59_58() {
let original = arrow58::array::UInt64Array::from(vec![1u64, 2, 3, 4, 5]);
let dora = DoraArray::try_from(&original as &dyn Array).expect("58 -> 59");
assert_eq!(dora.len(), 5);
assert_eq!(dora.type_name(), "UInt64");
let values: Vec<u64> = crate::into_vec(&dora).expect("read back");
assert_eq!(values, vec![1, 2, 3, 4, 5]);
let back = ArrayRef::try_from(&dora).expect("59 -> 58");
let back = back
.as_any()
.downcast_ref::<arrow58::array::UInt64Array>()
.expect("still a UInt64Array after the round trip");
assert_eq!(back.values(), original.values());
assert_eq!(back.data_type(), original.data_type());
}
#[test]
fn round_trip_58_59_58_nested_and_nullable() {
use std::sync::Arc;
let strings = arrow58::array::StringArray::from(vec![Some("a"), None, Some("ccc")]);
let ints = arrow58::array::Int32Array::from(vec![Some(1), Some(2), None]);
let original = arrow58::array::StructArray::from(vec![
(
Arc::new(arrow58::datatypes::Field::new(
"s",
arrow58::datatypes::DataType::Utf8,
true,
)),
Arc::new(strings) as arrow58::array::ArrayRef,
),
(
Arc::new(arrow58::datatypes::Field::new(
"i",
arrow58::datatypes::DataType::Int32,
true,
)),
Arc::new(ints) as arrow58::array::ArrayRef,
),
]);
let dora = DoraArray::try_from(&original as &dyn Array).expect("58 -> 59");
assert_eq!(dora.len(), 3);
let back = ArrayRef::try_from(&dora).expect("59 -> 58");
let back = back
.as_any()
.downcast_ref::<arrow58::array::StructArray>()
.expect("still a StructArray");
assert_eq!(back.len(), 3);
assert_eq!(back.num_columns(), 2);
assert_eq!(back.column(0).null_count(), 1);
assert_eq!(back.column(1).null_count(), 1);
assert_eq!(back.to_data(), original.to_data());
}
#[test]
fn release_callback_runs_when_import_is_dropped_unread() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct DropCounter(Arc<AtomicUsize>);
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
let releases = Arc::new(AtomicUsize::new(0));
let dora = {
let bytes: Vec<u8> = (0..64u8).collect();
let owner = Arc::new((bytes.clone(), DropCounter(releases.clone())));
let ptr = std::ptr::NonNull::new(owner.0.as_ptr() as *mut u8).unwrap();
let buffer =
unsafe { arrow58::buffer::Buffer::from_custom_allocation(ptr, bytes.len(), owner) };
let array = arrow58::array::UInt8Array::from(
arrow58::array::ArrayData::builder(arrow58::datatypes::DataType::UInt8)
.len(64)
.add_buffer(buffer)
.build()
.unwrap(),
);
let dora = DoraArray::try_from(&array as &dyn Array).expect("58 -> 59");
drop(array);
assert_eq!(
releases.load(Ordering::SeqCst),
0,
"Arrow 58 freed the buffer while the Arrow 59 import still held it"
);
dora
};
drop(dora);
assert_eq!(
releases.load(Ordering::SeqCst),
1,
"dropping the imported array must run Arrow 58's release callback exactly once"
);
}
#[test]
fn release_callback_runs_for_export_dropped_unread() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
struct DropCounter(Arc<AtomicUsize>);
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
let releases = Arc::new(AtomicUsize::new(0));
let exported = {
let bytes: Vec<u8> = (0..64u8).collect();
let owner = Arc::new((bytes.clone(), DropCounter(releases.clone())));
let ptr = std::ptr::NonNull::new(owner.0.as_ptr() as *mut u8).unwrap();
let buffer =
unsafe { arrow::buffer::Buffer::from_custom_allocation(ptr, bytes.len(), owner) };
let data = arrow::array::ArrayData::builder(arrow::datatypes::DataType::UInt8)
.len(64)
.add_buffer(buffer)
.build()
.unwrap();
let dora = crate::internal::from_array_data(data);
let exported = ArrayRef::try_from(&dora).expect("59 -> 58");
drop(dora);
assert_eq!(
releases.load(Ordering::SeqCst),
0,
"Arrow 59 freed the buffer while the Arrow 58 export still held it"
);
exported
};
drop(exported);
assert_eq!(
releases.load(Ordering::SeqCst),
1,
"dropping the exported array must run Arrow 59's release callback exactly once"
);
}
#[test]
fn round_trip_empty_array_preserves_type() {
let original: ArrayRef =
std::sync::Arc::new(arrow58::array::Float32Array::from(Vec::<f32>::new()));
let dora: DoraArray = (&original).try_into().expect("58 -> 59");
assert_eq!(dora.len(), 0);
assert_eq!(dora.type_name(), "Float32");
let back: ArrayRef = (&dora).try_into().expect("59 -> 58");
assert_eq!(back.data_type(), &arrow58::datatypes::DataType::Float32);
assert_eq!(back.len(), 0);
}
}