parquet_variant/variant.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18pub use self::decimal::{VariantDecimal4, VariantDecimal8, VariantDecimal16, VariantDecimalType};
19pub use self::list::VariantList;
20pub use self::metadata::{EMPTY_VARIANT_METADATA, EMPTY_VARIANT_METADATA_BYTES, VariantMetadata};
21pub use self::object::VariantObject;
22
23// Publically export types used in the API
24pub use half::f16;
25pub use uuid::Uuid;
26
27use crate::decoder::{
28 self, VariantBasicType, VariantPrimitiveType, get_basic_type, get_primitive_type,
29};
30use crate::path::{VariantPath, VariantPathElement};
31use crate::utils::{first_byte_from_slice, slice_from_slice};
32use std::ops::Deref;
33
34use arrow_schema::ArrowError;
35use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
36
37mod decimal;
38mod list;
39mod metadata;
40mod object;
41
42const MAX_SHORT_STRING_BYTES: usize = 0x3F;
43
44/// The maximum number of nested objects and arrays a [`Variant`] may contain.
45///
46/// Full [validation] recurses into nested values, so unbounded nesting would overflow the stack --
47/// an abort, not a catchable panic. Validation therefore rejects more deeply nested values, which
48/// also bounds the recursion of infallible accesses such as [`Debug`] and [`PartialEq`].
49///
50/// The variant [spec] does not specify a limit. This value matches the default `serde_json`
51/// recursion limit, so any variant parsed from JSON already satisfies it.
52///
53/// [validation]: Variant#Validation
54/// [spec]: https://github.com/apache/parquet-format/blob/master/VariantEncoding.md
55pub const MAX_NESTING_DEPTH: usize = 128;
56
57/// A Variant [`ShortString`]
58///
59/// This implementation is a zero cost wrapper over `&str` that ensures
60/// the length of the underlying string is a valid Variant short string (63 bytes or less)
61#[derive(Debug, Clone, Copy, PartialEq)]
62pub struct ShortString<'a>(pub(crate) &'a str);
63
64impl<'a> ShortString<'a> {
65 /// Attempts to interpret `value` as a variant short string value.
66 ///
67 /// # Errors
68 ///
69 /// Returns an error if `value` is longer than the maximum allowed length
70 /// of a Variant short string (63 bytes).
71 pub fn try_new(value: &'a str) -> Result<Self, ArrowError> {
72 if value.len() > MAX_SHORT_STRING_BYTES {
73 return Err(ArrowError::InvalidArgumentError(format!(
74 "value is larger than {MAX_SHORT_STRING_BYTES} bytes"
75 )));
76 }
77
78 Ok(Self(value))
79 }
80
81 /// Returns the underlying Variant short string as a &str
82 pub fn as_str(&self) -> &'a str {
83 self.0
84 }
85}
86
87impl<'a> From<ShortString<'a>> for &'a str {
88 fn from(value: ShortString<'a>) -> Self {
89 value.0
90 }
91}
92
93impl<'a> TryFrom<&'a str> for ShortString<'a> {
94 type Error = ArrowError;
95
96 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
97 Self::try_new(value)
98 }
99}
100
101impl AsRef<str> for ShortString<'_> {
102 fn as_ref(&self) -> &str {
103 self.0
104 }
105}
106
107impl Deref for ShortString<'_> {
108 type Target = str;
109
110 fn deref(&self) -> &Self::Target {
111 self.0
112 }
113}
114
115/// Represents a [Parquet Variant]
116///
117/// The lifetimes `'m` and `'v` are for metadata and value buffers, respectively.
118///
119/// # Background
120///
121/// The [specification] says:
122///
123/// The Variant Binary Encoding allows representation of semi-structured data
124/// (e.g. JSON) in a form that can be efficiently queried by path. The design is
125/// intended to allow efficient access to nested data even in the presence of
126/// very wide or deep structures.
127///
128/// Another motivation for the representation is that (aside from metadata) each
129/// nested Variant value is contiguous and self-contained. For example, in a
130/// Variant containing an Array of Variant values, the representation of an
131/// inner Variant value, when paired with the metadata of the full variant, is
132/// itself a valid Variant.
133///
134/// When stored in Parquet files, Variant fields can also be *shredded*. Shredding
135/// refers to extracting some elements of the variant into separate columns for
136/// more efficient extraction/filter pushdown. The [Variant Shredding
137/// specification] describes the details of shredding Variant values as typed
138/// Parquet columns.
139///
140/// A Variant represents a type that contains one of:
141///
142/// * Primitive: A type and corresponding value (e.g. INT, STRING)
143///
144/// * Array: An ordered list of Variant values
145///
146/// * Object: An unordered collection of string/Variant pairs (i.e. key/value
147/// pairs). An object may not contain duplicate keys.
148///
149/// # Encoding
150///
151/// A Variant is encoded with 2 binary values, the value and the metadata. The
152/// metadata stores a header and an optional dictionary of field names which are
153/// referred to by offset in the value. The value is a binary representation of
154/// the actual data, and varies depending on the type.
155///
156/// # Design Goals
157///
158/// The design goals of the Rust API are as follows:
159/// 1. Speed / Zero copy access (no `clone`ing is required)
160/// 2. Safety
161/// 3. Follow standard Rust conventions
162///
163/// [Parquet Variant]: https://github.com/apache/parquet-format/blob/master/VariantEncoding.md
164/// [specification]: https://github.com/apache/parquet-format/blob/master/VariantEncoding.md
165/// [Variant Shredding specification]: https://github.com/apache/parquet-format/blob/master/VariantShredding.md
166///
167/// # Examples:
168///
169/// ## Creating `Variant` from Rust Types
170/// ```
171/// use parquet_variant::Variant;
172/// // variants can be directly constructed
173/// let variant = Variant::Int32(123);
174/// // or constructed via `From` impls
175/// assert_eq!(variant, Variant::from(123i32));
176/// ```
177/// ## Creating `Variant` from metadata and value
178/// ```
179/// # use parquet_variant::{Variant, VariantMetadata};
180/// let metadata = [0x01, 0x00, 0x00];
181/// let value = [0x09, 0x48, 0x49];
182/// // parse the header metadata
183/// assert_eq!(
184/// Variant::from("HI"),
185/// Variant::new(&metadata, &value)
186/// );
187/// ```
188///
189/// ## Using `Variant` values
190/// ```
191/// # use parquet_variant::Variant;
192/// # let variant = Variant::Int32(123);
193/// // variants can be used in match statements like normal enums
194/// match variant {
195/// Variant::Int32(i) => println!("Integer: {}", i),
196/// Variant::String(s) => println!("String: {}", s),
197/// _ => println!("Other variant"),
198/// }
199/// ```
200///
201/// # Validation
202///
203/// Every instance of variant is either _valid_ or _invalid_. depending on whether the
204/// underlying bytes are a valid encoding of a variant value (see below).
205///
206/// Instances produced by [`Self::try_new`], [`Self::try_new_with_metadata`], or [`Self::with_full_validation`]
207/// are fully _validated_. They always contain _valid_ data, and infallible accesses such as
208/// iteration and indexing are panic-free. The validation cost is `O(m + v)` where `m` and
209/// `v` are the number of bytes in the metadata and value buffers, respectively.
210///
211/// Instances produced by [`Self::new`] and [`Self::new_with_metadata`] are _unvalidated_ and so
212/// they may contain either _valid_ or _invalid_ data. Infallible accesses to variant objects and
213/// arrays, such as iteration and indexing will panic if the underlying bytes are _invalid_, and
214/// fallible alternatives are provided as panic-free alternatives. [`Self::with_full_validation`] can also be
215/// used to _validate_ an _unvalidated_ instance, if desired.
216///
217/// _Unvalidated_ instances can be constructed in constant time. This can be useful if the caller
218/// knows the underlying bytes were already validated previously, or if the caller intends to
219/// perform a small number of (fallible) accesses to a large variant value.
220///
221/// A _validated_ variant value guarantees that the associated [metadata] and all nested [object]
222/// and [array] values are _valid_. Primitive variant subtypes are always _valid_ by construction.
223///
224/// # Safety
225///
226/// Even an _invalid_ variant value is still _safe_ to use in the Rust sense. Accessing it with
227/// infallible methods may cause panics but will never lead to undefined behavior.
228///
229/// [metadata]: VariantMetadata#Validation
230/// [object]: VariantObject#Validation
231/// [array]: VariantList#Validation
232#[derive(Clone, PartialEq)]
233pub enum Variant<'m, 'v> {
234 /// Primitive type: Null
235 Null,
236 /// Primitive (type_id=1): INT(8, SIGNED)
237 Int8(i8),
238 /// Primitive (type_id=1): INT(16, SIGNED)
239 Int16(i16),
240 /// Primitive (type_id=1): INT(32, SIGNED)
241 Int32(i32),
242 /// Primitive (type_id=1): INT(64, SIGNED)
243 Int64(i64),
244 /// Primitive (type_id=1): DATE
245 Date(NaiveDate),
246 /// Primitive (type_id=1): TIMESTAMP(isAdjustedToUTC=true, MICROS)
247 TimestampMicros(DateTime<Utc>),
248 /// Primitive (type_id=1): TIMESTAMP(isAdjustedToUTC=false, MICROS)
249 TimestampNtzMicros(NaiveDateTime),
250 /// Primitive (type_id=1): TIMESTAMP(isAdjustedToUTC=true, NANOS)
251 TimestampNanos(DateTime<Utc>),
252 /// Primitive (type_id=1): TIMESTAMP(isAdjustedToUTC=false, NANOS)
253 TimestampNtzNanos(NaiveDateTime),
254 /// Primitive (type_id=1): DECIMAL(precision, scale) 32-bits
255 Decimal4(VariantDecimal4),
256 /// Primitive (type_id=1): DECIMAL(precision, scale) 64-bits
257 Decimal8(VariantDecimal8),
258 /// Primitive (type_id=1): DECIMAL(precision, scale) 128-bits
259 Decimal16(VariantDecimal16),
260 /// Primitive (type_id=1): FLOAT
261 Float(f32),
262 /// Primitive (type_id=1): DOUBLE
263 Double(f64),
264 /// Primitive (type_id=1): BOOLEAN (true)
265 BooleanTrue,
266 /// Primitive (type_id=1): BOOLEAN (false)
267 BooleanFalse,
268 // Note: only need the *value* buffer for these types
269 /// Primitive (type_id=1): BINARY
270 Binary(&'v [u8]),
271 /// Primitive (type_id=1): STRING
272 String(&'v str),
273 /// Primitive (type_id=1): TIME(isAdjustedToUTC=false, MICROS)
274 Time(NaiveTime),
275 /// Primitive (type_id=1): UUID
276 Uuid(Uuid),
277 /// Short String (type_id=2): STRING
278 ShortString(ShortString<'v>),
279 // need both metadata & value
280 /// Object (type_id=3): N/A
281 Object(VariantObject<'m, 'v>),
282 /// Array (type_id=4): N/A
283 List(VariantList<'m, 'v>),
284}
285
286// We don't want this to grow because it could hurt performance of a frequently-created type.
287#[cfg(all(target_pointer_width = "64", target_arch = "s390x"))]
288const _: () = crate::utils::expect_size_of::<Variant>(72);
289#[cfg(all(target_pointer_width = "64", not(target_arch = "s390x")))]
290const _: () = crate::utils::expect_size_of::<Variant>(80);
291#[cfg(target_pointer_width = "32")]
292const _: () = crate::utils::expect_size_of::<Variant>(48);
293
294impl<'m, 'v> Variant<'m, 'v> {
295 /// Attempts to interpret a metadata and value buffer pair as a new `Variant`.
296 ///
297 /// The instance is fully [validated].
298 ///
299 /// # Example
300 /// ```
301 /// use parquet_variant::{Variant, VariantMetadata};
302 /// let metadata = [0x01, 0x00, 0x00];
303 /// let value = [0x09, 0x48, 0x49];
304 /// // parse the header metadata
305 /// assert_eq!(
306 /// Variant::from("HI"),
307 /// Variant::try_new(&metadata, &value).unwrap()
308 /// );
309 /// ```
310 ///
311 /// [validated]: Self#Validation
312 pub fn try_new(metadata: &'m [u8], value: &'v [u8]) -> Result<Self, ArrowError> {
313 let metadata = VariantMetadata::try_new(metadata)?;
314 Self::try_new_with_metadata(metadata, value)
315 }
316
317 /// Attempts to interpret a metadata and value buffer pair as a new `Variant`.
318 ///
319 /// The instance is [unvalidated].
320 ///
321 /// # Example
322 /// ```
323 /// use parquet_variant::{Variant, VariantMetadata};
324 /// let metadata = [0x01, 0x00, 0x00];
325 /// let value = [0x09, 0x48, 0x49];
326 /// // parse the header metadata
327 /// assert_eq!(
328 /// Variant::from("HI"),
329 /// Variant::new(&metadata, &value)
330 /// );
331 /// ```
332 ///
333 /// [unvalidated]: Self#Validation
334 ///
335 /// # Panics
336 ///
337 /// Panics if `metadata` or `value` fail basic validation. Use [`Self::try_new`]
338 /// for a fallible version.
339 pub fn new(metadata: &'m [u8], value: &'v [u8]) -> Self {
340 let metadata = VariantMetadata::try_new_with_shallow_validation(metadata)
341 .expect("Invalid variant metadata");
342 Self::try_new_with_metadata_and_shallow_validation(metadata, value)
343 .expect("Invalid variant data")
344 }
345
346 /// Create a new variant with existing metadata.
347 ///
348 /// The instance is fully [validated].
349 ///
350 /// # Example
351 /// ```
352 /// # use parquet_variant::{Variant, VariantMetadata};
353 /// let metadata = [0x01, 0x00, 0x00];
354 /// let value = [0x09, 0x48, 0x49];
355 /// // parse the header metadata first
356 /// let metadata = VariantMetadata::new(&metadata);
357 /// assert_eq!(
358 /// Variant::from("HI"),
359 /// Variant::try_new_with_metadata(metadata, &value).unwrap()
360 /// );
361 /// ```
362 ///
363 /// [validated]: Self#Validation
364 pub fn try_new_with_metadata(
365 metadata: VariantMetadata<'m>,
366 value: &'v [u8],
367 ) -> Result<Self, ArrowError> {
368 Self::try_new_with_metadata_at_depth(metadata, value, 0)
369 }
370
371 // Same as [`Self::try_new_with_metadata`], tracking how deeply validation has recursed.
372 pub(crate) fn try_new_with_metadata_at_depth(
373 metadata: VariantMetadata<'m>,
374 value: &'v [u8],
375 depth: usize,
376 ) -> Result<Self, ArrowError> {
377 Self::try_new_with_metadata_and_shallow_validation(metadata, value)?
378 .with_full_validation_at_depth(depth)
379 }
380
381 /// Similar to [`Self::try_new_with_metadata`], but [unvalidated].
382 ///
383 /// [unvalidated]: Self#Validation
384 ///
385 /// # Panics
386 ///
387 /// Panics if `value` fails basic validation. Use [`Self::try_new_with_metadata`]
388 /// for a fallible version.
389 pub fn new_with_metadata(metadata: VariantMetadata<'m>, value: &'v [u8]) -> Self {
390 Self::try_new_with_metadata_and_shallow_validation(metadata, value)
391 .expect("Invalid variant")
392 }
393
394 // The actual constructor, which only performs shallow (constant-time) validation.
395 fn try_new_with_metadata_and_shallow_validation(
396 metadata: VariantMetadata<'m>,
397 value: &'v [u8],
398 ) -> Result<Self, ArrowError> {
399 let value_metadata = first_byte_from_slice(value)?;
400 let value_data = slice_from_slice(value, 1..)?;
401 let new_self = match get_basic_type(value_metadata) {
402 VariantBasicType::Primitive => match get_primitive_type(value_metadata)? {
403 VariantPrimitiveType::Null => Variant::Null,
404 VariantPrimitiveType::Int8 => Variant::Int8(decoder::decode_int8(value_data)?),
405 VariantPrimitiveType::Int16 => Variant::Int16(decoder::decode_int16(value_data)?),
406 VariantPrimitiveType::Int32 => Variant::Int32(decoder::decode_int32(value_data)?),
407 VariantPrimitiveType::Int64 => Variant::Int64(decoder::decode_int64(value_data)?),
408 VariantPrimitiveType::Decimal4 => {
409 let (integer, scale) = decoder::decode_decimal4(value_data)?;
410 Variant::Decimal4(VariantDecimal4::try_new(integer, scale)?)
411 }
412 VariantPrimitiveType::Decimal8 => {
413 let (integer, scale) = decoder::decode_decimal8(value_data)?;
414 Variant::Decimal8(VariantDecimal8::try_new(integer, scale)?)
415 }
416 VariantPrimitiveType::Decimal16 => {
417 let (integer, scale) = decoder::decode_decimal16(value_data)?;
418 Variant::Decimal16(VariantDecimal16::try_new(integer, scale)?)
419 }
420 VariantPrimitiveType::Float => Variant::Float(decoder::decode_float(value_data)?),
421 VariantPrimitiveType::Double => {
422 Variant::Double(decoder::decode_double(value_data)?)
423 }
424 VariantPrimitiveType::BooleanTrue => Variant::BooleanTrue,
425 VariantPrimitiveType::BooleanFalse => Variant::BooleanFalse,
426 VariantPrimitiveType::Date => Variant::Date(decoder::decode_date(value_data)?),
427 VariantPrimitiveType::TimestampMicros => {
428 Variant::TimestampMicros(decoder::decode_timestamp_micros(value_data)?)
429 }
430 VariantPrimitiveType::TimestampNtzMicros => {
431 Variant::TimestampNtzMicros(decoder::decode_timestampntz_micros(value_data)?)
432 }
433 VariantPrimitiveType::TimestampNanos => {
434 Variant::TimestampNanos(decoder::decode_timestamp_nanos(value_data)?)
435 }
436 VariantPrimitiveType::TimestampNtzNanos => {
437 Variant::TimestampNtzNanos(decoder::decode_timestampntz_nanos(value_data)?)
438 }
439 VariantPrimitiveType::Uuid => Variant::Uuid(decoder::decode_uuid(value_data)?),
440 VariantPrimitiveType::Binary => {
441 Variant::Binary(decoder::decode_binary(value_data)?)
442 }
443 VariantPrimitiveType::String => {
444 Variant::String(decoder::decode_long_string(value_data)?)
445 }
446 VariantPrimitiveType::Time => Variant::Time(decoder::decode_time_ntz(value_data)?),
447 },
448 VariantBasicType::ShortString => {
449 Variant::ShortString(decoder::decode_short_string(value_metadata, value_data)?)
450 }
451 VariantBasicType::Object => Variant::Object(
452 VariantObject::try_new_with_shallow_validation(metadata, value)?,
453 ),
454 VariantBasicType::Array => Variant::List(VariantList::try_new_with_shallow_validation(
455 metadata, value,
456 )?),
457 };
458 Ok(new_self)
459 }
460
461 /// True if this variant instance has already been [validated].
462 ///
463 /// [validated]: Self#Validation
464 pub fn is_fully_validated(&self) -> bool {
465 match self {
466 Variant::List(list) => list.is_fully_validated(),
467 Variant::Object(obj) => obj.is_fully_validated(),
468 _ => true,
469 }
470 }
471
472 /// Recursively validates this variant value, ensuring that infallible access will not panic due
473 /// to invalid bytes.
474 ///
475 /// Variant leaf values are always valid by construction, but [objects] and [arrays] can be
476 /// constructed in unvalidated (and potentially invalid) state.
477 ///
478 /// If [`Self::is_fully_validated`] is true, validation is a no-op. Otherwise, the cost is `O(m + v)`
479 /// where `m` and `v` are the sizes of metadata and value buffers, respectively.
480 ///
481 /// Values nested more than [`MAX_NESTING_DEPTH`] deep are rejected.
482 ///
483 /// [objects]: VariantObject#Validation
484 /// [arrays]: VariantList#Validation
485 pub fn with_full_validation(self) -> Result<Self, ArrowError> {
486 self.with_full_validation_at_depth(0)
487 }
488
489 // Same as [`Self::with_full_validation`], tracking how deeply validation has recursed.
490 pub(crate) fn with_full_validation_at_depth(self, depth: usize) -> Result<Self, ArrowError> {
491 use Variant::*;
492 match self {
493 List(list) => list.with_full_validation_at_depth(depth).map(List),
494 Object(obj) => obj.with_full_validation_at_depth(depth).map(Object),
495 _ => Ok(self),
496 }
497 }
498
499 /// Converts this variant to `()` if it is null.
500 ///
501 /// Returns `Some(())` for null variants,
502 /// `None` for non-null variants.
503 ///
504 /// # Examples
505 ///
506 /// ```
507 /// use parquet_variant::Variant;
508 ///
509 /// // you can extract `()` from a null variant
510 /// let v1 = Variant::from(());
511 /// assert_eq!(v1.as_null(), Some(()));
512 ///
513 /// // but not from other variants
514 /// let v2 = Variant::from("hello!");
515 /// assert_eq!(v2.as_null(), None);
516 /// ```
517 pub fn as_null(&self) -> Option<()> {
518 matches!(self, Variant::Null).then_some(())
519 }
520
521 /// Converts this variant to a `bool` if possible.
522 ///
523 /// Returns `Some(bool)` for boolean variants,
524 /// `None` for non-boolean variants.
525 ///
526 /// # Examples
527 ///
528 /// ```
529 /// use parquet_variant::Variant;
530 ///
531 /// // you can extract a bool from the true variant
532 /// let v1 = Variant::from(true);
533 /// assert_eq!(v1.as_boolean(), Some(true));
534 ///
535 /// // and the false variant
536 /// let v2 = Variant::from(false);
537 /// assert_eq!(v2.as_boolean(), Some(false));
538 ///
539 /// // but not from other variants
540 /// let v3 = Variant::from("hello!");
541 /// assert_eq!(v3.as_boolean(), None);
542 /// ```
543 pub fn as_boolean(&self) -> Option<bool> {
544 match self {
545 Variant::BooleanTrue => Some(true),
546 Variant::BooleanFalse => Some(false),
547 _ => None,
548 }
549 }
550
551 /// Converts this variant to a `NaiveDate` if possible.
552 ///
553 /// Returns `Some(NaiveDate)` for date variants,
554 /// `None` for non-date variants.
555 ///
556 /// # Examples
557 ///
558 /// ```
559 /// use parquet_variant::Variant;
560 /// use chrono::NaiveDate;
561 ///
562 /// // you can extract a NaiveDate from a date variant
563 /// let date = NaiveDate::from_ymd_opt(2025, 4, 12).unwrap();
564 /// let v1 = Variant::from(date);
565 /// assert_eq!(v1.as_naive_date(), Some(date));
566 ///
567 /// // but not from other variants
568 /// let v2 = Variant::from("hello!");
569 /// assert_eq!(v2.as_naive_date(), None);
570 /// ```
571 pub fn as_naive_date(&self) -> Option<NaiveDate> {
572 if let Variant::Date(d) = self {
573 Some(*d)
574 } else {
575 None
576 }
577 }
578
579 /// Converts this variant to a `DateTime<Utc>` if possible.
580 ///
581 /// Returns `Some(DateTime<Utc>)` for timestamp(micro&nano) variants if fits in the range,
582 /// `None` for other variants or the value can't fit in the micro second range.
583 ///
584 /// # Examples
585 ///
586 /// ```
587 /// use parquet_variant::Variant;
588 /// use chrono::NaiveDate;
589 ///
590 /// // you can extract a DateTime<Utc> from a UTC-adjusted variant
591 /// let datetime = NaiveDate::from_ymd_opt(2025, 4, 16)
592 /// .unwrap()
593 /// .and_hms_milli_opt(12, 34, 56, 780)
594 /// .unwrap()
595 /// .and_utc();
596 /// let v1 = Variant::from(datetime);
597 /// assert_eq!(v1.as_timestamp_micros(), Some(datetime));
598 ///
599 /// // or from a timestamp nano variant that can fit into micro second range.
600 /// let datetime_nanos = NaiveDate::from_ymd_opt(2026, 7, 15)
601 /// .unwrap()
602 /// .and_hms_nano_opt(12, 34, 56, 123456000)
603 /// .unwrap()
604 /// .and_utc();
605 /// // construct the variant directly, because variant::from will treat this into a timestamp micro variant
606 /// let v2 = Variant::TimestampNanos(datetime_nanos);
607 /// assert_eq!(v2.as_timestamp_micros(), Some(datetime_nanos));
608 ///
609 /// // but not for a non-microsecond-aligned nanosecond variant
610 /// let datetime_nanos = NaiveDate::from_ymd_opt(2025, 8, 14)
611 /// .unwrap()
612 /// .and_hms_nano_opt(12, 33, 54, 123456789)
613 /// .unwrap()
614 /// .and_utc();
615 /// let v3 = Variant::from(datetime_nanos);
616 /// assert_eq!(v3.as_timestamp_micros(), None);
617 ///
618 /// // or from other variant
619 /// let v4 = Variant::from("hello");
620 /// assert_eq!(v4.as_timestamp_micros(), None);
621 /// ```
622 pub fn as_timestamp_micros(&self) -> Option<DateTime<Utc>> {
623 match *self {
624 Variant::TimestampMicros(d) => Some(d),
625 Variant::TimestampNanos(d) if d.nanosecond() % 1_000 == 0 => Some(d),
626 _ => None,
627 }
628 }
629
630 /// Converts this variant to a `NaiveDateTime` if possible.
631 ///
632 /// Returns `Some(NaiveDateTime)` for [`Variant::TimestampNtzMicros`] variants,
633 /// `None` for other variants.
634 ///
635 /// # Examples
636 ///
637 /// ```
638 /// use parquet_variant::Variant;
639 /// use chrono::NaiveDate;
640 ///
641 /// // you can extract a NaiveDateTime from a non-UTC-adjusted variant
642 /// let datetime = NaiveDate::from_ymd_opt(2025, 4, 16)
643 /// .unwrap()
644 /// .and_hms_milli_opt(12, 34, 56, 780)
645 /// .unwrap();
646 /// let v1 = Variant::from(datetime);
647 /// assert_eq!(v1.as_timestamp_ntz_micros(), Some(datetime));
648 ///
649 /// // or from a non-UTC-adjusted timestamp nano variant that can fit into microsecond range
650 /// let datetime_nanos = NaiveDate::from_ymd_opt(2026, 7, 15)
651 /// .unwrap()
652 /// .and_hms_nano_opt(12, 34, 56, 123456000)
653 /// .unwrap();
654 /// // construct the variant directly, because variant::from will treat this into a timestamp variant
655 /// let v2 = Variant::TimestampNtzNanos(datetime_nanos);
656 /// assert_eq!(v2.as_timestamp_ntz_micros(), Some(datetime_nanos));
657 ///
658 /// // but not for a non-microsecond-aligned nanosecond variant.
659 /// let datetime_nanos = NaiveDate::from_ymd_opt(2025, 8, 14)
660 /// .unwrap()
661 /// .and_hms_nano_opt(12, 33, 54, 123456789)
662 /// .unwrap();
663 /// let v3 = Variant::from(datetime_nanos);
664 /// assert_eq!(v3.as_timestamp_ntz_micros(), None);
665 ///
666 /// // or other variant
667 /// let v4 = Variant::from("hello");
668 /// assert_eq!(v4.as_timestamp_ntz_micros(), None);
669 /// ```
670 pub fn as_timestamp_ntz_micros(&self) -> Option<NaiveDateTime> {
671 match *self {
672 Variant::TimestampNtzMicros(d) => Some(d),
673 Variant::TimestampNtzNanos(d) if d.nanosecond() % 1000 == 0 => Some(d),
674 _ => None,
675 }
676 }
677
678 /// Converts this variant to a `DateTime<Utc>` if possible.
679 ///
680 /// Returns `Some(DateTime<Utc>)` for timestamp variants,
681 /// `None` for other variants.
682 ///
683 /// # Examples
684 ///
685 /// ```
686 /// use parquet_variant::Variant;
687 /// use chrono::NaiveDate;
688 ///
689 /// // you can extract a DateTime<Utc> from a UTC-adjusted nanosecond-precision variant
690 /// let datetime = NaiveDate::from_ymd_opt(2025, 4, 16)
691 /// .unwrap()
692 /// .and_hms_nano_opt(12, 34, 56, 789123456)
693 /// .unwrap()
694 /// .and_utc();
695 /// let v1 = Variant::from(datetime);
696 /// assert_eq!(v1.as_timestamp_nanos(), Some(datetime));
697 ///
698 /// // or from UTC-adjusted microsecond-precision variant
699 /// let datetime_micros = NaiveDate::from_ymd_opt(2025, 8, 14)
700 /// .unwrap()
701 /// .and_hms_milli_opt(12, 33, 54, 123)
702 /// .unwrap()
703 /// .and_utc();
704 /// // this will convert to `Variant::TimestampMicros`.
705 /// let v2 = Variant::from(datetime_micros);
706 /// assert_eq!(v2.as_timestamp_nanos(), Some(datetime_micros));
707 ///
708 /// // but not for other variants.
709 /// let v3 = Variant::from("hello!");
710 /// assert_eq!(v3.as_timestamp_nanos(), None);
711 /// ```
712 pub fn as_timestamp_nanos(&self) -> Option<DateTime<Utc>> {
713 match *self {
714 Variant::TimestampNanos(d) | Variant::TimestampMicros(d) => Some(d),
715 _ => None,
716 }
717 }
718
719 /// Converts this variant to a `NaiveDateTime` if possible.
720 ///
721 /// Returns `Some(NaiveDateTime)` for timestamp variants,
722 /// `None` for other variants.
723 ///
724 /// # Examples
725 ///
726 /// ```
727 /// use parquet_variant::Variant;
728 /// use chrono::NaiveDate;
729 ///
730 /// // you can extract a NaiveDateTime from a non-UTC-adjusted variant
731 /// let datetime = NaiveDate::from_ymd_opt(2025, 4, 16)
732 /// .unwrap()
733 /// .and_hms_nano_opt(12, 34, 56, 789123456)
734 /// .unwrap();
735 /// let v1 = Variant::from(datetime);
736 /// assert_eq!(v1.as_timestamp_ntz_nanos(), Some(datetime));
737 ///
738 /// // or from a microsecond-precision non-UTC-adjusted variant
739 /// let datetime_micros = NaiveDate::from_ymd_opt(2025, 8, 14)
740 /// .unwrap()
741 /// .and_hms_milli_opt(12, 33, 54, 123)
742 /// .unwrap();
743 /// // this will convert to `Variant::TimestampMicros`.
744 /// let v2 = Variant::from(datetime_micros);
745 /// assert_eq!(v2.as_timestamp_ntz_nanos(), Some(datetime_micros));
746 ///
747 /// // but not for other variants.
748 /// let v3 = Variant::from("hello!");
749 /// assert_eq!(v3.as_timestamp_ntz_nanos(), None);
750 /// ```
751 pub fn as_timestamp_ntz_nanos(&self) -> Option<NaiveDateTime> {
752 match *self {
753 Variant::TimestampNtzNanos(d) | Variant::TimestampNtzMicros(d) => Some(d),
754 _ => None,
755 }
756 }
757
758 /// Converts this variant to a `&[u8]` if possible.
759 ///
760 /// Returns `Some(&[u8])` for binary variants,
761 /// `None` for non-binary variants.
762 ///
763 /// # Examples
764 ///
765 /// ```
766 /// use parquet_variant::Variant;
767 ///
768 /// // you can extract a byte slice from a binary variant
769 /// let data = b"hello!";
770 /// let v1 = Variant::Binary(data);
771 /// assert_eq!(v1.as_u8_slice(), Some(data.as_slice()));
772 ///
773 /// // but not from other variant types
774 /// let v2 = Variant::from(123i64);
775 /// assert_eq!(v2.as_u8_slice(), None);
776 /// ```
777 pub fn as_u8_slice(&'v self) -> Option<&'v [u8]> {
778 if let Variant::Binary(d) = self {
779 Some(d)
780 } else {
781 None
782 }
783 }
784
785 /// Converts this variant to a `&str` if possible.
786 ///
787 /// Returns `Some(&str)` for string variants (both regular and short strings),
788 /// `None` for non-string variants.
789 ///
790 /// # Examples
791 ///
792 /// ```
793 /// use parquet_variant::Variant;
794 ///
795 /// // you can extract a string from string variants
796 /// let s = "hello!";
797 /// let v1 = Variant::from(s);
798 /// assert_eq!(v1.as_string(), Some(s));
799 ///
800 /// // but not from other variants
801 /// let v2 = Variant::from(123i64);
802 /// assert_eq!(v2.as_string(), None);
803 /// ```
804 pub fn as_string(&'v self) -> Option<&'v str> {
805 match self {
806 Variant::String(s) | Variant::ShortString(ShortString(s)) => Some(s),
807 _ => None,
808 }
809 }
810
811 /// Converts this variant to a `uuid hyphenated string` if possible.
812 ///
813 /// Returns `Some(String)` for UUID variants, `None` for non-UUID variants.
814 ///
815 /// # Examples
816 ///
817 /// ```
818 /// use parquet_variant::Variant;
819 ///
820 /// // You can extract a UUID from a UUID variant
821 /// let s = uuid::Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").unwrap();
822 /// let v1 = Variant::Uuid(s);
823 /// assert_eq!(s, v1.as_uuid().unwrap());
824 /// assert_eq!("67e55044-10b1-426f-9247-bb680e5fe0c8", v1.as_uuid().unwrap().to_string());
825 ///
826 /// //but not from other variants
827 /// let v2 = Variant::from(1234);
828 /// assert_eq!(None, v2.as_uuid())
829 /// ```
830 pub fn as_uuid(&self) -> Option<Uuid> {
831 match self {
832 Variant::Uuid(u) => Some(*u),
833 _ => None,
834 }
835 }
836
837 /// Converts this variant to an `i8` if possible.
838 ///
839 /// Returns `Some(i8)` for int variants, decimal variants has no fractional part
840 /// (scale = 0, or unscaled integer is divisible by 10^scale) that fits in `i8` range.
841 /// `None` for other variants or values that would overflow.
842 /// # Examples
843 ///
844 /// ```
845 /// use parquet_variant::{Variant, VariantDecimal4};
846 ///
847 /// // you can read an int64 variant into an i8 if it fits
848 /// let v1 = Variant::from(123i64);
849 /// assert_eq!(v1.as_int8(), Some(123i8));
850 ///
851 /// // or from a decimal variant with scale = 0 that fits in i8 range
852 /// let d = VariantDecimal4::try_new(123, 0).unwrap();
853 /// let v2 = Variant::from(d);
854 /// assert_eq!(v2.as_int8(), Some(123i8));
855 ///
856 /// // or from a decimal variant that unscaled value is divisible by 10^scale
857 /// let d = VariantDecimal4::try_new(100, 2).unwrap();
858 /// let v3 = Variant::from(d);
859 /// assert_eq!(v3.as_int8(), Some(1i8));
860 ///
861 /// // but not if it would overflow
862 /// let d = VariantDecimal4::try_new(1234i32, 0).unwrap();
863 /// let v4 = Variant::from(d);
864 /// assert_eq!(v4.as_int8(), None);
865 ///
866 /// // or if the variant cannot be cast into an integer
867 /// let v5 = Variant::from("hello");
868 /// assert_eq!(v5.as_int8(), None);
869 /// ```
870 pub fn as_int8(&self) -> Option<i8> {
871 match *self {
872 Variant::Int8(i) => Some(i),
873 Variant::Int16(i) => i.try_into().ok(),
874 Variant::Int32(i) => i.try_into().ok(),
875 Variant::Int64(i) => i.try_into().ok(),
876 Variant::Decimal4(d) => d.as_integer()?.try_into().ok(),
877 Variant::Decimal8(d) => d.as_integer()?.try_into().ok(),
878 Variant::Decimal16(d) => d.as_integer()?.try_into().ok(),
879 _ => None,
880 }
881 }
882
883 /// Converts this variant to an `i16` if possible.
884 ///
885 /// Returns `Some(i16)` for int variant, decimal variant has no fractional part
886 /// (scale=0 or unscaled integer is divisible by 10^scale) that fits in `i16` range.
887 /// `None` for other variants or values that would overflow.
888 ///
889 /// # Examples
890 ///
891 /// ```
892 /// use parquet_variant::{Variant, VariantDecimal4};
893 ///
894 /// // you can read an int64 variant into an i16 if it fits
895 /// let v1 = Variant::from(123i64);
896 /// assert_eq!(v1.as_int16(), Some(123i16));
897 ///
898 /// // or from a decimal variant that scale = 0
899 /// let d = VariantDecimal4::try_new(123, 0).unwrap();
900 /// let v2 = Variant::from(d);
901 /// assert_eq!(v2.as_int16(), Some(123i16));
902 ///
903 /// // or from a decimal variant that unscaled value is divisible by 10^scale
904 /// let d = VariantDecimal4::try_new(100, 2).unwrap();
905 /// let v3 = Variant::from(d);
906 /// assert_eq!(v3.as_int16(), Some(1i16));
907 ///
908 /// // but not if it would overflow
909 /// let d = VariantDecimal4::try_new(123456i32, 0).unwrap();
910 /// let v4 = Variant::from(d);
911 /// assert_eq!(v4.as_int16(), None);
912 ///
913 /// // or if the variant cannot be cast into an integer
914 /// let v5 = Variant::from("hello");
915 /// assert_eq!(v5.as_int16(), None);
916 /// ```
917 pub fn as_int16(&self) -> Option<i16> {
918 match *self {
919 Variant::Int8(i) => Some(i as i16),
920 Variant::Int16(i) => Some(i),
921 Variant::Int32(i) => i.try_into().ok(),
922 Variant::Int64(i) => i.try_into().ok(),
923 Variant::Decimal4(d) => d.as_integer()?.try_into().ok(),
924 Variant::Decimal8(d) => d.as_integer()?.try_into().ok(),
925 Variant::Decimal16(d) => d.as_integer()?.try_into().ok(),
926 _ => None,
927 }
928 }
929
930 /// Converts this variant to an `i32` if possible.
931 ///
932 /// Returns `Some(i32)` for int variant, decimal variant has no fractional part
933 /// (scale=0 or unscaled integer is divisible by 10^scale) that fits in `i32` range.
934 /// `None` for other variants or values that would overflow.
935 /// # Examples
936 ///
937 /// ```
938 /// use parquet_variant::{Variant, VariantDecimal4, VariantDecimal8};
939 ///
940 /// // you can read an int32 variant into an i32
941 /// let v1 = Variant::from(123i32);
942 /// assert_eq!(v1.as_int32(), Some(123i32));
943 ///
944 /// // or from an int64 if it fits
945 /// let v2 = Variant::from(1231i64);
946 /// assert_eq!(v2.as_int32(), Some(1231i32));
947 ///
948 /// // or from decimal variant that scale=0
949 /// let d = VariantDecimal4::try_new(123, 0).unwrap();
950 /// let v4 = Variant::from(d);
951 /// assert_eq!(v4.as_int32(), Some(123i32));
952 ///
953 /// // or from a decimal variant that unscaled value is divisible by 10^scale
954 /// let d = VariantDecimal4::try_new(100, 2).unwrap();
955 /// let v3 = Variant::from(d);
956 /// assert_eq!(v3.as_int32(), Some(1i32));
957 ///
958 /// // but not if it would overflow
959 /// let d = VariantDecimal8::try_new(1234567890123, 0).unwrap();
960 /// let v5 = Variant::from(d);
961 /// assert_eq!(v5.as_int32(), None);
962 ///
963 /// // or if the variant cannot be cast into an integer
964 /// let v6 = Variant::from("hello");
965 /// assert_eq!(v6.as_int32(), None)
966 /// ```
967 pub fn as_int32(&self) -> Option<i32> {
968 match *self {
969 Variant::Int8(i) => Some(i as i32),
970 Variant::Int16(i) => Some(i as i32),
971 Variant::Int32(i) => Some(i),
972 Variant::Int64(i) => i.try_into().ok(),
973 Variant::Decimal4(d) => d.as_integer(),
974 Variant::Decimal8(d) => d.as_integer()?.try_into().ok(),
975 Variant::Decimal16(d) => d.as_integer()?.try_into().ok(),
976 _ => None,
977 }
978 }
979
980 /// Converts this variant to an `i64` if possible.
981 ///
982 /// Returns `Some(i64)` for int variant, decimal variant has no fractional part
983 /// (scale=0 or unscaled integer is divisible by 10^scale) that fits in `i64` range.
984 /// `None` for other variants or values that would overflow.
985 ///
986 /// # Examples
987 ///
988 /// ```
989 /// use parquet_variant::{Variant, VariantDecimal16, VariantDecimal4};
990 ///
991 /// // you can read an int64 variant into an i64
992 /// let v1 = Variant::from(123i64);
993 /// assert_eq!(v1.as_int64(), Some(123i64));
994 ///
995 /// // or from a decimal variant that scale = 0
996 /// let d = VariantDecimal4::try_new(123, 0).unwrap();
997 /// let v2 = Variant::from(d);
998 /// assert_eq!(v2.as_int64(), Some(123i64));
999 ///
1000 /// // or from a decimal variant that unscaled value is divisible by 10^scale
1001 /// let d = VariantDecimal4::try_new(100, 2).unwrap();
1002 /// let v3 = Variant::from(d);
1003 /// assert_eq!(v3.as_int64(), Some(1i64));
1004 ///
1005 /// // but not if it would overflow
1006 /// let d = VariantDecimal16::try_new(i128::from(i64::MAX) + 1, 0).unwrap();
1007 /// let v4 = Variant::from(d);
1008 /// assert_eq!(v4.as_int64(), None);
1009 ///
1010 /// // or if the variant cannot be cast into an integer
1011 /// let v5 = Variant::from("hello!");
1012 /// assert_eq!(v5.as_int64(), None);
1013 /// ```
1014 pub fn as_int64(&self) -> Option<i64> {
1015 match *self {
1016 Variant::Int8(i) => Some(i as i64),
1017 Variant::Int16(i) => Some(i as i64),
1018 Variant::Int32(i) => Some(i as i64),
1019 Variant::Int64(i) => Some(i),
1020 Variant::Decimal4(d) => d.as_integer().map(|i| i as i64),
1021 Variant::Decimal8(d) => d.as_integer(),
1022 Variant::Decimal16(d) => d.as_integer()?.try_into().ok(),
1023 _ => None,
1024 }
1025 }
1026
1027 fn convert_to_unsigned_num<O>(variant: &Variant) -> Option<O>
1028 where
1029 O: TryFrom<i8> + TryFrom<i16> + TryFrom<i32> + TryFrom<i64> + TryFrom<i128>,
1030 {
1031 match *variant {
1032 Variant::Int8(i) => i.try_into().ok(),
1033 Variant::Int16(i) => i.try_into().ok(),
1034 Variant::Int32(i) => i.try_into().ok(),
1035 Variant::Int64(i) => i.try_into().ok(),
1036 Variant::Decimal4(d) => d.as_integer()?.try_into().ok(),
1037 Variant::Decimal8(d) => d.as_integer()?.try_into().ok(),
1038 Variant::Decimal16(d) => d.as_integer()?.try_into().ok(),
1039 _ => None,
1040 }
1041 }
1042
1043 /// Converts this variant to a `u8` if possible.
1044 ///
1045 /// Returns `Some(u8)` for int variant, decimal variant has no fractional part
1046 /// (scale=0 or unscaled integer is divisible by 10^scale) that fits in `u8` range.
1047 /// `None` for other variants or values that would overflow.
1048 ///
1049 /// # Examples
1050 ///
1051 /// ```
1052 /// use parquet_variant::{Variant, VariantDecimal4};
1053 ///
1054 /// // you can read an int64 variant into an u8
1055 /// let v1 = Variant::from(123i64);
1056 /// assert_eq!(v1.as_u8(), Some(123u8));
1057 ///
1058 /// // or from decimal variant that scale = 0
1059 /// let d = VariantDecimal4::try_new(123, 0).unwrap();
1060 /// let v2 = Variant::from(d);
1061 /// assert_eq!(v2.as_u8(), Some(123u8));
1062 ///
1063 /// // or from a decimal variant that unscaled integer is divisible by 10^scale
1064 /// let d = VariantDecimal4::try_new(100, 2).unwrap();
1065 /// let v3 = Variant::from(d);
1066 /// assert_eq!(v3.as_u8(), Some(1u8));
1067 ///
1068 /// // but not a variant that can't fit into the range
1069 /// let d = VariantDecimal4::try_new(-1, 0).unwrap();
1070 /// let v4 = Variant::from(d);
1071 /// assert_eq!(v4.as_u8(), None);
1072 ///
1073 /// // or not a variant that cannot be cast into an integer
1074 /// let v5 = Variant::from("hello");
1075 /// assert_eq!(v5.as_u8(), None);
1076 /// ```
1077 pub fn as_u8(&self) -> Option<u8> {
1078 Self::convert_to_unsigned_num(self)
1079 }
1080
1081 /// Converts this variant to an `u16` if possible.
1082 ///
1083 /// Returns `Some(u16)` for int variant, decimal variant has no fractional part
1084 /// (scale=0 or unscaled integer is divisible by 10^scale) that fits in `u16` range.
1085 /// `None` for other variants or values that would overflow.
1086 ///
1087 /// # Examples
1088 ///
1089 /// ```
1090 /// use parquet_variant::{Variant, VariantDecimal4};
1091 ///
1092 /// // you can read an int64 variant into an u16
1093 /// let v1 = Variant::from(123i64);
1094 /// assert_eq!(v1.as_u16(), Some(123u16));
1095 ///
1096 /// // or from decimal variant that scale = 0
1097 /// let d = VariantDecimal4::try_new(123, 0).unwrap();
1098 /// let v2 = Variant::from(d);
1099 /// assert_eq!(v2.as_u16(), Some(123u16));
1100 ///
1101 /// // or from a decimal variant that unscaled value is divisible by 10^scale
1102 /// let d = VariantDecimal4::try_new(100, 2).unwrap();
1103 /// let v3 = Variant::from(d);
1104 /// assert_eq!(v3.as_u16(), Some(1u16));
1105 ///
1106 /// // but not a variant that can't fit into the range
1107 /// let d = VariantDecimal4::try_new(-1, 0).unwrap();
1108 /// let v4 = Variant::from(d);
1109 /// assert_eq!(v4.as_u16(), None);
1110 ///
1111 /// // or not a variant that cannot be cast into an integer
1112 /// let v5 = Variant::from("hello");
1113 /// assert_eq!(v5.as_u16(), None);
1114 /// ```
1115 pub fn as_u16(&self) -> Option<u16> {
1116 Self::convert_to_unsigned_num(self)
1117 }
1118
1119 /// Converts this variant to an `u32` if possible.
1120 ///
1121 /// Returns `Some(u32)` for int variant, decimal variant has no fractional part
1122 /// (scale=0 or unscaled integer is divisible by 10^scale) that fits in `u32` range.
1123 /// `None` for other variants or values that would overflow.
1124 ///
1125 /// # Examples
1126 ///
1127 /// ```
1128 /// use parquet_variant::{Variant, VariantDecimal4, VariantDecimal8};
1129 ///
1130 /// // you can read an int64 variant into an u32
1131 /// let v1 = Variant::from(123i64);
1132 /// assert_eq!(v1.as_u32(), Some(123u32));
1133 ///
1134 /// // or from decimal variant that scale = 0
1135 /// let d = VariantDecimal4::try_new(123, 0).unwrap();
1136 /// let v2 = Variant::from(d);
1137 /// assert_eq!(v2.as_u32(), Some(123u32));
1138 ///
1139 /// // or from a decimal variant that unscaled value is divisible by 10^scale
1140 /// let d = VariantDecimal4::try_new(100, 2).unwrap();
1141 /// let v3 = Variant::from(d);
1142 /// assert_eq!(v3.as_u32(), Some(1u32));
1143 ///
1144 /// // but not a variant that can't fit into the range
1145 /// let d = VariantDecimal4::try_new(-1, 0).unwrap();
1146 /// let v4 = Variant::from(d);
1147 /// assert_eq!(v4.as_u32(), None);
1148 ///
1149 /// // or not a variant that cannot be cast into an integer
1150 /// let v5 = Variant::from("hello");
1151 /// assert_eq!(v5.as_u32(), None);
1152 /// ```
1153 pub fn as_u32(&self) -> Option<u32> {
1154 Self::convert_to_unsigned_num(self)
1155 }
1156
1157 /// Converts this variant to an `u64` if possible.
1158 ///
1159 /// Returns `Some(u64)` for integer variant, decimal variant has no fractional part
1160 /// (scale=0 or unscaled integer is divisible by 10^scale) that fits in `u64` range.
1161 /// `None` for other variants or values that would overflow.
1162 ///
1163 /// # Examples
1164 ///
1165 /// ```
1166 /// use parquet_variant::{Variant, VariantDecimal16, VariantDecimal4};
1167 ///
1168 /// // you can read an int64 variant into an u64
1169 /// let v1 = Variant::from(123i64);
1170 /// assert_eq!(v1.as_u64(), Some(123u64));
1171 ///
1172 /// // or from a variant decimal with scale = 0
1173 /// let d = VariantDecimal16::try_new(1, 0).unwrap();
1174 /// let v2 = Variant::from(d);
1175 /// assert_eq!(v2.as_u64(), Some(1u64));
1176 ///
1177 /// // or from a decimal variant that unscaled value is divisible by 10^scale
1178 /// let d = VariantDecimal16::try_new(100, 2).unwrap();
1179 /// let v3 = Variant::from(d);
1180 /// assert_eq!(v3.as_u64(), Some(1u64));
1181 ///
1182 /// // but not a variant that can't fit into the range
1183 /// let d = VariantDecimal4::try_new(-1, 0).unwrap();
1184 /// let v4 = Variant::from(d);
1185 /// assert_eq!(v4.as_u64(), None);
1186 ///
1187 /// // or not a variant that cannot be cast into an integer
1188 /// let v5 = Variant::from("hello!");
1189 /// assert_eq!(v5.as_u64(), None);
1190 /// ```
1191 pub fn as_u64(&self) -> Option<u64> {
1192 Self::convert_to_unsigned_num(self)
1193 }
1194
1195 /// Converts this variant to tuple with a 4-byte unscaled value if possible.
1196 ///
1197 /// Returns `Some((i32, u8))` for decimal variants, int variants where the unscaled value fits in
1198 /// `i32` range, `None` for other variants or the value would overflow.
1199 ///
1200 /// # Examples
1201 ///
1202 /// ```
1203 /// use parquet_variant::{Variant, VariantDecimal4, VariantDecimal8};
1204 ///
1205 /// // you can extract decimal parts from smaller or equally-sized decimal variants
1206 /// let v1 = Variant::from(VariantDecimal4::try_new(1234_i32, 2).unwrap());
1207 /// assert_eq!(v1.as_decimal4(), VariantDecimal4::try_new(1234_i32, 2).ok());
1208 ///
1209 /// // and from larger decimal variants if they fit
1210 /// let v2 = Variant::from(VariantDecimal8::try_new(1234_i64, 2).unwrap());
1211 /// assert_eq!(v2.as_decimal4(), VariantDecimal4::try_new(1234_i32, 2).ok());
1212 ///
1213 /// // and from integer if they fit
1214 /// let v3 = Variant::from(123);
1215 /// assert_eq!(v3.as_decimal4(), VariantDecimal4::try_new(123_i32, 0).ok());
1216 ///
1217 /// // but not if the value would overflow i32
1218 /// let v4 = Variant::from(VariantDecimal8::try_new(12345678901i64, 2).unwrap());
1219 /// assert_eq!(v4.as_decimal4(), None);
1220 ///
1221 /// // or if the variant is not a decimal
1222 /// let v5 = Variant::from("hello");
1223 /// assert_eq!(v5.as_decimal4(), None);
1224 /// ```
1225 pub fn as_decimal4(&self) -> Option<VariantDecimal4> {
1226 match *self {
1227 Variant::Int8(i) => VariantDecimal4::try_new(i as i32, 0).ok(),
1228 Variant::Int16(i) => VariantDecimal4::try_new(i as i32, 0).ok(),
1229 Variant::Int32(i) => VariantDecimal4::try_new(i, 0).ok(),
1230 Variant::Int64(i) => {
1231 let i = i32::try_from(i).ok()?;
1232 VariantDecimal4::try_new(i, 0).ok()
1233 }
1234 Variant::Decimal4(decimal4) => Some(decimal4),
1235 Variant::Decimal8(decimal8) => decimal8.try_into().ok(),
1236 Variant::Decimal16(decimal16) => decimal16.try_into().ok(),
1237 _ => None,
1238 }
1239 }
1240
1241 /// Converts this variant to tuple with an 8-byte unscaled value if possible.
1242 ///
1243 /// Returns `Some((i64, u8))` for decimal variants, int variants where the unscaled value
1244 /// fits in `i64` range, `None` for other variants or decimal values that would overflow.
1245 ///
1246 /// # Examples
1247 ///
1248 /// ```
1249 /// use parquet_variant::{Variant, VariantDecimal16, VariantDecimal4, VariantDecimal8};
1250 ///
1251 /// // you can extract decimal parts from smaller or equally-sized decimal variants
1252 /// let v1 = Variant::from(VariantDecimal4::try_new(1234_i32, 2).unwrap());
1253 /// assert_eq!(v1.as_decimal8(), VariantDecimal8::try_new(1234_i64, 2).ok());
1254 ///
1255 /// // and from larger decimal variants if they fit
1256 /// let v2 = Variant::from(VariantDecimal16::try_new(1234_i128, 2).unwrap());
1257 /// assert_eq!(v2.as_decimal8(), VariantDecimal8::try_new(1234_i64, 2).ok());
1258 ///
1259 /// // or from int variants if they fit
1260 /// let v3 = Variant::from(123);
1261 /// assert_eq!(v3.as_decimal8(), VariantDecimal8::try_new(123_i64, 0).ok());
1262 ///
1263 /// // but not if the value would overflow i64
1264 /// let v4 = Variant::from(VariantDecimal16::try_new(2e19 as i128, 2).unwrap());
1265 /// assert_eq!(v4.as_decimal8(), None);
1266 ///
1267 /// // or if the variant is not a decimal
1268 /// let v5 = Variant::from("hello");
1269 /// assert_eq!(v5.as_decimal8(), None);
1270 /// ```
1271 pub fn as_decimal8(&self) -> Option<VariantDecimal8> {
1272 match *self {
1273 Variant::Int8(i) => VariantDecimal8::try_new(i as i64, 0).ok(),
1274 Variant::Int16(i) => VariantDecimal8::try_new(i as i64, 0).ok(),
1275 Variant::Int32(i) => VariantDecimal8::try_new(i as i64, 0).ok(),
1276 Variant::Int64(i) => VariantDecimal8::try_new(i, 0).ok(),
1277 Variant::Decimal4(decimal4) => Some(decimal4.into()),
1278 Variant::Decimal8(decimal8) => Some(decimal8),
1279 Variant::Decimal16(decimal16) => decimal16.try_into().ok(),
1280 _ => None,
1281 }
1282 }
1283
1284 /// Converts this variant to tuple with a 16-byte unscaled value if possible.
1285 ///
1286 /// Returns `Some((i128, u8))` for decimal variants, int variants where the unscaled value
1287 /// fits in `i128` range, `None` for other variants or values that would overflow.
1288 ///
1289 /// # Examples
1290 ///
1291 /// ```
1292 /// use parquet_variant::{Variant, VariantDecimal16, VariantDecimal4};
1293 ///
1294 /// // you can extract decimal parts from smaller or equally-sized decimal variants
1295 /// let d = VariantDecimal16::try_new(2e19 as i128, 2).unwrap();
1296 /// let v1 = Variant::from(d);
1297 /// assert_eq!(v1.as_decimal16(), VariantDecimal16::try_new(2e19 as i128, 2).ok());
1298 ///
1299 /// // or from int variants
1300 /// let v2 = Variant::from(123);
1301 /// assert_eq!(v2.as_decimal16(), VariantDecimal16::try_new(123_i128, 0).ok());
1302 ///
1303 /// // but not if the variant is not a decimal
1304 /// let v3 = Variant::from("hello");
1305 /// assert_eq!(v3.as_decimal16(), None);
1306 /// ```
1307 pub fn as_decimal16(&self) -> Option<VariantDecimal16> {
1308 match *self {
1309 Variant::Int8(i) => VariantDecimal16::try_new(i as i128, 0).ok(),
1310 Variant::Int16(i) => VariantDecimal16::try_new(i as i128, 0).ok(),
1311 Variant::Int32(i) => VariantDecimal16::try_new(i as i128, 0).ok(),
1312 Variant::Int64(i) => VariantDecimal16::try_new(i as i128, 0).ok(),
1313 Variant::Decimal4(decimal4) => Some(decimal4.into()),
1314 Variant::Decimal8(decimal8) => Some(decimal8.into()),
1315 Variant::Decimal16(decimal16) => Some(decimal16),
1316 _ => None,
1317 }
1318 }
1319
1320 /// Converts this variant to an `f32` if possible.
1321 ///
1322 /// Returns `Some(f32)` for float variants, `None` for other variants.
1323 ///
1324 /// # Examples
1325 ///
1326 /// ```
1327 /// use parquet_variant::Variant;
1328 ///
1329 /// // you can extract an f32 from a float variant
1330 /// let v1 = Variant::from(std::f32::consts::PI);
1331 /// assert_eq!(v1.as_f32(), Some(std::f32::consts::PI));
1332 ///
1333 /// // but not from double variant
1334 /// let v3 = Variant::from(3f64);
1335 /// assert_eq!(v3.as_f32(), None);
1336 ///
1337 /// // or other variants
1338 /// let v4 = Variant::from("hello");
1339 /// assert_eq!(v4.as_f32(), None);
1340 /// ```
1341 pub fn as_f32(&self) -> Option<f32> {
1342 match *self {
1343 Variant::Float(i) => Some(i),
1344 _ => None,
1345 }
1346 }
1347
1348 /// Converts this variant to an `f64`.
1349 ///
1350 /// Returns `Some(f64)` for double variants, `None` otherwise.
1351 ///
1352 /// # Examples
1353 ///
1354 /// ```
1355 /// use parquet_variant::Variant;
1356 ///
1357 /// // you can extract an f64 from a double variant
1358 /// let v1 = Variant::from(std::f64::consts::PI);
1359 /// assert_eq!(v1.as_f64(), Some(std::f64::consts::PI));
1360 ///
1361 /// // but not from a float variant
1362 /// let v2 = Variant::from(std::f32::consts::PI);
1363 /// assert_eq!(v2.as_f64(), None);
1364 ///
1365 /// // or from other variants
1366 /// let v3 = Variant::from("hello");
1367 /// assert_eq!(v3.as_f64(), None);
1368 /// ```
1369 pub fn as_f64(&self) -> Option<f64> {
1370 match *self {
1371 Variant::Double(i) => Some(i),
1372 _ => None,
1373 }
1374 }
1375
1376 /// Converts this variant to an `Object` if it is an [`VariantObject`].
1377 ///
1378 /// Returns `Some(&VariantObject)` for object variants,
1379 /// `None` for non-object variants.
1380 ///
1381 /// See [`Self::get_path`] to dynamically traverse objects
1382 ///
1383 /// # Examples
1384 /// ```
1385 /// # use parquet_variant::{Variant, VariantBuilder, VariantObject};
1386 /// # let (metadata, value) = {
1387 /// # let mut builder = VariantBuilder::new();
1388 /// # let mut obj = builder.new_object();
1389 /// # obj.insert("name", "John");
1390 /// # obj.finish();
1391 /// # builder.finish()
1392 /// # };
1393 /// // object that is {"name": "John"}
1394 /// let variant = Variant::new(&metadata, &value);
1395 /// // use the `as_object` method to access the object
1396 /// let obj = variant.as_object().expect("variant should be an object");
1397 /// assert_eq!(obj.get("name"), Some(Variant::from("John")));
1398 /// ```
1399 pub fn as_object(&'m self) -> Option<&'m VariantObject<'m, 'v>> {
1400 if let Variant::Object(obj) = self {
1401 Some(obj)
1402 } else {
1403 None
1404 }
1405 }
1406
1407 /// If this is an object and the requested field name exists, retrieves the corresponding field
1408 /// value. Otherwise, returns None.
1409 ///
1410 /// This is shorthand for [`Self::as_object`] followed by [`VariantObject::get`].
1411 ///
1412 /// # Examples
1413 /// ```
1414 /// # use parquet_variant::{Variant, VariantBuilder, VariantObject};
1415 /// # let mut builder = VariantBuilder::new();
1416 /// # let mut obj = builder.new_object();
1417 /// # obj.insert("name", "John");
1418 /// # obj.finish();
1419 /// # let (metadata, value) = builder.finish();
1420 /// // object that is {"name": "John"}
1421 /// let variant = Variant::new(&metadata, &value);
1422 /// // use the `get_object_field` method to access the object
1423 /// let obj = variant.get_object_field("name");
1424 /// assert_eq!(obj, Some(Variant::from("John")));
1425 /// let obj = variant.get_object_field("foo");
1426 /// assert!(obj.is_none());
1427 /// ```
1428 pub fn get_object_field(&self, field_name: &str) -> Option<Self> {
1429 match self {
1430 Variant::Object(object) => object.get(field_name),
1431 _ => None,
1432 }
1433 }
1434
1435 /// Converts this variant to a `List` if it is a [`VariantList`].
1436 ///
1437 /// Returns `Some(&VariantList)` for list variants,
1438 /// `None` for non-list variants.
1439 ///
1440 /// See [`Self::get_path`] to dynamically traverse lists
1441 ///
1442 /// # Examples
1443 /// ```
1444 /// # use parquet_variant::{Variant, VariantBuilder, VariantList};
1445 /// # let (metadata, value) = {
1446 /// # let mut builder = VariantBuilder::new();
1447 /// # let mut list = builder.new_list();
1448 /// # list.append_value("John");
1449 /// # list.append_value("Doe");
1450 /// # list.finish();
1451 /// # builder.finish()
1452 /// # };
1453 /// // list that is ["John", "Doe"]
1454 /// let variant = Variant::new(&metadata, &value);
1455 /// // use the `as_list` method to access the list
1456 /// let list = variant.as_list().expect("variant should be a list");
1457 /// assert_eq!(list.len(), 2);
1458 /// assert_eq!(list.get(0).unwrap(), Variant::from("John"));
1459 /// assert_eq!(list.get(1).unwrap(), Variant::from("Doe"));
1460 /// ```
1461 pub fn as_list(&'m self) -> Option<&'m VariantList<'m, 'v>> {
1462 if let Variant::List(list) = self {
1463 Some(list)
1464 } else {
1465 None
1466 }
1467 }
1468
1469 /// Converts this variant to a `NaiveTime` if possible.
1470 ///
1471 /// Returns `Some(NaiveTime)` for `Variant::Time`,
1472 /// `None` for non-Time variants.
1473 ///
1474 /// # Example
1475 ///
1476 /// ```
1477 /// use chrono::NaiveTime;
1478 /// use parquet_variant::Variant;
1479 ///
1480 /// // you can extract a `NaiveTime` from a `Variant::Time`
1481 /// let time = NaiveTime::from_hms_micro_opt(1, 2, 3, 4).unwrap();
1482 /// let v1 = Variant::from(time);
1483 /// assert_eq!(Some(time), v1.as_time_utc());
1484 ///
1485 /// // but not from other variants.
1486 /// let v2 = Variant::from("Hello");
1487 /// assert_eq!(None, v2.as_time_utc());
1488 /// ```
1489 pub fn as_time_utc(&'m self) -> Option<NaiveTime> {
1490 if let Variant::Time(time) = self {
1491 Some(*time)
1492 } else {
1493 None
1494 }
1495 }
1496
1497 /// If this is a list and the requested index is in bounds, retrieves the corresponding
1498 /// element. Otherwise, returns None.
1499 ///
1500 /// This is shorthand for [`Self::as_list`] followed by [`VariantList::get`].
1501 ///
1502 /// # Examples
1503 /// ```
1504 /// # use parquet_variant::{Variant, VariantBuilder, VariantList};
1505 /// # let mut builder = VariantBuilder::new();
1506 /// # let mut list = builder.new_list();
1507 /// # list.append_value("John");
1508 /// # list.append_value("Doe");
1509 /// # list.finish();
1510 /// # let (metadata, value) = builder.finish();
1511 /// // list that is ["John", "Doe"]
1512 /// let variant = Variant::new(&metadata, &value);
1513 /// // use the `get_list_element` method to access the list
1514 /// assert_eq!(variant.get_list_element(0), Some(Variant::from("John")));
1515 /// assert_eq!(variant.get_list_element(1), Some(Variant::from("Doe")));
1516 /// assert!(variant.get_list_element(2).is_none());
1517 /// ```
1518 pub fn get_list_element(&self, index: usize) -> Option<Self> {
1519 match self {
1520 Variant::List(list) => list.get(index),
1521 _ => None,
1522 }
1523 }
1524
1525 /// Return the metadata dictionary associated with this variant value.
1526 pub fn metadata(&self) -> &VariantMetadata<'m> {
1527 match self {
1528 Variant::Object(VariantObject { metadata, .. })
1529 | Variant::List(VariantList { metadata, .. }) => metadata,
1530 _ => &EMPTY_VARIANT_METADATA,
1531 }
1532 }
1533
1534 /// Return a new Variant with the path followed.
1535 ///
1536 /// If the path is not found, `None` is returned.
1537 ///
1538 /// # Example
1539 /// ```
1540 /// # use parquet_variant::{Variant, VariantBuilder, VariantObject, VariantPath};
1541 /// # let mut builder = VariantBuilder::new();
1542 /// # let mut obj = builder.new_object();
1543 /// # let mut list = obj.new_list("foo");
1544 /// # list.append_value("bar");
1545 /// # list.append_value("baz");
1546 /// # list.finish();
1547 /// # obj.finish();
1548 /// # let (metadata, value) = builder.finish();
1549 /// // given a variant like `{"foo": ["bar", "baz"]}`
1550 /// let variant = Variant::new(&metadata, &value);
1551 /// // Accessing a non existent path returns None
1552 /// assert_eq!(variant.get_path(&VariantPath::try_from("non_existent").unwrap()), None);
1553 /// // Access obj["foo"]
1554 /// let path = VariantPath::try_from("foo").unwrap();
1555 /// let foo = variant.get_path(&path).expect("field `foo` should exist");
1556 /// assert!(foo.as_list().is_some(), "field `foo` should be a list");
1557 /// // Access foo[0]
1558 /// let path = VariantPath::from(0);
1559 /// let bar = foo.get_path(&path).expect("element 0 should exist");
1560 /// // bar is a string
1561 /// assert_eq!(bar.as_string(), Some("bar"));
1562 /// // You can also access nested paths
1563 /// let path = VariantPath::try_from("foo").unwrap().join(0);
1564 /// assert_eq!(variant.get_path(&path).unwrap(), bar);
1565 /// ```
1566 pub fn get_path(&self, path: &VariantPath) -> Option<Variant<'_, '_>> {
1567 path.iter()
1568 .try_fold(self.clone(), |output, element| match element {
1569 VariantPathElement::Field { name } => output.get_object_field(name),
1570 VariantPathElement::Index { index } => output.get_list_element(*index),
1571 })
1572 }
1573}
1574
1575impl From<()> for Variant<'_, '_> {
1576 fn from((): ()) -> Self {
1577 Variant::Null
1578 }
1579}
1580
1581impl From<bool> for Variant<'_, '_> {
1582 fn from(value: bool) -> Self {
1583 match value {
1584 true => Variant::BooleanTrue,
1585 false => Variant::BooleanFalse,
1586 }
1587 }
1588}
1589
1590impl From<i8> for Variant<'_, '_> {
1591 fn from(value: i8) -> Self {
1592 Variant::Int8(value)
1593 }
1594}
1595
1596impl From<i16> for Variant<'_, '_> {
1597 fn from(value: i16) -> Self {
1598 Variant::Int16(value)
1599 }
1600}
1601
1602impl From<i32> for Variant<'_, '_> {
1603 fn from(value: i32) -> Self {
1604 Variant::Int32(value)
1605 }
1606}
1607
1608impl From<i64> for Variant<'_, '_> {
1609 fn from(value: i64) -> Self {
1610 Variant::Int64(value)
1611 }
1612}
1613
1614impl From<u8> for Variant<'_, '_> {
1615 fn from(value: u8) -> Self {
1616 // if it fits in i8, use that, otherwise use i16
1617 if let Ok(value) = i8::try_from(value) {
1618 Variant::Int8(value)
1619 } else {
1620 Variant::Int16(i16::from(value))
1621 }
1622 }
1623}
1624
1625impl From<u16> for Variant<'_, '_> {
1626 fn from(value: u16) -> Self {
1627 // if it fits in i16, use that, otherwise use i32
1628 if let Ok(value) = i16::try_from(value) {
1629 Variant::Int16(value)
1630 } else {
1631 Variant::Int32(i32::from(value))
1632 }
1633 }
1634}
1635impl From<u32> for Variant<'_, '_> {
1636 fn from(value: u32) -> Self {
1637 // if it fits in i32, use that, otherwise use i64
1638 if let Ok(value) = i32::try_from(value) {
1639 Variant::Int32(value)
1640 } else {
1641 Variant::Int64(i64::from(value))
1642 }
1643 }
1644}
1645
1646impl From<u64> for Variant<'_, '_> {
1647 fn from(value: u64) -> Self {
1648 // if it fits in i64, use that, otherwise use Decimal16
1649 if let Ok(value) = i64::try_from(value) {
1650 Variant::Int64(value)
1651 } else {
1652 // u64 max is 18446744073709551615, which fits in i128
1653 Variant::Decimal16(VariantDecimal16::try_new(i128::from(value), 0).unwrap())
1654 }
1655 }
1656}
1657
1658impl From<VariantDecimal4> for Variant<'_, '_> {
1659 fn from(value: VariantDecimal4) -> Self {
1660 Variant::Decimal4(value)
1661 }
1662}
1663
1664impl From<VariantDecimal8> for Variant<'_, '_> {
1665 fn from(value: VariantDecimal8) -> Self {
1666 Variant::Decimal8(value)
1667 }
1668}
1669
1670impl From<VariantDecimal16> for Variant<'_, '_> {
1671 fn from(value: VariantDecimal16) -> Self {
1672 Variant::Decimal16(value)
1673 }
1674}
1675
1676impl From<half::f16> for Variant<'_, '_> {
1677 fn from(value: half::f16) -> Self {
1678 Variant::Float(value.into())
1679 }
1680}
1681
1682impl From<f32> for Variant<'_, '_> {
1683 fn from(value: f32) -> Self {
1684 Variant::Float(value)
1685 }
1686}
1687
1688impl From<f64> for Variant<'_, '_> {
1689 fn from(value: f64) -> Self {
1690 Variant::Double(value)
1691 }
1692}
1693
1694impl From<NaiveDate> for Variant<'_, '_> {
1695 fn from(value: NaiveDate) -> Self {
1696 Variant::Date(value)
1697 }
1698}
1699
1700impl From<DateTime<Utc>> for Variant<'_, '_> {
1701 fn from(value: DateTime<Utc>) -> Self {
1702 if !value.nanosecond().is_multiple_of(1000) {
1703 Variant::TimestampNanos(value)
1704 } else {
1705 Variant::TimestampMicros(value)
1706 }
1707 }
1708}
1709
1710impl From<NaiveDateTime> for Variant<'_, '_> {
1711 fn from(value: NaiveDateTime) -> Self {
1712 if !value.nanosecond().is_multiple_of(1000) {
1713 Variant::TimestampNtzNanos(value)
1714 } else {
1715 Variant::TimestampNtzMicros(value)
1716 }
1717 }
1718}
1719
1720impl<'v> From<&'v [u8]> for Variant<'_, 'v> {
1721 fn from(value: &'v [u8]) -> Self {
1722 Variant::Binary(value)
1723 }
1724}
1725
1726impl From<NaiveTime> for Variant<'_, '_> {
1727 fn from(value: NaiveTime) -> Self {
1728 Variant::Time(value)
1729 }
1730}
1731
1732impl From<Uuid> for Variant<'_, '_> {
1733 fn from(value: Uuid) -> Self {
1734 Variant::Uuid(value)
1735 }
1736}
1737
1738impl<'v> From<&'v str> for Variant<'_, 'v> {
1739 fn from(value: &'v str) -> Self {
1740 if value.len() > MAX_SHORT_STRING_BYTES {
1741 Variant::String(value)
1742 } else {
1743 Variant::ShortString(ShortString(value))
1744 }
1745 }
1746}
1747
1748impl TryFrom<(i32, u8)> for Variant<'_, '_> {
1749 type Error = ArrowError;
1750
1751 fn try_from(value: (i32, u8)) -> Result<Self, Self::Error> {
1752 Ok(Variant::Decimal4(VariantDecimal4::try_new(
1753 value.0, value.1,
1754 )?))
1755 }
1756}
1757
1758impl TryFrom<(i64, u8)> for Variant<'_, '_> {
1759 type Error = ArrowError;
1760
1761 fn try_from(value: (i64, u8)) -> Result<Self, Self::Error> {
1762 Ok(Variant::Decimal8(VariantDecimal8::try_new(
1763 value.0, value.1,
1764 )?))
1765 }
1766}
1767
1768impl TryFrom<(i128, u8)> for Variant<'_, '_> {
1769 type Error = ArrowError;
1770
1771 fn try_from(value: (i128, u8)) -> Result<Self, Self::Error> {
1772 Ok(Variant::Decimal16(VariantDecimal16::try_new(
1773 value.0, value.1,
1774 )?))
1775 }
1776}
1777
1778// helper to print <invalid> instead of "<invalid>" in debug mode when a VariantObject or VariantList contains invalid values.
1779struct InvalidVariant;
1780
1781impl std::fmt::Debug for InvalidVariant {
1782 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1783 write!(f, "<invalid>")
1784 }
1785}
1786
1787// helper to print binary data in hex format in debug mode, as space-separated hex byte values.
1788struct HexString<'a>(&'a [u8]);
1789
1790impl std::fmt::Debug for HexString<'_> {
1791 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1792 if let Some((first, rest)) = self.0.split_first() {
1793 write!(f, "{first:02x}")?;
1794 for b in rest {
1795 write!(f, " {b:02x}")?;
1796 }
1797 }
1798 Ok(())
1799 }
1800}
1801
1802impl std::fmt::Debug for Variant<'_, '_> {
1803 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1804 match self {
1805 Variant::Null => write!(f, "Null"),
1806 Variant::BooleanTrue => write!(f, "BooleanTrue"),
1807 Variant::BooleanFalse => write!(f, "BooleanFalse"),
1808 Variant::Int8(v) => f.debug_tuple("Int8").field(v).finish(),
1809 Variant::Int16(v) => f.debug_tuple("Int16").field(v).finish(),
1810 Variant::Int32(v) => f.debug_tuple("Int32").field(v).finish(),
1811 Variant::Int64(v) => f.debug_tuple("Int64").field(v).finish(),
1812 Variant::Float(v) => f.debug_tuple("Float").field(v).finish(),
1813 Variant::Double(v) => f.debug_tuple("Double").field(v).finish(),
1814 Variant::Decimal4(d) => f.debug_tuple("Decimal4").field(d).finish(),
1815 Variant::Decimal8(d) => f.debug_tuple("Decimal8").field(d).finish(),
1816 Variant::Decimal16(d) => f.debug_tuple("Decimal16").field(d).finish(),
1817 Variant::Date(d) => f.debug_tuple("Date").field(d).finish(),
1818 Variant::TimestampMicros(ts) => f.debug_tuple("TimestampMicros").field(ts).finish(),
1819 Variant::TimestampNtzMicros(ts) => {
1820 f.debug_tuple("TimestampNtzMicros").field(ts).finish()
1821 }
1822 Variant::TimestampNanos(ts) => f.debug_tuple("TimestampNanos").field(ts).finish(),
1823 Variant::TimestampNtzNanos(ts) => f.debug_tuple("TimestampNtzNanos").field(ts).finish(),
1824 Variant::Binary(bytes) => write!(f, "Binary({:?})", HexString(bytes)),
1825 Variant::String(s) => f.debug_tuple("String").field(s).finish(),
1826 Variant::Time(s) => f.debug_tuple("Time").field(s).finish(),
1827 Variant::ShortString(s) => f.debug_tuple("ShortString").field(s).finish(),
1828 Variant::Uuid(uuid) => f.debug_tuple("Uuid").field(&uuid).finish(),
1829 Variant::Object(obj) => {
1830 let mut map = f.debug_map();
1831 for res in obj.iter_try() {
1832 match res {
1833 Ok((k, v)) => map.entry(&k, &v),
1834 Err(_) => map.entry(&InvalidVariant, &InvalidVariant),
1835 };
1836 }
1837 map.finish()
1838 }
1839 Variant::List(arr) => {
1840 let mut list = f.debug_list();
1841 for res in arr.iter_try() {
1842 match res {
1843 Ok(v) => list.entry(&v),
1844 Err(_) => list.entry(&InvalidVariant),
1845 };
1846 }
1847 list.finish()
1848 }
1849 }
1850 }
1851}
1852
1853#[cfg(test)]
1854mod tests {
1855
1856 use super::*;
1857
1858 #[test]
1859 fn test_empty_variant_will_fail() {
1860 let metadata = VariantMetadata::try_new(&[1, 0, 0]).unwrap();
1861
1862 let err = Variant::try_new_with_metadata(metadata, &[]).unwrap_err();
1863
1864 assert!(matches!(
1865 err,
1866 ArrowError::InvalidArgumentError(ref msg) if msg == "Received empty bytes"));
1867 }
1868
1869 #[test]
1870 fn test_construct_short_string() {
1871 let short_string = ShortString::try_new("norm").expect("should fit in short string");
1872 assert_eq!(short_string.as_str(), "norm");
1873
1874 let long_string = "a".repeat(MAX_SHORT_STRING_BYTES + 1);
1875 let res = ShortString::try_new(&long_string);
1876 assert!(res.is_err());
1877 }
1878
1879 #[test]
1880 fn test_variant_all_subtypes_debug() {
1881 use crate::VariantBuilder;
1882
1883 let mut builder = VariantBuilder::new();
1884
1885 // Create a root object that contains one of every variant subtype
1886 let mut root_obj = builder.new_object();
1887
1888 // Add primitive types
1889 root_obj.insert("null", ());
1890 root_obj.insert("boolean_true", true);
1891 root_obj.insert("boolean_false", false);
1892 root_obj.insert("int8", 42i8);
1893 root_obj.insert("int16", 1234i16);
1894 root_obj.insert("int32", 123456i32);
1895 root_obj.insert("int64", 1234567890123456789i64);
1896 root_obj.insert("float", 1.234f32);
1897 root_obj.insert("double", 1.23456789f64);
1898
1899 // Add date and timestamp types
1900 let date = chrono::NaiveDate::from_ymd_opt(2024, 12, 25).unwrap();
1901 root_obj.insert("date", date);
1902
1903 let timestamp_utc = chrono::NaiveDate::from_ymd_opt(2024, 12, 25)
1904 .unwrap()
1905 .and_hms_milli_opt(15, 30, 45, 123)
1906 .unwrap()
1907 .and_utc();
1908 root_obj.insert("timestamp_micros", Variant::TimestampMicros(timestamp_utc));
1909
1910 let timestamp_ntz = chrono::NaiveDate::from_ymd_opt(2024, 12, 25)
1911 .unwrap()
1912 .and_hms_milli_opt(15, 30, 45, 123)
1913 .unwrap();
1914 root_obj.insert(
1915 "timestamp_ntz_micros",
1916 Variant::TimestampNtzMicros(timestamp_ntz),
1917 );
1918
1919 let timestamp_nanos_utc = chrono::NaiveDate::from_ymd_opt(2025, 8, 15)
1920 .unwrap()
1921 .and_hms_nano_opt(12, 3, 4, 123456789)
1922 .unwrap()
1923 .and_utc();
1924 root_obj.insert(
1925 "timestamp_nanos",
1926 Variant::TimestampNanos(timestamp_nanos_utc),
1927 );
1928
1929 let timestamp_ntz_nanos = chrono::NaiveDate::from_ymd_opt(2025, 8, 15)
1930 .unwrap()
1931 .and_hms_nano_opt(12, 3, 4, 123456789)
1932 .unwrap();
1933 root_obj.insert(
1934 "timestamp_ntz_nanos",
1935 Variant::TimestampNtzNanos(timestamp_ntz_nanos),
1936 );
1937
1938 // Add decimal types
1939 let decimal4 = VariantDecimal4::try_new(1234i32, 2).unwrap();
1940 root_obj.insert("decimal4", decimal4);
1941
1942 let decimal8 = VariantDecimal8::try_new(123456789i64, 3).unwrap();
1943 root_obj.insert("decimal8", decimal8);
1944
1945 let decimal16 = VariantDecimal16::try_new(123456789012345678901234567890i128, 4).unwrap();
1946 root_obj.insert("decimal16", decimal16);
1947
1948 // Add binary and string types
1949 let binary_data = b"\x01\x02\x03\x04\xde\xad\xbe\xef";
1950 root_obj.insert("binary", binary_data.as_slice());
1951
1952 let long_string =
1953 "This is a long string that exceeds the short string limit and contains emoji 🦀";
1954 root_obj.insert("string", long_string);
1955 root_obj.insert("short_string", "Short string with emoji 🎉");
1956 let time = NaiveTime::from_hms_micro_opt(1, 2, 3, 4).unwrap();
1957 root_obj.insert("time", time);
1958
1959 // Add uuid
1960 let uuid = Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").unwrap();
1961 root_obj.insert("uuid", Variant::Uuid(uuid));
1962
1963 // Add nested object
1964 let mut nested_obj = root_obj.new_object("nested_object");
1965 nested_obj.insert("inner_key1", "inner_value1");
1966 nested_obj.insert("inner_key2", 999i32);
1967 nested_obj.finish();
1968
1969 // Add list with mixed types
1970 let mut mixed_list = root_obj.new_list("mixed_list");
1971 mixed_list.append_value(1i32);
1972 mixed_list.append_value("two");
1973 mixed_list.append_value(true);
1974 mixed_list.append_value(4.0f32);
1975 mixed_list.append_value(());
1976
1977 // Add nested list inside the mixed list
1978 let mut nested_list = mixed_list.new_list();
1979 nested_list.append_value("nested");
1980 nested_list.append_value(10i8);
1981 nested_list.finish();
1982
1983 mixed_list.finish();
1984
1985 root_obj.finish();
1986
1987 let (metadata, value) = builder.finish();
1988 let variant = Variant::try_new(&metadata, &value).unwrap();
1989
1990 // Test Debug formatter (?)
1991 let debug_output = format!("{variant:?}");
1992
1993 // Verify that the debug output contains all the expected types
1994 assert!(debug_output.contains("\"null\": Null"));
1995 assert!(debug_output.contains("\"boolean_true\": BooleanTrue"));
1996 assert!(debug_output.contains("\"boolean_false\": BooleanFalse"));
1997 assert!(debug_output.contains("\"int8\": Int8(42)"));
1998 assert!(debug_output.contains("\"int16\": Int16(1234)"));
1999 assert!(debug_output.contains("\"int32\": Int32(123456)"));
2000 assert!(debug_output.contains("\"int64\": Int64(1234567890123456789)"));
2001 assert!(debug_output.contains("\"float\": Float(1.234)"));
2002 assert!(debug_output.contains("\"double\": Double(1.23456789"));
2003 assert!(debug_output.contains("\"date\": Date(2024-12-25)"));
2004 assert!(debug_output.contains("\"timestamp_micros\": TimestampMicros("));
2005 assert!(debug_output.contains("\"timestamp_ntz_micros\": TimestampNtzMicros("));
2006 assert!(debug_output.contains("\"timestamp_nanos\": TimestampNanos("));
2007 assert!(debug_output.contains("\"timestamp_ntz_nanos\": TimestampNtzNanos("));
2008 assert!(debug_output.contains("\"decimal4\": Decimal4("));
2009 assert!(debug_output.contains("\"decimal8\": Decimal8("));
2010 assert!(debug_output.contains("\"decimal16\": Decimal16("));
2011 assert!(debug_output.contains("\"binary\": Binary(01 02 03 04 de ad be ef)"));
2012 assert!(debug_output.contains("\"string\": String("));
2013 assert!(debug_output.contains("\"short_string\": ShortString("));
2014 assert!(debug_output.contains("\"uuid\": Uuid(67e55044-10b1-426f-9247-bb680e5fe0c8)"));
2015 assert!(debug_output.contains("\"time\": Time(01:02:03.000004)"));
2016 assert!(debug_output.contains("\"nested_object\":"));
2017 assert!(debug_output.contains("\"mixed_list\":"));
2018
2019 let expected = r#"{"binary": Binary(01 02 03 04 de ad be ef), "boolean_false": BooleanFalse, "boolean_true": BooleanTrue, "date": Date(2024-12-25), "decimal16": Decimal16(VariantDecimal16 { integer: 123456789012345678901234567890, scale: 4 }), "decimal4": Decimal4(VariantDecimal4 { integer: 1234, scale: 2 }), "decimal8": Decimal8(VariantDecimal8 { integer: 123456789, scale: 3 }), "double": Double(1.23456789), "float": Float(1.234), "int16": Int16(1234), "int32": Int32(123456), "int64": Int64(1234567890123456789), "int8": Int8(42), "mixed_list": [Int32(1), ShortString(ShortString("two")), BooleanTrue, Float(4.0), Null, [ShortString(ShortString("nested")), Int8(10)]], "nested_object": {"inner_key1": ShortString(ShortString("inner_value1")), "inner_key2": Int32(999)}, "null": Null, "short_string": ShortString(ShortString("Short string with emoji 🎉")), "string": String("This is a long string that exceeds the short string limit and contains emoji 🦀"), "time": Time(01:02:03.000004), "timestamp_micros": TimestampMicros(2024-12-25T15:30:45.123Z), "timestamp_nanos": TimestampNanos(2025-08-15T12:03:04.123456789Z), "timestamp_ntz_micros": TimestampNtzMicros(2024-12-25T15:30:45.123), "timestamp_ntz_nanos": TimestampNtzNanos(2025-08-15T12:03:04.123456789), "uuid": Uuid(67e55044-10b1-426f-9247-bb680e5fe0c8)}"#;
2020 assert_eq!(debug_output, expected);
2021
2022 // Test alternate Debug formatter (#?)
2023 let alt_debug_output = format!("{variant:#?}");
2024 let expected = r#"{
2025 "binary": Binary(01 02 03 04 de ad be ef),
2026 "boolean_false": BooleanFalse,
2027 "boolean_true": BooleanTrue,
2028 "date": Date(
2029 2024-12-25,
2030 ),
2031 "decimal16": Decimal16(
2032 VariantDecimal16 {
2033 integer: 123456789012345678901234567890,
2034 scale: 4,
2035 },
2036 ),
2037 "decimal4": Decimal4(
2038 VariantDecimal4 {
2039 integer: 1234,
2040 scale: 2,
2041 },
2042 ),
2043 "decimal8": Decimal8(
2044 VariantDecimal8 {
2045 integer: 123456789,
2046 scale: 3,
2047 },
2048 ),
2049 "double": Double(
2050 1.23456789,
2051 ),
2052 "float": Float(
2053 1.234,
2054 ),
2055 "int16": Int16(
2056 1234,
2057 ),
2058 "int32": Int32(
2059 123456,
2060 ),
2061 "int64": Int64(
2062 1234567890123456789,
2063 ),
2064 "int8": Int8(
2065 42,
2066 ),
2067 "mixed_list": [
2068 Int32(
2069 1,
2070 ),
2071 ShortString(
2072 ShortString(
2073 "two",
2074 ),
2075 ),
2076 BooleanTrue,
2077 Float(
2078 4.0,
2079 ),
2080 Null,
2081 [
2082 ShortString(
2083 ShortString(
2084 "nested",
2085 ),
2086 ),
2087 Int8(
2088 10,
2089 ),
2090 ],
2091 ],
2092 "nested_object": {
2093 "inner_key1": ShortString(
2094 ShortString(
2095 "inner_value1",
2096 ),
2097 ),
2098 "inner_key2": Int32(
2099 999,
2100 ),
2101 },
2102 "null": Null,
2103 "short_string": ShortString(
2104 ShortString(
2105 "Short string with emoji 🎉",
2106 ),
2107 ),
2108 "string": String(
2109 "This is a long string that exceeds the short string limit and contains emoji 🦀",
2110 ),
2111 "time": Time(
2112 01:02:03.000004,
2113 ),
2114 "timestamp_micros": TimestampMicros(
2115 2024-12-25T15:30:45.123Z,
2116 ),
2117 "timestamp_nanos": TimestampNanos(
2118 2025-08-15T12:03:04.123456789Z,
2119 ),
2120 "timestamp_ntz_micros": TimestampNtzMicros(
2121 2024-12-25T15:30:45.123,
2122 ),
2123 "timestamp_ntz_nanos": TimestampNtzNanos(
2124 2025-08-15T12:03:04.123456789,
2125 ),
2126 "uuid": Uuid(
2127 67e55044-10b1-426f-9247-bb680e5fe0c8,
2128 ),
2129}"#;
2130 assert_eq!(alt_debug_output, expected);
2131 }
2132}