Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions arrow/extensions/variant.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ func NewVariantType(storage arrow.DataType) (*VariantType, error) {
dt = dt.(arrow.ExtensionType).StorageType()
}

if dt.ID() == arrow.NULL {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new rejection makes NewShreddedVariantType(arrow.Null) silently return nil, because that constructor discards the error returned by NewVariantType. Normal use of the returned type then panics. Please handle arrow.Null with a valid non-nil representation or expose an error-returning construction path instead of discarding the validation failure.

return nil, fmt.Errorf("%w: typed_value field must not be null type", arrow.ErrInvalid)
}

if nt, ok := dt.(arrow.NestedType); ok {
if !validNestedType(nt) {
return nil, fmt.Errorf("%w: typed_value field must be a valid nested type, got %s", arrow.ErrInvalid, typedValueField.Type)
Expand Down Expand Up @@ -295,7 +299,10 @@ func validStruct(s *arrow.StructType) bool {
switch s.NumFields() {
case 1:
f := s.Field(0)
return (f.Name == "value" && isBinary(f.Type)) || f.Name == "typed_value"
if f.Name == "value" {
return isBinary(f.Type)
}
return f.Name == "typed_value" && f.Type.ID() != arrow.NULL

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking only f.Type.ID() lets an extension whose storage type is arrow.Null bypass this validation because its ID is EXTENSION. The same bypass exists in the two-field path below. Please unwrap extension storage before checking for Null in both nested typed_value forms and add one- and two-field regression tests.

case 2:
valField, ok := s.FieldByName("value")
if !ok || !valField.Nullable || !isBinary(valField.Type) {
Expand All @@ -311,7 +318,7 @@ func validStruct(s *arrow.StructType) bool {
return validNestedType(nt)
}

return true
return typedField.Type.ID() != arrow.NULL
default:
return false
}
Expand Down
10 changes: 10 additions & 0 deletions arrow/extensions/variant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ func TestVariantExtensionType(t *testing.T) {
arrow.Field{Name: "metadata", Type: arrow.BinaryTypes.String, Nullable: false},
arrow.Field{Name: "value", Type: arrow.BinaryTypes.Binary, Nullable: false}),
"metadata field must be non-nullable binary type, got utf8"},
{arrow.StructOf(
arrow.Field{Name: "metadata", Type: arrow.BinaryTypes.Binary, Nullable: false},
arrow.Field{Name: "value", Type: arrow.BinaryTypes.Binary, Nullable: true},
arrow.Field{Name: "typed_value", Type: arrow.Null, Nullable: true}),
"typed_value field must not be null type"},
}

for _, tt := range tests {
Expand Down Expand Up @@ -113,6 +118,11 @@ func TestVariantExtensionBadNestedTypes(t *testing.T) {
), Nullable: false})},
{"empty struct elem", arrow.StructOf(
arrow.Field{Name: "foobar", Type: arrow.StructOf(), Nullable: false})},
{"null typed_value in shredded field", arrow.StructOf(
arrow.Field{Name: "foobar", Type: arrow.StructOf(
arrow.Field{Name: "value", Type: arrow.BinaryTypes.Binary, Nullable: true},
arrow.Field{Name: "typed_value", Type: arrow.Null, Nullable: true},
), Nullable: false})},
{"non-nullable two elem struct", arrow.StructOf(
arrow.Field{Name: "foobar", Type: arrow.StructOf(
arrow.Field{Name: "value", Type: arrow.BinaryTypes.Binary, Nullable: true},
Expand Down