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 cbbc516b fix(parquet_reader): propagate batch read errors (#1081)
cbbc516b is described below

commit cbbc516bda20cc129d9259aa3da3e885d79a2ec7
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 5 20:20:55 2026 +0200

    fix(parquet_reader): propagate batch read errors (#1081)
    
    ## What changed
    
    Return typed column batch read errors from the parquet reader dumper and
    handle them in JSON, CSV, and table output modes. Errors include the
    affected column path.
    
    ## Why
    
    All `ReadBatch` errors were discarded. Corrupt page data could stop
    iteration and leave partial output without reporting why decoding
    failed.
    
    ## Testing
    
    - `go test ./parquet/cmd/parquet_reader`
---
 parquet/cmd/parquet_reader/dumper.go      | 49 +++++++++++++++++++------------
 parquet/cmd/parquet_reader/dumper_test.go | 43 +++++++++++++++++++++++++++
 parquet/cmd/parquet_reader/main.go        | 18 ++++++++++--
 3 files changed, 89 insertions(+), 21 deletions(-)

diff --git a/parquet/cmd/parquet_reader/dumper.go 
b/parquet/cmd/parquet_reader/dumper.go
index 15d3bd79..32d377c6 100644
--- a/parquet/cmd/parquet_reader/dumper.go
+++ b/parquet/cmd/parquet_reader/dumper.go
@@ -77,40 +77,47 @@ func createDumper(reader file.ColumnChunkReader) *Dumper {
        }
 }
 
-func (dump *Dumper) readNextBatch() {
+func (dump *Dumper) readNextBatch() (err error) {
        switch reader := dump.reader.(type) {
        case *file.BooleanColumnChunkReader:
                values := dump.valueBuffer.([]bool)
-               dump.levelsBuffered, dump.valuesBuffered, _ = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
+               dump.levelsBuffered, dump.valuesBuffered, err = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
        case *file.Int32ColumnChunkReader:
                values := dump.valueBuffer.([]int32)
-               dump.levelsBuffered, dump.valuesBuffered, _ = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
+               dump.levelsBuffered, dump.valuesBuffered, err = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
        case *file.Int64ColumnChunkReader:
                values := dump.valueBuffer.([]int64)
-               dump.levelsBuffered, dump.valuesBuffered, _ = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
+               dump.levelsBuffered, dump.valuesBuffered, err = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
        case *file.Float32ColumnChunkReader:
                values := dump.valueBuffer.([]float32)
-               dump.levelsBuffered, dump.valuesBuffered, _ = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
+               dump.levelsBuffered, dump.valuesBuffered, err = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
        case *file.Float64ColumnChunkReader:
                values := dump.valueBuffer.([]float64)
-               dump.levelsBuffered, dump.valuesBuffered, _ = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
+               dump.levelsBuffered, dump.valuesBuffered, err = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
        case *file.Int96ColumnChunkReader:
                values := dump.valueBuffer.([]parquet.Int96)
-               dump.levelsBuffered, dump.valuesBuffered, _ = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
+               dump.levelsBuffered, dump.valuesBuffered, err = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
        case *file.ByteArrayColumnChunkReader:
                values := dump.valueBuffer.([]parquet.ByteArray)
-               dump.levelsBuffered, dump.valuesBuffered, _ = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
+               dump.levelsBuffered, dump.valuesBuffered, err = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
        case *file.FixedLenByteArrayColumnChunkReader:
                values := dump.valueBuffer.([]parquet.FixedLenByteArray)
-               dump.levelsBuffered, dump.valuesBuffered, _ = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
+               dump.levelsBuffered, dump.valuesBuffered, err = 
reader.ReadBatch(dump.batchSize, values, dump.defLevels, dump.repLevels)
        }
 
        dump.valueOffset = 0
        dump.levelOffset = 0
+       return err
 }
 
-func (dump *Dumper) hasNext() bool {
-       return dump.levelOffset < dump.levelsBuffered || dump.reader.HasNext()
+func (dump *Dumper) hasNext() (bool, error) {
+       if dump.levelOffset < dump.levelsBuffered {
+               return true, nil
+       }
+       if dump.reader.HasNext() {
+               return true, nil
+       }
+       return false, dump.reader.Err()
 }
 
 const microSecondsPerDay = 24 * 3600e6
@@ -157,14 +164,20 @@ func (dump *Dumper) FormatValue(val interface{}, width 
int) string {
        }
 }
 
-func (dump *Dumper) Next() (interface{}, bool) {
+func (dump *Dumper) Next() (interface{}, bool, error) {
        if dump.levelOffset == dump.levelsBuffered {
-               if !dump.hasNext() {
-                       return nil, false
+               hasNext, err := dump.hasNext()
+               if err != nil {
+                       return nil, false, fmt.Errorf("reading column %s: %w", 
dump.reader.Descriptor().Path(), err)
+               }
+               if !hasNext {
+                       return nil, false, nil
+               }
+               if err := dump.readNextBatch(); err != nil {
+                       return nil, false, fmt.Errorf("reading column %s: %w", 
dump.reader.Descriptor().Path(), err)
                }
-               dump.readNextBatch()
                if dump.levelsBuffered == 0 {
-                       return nil, false
+                       return nil, false, nil
                }
        }
 
@@ -173,14 +186,14 @@ func (dump *Dumper) Next() (interface{}, bool) {
        dump.levelOffset++
 
        if defLevel < dump.reader.Descriptor().MaxDefinitionLevel() {
-               return nil, true
+               return nil, true, nil
        }
 
        vb := reflect.ValueOf(dump.valueBuffer)
        v := vb.Index(dump.valueOffset).Interface()
        dump.valueOffset++
 
-       return v, true
+       return v, true, nil
 }
 
 func dumpColIdxImpl[T parquet.ColumnTypes](cidx *metadata.TypedColumnIndex[T]) 
{
diff --git a/parquet/cmd/parquet_reader/dumper_test.go 
b/parquet/cmd/parquet_reader/dumper_test.go
new file mode 100644
index 00000000..75c034c3
--- /dev/null
+++ b/parquet/cmd/parquet_reader/dumper_test.go
@@ -0,0 +1,43 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package main
+
+import (
+       "errors"
+       "testing"
+
+       "github.com/apache/arrow-go/v18/parquet/file"
+       "github.com/stretchr/testify/require"
+)
+
+type errorColumnReader struct {
+       file.ColumnChunkReader
+       err error
+}
+
+func (r *errorColumnReader) HasNext() bool { return false }
+func (r *errorColumnReader) Err() error    { return r.err }
+
+func TestDumperHasNextReportsReaderErrors(t *testing.T) {
+       want := errors.New("page read failed")
+       dump := &Dumper{reader: &errorColumnReader{err: want}}
+
+       hasNext, err := dump.hasNext()
+       require.False(t, hasNext)
+       require.ErrorIs(t, err, want)
+}
diff --git a/parquet/cmd/parquet_reader/main.go 
b/parquet/cmd/parquet_reader/main.go
index 522781c9..11cb2fbf 100644
--- a/parquet/cmd/parquet_reader/main.go
+++ b/parquet/cmd/parquet_reader/main.go
@@ -329,7 +329,11 @@ func main() {
                                data := false
                                first := true
                                for idx, s := range scanners {
-                                       if val, ok := s.Next(); ok {
+                                       val, ok, err := s.Next()
+                                       if err != nil {
+                                               log.Fatal(err)
+                                       }
+                                       if ok {
                                                if !data {
                                                        fmt.Fprint(dataOut, 
line)
                                                }
@@ -387,7 +391,11 @@ func main() {
                                                        line += ","
                                                }
                                        }
-                                       if val, ok := s.Next(); ok {
+                                       val, ok, err := s.Next()
+                                       if err != nil {
+                                               log.Fatal(err)
+                                       }
+                                       if ok {
                                                if !data {
                                                        fmt.Fprint(dataOut, 
line)
                                                }
@@ -435,7 +443,11 @@ func main() {
                        for {
                                data := false
                                for _, s := range scanners {
-                                       if val, ok := s.Next(); ok {
+                                       val, ok, err := s.Next()
+                                       if err != nil {
+                                               log.Fatal(err)
+                                       }
+                                       if ok {
                                                if !data {
                                                        fmt.Fprint(dataOut, 
line)
                                                }

Reply via email to