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 8ed90de5 fix(parquet/pqarrow): propagate level builder errors (#1049)
8ed90de5 is described below

commit 8ed90de5da00a81f8a78493dbb1aba7f2758e252
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 5 19:38:58 2026 +0200

    fix(parquet/pqarrow): propagate level builder errors (#1049)
    
    ## Summary
    
    - return errors from multipath level builder construction instead of
    reporting a successful column write
    - release builders created for earlier chunks when a later chunk fails
    - release partially initialized builder state on visitor errors
    - leave the physical column position unchanged after builder
    construction fails
    
    ## Testing
    
    - `go test ./parquet/pqarrow`
    
    The regression test uses a valid dictionary chunk followed by a
    dictionary containing a null, which reaches the previously swallowed
    error path, verifies checked allocator cleanup, and successfully retries
    the same physical column with supported data.
---
 parquet/pqarrow/encode_arrow.go           | 14 +++++++++--
 parquet/pqarrow/encode_dictionary_test.go | 42 +++++++++++++++++++++++++++++++
 parquet/pqarrow/path_builder.go           |  1 +
 3 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/parquet/pqarrow/encode_arrow.go b/parquet/pqarrow/encode_arrow.go
index c7f089c2..73b52a34 100644
--- a/parquet/pqarrow/encode_arrow.go
+++ b/parquet/pqarrow/encode_arrow.go
@@ -126,6 +126,11 @@ func newArrowColumnWriter(data *arrow.Chunked, offset, 
size int64, manifest *Sch
        isNullable = nullableRoot(manifest, schemaField)
 
        builders := make([]*multipathLevelBuilder, 0)
+       releaseBuilders := func() {
+               for _, bldr := range builders {
+                       bldr.Release()
+               }
+       }
        for values < size {
                chunk := data.Chunk(chunkIdx)
                available := int64(chunk.Len() - int(chunkOffset))
@@ -134,17 +139,22 @@ func newArrowColumnWriter(data *arrow.Chunked, offset, 
size int64, manifest *Sch
                // the chunk offset will be 0 here except for possibly the 
first chunk
                // because of the above advancing logic
                arrToWrite := array.NewSlice(chunk, chunkOffset, 
chunkOffset+chunkWriteSize)
-               defer arrToWrite.Release()
 
                if arrToWrite.Len() > 0 {
                        bldr, err := newMultipathLevelBuilder(arrToWrite, 
isNullable)
+                       arrToWrite.Release()
                        if err != nil {
-                               return arrowColumnWriter{}, nil
+                               releaseBuilders()
+                               return arrowColumnWriter{}, err
                        }
                        if leafCount != bldr.leafCount() {
+                               bldr.Release()
+                               releaseBuilders()
                                return arrowColumnWriter{}, fmt.Errorf("data 
type leaf_count != builder leaf_count: %d - %d", leafCount, bldr.leafCount())
                        }
                        builders = append(builders, bldr)
+               } else {
+                       arrToWrite.Release()
                }
 
                if chunkWriteSize == available {
diff --git a/parquet/pqarrow/encode_dictionary_test.go 
b/parquet/pqarrow/encode_dictionary_test.go
index e92b2587..b027f7f0 100644
--- a/parquet/pqarrow/encode_dictionary_test.go
+++ b/parquet/pqarrow/encode_dictionary_test.go
@@ -21,6 +21,7 @@ package pqarrow_test
 import (
        "bytes"
        "context"
+       "errors"
        "fmt"
        "math"
        "strings"
@@ -39,6 +40,47 @@ import (
        "github.com/stretchr/testify/suite"
 )
 
+func TestWriteColumnChunkedPropagatesLevelBuilderError(t *testing.T) {
+       mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+       defer mem.AssertSize(t, 0)
+
+       dictType := &arrow.DictionaryType{IndexType: 
arrow.PrimitiveTypes.Int32, ValueType: arrow.BinaryTypes.String}
+       validIndices, _, err := array.FromJSON(mem, arrow.PrimitiveTypes.Int32, 
strings.NewReader(`[0]`))
+       require.NoError(t, err)
+       defer validIndices.Release()
+       validDictionary, _, err := array.FromJSON(mem, 
arrow.BinaryTypes.String, strings.NewReader(`["valid"]`))
+       require.NoError(t, err)
+       defer validDictionary.Release()
+       valid := array.NewDictionaryArray(dictType, validIndices, 
validDictionary)
+       defer valid.Release()
+
+       invalidIndices, _, err := array.FromJSON(mem, 
arrow.PrimitiveTypes.Int32, strings.NewReader(`[0]`))
+       require.NoError(t, err)
+       defer invalidIndices.Release()
+       invalidDictionary, _, err := array.FromJSON(mem, 
arrow.BinaryTypes.String, strings.NewReader(`[null]`))
+       require.NoError(t, err)
+       defer invalidDictionary.Release()
+       invalid := array.NewDictionaryArray(dictType, invalidIndices, 
invalidDictionary)
+       defer invalid.Release()
+
+       values := arrow.NewChunked(dictType, []arrow.Array{valid, invalid})
+       defer values.Release()
+       schema := arrow.NewSchema([]arrow.Field{{Name: "values", Type: 
dictType, Nullable: true}}, nil)
+
+       var output bytes.Buffer
+       writer, err := pqarrow.NewFileWriter(schema, &output,
+               parquet.NewWriterProperties(parquet.WithAllocator(mem)),
+               pqarrow.NewArrowWriterProperties(pqarrow.WithAllocator(mem)))
+       require.NoError(t, err)
+       require.NoError(t, writer.NewRowGroupChecked())
+
+       err = writer.WriteColumnChunked(values, 0, int64(values.Len()))
+       require.Error(t, err)
+       assert.True(t, errors.Is(err, arrow.ErrNotImplemented))
+       require.NoError(t, writer.WriteColumnData(valid))
+       require.NoError(t, writer.Close())
+}
+
 func (ps *ParquetIOTestSuite) TestSingleColumnOptionalDictionaryWrite() {
        for _, dt := range fullTypeList {
                // skip tests for bool as we don't do dictionaries for it
diff --git a/parquet/pqarrow/path_builder.go b/parquet/pqarrow/path_builder.go
index 991710b3..b10318a4 100644
--- a/parquet/pqarrow/path_builder.go
+++ b/parquet/pqarrow/path_builder.go
@@ -536,6 +536,7 @@ func newMultipathLevelBuilder(arr arrow.Array, 
fieldNullable bool) (*multipathLe
                builder:   pathBuilder{nullableInParent: fieldNullable, paths: 
make([]pathInfo, 0), refCount: utils.NewRefCount(1)},
        }
        if err := ret.builder.Visit(arr); err != nil {
+               ret.builder.Release()
                return nil, err
        }
        arr.Data().Retain()

Reply via email to