@@ -28,18 +28,20 @@ namespace arrow::extension {
2828
2929VariantExtensionType::VariantExtensionType (const std::shared_ptr<DataType>& storage_type)
3030 : ExtensionType(storage_type) {
31- // GH-45948: Shredded variants will need to handle an optional shredded_value as
32- // well as value_ becoming optional.
33-
34- // IsSupportedStorageType should have been called already, asserting that both
35- // metadata and value are present.
36- if (storage_type->field (0 )->name () == " metadata" ) {
37- metadata_ = storage_type->field (0 );
38- value_ = storage_type->field (1 );
39- } else {
40- value_ = storage_type->field (0 );
41- metadata_ = storage_type->field (1 );
31+ // Find fields by name (ordering does not matter per spec).
32+ for (int i = 0 ; i < storage_type->num_fields (); ++i) {
33+ const auto & f = storage_type->field (i);
34+ if (f->name () == " metadata" ) {
35+ metadata_ = f;
36+ } else if (f->name () == " value" ) {
37+ value_ = f;
38+ } else if (f->name () == " typed_value" ) {
39+ typed_value_ = f;
40+ }
4241 }
42+ // IsSupportedStorageType() should have been called before construction.
43+ DCHECK_NE (metadata_, nullptr );
44+ DCHECK_NE (value_, nullptr );
4345}
4446
4547bool VariantExtensionType::ExtensionEquals (const ExtensionType& other) const {
@@ -71,35 +73,52 @@ bool IsBinaryField(const std::shared_ptr<Field> field) {
7173
7274bool VariantExtensionType::IsSupportedStorageType (
7375 const std::shared_ptr<DataType>& storage_type) {
74- // For now we only supported unshredded variants. Unshredded variant storage
75- // type should be a struct with a binary metadata and binary value.
76- //
77- // GH-45948: In shredded variants, the binary value field can be replaced
78- // with one or more of the following: object, array, typed_value, and
79- // variant_value.
80- if (storage_type->id () == Type::STRUCT ) {
81- if (storage_type->num_fields () == 2 ) {
82- // Ordering of metadata and value fields does not matter, as we will assign
83- // these to the VariantExtensionType's member shared_ptrs in the constructor.
84- // Here we just need to check that they are both present.
85-
86- const auto & field0 = storage_type->field (0 );
87- const auto & field1 = storage_type->field (1 );
88-
89- bool metadata_and_value_present =
90- (field0->name () == " metadata" && field1->name () == " value" ) ||
91- (field1->name () == " metadata" && field0->name () == " value" );
92-
93- if (metadata_and_value_present) {
94- // Both metadata and value must be non-nullable binary types for unshredded
95- // variants. This will change in GH-46948, when we will require a Visitor
96- // to traverse the structure of the variant.
97- return IsBinaryField (field0) && IsBinaryField (field1) && !field0->nullable () &&
98- !field1->nullable ();
99- }
76+ if (storage_type->id () != Type::STRUCT ) {
77+ return false ;
78+ }
79+
80+ // Find fields by name
81+ std::shared_ptr<Field> metadata_field;
82+ std::shared_ptr<Field> value_field;
83+ std::shared_ptr<Field> typed_value_field;
84+
85+ for (int i = 0 ; i < storage_type->num_fields (); ++i) {
86+ const auto & f = storage_type->field (i);
87+ if (f->name () == " metadata" ) {
88+ metadata_field = f;
89+ } else if (f->name () == " value" ) {
90+ value_field = f;
91+ } else if (f->name () == " typed_value" ) {
92+ typed_value_field = f;
10093 }
10194 }
10295
96+ // metadata is always required and must be binary-like
97+ if (!metadata_field || !IsBinaryField (metadata_field)) {
98+ return false ;
99+ }
100+
101+ // Unshredded: required metadata + required value (both binary)
102+ if (value_field && !typed_value_field) {
103+ return IsBinaryField (value_field) && !metadata_field->nullable () &&
104+ !value_field->nullable ();
105+ }
106+
107+ // Shredded: required metadata + optional value + optional typed_value
108+ if (value_field && typed_value_field) {
109+ // metadata must be non-nullable, value must be nullable binary,
110+ // typed_value must be nullable (any type)
111+ return !metadata_field->nullable () && IsBinaryField (value_field) &&
112+ value_field->nullable () && typed_value_field->nullable ();
113+ }
114+
115+ // NOTE: The shredding spec allows leaf schemas where `value` is absent
116+ // (typed_value only, for fully-shredded columns with no residual). We
117+ // reject this case for now because the current shredding implementation
118+ // always produces a `value` column. Supporting value-absent schemas
119+ // requires changes to ShredVariantColumn/ReconstructVariantColumn to
120+ // handle the missing residual path. This can be added in a follow-up
121+ // when Parquet reader integration requires it.
103122 return false ;
104123}
105124
@@ -113,9 +132,12 @@ Result<std::shared_ptr<DataType>> VariantExtensionType::Make(
113132 return std::make_shared<VariantExtensionType>(std::move (storage_type));
114133}
115134
116- // / NOTE: this is still experimental. GH-45948 will add shredding support, at which point
117- // / we need to separate this into unshredded_variant and shredded_variant helper
118- // / functions.
135+ // / \brief Return a VariantExtensionType instance.
136+ // /
137+ // / Supports both unshredded and shredded storage types:
138+ // / - Unshredded: struct{required binary metadata, required binary value}
139+ // / - Shredded: struct{required binary metadata, optional binary value,
140+ // / optional <T> typed_value}
119141std::shared_ptr<DataType> variant (std::shared_ptr<DataType> storage_type) {
120142 return VariantExtensionType::Make (std::move (storage_type)).ValueOrDie ();
121143}
0 commit comments