Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Encode methods accept []byte #36

Draft
wants to merge 50 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
cd91cc0
wip
noisersup Sep 4, 2024
0768049
wip
noisersup Sep 4, 2024
9e1e3ad
wip
noisersup Sep 4, 2024
ffbce94
keep
noisersup Sep 4, 2024
05fd355
Revert "keep"
noisersup Sep 4, 2024
c24cdf9
wip
noisersup Sep 4, 2024
9aa72ad
cap
noisersup Sep 6, 2024
116bd79
oof
noisersup Sep 6, 2024
0ba5399
wip
noisersup Sep 6, 2024
4388911
wip
noisersup Sep 6, 2024
6d3425d
wip
noisersup Sep 6, 2024
72494e2
wip
noisersup Sep 7, 2024
d1ebbaf
wip
noisersup Sep 7, 2024
c181cd3
wip
noisersup Sep 7, 2024
dd0a228
wip
noisersup Sep 9, 2024
99820cb
wip
noisersup Sep 11, 2024
7d65b15
revert
noisersup Sep 12, 2024
b26d225
Merge branch 'doc-encode-byte-21-2' into doc-encode-byte-21
noisersup Sep 12, 2024
1346fe2
wip
noisersup Sep 12, 2024
a08e5a1
wip
noisersup Sep 12, 2024
dcf1b1b
wip
noisersup Sep 12, 2024
5f28ffc
wip
noisersup Sep 12, 2024
9546de2
wip
noisersup Sep 12, 2024
b932258
wip
noisersup Sep 12, 2024
702798a
wip
noisersup Sep 13, 2024
f5d82af
write directly to subslice
noisersup Sep 13, 2024
7076440
wip
noisersup Sep 13, 2024
1155ef2
wip
noisersup Sep 13, 2024
076b324
remove writeByte
noisersup Sep 13, 2024
7d115af
refactor write
noisersup Sep 13, 2024
20e8069
wip
noisersup Sep 13, 2024
5e5e73e
wip
noisersup Sep 13, 2024
5d45057
convert array
noisersup Sep 13, 2024
73836e3
test
noisersup Sep 13, 2024
5ec1fc4
bench
noisersup Sep 13, 2024
7f13077
wip
noisersup Sep 13, 2024
8dd7397
wip
noisersup Sep 13, 2024
1a4d945
wip
noisersup Sep 13, 2024
f6151af
wip
noisersup Sep 13, 2024
70c05bb
wip
noisersup Sep 13, 2024
c84c84a
less error
noisersup Sep 13, 2024
c87f7ab
simplify test
noisersup Sep 13, 2024
b7510cd
wip
noisersup Sep 13, 2024
5aa5871
wip
noisersup Sep 13, 2024
b231449
Merge remote-tracking branch 'upstream/main' into doc-encode-byte-21
noisersup Sep 13, 2024
b67c25b
test
noisersup Sep 13, 2024
28ccc75
fix bench
noisersup Sep 13, 2024
fa03706
Merge remote-tracking branch 'upstream/main' into doc-encode-byte-21
noisersup Sep 16, 2024
8620b82
Merge branch 'main' into doc-encode-byte-21
AlekSi Sep 19, 2024
e9b08a7
Add notes
AlekSi Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ tasks:
desc: "Run benchmarks"
cmds:
- go test -list='Benchmark.*' ./...
- go test -count=10 -bench=BenchmarkDocument -benchtime={{.BENCH_TIME}} ./wirebson | tee -a new.txt
# -timeout is needed due to https://github.com/golang/go/issues/69181
- go test -count=10 -bench=BenchmarkDocument -benchtime={{.BENCH_TIME}} -timeout=60m ./wirebson | tee -a new.txt
- bin/benchstat old.txt new.txt

fuzz:
Expand Down
3 changes: 2 additions & 1 deletion op_msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ type OpMsg struct {

// NewOpMsg creates a message with a single section of kind 0 with a single document.
func NewOpMsg(doc wirebson.AnyDocument) (*OpMsg, error) {
raw, err := doc.Encode()
raw := make([]byte, wirebson.Size(doc))
err := doc.Encode(raw)
if err != nil {
return nil, lazyerrors.Error(err)
}
Expand Down
3 changes: 2 additions & 1 deletion op_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ type OpQuery struct {

// NewOpQuery creates a new OpQuery message.
func NewOpQuery(doc wirebson.AnyDocument) (*OpQuery, error) {
raw, err := doc.Encode()
raw := make([]byte, wirebson.Size(doc))
err := doc.Encode(raw)
if err != nil {
return nil, lazyerrors.Error(err)
}
Expand Down
7 changes: 5 additions & 2 deletions op_reply.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ type OpReply struct {

// NewOpReply creates a new OpReply message.
func NewOpReply(doc wirebson.AnyDocument) (*OpReply, error) {
raw, err := doc.Encode()
raw := make([]byte, wirebson.Size(doc))
err := doc.Encode(raw)
if err != nil {
return nil, lazyerrors.Error(err)
}
Expand Down Expand Up @@ -132,7 +133,9 @@ func (reply *OpReply) RawDocument() wirebson.RawDocument {
// SetDocument sets reply document.
func (reply *OpReply) SetDocument(doc *wirebson.Document) {
var err error
reply.document, err = doc.Encode()

reply.document = make([]byte, wirebson.Size(doc))
err = doc.Encode(reply.document)
if err != nil {
panic(err)
}
Expand Down
4 changes: 3 additions & 1 deletion wire_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ func TestMain(m *testing.M) {
func makeRawDocument(pairs ...any) wirebson.RawDocument {
d := wirebson.MustDocument(pairs...)

raw, err := d.Encode()
raw := make([]byte, wirebson.Size(d))

err := d.Encode(raw)
if err != nil {
panic(err)
}
Expand Down
33 changes: 15 additions & 18 deletions wirebson/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package wirebson

import (
"bytes"
"encoding/binary"
"iter"
"log/slog"
Expand Down Expand Up @@ -149,32 +148,30 @@ func (arr *Array) SortInterface(less func(a, b any) bool) sort.Interface {
}
}

// Encode encodes non-nil Array.
// Encode encodes Array v into raw.
//
// TODO https://github.com/FerretDB/wire/issues/21
// This method should accept a slice of bytes, not return it.
// That would allow to avoid unnecessary allocations.
func (arr *Array) Encode() (RawArray, error) {
// raw must be at least Size(arr) bytes long; otherwise, Encode will panic.
// Only raw[0:Size(arr)] bytes are modified.
func (arr *Array) Encode(raw RawArray) error {
must.NotBeZero(arr)

size := sizeArray(arr)
buf := bytes.NewBuffer(make([]byte, 0, size))
// ensure raw length early
s := sizeArray(arr)
raw[s-1] = 0

if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil {
return nil, lazyerrors.Error(err)
}
binary.LittleEndian.PutUint32(raw, uint32(s))

for i, v := range arr.values {
if err := encodeField(buf, strconv.Itoa(i), v); err != nil {
return nil, lazyerrors.Error(err)
i := 4
for n, v := range arr.values {
w, err := encodeField(raw[i:], strconv.Itoa(n), v)
if err != nil {
return lazyerrors.Error(err)
}
}

if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil {
return nil, lazyerrors.Error(err)
i += w
}

return buf.Bytes(), nil
return nil
}

// Decode returns itself to implement [AnyArray].
Expand Down
4 changes: 2 additions & 2 deletions wirebson/bson.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ type ScalarType interface {
// Note that the Encode and Decode methods could return the receiver itself,
// so care must be taken when results are modified.
type AnyDocument interface {
Encode() (RawDocument, error)
Encode(RawDocument) error
Decode() (*Document, error)
LogMessage() string
LogMessageIndent() string
Expand All @@ -81,7 +81,7 @@ type AnyDocument interface {
// Note that the Encode and Decode methods could return the receiver itself,
// so care must be taken when results are modified.
type AnyArray interface {
Encode() (RawArray, error)
Encode(RawArray) error
Decode() (*Array, error)
LogMessage() string
LogMessageIndent() string
Expand Down
18 changes: 12 additions & 6 deletions wirebson/bson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,8 @@ func TestNormal(t *testing.T) {
assert.NotEmpty(t, doc.LogMessage())
assert.NotEmpty(t, doc.LogMessageIndent())

raw, err := doc.Encode()
raw := make(RawDocument, Size(doc))
err = doc.Encode(raw)
require.NoError(t, err)
assert.Equal(t, tc.raw, raw)
})
Expand All @@ -783,7 +784,8 @@ func TestNormal(t *testing.T) {
assert.NotEmpty(t, doc.LogMessage())
assert.Equal(t, testutil.Unindent(tc.b), doc.LogMessageIndent())

raw, err := doc.Encode()
raw := make(RawDocument, Size(doc))
err = doc.Encode(raw)
require.NoError(t, err)
assert.Equal(t, tc.raw, raw)
})
Expand Down Expand Up @@ -866,8 +868,9 @@ func BenchmarkDocument(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()

raw = make(RawDocument, Size(doc))
for range b.N {
raw, err = doc.Encode()
err = doc.Encode(raw)
}

b.StopTimer()
Expand Down Expand Up @@ -946,8 +949,9 @@ func BenchmarkDocument(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()

raw = make(RawDocument, Size(doc))
for range b.N {
raw, err = doc.Encode()
err = doc.Encode(raw)
}

b.StopTimer()
Expand Down Expand Up @@ -1041,7 +1045,8 @@ func testRawDocument(t *testing.T, rawDoc RawDocument) {
assert.NotEmpty(t, doc.LogMessage())
assert.NotEmpty(t, doc.LogMessageIndent())

raw, err := doc.Encode()
raw := make(RawDocument, Size(doc))
err = doc.Encode(raw)
if err == nil {
assert.Equal(t, rawDoc, raw)
}
Expand All @@ -1060,7 +1065,8 @@ func testRawDocument(t *testing.T, rawDoc RawDocument) {
assert.NotEmpty(t, doc.LogMessage())
assert.NotEmpty(t, doc.LogMessageIndent())

raw, err := doc.Encode()
raw := make(RawDocument, Size(doc))
err = doc.Encode(raw)
require.NoError(t, err)
assert.Equal(t, rawDoc, raw)
})
Expand Down
29 changes: 13 additions & 16 deletions wirebson/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package wirebson

import (
"bytes"
"encoding/binary"
"iter"
"log/slog"
Expand Down Expand Up @@ -227,30 +226,28 @@ func (doc *Document) Command() string {

// Encode encodes non-nil Document.
//
// TODO https://github.com/FerretDB/wire/issues/21
// This method should accept a slice of bytes, not return it.
// That would allow to avoid unnecessary allocations.
func (doc *Document) Encode() (RawDocument, error) {
// raw must be at least Size(doc) bytes long; otherwise, Encode will panic.
// Only raw[0:Size(doc)] bytes are modified.
func (doc *Document) Encode(raw RawDocument) error {
must.NotBeZero(doc)

size := sizeDocument(doc)
buf := bytes.NewBuffer(make([]byte, 0, size))
// ensure raw length early
s := sizeDocument(doc)
raw[s-1] = 0

if err := binary.Write(buf, binary.LittleEndian, uint32(size)); err != nil {
return nil, lazyerrors.Error(err)
}
binary.LittleEndian.PutUint32(raw, uint32(s))

i := 4
for _, f := range doc.fields {
if err := encodeField(buf, f.name, f.value); err != nil {
return nil, lazyerrors.Error(err)
w, err := encodeField(raw[i:], f.name, f.value)
if err != nil {
return lazyerrors.Error(err)
}
}

if err := binary.Write(buf, binary.LittleEndian, byte(0)); err != nil {
return nil, lazyerrors.Error(err)
i += w
}

return buf.Bytes(), nil
return nil
}

// Decode returns itself to implement [AnyDocument].
Expand Down
Loading
Loading