Skip to content

Commit 1f2d896

Browse files
pwallerSergey Kamardin
authored and
Sergey Kamardin
committed
Add (*Reader).CopyNext(w) (int64, error) (tinylib#167)
* Add (*Reader).CopyNext(w) (int64, error) It is useful to be able to efficiently copy objects without decoding them. My use case is filtering when I already know the indices of the objects I want to keep, and for rewriting a dictionary of objects as a column of objects. * Remove ReadNext * Add opportunistic optimization with m.R.Next * Remove unused ReadNextError * Remove commented code * small fixup - only call (*Reader).Next() when we're sure it won't realloc its buffer - promote io.ErrUnexpectedEOF to msgp.ErrShortBytes
1 parent 8eedca8 commit 1f2d896

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

msgp/read.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,56 @@ func (m *Reader) Read(p []byte) (int, error) {
146146
return m.R.Read(p)
147147
}
148148

149+
// CopyNext reads the next object from m without decoding it and writes it to w.
150+
// It avoids unnecessary copies internally.
151+
func (m *Reader) CopyNext(w io.Writer) (int64, error) {
152+
sz, o, err := getNextSize(m.R)
153+
if err != nil {
154+
return 0, err
155+
}
156+
157+
var n int64
158+
// Opportunistic optimization: if we can fit the whole thing in the m.R
159+
// buffer, then just get a pointer to that, and pass it to w.Write,
160+
// avoiding an allocation.
161+
if int(sz) <= m.R.BufferSize() {
162+
var nn int
163+
var buf []byte
164+
buf, err = m.R.Next(int(sz))
165+
if err != nil {
166+
if err == io.ErrUnexpectedEOF {
167+
err = ErrShortBytes
168+
}
169+
return 0, err
170+
}
171+
nn, err = w.Write(buf)
172+
n += int64(nn)
173+
} else {
174+
// Fall back to io.CopyN.
175+
// May avoid allocating if w is a ReaderFrom (e.g. bytes.Buffer)
176+
n, err = io.CopyN(w, m.R, int64(sz))
177+
if err == io.ErrUnexpectedEOF {
178+
err = ErrShortBytes
179+
}
180+
}
181+
if err != nil {
182+
return n, err
183+
} else if n < int64(sz) {
184+
return n, io.ErrShortWrite
185+
}
186+
187+
// for maps and slices, read elements
188+
for x := uintptr(0); x < o; x++ {
189+
var n2 int64
190+
n2, err = m.CopyNext(w)
191+
if err != nil {
192+
return n, err
193+
}
194+
n += n2
195+
}
196+
return n, nil
197+
}
198+
149199
// ReadFull implements `io.ReadFull`
150200
func (m *Reader) ReadFull(p []byte) (int, error) {
151201
return m.R.ReadFull(p)
@@ -194,8 +244,10 @@ func (m *Reader) IsNil() bool {
194244
return err == nil && p[0] == mnil
195245
}
196246

247+
// getNextSize returns the size of the next object on the wire.
197248
// returns (obj size, obj elements, error)
198249
// only maps and arrays have non-zero obj elements
250+
// for maps and arrays, obj size does not include elements
199251
//
200252
// use uintptr b/c it's guaranteed to be large enough
201253
// to hold whatever we can fit in memory.

msgp/read_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,3 +745,48 @@ func BenchmarkSkip(b *testing.B) {
745745
}
746746
}
747747
}
748+
749+
func TestCopyNext(t *testing.T) {
750+
var buf bytes.Buffer
751+
en := NewWriter(&buf)
752+
753+
en.WriteMapHeader(6)
754+
755+
en.WriteString("thing_one")
756+
en.WriteString("value_one")
757+
758+
en.WriteString("thing_two")
759+
en.WriteFloat64(3.14159)
760+
761+
en.WriteString("some_bytes")
762+
en.WriteBytes([]byte("nkl4321rqw908vxzpojnlk2314rqew098-s09123rdscasd"))
763+
764+
en.WriteString("the_time")
765+
en.WriteTime(time.Now())
766+
767+
en.WriteString("what?")
768+
en.WriteBool(true)
769+
770+
en.WriteString("ext")
771+
en.WriteExtension(&RawExtension{Type: 55, Data: []byte("raw data!!!")})
772+
773+
en.Flush()
774+
775+
// Read from a copy of the original buf.
776+
de := NewReader(bytes.NewReader(buf.Bytes()))
777+
778+
w := new(bytes.Buffer)
779+
780+
n, err := de.CopyNext(w)
781+
if err != nil {
782+
t.Fatal(err)
783+
}
784+
if n != int64(buf.Len()) {
785+
t.Fatalf("CopyNext returned the wrong value (%d != %d)",
786+
n, buf.Len())
787+
}
788+
789+
if !bytes.Equal(buf.Bytes(), w.Bytes()) {
790+
t.Fatalf("not equal! %v, %v", buf.Bytes(), w.Bytes())
791+
}
792+
}

0 commit comments

Comments
 (0)