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 4f42a9e0 fix(arrow/ipc): return errors for invalid record indexes
(#1120)
4f42a9e0 is described below
commit 4f42a9e05d17c6c45ac05301d28423b899d63e37
Author: Minh Vu <[email protected]>
AuthorDate: Mon Aug 10 19:58:12 2026 +0200
fix(arrow/ipc): return errors for invalid record indexes (#1120)
### Rationale for this change
RecordBatchAt panics for an invalid record index even though it returns
an error. ReadAt also converts an int64 index to int before checking the
range.
### What changes are included in this PR?
Return an arrow.ErrIndex error for invalid indexes in RecordBatchAt and
ReadAt, and check the int64 range before narrowing it to int.
### Are these changes tested?
- `go test ./arrow/ipc`
### Are there any user-facing changes?
Invalid record indexes now return errors instead of panicking.
---
arrow/ipc/file_reader.go | 5 ++++-
arrow/ipc/ipc_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/arrow/ipc/file_reader.go b/arrow/ipc/file_reader.go
index abf2a70f..c9e1f00a 100644
--- a/arrow/ipc/file_reader.go
+++ b/arrow/ipc/file_reader.go
@@ -445,7 +445,7 @@ func (f *FileReader) Record(i int) (arrow.Record, error) {
// call concurrently.
func (f *FileReader) RecordBatchAt(i int) (arrow.RecordBatch, error) {
if i < 0 || i >= f.NumRecords() {
- panic("arrow/ipc: record index out of bounds")
+ return nil, fmt.Errorf("%w: record index %d out of bounds",
arrow.ErrIndex, i)
}
blk, err := f.r.block(f.mem, &f.footer, i)
@@ -509,6 +509,9 @@ func (f *FileReader) Read() (rec arrow.RecordBatch, err
error) {
// ReadAt reads the i-th record batch from the underlying stream and an error,
if any.
func (f *FileReader) ReadAt(i int64) (arrow.RecordBatch, error) {
+ if i < 0 || i >= int64(f.NumRecords()) {
+ return nil, fmt.Errorf("%w: record index %d out of bounds",
arrow.ErrIndex, i)
+ }
return f.RecordBatch(int(i))
}
diff --git a/arrow/ipc/ipc_test.go b/arrow/ipc/ipc_test.go
index b7e006ba..895e70d8 100644
--- a/arrow/ipc/ipc_test.go
+++ b/arrow/ipc/ipc_test.go
@@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"io"
+ "math"
"math/rand"
"os"
"strconv"
@@ -802,6 +803,48 @@ func TestRecordBatchCustomMetadataFileRoundtrip(t
*testing.T) {
require.Equal(t, meta.Values(), rm.Metadata().Values())
}
+func TestFileReaderRecordBatchIndexErrors(t *testing.T) {
+ mem := memory.NewGoAllocator()
+ schema := arrow.NewSchema(
+ []arrow.Field{{Name: "x", Type: arrow.PrimitiveTypes.Int32}},
+ nil,
+ )
+
+ builder := array.NewInt32Builder(mem)
+ builder.Append(1)
+ column := builder.NewArray()
+ builder.Release()
+ record := array.NewRecordBatch(schema, []arrow.Array{column}, 1)
+ column.Release()
+
+ var buf bytes.Buffer
+ writer, err := ipc.NewFileWriter(&buf, ipc.WithSchema(schema))
+ require.NoError(t, err)
+ require.NoError(t, writer.Write(record))
+ require.NoError(t, writer.Close())
+ record.Release()
+
+ reader, err := ipc.NewFileReader(bytes.NewReader(buf.Bytes()))
+ require.NoError(t, err)
+ defer reader.Close()
+
+ _, err = reader.RecordBatchAt(-1)
+ require.ErrorIs(t, err, arrow.ErrIndex)
+ _, err = reader.RecordBatchAt(reader.NumRecords())
+ require.ErrorIs(t, err, arrow.ErrIndex)
+
+ _, err = reader.ReadAt(-1)
+ require.ErrorIs(t, err, arrow.ErrIndex)
+ _, err = reader.ReadAt(math.MaxInt64)
+ require.ErrorIs(t, err, arrow.ErrIndex)
+ _, err = reader.ReadAt(int64(reader.NumRecords()))
+ require.ErrorIs(t, err, arrow.ErrIndex)
+
+ batch, err := reader.RecordBatchAt(0)
+ require.NoError(t, err)
+ batch.Release()
+}
+
func TestRecordBatchCustomMetadataInterop(t *testing.T) {
t.Run("file", func(t *testing.T) {
f, err := os.Open("testdata/custom_metadata.arrow")