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 e1faf3da fix(arrow/ipc): reject non-dictionary initial messages (#1054)
e1faf3da is described below
commit e1faf3dacc626308b35ed514d928b68fe54d25bb
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 5 20:08:27 2026 +0200
fix(arrow/ipc): reject non-dictionary initial messages (#1054)
### Rationale for this change
When an IPC schema declares dictionaries, the reader expects dictionary
batches before records. It detected an unexpected message but continued
into `readDictionary`, which could replace the useful protocol error and
consume the wrong message.
### What changes are included in this PR?
Stop immediately, mark the reader done, and preserve the
expected-dictionary error.
### Are these changes tested?
Yes. A regression test provides a dictionary schema followed directly by
a record batch.
`go test ./arrow/ipc -run
TestReaderRejectsRecordBatchBeforeInitialDictionary`
### Are there any user-facing changes?
Malformed streams now fail deterministically with the specific
dictionary-ordering error.
---
arrow/ipc/reader.go | 4 ++++
arrow/ipc/writer_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/arrow/ipc/reader.go b/arrow/ipc/reader.go
index df9e5000..dc035564 100644
--- a/arrow/ipc/reader.go
+++ b/arrow/ipc/reader.go
@@ -224,6 +224,7 @@ func (r *Reader) getInitialDicts() bool {
if msg.Type() != MessageDictionaryBatch {
r.err = fmt.Errorf("arrow/ipc: IPC stream did not have
the expected (%d) dictionaries at the start of the stream", numDicts)
+ return false
}
if _, err := readDictionary(&r.memo, msg.meta, msg.body,
r.swapEndianness, r.mem); err != nil {
r.done = true
@@ -241,6 +242,9 @@ func (r *Reader) next() bool {
r.err = utils.FormatRecoveredError("arrow/ipc: unknown
error while reading", pErr)
}
}()
+ if r.err != nil || r.done {
+ return false
+ }
if r.schema == nil {
if err := r.readSchema(r.expectedSchema); err != nil {
r.err = fmt.Errorf("arrow/ipc: could not read schema
from stream: %w", err)
diff --git a/arrow/ipc/writer_test.go b/arrow/ipc/writer_test.go
index decef53a..6de7ee0d 100644
--- a/arrow/ipc/writer_test.go
+++ b/arrow/ipc/writer_test.go
@@ -359,6 +359,50 @@ func TestWritePayload(t *testing.T) {
require.True(t, msg.Type() == MessageRecordBatch)
}
+func TestReaderRejectsRecordBatchBeforeInitialDictionary(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ dictType := &arrow.DictionaryType{
+ IndexType: arrow.PrimitiveTypes.Int8,
+ ValueType: arrow.BinaryTypes.String,
+ }
+ schema := arrow.NewSchema([]arrow.Field{{Name: "dict", Type:
dictType}}, nil)
+ bldr := array.NewBuilder(mem, dictType)
+ defer bldr.Release()
+ require.NoError(t, bldr.UnmarshalJSON([]byte(`["value"]`)))
+ arr := bldr.NewArray()
+ defer arr.Release()
+ rec := array.NewRecordBatch(schema, []arrow.Array{arr}, 1)
+ defer rec.Release()
+
+ var stream bytes.Buffer
+ schemaPayload := GetSchemaPayload(schema, mem)
+ defer schemaPayload.Release()
+ _, err := schemaPayload.WritePayload(&stream)
+ require.NoError(t, err)
+ recordPayload, err := GetRecordBatchPayload(rec, WithAllocator(mem))
+ require.NoError(t, err)
+ defer recordPayload.Release()
+ _, err = recordPayload.WritePayload(&stream)
+ require.NoError(t, err)
+ streamBytes := append([]byte(nil), stream.Bytes()...)
+
+ rdr, err := NewReader(bytes.NewReader(streamBytes), WithAllocator(mem))
+ require.NoError(t, err)
+ defer rdr.Release()
+ require.False(t, rdr.Next())
+ require.EqualError(t, rdr.Err(), "arrow/ipc: IPC stream did not have
the expected (1) dictionaries at the start of the stream")
+
+ rdr, err = NewReader(bytes.NewReader(streamBytes), WithAllocator(mem))
+ require.NoError(t, err)
+ defer rdr.Release()
+ _, err = rdr.Read()
+ require.EqualError(t, err, "arrow/ipc: IPC stream did not have the
expected (1) dictionaries at the start of the stream")
+ _, err = rdr.Read()
+ require.EqualError(t, err, "arrow/ipc: IPC stream did not have the
expected (1) dictionaries at the start of the stream")
+}
+
// TestVariadicCountsNotAccumulatedAcrossEncode verifies that variadicCounts
// does not accumulate across encode calls separated by reset(). Without this,
// each batch's variadic counts would include counts from previous batches,