Skip to content

Commit d23c3a5

Browse files
committed
Fix cursor types and implement BatchCursor
GODRIVER-3 GODRIVER-759 GODRIVER-791 Change-Id: I7d4121e7fffcfadd7427a6fc64d97d4c131acbbe
1 parent 42e68e3 commit d23c3a5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1429
-1244
lines changed

.errcheck-excludes

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
(*github.com/mongodb/mongo-go-driver/x/mongo/driver/topology.Server).Close
55
(*github.com/mongodb/mongo-go-driver/x/network/connection.pool).closeConnection
66
(github.com/mongodb/mongo-go-driver/x/network/wiremessage.ReadWriteCloser).Close
7-
(github.com/mongodb/mongo-go-driver/mongo.Cursor).Close
7+
(*github.com/mongodb/mongo-go-driver/mongo.Cursor).Close
8+
(*github.com/mongodb/mongo-go-driver/mongo.ChangeStream).Close
89
(net.Conn).Close
910
encoding/pem.Encode
1011
fmt.Fprintf

benchmark/multi.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,7 @@ func MultiFindMany(ctx context.Context, tm TimerManager, iters int) error {
6060
return err
6161
}
6262
var r bson.Raw
63-
r, err = cursor.DecodeBytes()
64-
if err != nil {
65-
return err
66-
}
63+
r = cursor.DecodeBytes()
6764
if len(r) == 0 {
6865
return errors.New("error retrieving document")
6966
}

examples/documentation_examples/examples.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"github.com/stretchr/testify/require"
2222
)
2323

24-
func requireCursorLength(t *testing.T, cursor mongo.Cursor, length int) {
24+
func requireCursorLength(t *testing.T, cursor *mongo.Cursor, length int) {
2525
i := 0
2626
for cursor.Next(context.Background()) {
2727
i++

examples/documentation_examples/examples_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import (
1414
"testing"
1515

1616
"github.com/mongodb/mongo-go-driver/examples/documentation_examples"
17+
"github.com/mongodb/mongo-go-driver/internal/testutil"
1718
"github.com/mongodb/mongo-go-driver/mongo"
1819
"github.com/stretchr/testify/require"
1920
)
2021

2122
func TestDocumentationExamples(t *testing.T) {
22-
client, err := mongo.Connect(context.Background(), "mongodb://localhost:27017", nil)
23+
cs := testutil.ConnString(t)
24+
client, err := mongo.Connect(context.Background(), cs.String(), nil)
2325
require.NoError(t, err)
2426

2527
db := client.Database("documentation_examples")

mongo/batch_cursor.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package mongo
2+
3+
import (
4+
"context"
5+
)
6+
7+
// batchCursor is the interface implemented by types that can provide batches of document results.
8+
// The Cursor type is built on top of this type.
9+
type batchCursor interface {
10+
// ID returns the ID of the cursor.
11+
ID() int64
12+
13+
// Next returns true if there is a batch available.
14+
Next(context.Context) bool
15+
16+
// Batch appends the current batch of documents to dst. RequiredBytes can be used to determine
17+
// the length of the current batch of documents.
18+
//
19+
// If there is no batch available, this method should do nothing.
20+
Batch(dst []byte) []byte
21+
22+
// RequiredBytes returns the number of bytes required fo rthe current batch.
23+
RequiredBytes() int
24+
25+
// Err returns the last error encountered.
26+
Err() error
27+
28+
// Close closes the cursor.
29+
Close(context.Context) error
30+
}

mongo/change_stream.go

Lines changed: 52 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/mongodb/mongo-go-driver/mongo/readconcern"
1818
"github.com/mongodb/mongo-go-driver/mongo/readpref"
1919
"github.com/mongodb/mongo-go-driver/x/bsonx"
20+
"github.com/mongodb/mongo-go-driver/x/bsonx/bsoncore"
2021
"github.com/mongodb/mongo-go-driver/x/mongo/driver"
2122
"github.com/mongodb/mongo-go-driver/x/mongo/driver/session"
2223
"github.com/mongodb/mongo-go-driver/x/network/command"
@@ -34,14 +35,25 @@ var ErrMissingResumeToken = errors.New("cannot provide resume functionality when
3435
// ErrNilCursor indicates that the cursor for the change stream is nil.
3536
var ErrNilCursor = errors.New("cursor is nil")
3637

37-
type changeStream struct {
38-
cmd bsonx.Doc // aggregate command to run to create stream and rebuild cursor
39-
pipeline bsonx.Arr
40-
options *options.ChangeStreamOptions
41-
coll *Collection
42-
db *Database
43-
ns command.Namespace
44-
cursor Cursor
38+
// ChangeStream instances iterate a stream of change documents. Each document can be decoded via the
39+
// Decode method. Resume tokens should be retrieved via the ResumeToken method and can be stored to
40+
// resume the change stream at a specific point in time.
41+
//
42+
// A typical usage of the ChangeStream type would be:
43+
type ChangeStream struct {
44+
// Current is the BSON bytes of the current change document. This property is only valid until
45+
// the next call to Next or Close. If continued access is required to the bson.Raw, you must
46+
// make a copy of it.
47+
Current bson.Raw
48+
49+
cmd bsonx.Doc // aggregate command to run to create stream and rebuild cursor
50+
pipeline bsonx.Arr
51+
options *options.ChangeStreamOptions
52+
coll *Collection
53+
db *Database
54+
ns command.Namespace
55+
cursor *Cursor
56+
cursorOpts bsonx.Doc
4557

4658
resumeToken bsonx.Doc
4759
err error
@@ -53,7 +65,7 @@ type changeStream struct {
5365
registry *bsoncodec.Registry
5466
}
5567

56-
func (cs *changeStream) replaceOptions(desc description.SelectedServer) {
68+
func (cs *ChangeStream) replaceOptions(desc description.SelectedServer) {
5769
// if cs has not received any changes and resumeAfter not specified and max wire version >= 7, run known agg cmd
5870
// with startAtOperationTime set to startAtOperationTime provided by user or saved from initial agg
5971
// must not send resumeAfter key
@@ -156,7 +168,7 @@ func parseOptions(csType StreamType, opts *options.ChangeStreamOptions, registry
156168
return pipelineDoc, cursorDoc, optsDoc, nil
157169
}
158170

159-
func (cs *changeStream) runCommand(ctx context.Context, replaceOptions bool) error {
171+
func (cs *ChangeStream) runCommand(ctx context.Context, replaceOptions bool) error {
160172
ss, err := cs.client.topology.SelectServer(ctx, cs.db.writeSelector)
161173
if err != nil {
162174
return err
@@ -198,7 +210,12 @@ func (cs *changeStream) runCommand(ctx context.Context, replaceOptions bool) err
198210
return err
199211
}
200212

201-
cursor, err := ss.BuildCursor(rdr, readCmd.Session, readCmd.Clock)
213+
batchCursor, err := driver.NewBatchCursor(bsoncore.Document(rdr), readCmd.Session, readCmd.Clock, ss.Server)
214+
if err != nil {
215+
cs.sess.EndSession(ctx)
216+
return err
217+
}
218+
cursor, err := newCursor(batchCursor, cs.registry)
202219
if err != nil {
203220
cs.sess.EndSession(ctx)
204221
return err
@@ -216,7 +233,7 @@ func (cs *changeStream) runCommand(ctx context.Context, replaceOptions bool) err
216233
}
217234

218235
func newChangeStream(ctx context.Context, coll *Collection, pipeline interface{},
219-
opts ...*options.ChangeStreamOptions) (*changeStream, error) {
236+
opts ...*options.ChangeStreamOptions) (*ChangeStream, error) {
220237

221238
pipelineArr, err := transformAggregatePipeline(coll.registry, pipeline)
222239
if err != nil {
@@ -245,7 +262,7 @@ func newChangeStream(ctx context.Context, coll *Collection, pipeline interface{}
245262
}
246263
cmd = append(cmd, optsDoc...)
247264

248-
cs := &changeStream{
265+
cs := &ChangeStream{
249266
client: coll.client,
250267
sess: sess,
251268
cmd: cmd,
@@ -257,6 +274,7 @@ func newChangeStream(ctx context.Context, coll *Collection, pipeline interface{}
257274
readConcern: coll.readConcern,
258275
options: csOpts,
259276
registry: coll.registry,
277+
cursorOpts: cursorDoc,
260278
}
261279

262280
err = cs.runCommand(ctx, false)
@@ -268,7 +286,7 @@ func newChangeStream(ctx context.Context, coll *Collection, pipeline interface{}
268286
}
269287

270288
func newDbChangeStream(ctx context.Context, db *Database, pipeline interface{},
271-
opts ...*options.ChangeStreamOptions) (*changeStream, error) {
289+
opts ...*options.ChangeStreamOptions) (*ChangeStream, error) {
272290

273291
pipelineArr, err := transformAggregatePipeline(db.registry, pipeline)
274292
if err != nil {
@@ -297,7 +315,7 @@ func newDbChangeStream(ctx context.Context, db *Database, pipeline interface{},
297315
}
298316
cmd = append(cmd, optsDoc...)
299317

300-
cs := &changeStream{
318+
cs := &ChangeStream{
301319
client: db.client,
302320
db: db,
303321
sess: sess,
@@ -308,6 +326,7 @@ func newDbChangeStream(ctx context.Context, db *Database, pipeline interface{},
308326
readConcern: db.readConcern,
309327
options: csOpts,
310328
registry: db.registry,
329+
cursorOpts: cursorDoc,
311330
}
312331

313332
err = cs.runCommand(ctx, false)
@@ -319,7 +338,7 @@ func newDbChangeStream(ctx context.Context, db *Database, pipeline interface{},
319338
}
320339

321340
func newClientChangeStream(ctx context.Context, client *Client, pipeline interface{},
322-
opts ...*options.ChangeStreamOptions) (*changeStream, error) {
341+
opts ...*options.ChangeStreamOptions) (*ChangeStream, error) {
323342

324343
pipelineArr, err := transformAggregatePipeline(client.registry, pipeline)
325344
if err != nil {
@@ -348,7 +367,7 @@ func newClientChangeStream(ctx context.Context, client *Client, pipeline interfa
348367
}
349368
cmd = append(cmd, optsDoc...)
350369

351-
cs := &changeStream{
370+
cs := &ChangeStream{
352371
client: client,
353372
db: client.Database("admin"),
354373
sess: sess,
@@ -359,6 +378,7 @@ func newClientChangeStream(ctx context.Context, client *Client, pipeline interfa
359378
readConcern: client.readConcern,
360379
options: csOpts,
361380
registry: client.registry,
381+
cursorOpts: cursorDoc,
362382
}
363383

364384
err = cs.runCommand(ctx, false)
@@ -369,13 +389,8 @@ func newClientChangeStream(ctx context.Context, client *Client, pipeline interfa
369389
return cs, nil
370390
}
371391

372-
func (cs *changeStream) storeResumeToken() error {
373-
br, err := cs.cursor.DecodeBytes()
374-
if err != nil {
375-
return err
376-
}
377-
378-
idVal, err := br.LookupErr("_id")
392+
func (cs *ChangeStream) storeResumeToken() error {
393+
idVal, err := cs.cursor.Current.LookupErr("_id")
379394
if err != nil {
380395
_ = cs.Close(context.Background())
381396
return ErrMissingResumeToken
@@ -397,15 +412,18 @@ func (cs *changeStream) storeResumeToken() error {
397412
return nil
398413
}
399414

400-
func (cs *changeStream) ID() int64 {
415+
// ID returns the cursor ID for this change stream.
416+
func (cs *ChangeStream) ID() int64 {
401417
if cs.cursor == nil {
402418
return 0
403419
}
404420

405421
return cs.cursor.ID()
406422
}
407423

408-
func (cs *changeStream) Next(ctx context.Context) bool {
424+
// Next gets the next result from this change stream. Returns true if there were no errors and the next
425+
// result is available for decoding.
426+
func (cs *ChangeStream) Next(ctx context.Context) bool {
409427
// execute in a loop to retry resume-able errors and advance the underlying cursor
410428
for {
411429
if cs.cursor == nil {
@@ -419,6 +437,7 @@ func (cs *changeStream) Next(ctx context.Context) bool {
419437
return false
420438
}
421439

440+
cs.Current = cs.cursor.Current
422441
return true
423442
}
424443

@@ -447,31 +466,17 @@ func (cs *changeStream) Next(ctx context.Context) bool {
447466
}
448467
}
449468

450-
func (cs *changeStream) Decode(out interface{}) error {
469+
// Decode will decode the current document into val.
470+
func (cs *ChangeStream) Decode(out interface{}) error {
451471
if cs.cursor == nil {
452472
return ErrNilCursor
453473
}
454474

455-
br, err := cs.DecodeBytes()
456-
if err != nil {
457-
return err
458-
}
459-
460-
return bson.UnmarshalWithRegistry(cs.registry, br, out)
461-
}
462-
463-
func (cs *changeStream) DecodeBytes() (bson.Raw, error) {
464-
if cs.cursor == nil {
465-
return nil, ErrNilCursor
466-
}
467-
if cs.err != nil {
468-
return nil, cs.err
469-
}
470-
471-
return cs.cursor.DecodeBytes()
475+
return bson.UnmarshalWithRegistry(cs.registry, cs.Current, out)
472476
}
473477

474-
func (cs *changeStream) Err() error {
478+
// Err returns the current error.
479+
func (cs *ChangeStream) Err() error {
475480
if cs.err != nil {
476481
return cs.err
477482
}
@@ -482,7 +487,8 @@ func (cs *changeStream) Err() error {
482487
return cs.cursor.Err()
483488
}
484489

485-
func (cs *changeStream) Close(ctx context.Context) error {
490+
// Close closes this cursor.
491+
func (cs *ChangeStream) Close(ctx context.Context) error {
486492
if cs.cursor == nil {
487493
return nil // cursor is already closed
488494
}

mongo/change_stream_spec_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func TestChangeStreamSpec(t *testing.T) {
7070
}
7171
}
7272

73-
func closeCursor(stream Cursor) {
73+
func closeCursor(stream *ChangeStream) {
7474
_ = stream.Close(ctx)
7575
}
7676

@@ -214,7 +214,7 @@ func runCsTestFile(t *testing.T, globalClient *Client, path string) {
214214

215215
drainChannels()
216216
opts := getStreamOptions(&test)
217-
var cursor Cursor
217+
var cursor *ChangeStream
218218
switch test.Target {
219219
case "collection":
220220
cursor, err = clientColl.Watch(ctx, test.Pipeline, opts)

0 commit comments

Comments
 (0)