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