1use crate::variant_array::{binary_array_value, validate_binary_array};
21use crate::{VariantArray, VariantValueArrayBuilder};
22use arrow::array::{
23 Array, ArrayRef, AsArray as _, BinaryArray, BinaryViewArray, BooleanArray,
24 FixedSizeBinaryArray, FixedSizeListArray, GenericListArray, GenericListViewArray,
25 LargeBinaryArray, LargeStringArray, ListLikeArray, PrimitiveArray, StringArray,
26 StringViewArray, StructArray,
27};
28use arrow::buffer::NullBuffer;
29use arrow::datatypes::{
30 ArrowPrimitiveType, DataType, Date32Type, Decimal32Type, Decimal64Type, Decimal128Type,
31 DecimalType, Float32Type, Float64Type, Int8Type, Int16Type, Int32Type, Int64Type,
32 Time64MicrosecondType, TimeUnit, TimestampMicrosecondType, TimestampNanosecondType,
33};
34use arrow::error::{ArrowError, Result};
35use arrow::temporal_conversions::time64us_to_time;
36use chrono::{DateTime, Utc};
37use indexmap::IndexMap;
38use parquet_variant::{
39 ListBuilder, ObjectBuilder, ObjectFieldBuilder, Variant, VariantBuilderExt, VariantDecimal4,
40 VariantDecimal8, VariantDecimal16, VariantDecimalType, VariantMetadata,
41};
42use std::marker::PhantomData;
43use std::sync::Arc;
44use uuid::Uuid;
45
46pub fn unshred_variant(array: &VariantArray) -> Result<VariantArray> {
62 let nulls = array.nulls();
63 let metadata = array.metadata_column();
64 let value_col = array.value_column();
65 let typed_value_col = array.typed_value_column();
66
67 if typed_value_col.is_none() {
72 if value_field_is_non_nullable(array) {
73 return Ok(array.clone());
74 }
75 if value_nulls_are_masked(value_col, nulls) {
76 return Ok(VariantArray::from_parts_unshredded(
77 metadata.clone(),
78 value_col.clone(),
79 nulls.cloned(),
80 ));
81 }
82 }
83
84 let mut row_builder = UnshredVariantRowBuilder::try_new_opt(array.inner())?
87 .unwrap_or_else(UnshredVariantRowBuilder::null);
88
89 let mut value_builder = VariantValueArrayBuilder::new(array.len());
90 for i in 0..array.len() {
91 if array.is_null(i) {
92 value_builder.append_null();
93 } else {
94 let metadata_bytes = binary_array_value(metadata.as_ref(), i).ok_or_else(|| {
95 ArrowError::InvalidArgumentError(
96 "metadata field must be a binary-like array".to_string(),
97 )
98 })?;
99 let metadata = VariantMetadata::try_new(metadata_bytes)?;
100 let mut row_sink = TopLevelRowSink(value_builder.builder_ext(&metadata));
101 row_builder.append_row(&mut row_sink, &metadata, i)?;
102 }
103 }
104
105 let value = value_builder.build()?;
106 Ok(VariantArray::from_parts_unshredded(
107 metadata.clone(),
108 Arc::new(value),
109 nulls.cloned(),
110 ))
111}
112
113fn value_field_is_non_nullable(array: &VariantArray) -> bool {
114 array
115 .inner()
116 .field_by_name("value")
117 .is_some_and(|field| !field.is_nullable())
118}
119
120fn value_nulls_are_masked(value: &ArrayRef, parent_nulls: Option<&NullBuffer>) -> bool {
123 value.null_count() == 0
124 || parent_nulls
125 .zip(value.nulls())
126 .is_some_and(|(parent, value_nulls)| parent.contains(value_nulls))
127}
128
129struct TopLevelRowSink<B>(B);
137
138impl<B: VariantBuilderExt> VariantBuilderExt for TopLevelRowSink<B> {
139 type State<'a>
140 = B::State<'a>
141 where
142 Self: 'a;
143
144 fn append_null(&mut self) {
145 self.0.append_value(Variant::Null);
146 }
147
148 fn append_value<'m, 'v>(&mut self, value: impl Into<Variant<'m, 'v>>) {
149 self.0.append_value(value);
150 }
151
152 fn try_new_list(&mut self) -> Result<ListBuilder<'_, Self::State<'_>>> {
153 self.0.try_new_list()
154 }
155
156 fn try_new_object(&mut self) -> Result<ObjectBuilder<'_, Self::State<'_>>> {
157 self.0.try_new_object()
158 }
159}
160
161enum UnshredVariantRowBuilder<'a> {
163 PrimitiveInt8(UnshredPrimitiveRowBuilder<'a, PrimitiveArray<Int8Type>>),
164 PrimitiveInt16(UnshredPrimitiveRowBuilder<'a, PrimitiveArray<Int16Type>>),
165 PrimitiveInt32(UnshredPrimitiveRowBuilder<'a, PrimitiveArray<Int32Type>>),
166 PrimitiveInt64(UnshredPrimitiveRowBuilder<'a, PrimitiveArray<Int64Type>>),
167 PrimitiveFloat32(UnshredPrimitiveRowBuilder<'a, PrimitiveArray<Float32Type>>),
168 PrimitiveFloat64(UnshredPrimitiveRowBuilder<'a, PrimitiveArray<Float64Type>>),
169 Decimal32(DecimalUnshredRowBuilder<'a, Decimal32Type, VariantDecimal4>),
170 Decimal64(DecimalUnshredRowBuilder<'a, Decimal64Type, VariantDecimal8>),
171 Decimal128(DecimalUnshredRowBuilder<'a, Decimal128Type, VariantDecimal16>),
172 PrimitiveDate32(UnshredPrimitiveRowBuilder<'a, PrimitiveArray<Date32Type>>),
173 PrimitiveTime64(UnshredPrimitiveRowBuilder<'a, PrimitiveArray<Time64MicrosecondType>>),
174 TimestampMicrosecond(TimestampUnshredRowBuilder<'a, TimestampMicrosecondType>),
175 TimestampNanosecond(TimestampUnshredRowBuilder<'a, TimestampNanosecondType>),
176 PrimitiveBoolean(UnshredPrimitiveRowBuilder<'a, BooleanArray>),
177 PrimitiveString(UnshredPrimitiveRowBuilder<'a, StringArray>),
178 PrimitiveStringView(UnshredPrimitiveRowBuilder<'a, StringViewArray>),
179 PrimitiveLargeString(UnshredPrimitiveRowBuilder<'a, LargeStringArray>),
180 PrimitiveBinary(UnshredPrimitiveRowBuilder<'a, BinaryArray>),
181 PrimitiveBinaryView(UnshredPrimitiveRowBuilder<'a, BinaryViewArray>),
182 PrimitiveLargeBinary(UnshredPrimitiveRowBuilder<'a, LargeBinaryArray>),
183 PrimitiveUuid(UnshredPrimitiveRowBuilder<'a, FixedSizeBinaryArray>),
184 List(ListUnshredVariantBuilder<'a, GenericListArray<i32>>),
185 LargeList(ListUnshredVariantBuilder<'a, GenericListArray<i64>>),
186 ListView(ListUnshredVariantBuilder<'a, GenericListViewArray<i32>>),
187 LargeListView(ListUnshredVariantBuilder<'a, GenericListViewArray<i64>>),
188 FixedSizeList(ListUnshredVariantBuilder<'a, FixedSizeListArray>),
189 Struct(StructUnshredVariantBuilder<'a>),
190 ValueOnly(ValueOnlyUnshredVariantBuilder<'a>),
191 Null(NullUnshredVariantBuilder),
192}
193
194impl<'a> UnshredVariantRowBuilder<'a> {
195 fn null() -> Self {
197 Self::Null(NullUnshredVariantBuilder)
198 }
199
200 fn append_row(
202 &mut self,
203 builder: &mut impl VariantBuilderExt,
204 metadata: &VariantMetadata,
205 index: usize,
206 ) -> Result<()> {
207 match self {
208 Self::PrimitiveInt8(b) => b.append_row(builder, metadata, index),
209 Self::PrimitiveInt16(b) => b.append_row(builder, metadata, index),
210 Self::PrimitiveInt32(b) => b.append_row(builder, metadata, index),
211 Self::PrimitiveInt64(b) => b.append_row(builder, metadata, index),
212 Self::PrimitiveFloat32(b) => b.append_row(builder, metadata, index),
213 Self::PrimitiveFloat64(b) => b.append_row(builder, metadata, index),
214 Self::Decimal32(b) => b.append_row(builder, metadata, index),
215 Self::Decimal64(b) => b.append_row(builder, metadata, index),
216 Self::Decimal128(b) => b.append_row(builder, metadata, index),
217 Self::PrimitiveDate32(b) => b.append_row(builder, metadata, index),
218 Self::PrimitiveTime64(b) => b.append_row(builder, metadata, index),
219 Self::TimestampMicrosecond(b) => b.append_row(builder, metadata, index),
220 Self::TimestampNanosecond(b) => b.append_row(builder, metadata, index),
221 Self::PrimitiveBoolean(b) => b.append_row(builder, metadata, index),
222 Self::PrimitiveString(b) => b.append_row(builder, metadata, index),
223 Self::PrimitiveStringView(b) => b.append_row(builder, metadata, index),
224 Self::PrimitiveLargeString(b) => b.append_row(builder, metadata, index),
225 Self::PrimitiveBinary(b) => b.append_row(builder, metadata, index),
226 Self::PrimitiveBinaryView(b) => b.append_row(builder, metadata, index),
227 Self::PrimitiveLargeBinary(b) => b.append_row(builder, metadata, index),
228 Self::PrimitiveUuid(b) => b.append_row(builder, metadata, index),
229 Self::List(b) => b.append_row(builder, metadata, index),
230 Self::LargeList(b) => b.append_row(builder, metadata, index),
231 Self::ListView(b) => b.append_row(builder, metadata, index),
232 Self::LargeListView(b) => b.append_row(builder, metadata, index),
233 Self::FixedSizeList(b) => b.append_row(builder, metadata, index),
234 Self::Struct(b) => b.append_row(builder, metadata, index),
235 Self::ValueOnly(b) => b.append_row(builder, metadata, index),
236 Self::Null(b) => b.append_row(builder, metadata, index),
237 }
238 }
239
240 fn try_new_opt(inner_struct: &'a StructArray) -> Result<Option<Self>> {
244 let value = if let Some(value_col) = inner_struct.column_by_name("value") {
245 validate_binary_array(value_col.as_ref(), "value")?;
246 Some(value_col)
247 } else {
248 None
249 };
250 let Some(typed_value) = inner_struct.column_by_name("typed_value") else {
251 return Ok(value.map(|v| Self::ValueOnly(ValueOnlyUnshredVariantBuilder::new(v))));
253 };
254
255 macro_rules! primitive_builder {
257 ($enum_variant:ident, $cast_fn:ident) => {
258 Self::$enum_variant(UnshredPrimitiveRowBuilder::new(
259 value,
260 typed_value.$cast_fn(),
261 ))
262 };
263 }
264
265 let builder = match typed_value.data_type() {
266 DataType::Int8 => primitive_builder!(PrimitiveInt8, as_primitive),
267 DataType::Int16 => primitive_builder!(PrimitiveInt16, as_primitive),
268 DataType::Int32 => primitive_builder!(PrimitiveInt32, as_primitive),
269 DataType::Int64 => primitive_builder!(PrimitiveInt64, as_primitive),
270 DataType::Float32 => primitive_builder!(PrimitiveFloat32, as_primitive),
271 DataType::Float64 => primitive_builder!(PrimitiveFloat64, as_primitive),
272 DataType::Decimal32(p, s) if VariantDecimal4::is_valid_precision_and_scale(p, s) => {
273 Self::Decimal32(DecimalUnshredRowBuilder::new(value, typed_value, *s))
274 }
275 DataType::Decimal64(p, s) if VariantDecimal8::is_valid_precision_and_scale(p, s) => {
276 Self::Decimal64(DecimalUnshredRowBuilder::new(value, typed_value, *s))
277 }
278 DataType::Decimal128(p, s) if VariantDecimal16::is_valid_precision_and_scale(p, s) => {
279 Self::Decimal128(DecimalUnshredRowBuilder::new(value, typed_value, *s))
280 }
281 DataType::Decimal32(_, _)
282 | DataType::Decimal64(_, _)
283 | DataType::Decimal128(_, _)
284 | DataType::Decimal256(_, _) => {
285 return Err(ArrowError::InvalidArgumentError(format!(
286 "{} is not a valid variant shredding type",
287 typed_value.data_type()
288 )));
289 }
290 DataType::Date32 => primitive_builder!(PrimitiveDate32, as_primitive),
291 DataType::Time64(TimeUnit::Microsecond) => {
292 primitive_builder!(PrimitiveTime64, as_primitive)
293 }
294 DataType::Time64(time_unit) => {
295 return Err(ArrowError::InvalidArgumentError(format!(
296 "Time64({time_unit}) is not a valid variant shredding type",
297 )));
298 }
299 DataType::Timestamp(TimeUnit::Microsecond, timezone) => Self::TimestampMicrosecond(
300 TimestampUnshredRowBuilder::new(value, typed_value, timezone.is_some()),
301 ),
302 DataType::Timestamp(TimeUnit::Nanosecond, timezone) => Self::TimestampNanosecond(
303 TimestampUnshredRowBuilder::new(value, typed_value, timezone.is_some()),
304 ),
305 DataType::Timestamp(time_unit, _) => {
306 return Err(ArrowError::InvalidArgumentError(format!(
307 "Timestamp({time_unit}) is not a valid variant shredding type",
308 )));
309 }
310 DataType::Boolean => primitive_builder!(PrimitiveBoolean, as_boolean),
311 DataType::Utf8 => primitive_builder!(PrimitiveString, as_string),
312 DataType::Utf8View => primitive_builder!(PrimitiveStringView, as_string_view),
313 DataType::LargeUtf8 => primitive_builder!(PrimitiveLargeString, as_string),
314 DataType::Binary => primitive_builder!(PrimitiveBinary, as_binary),
315 DataType::BinaryView => primitive_builder!(PrimitiveBinaryView, as_binary_view),
316 DataType::LargeBinary => primitive_builder!(PrimitiveLargeBinary, as_binary),
317 DataType::FixedSizeBinary(16) => {
318 primitive_builder!(PrimitiveUuid, as_fixed_size_binary)
319 }
320 DataType::FixedSizeBinary(size) => {
321 return Err(ArrowError::InvalidArgumentError(format!(
322 "FixedSizeBinary({size}) is not a valid variant shredding type",
323 )));
324 }
325 DataType::Struct(_) => Self::Struct(StructUnshredVariantBuilder::try_new(
326 value,
327 typed_value.as_struct(),
328 )?),
329 DataType::List(_) => Self::List(ListUnshredVariantBuilder::try_new(
330 value,
331 typed_value.as_list(),
332 )?),
333 DataType::LargeList(_) => Self::LargeList(ListUnshredVariantBuilder::try_new(
334 value,
335 typed_value.as_list(),
336 )?),
337 DataType::ListView(_) => Self::ListView(ListUnshredVariantBuilder::try_new(
338 value,
339 typed_value.as_list_view(),
340 )?),
341 DataType::LargeListView(_) => Self::LargeListView(ListUnshredVariantBuilder::try_new(
342 value,
343 typed_value.as_list_view(),
344 )?),
345 DataType::FixedSizeList(_, _) => Self::FixedSizeList(
346 ListUnshredVariantBuilder::try_new(value, typed_value.as_fixed_size_list())?,
347 ),
348 _ => {
349 return Err(ArrowError::NotYetImplemented(format!(
350 "Unshredding not yet supported for type: {}",
351 typed_value.data_type()
352 )));
353 }
354 };
355 Ok(Some(builder))
356 }
357}
358
359struct NullUnshredVariantBuilder;
361
362impl NullUnshredVariantBuilder {
363 fn append_row(
364 &mut self,
365 builder: &mut impl VariantBuilderExt,
366 _metadata: &VariantMetadata,
367 _index: usize,
368 ) -> Result<()> {
369 builder.append_value(Variant::Null);
370 Ok(())
371 }
372}
373
374struct ValueOnlyUnshredVariantBuilder<'a> {
376 value: &'a ArrayRef,
377}
378
379impl<'a> ValueOnlyUnshredVariantBuilder<'a> {
380 fn new(value: &'a ArrayRef) -> Self {
381 Self { value }
382 }
383
384 fn append_row(
385 &mut self,
386 builder: &mut impl VariantBuilderExt,
387 metadata: &VariantMetadata,
388 index: usize,
389 ) -> Result<()> {
390 if self.value.is_null(index) {
391 builder.append_null();
392 } else {
393 let value_bytes = binary_array_value(self.value.as_ref(), index).ok_or_else(|| {
394 ArrowError::InvalidArgumentError(
395 "value field must be a binary-like array".to_string(),
396 )
397 })?;
398 let variant = Variant::try_new_with_metadata(metadata.clone(), value_bytes)?;
399 builder.append_value(variant);
400 }
401 Ok(())
402 }
403}
404
405trait AppendToVariantBuilder: Array {
408 fn append_to_variant_builder(
409 &self,
410 builder: &mut impl VariantBuilderExt,
411 index: usize,
412 ) -> Result<()>;
413}
414
415macro_rules! handle_unshredded_case {
418 ($self:expr, $builder:expr, $metadata:expr, $index:expr, $partial_shredding:expr) => {{
419 let value = $self.value.as_ref().filter(|v| v.is_valid($index));
420 let value = value
421 .map(|v| {
422 let bytes = binary_array_value(v.as_ref(), $index).ok_or_else(|| {
423 ArrowError::InvalidArgumentError(format!(
424 "value field must be a binary-like array, instead got {}",
425 v.data_type(),
426 ))
427 })?;
428 Variant::try_new_with_metadata($metadata.clone(), bytes)
429 })
430 .transpose()?;
431
432 if $self.typed_value.is_null($index) {
434 match value {
435 Some(value) => $builder.append_value(value),
436 None => $builder.append_null(),
437 }
438 return Ok(());
439 }
440
441 if !$partial_shredding && value.is_some() {
443 return Err(ArrowError::InvalidArgumentError(
444 "Invalid shredded variant: both value and typed_value are non-null".to_string(),
445 ));
446 }
447
448 value
450 }};
451}
452
453struct UnshredPrimitiveRowBuilder<'a, T> {
455 value: Option<&'a ArrayRef>,
456 typed_value: &'a T,
457}
458
459impl<'a, T: AppendToVariantBuilder> UnshredPrimitiveRowBuilder<'a, T> {
460 fn new(value: Option<&'a ArrayRef>, typed_value: &'a T) -> Self {
461 Self { value, typed_value }
462 }
463
464 fn append_row(
465 &mut self,
466 builder: &mut impl VariantBuilderExt,
467 metadata: &VariantMetadata,
468 index: usize,
469 ) -> Result<()> {
470 handle_unshredded_case!(self, builder, metadata, index, false);
471
472 self.typed_value.append_to_variant_builder(builder, index)
474 }
475}
476
477macro_rules! impl_append_to_variant_builder {
479 ($array_type:ty $(, |$v:ident| $transform:expr)? ) => {
480 impl AppendToVariantBuilder for $array_type {
481 fn append_to_variant_builder(
482 &self,
483 builder: &mut impl VariantBuilderExt,
484 index: usize,
485 ) -> Result<()> {
486 let value = self.value(index);
487 $(
488 let $v = value;
489 let value = $transform;
490 )?
491 builder.append_value(value);
492 Ok(())
493 }
494 }
495 };
496}
497
498impl_append_to_variant_builder!(BooleanArray);
499impl_append_to_variant_builder!(StringArray);
500impl_append_to_variant_builder!(StringViewArray);
501impl_append_to_variant_builder!(LargeStringArray);
502impl_append_to_variant_builder!(BinaryArray);
503impl_append_to_variant_builder!(BinaryViewArray);
504impl_append_to_variant_builder!(LargeBinaryArray);
505impl_append_to_variant_builder!(PrimitiveArray<Int8Type>);
506impl_append_to_variant_builder!(PrimitiveArray<Int16Type>);
507impl_append_to_variant_builder!(PrimitiveArray<Int32Type>);
508impl_append_to_variant_builder!(PrimitiveArray<Int64Type>);
509impl_append_to_variant_builder!(PrimitiveArray<Float32Type>);
510impl_append_to_variant_builder!(PrimitiveArray<Float64Type>);
511
512impl_append_to_variant_builder!(PrimitiveArray<Date32Type>, |days_since_epoch| {
513 Date32Type::to_naive_date_opt(days_since_epoch).ok_or_else(|| {
514 ArrowError::InvalidArgumentError(format!("Invalid Date32 value: {days_since_epoch}"))
515 })?
516});
517
518impl_append_to_variant_builder!(
519 PrimitiveArray<Time64MicrosecondType>,
520 |micros_since_midnight| {
521 time64us_to_time(micros_since_midnight).ok_or_else(|| {
522 ArrowError::InvalidArgumentError(format!(
523 "Invalid Time64 microsecond value: {micros_since_midnight}"
524 ))
525 })?
526 }
527);
528
529impl_append_to_variant_builder!(FixedSizeBinaryArray, |bytes| {
532 Uuid::from_slice(bytes).unwrap()
533});
534
535trait TimestampType: ArrowPrimitiveType<Native = i64> {
537 fn to_datetime_utc(value: i64) -> Result<DateTime<Utc>>;
538}
539
540impl TimestampType for TimestampMicrosecondType {
541 fn to_datetime_utc(micros: i64) -> Result<DateTime<Utc>> {
542 DateTime::from_timestamp_micros(micros).ok_or_else(|| {
543 ArrowError::InvalidArgumentError(format!(
544 "Invalid timestamp microsecond value: {micros}"
545 ))
546 })
547 }
548}
549
550impl TimestampType for TimestampNanosecondType {
551 fn to_datetime_utc(nanos: i64) -> Result<DateTime<Utc>> {
552 Ok(DateTime::from_timestamp_nanos(nanos))
553 }
554}
555
556struct TimestampUnshredRowBuilder<'a, T: TimestampType> {
558 value: Option<&'a ArrayRef>,
559 typed_value: &'a PrimitiveArray<T>,
560 has_timezone: bool,
561}
562
563impl<'a, T: TimestampType> TimestampUnshredRowBuilder<'a, T> {
564 fn new(value: Option<&'a ArrayRef>, typed_value: &'a dyn Array, has_timezone: bool) -> Self {
565 Self {
566 value,
567 typed_value: typed_value.as_primitive(),
568 has_timezone,
569 }
570 }
571
572 fn append_row(
573 &mut self,
574 builder: &mut impl VariantBuilderExt,
575 metadata: &VariantMetadata,
576 index: usize,
577 ) -> Result<()> {
578 handle_unshredded_case!(self, builder, metadata, index, false);
579
580 let timestamp_value = self.typed_value.value(index);
582 let dt = T::to_datetime_utc(timestamp_value)?;
583 if self.has_timezone {
584 builder.append_value(dt);
585 } else {
586 builder.append_value(dt.naive_utc());
587 }
588 Ok(())
589 }
590}
591
592struct DecimalUnshredRowBuilder<'a, A: DecimalType, V>
594where
595 V: VariantDecimalType<Native = A::Native>,
596{
597 value: Option<&'a ArrayRef>,
598 typed_value: &'a PrimitiveArray<A>,
599 scale: i8,
600 _phantom: PhantomData<V>,
601}
602
603impl<'a, A: DecimalType, V> DecimalUnshredRowBuilder<'a, A, V>
604where
605 V: VariantDecimalType<Native = A::Native>,
606{
607 fn new(value: Option<&'a ArrayRef>, typed_value: &'a dyn Array, scale: i8) -> Self {
608 Self {
609 value,
610 typed_value: typed_value.as_primitive(),
611 scale,
612 _phantom: PhantomData,
613 }
614 }
615
616 fn append_row(
617 &mut self,
618 builder: &mut impl VariantBuilderExt,
619 metadata: &VariantMetadata,
620 index: usize,
621 ) -> Result<()> {
622 handle_unshredded_case!(self, builder, metadata, index, false);
623
624 let raw = self.typed_value.value(index);
625 let variant = V::try_new_with_signed_scale(raw, self.scale)?;
626 builder.append_value(variant);
627 Ok(())
628 }
629}
630
631struct StructUnshredVariantBuilder<'a> {
633 value: Option<&'a ArrayRef>,
634 typed_value: &'a arrow::array::StructArray,
635 field_unshredders: IndexMap<&'a str, Option<UnshredVariantRowBuilder<'a>>>,
636}
637
638impl<'a> StructUnshredVariantBuilder<'a> {
639 fn try_new(value: Option<&'a ArrayRef>, typed_value: &'a StructArray) -> Result<Self> {
640 let mut field_unshredders = IndexMap::new();
642 for (field, field_array) in typed_value.fields().iter().zip(typed_value.columns()) {
643 let Some(field_array) = field_array.as_struct_opt() else {
645 return Err(ArrowError::InvalidArgumentError(format!(
646 "Invalid shredded variant object field: expected Struct, got {}",
647 field_array.data_type()
648 )));
649 };
650 let field_unshredder = UnshredVariantRowBuilder::try_new_opt(field_array)?;
651 field_unshredders.insert(field.name().as_ref(), field_unshredder);
652 }
653
654 Ok(Self {
655 value,
656 typed_value,
657 field_unshredders,
658 })
659 }
660
661 fn append_row(
662 &mut self,
663 builder: &mut impl VariantBuilderExt,
664 metadata: &VariantMetadata,
665 index: usize,
666 ) -> Result<()> {
667 let value = handle_unshredded_case!(self, builder, metadata, index, true);
668
669 let mut object_builder = builder.try_new_object()?;
671
672 for (field_name, field_unshredder_opt) in &mut self.field_unshredders {
674 if let Some(field_unshredder) = field_unshredder_opt {
675 let mut field_builder = ObjectFieldBuilder::new(field_name, &mut object_builder);
676 field_unshredder.append_row(&mut field_builder, metadata, index)?;
677 }
678 }
679
680 if let Some(value) = value {
682 let Variant::Object(object) = value else {
683 return Err(ArrowError::InvalidArgumentError(
684 "Expected object in value field for partially shredded struct".to_string(),
685 ));
686 };
687
688 for entry in object.iter_try() {
689 let (field_name, field_value) = entry?;
690 if self.field_unshredders.contains_key(field_name) {
691 return Err(ArrowError::InvalidArgumentError(format!(
692 "Field '{field_name}' appears in both typed_value and value",
693 )));
694 }
695 object_builder.insert_bytes(field_name, field_value);
696 }
697 }
698
699 object_builder.finish();
700 Ok(())
701 }
702}
703
704struct ListUnshredVariantBuilder<'a, L: ListLikeArray> {
706 value: Option<&'a ArrayRef>,
707 typed_value: &'a L,
708 element_unshredder: Box<UnshredVariantRowBuilder<'a>>,
709}
710
711impl<'a, L: ListLikeArray> ListUnshredVariantBuilder<'a, L> {
712 fn try_new(value: Option<&'a ArrayRef>, typed_value: &'a L) -> Result<Self> {
713 let element_values = typed_value.values();
716
717 let Some(element_values) = element_values.as_struct_opt() else {
720 return Err(ArrowError::InvalidArgumentError(format!(
721 "Invalid shredded variant array element: expected Struct, got {}",
722 element_values.data_type()
723 )));
724 };
725
726 let element_unshredder = UnshredVariantRowBuilder::try_new_opt(element_values)?
731 .unwrap_or_else(UnshredVariantRowBuilder::null);
732
733 Ok(Self {
734 value,
735 typed_value,
736 element_unshredder: Box::new(element_unshredder),
737 })
738 }
739
740 fn append_row(
741 &mut self,
742 builder: &mut impl VariantBuilderExt,
743 metadata: &VariantMetadata,
744 index: usize,
745 ) -> Result<()> {
746 handle_unshredded_case!(self, builder, metadata, index, false);
747
748 let mut list_builder = builder.try_new_list()?;
750 for element_index in self.typed_value.element_range(index) {
751 self.element_unshredder
752 .append_row(&mut list_builder, metadata, element_index)?;
753 }
754
755 list_builder.finish();
756 Ok(())
757 }
758}
759
760#[cfg(test)]
761mod tests {
762 use crate::{VariantArray, VariantArrayBuilder, shred_variant};
763 use arrow::array::{
764 Array, ArrayRef, AsArray, BinaryArray, BinaryViewArray, Int64Array, LargeBinaryArray,
765 LargeStringArray, StringViewArray,
766 };
767 use arrow::datatypes::{DataType, Field, Fields, TimeUnit};
768 use parquet_variant::{EMPTY_VARIANT_METADATA_BYTES, Variant, VariantBuilder, VariantDecimal8};
769 use std::sync::Arc;
770
771 fn value_field_is_nullable(array: &VariantArray) -> bool {
773 array.inner().field_by_name("value").unwrap().is_nullable()
774 }
775
776 #[test]
777 fn test_unshred_utf8view_typed_value() {
778 let metadata_bytes = EMPTY_VARIANT_METADATA_BYTES;
779 let metadata: ArrayRef =
780 Arc::new(BinaryViewArray::from_iter_values(vec![metadata_bytes; 3]));
781
782 let typed_value: ArrayRef = Arc::new(StringViewArray::from(vec![
783 Some("hello"),
784 Some("middle"),
785 Some("world"),
786 ]));
787
788 let variant_array = VariantArray::perfectly_shredded(metadata, typed_value, None);
789
790 let result = crate::unshred_variant(&variant_array).unwrap();
791
792 assert_eq!(result.len(), 3);
793 assert_eq!(result.value(0), Variant::from("hello"));
794 assert_eq!(result.value(1), Variant::from("middle"));
795 assert_eq!(result.value(2), Variant::from("world"));
796 }
797
798 #[test]
799 fn test_unshred_largeutf8_typed_value() {
800 let metadata_bytes = EMPTY_VARIANT_METADATA_BYTES;
801 let metadata: ArrayRef =
802 Arc::new(BinaryViewArray::from_iter_values(vec![metadata_bytes; 3]));
803
804 let typed_value: ArrayRef = Arc::new(LargeStringArray::from(vec![
805 Some("hello"),
806 Some("middle"),
807 Some("world"),
808 ]));
809
810 let variant_array = VariantArray::perfectly_shredded(metadata, typed_value, None);
811
812 let result = crate::unshred_variant(&variant_array).unwrap();
813
814 assert_eq!(result.len(), 3);
815 assert_eq!(result.value(0), Variant::from("hello"));
816 assert_eq!(result.value(1), Variant::from("middle"));
817 assert_eq!(result.value(2), Variant::from("world"));
818 }
819
820 #[test]
821 fn test_unshred_binary_typed_value() {
822 let metadata_bytes = EMPTY_VARIANT_METADATA_BYTES;
823 let metadata: ArrayRef =
824 Arc::new(BinaryViewArray::from_iter_values(vec![metadata_bytes; 3]));
825
826 let typed_value: ArrayRef = Arc::new(BinaryArray::from_iter_values(vec![
827 &b"\x00\x01\x02"[..],
828 &b"\xff\xaa"[..],
829 &b"\xde\xad\xbe\xef"[..],
830 ]));
831
832 let variant_array = VariantArray::perfectly_shredded(metadata, typed_value, None);
833
834 let result = crate::unshred_variant(&variant_array).unwrap();
835
836 assert_eq!(result.len(), 3);
837 assert_eq!(result.value(0), Variant::from(&b"\x00\x01\x02"[..]));
838 assert_eq!(result.value(1), Variant::from(&b"\xff\xaa"[..]));
839 assert_eq!(result.value(2), Variant::from(&b"\xde\xad\xbe\xef"[..]));
840 }
841
842 #[test]
843 fn test_unshred_largebinary_typed_value() {
844 let metadata_bytes = EMPTY_VARIANT_METADATA_BYTES;
845 let metadata: ArrayRef =
846 Arc::new(BinaryViewArray::from_iter_values(vec![metadata_bytes; 3]));
847
848 let typed_value: ArrayRef = Arc::new(LargeBinaryArray::from_iter_values(vec![
849 &b"\x00\x01\x02"[..],
850 &b"\xff\xaa"[..],
851 &b"\xde\xad\xbe\xef"[..],
852 ]));
853
854 let variant_array = VariantArray::perfectly_shredded(metadata, typed_value, None);
855
856 let result = crate::unshred_variant(&variant_array).unwrap();
857
858 assert_eq!(result.len(), 3);
859 assert_eq!(result.value(0), Variant::from(&b"\x00\x01\x02"[..]));
860 assert_eq!(result.value(1), Variant::from(&b"\xff\xaa"[..]));
861 assert_eq!(result.value(2), Variant::from(&b"\xde\xad\xbe\xef"[..]));
862 }
863
864 #[test]
865 fn test_shred_unshred_round_trip_annotates_value_non_nullable() {
866 let mut builder = VariantArrayBuilder::new(1);
867 builder.append_variant(Variant::from(42i64));
868 let original = builder.build();
869 assert!(!value_field_is_nullable(&original));
870
871 let shredded = shred_variant(&original, &DataType::Int64).unwrap();
872 assert!(value_field_is_nullable(&shredded)); let unshredded = crate::unshred_variant(&shredded).unwrap();
875 assert!(!value_field_is_nullable(&unshredded));
876 assert_eq!(unshredded, original);
877 }
878
879 #[test]
880 fn test_unshred_with_nulls_annotates_value_non_nullable() {
881 let mut builder = VariantArrayBuilder::new(3);
884 builder.append_variant(Variant::from(1i64));
885 builder.append_null();
886 builder.append_variant(Variant::from("s"));
887 let original = builder.build();
888
889 let shredded = shred_variant(&original, &DataType::Int64).unwrap();
890 let unshredded = crate::unshred_variant(&shredded).unwrap();
891
892 assert!(!value_field_is_nullable(&unshredded));
893 assert_eq!(unshredded.len(), 3);
894 assert_eq!(unshredded.value(0), Variant::from(1i64));
895 assert!(unshredded.is_null(1));
896 assert_eq!(unshredded.value(2), Variant::from("s"));
897 }
898
899 #[test]
900 fn test_unshred_already_unshredded_reannotates_nullable_value() {
901 let mut builder = VariantArrayBuilder::new(2);
902 builder.append_variant(Variant::from(42i64));
903 builder.append_null();
904 let original = builder.build();
905
906 let nullable_input = VariantArray::from_parts(
908 original.metadata_column().clone(),
909 original.value_column().clone(),
910 None,
911 original.nulls().cloned(),
912 );
913 assert!(value_field_is_nullable(&nullable_input));
914
915 let unshredded = crate::unshred_variant(&nullable_input).unwrap();
916 assert!(!value_field_is_nullable(&unshredded));
917 assert_eq!(unshredded, original);
918 }
919
920 #[test]
921 fn test_unshred_missing_top_level_value_becomes_variant_null() {
922 let metadata_bytes = EMPTY_VARIANT_METADATA_BYTES;
923 let metadata: ArrayRef =
924 Arc::new(BinaryViewArray::from_iter_values(vec![metadata_bytes; 3]));
925
926 let typed_value: ArrayRef = Arc::new(Int64Array::from(vec![Some(1), None, Some(3)]));
929 let variant_array = VariantArray::perfectly_shredded(metadata, typed_value, None);
930
931 let result = crate::unshred_variant(&variant_array).unwrap();
932
933 assert!(!value_field_is_nullable(&result));
934 assert_eq!(result.inner().null_count(), 0);
935 assert_eq!(result.value(0), Variant::from(1i64));
936 assert_eq!(result.value(1), Variant::Null);
937 assert_eq!(result.value(2), Variant::from(3i64));
938 }
939
940 fn assert_missing_row_unshreds_to_variant_null(original: &VariantArray, as_type: &DataType) {
944 let shredded = shred_variant(original, as_type).unwrap();
945 assert!(shredded.typed_value_column().unwrap().is_valid(0));
948
949 let metadata_bytes = shredded.metadata_column().as_binary_view().value(0);
951 let metadata: ArrayRef = Arc::new(BinaryViewArray::from_iter_values(std::iter::repeat_n(
952 metadata_bytes,
953 original.len(),
954 )));
955
956 let input = VariantArray::from_parts(
957 metadata,
958 shredded.value_column().clone(),
959 shredded.typed_value_column().cloned(),
960 None,
961 );
962
963 let result = crate::unshred_variant(&input).unwrap();
964 assert!(!value_field_is_nullable(&result));
965 assert_eq!(result.inner().null_count(), 0);
966 assert_eq!(result.value(0), original.value(0));
967 assert_eq!(result.value(1), Variant::Null);
968 }
969
970 #[test]
973 fn test_unshred_missing_row_for_decimal_timestamp_object_list() {
974 let mut builder = VariantArrayBuilder::new(2);
975 builder.append_variant(Variant::from(VariantDecimal8::try_new(1234, 2).unwrap()));
976 builder.append_null();
977 assert_missing_row_unshreds_to_variant_null(&builder.build(), &DataType::Decimal64(18, 2));
978
979 let mut builder = VariantArrayBuilder::new(2);
980 builder.append_variant(Variant::from(
981 chrono::DateTime::from_timestamp(1, 0).unwrap(),
982 ));
983 builder.append_null();
984 assert_missing_row_unshreds_to_variant_null(
985 &builder.build(),
986 &DataType::Timestamp(TimeUnit::Microsecond, Some("UTC".into())),
987 );
988
989 let mut variant_builder = VariantBuilder::new();
990 let mut object_builder = variant_builder.new_object();
991 object_builder.insert("a", 1i64);
992 object_builder.finish();
993 let (object_metadata, object_value) = variant_builder.finish();
994 let mut builder = VariantArrayBuilder::new(2);
995 builder.append_variant(Variant::try_new(&object_metadata, &object_value).unwrap());
996 builder.append_null();
997 assert_missing_row_unshreds_to_variant_null(
998 &builder.build(),
999 &DataType::Struct(Fields::from(vec![Field::new("a", DataType::Int64, true)])),
1000 );
1001
1002 let mut variant_builder = VariantBuilder::new();
1003 let mut list_builder = variant_builder.new_list();
1004 list_builder.append_value(1i64);
1005 list_builder.append_value(2i64);
1006 list_builder.finish();
1007 let (list_metadata, list_value) = variant_builder.finish();
1008 let mut builder = VariantArrayBuilder::new(2);
1009 builder.append_variant(Variant::try_new(&list_metadata, &list_value).unwrap());
1010 builder.append_null();
1011 assert_missing_row_unshreds_to_variant_null(
1012 &builder.build(),
1013 &DataType::List(Arc::new(Field::new_list_field(DataType::Int64, true))),
1014 );
1015 }
1016
1017 #[test]
1018 fn test_unshred_value_only_with_unmasked_nulls_materializes_variant_null() {
1019 let mut builder = VariantArrayBuilder::new(1);
1020 builder.append_variant(Variant::from(42i64));
1021 let single = builder.build();
1022 let metadata_bytes = single.metadata_column().as_binary_view().value(0);
1023 let value_bytes = single.value_column().as_binary_view().value(0);
1024
1025 let metadata: ArrayRef =
1028 Arc::new(BinaryViewArray::from_iter_values(vec![metadata_bytes; 2]));
1029 let value: ArrayRef = Arc::new(BinaryViewArray::from(vec![Some(value_bytes), None]));
1030 let input = VariantArray::from_parts(metadata, value, None, None);
1031
1032 let result = crate::unshred_variant(&input).unwrap();
1033
1034 assert!(!value_field_is_nullable(&result));
1035 assert_eq!(result.inner().null_count(), 0);
1036 assert_eq!(result.value(0), Variant::from(42i64));
1037 assert_eq!(result.value(1), Variant::Null);
1038 }
1039
1040 #[test]
1041 fn test_unshred_returns_err_on_malformed_metadata() {
1042 let metadata: ArrayRef = Arc::new(BinaryViewArray::from_iter_values(vec![&b""[..]]));
1047
1048 let typed_value: ArrayRef = Arc::new(StringViewArray::from(vec![Some("hello")]));
1049
1050 let variant_array = VariantArray::perfectly_shredded(metadata, typed_value, None);
1051
1052 let result = crate::unshred_variant(&variant_array);
1053
1054 assert!(
1055 result.is_err(),
1056 "unshred_variant must return Err on malformed metadata, not panic",
1057 );
1058 }
1059}