diff --git a/arrow-cast/src/cast/mod.rs b/arrow-cast/src/cast/mod.rs index 67efb5742485..184695f84889 100644 --- a/arrow-cast/src/cast/mod.rs +++ b/arrow-cast/src/cast/mod.rs @@ -71,6 +71,7 @@ use arrow_select::take::take; use num_traits::{NumCast, ToPrimitive, cast::AsPrimitive}; pub use decimal::{DecimalCast, rescale_decimal}; +pub use string::cast_single_string_to_boolean_default; /// CastOptions provides a way to override the default cast behaviors #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -2464,7 +2465,7 @@ where R::Native: NumCast, { from.try_unary(|value| { - num_traits::cast::cast::(value).ok_or_else(|| { + num_cast::(value).ok_or_else(|| { ArrowError::CastError(format!( "Can't cast value {:?} to type {}", value, @@ -2474,6 +2475,17 @@ where }) } +/// Natural cast between numeric types +/// Return None if the input `value` can't be casted to type `O`. +#[inline] +pub fn num_cast(value: I) -> Option +where + I: NumCast, + O: NumCast, +{ + num_traits::cast::cast::(value) +} + // Natural cast between numeric types // If the value of T can't be casted to R, it will be converted to null fn numeric_cast(from: &PrimitiveArray) -> PrimitiveArray @@ -2483,7 +2495,7 @@ where T::Native: NumCast, R::Native: NumCast, { - from.unary_opt::<_, R>(num_traits::cast::cast::) + from.unary_opt::<_, R>(num_cast::) } fn cast_numeric_to_binary( @@ -2540,16 +2552,23 @@ where for i in 0..from.len() { if from.is_null(i) { b.append_null(); - } else if from.value(i) != T::default_value() { - b.append_value(true); } else { - b.append_value(false); + b.append_value(cast_num_to_bool::(from.value(i))); } } Ok(b.finish()) } +/// Cast numeric types to boolean +#[inline] +pub fn cast_num_to_bool(value: I) -> bool +where + I: Default + PartialEq, +{ + value != I::default() +} + /// Cast Boolean types to numeric /// /// `false` returns 0 while `true` returns 1 @@ -2575,11 +2594,8 @@ where let iter = (0..from.len()).map(|i| { if from.is_null(i) { None - } else if from.value(i) { - // a workaround to cast a primitive to T::Native, infallible - num_traits::cast::cast(1) } else { - Some(T::default_value()) + single_bool_to_numeric::(from.value(i)) } }); // Benefit: @@ -2589,6 +2605,20 @@ where unsafe { PrimitiveArray::::from_trusted_len_iter(iter) } } +/// Cat single bool value to numeric value. +#[inline] +pub fn single_bool_to_numeric(value: bool) -> Option +where + O: num_traits::NumCast + Default, +{ + if value { + // a workaround to cast a primitive to type O, infallible + num_traits::cast::cast(1) + } else { + Some(O::default()) + } +} + /// Helper function to cast from one `BinaryArray` or 'LargeBinaryArray' to 'FixedSizeBinaryArray'. fn cast_binary_to_fixed_size_binary( array: &dyn Array, diff --git a/arrow-cast/src/cast/string.rs b/arrow-cast/src/cast/string.rs index 77696ae0d8cc..2fd29b388090 100644 --- a/arrow-cast/src/cast/string.rs +++ b/arrow-cast/src/cast/string.rs @@ -401,18 +401,7 @@ where let output_array = array .iter() .map(|value| match value { - Some(value) => match value.to_ascii_lowercase().trim() { - "t" | "tr" | "tru" | "true" | "y" | "ye" | "yes" | "on" | "1" => Ok(Some(true)), - "f" | "fa" | "fal" | "fals" | "false" | "n" | "no" | "of" | "off" | "0" => { - Ok(Some(false)) - } - invalid_value => match cast_options.safe { - true => Ok(None), - false => Err(ArrowError::CastError(format!( - "Cannot cast value '{invalid_value}' to value of Boolean type", - ))), - }, - }, + Some(value) => cast_single_string_to_boolean(value, cast_options), None => Ok(None), }) .collect::>()?; @@ -420,6 +409,29 @@ where Ok(Arc::new(output_array)) } +fn cast_single_string_to_boolean( + value: &str, + cast_options: &CastOptions, +) -> Result, ArrowError> { + match value.to_ascii_lowercase().trim() { + "t" | "tr" | "tru" | "true" | "y" | "ye" | "yes" | "on" | "1" => Ok(Some(true)), + "f" | "fa" | "fal" | "fals" | "false" | "n" | "no" | "of" | "off" | "0" => Ok(Some(false)), + invalid_value => match cast_options.safe { + true => Ok(None), + false => Err(ArrowError::CastError(format!( + "Cannot cast value '{invalid_value}' to value of Boolean type", + ))), + }, + } +} + +/// Cast a single string to boolean with default cast option(safe=true). +pub fn cast_single_string_to_boolean_default(value: &str) -> Option { + cast_single_string_to_boolean(value, &CastOptions::default()) + .ok() + .flatten() +} + pub(crate) fn cast_utf8_to_boolean( from: &dyn Array, cast_options: &CastOptions, diff --git a/parquet-variant-compute/src/shred_variant.rs b/parquet-variant-compute/src/shred_variant.rs index c60c602baa37..994be7723b04 100644 --- a/parquet-variant-compute/src/shred_variant.rs +++ b/parquet-variant-compute/src/shred_variant.rs @@ -1128,7 +1128,7 @@ mod tests { .downcast_ref::() .unwrap(); assert_eq!(typed_value_int32.value(0), 42); - assert!(typed_value_int32.is_null(1)); // float doesn't convert to int32 + assert_eq!(typed_value_int32.value(1), 3); assert!(typed_value_int32.is_null(2)); // string doesn't convert to int32 // Test Float64 target diff --git a/parquet-variant-compute/src/variant_get.rs b/parquet-variant-compute/src/variant_get.rs index f9985084cc49..c595e9fd08cf 100644 --- a/parquet-variant-compute/src/variant_get.rs +++ b/parquet-variant-compute/src/variant_get.rs @@ -2626,7 +2626,7 @@ mod test { #[test] fn test_error_message_boolean_type_display() { let mut builder = VariantArrayBuilder::new(1); - builder.append_variant(Variant::Int32(123)); + builder.append_variant(Variant::Null); let variant_array: ArrayRef = ArrayRef::from(builder.build()); // Request Boolean with strict casting to force an error @@ -2647,7 +2647,7 @@ mod test { #[test] fn test_error_message_numeric_type_display() { let mut builder = VariantArrayBuilder::new(1); - builder.append_variant(Variant::BooleanTrue); + builder.append_variant(Variant::Null); let variant_array: ArrayRef = ArrayRef::from(builder.build()); // Request Boolean with strict casting to force an error diff --git a/parquet-variant/Cargo.toml b/parquet-variant/Cargo.toml index 51671d518910..7d5064331e4c 100644 --- a/parquet-variant/Cargo.toml +++ b/parquet-variant/Cargo.toml @@ -29,10 +29,12 @@ edition = { workspace = true } rust-version = { workspace = true } [dependencies] +arrow = { workspace = true , features = ["canonical_extension_types"] } arrow-schema = { workspace = true } chrono = { workspace = true } half = { version = "2.1", default-features = false } indexmap = "2.10.0" +num-traits = { version = "0.2", default-features = false } uuid = { version = "1.18.0", features = ["v4"]} simdutf8 = { workspace = true , optional = true } diff --git a/parquet-variant/src/utils.rs b/parquet-variant/src/utils.rs index 0984a601b213..10bbfa986778 100644 --- a/parquet-variant/src/utils.rs +++ b/parquet-variant/src/utils.rs @@ -146,10 +146,6 @@ pub(crate) const fn expect_size_of(expected: usize) { } } -pub(crate) fn fits_precision(n: impl Into) -> bool { - n.into().unsigned_abs().leading_zeros() >= (i64::BITS - N) -} - /// Parse a path string into a vector of [`VariantPathElement`]. /// /// # Syntax @@ -274,16 +270,3 @@ fn parse_in_bracket(s: &str, i: usize) -> Result<(VariantPathElement<'_>, usize) Ok((element, end + 1)) } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_fits_precision() { - assert!(fits_precision::<10>(1023)); - assert!(!fits_precision::<10>(1024)); - assert!(fits_precision::<10>(-1023)); - assert!(!fits_precision::<10>(-1024)); - } -} diff --git a/parquet-variant/src/variant.rs b/parquet-variant/src/variant.rs index 53fb3c4b1e10..2f0bcc616ca7 100644 --- a/parquet-variant/src/variant.rs +++ b/parquet-variant/src/variant.rs @@ -28,11 +28,14 @@ use crate::decoder::{ self, VariantBasicType, VariantPrimitiveType, get_basic_type, get_primitive_type, }; use crate::path::{VariantPath, VariantPathElement}; -use crate::utils::{first_byte_from_slice, fits_precision, slice_from_slice}; -use std::ops::Deref; - +use crate::utils::{first_byte_from_slice, slice_from_slice}; +use arrow::compute::{ + cast_num_to_bool, cast_single_string_to_boolean_default, num_cast, single_bool_to_numeric, +}; use arrow_schema::ArrowError; use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc}; +use num_traits::NumCast; +use std::ops::Deref; mod decimal; mod list; @@ -475,7 +478,7 @@ impl<'m, 'v> Variant<'m, 'v> { /// Converts this variant to a `bool` if possible. /// - /// Returns `Some(bool)` for boolean variants, + /// Returns `Some(bool)` for boolean, numeric and string variants, /// `None` for non-boolean variants. /// /// # Examples @@ -491,14 +494,30 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v2 = Variant::from(false); /// assert_eq!(v2.as_boolean(), Some(false)); /// + /// // and a numeric variant + /// let v3 = Variant::from(3); + /// assert_eq!(v3.as_boolean(), Some(true)); + /// + /// // and a string variant + /// let v4 = Variant::from("true"); + /// assert_eq!(v4.as_boolean(), Some(true)); + /// /// // but not from other variants - /// let v3 = Variant::from("hello!"); - /// assert_eq!(v3.as_boolean(), None); + /// let v5 = Variant::from("hello!"); + /// assert_eq!(v5.as_boolean(), None); /// ``` pub fn as_boolean(&self) -> Option { match self { Variant::BooleanTrue => Some(true), Variant::BooleanFalse => Some(false), + Variant::Int8(i) => Some(cast_num_to_bool(*i)), + Variant::Int16(i) => Some(cast_num_to_bool(*i)), + Variant::Int32(i) => Some(cast_num_to_bool(*i)), + Variant::Int64(i) => Some(cast_num_to_bool(*i)), + Variant::Float(f) => Some(cast_num_to_bool(*f)), + Variant::Double(d) => Some(cast_num_to_bool(*d)), + Variant::ShortString(s) => cast_single_string_to_boolean_default(s.as_str()), + Variant::String(s) => cast_single_string_to_boolean_default(s), _ => None, } } @@ -760,10 +779,34 @@ impl<'m, 'v> Variant<'m, 'v> { } } + /// Converts a boolean or numeric variant to the specified numeric type `T`. + /// + /// Uses Arrow's casting logic to perform the conversion. Returns `Some(T)` if + /// the conversion succeeds, `None` if the variant can't be casted to type `T`. + fn as_num(&self) -> Option + where + T: NumCast + Default, + { + match *self { + Variant::BooleanFalse => single_bool_to_numeric::(false), + Variant::BooleanTrue => single_bool_to_numeric::(true), + Variant::Int8(i) => num_cast::<_, T>(i), + Variant::Int16(i) => num_cast::<_, T>(i), + Variant::Int32(i) => num_cast::<_, T>(i), + Variant::Int64(i) => num_cast::<_, T>(i), + Variant::Float(f) => num_cast::<_, T>(f), + Variant::Double(d) => num_cast::<_, T>(d), + Variant::Decimal4(d) if d.scale() == 0 => num_cast::<_, T>(d.integer()), + Variant::Decimal8(d) if d.scale() == 0 => num_cast::<_, T>(d.integer()), + Variant::Decimal16(d) if d.scale() == 0 => num_cast::<_, T>(d.integer()), + _ => None, + } + } + /// Converts this variant to an `i8` if possible. /// - /// Returns `Some(i8)` for integer variants that fit in `i8` range, - /// `None` for non-integer variants or values that would overflow. + /// Returns `Some(i8)` for integer variants that fit in `i8` range and boolean variant, + /// `None` for other variants or values that would overflow. /// /// # Examples /// @@ -774,31 +817,26 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v1 = Variant::from(123i64); /// assert_eq!(v1.as_int8(), Some(123i8)); /// + /// // or from boolean variant + /// let v2 = Variant::BooleanFalse; + /// assert_eq!(v2.as_int8(), Some(0)); + /// /// // but not if it would overflow - /// let v2 = Variant::from(1234i64); - /// assert_eq!(v2.as_int8(), None); + /// let v3 = Variant::from(1234i64); + /// assert_eq!(v3.as_int8(), None); /// /// // or if the variant cannot be cast into an integer - /// let v3 = Variant::from("hello!"); - /// assert_eq!(v3.as_int8(), None); + /// let v4 = Variant::from("hello!"); + /// assert_eq!(v4.as_int8(), None); /// ``` pub fn as_int8(&self) -> Option { - match *self { - Variant::Int8(i) => Some(i), - Variant::Int16(i) => i.try_into().ok(), - Variant::Int32(i) => i.try_into().ok(), - Variant::Int64(i) => i.try_into().ok(), - Variant::Decimal4(d) if d.scale() == 0 => d.integer().try_into().ok(), - Variant::Decimal8(d) if d.scale() == 0 => d.integer().try_into().ok(), - Variant::Decimal16(d) if d.scale() == 0 => d.integer().try_into().ok(), - _ => None, - } + self.as_num() } /// Converts this variant to an `i16` if possible. /// - /// Returns `Some(i16)` for integer variants that fit in `i16` range, - /// `None` for non-integer variants or values that would overflow. + /// Returns `Some(i16)` for integer variants that fit in `i16` range and boolean variant, + /// `None` for other variants or values that would overflow. /// /// # Examples /// @@ -809,31 +847,26 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v1 = Variant::from(123i64); /// assert_eq!(v1.as_int16(), Some(123i16)); /// + /// // or from boolean variant + /// let v2 = Variant::BooleanFalse; + /// assert_eq!(v2.as_int16(), Some(0)); + /// /// // but not if it would overflow - /// let v2 = Variant::from(123456i64); - /// assert_eq!(v2.as_int16(), None); + /// let v3 = Variant::from(123456i64); + /// assert_eq!(v3.as_int16(), None); /// /// // or if the variant cannot be cast into an integer - /// let v3 = Variant::from("hello!"); - /// assert_eq!(v3.as_int16(), None); + /// let v4 = Variant::from("hello!"); + /// assert_eq!(v4.as_int16(), None); /// ``` pub fn as_int16(&self) -> Option { - match *self { - Variant::Int8(i) => Some(i.into()), - Variant::Int16(i) => Some(i), - Variant::Int32(i) => i.try_into().ok(), - Variant::Int64(i) => i.try_into().ok(), - Variant::Decimal4(d) if d.scale() == 0 => d.integer().try_into().ok(), - Variant::Decimal8(d) if d.scale() == 0 => d.integer().try_into().ok(), - Variant::Decimal16(d) if d.scale() == 0 => d.integer().try_into().ok(), - _ => None, - } + self.as_num() } /// Converts this variant to an `i32` if possible. /// - /// Returns `Some(i32)` for integer variants that fit in `i32` range, - /// `None` for non-integer variants or values that would overflow. + /// Returns `Some(i32)` for integer variants that fit in `i32` range and boolean variant, + /// `None` for other variants or values that would overflow. /// /// # Examples /// @@ -844,31 +877,26 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v1 = Variant::from(123i64); /// assert_eq!(v1.as_int32(), Some(123i32)); /// + /// // or from boolean variant + /// let v2 = Variant::BooleanFalse; + /// assert_eq!(v2.as_int32(), Some(0)); + /// /// // but not if it would overflow - /// let v2 = Variant::from(12345678901i64); - /// assert_eq!(v2.as_int32(), None); + /// let v3 = Variant::from(12345678901i64); + /// assert_eq!(v3.as_int32(), None); /// /// // or if the variant cannot be cast into an integer - /// let v3 = Variant::from("hello!"); - /// assert_eq!(v3.as_int32(), None); + /// let v4 = Variant::from("hello!"); + /// assert_eq!(v4.as_int32(), None); /// ``` pub fn as_int32(&self) -> Option { - match *self { - Variant::Int8(i) => Some(i.into()), - Variant::Int16(i) => Some(i.into()), - Variant::Int32(i) => Some(i), - Variant::Int64(i) => i.try_into().ok(), - Variant::Decimal4(d) if d.scale() == 0 => Some(d.integer()), - Variant::Decimal8(d) if d.scale() == 0 => d.integer().try_into().ok(), - Variant::Decimal16(d) if d.scale() == 0 => d.integer().try_into().ok(), - _ => None, - } + self.as_num() } /// Converts this variant to an `i64` if possible. /// - /// Returns `Some(i64)` for integer variants that fit in `i64` range, - /// `None` for non-integer variants or values that would overflow. + /// Returns `Some(i64)` for integer variants that fit in `i64` range and boolean variant, + /// `None` for other variants or values that would overflow. /// /// # Examples /// @@ -879,43 +907,22 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v1 = Variant::from(123i64); /// assert_eq!(v1.as_int64(), Some(123i64)); /// + /// // or from boolean variant + /// let v2 = Variant::BooleanFalse; + /// assert_eq!(v2.as_int64(), Some(0)); + /// /// // but not a variant that cannot be cast into an integer - /// let v2 = Variant::from("hello!"); - /// assert_eq!(v2.as_int64(), None); + /// let v3 = Variant::from("hello!"); + /// assert_eq!(v3.as_int64(), None); /// ``` pub fn as_int64(&self) -> Option { - match *self { - Variant::Int8(i) => Some(i.into()), - Variant::Int16(i) => Some(i.into()), - Variant::Int32(i) => Some(i.into()), - Variant::Int64(i) => Some(i), - Variant::Decimal4(d) if d.scale() == 0 => Some(d.integer().into()), - Variant::Decimal8(d) if d.scale() == 0 => Some(d.integer()), - Variant::Decimal16(d) if d.scale() == 0 => d.integer().try_into().ok(), - _ => None, - } - } - - fn generic_convert_unsigned_primitive(&self) -> Option - where - T: TryFrom + TryFrom + TryFrom + TryFrom + TryFrom, - { - match *self { - Variant::Int8(i) => i.try_into().ok(), - Variant::Int16(i) => i.try_into().ok(), - Variant::Int32(i) => i.try_into().ok(), - Variant::Int64(i) => i.try_into().ok(), - Variant::Decimal4(d) if d.scale() == 0 => d.integer().try_into().ok(), - Variant::Decimal8(d) if d.scale() == 0 => d.integer().try_into().ok(), - Variant::Decimal16(d) if d.scale() == 0 => d.integer().try_into().ok(), - _ => None, - } + self.as_num() } /// Converts this variant to a `u8` if possible. /// - /// Returns `Some(u8)` for integer variants that fit in `u8` - /// `None` for non-integer variants or values that would overflow. + /// Returns `Some(u8)` for integer variants that fit in `u8` and boolean variant + /// `None` for other variants or values that would overflow. /// /// # Examples /// @@ -931,27 +938,31 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v2 = Variant::from(d); /// assert_eq!(v2.as_u8(), Some(26u8)); /// + /// // or from boolean variant + /// let v3 = Variant::BooleanFalse; + /// assert_eq!(v3.as_int8(), Some(0)); + /// /// // but not a variant that can't fit into the range - /// let v3 = Variant::from(-1); - /// assert_eq!(v3.as_u8(), None); + /// let v4 = Variant::from(-1); + /// assert_eq!(v4.as_u8(), None); /// /// // not a variant that decimal with scale not equal to zero /// let d = VariantDecimal4::try_new(1, 2).unwrap(); - /// let v4 = Variant::from(d); - /// assert_eq!(v4.as_u8(), None); + /// let v5 = Variant::from(d); + /// assert_eq!(v5.as_u8(), None); /// /// // or not a variant that cannot be cast into an integer - /// let v5 = Variant::from("hello!"); - /// assert_eq!(v5.as_u8(), None); + /// let v6 = Variant::from("hello!"); + /// assert_eq!(v6.as_u8(), None); /// ``` pub fn as_u8(&self) -> Option { - self.generic_convert_unsigned_primitive::() + self.as_num() } /// Converts this variant to an `u16` if possible. /// - /// Returns `Some(u16)` for integer variants that fit in `u16` - /// `None` for non-integer variants or values that would overflow. + /// Returns `Some(u16)` for integer variants that fit in `u16` or boolean variant + /// `None` for other variants or values that would overflow. /// /// # Examples /// @@ -967,27 +978,31 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v2 = Variant::from(d); /// assert_eq!(v2.as_u16(), Some(u16::MAX)); /// + /// // or from boolean variant + /// let v3= Variant::BooleanFalse; + /// assert_eq!(v3.as_int8(), Some(0)); + /// /// // but not a variant that can't fit into the range - /// let v3 = Variant::from(-1); - /// assert_eq!(v3.as_u16(), None); + /// let v4 = Variant::from(-1); + /// assert_eq!(v4.as_u16(), None); /// /// // not a variant that decimal with scale not equal to zero /// let d = VariantDecimal4::try_new(1, 2).unwrap(); - /// let v4 = Variant::from(d); - /// assert_eq!(v4.as_u16(), None); + /// let v5 = Variant::from(d); + /// assert_eq!(v5.as_u16(), None); /// /// // or not a variant that cannot be cast into an integer - /// let v5 = Variant::from("hello!"); - /// assert_eq!(v5.as_u16(), None); + /// let v6 = Variant::from("hello!"); + /// assert_eq!(v6.as_u16(), None); /// ``` pub fn as_u16(&self) -> Option { - self.generic_convert_unsigned_primitive::() + self.as_num() } /// Converts this variant to an `u32` if possible. /// - /// Returns `Some(u32)` for integer variants that fit in `u32` - /// `None` for non-integer variants or values that would overflow. + /// Returns `Some(u32)` for integer variants that fit in `u32` and boolean variant + /// `None` for other variants or values that would overflow. /// /// # Examples /// @@ -1003,27 +1018,31 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v2 = Variant::from(d); /// assert_eq!(v2.as_u32(), Some(u32::MAX)); /// + /// // or from boolean variant + /// let v3 = Variant::BooleanFalse; + /// assert_eq!(v3.as_int8(), Some(0)); + /// /// // but not a variant that can't fit into the range - /// let v3 = Variant::from(-1); - /// assert_eq!(v3.as_u32(), None); + /// let v4 = Variant::from(-1); + /// assert_eq!(v4.as_u32(), None); /// /// // not a variant that decimal with scale not equal to zero /// let d = VariantDecimal8::try_new(1, 2).unwrap(); - /// let v4 = Variant::from(d); - /// assert_eq!(v4.as_u32(), None); + /// let v5 = Variant::from(d); + /// assert_eq!(v5.as_u32(), None); /// /// // or not a variant that cannot be cast into an integer - /// let v5 = Variant::from("hello!"); - /// assert_eq!(v5.as_u32(), None); + /// let v6 = Variant::from("hello!"); + /// assert_eq!(v6.as_u32(), None); /// ``` pub fn as_u32(&self) -> Option { - self.generic_convert_unsigned_primitive::() + self.as_num() } /// Converts this variant to an `u64` if possible. /// - /// Returns `Some(u64)` for integer variants that fit in `u64` - /// `None` for non-integer variants or values that would overflow. + /// Returns `Some(u64)` for integer variants that fit in `u64` and boolean variant + /// `None` for other variants or values that would overflow. /// /// # Examples /// @@ -1039,21 +1058,25 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v2 = Variant::from(d); /// assert_eq!(v2.as_u64(), Some(u64::MAX)); /// + /// // or from boolean variant + /// let v3 = Variant::BooleanFalse; + /// assert_eq!(v3.as_int8(), Some(0)); + /// /// // but not a variant that can't fit into the range - /// let v3 = Variant::from(-1); - /// assert_eq!(v3.as_u64(), None); + /// let v4 = Variant::from(-1); + /// assert_eq!(v4.as_u64(), None); /// /// // not a variant that decimal with scale not equal to zero /// let d = VariantDecimal16::try_new(1, 2).unwrap(); - /// let v4 = Variant::from(d); - /// assert_eq!(v4.as_u64(), None); + /// let v5 = Variant::from(d); + /// assert_eq!(v5.as_u64(), None); /// /// // or not a variant that cannot be cast into an integer - /// let v5 = Variant::from("hello!"); - /// assert_eq!(v5.as_u64(), None); + /// let v6 = Variant::from("hello!"); + /// assert_eq!(v6.as_u64(), None); /// ``` pub fn as_u64(&self) -> Option { - self.generic_convert_unsigned_primitive::() + self.as_num() } /// Converts this variant to tuple with a 4-byte unscaled value if possible. @@ -1085,10 +1108,9 @@ impl<'m, 'v> Variant<'m, 'v> { /// ``` pub fn as_decimal4(&self) -> Option { match *self { - Variant::Int8(i) => i32::from(i).try_into().ok(), - Variant::Int16(i) => i32::from(i).try_into().ok(), - Variant::Int32(i) => i.try_into().ok(), - Variant::Int64(i) => i32::try_from(i).ok()?.try_into().ok(), + Variant::Int8(_) | Variant::Int16(_) | Variant::Int32(_) | Variant::Int64(_) => { + self.as_num::().and_then(|x| x.try_into().ok()) + } Variant::Decimal4(decimal4) => Some(decimal4), Variant::Decimal8(decimal8) => decimal8.try_into().ok(), Variant::Decimal16(decimal16) => decimal16.try_into().ok(), @@ -1125,10 +1147,9 @@ impl<'m, 'v> Variant<'m, 'v> { /// ``` pub fn as_decimal8(&self) -> Option { match *self { - Variant::Int8(i) => i64::from(i).try_into().ok(), - Variant::Int16(i) => i64::from(i).try_into().ok(), - Variant::Int32(i) => i64::from(i).try_into().ok(), - Variant::Int64(i) => i.try_into().ok(), + Variant::Int8(_) | Variant::Int16(_) | Variant::Int32(_) | Variant::Int64(_) => { + self.as_num::().and_then(|x| x.try_into().ok()) + } Variant::Decimal4(decimal4) => Some(decimal4.into()), Variant::Decimal8(decimal8) => Some(decimal8), Variant::Decimal16(decimal16) => decimal16.try_into().ok(), @@ -1157,10 +1178,9 @@ impl<'m, 'v> Variant<'m, 'v> { /// ``` pub fn as_decimal16(&self) -> Option { match *self { - Variant::Int8(i) => i128::from(i).try_into().ok(), - Variant::Int16(i) => i128::from(i).try_into().ok(), - Variant::Int32(i) => i128::from(i).try_into().ok(), - Variant::Int64(i) => i128::from(i).try_into().ok(), + Variant::Int8(_) | Variant::Int16(_) | Variant::Int32(_) | Variant::Int64(_) => { + self.as_num::().and_then(|x| x.try_into().ok()) + } Variant::Decimal4(decimal4) => Some(decimal4.into()), Variant::Decimal8(decimal8) => Some(decimal8.into()), Variant::Decimal16(decimal16) => Some(decimal16), @@ -1170,8 +1190,8 @@ impl<'m, 'v> Variant<'m, 'v> { /// Converts this variant to an `f16` if possible. /// - /// Returns `Some(f16)` for floating point values, and integers with up to 11 bits of - /// precision. `None` otherwise. + /// Returns `Some(f16)` for floating point values, integer and boolean variants. + /// `None` otherwise. /// /// # Example /// @@ -1187,29 +1207,25 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v2 = Variant::from(std::f64::consts::PI); /// assert_eq!(v2.as_f16(), Some(f16::from_f64(std::f64::consts::PI))); /// - /// // and from integers with no more than 11 bits of precision - /// let v3 = Variant::from(2047); - /// assert_eq!(v3.as_f16(), Some(f16::from_f32(2047.0))); + /// // and from boolean + /// let v3 = Variant::BooleanTrue; + /// assert_eq!(v3.as_f16(), Some(f16::from_f32(1.0))); + /// + /// // return inf if overflow + /// let v4 = Variant::from(123456); + /// assert_eq!(v4.as_f16(), Some(f16::INFINITY)); /// /// // but not from other variants - /// let v4 = Variant::from("hello!"); - /// assert_eq!(v4.as_f16(), None); + /// let v5 = Variant::from("hello!"); + /// assert_eq!(v5.as_f16(), None); pub fn as_f16(&self) -> Option { - match *self { - Variant::Float(i) => Some(f16::from_f32(i)), - Variant::Double(i) => Some(f16::from_f64(i)), - Variant::Int8(i) => Some(i.into()), - Variant::Int16(i) if fits_precision::<11>(i) => Some(f16::from_f32(i as _)), - Variant::Int32(i) if fits_precision::<11>(i) => Some(f16::from_f32(i as _)), - Variant::Int64(i) if fits_precision::<11>(i) => Some(f16::from_f32(i as _)), - _ => None, - } + self.as_num() } /// Converts this variant to an `f32` if possible. /// - /// Returns `Some(f32)` for floating point values, and integer values with up to 24 bits of - /// precision. `None` otherwise. + /// Returns `Some(f32)` for floating point values, integer values, and boolean variants. + /// `None` otherwise. /// /// # Examples /// @@ -1224,36 +1240,33 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v2 = Variant::from(std::f64::consts::PI); /// assert_eq!(v2.as_f32(), Some(std::f32::consts::PI)); /// - /// // and from integers with no more than 24 bits of precision - /// let v3 = Variant::from(16777215i64); - /// assert_eq!(v3.as_f32(), Some(16777215.0)); + /// // and from boolean variant + /// let v3 = Variant::BooleanTrue; + /// assert_eq!(v3.as_f32(), Some(1.0)); + /// + /// // and return inf if overflow + /// let v4 = Variant::from(f64::MAX); + /// assert_eq!(v4.as_f32(), Some(f32::INFINITY)); /// /// // but not from other variants - /// let v4 = Variant::from("hello!"); - /// assert_eq!(v4.as_f32(), None); + /// let v5 = Variant::from("hello!"); + /// assert_eq!(v5.as_f32(), None); /// ``` #[allow(clippy::cast_possible_truncation)] pub fn as_f32(&self) -> Option { - match *self { - Variant::Float(i) => Some(i), - Variant::Double(i) => Some(i as f32), - Variant::Int8(i) => Some(i.into()), - Variant::Int16(i) => Some(i.into()), - Variant::Int32(i) if fits_precision::<24>(i) => Some(i as _), - Variant::Int64(i) if fits_precision::<24>(i) => Some(i as _), - _ => None, - } + self.as_num() } /// Converts this variant to an `f64` if possible. /// - /// Returns `Some(f64)` for floating point values, and integer values with up to 53 bits of - /// precision. `None` otherwise. + /// Returns `Some(f64)` for floating point values, integer values, and boolean variants + /// `None` for other variants or can't be represented by an f64. /// /// # Examples /// /// ``` /// use parquet_variant::Variant; + /// use parquet_variant::VariantDecimal16; /// /// // you can extract an f64 from a float variant /// let v1 = Variant::from(std::f32::consts::PI); @@ -1263,24 +1276,16 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v2 = Variant::from(std::f64::consts::PI); /// assert_eq!(v2.as_f64(), Some(std::f64::consts::PI)); /// - /// // and from integers with no more than 53 bits of precision - /// let v3 = Variant::from(9007199254740991i64); - /// assert_eq!(v3.as_f64(), Some(9007199254740991.0)); + /// // and from boolean variant + /// let v3 = Variant::BooleanTrue; + /// assert_eq!(v3.as_f64(), Some(1.0f64)); /// /// // but not from other variants - /// let v4 = Variant::from("hello!"); - /// assert_eq!(v4.as_f64(), None); + /// let v5 = Variant::from("hello!"); + /// assert_eq!(v5.as_f64(), None); /// ``` pub fn as_f64(&self) -> Option { - match *self { - Variant::Float(i) => Some(i.into()), - Variant::Double(i) => Some(i), - Variant::Int8(i) => Some(i.into()), - Variant::Int16(i) => Some(i.into()), - Variant::Int32(i) => Some(i.into()), - Variant::Int64(i) if fits_precision::<53>(i) => Some(i as _), - _ => None, - } + self.as_num() } /// Converts this variant to an `Object` if it is an [`VariantObject`]. @@ -1527,7 +1532,8 @@ impl From for Variant<'_, '_> { if let Ok(value) = i8::try_from(value) { Variant::Int8(value) } else { - Variant::Int16(i16::from(value)) + // It will always fit in i16 because u8 max is 255 and i16 max is 32767 + Variant::Int16(num_cast(value).unwrap()) } } } @@ -1538,7 +1544,8 @@ impl From for Variant<'_, '_> { if let Ok(value) = i16::try_from(value) { Variant::Int16(value) } else { - Variant::Int32(i32::from(value)) + // It will always fit in i32 because u16 max is 65535 and i32 max is 2147483647 + Variant::Int32(num_cast(value).unwrap()) } } } @@ -1548,7 +1555,8 @@ impl From for Variant<'_, '_> { if let Ok(value) = i32::try_from(value) { Variant::Int32(value) } else { - Variant::Int64(i64::from(value)) + // It will always fit in i64 because u32 max is 4294967295 and i64 max is 9223372036854775807 + Variant::Int64(num_cast(value).unwrap()) } } } @@ -1560,7 +1568,7 @@ impl From for Variant<'_, '_> { Variant::Int64(value) } else { // u64 max is 18446744073709551615, which fits in i128 - Variant::Decimal16(VariantDecimal16::try_new(i128::from(value), 0).unwrap()) + Variant::Decimal16(VariantDecimal16::try_new(num_cast(value).unwrap(), 0).unwrap()) } } }