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 e55bca45 fix(parquet/pqarrow): validate reader indexes (#1124)
e55bca45 is described below

commit e55bca4599dcd7183568a29ba687606feec54036
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 12 20:31:34 2026 +0200

    fix(parquet/pqarrow): validate reader indexes (#1124)
    
    ### Rationale for this change
    
    Some Arrow Parquet reader paths index the field manifest or row-group
    metadata directly. Invalid input can panic even though the surrounding
    APIs return errors.
    
    ### What changes are included in this PR?
    
    Validate field indexes in GetFieldReader and row-group indexes in
    GetFieldReader and ReadColumn before using them.
    
    ### Are these changes tested?
    
    - `go test ./parquet/pqarrow -run TestFileReaderIndexValidation`
    
    ### Are there any user-facing changes?
    
    Invalid field and row-group indexes now return errors instead of
    panicking.
    
    ---------
    
    Co-authored-by: Matt Topol <[email protected]>
---
 parquet/pqarrow/file_reader.go      | 17 +++++++++++---
 parquet/pqarrow/file_reader_test.go | 45 +++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 3 deletions(-)

diff --git a/parquet/pqarrow/file_reader.go b/parquet/pqarrow/file_reader.go
index 37b847fc..ae659d22 100644
--- a/parquet/pqarrow/file_reader.go
+++ b/parquet/pqarrow/file_reader.go
@@ -219,6 +219,13 @@ func (fr *FileReader) allRowGroupFactory() itrFactory {
 //
 // IncludedLeaves and RowGroups are used to specify precisely which leaf 
indexes and row groups to read a subset of.
 func (fr *FileReader) GetFieldReader(ctx context.Context, i int, 
includedLeaves map[int]bool, rowGroups []int) (*ColumnReader, error) {
+       if i < 0 || i >= len(fr.Manifest.Fields) {
+               return nil, fmt.Errorf("%w: invalid field index chosen %d, 
there are only %d fields", arrow.ErrIndex, i, len(fr.Manifest.Fields))
+       }
+       if err := fr.checkRowGroups(rowGroups); err != nil {
+               return nil, err
+       }
+
        ctx = context.WithValue(ctx, rdrCtxKey{}, readerCtx{
                rdr:            fr.rdr,
                mem:            fr.mem,
@@ -287,6 +294,10 @@ func (fr *FileReader) RowGroup(idx int) RowGroupReader {
 
 // ReadColumn reads data to create a chunked array only from the requested row 
groups.
 func (fr *FileReader) ReadColumn(rowGroups []int, rdr *ColumnReader) 
(*arrow.Chunked, error) {
+       if err := fr.checkRowGroups(rowGroups); err != nil {
+               return nil, err
+       }
+
        recs := int64(0)
        for _, rg := range rowGroups {
                recs += fr.rdr.MetaData().RowGroups[rg].GetNumRows()
@@ -312,7 +323,7 @@ func (fr *FileReader) ReadTable(ctx context.Context) 
(arrow.Table, error) {
 func (fr *FileReader) checkCols(indices []int) (err error) {
        for _, col := range indices {
                if col < 0 || col >= fr.rdr.MetaData().Schema.NumColumns() {
-                       err = fmt.Errorf("invalid column index specified %d out 
of %d", col, fr.rdr.MetaData().Schema.NumColumns())
+                       err = fmt.Errorf("%w: invalid column index specified %d 
out of %d", arrow.ErrIndex, col, fr.rdr.MetaData().Schema.NumColumns())
                        break
                }
        }
@@ -322,7 +333,7 @@ func (fr *FileReader) checkCols(indices []int) (err error) {
 func (fr *FileReader) checkRowGroups(indices []int) (err error) {
        for _, rg := range indices {
                if rg < 0 || rg >= fr.rdr.NumRowGroups() {
-                       err = fmt.Errorf("invalid row group specified: %d, file 
only has %d row groups", rg, fr.rdr.NumRowGroups())
+                       err = fmt.Errorf("%w: invalid row group specified: %d, 
file only has %d row groups", arrow.ErrIndex, rg, fr.rdr.NumRowGroups())
                        break
                }
        }
@@ -452,7 +463,7 @@ func (fr *FileReader) ReadRowGroups(ctx context.Context, 
indices, rowGroups []in
 
 func (fr *FileReader) getColumnReader(ctx context.Context, i int, colFactory 
itrFactory) (*ColumnReader, error) {
        if i < 0 || i >= len(fr.Manifest.Fields) {
-               return nil, fmt.Errorf("invalid column index chosen %d, there 
are only %d columns", i, len(fr.Manifest.Fields))
+               return nil, fmt.Errorf("%w: invalid column index chosen %d, 
there are only %d columns", arrow.ErrIndex, i, len(fr.Manifest.Fields))
        }
 
        ctx = context.WithValue(ctx, rdrCtxKey{}, readerCtx{
diff --git a/parquet/pqarrow/file_reader_test.go 
b/parquet/pqarrow/file_reader_test.go
index 45e0a4f3..16c0c954 100644
--- a/parquet/pqarrow/file_reader_test.go
+++ b/parquet/pqarrow/file_reader_test.go
@@ -606,6 +606,51 @@ func TestFileReaderColumnChunkBoundsErrors(t *testing.T) {
        }
 }
 
+func TestFileReaderIndexValidation(t *testing.T) {
+       schema := arrow.NewSchema([]arrow.Field{{Name: "value", Type: 
arrow.PrimitiveTypes.Int32}}, nil)
+       record, _, err := array.RecordFromJSON(memory.DefaultAllocator, schema,
+               strings.NewReader(`[{"value": 1}]`))
+       require.NoError(t, err)
+       defer record.Release()
+
+       var buf bytes.Buffer
+       writer, err := pqarrow.NewFileWriter(schema, &buf, nil, 
pqarrow.DefaultWriterProps())
+       require.NoError(t, err)
+       require.NoError(t, writer.Write(record))
+       require.NoError(t, writer.Close())
+
+       fileReader, err := file.NewParquetReader(bytes.NewReader(buf.Bytes()))
+       require.NoError(t, err)
+       defer fileReader.Close()
+
+       arrowReader, err := pqarrow.NewFileReader(fileReader, 
pqarrow.ArrowReadProperties{}, memory.DefaultAllocator)
+       require.NoError(t, err)
+
+       _, err = arrowReader.GetFieldReader(context.Background(), -1, nil, 
[]int{0})
+       require.ErrorIs(t, err, arrow.ErrIndex)
+       _, err = arrowReader.GetFieldReader(context.Background(), 1, nil, 
[]int{0})
+       require.ErrorIs(t, err, arrow.ErrIndex)
+       _, err = arrowReader.GetFieldReader(context.Background(), 0, nil, 
[]int{1})
+       require.ErrorIs(t, err, arrow.ErrIndex)
+       _, err = arrowReader.GetFieldReader(context.Background(), 0, nil, 
[]int{-1})
+       require.ErrorIs(t, err, arrow.ErrIndex)
+
+       fieldReader, err := arrowReader.GetFieldReader(context.Background(), 0, 
map[int]bool{0: true}, []int{0})
+       require.NoError(t, err)
+       fieldReader.Release()
+
+       _, err = arrowReader.GetColumn(context.Background(), -1)
+       require.ErrorIs(t, err, arrow.ErrIndex)
+       columnReader, err := arrowReader.GetColumn(context.Background(), 0)
+       require.NoError(t, err)
+       defer columnReader.Release()
+       _, err = arrowReader.ReadColumn([]int{1}, columnReader)
+       require.ErrorIs(t, err, arrow.ErrIndex)
+       chunked, err := arrowReader.ReadColumn([]int{0}, columnReader)
+       require.NoError(t, err)
+       chunked.Release()
+}
+
 func TestReadParquetFile(t *testing.T) {
        dir := os.Getenv("PARQUET_TEST_BAD_DATA")
        if dir == "" {

Reply via email to