github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.mongodb.org/mongo-driver/mongo/cursor.go (about) 1 // Copyright (C) MongoDB, Inc. 2017-present. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may 4 // not use this file except in compliance with the License. You may obtain 5 // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 7 package mongo 8 9 import ( 10 "context" 11 "errors" 12 "fmt" 13 "io" 14 "reflect" 15 16 "go.mongodb.org/mongo-driver/bson" 17 "go.mongodb.org/mongo-driver/bson/bsoncodec" 18 "go.mongodb.org/mongo-driver/bson/bsonrw" 19 "go.mongodb.org/mongo-driver/mongo/options" 20 "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" 21 "go.mongodb.org/mongo-driver/x/mongo/driver" 22 "go.mongodb.org/mongo-driver/x/mongo/driver/session" 23 ) 24 25 // Cursor is used to iterate over a stream of documents. Each document can be decoded into a Go type via the Decode 26 // method or accessed as raw BSON via the Current field. This type is not goroutine safe and must not be used 27 // concurrently by multiple goroutines. 28 type Cursor struct { 29 // Current contains the BSON bytes of the current change document. This property is only valid until the next call 30 // to Next or TryNext. If continued access is required, a copy must be made. 31 Current bson.Raw 32 33 bc batchCursor 34 batch *bsoncore.DocumentSequence 35 batchLength int 36 bsonOpts *options.BSONOptions 37 registry *bsoncodec.Registry 38 clientSession *session.Client 39 40 err error 41 } 42 43 func newCursor( 44 bc batchCursor, 45 bsonOpts *options.BSONOptions, 46 registry *bsoncodec.Registry, 47 ) (*Cursor, error) { 48 return newCursorWithSession(bc, bsonOpts, registry, nil) 49 } 50 51 func newCursorWithSession( 52 bc batchCursor, 53 bsonOpts *options.BSONOptions, 54 registry *bsoncodec.Registry, 55 clientSession *session.Client, 56 ) (*Cursor, error) { 57 if registry == nil { 58 registry = bson.DefaultRegistry 59 } 60 if bc == nil { 61 return nil, errors.New("batch cursor must not be nil") 62 } 63 c := &Cursor{ 64 bc: bc, 65 bsonOpts: bsonOpts, 66 registry: registry, 67 clientSession: clientSession, 68 } 69 if bc.ID() == 0 { 70 c.closeImplicitSession() 71 } 72 73 // Initialize just the batchLength here so RemainingBatchLength will return an accurate result. The actual batch 74 // will be pulled up by the first Next/TryNext call. 75 c.batchLength = c.bc.Batch().DocumentCount() 76 return c, nil 77 } 78 79 func newEmptyCursor() *Cursor { 80 return &Cursor{bc: driver.NewEmptyBatchCursor()} 81 } 82 83 // NewCursorFromDocuments creates a new Cursor pre-loaded with the provided documents, error and registry. If no registry is provided, 84 // bson.DefaultRegistry will be used. 85 // 86 // The documents parameter must be a slice of documents. The slice may be nil or empty, but all elements must be non-nil. 87 func NewCursorFromDocuments(documents []interface{}, err error, registry *bsoncodec.Registry) (*Cursor, error) { 88 if registry == nil { 89 registry = bson.DefaultRegistry 90 } 91 92 // Convert documents slice to a sequence-style byte array. 93 var docsBytes []byte 94 for _, doc := range documents { 95 switch t := doc.(type) { 96 case nil: 97 return nil, ErrNilDocument 98 case []byte: 99 // Slight optimization so we'll just use MarshalBSON and not go through the codec machinery. 100 doc = bson.Raw(t) 101 } 102 var marshalErr error 103 docsBytes, marshalErr = bson.MarshalAppendWithRegistry(registry, docsBytes, doc) 104 if marshalErr != nil { 105 return nil, marshalErr 106 } 107 } 108 109 c := &Cursor{ 110 bc: driver.NewBatchCursorFromDocuments(docsBytes), 111 registry: registry, 112 err: err, 113 } 114 115 // Initialize batch and batchLength here. The underlying batch cursor will be preloaded with the 116 // provided contents, and thus already has a batch before calls to Next/TryNext. 117 c.batch = c.bc.Batch() 118 c.batchLength = c.bc.Batch().DocumentCount() 119 return c, nil 120 } 121 122 // ID returns the ID of this cursor, or 0 if the cursor has been closed or exhausted. 123 func (c *Cursor) ID() int64 { return c.bc.ID() } 124 125 // Next gets the next document for this cursor. It returns true if there were no errors and the cursor has not been 126 // exhausted. 127 // 128 // Next blocks until a document is available or an error occurs. If the context expires, the cursor's error will 129 // be set to ctx.Err(). In case of an error, Next will return false. 130 // 131 // If Next returns false, subsequent calls will also return false. 132 func (c *Cursor) Next(ctx context.Context) bool { 133 return c.next(ctx, false) 134 } 135 136 // TryNext attempts to get the next document for this cursor. It returns true if there were no errors and the next 137 // document is available. This is only recommended for use with tailable cursors as a non-blocking alternative to 138 // Next. See https://www.mongodb.com/docs/manual/core/tailable-cursors/ for more information about tailable cursors. 139 // 140 // TryNext returns false if the cursor is exhausted, an error occurs when getting results from the server, the next 141 // document is not yet available, or ctx expires. If the context expires, the cursor's error will be set to ctx.Err(). 142 // 143 // If TryNext returns false and an error occurred or the cursor has been exhausted (i.e. c.Err() != nil || c.ID() == 0), 144 // subsequent attempts will also return false. Otherwise, it is safe to call TryNext again until a document is 145 // available. 146 // 147 // This method requires driver version >= 1.2.0. 148 func (c *Cursor) TryNext(ctx context.Context) bool { 149 return c.next(ctx, true) 150 } 151 152 func (c *Cursor) next(ctx context.Context, nonBlocking bool) bool { 153 // return false right away if the cursor has already errored. 154 if c.err != nil { 155 return false 156 } 157 158 if ctx == nil { 159 ctx = context.Background() 160 } 161 doc, err := c.batch.Next() 162 switch err { 163 case nil: 164 // Consume the next document in the current batch. 165 c.batchLength-- 166 c.Current = bson.Raw(doc) 167 return true 168 case io.EOF: // Need to do a getMore 169 default: 170 c.err = err 171 return false 172 } 173 174 // call the Next method in a loop until at least one document is returned in the next batch or 175 // the context times out. 176 for { 177 // If we don't have a next batch 178 if !c.bc.Next(ctx) { 179 // Do we have an error? If so we return false. 180 c.err = replaceErrors(c.bc.Err()) 181 if c.err != nil { 182 return false 183 } 184 // Is the cursor ID zero? 185 if c.bc.ID() == 0 { 186 c.closeImplicitSession() 187 return false 188 } 189 // empty batch, but cursor is still valid. 190 // use nonBlocking to determine if we should continue or return control to the caller. 191 if nonBlocking { 192 return false 193 } 194 continue 195 } 196 197 // close the implicit session if this was the last getMore 198 if c.bc.ID() == 0 { 199 c.closeImplicitSession() 200 } 201 202 // Use the new batch to update the batch and batchLength fields. Consume the first document in the batch. 203 c.batch = c.bc.Batch() 204 c.batchLength = c.batch.DocumentCount() 205 doc, err = c.batch.Next() 206 switch err { 207 case nil: 208 c.batchLength-- 209 c.Current = bson.Raw(doc) 210 return true 211 case io.EOF: // Empty batch so we continue 212 default: 213 c.err = err 214 return false 215 } 216 } 217 } 218 219 func getDecoder( 220 data []byte, 221 opts *options.BSONOptions, 222 reg *bsoncodec.Registry, 223 ) (*bson.Decoder, error) { 224 dec, err := bson.NewDecoder(bsonrw.NewBSONDocumentReader(data)) 225 if err != nil { 226 return nil, err 227 } 228 229 if opts != nil { 230 if opts.AllowTruncatingDoubles { 231 dec.AllowTruncatingDoubles() 232 } 233 if opts.BinaryAsSlice { 234 dec.BinaryAsSlice() 235 } 236 if opts.DefaultDocumentD { 237 dec.DefaultDocumentD() 238 } 239 if opts.DefaultDocumentM { 240 dec.DefaultDocumentM() 241 } 242 if opts.UseJSONStructTags { 243 dec.UseJSONStructTags() 244 } 245 if opts.UseLocalTimeZone { 246 dec.UseLocalTimeZone() 247 } 248 if opts.ZeroMaps { 249 dec.ZeroMaps() 250 } 251 if opts.ZeroStructs { 252 dec.ZeroStructs() 253 } 254 } 255 256 if reg != nil { 257 // TODO:(GODRIVER-2719): Remove error handling. 258 if err := dec.SetRegistry(reg); err != nil { 259 return nil, err 260 } 261 } 262 263 return dec, nil 264 } 265 266 // Decode will unmarshal the current document into val and return any errors from the unmarshalling process without any 267 // modification. If val is nil or is a typed nil, an error will be returned. 268 func (c *Cursor) Decode(val interface{}) error { 269 dec, err := getDecoder(c.Current, c.bsonOpts, c.registry) 270 if err != nil { 271 return fmt.Errorf("error configuring BSON decoder: %w", err) 272 } 273 274 return dec.Decode(val) 275 } 276 277 // Err returns the last error seen by the Cursor, or nil if no error has occurred. 278 func (c *Cursor) Err() error { return c.err } 279 280 // Close closes this cursor. Next and TryNext must not be called after Close has been called. Close is idempotent. After 281 // the first call, any subsequent calls will not change the state. 282 func (c *Cursor) Close(ctx context.Context) error { 283 defer c.closeImplicitSession() 284 return replaceErrors(c.bc.Close(ctx)) 285 } 286 287 // All iterates the cursor and decodes each document into results. The results parameter must be a pointer to a slice. 288 // The slice pointed to by results will be completely overwritten. This method will close the cursor after retrieving 289 // all documents. If the cursor has been iterated, any previously iterated documents will not be included in results. 290 // 291 // This method requires driver version >= 1.1.0. 292 func (c *Cursor) All(ctx context.Context, results interface{}) error { 293 resultsVal := reflect.ValueOf(results) 294 if resultsVal.Kind() != reflect.Ptr { 295 return fmt.Errorf("results argument must be a pointer to a slice, but was a %s", resultsVal.Kind()) 296 } 297 298 sliceVal := resultsVal.Elem() 299 if sliceVal.Kind() == reflect.Interface { 300 sliceVal = sliceVal.Elem() 301 } 302 303 if sliceVal.Kind() != reflect.Slice { 304 return fmt.Errorf("results argument must be a pointer to a slice, but was a pointer to %s", sliceVal.Kind()) 305 } 306 307 elementType := sliceVal.Type().Elem() 308 var index int 309 var err error 310 311 // Defer a call to Close to try to clean up the cursor server-side when all 312 // documents have not been exhausted. Use context.Background() to ensure Close 313 // completes even if the context passed to All has errored. 314 defer c.Close(context.Background()) 315 316 batch := c.batch // exhaust the current batch before iterating the batch cursor 317 for { 318 sliceVal, index, err = c.addFromBatch(sliceVal, elementType, batch, index) 319 if err != nil { 320 return err 321 } 322 323 if !c.bc.Next(ctx) { 324 break 325 } 326 327 batch = c.bc.Batch() 328 } 329 330 if err = replaceErrors(c.bc.Err()); err != nil { 331 return err 332 } 333 334 resultsVal.Elem().Set(sliceVal.Slice(0, index)) 335 return nil 336 } 337 338 // RemainingBatchLength returns the number of documents left in the current batch. If this returns zero, the subsequent 339 // call to Next or TryNext will do a network request to fetch the next batch. 340 func (c *Cursor) RemainingBatchLength() int { 341 return c.batchLength 342 } 343 344 // addFromBatch adds all documents from batch to sliceVal starting at the given index. It returns the new slice value, 345 // the next empty index in the slice, and an error if one occurs. 346 func (c *Cursor) addFromBatch(sliceVal reflect.Value, elemType reflect.Type, batch *bsoncore.DocumentSequence, 347 index int) (reflect.Value, int, error) { 348 349 docs, err := batch.Documents() 350 if err != nil { 351 return sliceVal, index, err 352 } 353 354 for _, doc := range docs { 355 if sliceVal.Len() == index { 356 // slice is full 357 newElem := reflect.New(elemType) 358 sliceVal = reflect.Append(sliceVal, newElem.Elem()) 359 sliceVal = sliceVal.Slice(0, sliceVal.Cap()) 360 } 361 362 currElem := sliceVal.Index(index).Addr().Interface() 363 dec, err := getDecoder(doc, c.bsonOpts, c.registry) 364 if err != nil { 365 return sliceVal, index, fmt.Errorf("error configuring BSON decoder: %w", err) 366 } 367 err = dec.Decode(currElem) 368 if err != nil { 369 return sliceVal, index, err 370 } 371 372 index++ 373 } 374 375 return sliceVal, index, nil 376 } 377 378 func (c *Cursor) closeImplicitSession() { 379 if c.clientSession != nil && c.clientSession.IsImplicit { 380 c.clientSession.EndSession() 381 } 382 } 383 384 // SetBatchSize sets the number of documents to fetch from the database with 385 // each iteration of the cursor's "Next" method. Note that some operations set 386 // an initial cursor batch size, so this setting only affects subsequent 387 // document batches fetched from the database. 388 func (c *Cursor) SetBatchSize(batchSize int32) { 389 c.bc.SetBatchSize(batchSize) 390 } 391 392 // BatchCursorFromCursor returns a driver.BatchCursor for the given Cursor. If there is no underlying 393 // driver.BatchCursor, nil is returned. 394 // 395 // Deprecated: This is an unstable function because the driver.BatchCursor type exists in the "x" package. Neither this 396 // function nor the driver.BatchCursor type should be used by applications and may be changed or removed in any release. 397 func BatchCursorFromCursor(c *Cursor) *driver.BatchCursor { 398 bc, _ := c.bc.(*driver.BatchCursor) 399 return bc 400 }