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 0420dc80 fix(parquet/metadata): handle invalid row-group column 
indexes (#1125)
0420dc80 is described below

commit 0420dc80c1486535068ca7c4fe4f7ee983f5d2d6
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 12 18:06:06 2026 +0200

    fix(parquet/metadata): handle invalid row-group column indexes (#1125)
    
    ### Rationale for this change
    
    RowGroupMetaData.ColumnChunk returns an error but currently panics for
    invalid indexes. The column and offset index location helpers also index
    the underlying Thrift slice directly.
    
    ### What changes are included in this PR?
    
    Return an arrow.ErrIndex error from ColumnChunk for invalid indexes, and
    return false from the location helpers when the index is outside the
    row-group range.
    
    ### Are these changes tested?
    
    - `go test ./parquet/metadata`
    
    ### Are there any user-facing changes?
    
    Invalid row-group column indexes are handled as normal errors or missing
    locations instead of panics.
    
    ---------
    
    Co-authored-by: Matt Topol <[email protected]>
---
 parquet/metadata/row_group.go             | 11 ++++-
 parquet/metadata/row_group_bounds_test.go | 79 +++++++++++++++++++++++++++++++
 2 files changed, 88 insertions(+), 2 deletions(-)

diff --git a/parquet/metadata/row_group.go b/parquet/metadata/row_group.go
index b578e343..1c698fc8 100644
--- a/parquet/metadata/row_group.go
+++ b/parquet/metadata/row_group.go
@@ -20,6 +20,7 @@ import (
        "fmt"
        "reflect"
 
+       "github.com/apache/arrow-go/v18/arrow"
        "github.com/apache/arrow-go/v18/parquet"
        "github.com/apache/arrow-go/v18/parquet/internal/encryption"
        format "github.com/apache/arrow-go/v18/parquet/internal/gen-go/parquet"
@@ -79,8 +80,8 @@ func (r *RowGroupMetaData) Ordinal() int16 { return 
r.rowGroup.GetOrdinal() }
 
 // ColumnChunk returns the metadata for the requested (0-based) chunk index
 func (r *RowGroupMetaData) ColumnChunk(i int) (*ColumnChunkMetaData, error) {
-       if i >= r.NumColumns() {
-               panic(fmt.Errorf("parquet: the file only has %d columns, 
requested metadata for column: %d", r.NumColumns(), i))
+       if i < 0 || i >= r.NumColumns() {
+               return nil, fmt.Errorf("%w: parquet file only has %d columns, 
requested metadata for column %d", arrow.ErrIndex, r.NumColumns(), i)
        }
 
        return NewColumnChunkMetaData(r.rowGroup.Columns[i], 
r.Schema.Column(i), r.version, r.rowGroup.GetOrdinal(), int16(i), 
r.fileDecryptor)
@@ -95,6 +96,9 @@ func (r *RowGroupMetaData) SortingColumns() 
[]parquet.SortingColumn {
 // directly from the underlying thrift struct, avoiding the overhead of
 // constructing a full ColumnChunkMetaData.
 func (r *RowGroupMetaData) ColumnIndexLocation(i int) (IndexLocation, bool) {
+       if i < 0 || i >= r.NumColumns() {
+               return IndexLocation{}, false
+       }
        col := r.rowGroup.Columns[i]
        if col.IsSetColumnIndexOffset() {
                return IndexLocation{
@@ -109,6 +113,9 @@ func (r *RowGroupMetaData) ColumnIndexLocation(i int) 
(IndexLocation, bool) {
 // directly from the underlying thrift struct, avoiding the overhead of
 // constructing a full ColumnChunkMetaData.
 func (r *RowGroupMetaData) OffsetIndexLocation(i int) (IndexLocation, bool) {
+       if i < 0 || i >= r.NumColumns() {
+               return IndexLocation{}, false
+       }
        col := r.rowGroup.Columns[i]
        if col.IsSetOffsetIndexOffset() {
                return IndexLocation{
diff --git a/parquet/metadata/row_group_bounds_test.go 
b/parquet/metadata/row_group_bounds_test.go
new file mode 100644
index 00000000..489777e3
--- /dev/null
+++ b/parquet/metadata/row_group_bounds_test.go
@@ -0,0 +1,79 @@
+// 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 metadata_test
+
+import (
+       "testing"
+
+       "github.com/apache/arrow-go/v18/arrow"
+       "github.com/apache/arrow-go/v18/parquet"
+       format "github.com/apache/arrow-go/v18/parquet/internal/gen-go/parquet"
+       "github.com/apache/arrow-go/v18/parquet/metadata"
+       "github.com/apache/arrow-go/v18/parquet/schema"
+       "github.com/stretchr/testify/require"
+)
+
+func TestRowGroupColumnIndexBounds(t *testing.T) {
+       columnIndexOffset, columnIndexLength := int64(100), int32(10)
+       offsetIndexOffset, offsetIndexLength := int64(200), int32(20)
+       primitive := schema.Must(schema.NewPrimitiveNode(
+               "value", parquet.Repetitions.Required, parquet.Types.Int32, -1, 
-1,
+       ))
+       parquetSchema := schema.MustGroup(schema.NewGroupNode(
+               "schema", parquet.Repetitions.Required, 
schema.FieldList{primitive}, -1,
+       ))
+       rowGroup := metadata.NewRowGroupMetaData(
+               &format.RowGroup{Columns: []*format.ColumnChunk{{
+                       MetaData: &format.ColumnMetaData{
+                               Type:         format.Type_INT32,
+                               Encodings:    
[]format.Encoding{format.Encoding_PLAIN},
+                               PathInSchema: []string{"value"},
+                       },
+                       ColumnIndexOffset: &columnIndexOffset,
+                       ColumnIndexLength: &columnIndexLength,
+                       OffsetIndexOffset: &offsetIndexOffset,
+                       OffsetIndexLength: &offsetIndexLength,
+               }}},
+               schema.NewSchema(parquetSchema),
+               nil,
+               nil,
+       )
+
+       _, err := rowGroup.ColumnChunk(-1)
+       require.ErrorIs(t, err, arrow.ErrIndex)
+       _, err = rowGroup.ColumnChunk(1)
+       require.ErrorIs(t, err, arrow.ErrIndex)
+       column, err := rowGroup.ColumnChunk(0)
+       require.NoError(t, err)
+       require.NotNil(t, column)
+
+       _, ok := rowGroup.ColumnIndexLocation(-1)
+       require.False(t, ok)
+       _, ok = rowGroup.ColumnIndexLocation(1)
+       require.False(t, ok)
+       location, ok := rowGroup.ColumnIndexLocation(0)
+       require.True(t, ok)
+       require.Equal(t, metadata.IndexLocation{Offset: columnIndexOffset, 
Length: columnIndexLength}, location)
+
+       _, ok = rowGroup.OffsetIndexLocation(-1)
+       require.False(t, ok)
+       _, ok = rowGroup.OffsetIndexLocation(1)
+       require.False(t, ok)
+       location, ok = rowGroup.OffsetIndexLocation(0)
+       require.True(t, ok)
+       require.Equal(t, metadata.IndexLocation{Offset: offsetIndexOffset, 
Length: offsetIndexLength}, location)
+}

Reply via email to