diff --git a/parquet-variant-compute/src/lib.rs b/parquet-variant-compute/src/lib.rs index 496d550d95b1..d52d921247e1 100644 --- a/parquet-variant-compute/src/lib.rs +++ b/parquet-variant-compute/src/lib.rs @@ -46,7 +46,7 @@ mod variant_array_builder; pub mod variant_get; mod variant_to_arrow; -pub use variant_array::{ShreddingState, VariantArray, VariantType}; +pub use variant_array::{ShreddingState, VariantArray, VariantArrayValue, VariantType}; pub use variant_array_builder::{VariantArrayBuilder, VariantValueArrayBuilder}; pub use cast_to_variant::{cast_to_variant, cast_to_variant_with_options}; diff --git a/parquet-variant-compute/src/shred_variant.rs b/parquet-variant-compute/src/shred_variant.rs index 138209802ab4..0408ad66183d 100644 --- a/parquet-variant-compute/src/shred_variant.rs +++ b/parquet-variant-compute/src/shred_variant.rs @@ -86,7 +86,9 @@ pub fn shred_variant(array: &VariantArray, as_type: &DataType) -> Result { + Borrowed(Variant<'m, 'v>), + Owned { + metadata: VariantMetadata<'m>, + value_bytes: Vec, + }, +} + +impl<'m, 'v> VariantArrayValue<'m, 'v> { + /// Creates a new instance that borrows from a normal [`Variant`] value. + pub fn borrowed(value: Variant<'m, 'v>) -> Self { + Self::Borrowed(value) + } + + /// Creates a new instance that wraps owned bytes that can be converted to a [`Variant`] value. + pub fn owned(metadata: VariantMetadata<'m>, value_bytes: Vec) -> Self { + Self::Owned { + metadata, + value_bytes, + } + } + + /// Consumes this variant value, passing the result to a `visitor` function. + /// + /// The visitor idiom is helpful because a variant value based on owned bytes cannot outlive + /// self. + pub fn consume(self, visitor: impl FnOnce(Variant<'_, '_>) -> R) -> R { + match self { + VariantArrayValue::Borrowed(v) => visitor(v), + VariantArrayValue::Owned { + metadata, + value_bytes, + } => visitor(Variant::new_with_metadata(metadata, &value_bytes)), + } + } + + // internal helper for when we don't want to pay the extra clone + fn as_variant_cow(&self) -> Cow<'_, Variant<'m, '_>> { + match self { + VariantArrayValue::Borrowed(v) => Cow::Borrowed(v), + VariantArrayValue::Owned { + metadata, + value_bytes, + } => Cow::Owned(Variant::new_with_metadata(metadata.clone(), value_bytes)), + } + } + + /// Returns a [`Variant`] instance for this value. + pub fn as_variant(&self) -> Variant<'m, '_> { + self.as_variant_cow().into_owned() + } + + /// Returns the variant metadata that backs this value. + pub fn metadata(&self) -> &VariantMetadata<'m> { + match self { + VariantArrayValue::Borrowed(v) => v.metadata(), + VariantArrayValue::Owned { metadata, .. } => metadata, + } + } + + /// Extracts the underlying [`VariantObject`], if this is a variant object. + /// + /// See also [`Variant::as_object`]. + pub fn as_object(&self) -> Option> { + self.as_variant_cow().as_object().cloned() + } + + /// Extracts the underlying [`VariantList`], if this is a variant array. + /// + /// See also [`Variant::as_list`]. + pub fn as_list(&self) -> Option> { + self.as_variant_cow().as_list().cloned() + } + + /// Extracts the value of the named variant object field, if this is a variant object. + /// + /// See also [`Variant::get_object_field`]. + pub fn get_object_field<'s>(&'s self, field_name: &str) -> Option> { + self.as_variant_cow().get_object_field(field_name) + } + + /// Extracts the value of the variant array element at `index`, if this is a variant object. + /// + /// See also [`Variant::get_list_element`]. + pub fn get_list_element(&self, index: usize) -> Option> { + self.as_variant_cow().get_list_element(index) + } +} + +impl<'m, 'v> From> for VariantArrayValue<'m, 'v> { + fn from(value: Variant<'m, 'v>) -> Self { + Self::borrowed(value) + } +} + +// By providing PartialEq for all three combinations, we avoid changing a lot of unit test code that +// relies on comparisons. +impl PartialEq for VariantArrayValue<'_, '_> { + fn eq(&self, other: &VariantArrayValue<'_, '_>) -> bool { + self.as_variant_cow().as_ref() == other.as_variant_cow().as_ref() + } +} + +impl PartialEq> for VariantArrayValue<'_, '_> { + fn eq(&self, other: &Variant<'_, '_>) -> bool { + self.as_variant_cow().as_ref() == other + } +} + +impl PartialEq> for Variant<'_, '_> { + fn eq(&self, other: &VariantArrayValue<'_, '_>) -> bool { + self == other.as_variant_cow().as_ref() + } +} + +// Make it transparent -- looks just like the underlying value it proxies +impl std::fmt::Debug for VariantArrayValue<'_, '_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_variant_cow().as_ref().fmt(f) + } +} + /// An array of Parquet [`Variant`] values /// /// A [`VariantArray`] wraps an Arrow [`StructArray`] that stores the underlying @@ -340,6 +467,29 @@ impl VariantArray { /// /// # Panics /// * if the index is out of bounds + /// * if variant construction failed + /// + /// If this is a shredded variant but has no value at the shredded location, it + /// will return [`Variant::Null`]. + /// + /// # Performance Note + /// + /// This is certainly not the most efficient way to access values in a + /// `VariantArray`, but it is useful for testing and debugging. + /// + /// Note: Does not do deep validation of the [`Variant`], so it is up to the + /// caller to ensure that the metadata and value were constructed correctly. + pub fn value(&self, index: usize) -> VariantArrayValue<'_, '_> { + self.try_value(index).unwrap() + } + + /// Try to return the [`Variant`] instance stored at the given row + /// + /// Note: This method does not check for nulls and the value is arbitrary + /// (but still well-defined) if [`is_null`](Self::is_null) returns true for the index. + /// + /// # Panics + /// * if the index is out of bounds /// * if the array value is null /// /// If this is a shredded variant but has no value at the shredded location, it @@ -353,11 +503,17 @@ impl VariantArray { /// /// Note: Does not do deep validation of the [`Variant`], so it is up to the /// caller to ensure that the metadata and value were constructed correctly. - pub fn value(&self, index: usize) -> Variant<'_, '_> { - match (self.typed_value_field(), self.value_field()) { + pub fn try_value(&self, index: usize) -> Result, ArrowError> { + let value = match (self.typed_value_field(), self.value_field()) { // Always prefer typed_value, if available (Some(typed_value), value) if typed_value.is_valid(index) => { - typed_value_to_variant(typed_value, value, index) + let metadata = VariantMetadata::new(self.metadata.value(index)); + let mut builder = SingleValueVariantBuilder::new(metadata.clone()); + typed_value_to_variant(typed_value, value, index, &metadata, &mut builder)?; + return Ok(VariantArrayValue::owned( + metadata.clone(), + builder.into_bytes(), + )); } // Otherwise fall back to value, if available (_, Some(value)) if value.is_valid(index) => { @@ -366,7 +522,8 @@ impl VariantArray { // It is technically invalid for neither value nor typed_value fields to be available, // but the spec specifically requires readers to return Variant::Null in this case. _ => Variant::Null, - } + }; + Ok(value.into()) } /// Return a reference to the metadata field of the [`StructArray`] @@ -777,65 +934,119 @@ impl StructArrayBuilder { } } +/// A simple wrapper that provides VariantBuilderExt for building a single variant value +/// +/// This is used specifically by VariantArray::value to build a single variant from shredded data +/// without the complexity of array-level state management. +struct SingleValueVariantBuilder<'m> { + value_builder: ValueBuilder, + metadata_builder: ReadOnlyMetadataBuilder<'m>, +} + +impl<'m> SingleValueVariantBuilder<'m> { + fn new(metadata: VariantMetadata<'m>) -> Self { + Self { + value_builder: ValueBuilder::new(), + metadata_builder: ReadOnlyMetadataBuilder::new(metadata), + } + } + + fn into_bytes(self) -> Vec { + self.value_builder.into_inner() + } + + fn parent_state(&mut self) -> ParentState<'_, ()> { + ParentState::variant(&mut self.value_builder, &mut self.metadata_builder) + } +} + +impl VariantBuilderExt for SingleValueVariantBuilder<'_> { + type State<'a> + = () + where + Self: 'a; + + fn append_null(&mut self) { + self.value_builder.append_null(); + } + + fn append_value<'m, 'v>(&mut self, value: impl Into>) { + ValueBuilder::append_variant_bytes(self.parent_state(), value.into()); + } + + fn try_new_list(&mut self) -> Result>, ArrowError> { + Ok(ListBuilder::new(self.parent_state(), false)) + } + + fn try_new_object(&mut self) -> Result>, ArrowError> { + Ok(ObjectBuilder::new(self.parent_state(), false)) + } +} + /// returns the non-null element at index as a Variant -fn typed_value_to_variant<'a>( - typed_value: &'a ArrayRef, +fn typed_value_to_variant( + typed_value: &ArrayRef, value: Option<&BinaryViewArray>, index: usize, -) -> Variant<'a, 'a> { - let data_type = typed_value.data_type(); - if value.is_some_and(|v| !matches!(data_type, DataType::Struct(_)) && v.is_valid(index)) { - // Only a partially shredded struct is allowed to have values for both columns - panic!("Invalid variant, conflicting value and typed_value"); - } - match data_type { + metadata: &VariantMetadata, + builder: &mut impl VariantBuilderExt, +) -> Result<(), ArrowError> { + match typed_value.data_type() { DataType::Boolean => { let boolean_array = typed_value.as_boolean(); let value = boolean_array.value(index); - Variant::from(value) + builder.append_value(value); } DataType::Date32 => { let array = typed_value.as_primitive::(); let value = array.value(index); let date = Date32Type::to_naive_date(value); - Variant::from(date) + builder.append_value(date); } // 16-byte FixedSizeBinary alway corresponds to a UUID; all other sizes are illegal. DataType::FixedSizeBinary(16) => { let array = typed_value.as_fixed_size_binary(); let value = array.value(index); - Uuid::from_slice(value).unwrap().into() // unwrap is safe: slice is always 16 bytes + let value = Uuid::from_slice(value).unwrap(); // unwrap safety: slice is always 16 bytes + builder.append_value(value); } DataType::BinaryView => { let array = typed_value.as_binary_view(); let value = array.value(index); - Variant::from(value) + builder.append_value(value); } DataType::Utf8 => { let array = typed_value.as_string::(); let value = array.value(index); - Variant::from(value) + builder.append_value(value); } DataType::Int8 => { - primitive_conversion_single_value!(Int8Type, typed_value, index) + let variant = primitive_conversion_single_value!(Int8Type, typed_value, index); + builder.append_value(variant); } DataType::Int16 => { - primitive_conversion_single_value!(Int16Type, typed_value, index) + let variant = primitive_conversion_single_value!(Int16Type, typed_value, index); + builder.append_value(variant); } DataType::Int32 => { - primitive_conversion_single_value!(Int32Type, typed_value, index) + let variant = primitive_conversion_single_value!(Int32Type, typed_value, index); + builder.append_value(variant); } DataType::Int64 => { - primitive_conversion_single_value!(Int64Type, typed_value, index) - } - DataType::Float16 => { - primitive_conversion_single_value!(Float16Type, typed_value, index) + let variant = primitive_conversion_single_value!(Int64Type, typed_value, index); + builder.append_value(variant); } DataType::Float32 => { - primitive_conversion_single_value!(Float32Type, typed_value, index) + let variant = primitive_conversion_single_value!(Float32Type, typed_value, index); + builder.append_value(variant); } DataType::Float64 => { - primitive_conversion_single_value!(Float64Type, typed_value, index) + let variant = primitive_conversion_single_value!(Float64Type, typed_value, index); + builder.append_value(variant); + } + DataType::Struct(_) => { + // Return directly in order to bypass the partial shredding check below + return struct_typed_value_to_variant(typed_value, value, index, metadata, builder); } // todo other types here (note this is very similar to cast_to_variant.rs) // so it would be great to figure out how to share this code @@ -848,9 +1059,82 @@ fn typed_value_to_variant<'a>( "Unsupported typed_value type: {}", typed_value.data_type() ); - Variant::Null + builder.append_value(Variant::Null); } } + + // Only a partially shredded struct is allowed to have values for both columns + if value.is_some_and(|v| v.is_valid(index)) { + return Err(ArrowError::InvalidArgumentError( + "Invalid variant, conflicting value and typed_value".to_string(), + )); + } + + Ok(()) +} + +/// Handles reconstruction of variant objects from shredded struct data +fn struct_typed_value_to_variant( + typed_value: &ArrayRef, + value: Option<&BinaryViewArray>, + index: usize, + metadata: &VariantMetadata, + builder: &mut impl VariantBuilderExt, +) -> Result<(), ArrowError> { + let struct_array = typed_value.as_struct(); + let mut obj_builder = builder.try_new_object()?; + + // Track all shredded field names -- we must ignore them when processing value fields below. + let mut shredded_field_names = std::collections::HashSet::new(); + for (field_name, field_array) in struct_array + .column_names() + .iter() + .zip(struct_array.columns()) + { + shredded_field_names.insert(*field_name); + let field_shredded_array = ShreddedVariantFieldArray::try_new(field_array.as_ref())?; + let shredding_state = field_shredded_array.shredding_state(); + let value_field = shredding_state.value_field(); + let typed_value_field = shredding_state.typed_value_field(); + + match (typed_value_field, value_field) { + (Some(typed_value), value) if typed_value.is_valid(index) => { + // Handle typed value with optional value column + let mut field_builder = ObjectFieldBuilder::new(field_name, &mut obj_builder); + typed_value_to_variant(typed_value, value, index, metadata, &mut field_builder)?; + } + (_, Some(value)) if value.is_valid(index) => { + // Handle unshredded value only + // TODO: Add raw byte append capability to VariantBuilderExt to avoid bytes -> variant -> bytes conversion + let variant_bytes = value.value(index); + let field_variant = Variant::new_with_metadata(metadata.clone(), variant_bytes); + obj_builder.insert_bytes(field_name, field_variant); + } + // Skip missing or invalid fields + _ => {} + } + } + + // Add fields from the value column if present (with collision detection) + if let Some(value_array) = value { + if value_array.is_valid(index) { + let variant_bytes = value_array.value(index); + let field_variant = Variant::new_with_metadata(metadata.clone(), variant_bytes); + let Variant::Object(obj) = field_variant else { + return Err(ArrowError::InvalidArgumentError( + "Invalid variant, non-object value with shredded fields".to_string(), + )); + }; + for (obj_field_name, obj_field_value) in obj.iter() { + if !shredded_field_names.contains(obj_field_name) { + obj_builder.insert_bytes(obj_field_name, obj_field_value); + } + } + } + } + + obj_builder.finish(); + Ok(()) } /// Workaround for lack of direct support for BinaryArray diff --git a/parquet-variant-compute/src/variant_array_builder.rs b/parquet-variant-compute/src/variant_array_builder.rs index 68c1fd6b5492..cbd264384778 100644 --- a/parquet-variant-compute/src/variant_array_builder.rs +++ b/parquet-variant-compute/src/variant_array_builder.rs @@ -485,7 +485,7 @@ mod test { let mut builder = VariantValueArrayBuilder::new(3); // straight copy - builder.append_value(array.value(0)); + array.value(0).consume(|value| builder.append_value(value)); // filtering fields takes more work because we need to manually create an object builder let value = array.value(1); @@ -498,6 +498,7 @@ mod test { // same bytes, but now nested and duplicated inside a list let value = array.value(2); + let value = value.as_variant(); let mut metadata_builder = ReadOnlyMetadataBuilder::new(value.metadata().clone()); let state = builder.parent_state(&mut metadata_builder); ListBuilder::new(state, false) diff --git a/parquet-variant-compute/src/variant_get.rs b/parquet-variant-compute/src/variant_get.rs index 49f56af57327..04170e37e189 100644 --- a/parquet-variant-compute/src/variant_get.rs +++ b/parquet-variant-compute/src/variant_get.rs @@ -144,7 +144,9 @@ fn shredded_get_path( if target.is_null(i) { builder.append_null()?; } else { - builder.append_value(target.value(i))?; + target + .value(i) + .consume(|value| builder.append_value(value))?; } } builder.finish() @@ -1392,7 +1394,9 @@ mod test { let json_str = r#"{"x": 42}"#; let string_array: ArrayRef = Arc::new(StringArray::from(vec![json_str])); if let Ok(variant_array) = json_to_variant(&string_array) { - builder.append_variant(variant_array.value(0)); + variant_array + .value(0) + .consume(|value| builder.append_variant(value)); } else { builder.append_null(); } @@ -1403,7 +1407,9 @@ mod test { let json_str = r#"{"x": "foo"}"#; let string_array: ArrayRef = Arc::new(StringArray::from(vec![json_str])); if let Ok(variant_array) = json_to_variant(&string_array) { - builder.append_variant(variant_array.value(0)); + variant_array + .value(0) + .consume(|value| builder.append_variant(value)); } else { builder.append_null(); } @@ -1414,7 +1420,9 @@ mod test { let json_str = r#"{"y": 10}"#; let string_array: ArrayRef = Arc::new(StringArray::from(vec![json_str])); if let Ok(variant_array) = json_to_variant(&string_array) { - builder.append_variant(variant_array.value(0)); + variant_array + .value(0) + .consume(|value| builder.append_variant(value)); } else { builder.append_null(); } @@ -1433,7 +1441,9 @@ mod test { let json_str = r#"{"a": {"x": 55}, "b": 42}"#; let string_array: ArrayRef = Arc::new(StringArray::from(vec![json_str])); if let Ok(variant_array) = json_to_variant(&string_array) { - builder.append_variant(variant_array.value(0)); + variant_array + .value(0) + .consume(|value| builder.append_variant(value)); } else { builder.append_null(); } @@ -1444,7 +1454,9 @@ mod test { let json_str = r#"{"a": {"x": "foo"}, "b": 42}"#; let string_array: ArrayRef = Arc::new(StringArray::from(vec![json_str])); if let Ok(variant_array) = json_to_variant(&string_array) { - builder.append_variant(variant_array.value(0)); + variant_array + .value(0) + .consume(|value| builder.append_variant(value)); } else { builder.append_null(); } @@ -1463,7 +1475,9 @@ mod test { let json_str = r#"{"a": {"b": {"x": 100}}}"#; let string_array: ArrayRef = Arc::new(StringArray::from(vec![json_str])); if let Ok(variant_array) = json_to_variant(&string_array) { - builder.append_variant(variant_array.value(0)); + variant_array + .value(0) + .consume(|value| builder.append_variant(value)); } else { builder.append_null(); } @@ -1474,7 +1488,9 @@ mod test { let json_str = r#"{"a": {"b": {"x": "bar"}}}"#; let string_array: ArrayRef = Arc::new(StringArray::from(vec![json_str])); if let Ok(variant_array) = json_to_variant(&string_array) { - builder.append_variant(variant_array.value(0)); + variant_array + .value(0) + .consume(|value| builder.append_variant(value)); } else { builder.append_null(); } @@ -1485,7 +1501,9 @@ mod test { let json_str = r#"{"a": {"b": {"y": 200}}}"#; let string_array: ArrayRef = Arc::new(StringArray::from(vec![json_str])); if let Ok(variant_array) = json_to_variant(&string_array) { - builder.append_variant(variant_array.value(0)); + variant_array + .value(0) + .consume(|value| builder.append_variant(value)); } else { builder.append_null(); } diff --git a/parquet-variant/src/builder.rs b/parquet-variant/src/builder.rs index 95a30c206d59..4b8fabc134bf 100644 --- a/parquet-variant/src/builder.rs +++ b/parquet-variant/src/builder.rs @@ -154,7 +154,7 @@ impl ValueBuilder { // Variant types below - fn append_null(&mut self) { + pub fn append_null(&mut self) { self.append_primitive_header(VariantPrimitiveType::Null); } diff --git a/parquet-variant/src/variant.rs b/parquet-variant/src/variant.rs index 849947675b13..1db20fa776ef 100644 --- a/parquet-variant/src/variant.rs +++ b/parquet-variant/src/variant.rs @@ -1218,7 +1218,7 @@ impl<'m, 'v> Variant<'m, 'v> { /// let obj = variant.as_object().expect("variant should be an object"); /// assert_eq!(obj.get("name"), Some(Variant::from("John"))); /// ``` - pub fn as_object(&'m self) -> Option<&'m VariantObject<'m, 'v>> { + pub fn as_object(&self) -> Option<&VariantObject<'m, 'v>> { if let Variant::Object(obj) = self { Some(obj) } else { @@ -1280,7 +1280,7 @@ impl<'m, 'v> Variant<'m, 'v> { /// assert_eq!(list.get(0).unwrap(), Variant::from("John")); /// assert_eq!(list.get(1).unwrap(), Variant::from("Doe")); /// ``` - pub fn as_list(&'m self) -> Option<&'m VariantList<'m, 'v>> { + pub fn as_list(&self) -> Option<&VariantList<'m, 'v>> { if let Variant::List(list) = self { Some(list) } else { @@ -1308,7 +1308,7 @@ impl<'m, 'v> Variant<'m, 'v> { /// let v2 = Variant::from("Hello"); /// assert_eq!(None, v2.as_time_utc()); /// ``` - pub fn as_time_utc(&'m self) -> Option { + pub fn as_time_utc(&self) -> Option { if let Variant::Time(time) = self { Some(*time) } else { diff --git a/parquet/tests/variant_integration.rs b/parquet/tests/variant_integration.rs index 9f202f4db803..1f467176f5b1 100644 --- a/parquet/tests/variant_integration.rs +++ b/parquet/tests/variant_integration.rs @@ -113,20 +113,18 @@ variant_test_case!(34, "Unsupported typed_value type: Timestamp(ns, \"UTC\")"); variant_test_case!(35, "Unsupported typed_value type: Timestamp(ns)"); variant_test_case!(36, "Unsupported typed_value type: Timestamp(ns)"); variant_test_case!(37); -// https://github.com/apache/arrow-rs/issues/8336 -variant_test_case!(38, "Unsupported typed_value type: Struct("); +variant_test_case!(38); variant_test_case!(39); // Is an error case (should be failing as the expected error message indicates) variant_test_case!(40, "Unsupported typed_value type: List("); variant_test_case!(41, "Unsupported typed_value type: List("); // Is an error case (should be failing as the expected error message indicates) variant_test_case!(42, "Invalid variant, conflicting value and typed_value"); -// https://github.com/apache/arrow-rs/issues/8336 -variant_test_case!(43, "Unsupported typed_value type: Struct("); -variant_test_case!(44, "Unsupported typed_value type: Struct("); +variant_test_case!(43); +variant_test_case!(44); // https://github.com/apache/arrow-rs/issues/8337 variant_test_case!(45, "Unsupported typed_value type: List("); -variant_test_case!(46, "Unsupported typed_value type: Struct("); +variant_test_case!(46); variant_test_case!(47); variant_test_case!(48); variant_test_case!(49); @@ -163,15 +161,13 @@ variant_test_case!(79); variant_test_case!(80); variant_test_case!(81); variant_test_case!(82); -// https://github.com/apache/arrow-rs/issues/8336 -variant_test_case!(83, "Unsupported typed_value type: Struct("); -variant_test_case!(84, "Unsupported typed_value type: Struct("); +variant_test_case!(83); +variant_test_case!(84); // https://github.com/apache/arrow-rs/issues/8337 variant_test_case!(85, "Unsupported typed_value type: List("); variant_test_case!(86, "Unsupported typed_value type: List("); // Is an error case (should be failing as the expected error message indicates) -// TODO: Once structs are supported, expect "Invalid variant, non-object value with shredded fields" -variant_test_case!(87, "Unsupported typed_value type: Struct("); +variant_test_case!(87, "Invalid variant, non-object value with shredded fields"); variant_test_case!(88, "Unsupported typed_value type: List("); variant_test_case!(89); variant_test_case!(90); @@ -209,23 +205,25 @@ variant_test_case!(121); variant_test_case!(122); variant_test_case!(123); variant_test_case!(124); -variant_test_case!(125, "Unsupported typed_value type: Struct"); +variant_test_case!(125); variant_test_case!(126, "Unsupported typed_value type: List("); -// Is an error case (should be failing as the expected error message indicates) +// Is an error case (error message mentions arrow data type instead of parquet logical type) variant_test_case!(127, "Illegal shredded value type: UInt32"); // Is an error case (should be failing as the expected error message indicates) -// TODO: Once structs are supported, expect "Invalid variant, non-object value with shredded fields" -variant_test_case!(128, "Unsupported typed_value type: Struct("); +variant_test_case!( + 128, + "Invalid variant, non-object value with shredded fields" +); variant_test_case!(129); -variant_test_case!(130, "Unsupported typed_value type: Struct("); +variant_test_case!(130); variant_test_case!(131); -variant_test_case!(132, "Unsupported typed_value type: Struct("); -variant_test_case!(133, "Unsupported typed_value type: Struct("); -variant_test_case!(134, "Unsupported typed_value type: Struct("); +variant_test_case!(132); +variant_test_case!(133); +variant_test_case!(134); variant_test_case!(135); variant_test_case!(136, "Unsupported typed_value type: List("); variant_test_case!(137, "Illegal shredded value type: FixedSizeBinary(4)"); -variant_test_case!(138, "Unsupported typed_value type: Struct("); +variant_test_case!(138); /// Test case definition structure matching the format from /// `parquet-testing/parquet_shredded/cases.json`