This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 97f780be fix(arrow/extensions): avoid child values for missing variant 
lists (#1293)
97f780be is described below

commit 97f780beb5a9129cbcf2c14193bc5830cf4ad035
Author: matanper <[email protected]>
AuthorDate: Tue Sep 8 20:21:09 2026 +0300

    fix(arrow/extensions): avoid child values for missing variant lists (#1293)
    
    ### Rationale for this change
    
    Writing shredded VARIANT values to Parquet fails with `lists with
    non-zero length null components are not supported` when a nested list
    has a null, missing, or scalar parent between populated rows. For
    example:
    
    ```json
    {"obj":{"items":[1]}}
    {"obj":null}
    {"obj":{"items":[2]}}
    ```
    
    `shreddedObjBuilder.AppendMissing` marks the parent struct null, but
    recursively calling `shreddedArrayBuilder.AppendMissing` adds a valid
    list and a placeholder child element. The Parquet writer rejects the
    resulting skipped child range.
    
    Fixes #1292.
    
    ### What changes are included in this PR?
    
    Make `shreddedArrayBuilder.AppendMissing` append a null list without
    adding child elements. The production change is two added lines
    (including a comment) and four removed lines.
    
    Add a self-contained Parquet write/read regression for null, missing,
    and scalar parent values between populated lists. It verifies all
    decoded JSON values, row count, and checked-allocator cleanup. No
    external fixtures are required.
    
    ### Are these changes tested?
    
    All three regression cases reproduce the original write error before the
    fix and pass afterward.
    
    - `go test ./parquet/pqarrow -run
    TestShreddedVariantListsUnderMissingParent -count=1`
    - `go test ./arrow/extensions ./parquet/variant`
    
    ### Are there any user-facing changes?
    
    These valid shredded VARIANT values can now be written to Parquet while
    preserving null, missing, and scalar parent values. No public API
    changes.
---
 arrow/extensions/variant.go     |  6 ++---
 parquet/pqarrow/variant_test.go | 55 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+), 4 deletions(-)

diff --git a/arrow/extensions/variant.go b/arrow/extensions/variant.go
index f4474133..270f1db3 100644
--- a/arrow/extensions/variant.go
+++ b/arrow/extensions/variant.go
@@ -1456,10 +1456,8 @@ type shreddedArrayBuilder struct {
 }
 
 func (s *shreddedArrayBuilder) AppendMissing() {
-       s.listBldr.Append(true)
-       s.elemBldr.Append(true)
-       s.valueBldr.AppendNull()
-       s.typedBldr.AppendMissing()
+       // Missing lists must not add child elements beneath a null parent 
struct.
+       s.listBldr.AppendNull()
 }
 
 func (b *shreddedArrayBuilder) tryTyped(v variant.Value) (residual []byte) {
diff --git a/parquet/pqarrow/variant_test.go b/parquet/pqarrow/variant_test.go
index 81fa246b..078a9dc4 100644
--- a/parquet/pqarrow/variant_test.go
+++ b/parquet/pqarrow/variant_test.go
@@ -17,6 +17,7 @@
 package pqarrow_test
 
 import (
+       "bytes"
        "context"
        "fmt"
        "io"
@@ -29,6 +30,7 @@ import (
        "unsafe"
 
        "github.com/apache/arrow-go/v18/arrow"
+       "github.com/apache/arrow-go/v18/arrow/array"
        "github.com/apache/arrow-go/v18/arrow/endian"
        "github.com/apache/arrow-go/v18/arrow/extensions"
        "github.com/apache/arrow-go/v18/arrow/memory"
@@ -36,6 +38,7 @@ import (
        "github.com/apache/arrow-go/v18/parquet"
        "github.com/apache/arrow-go/v18/parquet/pqarrow"
        "github.com/apache/arrow-go/v18/parquet/variant"
+       "github.com/stretchr/testify/require"
        "github.com/stretchr/testify/suite"
 )
 
@@ -324,3 +327,55 @@ func (s *ShreddedVariantTestSuite) TestErrorCases() {
 func TestShreddedVariantExamples(t *testing.T) {
        suite.Run(t, &ShreddedVariantTestSuite{generate: false})
 }
+
+// A missing list below a null struct must not introduce skipped child values.
+func TestShreddedVariantListsUnderMissingParent(t *testing.T) {
+       for name, middle := range map[string]string{
+               "null_parent":    `{"obj":null}`,
+               "missing_parent": `{}`,
+               "scalar_parent":  `{"obj":"other"}`,
+       } {
+               t.Run(name, func(t *testing.T) {
+                       alloc := 
memory.NewCheckedAllocator(memory.DefaultAllocator)
+                       defer alloc.AssertSize(t, 0)
+                       typ := extensions.NewShreddedVariantType(arrow.StructOf(
+                               arrow.Field{Name: "obj", Nullable: true, Type: 
arrow.StructOf(
+                                       arrow.Field{Name: "items", Nullable: 
true, Type: arrow.ListOf(arrow.PrimitiveTypes.Int64)},
+                               )},
+                       ))
+                       builder := extensions.NewVariantBuilder(alloc, typ)
+                       defer builder.Release()
+                       input := []string{`{"obj":{"items":[1]}}`, middle, 
`{"obj":{"items":[2]}}`}
+                       require.NoError(t, 
builder.UnmarshalJSON([]byte("["+strings.Join(input, ",")+"]")))
+                       values := builder.NewArray()
+                       defer values.Release()
+                       schema := arrow.NewSchema([]arrow.Field{{Name: "event", 
Type: typ, Nullable: true}}, nil)
+                       rec := array.NewRecordBatch(schema, 
[]arrow.Array{values}, int64(len(input)))
+                       defer rec.Release()
+                       tbl := array.NewTableFromRecords(schema, 
[]arrow.RecordBatch{rec})
+                       defer tbl.Release()
+                       var encoded bytes.Buffer
+                       require.NoError(t, pqarrow.WriteTable(tbl, &encoded, 
1024, parquet.NewWriterProperties(parquet.WithAllocator(alloc)), 
pqarrow.DefaultWriterProps()))
+                       result, err := pqarrow.ReadTable(t.Context(), 
bytes.NewReader(encoded.Bytes()), nil, pqarrow.ArrowReadProperties{}, alloc)
+                       require.NoError(t, err)
+                       defer result.Release()
+                       require.EqualValues(t, len(input), result.NumRows())
+                       reader := array.NewTableReader(result, 1024)
+                       defer reader.Release()
+                       row := 0
+                       for reader.Next() {
+                               variants := 
reader.RecordBatch().Column(0).(*extensions.VariantArray)
+                               for i := 0; i < variants.Len(); i++ {
+                                       value, err := variants.Value(i)
+                                       require.NoError(t, err)
+                                       decoded, err := value.MarshalJSON()
+                                       require.NoError(t, err)
+                                       require.JSONEq(t, input[row], 
string(decoded))
+                                       row++
+                               }
+                       }
+                       require.NoError(t, reader.Err())
+                       require.Len(t, input, row)
+               })
+       }
+}

Reply via email to