diff --git a/Cargo.toml b/Cargo.toml index 3664fbd..295be47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,6 @@ quote = "1" syn = "2" trybuild = "1.0" pretty_assertions = "1.4" +tinystr = "0.7" +rust_decimal = "1.36" +glam = "0.29" diff --git a/README.md b/README.md index e7290ea..53c654a 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,14 @@ Default implementations of the above traits are provided for the following: - Large Arrow types [`LargeBinary`], [`LargeString`], [`LargeList`] are supported via the `type` attribute. Please see the [complex_example.rs](./arrow_convert/tests/complex_example.rs) for usage. - Fixed size types [`FixedSizeBinary`], [`FixedSizeList`] are supported via the `FixedSizeVec` type override. - Note: nesting of [`FixedSizeList`] is not supported. +- `TinyAsciiStr` from the [tinystr](https://github.com/zbraniecki/tinystr) crate (with the `tinystr` feature enabled) +- `Decimal` from the [rust_decimal](https://github.com/paupino/rust-decimal) crate (with the `rust_decimal` feature enabled) +- `Glam` vector and matrix types (with the `glam` feature enabled): + - `Vec2`, `Vec3`, `Vec4` + - `DVec2`, `DVec3`, `DVec4` + - `BVec2`, `BVec3`, `BVec4` + - `Mat2`, `Mat3`, `Mat4` + - `DMat2`, `DMat3`, `DMat4` ### Enums diff --git a/arrow_convert/Cargo.toml b/arrow_convert/Cargo.toml index f5225f9..80e2c65 100644 --- a/arrow_convert/Cargo.toml +++ b/arrow_convert/Cargo.toml @@ -8,6 +8,14 @@ keywords.workspace = true repository.workspace = true description = "Convert between nested rust types and Arrow with arrow" +[features] +default = ["derive"] + +derive = ["arrow_convert_derive"] +tinystr = ["dep:tinystr"] +rust_decimal = ["dep:rust_decimal"] +glam = ["dep:glam"] + [dependencies] arrow = { workspace = true } arrow_convert_derive = { workspace = true, optional = true } @@ -15,16 +23,20 @@ half = { workspace = true } chrono = { workspace = true, features = ["std"] } err-derive = { workspace = true } +# optional deps +tinystr = { workspace = true, optional = true } +rust_decimal = { workspace = true, optional = true } +glam = { workspace = true, optional = true } + [dev-dependencies] arrow_convert_derive = { workspace = true } +glam = { workspace = true } +tinystr = { workspace = true } +rust_decimal = { workspace = true } criterion = { workspace = true } trybuild = { workspace = true } pretty_assertions = { workspace = true } -[features] -default = ["derive"] -derive = ["arrow_convert_derive"] - [lib] bench = false diff --git a/arrow_convert/src/features/glam.rs b/arrow_convert/src/features/glam.rs new file mode 100644 index 0000000..9ff190f --- /dev/null +++ b/arrow_convert/src/features/glam.rs @@ -0,0 +1,172 @@ +use arrow::datatypes::DataType; + +use crate::arrow_enable_vec_for_type; +use crate::deserialize::ArrowDeserialize; +use crate::field::ArrowField; +use crate::serialize::ArrowSerialize; +use arrow::datatypes::Field; + +use crate::deserialize::arrow_deserialize_vec_helper; +use arrow::array::ArrayRef; +use arrow::array::{BooleanBuilder, Float32Builder, Float64Builder}; +use arrow::array::{FixedSizeListArray, FixedSizeListBuilder}; +use std::sync::Arc; + +/// This macro implements the `ArrowSerialize` and `ArrowDeserialize` traits for a given `glam` vector or matrix type. +/// +/// The macro takes the following parameters: +/// - `$type`: The type of the `glam` vector or matrix to implement the traits for. +/// - `$size`: The size of the vector or matrix (e.g. 2 for `glam::Vec2`, 4 for `glam::Mat4`). +/// - `$dt`: The data type of the elements in the vector or matrix (e.g. `bool`, `f32`). +/// - `$arrow_dt`: The corresponding Arrow data type for the element type. +/// - `$array_builder`: The Arrow array builder type to use for the element type. +/// - `$se`: A closure that serializes the `$type` to a slice of the element type. +/// - `$de`: A closure that deserializes a `Vec` of the element type to the `$type`. +macro_rules! impl_glam_ty { + ($type:ty, $size:expr, $dt:ident, $arrow_dt:expr, $array_builder:ident, $se:expr, $de:expr) => { + impl ArrowField for $type { + type Type = Self; + + fn data_type() -> DataType { + let field = Field::new("scalar", $arrow_dt, false); + DataType::FixedSizeList(Arc::new(field), $size) + } + } + + arrow_enable_vec_for_type!($type); + + impl ArrowSerialize for $type { + type ArrayBuilderType = FixedSizeListBuilder<$array_builder>; + + fn new_array() -> Self::ArrayBuilderType { + let field = Field::new("scalar", $arrow_dt, false); + Self::ArrayBuilderType::new(<$dt as ArrowSerialize>::new_array(), $size).with_field(field) + } + + fn arrow_serialize(v: &Self::Type, array: &mut Self::ArrayBuilderType) -> arrow::error::Result<()> { + let v = $se(v); + + array.values().append_slice(v.as_ref()); + array.append(true); + Ok(()) + } + } + + impl ArrowDeserialize for $type { + type ArrayType = FixedSizeListArray; + + fn arrow_deserialize(v: Option) -> Option { + let v = arrow_deserialize_vec_helper::<$dt>(v)?; + Some($de(v)) + } + } + }; +} + +/// Implements the `ArrowSerialize` and `ArrowDeserialize` traits for the given `glam::Vec` type. +macro_rules! impl_glam_vec_bool { + ($type:ty, $size:expr) => { + impl_glam_ty!( + $type, + $size, + bool, + DataType::Boolean, + BooleanBuilder, + |v: &$type| <[bool; $size]>::from(*v), + |v: Vec| { + let length = v.len(); + + match <[bool; $size]>::try_from(v).ok() { + None => panic!( + "Expected size of {} deserializing array of type `{}`, got {}", + std::any::type_name::<$type>(), + $size, + length + ), + Some(array) => Self::from_array(array), + } + } + ); + }; +} + +/// Implements the `ArrowSerialize` and `ArrowDeserialize` traits for the given `glam::Vec2` type. +macro_rules! impl_glam_vec_f32 { + ($type:ty, $size:expr) => { + impl_glam_ty!( + $type, + $size, + f32, + DataType::Float32, + Float32Builder, + |v: &$type| *v, + |v: Vec| Self::from_slice(&v) + ); + }; +} + +/// Implements the `ArrowSerialize` and `ArrowDeserialize` traits for the given `glam::Mat2`, `glam::Mat3`, and `glam::Mat4` types. +macro_rules! impl_glam_mat_f32 { + ($type:ty, $size:expr) => { + impl_glam_ty!( + $type, + $size, + f32, + DataType::Float32, + Float32Builder, + |v: &$type| *v, + |v: Vec| Self::from_cols_slice(&v) + ); + }; +} + +/// Implements the `ArrowSerialize` and `ArrowDeserialize` traits for the given `glam::DVec2`, `glam::DVec3`, and `glam::DVec4` types. +macro_rules! impl_glam_vec_f64 { + ($type:ty, $size:expr) => { + impl_glam_ty!( + $type, + $size, + f64, + DataType::Float64, + Float64Builder, + |v: &$type| *v, + |v: Vec| Self::from_slice(&v) + ); + }; +} + +/// Implements the `ArrowSerialize` and `ArrowDeserialize` traits for the given `glam::DMat2`, `glam::DMat3`, and `glam::DMat4` types. +macro_rules! impl_glam_mat_f64 { + ($type:ty, $size:expr) => { + impl_glam_ty!( + $type, + $size, + f64, + DataType::Float64, + Float64Builder, + |v: &$type| *v, + |v: Vec| Self::from_cols_slice(&v) + ); + }; +} + +// Boolean vectors +impl_glam_vec_bool!(glam::BVec2, 2); +impl_glam_vec_bool!(glam::BVec3, 3); +impl_glam_vec_bool!(glam::BVec4, 4); + +// Float32 vectors and matrices +impl_glam_vec_f32!(glam::Vec2, 2); +impl_glam_vec_f32!(glam::Vec3, 3); +impl_glam_vec_f32!(glam::Vec4, 4); +impl_glam_mat_f32!(glam::Mat2, 4); +impl_glam_mat_f32!(glam::Mat3, 9); +impl_glam_mat_f32!(glam::Mat4, 16); + +// Float64 vectors and matrices +impl_glam_vec_f64!(glam::DVec2, 2); +impl_glam_vec_f64!(glam::DVec3, 3); +impl_glam_vec_f64!(glam::DVec4, 4); +impl_glam_mat_f64!(glam::DMat2, 4); +impl_glam_mat_f64!(glam::DMat3, 9); +impl_glam_mat_f64!(glam::DMat4, 16); diff --git a/arrow_convert/src/features/mod.rs b/arrow_convert/src/features/mod.rs new file mode 100644 index 0000000..36e2ed0 --- /dev/null +++ b/arrow_convert/src/features/mod.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "tinystr")] +mod tinystr; + +#[cfg(feature = "rust_decimal")] +mod rust_decimal; + +#[cfg(feature = "glam")] +mod glam; diff --git a/arrow_convert/src/features/rust_decimal.rs b/arrow_convert/src/features/rust_decimal.rs new file mode 100644 index 0000000..a6f004c --- /dev/null +++ b/arrow_convert/src/features/rust_decimal.rs @@ -0,0 +1,55 @@ +use crate::arrow_enable_vec_for_type; +use crate::deserialize::ArrowDeserialize; +use crate::field::ArrowField; +use crate::serialize::ArrowSerialize; + +use arrow::datatypes::{DataType, DECIMAL128_MAX_PRECISION, DECIMAL_DEFAULT_SCALE}; +use rust_decimal::Decimal; + +use arrow::array::{Decimal128Array, Decimal128Builder}; + +impl ArrowField for Decimal { + type Type = Decimal; + + #[inline] + fn data_type() -> DataType { + DataType::Decimal128(DECIMAL128_MAX_PRECISION, DECIMAL_DEFAULT_SCALE) + } +} + +arrow_enable_vec_for_type!(Decimal); + +impl ArrowSerialize for Decimal { + type ArrayBuilderType = Decimal128Builder; + + fn new_array() -> Self::ArrayBuilderType { + Decimal128Builder::new().with_data_type(Self::data_type()) + } + + fn arrow_serialize(v: &Self::Type, array: &mut Self::ArrayBuilderType) -> arrow::error::Result<()> { + array.append_value(decimal_to_scaled_i128(*v)); + Ok(()) + } +} + +impl ArrowDeserialize for Decimal { + type ArrayType = Decimal128Array; + + fn arrow_deserialize(v: Option) -> Option { + v.map(|d| Decimal::from_i128_with_scale(d, DECIMAL_DEFAULT_SCALE as _)) + } +} + +/// Converts a `Decimal` value to an `i128` representation, adjusting the scale to match the default scale. +fn decimal_to_scaled_i128(decimal: Decimal) -> i128 { + let m = decimal.mantissa(); + let scale_diff = DECIMAL_DEFAULT_SCALE as i32 - decimal.scale() as i32; + + if scale_diff == 0 { + m + } else if scale_diff < 0 { + m / 10_i128.pow(scale_diff.unsigned_abs()) + } else { + m * 10_i128.pow(scale_diff as u32) + } +} diff --git a/arrow_convert/src/features/tinystr.rs b/arrow_convert/src/features/tinystr.rs new file mode 100644 index 0000000..a7d27fa --- /dev/null +++ b/arrow_convert/src/features/tinystr.rs @@ -0,0 +1,37 @@ +use arrow::datatypes::DataType; +use tinystr::TinyAsciiStr; + +use crate::deserialize::ArrowDeserialize; +use crate::field::ArrowField; +use crate::serialize::ArrowSerialize; + +use arrow::array::{FixedSizeBinaryArray, FixedSizeBinaryBuilder}; + +impl ArrowField for TinyAsciiStr { + type Type = Self; + + fn data_type() -> DataType { + DataType::FixedSizeBinary(N as i32) + } +} + +impl ArrowSerialize for TinyAsciiStr { + type ArrayBuilderType = FixedSizeBinaryBuilder; + + fn new_array() -> Self::ArrayBuilderType { + FixedSizeBinaryBuilder::new(N as i32) + } + + fn arrow_serialize(v: &Self::Type, array: &mut Self::ArrayBuilderType) -> arrow::error::Result<()> { + array.append_value(v.as_bytes())?; + Ok(()) + } +} + +impl ArrowDeserialize for TinyAsciiStr { + type ArrayType = FixedSizeBinaryArray; + + fn arrow_deserialize(v: Option<&[u8]>) -> Option { + v.and_then(|bytes| TinyAsciiStr::from_bytes(bytes).ok()) + } +} diff --git a/arrow_convert/src/lib.rs b/arrow_convert/src/lib.rs index 3244e51..b21405a 100644 --- a/arrow_convert/src/lib.rs +++ b/arrow_convert/src/lib.rs @@ -17,3 +17,5 @@ pub use arrow_convert_derive::{ArrowDeserialize, ArrowField, ArrowSerialize}; #[cfg_attr(not(target_os = "windows"), doc = include_str!("../README.md"))] #[cfg(doctest)] struct ReadmeDoctests; + +mod features; diff --git a/arrow_convert/tests/test_glam.rs b/arrow_convert/tests/test_glam.rs new file mode 100644 index 0000000..7c75af9 --- /dev/null +++ b/arrow_convert/tests/test_glam.rs @@ -0,0 +1,189 @@ +#[cfg(feature = "glam")] +#[test] +fn test_vec3_roundtrip() { + use glam::*; + + use arrow::array::{Array, ArrayRef}; + use arrow_convert::deserialize::TryIntoCollection; + use arrow_convert::serialize::TryIntoArrow; + use arrow_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; + use pretty_assertions::assert_eq; + + #[derive(Debug, Clone, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)] + pub struct GlamObj { + a1: Vec2, + a2: Vec3, + a3: Vec4, + b1: DVec2, + b2: DVec3, + b3: DVec4, + c1: BVec2, + c2: BVec3, + c3: BVec4, + m1: Mat2, + m2: Mat3, + m3: Mat4, + dm1: DMat2, + dm2: DMat3, + dm3: DMat4, + } + + let original: Vec = vec![ + GlamObj { + a1: Vec2::new(1.0, 2.0), + a2: Vec3::new(3.0, 4.0, 5.0), + a3: Vec4::new(6.0, 7.0, 8.0, 9.0), + b1: DVec2::new(10.0, 11.0), + b2: DVec3::new(12.0, 13.0, 14.0), + b3: DVec4::new(15.0, 16.0, 17.0, 18.0), + c1: BVec2::new(false, true), + c2: BVec3::new(false, true, true), + c3: BVec4::new(false, true, true, false), + m1: Mat2::from_cols_array(&[1.0, 2.0, 3.0, 4.0]), + m2: Mat3::from_cols_array(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]), + m3: Mat4::from_cols_array(&[ + 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + ]), + dm1: DMat2::from_cols_array(&[1.0, 2.0, 3.0, 4.0]), + dm2: DMat3::from_cols_array(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]), + dm3: DMat4::from_cols_array(&[ + 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, + ]), + }, + GlamObj { + a1: Vec2::new(19.0, 20.0), + a2: Vec3::new(21.0, 22.0, 23.0), + a3: Vec4::new(24.0, 25.0, 26.0, 27.0), + b1: DVec2::new(28.0, 29.0), + b2: DVec3::new(30.0, 31.0, 32.0), + b3: DVec4::new(33.0, 34.0, 35.0, 36.0), + c1: BVec2::new(true, false), + c2: BVec3::new(true, false, true), + c3: BVec4::new(true, false, false, true), + m1: Mat2::from_cols_array(&[5.0, 6.0, 7.0, 8.0]), + m2: Mat3::from_cols_array(&[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0]), + m3: Mat4::from_cols_array(&[ + 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, + ]), + dm1: DMat2::from_cols_array(&[5.0, 6.0, 7.0, 8.0]), + dm2: DMat3::from_cols_array(&[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0]), + dm3: DMat4::from_cols_array(&[ + 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, + ]), + }, + ]; + + // Serialize to Arrow + let arrow_array: ArrayRef = original.try_into_arrow().unwrap(); + + assert!(arrow_array.as_any().is::()); + let struct_array = arrow_array + .as_any() + .downcast_ref::() + .unwrap(); + + // Verify the number of fields in the struct + assert_eq!(struct_array.num_columns(), 15); + + // Deserialize back to Vec + let roundtrip: Vec = arrow_array.try_into_collection().unwrap(); + + // Compare original and roundtrip data + assert_eq!(original, roundtrip); +} + +#[cfg(feature = "glam")] +#[test] +fn test_glam_edge_values() { + use arrow::array::{Array, ArrayRef}; + use arrow_convert::deserialize::TryIntoCollection; + use arrow_convert::serialize::TryIntoArrow; + use arrow_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; + use glam::*; + use pretty_assertions::assert_eq; + + #[derive(Debug, Clone, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)] + pub struct GlamObj { + a1: Vec2, + a2: Vec3, + a3: Vec4, + b1: DVec2, + b2: DVec3, + b3: DVec4, + c1: BVec2, + c2: BVec3, + c3: BVec4, + m1: Mat2, + m2: Mat3, + m3: Mat4, + dm1: DMat2, + dm2: DMat3, + dm3: DMat4, + } + + let original: Vec = vec![GlamObj { + a1: Vec2::new(f32::MAX, f32::MIN), + a2: Vec3::new(f32::MAX, f32::MIN, 0.0), + a3: Vec4::new(f32::MAX, f32::MIN, 0.0, 1.0), + b1: DVec2::new(f64::MAX, f64::MIN), + b2: DVec3::new(f64::MAX, f64::MIN, 0.0), + b3: DVec4::new(f64::MAX, f64::MIN, 0.0, 1.0), + c1: BVec2::new(true, false), + c2: BVec3::new(true, false, true), + c3: BVec4::new(true, false, true, false), + m1: Mat2::from_cols_array(&[f32::MAX, f32::MIN, 0.0, 1.0]), + m2: Mat3::from_cols_array(&[f32::MAX, f32::MIN, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]), + m3: Mat4::from_cols_array(&[ + f32::MAX, + f32::MIN, + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + ]), + dm1: DMat2::from_cols_array(&[f64::MAX, f64::MIN, 0.0, 1.0]), + dm2: DMat3::from_cols_array(&[f64::MAX, f64::MIN, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]), + dm3: DMat4::from_cols_array(&[ + f64::MAX, + f64::MIN, + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + ]), + }]; + + let arrow_array: ArrayRef = original.try_into_arrow().expect("Failed to convert to Arrow array"); + assert!(arrow_array.as_any().is::()); + let struct_array = arrow_array + .as_any() + .downcast_ref::() + .unwrap(); + + assert_eq!(struct_array.num_columns(), 15); + + let roundtrip: Vec = arrow_array + .try_into_collection() + .expect("Failed to convert from Arrow array"); + assert_eq!(original, roundtrip); +} diff --git a/arrow_convert/tests/test_rust_decimal.rs b/arrow_convert/tests/test_rust_decimal.rs new file mode 100644 index 0000000..50e515e --- /dev/null +++ b/arrow_convert/tests/test_rust_decimal.rs @@ -0,0 +1,68 @@ +#[cfg(feature = "rust_decimal")] +#[test] +fn test_decimal_roundtrip() { + use arrow::array::{Array, ArrayRef}; + use arrow::datatypes::DECIMAL_DEFAULT_SCALE; + use arrow::{array::Decimal128Array, datatypes::DECIMAL128_MAX_PRECISION}; + use arrow_convert::deserialize::TryIntoCollection; + use arrow_convert::serialize::*; + use pretty_assertions::assert_eq; + use rust_decimal::Decimal; + + let original: Vec = vec![ + Decimal::from_str_exact("123.45").unwrap(), + Decimal::from_str_exact("67890").unwrap(), + Decimal::from_str_exact("0.11111").unwrap(), + Decimal::from_str_exact("67890").unwrap(), + Decimal::from_str_exact("0.11111").unwrap(), + Decimal::from_str_exact("-9876.54321").unwrap(), + Decimal::from_str_exact("1000000.000001").unwrap(), + Decimal::from_str_exact("0.0000000001").unwrap(), + Decimal::from_str_exact("-0.9999999999").unwrap(), + ]; + + let arrow_array: ArrayRef = original.try_into_arrow().expect("Failed to convert to Arrow array"); + assert!(arrow_array.as_any().is::()); + + let decimal_array = arrow_array + .as_any() + .downcast_ref::() + .expect("Failed to downcast to Decimal128Array"); + + assert_eq!(decimal_array.precision(), DECIMAL128_MAX_PRECISION); + assert_eq!(decimal_array.scale(), DECIMAL_DEFAULT_SCALE); + + let roundtrip: Vec = arrow_array + .try_into_collection() + .expect("Failed to convert back to Vec"); + assert_eq!(original, roundtrip); +} + +#[cfg(feature = "rust_decimal")] +#[test] +fn test_decimal_edge_values() { + use arrow::array::{Array, ArrayRef}; + use arrow::datatypes::DECIMAL_DEFAULT_SCALE; + use arrow::{array::Decimal128Array, datatypes::DECIMAL128_MAX_PRECISION}; + use arrow_convert::deserialize::TryIntoCollection; + use arrow_convert::serialize::*; + use rust_decimal::Decimal; + + let original: Vec = vec![ + Decimal::new(i64::MAX, DECIMAL_DEFAULT_SCALE as _), + Decimal::new(i64::MIN, DECIMAL_DEFAULT_SCALE as _), + Decimal::new(0, DECIMAL_DEFAULT_SCALE as _), + ]; + + let arrow_array: ArrayRef = original.try_into_arrow().expect("Failed to convert to Arrow array"); + assert!(arrow_array.as_any().is::()); + + let decimal_array = arrow_array.as_any().downcast_ref::().unwrap(); + assert_eq!(decimal_array.precision(), DECIMAL128_MAX_PRECISION); + assert_eq!(decimal_array.scale(), DECIMAL_DEFAULT_SCALE); + + let roundtrip: Vec = arrow_array + .try_into_collection() + .expect("Failed to convert from Arrow array"); + assert_eq!(original, roundtrip); +} diff --git a/arrow_convert/tests/test_tinystr.rs b/arrow_convert/tests/test_tinystr.rs new file mode 100644 index 0000000..0e072f7 --- /dev/null +++ b/arrow_convert/tests/test_tinystr.rs @@ -0,0 +1,49 @@ +#[cfg(feature = "tinystr")] +#[test] +fn test_tinyasciistr_roundtrip() { + use arrow::array::{Array, ArrayRef, FixedSizeBinaryArray}; + use arrow_convert::deserialize::TryIntoCollection; + use arrow_convert::serialize::TryIntoArrow; + use tinystr::TinyAsciiStr; + + let original: Vec> = vec![ + TinyAsciiStr::from_str("ABC").unwrap(), + TinyAsciiStr::from_str("XYZ").unwrap(), + TinyAsciiStr::from_str("123").unwrap(), + ]; + + // Serialize to Arrow + let arrow_array: ArrayRef = original.try_into_arrow().unwrap(); + + // Verify the array type + assert!(arrow_array.as_any().is::()); + let fixed_size_array = arrow_array.as_any().downcast_ref::().unwrap(); + assert_eq!(fixed_size_array.value_length(), 3); + + // Deserialize back to Vec> + let roundtrip: Vec> = arrow_array.try_into_collection().unwrap(); + + // Verify the roundtrip + assert_eq!(original, roundtrip); +} + +#[cfg(feature = "tinystr")] +#[test] +fn test_tinyasciistr_max_length() { + use arrow::array::{Array, ArrayRef, FixedSizeBinaryArray}; + use arrow_convert::deserialize::TryIntoCollection; + use arrow_convert::serialize::TryIntoArrow; + use tinystr::TinyAsciiStr; + + let original: Vec> = vec![TinyAsciiStr::from_str("ABCDEFGHIJ").unwrap()]; + + let arrow_array: ArrayRef = original.try_into_arrow().expect("Failed to convert to Arrow array"); + assert!(arrow_array.as_any().is::()); + let fixed_size_array = arrow_array.as_any().downcast_ref::().unwrap(); + assert_eq!(fixed_size_array.value_length(), 10); + + let roundtrip: Vec> = arrow_array + .try_into_collection() + .expect("Failed to convert from Arrow array"); + assert_eq!(original, roundtrip); +}