// Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package mongo import ( "bytes" "context" "errors" "fmt" "io" "reflect" "time" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/v2/x/mongo/driver" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/session" ) // Cursor is used to iterate over a stream of documents. Each document can be decoded into a Go type via the Decode // method or accessed as raw BSON via the Current field. This type is not goroutine safe and must not be used // concurrently by multiple goroutines. type Cursor struct { // Current contains the BSON bytes of the current change document. This property is only valid until the next call // to Next or TryNext. If continued access is required, a copy must be made. Current bson.Raw bc batchCursor batch *bsoncore.Iterator batchLength int bsonOpts *options.BSONOptions registry *bson.Registry clientSession *session.Client clientTimeout time.Duration hasClientTimeout bool err error } type cursorOptions struct { clientTimeout time.Duration hasClientTimeout bool } type cursorOption func(*cursorOptions) func withCursorOptionClientTimeout(dur *time.Duration) cursorOption { return func(opts *cursorOptions) { if dur != nil && *dur > 0 { opts.clientTimeout = *dur opts.hasClientTimeout = true } } } func newCursor( bc batchCursor, bsonOpts *options.BSONOptions, registry *bson.Registry, opts ...cursorOption, ) (*Cursor, error) { return newCursorWithSession(bc, bsonOpts, registry, nil, opts...) } func newCursorWithSession( bc batchCursor, bsonOpts *options.BSONOptions, registry *bson.Registry, clientSession *session.Client, opts ...cursorOption, ) (*Cursor, error) { if registry == nil { registry = defaultRegistry } if bc == nil { return nil, errors.New("batch cursor must not be nil") } cursorOpts := &cursorOptions{} for _, opt := range opts { opt(cursorOpts) } c := &Cursor{ bc: bc, bsonOpts: bsonOpts, registry: registry, clientSession: clientSession, clientTimeout: cursorOpts.clientTimeout, hasClientTimeout: cursorOpts.hasClientTimeout, } if bc.ID() == 0 { c.closeImplicitSession() } // Initialize just the batchLength here so RemainingBatchLength will return an // accurate result. The actual batch will be pulled up by the first // Next/TryNext call. c.batchLength = c.bc.Batch().Count() return c, nil } func newEmptyCursor() *Cursor { return &Cursor{bc: driver.NewEmptyBatchCursor()} } // NewCursorFromDocuments creates a new Cursor pre-loaded with the provided documents, error and registry. If no registry is provided, // bson.NewRegistry() will be used. // // The documents parameter must be a slice of documents. The slice may be nil or empty, but all elements must be non-nil. func NewCursorFromDocuments(documents []any, preloadedErr error, registry *bson.Registry) (*Cursor, error) { if registry == nil { registry = defaultRegistry } buf := new(bytes.Buffer) enc := new(bson.Encoder) values := make([]bsoncore.Value, len(documents)) for i, doc := range documents { switch t := doc.(type) { case nil: return nil, fmt.Errorf("invalid document at index %d: %w", i, ErrNilDocument) case []byte: // Slight optimization so we'll just use MarshalBSON and not go through the codec machinery. doc = bson.Raw(t) } vw := bson.NewDocumentWriter(buf) enc.Reset(vw) enc.SetRegistry(registry) if err := enc.Encode(doc); err != nil { return nil, err } dup := make([]byte, len(buf.Bytes())) copy(dup, buf.Bytes()) values[i] = bsoncore.Value{ Type: bsoncore.TypeEmbeddedDocument, Data: dup, } buf.Reset() } c := &Cursor{ bc: driver.NewBatchCursorFromList(bsoncore.BuildArray(nil, values...)), registry: registry, err: preloadedErr, } // Initialize batch and batchLength here. The underlying batch cursor will be preloaded with the // provided contents, and thus already has a batch before calls to Next/TryNext. c.batch = c.bc.Batch() c.batchLength = c.bc.Batch().Count() return c, nil } // ID returns the ID of this cursor, or 0 if the cursor has been closed or exhausted. func (c *Cursor) ID() int64 { return c.bc.ID() } // Next gets the next document for this cursor. It returns true if there were no // errors and the cursor has not been exhausted. // // Next blocks until a document is available or an error occurs. If the context // expires, the cursor's error will be set to ctx.Err(). In case of an error, // Next will return false. // // If MaxAwaitTime is set, the operation will be bound by the Context's // deadline. If the context does not have a deadline, the operation will be // bound by the client-level timeout, if one is set. If MaxAwaitTime is greater // than the user-provided timeout, Next will return false. // // If Next returns false, subsequent calls will also return false. func (c *Cursor) Next(ctx context.Context) bool { return c.next(ctx, false) } // TryNext attempts to get the next document for this cursor. It returns true if there were no errors and the next // document is available. This is only recommended for use with tailable cursors as a non-blocking alternative to // Next. See https://www.mongodb.com/docs/manual/core/tailable-cursors/ for more information about tailable cursors. // // TryNext returns false if the cursor is exhausted, an error occurs when getting results from the server, the next // document is not yet available, or ctx expires. If the context expires, the cursor's error will be set to ctx.Err(). // // If TryNext returns false and an error occurred or the cursor has been exhausted (i.e. c.Err() != nil || c.ID() == 0), // subsequent attempts will also return false. Otherwise, it is safe to call TryNext again until a document is // available. // // This method requires driver version >= 1.2.0. func (c *Cursor) TryNext(ctx context.Context) bool { return c.next(ctx, true) } func (c *Cursor) next(ctx context.Context, nonBlocking bool) bool { // return false right away if the cursor has already errored. if c.err != nil { return false } if ctx == nil { ctx = context.Background() } // If the context does not have a deadline we defer to a client-level timeout, // if one is set. if _, ok := ctx.Deadline(); !ok && c.hasClientTimeout { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, c.clientTimeout) defer cancel() } // To avoid unnecessary socket timeouts, we attempt to short-circuit tailable // awaitData "getMore" operations by ensuring that the maxAwaitTimeMS is less // than the operation timeout. // // The specifications assume that drivers iteratively apply the timeout // provided at the constructor level (e.g., (*collection).Find) for tailable // awaitData cursors: // // If set, drivers MUST apply the timeoutMS option to the initial aggregate // operation. Drivers MUST also apply the original timeoutMS value to each // next call on the change stream but MUST NOT use it to derive a maxTimeMS // field for getMore commands. // // The Go Driver might decide to support the above behavior with DRIVERS-2722. // The principal concern is that it would be unexpected for users to apply an // operation-level timeout via contexts to a constructor and then that timeout // later be applied while working with a resulting cursor. Instead, it is more // idiomatic to apply the timeout to the context passed to Next or TryNext. maxAwaitTime := c.bc.MaxAwaitTime() // if maxAwaitTime != nil && !nonBlocking && !mongoutil.TimeoutWithinContext(ctx, *maxAwaitTime) { c.err = fmt.Errorf("MaxAwaitTime must be less than the operation timeout") return false } val, err := c.batch.Next() switch { case err == nil: // Consume the next document in the current batch. c.batchLength-- c.Current = bson.Raw(val.Data) return true case errors.Is(err, io.EOF): // Need to do a getMore default: c.err = err return false } // call the Next method in a loop until at least one document is returned in the next batch or // the context times out. for { // If we don't have a next batch if !c.bc.Next(ctx) { // Do we have an error? If so we return false. c.err = wrapErrors(c.bc.Err()) if c.err != nil { return false } // Is the cursor ID zero? if c.bc.ID() == 0 { c.closeImplicitSession() return false } // empty batch, but cursor is still valid. // use nonBlocking to determine if we should continue or return control to the caller. if nonBlocking { return false } continue } // close the implicit session if this was the last getMore if c.bc.ID() == 0 { c.closeImplicitSession() } // Use the new batch to update the batch and batchLength fields. Consume the first document in the batch. c.batch = c.bc.Batch() c.batchLength = c.batch.Count() val, err = c.batch.Next() switch { case err == nil: c.batchLength-- c.Current = bson.Raw(val.Data) return true case errors.Is(err, io.EOF): // Empty batch so we continue default: c.err = err return false } } } func getDecoder( data []byte, opts *options.BSONOptions, reg *bson.Registry, ) *bson.Decoder { dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(data))) if opts != nil { if opts.AllowTruncatingDoubles { dec.AllowTruncatingDoubles() } if opts.BinaryAsSlice { dec.BinaryAsSlice() } if opts.DefaultDocumentM { dec.DefaultDocumentM() } if opts.DefaultDocumentMap { dec.DefaultDocumentMap() } if opts.ObjectIDAsHexString { dec.ObjectIDAsHexString() } if opts.UseJSONStructTags { dec.UseJSONStructTags() } if opts.UseLocalTimeZone { dec.UseLocalTimeZone() } if opts.ZeroMaps { dec.ZeroMaps() } if opts.ZeroStructs { dec.ZeroStructs() } } if reg != nil { dec.SetRegistry(reg) } return dec } // Decode will unmarshal the current document into val and return any errors from the unmarshalling process without any // modification. If val is nil or is a typed nil, an error will be returned. func (c *Cursor) Decode(val any) error { dec := getDecoder(c.Current, c.bsonOpts, c.registry) return dec.Decode(val) } // Err returns the last error seen by the Cursor, or nil if no error has occurred. func (c *Cursor) Err() error { return c.err } // Close closes this cursor. Next and TryNext must not be called after Close has been called. Close is idempotent. After // the first call, any subsequent calls will not change the state. func (c *Cursor) Close(ctx context.Context) error { defer c.closeImplicitSession() return wrapErrors(c.bc.Close(ctx)) } // All iterates the cursor and decodes each document into results. The results parameter must be a pointer to a slice. // The slice pointed to by results will be completely overwritten. A nil slice pointer will not be modified if the cursor // has been closed, exhausted, or is empty. This method will close the cursor after retrieving all documents. If the // cursor has been iterated, any previously iterated documents will not be included in results. // // This method requires driver version >= 1.1.0. func (c *Cursor) All(ctx context.Context, results any) error { resultsVal := reflect.ValueOf(results) if resultsVal.Kind() != reflect.Ptr { return fmt.Errorf("results argument must be a pointer to a slice, but was a %s", resultsVal.Kind()) } sliceVal := resultsVal.Elem() if sliceVal.Kind() == reflect.Interface { sliceVal = sliceVal.Elem() } if sliceVal.Kind() != reflect.Slice { return fmt.Errorf("results argument must be a pointer to a slice, but was a pointer to %s", sliceVal.Kind()) } elementType := sliceVal.Type().Elem() var index int var err error // Defer a call to Close to try to clean up the cursor server-side when all // documents have not been exhausted. Use context.Background() to ensure Close // completes even if the context passed to All has errored. defer c.Close(context.Background()) batch := c.batch // exhaust the current batch before iterating the batch cursor for { sliceVal, index, err = c.addFromBatch(sliceVal, elementType, batch, index) if err != nil { return err } if !c.bc.Next(ctx) { break } batch = c.bc.Batch() } if err = wrapErrors(c.bc.Err()); err != nil { return err } resultsVal.Elem().Set(sliceVal.Slice(0, index)) return nil } // RemainingBatchLength returns the number of documents left in the current batch. If this returns zero, the subsequent // call to Next or TryNext will do a network request to fetch the next batch. func (c *Cursor) RemainingBatchLength() int { return c.batchLength } // addFromBatch adds all documents from batch to sliceVal starting at the given index. It returns the new slice value, // the next empty index in the slice, and an error if one occurs. func (c *Cursor) addFromBatch(sliceVal reflect.Value, elemType reflect.Type, batch *bsoncore.Iterator, index int, ) (reflect.Value, int, error) { docs, err := batch.Documents() if err != nil { return sliceVal, index, err } for _, doc := range docs { if sliceVal.Len() == index { // slice is full newElem := reflect.New(elemType) sliceVal = reflect.Append(sliceVal, newElem.Elem()) sliceVal = sliceVal.Slice(0, sliceVal.Cap()) } currElem := sliceVal.Index(index).Addr().Interface() dec := getDecoder(doc, c.bsonOpts, c.registry) err = dec.Decode(currElem) if err != nil { return sliceVal, index, err } index++ } return sliceVal, index, nil } func (c *Cursor) closeImplicitSession() { if c.clientSession != nil && c.clientSession.IsImplicit { c.clientSession.EndSession() } } // SetBatchSize sets the number of documents to fetch from the database with // each iteration of the cursor's "Next" method. Note that some operations set // an initial cursor batch size, so this setting only affects subsequent // document batches fetched from the database. func (c *Cursor) SetBatchSize(batchSize int32) { c.bc.SetBatchSize(batchSize) } // SetMaxAwaitTime will set the maximum amount of time the server will allow the // operations to execute. The server will error if this field is set but the // cursor is not configured with awaitData=true. // // The time.Duration value passed by this setter will be converted and rounded // down to the nearest millisecond. func (c *Cursor) SetMaxAwaitTime(dur time.Duration) { c.bc.SetMaxAwaitTime(dur) } // SetComment will set a user-configurable comment that can be used to identify // the operation in server logs. func (c *Cursor) SetComment(comment any) { c.bc.SetComment(comment) } // BatchCursorFromCursor returns a driver.BatchCursor for the given Cursor. If there is no underlying // driver.BatchCursor, nil is returned. // // Deprecated: This is an unstable function because the driver.BatchCursor type exists in the "x" package. Neither this // function nor the driver.BatchCursor type should be used by applications and may be changed or removed in any release. func BatchCursorFromCursor(c *Cursor) *driver.BatchCursor { bc, _ := c.bc.(*driver.BatchCursor) return bc }