Skip to content
Merged
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
43 changes: 35 additions & 8 deletions arrow/extensions/variant.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ func createShreddedField(dt arrow.DataType) arrow.DataType {
// ), Nullable: true})
//
// This is intended to be a convenient way to create a shredded variant type from a definition
// of the fields to shred. If the provided data type is nil, it will create a default
// variant type.
// of the fields to shred. If the provided data type is nil or Null (including an
// extension whose storage is Null), it will create a default variant type.
func NewShreddedVariantType(dt arrow.DataType) *VariantType {
if dt == nil {
if dt == nil || isNullType(dt) {
return NewDefaultVariantType()
}

Expand Down Expand Up @@ -220,9 +220,12 @@ func NewVariantType(storage arrow.DataType) (*VariantType, error) {
return nil, fmt.Errorf("%w: typed_value field must be nullable, got %s", arrow.ErrInvalid, typedValueField.Type)
}

dt := typedValueField.Type
if dt.ID() == arrow.EXTENSION {
dt = dt.(arrow.ExtensionType).StorageType()
dt := storageType(typedValueField.Type)
if dt == nil {
return nil, fmt.Errorf("%w: typed_value field has invalid storage type", arrow.ErrInvalid)
}
if dt.ID() == arrow.NULL {
Comment thread
dgvj-work marked this conversation as resolved.
return nil, fmt.Errorf("%w: typed_value field must not be null type", arrow.ErrInvalid)
}

if nt, ok := dt.(arrow.NestedType); ok {
Expand Down Expand Up @@ -291,11 +294,35 @@ func isBinary(dt arrow.DataType) bool {
dt.ID() == arrow.BINARY_VIEW
}

func storageType(dt arrow.DataType) arrow.DataType {
seen := make(map[arrow.DataType]struct{})
for dt != nil {
ext, ok := dt.(arrow.ExtensionType)
if !ok {
return dt
}
if _, dup := seen[dt]; dup {
return nil
}
seen[dt] = struct{}{}
dt = ext.StorageType()
}
return nil
}

func isNullType(dt arrow.DataType) bool {
st := storageType(dt)
return st == nil || st.ID() == arrow.NULL
}

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" && !isNullType(f.Type)
case 2:
valField, ok := s.FieldByName("value")
if !ok || !valField.Nullable || !isBinary(valField.Type) {
Expand All @@ -311,7 +338,7 @@ func validStruct(s *arrow.StructType) bool {
return validNestedType(nt)
}

return true
return !isNullType(typedField.Type)
default:
return false
}
Expand Down
55 changes: 55 additions & 0 deletions arrow/extensions/variant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ 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"},
{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: extensions.NewOpaqueType(arrow.Null, "null", "test"), Nullable: true}),
"typed_value field must not be null type"},
{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: extensions.NewOpaqueType(
extensions.NewOpaqueType(arrow.Null, "null", "test"), "null", "test"), Nullable: true}),
"typed_value field must not be null type"},
{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: extensions.NewOpaqueType(nil, "null", "test"), Nullable: true}),
"typed_value field has invalid storage type"},
}

for _, tt := range tests {
Expand Down Expand Up @@ -113,6 +134,31 @@ 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})},
{"null typed_value extension in one-field shredded field", arrow.StructOf(
arrow.Field{Name: "foobar", Type: arrow.StructOf(
arrow.Field{Name: "typed_value", Type: extensions.NewOpaqueType(arrow.Null, "null", "test"), Nullable: true},
), Nullable: false})},
{"null typed_value extension in two-field 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: extensions.NewOpaqueType(arrow.Null, "null", "test"), Nullable: true},
), Nullable: false})},
{"double-wrapped null typed_value in one-field shredded field", arrow.StructOf(
arrow.Field{Name: "foobar", Type: arrow.StructOf(
arrow.Field{Name: "typed_value", Type: extensions.NewOpaqueType(
extensions.NewOpaqueType(arrow.Null, "null", "test"), "null", "test"), Nullable: true},
), Nullable: false})},
{"double-wrapped null typed_value in two-field 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: extensions.NewOpaqueType(
extensions.NewOpaqueType(arrow.Null, "null", "test"), "null", "test"), 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 Expand Up @@ -1560,6 +1606,15 @@ func TestVariantBuilderUnmarshalJSON(t *testing.T) {
func TestNewSimpleShreddedVariantType(t *testing.T) {
assert.True(t, arrow.TypeEqual(extensions.NewDefaultVariantType(),
extensions.NewShreddedVariantType(nil)))
assert.True(t, arrow.TypeEqual(extensions.NewDefaultVariantType(),
extensions.NewShreddedVariantType(arrow.Null)))
assert.True(t, arrow.TypeEqual(extensions.NewDefaultVariantType(),
extensions.NewShreddedVariantType(extensions.NewOpaqueType(arrow.Null, "null", "test"))))
assert.True(t, arrow.TypeEqual(extensions.NewDefaultVariantType(),
extensions.NewShreddedVariantType(extensions.NewOpaqueType(
extensions.NewOpaqueType(arrow.Null, "null", "test"), "null", "test"))))
assert.True(t, arrow.TypeEqual(extensions.NewDefaultVariantType(),
extensions.NewShreddedVariantType(extensions.NewOpaqueType(nil, "null", "test"))))

vt := extensions.NewShreddedVariantType(arrow.PrimitiveTypes.Float32)
s := arrow.StructOf(
Expand Down
Loading