Skip to content

Commit 64bf689

Browse files
authored
Tolerate missing ns.coll in change event. (#149)
Missing `ns.coll` can happen if, e.g., there’s a `dropDatabase` event. This changeset tolerates that. (We don’t support such events on the source, but on the destination we ignore them.) The fix rewrites the UnmarshalBSON method to avoid LookupErr. Pprof shows it slightly more efficient to do things this way, and it better mimics the driver’s own tolerance missing fields when unmarshaling.
1 parent d98f02c commit 64bf689

File tree

3 files changed

+121
-7
lines changed

3 files changed

+121
-7
lines changed

internal/verifier/util.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,29 @@ func (ns *Namespace) FullName() string {
6767
// avoid reflection, which can substantially impede performance in “hot”
6868
// code paths like this.
6969
func (ns *Namespace) UnmarshalBSON(in []byte) error {
70-
rawDoc := bson.Raw(in)
71-
72-
err := mbson.LookupTo(rawDoc, &ns.DB, "db")
73-
74-
if err != nil {
75-
return err
70+
for el, err := range mbson.RawElements(in) {
71+
if err != nil {
72+
return errors.Wrap(err, "iterating BSON fields")
73+
}
74+
75+
key, err := el.KeyErr()
76+
if err != nil {
77+
return errors.Wrap(err, "reading BSON field name")
78+
}
79+
80+
switch key {
81+
case "db":
82+
if err := mbson.UnmarshalElementValue(el, &ns.DB); err != nil {
83+
return err
84+
}
85+
case "coll":
86+
if err := mbson.UnmarshalElementValue(el, &ns.Coll); err != nil {
87+
return err
88+
}
89+
}
7690
}
7791

78-
return mbson.LookupTo(rawDoc, &ns.Coll, "coll")
92+
return nil
7993
}
8094

8195
// NewNamespace returns a new Namespace struct with the given parameters.

mbson/bson_raw.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package mbson
22

33
import (
4+
"fmt"
5+
"iter"
6+
47
"github.com/pkg/errors"
58
"go.mongodb.org/mongo-driver/v2/bson"
69
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
@@ -28,6 +31,41 @@ func RawContains(doc bson.Raw, keys ...string) (bool, error) {
2831
return RawLookup(doc, &val, keys...)
2932
}
3033

34+
// RawElements returns an iterator over a Raw’s elements.
35+
func RawElements(doc bson.Raw) iter.Seq2[bson.RawElement, error] {
36+
remaining := doc[4:]
37+
38+
return func(yield func(bson.RawElement, error) bool) {
39+
var el bsoncore.Element
40+
var ok bool
41+
42+
for len(remaining) > 1 {
43+
el, remaining, ok = bsoncore.ReadElement(remaining)
44+
45+
var err error
46+
47+
if !ok {
48+
err = bsoncore.NewInsufficientBytesError(doc, remaining)
49+
} else {
50+
err = el.Validate()
51+
}
52+
53+
if err != nil {
54+
if yield(nil, err) {
55+
panic(fmt.Sprintf("Must stop iteration after error (%v)", err))
56+
}
57+
58+
return
59+
}
60+
61+
if !yield(bson.RawElement(el), nil) {
62+
return
63+
}
64+
}
65+
66+
}
67+
}
68+
3169
// ConvertToRawValue converts the specified argument to a bson.RawValue.
3270
func ConvertToRawValue(thing any) (bson.RawValue, error) {
3371
if thing == nil {

mbson/raw_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package mbson
2+
3+
import (
4+
"testing"
5+
6+
"github.com/samber/lo"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"go.mongodb.org/mongo-driver/bson"
10+
)
11+
12+
func TestRawElements(t *testing.T) {
13+
srcD := bson.D{
14+
{"foo", "xxx"},
15+
{"bar", "baz"},
16+
}
17+
18+
mydoc := lo.Must(bson.Marshal(srcD))
19+
20+
received := bson.D{}
21+
22+
for el, err := range RawElements(mydoc) {
23+
require.NoError(t, err, "should iterate")
24+
25+
received = append(received, bson.E{
26+
lo.Must(el.KeyErr()),
27+
lo.Must(el.ValueErr()).StringValue(),
28+
})
29+
}
30+
31+
assert.Equal(t, srcD, received, "should iterate all fields")
32+
33+
// Now make the document invalid
34+
mydoc = mydoc[:len(mydoc)-3]
35+
36+
received = received[:0]
37+
38+
var iterErr error
39+
for _, err := range RawElements(mydoc) {
40+
if err != nil {
41+
iterErr = err
42+
break
43+
}
44+
}
45+
46+
assert.Error(t, iterErr, "should fail somewhere in the iteration")
47+
48+
assert.Panics(
49+
t,
50+
func() {
51+
var gotErr error
52+
for _, err := range RawElements(mydoc) {
53+
if err != nil {
54+
gotErr = err
55+
}
56+
}
57+
58+
assert.Error(t, gotErr, "should get an error")
59+
},
60+
"should panic if we fail but didn’t stop iterating",
61+
)
62+
}

0 commit comments

Comments
 (0)