-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patharray.go
More file actions
385 lines (344 loc) · 11 KB
/
Copy patharray.go
File metadata and controls
385 lines (344 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
// Copyright (c) 2026 Z5Labs and Contributors
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
package avro
import (
"bytes"
"errors"
"io"
"math"
)
var (
// ErrTruncatedArray is returned when a stream ends before an array's
// terminating zero-count block. It is what an ArrayWriter that was never
// closed leaves behind, so callers can distinguish a truncated array from
// a genuinely empty one.
ErrTruncatedArray = errors.New("avro: truncated array")
// ErrBlockSizeMismatch is returned when the items of a sized block do not
// consume exactly the number of bytes the block declared.
ErrBlockSizeMismatch = errors.New("avro: array block size mismatch")
// ErrArrayWriterClosed is returned by ArrayWriter.Write after the writer
// has been closed.
ErrArrayWriterClosed = errors.New("avro: array writer closed")
)
// Skip reports how [ArrayReader.SkipBlock] handled the current block.
type Skip int
const (
// SkipNone reports that no block remained to skip because the array's
// terminating zero-count block has been reached.
SkipNone Skip = iota
// SkipSized reports that the current block declared its encoded size in
// bytes and so was discarded without decoding any item.
SkipSized
// SkipUnsized reports that the current block did not declare its encoded
// size. Item boundaries inside such a block can only be found by decoding,
// so SkipBlock consumed nothing and the caller must drain the block with
// [ArrayReader.Next] instead.
SkipUnsized
)
func (s Skip) String() string {
switch s {
case SkipNone:
return "none"
case SkipSized:
return "sized"
case SkipUnsized:
return "unsized"
}
return "unknown"
}
// ArrayReader decodes an Avro array one item at a time.
//
// An Avro array is encoded as a series of blocks, each a long item count
// followed by that many items, terminated by a block whose count is zero. A
// negative count is the absolute item count and is followed by a long giving
// the block's encoded size in bytes; see [ArrayReader.SkipBlock].
//
// ArrayReader holds no item state of its own, so memory use is a function of
// the destination passed to [ArrayReader.Next] rather than of the array's
// length.
type ArrayReader struct {
r *BinaryReader
remaining int64 // items left to decode in the current block
blockEnd int64 // reader offset at which the current block ends; only valid when sized
sized bool // whether the current block declared its encoded size
done bool // whether the terminating zero-count block has been read
err error // sticky error
}
// NewArrayReader returns an ArrayReader that decodes an array from r.
func NewArrayReader(r *BinaryReader) *ArrayReader {
return &ArrayReader{r: r}
}
// Next decodes the next item into v. It reports false at the array's
// terminating zero-count block, and keeps reporting false thereafter.
//
// Passing the same v on every call reuses a single destination for the whole
// array; passing a fresh v each time keeps every item. That choice is the
// caller's.
//
// Once Next returns an error, every later call to Next or
// [ArrayReader.SkipBlock] returns that same error.
func (a *ArrayReader) Next(v BinaryUnmarshaler) (bool, error) {
if a.err != nil {
return false, a.err
}
if a.done {
return false, nil
}
if a.remaining == 0 {
ok, err := a.nextBlock()
if err != nil || !ok {
return false, err
}
}
if err := v.UnmarshalAvroBinary(a.r); err != nil {
return false, a.fail(err)
}
a.remaining--
if a.remaining == 0 && a.sized && a.r.Offset() != a.blockEnd {
return false, a.fail(a.r.wrapErr(ErrBlockSizeMismatch))
}
return true, nil
}
// SkipBlock discards the remainder of the current block without decoding it
// and reports which path it took.
//
// It returns [SkipSized] when the block declared its encoded size, in which
// case the remaining bytes are discarded straight from the underlying reader
// and no item is decoded. It returns [SkipUnsized] when the block declared no
// size: nothing is consumed, because finding the block's end would mean
// decoding every remaining item, which is no cheaper than draining the block
// with [ArrayReader.Next]. It returns [SkipNone] once the array's terminating
// zero-count block has been reached.
func (a *ArrayReader) SkipBlock() (Skip, error) {
if a.err != nil {
return SkipNone, a.err
}
if a.done {
return SkipNone, nil
}
if a.remaining == 0 {
ok, err := a.nextBlock()
if err != nil || !ok {
return SkipNone, err
}
}
if !a.sized {
return SkipUnsized, nil
}
switch n := a.blockEnd - a.r.Offset(); {
case n < 0:
return SkipNone, a.fail(a.r.wrapErr(ErrBlockSizeMismatch))
case n > 0:
if err := a.r.discard(n); err != nil {
return SkipNone, a.fail(err)
}
}
a.remaining = 0
return SkipSized, nil
}
// nextBlock reads the header of the next block. It reports false once the
// array's terminating zero-count block has been read.
func (a *ArrayReader) nextBlock() (bool, error) {
start := a.r.Offset()
count, err := a.r.ReadLong()
if err != nil {
// A clean EOF before any byte of the count is an array that was never
// terminated; anywhere else it is a stream cut mid-value.
if a.r.Offset() == start && isEOF(err) {
return false, a.fail(a.r.wrapErr(ErrTruncatedArray))
}
return false, a.fail(unexpectedEOF(err))
}
if count == 0 {
a.done = true
return false, nil
}
a.sized = count < 0
if a.sized {
if count == math.MinInt64 {
return false, a.fail(a.r.wrapErr(ErrOverflow))
}
count = -count
size, err := a.r.ReadLong()
if err != nil {
return false, a.fail(unexpectedEOF(err))
}
if size < 0 {
return false, a.fail(a.r.wrapErr(ErrNegativeLength))
}
a.blockEnd = a.r.Offset() + size
if a.blockEnd < 0 {
return false, a.fail(a.r.wrapErr(ErrOverflow))
}
}
a.remaining = count
return true, nil
}
func (a *ArrayReader) fail(err error) error {
a.err = err
return err
}
func isEOF(err error) bool {
var rerr *BinaryReaderError
return errors.As(err, &rerr) && errors.Is(rerr.Err, io.EOF)
}
// unexpectedEOF rewrites a clean io.EOF into io.ErrUnexpectedEOF, preserving
// the offset it was reported at. Every value an ArrayReader reads for itself is
// required by the array framing, so reaching EOF while reading one is a short
// read rather than a clean end of input.
func unexpectedEOF(err error) error {
var rerr *BinaryReaderError
if errors.As(err, &rerr) && errors.Is(rerr.Err, io.EOF) {
return &BinaryReaderError{Offset: rerr.Offset, Err: io.ErrUnexpectedEOF}
}
return err
}
// DefaultBlockBufferSize is the buffer size [WithSizedBlocks] uses when it is
// given a non-positive size.
const DefaultBlockBufferSize = 64 << 10
type arrayWriterOptions struct {
sized bool
bufferSize int
}
// ArrayWriterOption configures an [ArrayWriter].
type ArrayWriterOption func(*arrayWriterOptions)
// WithSizedBlocks makes an [ArrayWriter] emit sized blocks: each block is
// prefixed with its negated item count and its encoded size in bytes, which
// lets a reader discard the whole block with [ArrayReader.SkipBlock] instead of
// decoding it.
//
// A block's size is only known once the block has been encoded, so this buffers
// items. The buffer is flushed as soon as it reaches bufferSize, and therefore
// holds at most bufferSize plus the encoding of a single item. A non-positive
// bufferSize selects [DefaultBlockBufferSize].
func WithSizedBlocks(bufferSize int) ArrayWriterOption {
return func(o *arrayWriterOptions) {
o.sized = true
o.bufferSize = bufferSize
}
}
// ArrayWriter encodes an Avro array one item at a time.
//
// By default it emits unsized blocks and buffers nothing: each item is written
// straight through as its own block, costing one extra byte per item. Pass
// [WithSizedBlocks] to batch items into sized blocks instead, trading a bounded
// buffer for fewer count prefixes and a stream a reader can skip through.
//
// An array is terminated by a zero-count block, so an ArrayWriter that is never
// closed produces a truncated array rather than a complete one missing a flush.
// [ArrayWriter.Close] must be called, and its error checked; reading such a
// stream back fails with [ErrTruncatedArray]. Use [WriteArray] to have the close
// handled for you.
type ArrayWriter struct {
w *BinaryWriter
buf *bytes.Buffer // pending block; nil when blocks are unsized
bufw *BinaryWriter // writes into buf
limit int // buffered byte count at which the block is flushed
count int64 // items buffered in the pending block
closed bool
err error // sticky error
}
// NewArrayWriter returns an ArrayWriter that encodes an array to w.
func NewArrayWriter(w *BinaryWriter, opts ...ArrayWriterOption) *ArrayWriter {
o := arrayWriterOptions{bufferSize: DefaultBlockBufferSize}
for _, opt := range opts {
opt(&o)
}
a := &ArrayWriter{w: w}
if o.sized {
if o.bufferSize <= 0 {
o.bufferSize = DefaultBlockBufferSize
}
a.limit = o.bufferSize
a.buf = bytes.NewBuffer(make([]byte, 0, o.bufferSize))
a.bufw = NewBinaryWriter(a.buf)
}
return a
}
// Write encodes v as the next item of the array. With sized blocks the item is
// buffered and only reaches the underlying writer when the block is flushed.
//
// Once Write returns an error, every later call to Write or
// [ArrayWriter.Close] returns that same error.
func (a *ArrayWriter) Write(v BinaryMarshaler) error {
if a.err != nil {
return a.err
}
if a.closed {
return ErrArrayWriterClosed
}
if a.buf == nil {
if err := a.w.WriteLong(1); err != nil {
return a.fail(err)
}
if err := v.MarshalAvroBinary(a.w); err != nil {
return a.fail(err)
}
return nil
}
if err := v.MarshalAvroBinary(a.bufw); err != nil {
return a.fail(err)
}
a.count++
if a.buf.Len() >= a.limit {
return a.flush()
}
return nil
}
// Close flushes any pending block and writes the array's terminating
// zero-count block. Closing an already closed ArrayWriter is a no-op.
func (a *ArrayWriter) Close() error {
if a.err != nil {
return a.err
}
if a.closed {
return nil
}
if a.buf != nil {
if err := a.flush(); err != nil {
return err
}
}
if err := a.w.WriteLong(0); err != nil {
return a.fail(err)
}
a.closed = true
return nil
}
// flush writes the pending block, if any, as a sized block.
func (a *ArrayWriter) flush() error {
if a.count == 0 {
return nil
}
if err := a.w.WriteLong(-a.count); err != nil {
return a.fail(err)
}
if err := a.w.WriteLong(int64(a.buf.Len())); err != nil {
return a.fail(err)
}
if err := a.w.WriteFixed(a.buf.Bytes()); err != nil {
return a.fail(err)
}
a.count = 0
a.buf.Reset()
return nil
}
func (a *ArrayWriter) fail(err error) error {
a.err = err
return err
}
// WriteArray encodes an Avro array to w, calling f with the [ArrayWriter] to
// write items to. It closes the writer, terminating the array, once f returns
// without error.
//
// If f returns an error the array is left unterminated, since a partial array
// should not be presented as a complete one.
func WriteArray(w *BinaryWriter, f func(*ArrayWriter) error, opts ...ArrayWriterOption) error {
a := NewArrayWriter(w, opts...)
if err := f(a); err != nil {
return err
}
return a.Close()
}