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 2b2e5c70 fix(arrow/array): skip empty table chunks (#1023)
2b2e5c70 is described below
commit 2b2e5c7040e5c65b85fadb90284acad8508eed94
Author: Minh Vu <[email protected]>
AuthorDate: Mon Jul 27 22:30:48 2026 +0200
fix(arrow/array): skip empty table chunks (#1023)
### Rationale for this change
TableReader treats an empty column chunk as the next batch boundary. A
valid table with an empty chunk therefore emits a zero-row record batch
before returning its actual rows.
### What changes are included in this PR?
* Advance each column past empty chunks before calculating the next
batch size.
* Keep chunk slots synchronized without exposing zero-row batches.
### Are these changes tested?
Yes. The regression test builds a valid chunked column with empty chunks
and verifies that only the non-empty batch is returned. The full
arrow/array package, assertion build, compute packages, and IPC package
also pass.
---
arrow/array/table.go | 5 +++++
arrow/array/table_test.go | 33 +++++++++++++++++++++++++++++++++
2 files changed, 38 insertions(+)
diff --git a/arrow/array/table.go b/arrow/array/table.go
index 6bd44045..92d265e0 100644
--- a/arrow/array/table.go
+++ b/arrow/array/table.go
@@ -353,6 +353,11 @@ func (tr *TableReader) Next() bool {
for i := range chunks {
j := tr.slots[i]
chunk := tr.chunks[i].Chunk(j)
+ for chunk.Len() == 0 && j+1 < len(tr.chunks[i].Chunks()) {
+ j++
+ tr.slots[i] = j
+ chunk = tr.chunks[i].Chunk(j)
+ }
remain := int64(chunk.Len()) - tr.offsets[i]
if remain < chunksz {
chunksz = remain
diff --git a/arrow/array/table_test.go b/arrow/array/table_test.go
index 110b1c7c..08fea1a8 100644
--- a/arrow/array/table_test.go
+++ b/arrow/array/table_test.go
@@ -845,6 +845,39 @@ func TestTableReader(t *testing.T) {
}
}
+func TestTableReaderSkipsEmptyChunks(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+ defer mem.AssertSize(t, 0)
+
+ builder := array.NewInt32Builder(mem)
+ empty := builder.NewInt32Array()
+ defer empty.Release()
+ builder.AppendValues([]int32{1, 2}, nil)
+ values := builder.NewInt32Array()
+ defer values.Release()
+ builder.Release()
+
+ chunked := arrow.NewChunked(arrow.PrimitiveTypes.Int32,
[]arrow.Array{empty, values, empty})
+ defer chunked.Release()
+ field := arrow.Field{Name: "values", Type: arrow.PrimitiveTypes.Int32}
+ column := arrow.NewColumn(field, chunked)
+ defer column.Release()
+ table := array.NewTable(arrow.NewSchema([]arrow.Field{field}, nil),
[]arrow.Column{*column}, -1)
+ defer table.Release()
+
+ reader := array.NewTableReader(table, 10)
+ defer reader.Release()
+ if !reader.Next() {
+ t.Fatal("expected a record batch")
+ }
+ if got, want := reader.RecordBatch().NumRows(), int64(2); got != want {
+ t.Fatalf("invalid number of rows: got=%d, want=%d", got, want)
+ }
+ if reader.Next() {
+ t.Fatal("unexpected additional record batch")
+ }
+}
+
func TestTableToString(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)