github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.mongodb.org/mongo-driver/x/mongo/driver/operation.go (about) 1 // Copyright (C) MongoDB, Inc. 2022-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 driver 8 9 import ( 10 "bytes" 11 "context" 12 "errors" 13 "fmt" 14 "math" 15 "net" 16 "strconv" 17 "strings" 18 "sync" 19 "time" 20 21 "go.mongodb.org/mongo-driver/bson" 22 "go.mongodb.org/mongo-driver/bson/bsontype" 23 "go.mongodb.org/mongo-driver/bson/primitive" 24 "go.mongodb.org/mongo-driver/event" 25 "go.mongodb.org/mongo-driver/internal" 26 "go.mongodb.org/mongo-driver/internal/logger" 27 "go.mongodb.org/mongo-driver/mongo/address" 28 "go.mongodb.org/mongo-driver/mongo/description" 29 "go.mongodb.org/mongo-driver/mongo/readconcern" 30 "go.mongodb.org/mongo-driver/mongo/readpref" 31 "go.mongodb.org/mongo-driver/mongo/writeconcern" 32 "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" 33 "go.mongodb.org/mongo-driver/x/mongo/driver/session" 34 "go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage" 35 ) 36 37 const defaultLocalThreshold = 15 * time.Millisecond 38 39 var dollarCmd = [...]byte{'.', '$', 'c', 'm', 'd'} 40 41 var ( 42 // ErrNoDocCommandResponse occurs when the server indicated a response existed, but none was found. 43 ErrNoDocCommandResponse = errors.New("command returned no documents") 44 // ErrMultiDocCommandResponse occurs when the server sent multiple documents in response to a command. 45 ErrMultiDocCommandResponse = errors.New("command returned multiple documents") 46 // ErrReplyDocumentMismatch occurs when the number of documents returned in an OP_QUERY does not match the numberReturned field. 47 ErrReplyDocumentMismatch = errors.New("number of documents returned does not match numberReturned field") 48 // ErrNonPrimaryReadPref is returned when a read is attempted in a transaction with a non-primary read preference. 49 ErrNonPrimaryReadPref = errors.New("read preference in a transaction must be primary") 50 // errDatabaseNameEmpty occurs when a database name is not provided. 51 errDatabaseNameEmpty = errors.New("database name cannot be empty") 52 ) 53 54 const ( 55 // maximum BSON object size when client side encryption is enabled 56 cryptMaxBsonObjectSize uint32 = 2097152 57 // minimum wire version necessary to use automatic encryption 58 cryptMinWireVersion int32 = 8 59 // minimum wire version necessary to use read snapshots 60 readSnapshotMinWireVersion int32 = 13 61 ) 62 63 // RetryablePoolError is a connection pool error that can be retried while executing an operation. 64 type RetryablePoolError interface { 65 Retryable() bool 66 } 67 68 // labeledError is an error that can have error labels added to it. 69 type labeledError interface { 70 error 71 HasErrorLabel(string) bool 72 } 73 74 // InvalidOperationError is returned from Validate and indicates that a required field is missing 75 // from an instance of Operation. 76 type InvalidOperationError struct{ MissingField string } 77 78 func (err InvalidOperationError) Error() string { 79 return "the " + err.MissingField + " field must be set on Operation" 80 } 81 82 // opReply stores information returned in an OP_REPLY response from the server. 83 // The err field stores any error that occurred when decoding or validating the OP_REPLY response. 84 type opReply struct { 85 responseFlags wiremessage.ReplyFlag 86 cursorID int64 87 startingFrom int32 88 numReturned int32 89 documents []bsoncore.Document 90 err error 91 } 92 93 // startedInformation keeps track of all of the information necessary for monitoring started events. 94 type startedInformation struct { 95 cmd bsoncore.Document 96 requestID int32 97 cmdName string 98 documentSequenceIncluded bool 99 connID string 100 driverConnectionID uint64 // TODO(GODRIVER-2824): change type to int64. 101 serverConnID *int64 102 redacted bool 103 serviceID *primitive.ObjectID 104 serverAddress address.Address 105 } 106 107 // finishedInformation keeps track of all of the information necessary for monitoring success and failure events. 108 type finishedInformation struct { 109 cmdName string 110 requestID int32 111 response bsoncore.Document 112 cmdErr error 113 connID string 114 driverConnectionID uint64 // TODO(GODRIVER-2824): change type to int64. 115 serverConnID *int64 116 redacted bool 117 serviceID *primitive.ObjectID 118 serverAddress address.Address 119 duration time.Duration 120 } 121 122 // convertInt64PtrToInt32Ptr will convert an int64 pointer reference to an int32 pointer 123 // reference. If the int64 value cannot be converted to int32 without causing 124 // an overflow, then this function will return nil. 125 func convertInt64PtrToInt32Ptr(i64 *int64) *int32 { 126 if i64 == nil { 127 return nil 128 } 129 130 if *i64 > math.MaxInt32 || *i64 < math.MinInt32 { 131 return nil 132 } 133 134 i32 := int32(*i64) 135 return &i32 136 } 137 138 // success returns true if there was no command error or the command error is a 139 // "WriteCommandError". Commands that executed on the server and return a status 140 // of { ok: 1.0 } are considered successful commands and MUST generate a 141 // CommandSucceededEvent and "command succeeded" log message. Commands that have 142 // write errors are included since the actual command did succeed, only writes 143 // failed. 144 func (info finishedInformation) success() bool { 145 if _, ok := info.cmdErr.(WriteCommandError); ok { 146 return true 147 } 148 149 return info.cmdErr == nil 150 } 151 152 // ResponseInfo contains the context required to parse a server response. 153 type ResponseInfo struct { 154 ServerResponse bsoncore.Document 155 Server Server 156 Connection Connection 157 ConnectionDescription description.Server 158 CurrentIndex int 159 } 160 161 func redactStartedInformationCmd(op Operation, info startedInformation) bson.Raw { 162 var cmdCopy bson.Raw 163 164 // Make a copy of the command. Redact if the command is security 165 // sensitive and cannot be monitored. If there was a type 1 payload for 166 // the current batch, convert it to a BSON array 167 if !info.redacted { 168 cmdCopy = make([]byte, len(info.cmd)) 169 copy(cmdCopy, info.cmd) 170 171 if info.documentSequenceIncluded { 172 // remove 0 byte at end 173 cmdCopy = cmdCopy[:len(info.cmd)-1] 174 cmdCopy = op.addBatchArray(cmdCopy) 175 176 // add back 0 byte and update length 177 cmdCopy, _ = bsoncore.AppendDocumentEnd(cmdCopy, 0) 178 } 179 } 180 181 return cmdCopy 182 } 183 184 func redactFinishedInformationResponse(info finishedInformation) bson.Raw { 185 if !info.redacted { 186 return bson.Raw(info.response) 187 } 188 189 return bson.Raw{} 190 } 191 192 // Operation is used to execute an operation. It contains all of the common code required to 193 // select a server, transform an operation into a command, write the command to a connection from 194 // the selected server, read a response from that connection, process the response, and potentially 195 // retry. 196 // 197 // The required fields are Database, CommandFn, and Deployment. All other fields are optional. 198 // 199 // While an Operation can be constructed manually, drivergen should be used to generate an 200 // implementation of an operation instead. This will ensure that there are helpers for constructing 201 // the operation and that this type isn't configured incorrectly. 202 type Operation struct { 203 // CommandFn is used to create the command that will be wrapped in a wire message and sent to 204 // the server. This function should only add the elements of the command and not start or end 205 // the enclosing BSON document. Per the command API, the first element must be the name of the 206 // command to run. This field is required. 207 CommandFn func(dst []byte, desc description.SelectedServer) ([]byte, error) 208 209 // Database is the database that the command will be run against. This field is required. 210 Database string 211 212 // Deployment is the MongoDB Deployment to use. While most of the time this will be multiple 213 // servers, commands that need to run against a single, preselected server can use the 214 // SingleServerDeployment type. Commands that need to run on a preselected connection can use 215 // the SingleConnectionDeployment type. 216 Deployment Deployment 217 218 // ProcessResponseFn is called after a response to the command is returned. The server is 219 // provided for types like Cursor that are required to run subsequent commands using the same 220 // server. 221 ProcessResponseFn func(ResponseInfo) error 222 223 // Selector is the server selector that's used during both initial server selection and 224 // subsequent selection for retries. Depending on the Deployment implementation, the 225 // SelectServer method may not actually be called. 226 Selector description.ServerSelector 227 228 // ReadPreference is the read preference that will be attached to the command. If this field is 229 // not specified a default read preference of primary will be used. 230 ReadPreference *readpref.ReadPref 231 232 // ReadConcern is the read concern used when running read commands. This field should not be set 233 // for write operations. If this field is set, it will be encoded onto the commands sent to the 234 // server. 235 ReadConcern *readconcern.ReadConcern 236 237 // MinimumReadConcernWireVersion specifies the minimum wire version to add the read concern to 238 // the command being executed. 239 MinimumReadConcernWireVersion int32 240 241 // WriteConcern is the write concern used when running write commands. This field should not be 242 // set for read operations. If this field is set, it will be encoded onto the commands sent to 243 // the server. 244 WriteConcern *writeconcern.WriteConcern 245 246 // MinimumWriteConcernWireVersion specifies the minimum wire version to add the write concern to 247 // the command being executed. 248 MinimumWriteConcernWireVersion int32 249 250 // Client is the session used with this operation. This can be either an implicit or explicit 251 // session. If the server selected does not support sessions and Client is specified the 252 // behavior depends on the session type. If the session is implicit, the session fields will not 253 // be encoded onto the command. If the session is explicit, an error will be returned. The 254 // caller is responsible for ensuring that this field is nil if the Deployment does not support 255 // sessions. 256 Client *session.Client 257 258 // Clock is a cluster clock, different from the one contained within a session.Client. This 259 // allows updating cluster times for a global cluster clock while allowing individual session's 260 // cluster clocks to be only updated as far as the last command that's been run. 261 Clock *session.ClusterClock 262 263 // RetryMode specifies how to retry. There are three modes that enable retry: RetryOnce, 264 // RetryOncePerCommand, and RetryContext. For more information about what these modes do, please 265 // refer to their definitions. Both RetryMode and Type must be set for retryability to be enabled. 266 // If Timeout is set on the Client, the operation will automatically retry as many times as 267 // possible unless RetryNone is used. 268 RetryMode *RetryMode 269 270 // Type specifies the kind of operation this is. There is only one mode that enables retry: Write. 271 // For more information about what this mode does, please refer to it's definition. Both Type and 272 // RetryMode must be set for retryability to be enabled. 273 Type Type 274 275 // Batches contains the documents that are split when executing a write command that potentially 276 // has more documents than can fit in a single command. This should only be specified for 277 // commands that are batch compatible. For more information, please refer to the definition of 278 // Batches. 279 Batches *Batches 280 281 // Legacy sets the legacy type for this operation. There are only 3 types that require legacy 282 // support: find, getMore, and killCursors. For more information about LegacyOperationKind, 283 // please refer to it's definition. 284 Legacy LegacyOperationKind 285 286 // CommandMonitor specifies the monitor to use for APM events. If this field is not set, 287 // no events will be reported. 288 CommandMonitor *event.CommandMonitor 289 290 // Crypt specifies a Crypt object to use for automatic client side encryption and decryption. 291 Crypt Crypt 292 293 // ServerAPI specifies options used to configure the API version sent to the server. 294 ServerAPI *ServerAPIOptions 295 296 // IsOutputAggregate specifies whether this operation is an aggregate with an output stage. If true, 297 // read preference will not be added to the command on wire versions < 13. 298 IsOutputAggregate bool 299 300 // MaxTime specifies the maximum amount of time to allow the operation to run on the server. 301 MaxTime *time.Duration 302 303 // Timeout is the amount of time that this operation can execute before returning an error. The default value 304 // nil, which means that the timeout of the operation's caller will be used. 305 Timeout *time.Duration 306 307 Logger *logger.Logger 308 309 // cmdName is only set when serializing OP_MSG and is used internally in readWireMessage. 310 cmdName string 311 } 312 313 // shouldEncrypt returns true if this operation should automatically be encrypted. 314 func (op Operation) shouldEncrypt() bool { 315 return op.Crypt != nil && !op.Crypt.BypassAutoEncryption() 316 } 317 318 // selectServer handles performing server selection for an operation. 319 func (op Operation) selectServer(ctx context.Context) (Server, error) { 320 if err := op.Validate(); err != nil { 321 return nil, err 322 } 323 324 selector := op.Selector 325 if selector == nil { 326 rp := op.ReadPreference 327 if rp == nil { 328 rp = readpref.Primary() 329 } 330 selector = description.CompositeSelector([]description.ServerSelector{ 331 description.ReadPrefSelector(rp), 332 description.LatencySelector(defaultLocalThreshold), 333 }) 334 } 335 336 return op.Deployment.SelectServer(ctx, selector) 337 } 338 339 // getServerAndConnection should be used to retrieve a Server and Connection to execute an operation. 340 func (op Operation) getServerAndConnection(ctx context.Context) (Server, Connection, error) { 341 server, err := op.selectServer(ctx) 342 if err != nil { 343 if op.Client != nil && 344 !(op.Client.Committing || op.Client.Aborting) && op.Client.TransactionRunning() { 345 err = Error{ 346 Message: err.Error(), 347 Labels: []string{TransientTransactionError}, 348 Wrapped: err, 349 } 350 } 351 return nil, nil, err 352 } 353 354 // If the provided client session has a pinned connection, it should be used for the operation because this 355 // indicates that we're in a transaction and the target server is behind a load balancer. 356 if op.Client != nil && op.Client.PinnedConnection != nil { 357 return server, op.Client.PinnedConnection, nil 358 } 359 360 // Otherwise, default to checking out a connection from the server's pool. 361 conn, err := server.Connection(ctx) 362 if err != nil { 363 return nil, nil, err 364 } 365 366 // If we're in load balanced mode and this is the first operation in a transaction, pin the session to a connection. 367 if conn.Description().LoadBalanced() && op.Client != nil && op.Client.TransactionStarting() { 368 pinnedConn, ok := conn.(PinnedConnection) 369 if !ok { 370 // Close the original connection to avoid a leak. 371 _ = conn.Close() 372 return nil, nil, fmt.Errorf("expected Connection used to start a transaction to be a PinnedConnection, but got %T", conn) 373 } 374 if err := pinnedConn.PinToTransaction(); err != nil { 375 // Close the original connection to avoid a leak. 376 _ = conn.Close() 377 return nil, nil, fmt.Errorf("error incrementing connection reference count when starting a transaction: %v", err) 378 } 379 op.Client.PinnedConnection = pinnedConn 380 } 381 382 return server, conn, nil 383 } 384 385 // Validate validates this operation, ensuring the fields are set properly. 386 func (op Operation) Validate() error { 387 if op.CommandFn == nil { 388 return InvalidOperationError{MissingField: "CommandFn"} 389 } 390 if op.Deployment == nil { 391 return InvalidOperationError{MissingField: "Deployment"} 392 } 393 if op.Database == "" { 394 return errDatabaseNameEmpty 395 } 396 if op.Client != nil && !writeconcern.AckWrite(op.WriteConcern) { 397 return errors.New("session provided for an unacknowledged write") 398 } 399 return nil 400 } 401 402 var memoryPool = sync.Pool{ 403 New: func() interface{} { 404 // Start with 1kb buffers. 405 b := make([]byte, 1024) 406 // Return a pointer as the static analysis tool suggests. 407 return &b 408 }, 409 } 410 411 // Execute runs this operation. 412 func (op Operation) Execute(ctx context.Context) error { 413 err := op.Validate() 414 if err != nil { 415 return err 416 } 417 418 // If no deadline is set on the passed-in context, op.Timeout is set, and context is not already 419 // a Timeout context, honor op.Timeout in new Timeout context for operation execution. 420 if _, deadlineSet := ctx.Deadline(); !deadlineSet && op.Timeout != nil && !internal.IsTimeoutContext(ctx) { 421 newCtx, cancelFunc := internal.MakeTimeoutContext(ctx, *op.Timeout) 422 // Redefine ctx to be the new timeout-derived context. 423 ctx = newCtx 424 // Cancel the timeout-derived context at the end of Execute to avoid a context leak. 425 defer cancelFunc() 426 } 427 428 if op.Client != nil { 429 if err := op.Client.StartCommand(); err != nil { 430 return err 431 } 432 } 433 434 var retries int 435 if op.RetryMode != nil { 436 switch op.Type { 437 case Write: 438 if op.Client == nil { 439 break 440 } 441 switch *op.RetryMode { 442 case RetryOnce, RetryOncePerCommand: 443 retries = 1 444 case RetryContext: 445 retries = -1 446 } 447 case Read: 448 switch *op.RetryMode { 449 case RetryOnce, RetryOncePerCommand: 450 retries = 1 451 case RetryContext: 452 retries = -1 453 } 454 } 455 } 456 // If context is a Timeout context, automatically set retries to -1 (infinite) if retrying is 457 // enabled. 458 retryEnabled := op.RetryMode != nil && op.RetryMode.Enabled() 459 if internal.IsTimeoutContext(ctx) && retryEnabled { 460 retries = -1 461 } 462 463 var srvr Server 464 var conn Connection 465 var res bsoncore.Document 466 var operationErr WriteCommandError 467 var prevErr error 468 var prevIndefiniteErr error 469 batching := op.Batches.Valid() 470 retrySupported := false 471 first := true 472 currIndex := 0 473 474 // resetForRetry records the error that caused the retry, decrements retries, and resets the 475 // retry loop variables to request a new server and a new connection for the next attempt. 476 resetForRetry := func(err error) { 477 retries-- 478 prevErr = err 479 480 // Set the previous indefinite error to be returned in any case where a retryable write error does not have a 481 // NoWritesPerfomed label (the definite case). 482 switch err := err.(type) { 483 case labeledError: 484 // If the "prevIndefiniteErr" is nil, then the current error is the first error encountered 485 // during the retry attempt cycle. We must persist the first error in the case where all 486 // following errors are labeled "NoWritesPerformed", which would otherwise raise nil as the 487 // error. 488 if prevIndefiniteErr == nil { 489 prevIndefiniteErr = err 490 } 491 492 // If the error is not labeled NoWritesPerformed and is retryable, then set the previous 493 // indefinite error to be the current error. 494 if !err.HasErrorLabel(NoWritesPerformed) && err.HasErrorLabel(RetryableWriteError) { 495 prevIndefiniteErr = err 496 } 497 } 498 499 // If we got a connection, close it immediately to release pool resources for 500 // subsequent retries. 501 if conn != nil { 502 conn.Close() 503 } 504 // Set the server and connection to nil to request a new server and connection. 505 srvr = nil 506 conn = nil 507 } 508 509 wm := memoryPool.Get().(*[]byte) 510 defer func() { 511 // Proper usage of a sync.Pool requires each entry to have approximately the same memory 512 // cost. To obtain this property when the stored type contains a variably-sized buffer, 513 // we add a hard limit on the maximum buffer to place back in the pool. We limit the 514 // size to 16MiB because that's the maximum wire message size supported by MongoDB. 515 // 516 // Comment copied from https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/fmt/print.go;l=147 517 // 518 // Recycle byte slices that are smaller than 16MiB and at least half occupied. 519 if c := cap(*wm); c < 16*1024*1024 && c/2 < len(*wm) { 520 memoryPool.Put(wm) 521 } 522 }() 523 for { 524 // If the server or connection are nil, try to select a new server and get a new connection. 525 if srvr == nil || conn == nil { 526 srvr, conn, err = op.getServerAndConnection(ctx) 527 if err != nil { 528 // If the returned error is retryable and there are retries remaining (negative 529 // retries means retry indefinitely), then retry the operation. Set the server 530 // and connection to nil to request a new server and connection. 531 if rerr, ok := err.(RetryablePoolError); ok && rerr.Retryable() && retries != 0 { 532 resetForRetry(err) 533 continue 534 } 535 536 // If this is a retry and there's an error from a previous attempt, return the previous 537 // error instead of the current connection error. 538 if prevErr != nil { 539 return prevErr 540 } 541 return err 542 } 543 defer conn.Close() 544 545 // Set the server if it has not already been set and the session type is implicit. This will 546 // limit the number of implicit sessions to no greater than an application's maxPoolSize 547 // (ignoring operations that hold on to the session like cursors). 548 if op.Client != nil && op.Client.Server == nil && op.Client.IsImplicit { 549 if op.Client.Terminated { 550 return fmt.Errorf("unexpected nil session for a terminated implicit session") 551 } 552 if err := op.Client.SetServer(); err != nil { 553 return err 554 } 555 } 556 } 557 558 // Run steps that must only be run on the first attempt, but not again for retries. 559 if first { 560 // Determine if retries are supported for the current operation on the current server 561 // description. Per the retryable writes specification, only determine this for the 562 // first server selected: 563 // 564 // If the server selected for the first attempt of a retryable write operation does 565 // not support retryable writes, drivers MUST execute the write as if retryable writes 566 // were not enabled. 567 retrySupported = op.retryable(conn.Description()) 568 569 // If retries are supported for the current operation on the current server description, 570 // client retries are enabled, the operation type is write, and we haven't incremented 571 // the txn number yet, enable retry writes on the session and increment the txn number. 572 // Calling IncrementTxnNumber() for server descriptions or topologies that do not 573 // support retries (e.g. standalone topologies) will cause server errors. Only do this 574 // check for the first attempt to keep retried writes in the same transaction. 575 if retrySupported && op.RetryMode != nil && op.Type == Write && op.Client != nil { 576 op.Client.RetryWrite = false 577 if op.RetryMode.Enabled() { 578 op.Client.RetryWrite = true 579 if !op.Client.Committing && !op.Client.Aborting { 580 op.Client.IncrementTxnNumber() 581 } 582 } 583 } 584 585 first = false 586 } 587 588 // Calculate maxTimeMS value to potentially be appended to the wire message. 589 maxTimeMS, err := op.calculateMaxTimeMS(ctx, srvr.RTTMonitor().P90(), srvr.RTTMonitor().Stats()) 590 if err != nil { 591 return err 592 } 593 594 // Set maxTimeMS to 0 if connected to mongocryptd to avoid appending the field. The final 595 // encrypted command may contain multiple maxTimeMS fields otherwise. 596 if conn.Description().IsCryptd { 597 maxTimeMS = 0 598 } 599 600 desc := description.SelectedServer{Server: conn.Description(), Kind: op.Deployment.Kind()} 601 602 if batching { 603 targetBatchSize := desc.MaxDocumentSize 604 maxDocSize := desc.MaxDocumentSize 605 if op.shouldEncrypt() { 606 // For client-side encryption, we want the batch to be split at 2 MiB instead of 16MiB. 607 // If there's only one document in the batch, it can be up to 16MiB, so we set target batch size to 608 // 2MiB but max document size to 16MiB. This will allow the AdvanceBatch call to create a batch 609 // with a single large document. 610 targetBatchSize = cryptMaxBsonObjectSize 611 } 612 613 err = op.Batches.AdvanceBatch(int(desc.MaxBatchCount), int(targetBatchSize), int(maxDocSize)) 614 if err != nil { 615 // TODO(GODRIVER-982): Should we also be returning operationErr? 616 return err 617 } 618 } 619 620 var startedInfo startedInformation 621 *wm, startedInfo, err = op.createWireMessage(ctx, (*wm)[:0], desc, maxTimeMS, conn) 622 if err != nil { 623 return err 624 } 625 626 // set extra data and send event if possible 627 startedInfo.connID = conn.ID() 628 startedInfo.driverConnectionID = conn.DriverConnectionID() 629 startedInfo.cmdName = op.getCommandName(startedInfo.cmd) 630 op.cmdName = startedInfo.cmdName 631 startedInfo.redacted = op.redactCommand(startedInfo.cmdName, startedInfo.cmd) 632 startedInfo.serviceID = conn.Description().ServiceID 633 startedInfo.serverConnID = conn.ServerConnectionID() 634 startedInfo.serverAddress = conn.Description().Addr 635 636 op.publishStartedEvent(ctx, startedInfo) 637 638 // get the moreToCome flag information before we compress 639 moreToCome := wiremessage.IsMsgMoreToCome(*wm) 640 641 // compress wiremessage if allowed 642 if compressor, ok := conn.(Compressor); ok && op.canCompress(startedInfo.cmdName) { 643 b := memoryPool.Get().(*[]byte) 644 *b, err = compressor.CompressWireMessage(*wm, (*b)[:0]) 645 memoryPool.Put(wm) 646 wm = b 647 if err != nil { 648 return err 649 } 650 } 651 652 finishedInfo := finishedInformation{ 653 cmdName: startedInfo.cmdName, 654 driverConnectionID: startedInfo.driverConnectionID, 655 requestID: startedInfo.requestID, 656 connID: startedInfo.connID, 657 serverConnID: startedInfo.serverConnID, 658 redacted: startedInfo.redacted, 659 serviceID: startedInfo.serviceID, 660 serverAddress: desc.Server.Addr, 661 } 662 663 startedTime := time.Now() 664 665 // Check for possible context error. If no context error, check if there's enough time to perform a 666 // round trip before the Context deadline. If ctx is a Timeout Context, use the 90th percentile RTT 667 // as a threshold. Otherwise, use the minimum observed RTT. 668 if ctx.Err() != nil { 669 err = ctx.Err() 670 } else if deadline, ok := ctx.Deadline(); ok { 671 if internal.IsTimeoutContext(ctx) && time.Now().Add(srvr.RTTMonitor().P90()).After(deadline) { 672 err = internal.WrapErrorf(ErrDeadlineWouldBeExceeded, 673 "remaining time %v until context deadline is less than 90th percentile RTT\n%v", time.Until(deadline), srvr.RTTMonitor().Stats()) 674 } else if time.Now().Add(srvr.RTTMonitor().Min()).After(deadline) { 675 err = context.DeadlineExceeded 676 } 677 } 678 679 if err == nil { 680 // roundtrip using either the full roundTripper or a special one for when the moreToCome 681 // flag is set 682 roundTrip := op.roundTrip 683 if moreToCome { 684 roundTrip = op.moreToComeRoundTrip 685 } 686 res, err = roundTrip(ctx, conn, *wm) 687 688 if ep, ok := srvr.(ErrorProcessor); ok { 689 _ = ep.ProcessError(err, conn) 690 } 691 } 692 693 finishedInfo.response = res 694 finishedInfo.cmdErr = err 695 finishedInfo.duration = time.Since(startedTime) 696 697 op.publishFinishedEvent(ctx, finishedInfo) 698 699 // prevIndefiniteErrorIsSet is "true" if the "err" variable has been set to the "prevIndefiniteErr" in 700 // a case in the switch statement below. 701 var prevIndefiniteErrIsSet bool 702 703 // TODO(GODRIVER-2579): When refactoring the "Execute" method, consider creating a separate method for the 704 // error handling logic below. This will remove the necessity of the "checkError" goto label. 705 checkError: 706 var perr error 707 switch tt := err.(type) { 708 case WriteCommandError: 709 if e := err.(WriteCommandError); retrySupported && op.Type == Write && e.UnsupportedStorageEngine() { 710 return ErrUnsupportedStorageEngine 711 } 712 713 connDesc := conn.Description() 714 retryableErr := tt.Retryable(connDesc.WireVersion) 715 preRetryWriteLabelVersion := connDesc.WireVersion != nil && connDesc.WireVersion.Max < 9 716 inTransaction := op.Client != nil && 717 !(op.Client.Committing || op.Client.Aborting) && op.Client.TransactionRunning() 718 // If retry is enabled and the operation isn't in a transaction, add a RetryableWriteError label for 719 // retryable errors from pre-4.4 servers 720 if retryableErr && preRetryWriteLabelVersion && retryEnabled && !inTransaction { 721 tt.Labels = append(tt.Labels, RetryableWriteError) 722 } 723 724 // If retries are supported for the current operation on the first server description, 725 // the error is considered retryable, and there are retries remaining (negative retries 726 // means retry indefinitely), then retry the operation. 727 if retrySupported && retryableErr && retries != 0 { 728 if op.Client != nil && op.Client.Committing { 729 // Apply majority write concern for retries 730 op.Client.UpdateCommitTransactionWriteConcern() 731 op.WriteConcern = op.Client.CurrentWc 732 } 733 resetForRetry(tt) 734 continue 735 } 736 737 // If the error is no longer retryable and has the NoWritesPerformed label, then we should 738 // set the error to the "previous indefinite error" unless the current error is already the 739 // "previous indefinite error". After reseting, repeat the error check. 740 if tt.HasErrorLabel(NoWritesPerformed) && !prevIndefiniteErrIsSet { 741 err = prevIndefiniteErr 742 prevIndefiniteErrIsSet = true 743 744 goto checkError 745 } 746 747 // If the operation isn't being retried, process the response 748 if op.ProcessResponseFn != nil { 749 info := ResponseInfo{ 750 ServerResponse: res, 751 Server: srvr, 752 Connection: conn, 753 ConnectionDescription: desc.Server, 754 CurrentIndex: currIndex, 755 } 756 _ = op.ProcessResponseFn(info) 757 } 758 759 if batching && len(tt.WriteErrors) > 0 && currIndex > 0 { 760 for i := range tt.WriteErrors { 761 tt.WriteErrors[i].Index += int64(currIndex) 762 } 763 } 764 765 // If batching is enabled and either ordered is the default (which is true) or 766 // explicitly set to true and we have write errors, return the errors. 767 if batching && (op.Batches.Ordered == nil || *op.Batches.Ordered) && len(tt.WriteErrors) > 0 { 768 return tt 769 } 770 if op.Client != nil && op.Client.Committing && tt.WriteConcernError != nil { 771 // When running commitTransaction we return WriteConcernErrors as an Error. 772 err := Error{ 773 Name: tt.WriteConcernError.Name, 774 Code: int32(tt.WriteConcernError.Code), 775 Message: tt.WriteConcernError.Message, 776 Labels: tt.Labels, 777 Raw: tt.Raw, 778 } 779 // The UnknownTransactionCommitResult label is added to all writeConcernErrors besides unknownReplWriteConcernCode 780 // and unsatisfiableWriteConcernCode 781 if err.Code != unknownReplWriteConcernCode && err.Code != unsatisfiableWriteConcernCode { 782 err.Labels = append(err.Labels, UnknownTransactionCommitResult) 783 } 784 if retryableErr && retryEnabled { 785 err.Labels = append(err.Labels, RetryableWriteError) 786 } 787 return err 788 } 789 operationErr.WriteConcernError = tt.WriteConcernError 790 operationErr.WriteErrors = append(operationErr.WriteErrors, tt.WriteErrors...) 791 operationErr.Labels = tt.Labels 792 operationErr.Raw = tt.Raw 793 case Error: 794 if tt.HasErrorLabel(TransientTransactionError) || tt.HasErrorLabel(UnknownTransactionCommitResult) { 795 if err := op.Client.ClearPinnedResources(); err != nil { 796 return err 797 } 798 } 799 800 if e := err.(Error); retrySupported && op.Type == Write && e.UnsupportedStorageEngine() { 801 return ErrUnsupportedStorageEngine 802 } 803 804 connDesc := conn.Description() 805 var retryableErr bool 806 if op.Type == Write { 807 retryableErr = tt.RetryableWrite(connDesc.WireVersion) 808 preRetryWriteLabelVersion := connDesc.WireVersion != nil && connDesc.WireVersion.Max < 9 809 inTransaction := op.Client != nil && 810 !(op.Client.Committing || op.Client.Aborting) && op.Client.TransactionRunning() 811 // If retryWrites is enabled and the operation isn't in a transaction, add a RetryableWriteError label 812 // for network errors and retryable errors from pre-4.4 servers 813 if retryEnabled && !inTransaction && 814 (tt.HasErrorLabel(NetworkError) || (retryableErr && preRetryWriteLabelVersion)) { 815 tt.Labels = append(tt.Labels, RetryableWriteError) 816 } 817 } else { 818 retryableErr = tt.RetryableRead() 819 } 820 821 // If retries are supported for the current operation on the first server description, 822 // the error is considered retryable, and there are retries remaining (negative retries 823 // means retry indefinitely), then retry the operation. 824 if retrySupported && retryableErr && retries != 0 { 825 if op.Client != nil && op.Client.Committing { 826 // Apply majority write concern for retries 827 op.Client.UpdateCommitTransactionWriteConcern() 828 op.WriteConcern = op.Client.CurrentWc 829 } 830 resetForRetry(tt) 831 continue 832 } 833 834 // If the error is no longer retryable and has the NoWritesPerformed label, then we should 835 // set the error to the "previous indefinite error" unless the current error is already the 836 // "previous indefinite error". After reseting, repeat the error check. 837 if tt.HasErrorLabel(NoWritesPerformed) && !prevIndefiniteErrIsSet { 838 err = prevIndefiniteErr 839 prevIndefiniteErrIsSet = true 840 841 goto checkError 842 } 843 844 // If the operation isn't being retried, process the response 845 if op.ProcessResponseFn != nil { 846 info := ResponseInfo{ 847 ServerResponse: res, 848 Server: srvr, 849 Connection: conn, 850 ConnectionDescription: desc.Server, 851 CurrentIndex: currIndex, 852 } 853 _ = op.ProcessResponseFn(info) 854 } 855 856 if op.Client != nil && op.Client.Committing && (retryableErr || tt.Code == 50) { 857 // If we got a retryable error or MaxTimeMSExpired error, we add UnknownTransactionCommitResult. 858 tt.Labels = append(tt.Labels, UnknownTransactionCommitResult) 859 } 860 return tt 861 case nil: 862 if moreToCome { 863 return ErrUnacknowledgedWrite 864 } 865 if op.ProcessResponseFn != nil { 866 info := ResponseInfo{ 867 ServerResponse: res, 868 Server: srvr, 869 Connection: conn, 870 ConnectionDescription: desc.Server, 871 CurrentIndex: currIndex, 872 } 873 perr = op.ProcessResponseFn(info) 874 } 875 if perr != nil { 876 return perr 877 } 878 default: 879 if op.ProcessResponseFn != nil { 880 info := ResponseInfo{ 881 ServerResponse: res, 882 Server: srvr, 883 Connection: conn, 884 ConnectionDescription: desc.Server, 885 CurrentIndex: currIndex, 886 } 887 _ = op.ProcessResponseFn(info) 888 } 889 return err 890 } 891 892 // If we're batching and there are batches remaining, advance to the next batch. This isn't 893 // a retry, so increment the transaction number, reset the retries number, and don't set 894 // server or connection to nil to continue using the same connection. 895 if batching && len(op.Batches.Documents) > 0 { 896 // If retries are supported for the current operation on the current server description, 897 // the session isn't nil, and client retries are enabled, increment the txn number. 898 // Calling IncrementTxnNumber() for server descriptions or topologies that do not 899 // support retries (e.g. standalone topologies) will cause server errors. 900 if retrySupported && op.Client != nil && op.RetryMode != nil { 901 if op.RetryMode.Enabled() { 902 op.Client.IncrementTxnNumber() 903 } 904 // Reset the retries number for RetryOncePerCommand unless context is a Timeout context, in 905 // which case retries should remain as -1 (as many times as possible). 906 if *op.RetryMode == RetryOncePerCommand && !internal.IsTimeoutContext(ctx) { 907 retries = 1 908 } 909 } 910 currIndex += len(op.Batches.Current) 911 op.Batches.ClearBatch() 912 continue 913 } 914 break 915 } 916 if len(operationErr.WriteErrors) > 0 || operationErr.WriteConcernError != nil { 917 return operationErr 918 } 919 return nil 920 } 921 922 // Retryable writes are supported if the server supports sessions, the operation is not 923 // within a transaction, and the write is acknowledged 924 func (op Operation) retryable(desc description.Server) bool { 925 switch op.Type { 926 case Write: 927 if op.Client != nil && (op.Client.Committing || op.Client.Aborting) { 928 return true 929 } 930 if retryWritesSupported(desc) && 931 op.Client != nil && !(op.Client.TransactionInProgress() || op.Client.TransactionStarting()) && 932 writeconcern.AckWrite(op.WriteConcern) { 933 return true 934 } 935 case Read: 936 if op.Client != nil && (op.Client.Committing || op.Client.Aborting) { 937 return true 938 } 939 if op.Client == nil || !(op.Client.TransactionInProgress() || op.Client.TransactionStarting()) { 940 return true 941 } 942 } 943 return false 944 } 945 946 // roundTrip writes a wiremessage to the connection and then reads a wiremessage. The wm parameter 947 // is reused when reading the wiremessage. 948 func (op Operation) roundTrip(ctx context.Context, conn Connection, wm []byte) ([]byte, error) { 949 err := conn.WriteWireMessage(ctx, wm) 950 if err != nil { 951 return nil, op.networkError(err) 952 } 953 return op.readWireMessage(ctx, conn) 954 } 955 956 func (op Operation) readWireMessage(ctx context.Context, conn Connection) (result []byte, err error) { 957 wm, err := conn.ReadWireMessage(ctx) 958 if err != nil { 959 return nil, op.networkError(err) 960 } 961 962 // If we're using a streamable connection, we set its streaming state based on the moreToCome flag in the server 963 // response. 964 if streamer, ok := conn.(StreamerConnection); ok { 965 streamer.SetStreaming(wiremessage.IsMsgMoreToCome(wm)) 966 } 967 968 length, _, _, opcode, rem, ok := wiremessage.ReadHeader(wm) 969 if !ok || len(wm) < int(length) { 970 return nil, errors.New("malformed wire message: insufficient bytes") 971 } 972 if opcode == wiremessage.OpCompressed { 973 rawsize := length - 16 // remove header size 974 // decompress wiremessage 975 opcode, rem, err = op.decompressWireMessage(rem[:rawsize]) 976 if err != nil { 977 return nil, err 978 } 979 } 980 981 // decode 982 res, err := op.decodeResult(opcode, rem) 983 // Update cluster/operation time and recovery tokens before handling the error to ensure we're properly updating 984 // everything. 985 op.updateClusterTimes(res) 986 op.updateOperationTime(res) 987 op.Client.UpdateRecoveryToken(bson.Raw(res)) 988 989 // Update snapshot time if operation was a "find", "aggregate" or "distinct". 990 if op.cmdName == "find" || op.cmdName == "aggregate" || op.cmdName == "distinct" { 991 op.Client.UpdateSnapshotTime(res) 992 } 993 994 if err != nil { 995 return res, err 996 } 997 998 // If there is no error, automatically attempt to decrypt all results if client side encryption is enabled. 999 if op.Crypt != nil { 1000 res, err = op.Crypt.Decrypt(ctx, res) 1001 } 1002 return res, err 1003 } 1004 1005 // networkError wraps the provided error in an Error with label "NetworkError" and, if a transaction 1006 // is running or committing, the appropriate transaction state labels. The returned error indicates 1007 // the operation should be retried for reads and writes. If err is nil, networkError returns nil. 1008 func (op Operation) networkError(err error) error { 1009 if err == nil { 1010 return nil 1011 } 1012 1013 labels := []string{NetworkError} 1014 if op.Client != nil { 1015 op.Client.MarkDirty() 1016 } 1017 if op.Client != nil && op.Client.TransactionRunning() && !op.Client.Committing { 1018 labels = append(labels, TransientTransactionError) 1019 } 1020 if op.Client != nil && op.Client.Committing { 1021 labels = append(labels, UnknownTransactionCommitResult) 1022 } 1023 return Error{Message: err.Error(), Labels: labels, Wrapped: err} 1024 } 1025 1026 // moreToComeRoundTrip writes a wiremessage to the provided connection. This is used when an OP_MSG is 1027 // being sent with the moreToCome bit set. 1028 func (op *Operation) moreToComeRoundTrip(ctx context.Context, conn Connection, wm []byte) (result []byte, err error) { 1029 err = conn.WriteWireMessage(ctx, wm) 1030 if err != nil { 1031 if op.Client != nil { 1032 op.Client.MarkDirty() 1033 } 1034 err = Error{Message: err.Error(), Labels: []string{TransientTransactionError, NetworkError}, Wrapped: err} 1035 } 1036 return bsoncore.BuildDocument(nil, bsoncore.AppendInt32Element(nil, "ok", 1)), err 1037 } 1038 1039 // decompressWireMessage handles decompressing a wiremessage without the header. 1040 func (Operation) decompressWireMessage(wm []byte) (wiremessage.OpCode, []byte, error) { 1041 // get the original opcode and uncompressed size 1042 opcode, rem, ok := wiremessage.ReadCompressedOriginalOpCode(wm) 1043 if !ok { 1044 return 0, nil, errors.New("malformed OP_COMPRESSED: missing original opcode") 1045 } 1046 uncompressedSize, rem, ok := wiremessage.ReadCompressedUncompressedSize(rem) 1047 if !ok { 1048 return 0, nil, errors.New("malformed OP_COMPRESSED: missing uncompressed size") 1049 } 1050 // get the compressor ID and decompress the message 1051 compressorID, rem, ok := wiremessage.ReadCompressedCompressorID(rem) 1052 if !ok { 1053 return 0, nil, errors.New("malformed OP_COMPRESSED: missing compressor ID") 1054 } 1055 compressedSize := len(wm) - 9 // original opcode (4) + uncompressed size (4) + compressor ID (1) 1056 // return the original wiremessage 1057 msg, _, ok := wiremessage.ReadCompressedCompressedMessage(rem, int32(compressedSize)) 1058 if !ok { 1059 return 0, nil, errors.New("malformed OP_COMPRESSED: insufficient bytes for compressed wiremessage") 1060 } 1061 1062 opts := CompressionOpts{ 1063 Compressor: compressorID, 1064 UncompressedSize: uncompressedSize, 1065 } 1066 uncompressed, err := DecompressPayload(msg, opts) 1067 if err != nil { 1068 return 0, nil, err 1069 } 1070 1071 return opcode, uncompressed, nil 1072 } 1073 1074 func (op Operation) createWireMessage( 1075 ctx context.Context, 1076 dst []byte, 1077 desc description.SelectedServer, 1078 maxTimeMS uint64, 1079 conn Connection, 1080 ) ([]byte, startedInformation, error) { 1081 // If topology is not LoadBalanced, API version is not declared, and wire version is unknown 1082 // or less than 6, use OP_QUERY. Otherwise, use OP_MSG. 1083 if desc.Kind != description.LoadBalanced && op.ServerAPI == nil && 1084 (desc.WireVersion == nil || desc.WireVersion.Max < wiremessage.OpmsgWireVersion) { 1085 return op.createQueryWireMessage(maxTimeMS, dst, desc) 1086 } 1087 return op.createMsgWireMessage(ctx, maxTimeMS, dst, desc, conn) 1088 } 1089 1090 func (op Operation) addBatchArray(dst []byte) []byte { 1091 aidx, dst := bsoncore.AppendArrayElementStart(dst, op.Batches.Identifier) 1092 for i, doc := range op.Batches.Current { 1093 dst = bsoncore.AppendDocumentElement(dst, strconv.Itoa(i), doc) 1094 } 1095 dst, _ = bsoncore.AppendArrayEnd(dst, aidx) 1096 return dst 1097 } 1098 1099 func (op Operation) createQueryWireMessage(maxTimeMS uint64, dst []byte, desc description.SelectedServer) ([]byte, startedInformation, error) { 1100 var info startedInformation 1101 flags := op.secondaryOK(desc) 1102 var wmindex int32 1103 info.requestID = wiremessage.NextRequestID() 1104 wmindex, dst = wiremessage.AppendHeaderStart(dst, info.requestID, 0, wiremessage.OpQuery) 1105 dst = wiremessage.AppendQueryFlags(dst, flags) 1106 // FullCollectionName 1107 dst = append(dst, op.Database...) 1108 dst = append(dst, dollarCmd[:]...) 1109 dst = append(dst, 0x00) 1110 dst = wiremessage.AppendQueryNumberToSkip(dst, 0) 1111 dst = wiremessage.AppendQueryNumberToReturn(dst, -1) 1112 1113 wrapper := int32(-1) 1114 rp, err := op.createReadPref(desc, true) 1115 if err != nil { 1116 return dst, info, err 1117 } 1118 if len(rp) > 0 { 1119 wrapper, dst = bsoncore.AppendDocumentStart(dst) 1120 dst = bsoncore.AppendHeader(dst, bsontype.EmbeddedDocument, "$query") 1121 } 1122 idx, dst := bsoncore.AppendDocumentStart(dst) 1123 dst, err = op.CommandFn(dst, desc) 1124 if err != nil { 1125 return dst, info, err 1126 } 1127 1128 if op.Batches != nil && len(op.Batches.Current) > 0 { 1129 dst = op.addBatchArray(dst) 1130 } 1131 1132 dst, err = op.addReadConcern(dst, desc) 1133 if err != nil { 1134 return dst, info, err 1135 } 1136 1137 dst, err = op.addWriteConcern(dst, desc) 1138 if err != nil { 1139 return dst, info, err 1140 } 1141 1142 dst, err = op.addSession(dst, desc) 1143 if err != nil { 1144 return dst, info, err 1145 } 1146 1147 dst = op.addClusterTime(dst, desc) 1148 dst = op.addServerAPI(dst) 1149 // If maxTimeMS is greater than 0 append it to wire message. A maxTimeMS value of 0 only explicitly 1150 // specifies the default behavior of no timeout server-side. 1151 if maxTimeMS > 0 { 1152 dst = bsoncore.AppendInt64Element(dst, "maxTimeMS", int64(maxTimeMS)) 1153 } 1154 1155 dst, _ = bsoncore.AppendDocumentEnd(dst, idx) 1156 // Command monitoring only reports the document inside $query 1157 info.cmd = dst[idx:] 1158 1159 if len(rp) > 0 { 1160 var err error 1161 dst = bsoncore.AppendDocumentElement(dst, "$readPreference", rp) 1162 dst, err = bsoncore.AppendDocumentEnd(dst, wrapper) 1163 if err != nil { 1164 return dst, info, err 1165 } 1166 } 1167 1168 return bsoncore.UpdateLength(dst, wmindex, int32(len(dst[wmindex:]))), info, nil 1169 } 1170 1171 func (op Operation) createMsgWireMessage(ctx context.Context, maxTimeMS uint64, dst []byte, desc description.SelectedServer, 1172 conn Connection, 1173 ) ([]byte, startedInformation, error) { 1174 var info startedInformation 1175 var flags wiremessage.MsgFlag 1176 var wmindex int32 1177 // We set the MoreToCome bit if we have a write concern, it's unacknowledged, and we either 1178 // aren't batching or we are encoding the last batch. 1179 if op.WriteConcern != nil && !writeconcern.AckWrite(op.WriteConcern) && (op.Batches == nil || len(op.Batches.Documents) == 0) { 1180 flags = wiremessage.MoreToCome 1181 } 1182 // Set the ExhaustAllowed flag if the connection supports streaming. This will tell the server that it can 1183 // respond with the MoreToCome flag and then stream responses over this connection. 1184 if streamer, ok := conn.(StreamerConnection); ok && streamer.SupportsStreaming() { 1185 flags |= wiremessage.ExhaustAllowed 1186 } 1187 1188 info.requestID = wiremessage.NextRequestID() 1189 wmindex, dst = wiremessage.AppendHeaderStart(dst, info.requestID, 0, wiremessage.OpMsg) 1190 dst = wiremessage.AppendMsgFlags(dst, flags) 1191 // Body 1192 dst = wiremessage.AppendMsgSectionType(dst, wiremessage.SingleDocument) 1193 1194 idx, dst := bsoncore.AppendDocumentStart(dst) 1195 1196 dst, err := op.addCommandFields(ctx, dst, desc) 1197 if err != nil { 1198 return dst, info, err 1199 } 1200 dst, err = op.addReadConcern(dst, desc) 1201 if err != nil { 1202 return dst, info, err 1203 } 1204 dst, err = op.addWriteConcern(dst, desc) 1205 if err != nil { 1206 return dst, info, err 1207 } 1208 dst, err = op.addSession(dst, desc) 1209 if err != nil { 1210 return dst, info, err 1211 } 1212 1213 dst = op.addClusterTime(dst, desc) 1214 dst = op.addServerAPI(dst) 1215 // If maxTimeMS is greater than 0 append it to wire message. A maxTimeMS value of 0 only explicitly 1216 // specifies the default behavior of no timeout server-side. 1217 if maxTimeMS > 0 { 1218 dst = bsoncore.AppendInt64Element(dst, "maxTimeMS", int64(maxTimeMS)) 1219 } 1220 1221 dst = bsoncore.AppendStringElement(dst, "$db", op.Database) 1222 rp, err := op.createReadPref(desc, false) 1223 if err != nil { 1224 return dst, info, err 1225 } 1226 if len(rp) > 0 { 1227 dst = bsoncore.AppendDocumentElement(dst, "$readPreference", rp) 1228 } 1229 1230 dst, _ = bsoncore.AppendDocumentEnd(dst, idx) 1231 // The command document for monitoring shouldn't include the type 1 payload as a document sequence 1232 info.cmd = dst[idx:] 1233 1234 // add batch as a document sequence if auto encryption is not enabled 1235 // if auto encryption is enabled, the batch will already be an array in the command document 1236 if !op.shouldEncrypt() && op.Batches != nil && len(op.Batches.Current) > 0 { 1237 info.documentSequenceIncluded = true 1238 dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence) 1239 idx, dst = bsoncore.ReserveLength(dst) 1240 1241 dst = append(dst, op.Batches.Identifier...) 1242 dst = append(dst, 0x00) 1243 1244 for _, doc := range op.Batches.Current { 1245 dst = append(dst, doc...) 1246 } 1247 1248 dst = bsoncore.UpdateLength(dst, idx, int32(len(dst[idx:]))) 1249 } 1250 1251 return bsoncore.UpdateLength(dst, wmindex, int32(len(dst[wmindex:]))), info, nil 1252 } 1253 1254 // addCommandFields adds the fields for a command to the wire message in dst. This assumes that the start of the document 1255 // has already been added and does not add the final 0 byte. 1256 func (op Operation) addCommandFields(ctx context.Context, dst []byte, desc description.SelectedServer) ([]byte, error) { 1257 if !op.shouldEncrypt() { 1258 return op.CommandFn(dst, desc) 1259 } 1260 1261 if desc.WireVersion.Max < cryptMinWireVersion { 1262 return dst, errors.New("auto-encryption requires a MongoDB version of 4.2") 1263 } 1264 1265 // create temporary command document 1266 cidx, cmdDst := bsoncore.AppendDocumentStart(nil) 1267 var err error 1268 cmdDst, err = op.CommandFn(cmdDst, desc) 1269 if err != nil { 1270 return dst, err 1271 } 1272 // use a BSON array instead of a type 1 payload because mongocryptd will convert to arrays regardless 1273 if op.Batches != nil && len(op.Batches.Current) > 0 { 1274 cmdDst = op.addBatchArray(cmdDst) 1275 } 1276 cmdDst, _ = bsoncore.AppendDocumentEnd(cmdDst, cidx) 1277 1278 // encrypt the command 1279 encrypted, err := op.Crypt.Encrypt(ctx, op.Database, cmdDst) 1280 if err != nil { 1281 return dst, err 1282 } 1283 // append encrypted command to original destination, removing the first 4 bytes (length) and final byte (terminator) 1284 dst = append(dst, encrypted[4:len(encrypted)-1]...) 1285 return dst, nil 1286 } 1287 1288 // addServerAPI adds the relevant fields for server API specification to the wire message in dst. 1289 func (op Operation) addServerAPI(dst []byte) []byte { 1290 sa := op.ServerAPI 1291 if sa == nil { 1292 return dst 1293 } 1294 1295 dst = bsoncore.AppendStringElement(dst, "apiVersion", sa.ServerAPIVersion) 1296 if sa.Strict != nil { 1297 dst = bsoncore.AppendBooleanElement(dst, "apiStrict", *sa.Strict) 1298 } 1299 if sa.DeprecationErrors != nil { 1300 dst = bsoncore.AppendBooleanElement(dst, "apiDeprecationErrors", *sa.DeprecationErrors) 1301 } 1302 return dst 1303 } 1304 1305 func (op Operation) addReadConcern(dst []byte, desc description.SelectedServer) ([]byte, error) { 1306 if op.MinimumReadConcernWireVersion > 0 && (desc.WireVersion == nil || !desc.WireVersion.Includes(op.MinimumReadConcernWireVersion)) { 1307 return dst, nil 1308 } 1309 rc := op.ReadConcern 1310 client := op.Client 1311 // Starting transaction's read concern overrides all others 1312 if client != nil && client.TransactionStarting() && client.CurrentRc != nil { 1313 rc = client.CurrentRc 1314 } 1315 1316 // start transaction must append afterclustertime IF causally consistent and operation time exists 1317 if rc == nil && client != nil && client.TransactionStarting() && client.Consistent && client.OperationTime != nil { 1318 rc = readconcern.New() 1319 } 1320 1321 if client != nil && client.Snapshot { 1322 if desc.WireVersion.Max < readSnapshotMinWireVersion { 1323 return dst, errors.New("snapshot reads require MongoDB 5.0 or later") 1324 } 1325 rc = readconcern.Snapshot() 1326 } 1327 1328 if rc == nil { 1329 return dst, nil 1330 } 1331 1332 _, data, err := rc.MarshalBSONValue() // always returns a document 1333 if err != nil { 1334 return dst, err 1335 } 1336 1337 if sessionsSupported(desc.WireVersion) && client != nil { 1338 if client.Consistent && client.OperationTime != nil { 1339 data = data[:len(data)-1] // remove the null byte 1340 data = bsoncore.AppendTimestampElement(data, "afterClusterTime", client.OperationTime.T, client.OperationTime.I) 1341 data, _ = bsoncore.AppendDocumentEnd(data, 0) 1342 } 1343 if client.Snapshot && client.SnapshotTime != nil { 1344 data = data[:len(data)-1] // remove the null byte 1345 data = bsoncore.AppendTimestampElement(data, "atClusterTime", client.SnapshotTime.T, client.SnapshotTime.I) 1346 data, _ = bsoncore.AppendDocumentEnd(data, 0) 1347 } 1348 } 1349 1350 if len(data) == bsoncore.EmptyDocumentLength { 1351 return dst, nil 1352 } 1353 return bsoncore.AppendDocumentElement(dst, "readConcern", data), nil 1354 } 1355 1356 func (op Operation) addWriteConcern(dst []byte, desc description.SelectedServer) ([]byte, error) { 1357 if op.MinimumWriteConcernWireVersion > 0 && (desc.WireVersion == nil || !desc.WireVersion.Includes(op.MinimumWriteConcernWireVersion)) { 1358 return dst, nil 1359 } 1360 wc := op.WriteConcern 1361 if wc == nil { 1362 return dst, nil 1363 } 1364 1365 t, data, err := wc.MarshalBSONValue() 1366 if err == writeconcern.ErrEmptyWriteConcern { 1367 return dst, nil 1368 } 1369 if err != nil { 1370 return dst, err 1371 } 1372 1373 return append(bsoncore.AppendHeader(dst, t, "writeConcern"), data...), nil 1374 } 1375 1376 func (op Operation) addSession(dst []byte, desc description.SelectedServer) ([]byte, error) { 1377 client := op.Client 1378 if client == nil || !sessionsSupported(desc.WireVersion) || desc.SessionTimeoutMinutes == 0 { 1379 return dst, nil 1380 } 1381 if err := client.UpdateUseTime(); err != nil { 1382 return dst, err 1383 } 1384 dst = bsoncore.AppendDocumentElement(dst, "lsid", client.SessionID) 1385 1386 var addedTxnNumber bool 1387 if op.Type == Write && client.RetryWrite { 1388 addedTxnNumber = true 1389 dst = bsoncore.AppendInt64Element(dst, "txnNumber", op.Client.TxnNumber) 1390 } 1391 if client.TransactionRunning() || client.RetryingCommit { 1392 if !addedTxnNumber { 1393 dst = bsoncore.AppendInt64Element(dst, "txnNumber", op.Client.TxnNumber) 1394 } 1395 if client.TransactionStarting() { 1396 dst = bsoncore.AppendBooleanElement(dst, "startTransaction", true) 1397 } 1398 dst = bsoncore.AppendBooleanElement(dst, "autocommit", false) 1399 } 1400 1401 return dst, client.ApplyCommand(desc.Server) 1402 } 1403 1404 func (op Operation) addClusterTime(dst []byte, desc description.SelectedServer) []byte { 1405 client, clock := op.Client, op.Clock 1406 if (clock == nil && client == nil) || !sessionsSupported(desc.WireVersion) { 1407 return dst 1408 } 1409 clusterTime := clock.GetClusterTime() 1410 if client != nil { 1411 clusterTime = session.MaxClusterTime(clusterTime, client.ClusterTime) 1412 } 1413 if clusterTime == nil { 1414 return dst 1415 } 1416 val, err := clusterTime.LookupErr("$clusterTime") 1417 if err != nil { 1418 return dst 1419 } 1420 return append(bsoncore.AppendHeader(dst, val.Type, "$clusterTime"), val.Value...) 1421 // return bsoncore.AppendDocumentElement(dst, "$clusterTime", clusterTime) 1422 } 1423 1424 // calculateMaxTimeMS calculates the value of the 'maxTimeMS' field to potentially append 1425 // to the wire message based on the current context's deadline and the 90th percentile RTT 1426 // if the ctx is a Timeout context. If the context is not a Timeout context, it uses the 1427 // operation's MaxTimeMS if set. If no MaxTimeMS is set on the operation, and context is 1428 // not a Timeout context, calculateMaxTimeMS returns 0. 1429 func (op Operation) calculateMaxTimeMS(ctx context.Context, rtt90 time.Duration, rttStats string) (uint64, error) { 1430 if internal.IsTimeoutContext(ctx) { 1431 if deadline, ok := ctx.Deadline(); ok { 1432 remainingTimeout := time.Until(deadline) 1433 maxTime := remainingTimeout - rtt90 1434 1435 // Always round up to the next millisecond value so we never truncate the calculated 1436 // maxTimeMS value (e.g. 400 microseconds evaluates to 1ms, not 0ms). 1437 maxTimeMS := int64((maxTime + (time.Millisecond - 1)) / time.Millisecond) 1438 if maxTimeMS <= 0 { 1439 return 0, internal.WrapErrorf(ErrDeadlineWouldBeExceeded, 1440 "remaining time %v until context deadline is less than or equal to 90th percentile RTT\n%v", 1441 remainingTimeout, rttStats) 1442 } 1443 return uint64(maxTimeMS), nil 1444 } 1445 } else if op.MaxTime != nil { 1446 // Users are not allowed to pass a negative value as MaxTime. A value of 0 would indicate 1447 // no timeout and is allowed. 1448 if *op.MaxTime < 0 { 1449 return 0, ErrNegativeMaxTime 1450 } 1451 // Always round up to the next millisecond value so we never truncate the requested 1452 // MaxTime value (e.g. 400 microseconds evaluates to 1ms, not 0ms). 1453 return uint64((*op.MaxTime + (time.Millisecond - 1)) / time.Millisecond), nil 1454 } 1455 return 0, nil 1456 } 1457 1458 // updateClusterTimes updates the cluster times for the session and cluster clock attached to this 1459 // operation. While the session's AdvanceClusterTime may return an error, this method does not 1460 // because an error being returned from this method will not be returned further up. 1461 func (op Operation) updateClusterTimes(response bsoncore.Document) { 1462 // Extract cluster time. 1463 value, err := response.LookupErr("$clusterTime") 1464 if err != nil { 1465 // $clusterTime not included by the server 1466 return 1467 } 1468 clusterTime := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendValueElement(nil, "$clusterTime", value)) 1469 1470 sess, clock := op.Client, op.Clock 1471 1472 if sess != nil { 1473 _ = sess.AdvanceClusterTime(bson.Raw(clusterTime)) 1474 } 1475 1476 if clock != nil { 1477 clock.AdvanceClusterTime(bson.Raw(clusterTime)) 1478 } 1479 } 1480 1481 // updateOperationTime updates the operation time on the session attached to this operation. While 1482 // the session's AdvanceOperationTime method may return an error, this method does not because an 1483 // error being returned from this method will not be returned further up. 1484 func (op Operation) updateOperationTime(response bsoncore.Document) { 1485 sess := op.Client 1486 if sess == nil { 1487 return 1488 } 1489 1490 opTimeElem, err := response.LookupErr("operationTime") 1491 if err != nil { 1492 // operationTime not included by the server 1493 return 1494 } 1495 1496 t, i := opTimeElem.Timestamp() 1497 _ = sess.AdvanceOperationTime(&primitive.Timestamp{ 1498 T: t, 1499 I: i, 1500 }) 1501 } 1502 1503 func (op Operation) getReadPrefBasedOnTransaction() (*readpref.ReadPref, error) { 1504 if op.Client != nil && op.Client.TransactionRunning() { 1505 // Transaction's read preference always takes priority 1506 rp := op.Client.CurrentRp 1507 // Reads in a transaction must have read preference primary 1508 // This must not be checked in startTransaction 1509 if rp != nil && !op.Client.TransactionStarting() && rp.Mode() != readpref.PrimaryMode { 1510 return nil, ErrNonPrimaryReadPref 1511 } 1512 return rp, nil 1513 } 1514 return op.ReadPreference, nil 1515 } 1516 1517 func (op Operation) createReadPref(desc description.SelectedServer, isOpQuery bool) (bsoncore.Document, error) { 1518 // TODO(GODRIVER-2231): Instead of checking if isOutputAggregate and desc.Server.WireVersion.Max < 13, somehow check 1519 // TODO if supplied readPreference was "overwritten" with primary in description.selectForReplicaSet. 1520 if desc.Server.Kind == description.Standalone || (isOpQuery && desc.Server.Kind != description.Mongos) || 1521 op.Type == Write || (op.IsOutputAggregate && desc.Server.WireVersion.Max < 13) { 1522 // Don't send read preference for: 1523 // 1. all standalones 1524 // 2. non-mongos when using OP_QUERY 1525 // 3. all writes 1526 // 4. when operation is an aggregate with an output stage, and selected server's wire 1527 // version is < 13 1528 return nil, nil 1529 } 1530 1531 idx, doc := bsoncore.AppendDocumentStart(nil) 1532 rp, err := op.getReadPrefBasedOnTransaction() 1533 if err != nil { 1534 return nil, err 1535 } 1536 1537 if rp == nil { 1538 if desc.Kind == description.Single && desc.Server.Kind != description.Mongos { 1539 doc = bsoncore.AppendStringElement(doc, "mode", "primaryPreferred") 1540 doc, _ = bsoncore.AppendDocumentEnd(doc, idx) 1541 return doc, nil 1542 } 1543 return nil, nil 1544 } 1545 1546 switch rp.Mode() { 1547 case readpref.PrimaryMode: 1548 if desc.Server.Kind == description.Mongos { 1549 return nil, nil 1550 } 1551 if desc.Kind == description.Single { 1552 doc = bsoncore.AppendStringElement(doc, "mode", "primaryPreferred") 1553 doc, _ = bsoncore.AppendDocumentEnd(doc, idx) 1554 return doc, nil 1555 } 1556 doc = bsoncore.AppendStringElement(doc, "mode", "primary") 1557 case readpref.PrimaryPreferredMode: 1558 doc = bsoncore.AppendStringElement(doc, "mode", "primaryPreferred") 1559 case readpref.SecondaryPreferredMode: 1560 _, ok := rp.MaxStaleness() 1561 if desc.Server.Kind == description.Mongos && isOpQuery && !ok && len(rp.TagSets()) == 0 && rp.HedgeEnabled() == nil { 1562 return nil, nil 1563 } 1564 doc = bsoncore.AppendStringElement(doc, "mode", "secondaryPreferred") 1565 case readpref.SecondaryMode: 1566 doc = bsoncore.AppendStringElement(doc, "mode", "secondary") 1567 case readpref.NearestMode: 1568 doc = bsoncore.AppendStringElement(doc, "mode", "nearest") 1569 } 1570 1571 sets := make([]bsoncore.Document, 0, len(rp.TagSets())) 1572 for _, ts := range rp.TagSets() { 1573 i, set := bsoncore.AppendDocumentStart(nil) 1574 for _, t := range ts { 1575 set = bsoncore.AppendStringElement(set, t.Name, t.Value) 1576 } 1577 set, _ = bsoncore.AppendDocumentEnd(set, i) 1578 sets = append(sets, set) 1579 } 1580 if len(sets) > 0 { 1581 var aidx int32 1582 aidx, doc = bsoncore.AppendArrayElementStart(doc, "tags") 1583 for i, set := range sets { 1584 doc = bsoncore.AppendDocumentElement(doc, strconv.Itoa(i), set) 1585 } 1586 doc, _ = bsoncore.AppendArrayEnd(doc, aidx) 1587 } 1588 1589 if d, ok := rp.MaxStaleness(); ok { 1590 doc = bsoncore.AppendInt32Element(doc, "maxStalenessSeconds", int32(d.Seconds())) 1591 } 1592 1593 if hedgeEnabled := rp.HedgeEnabled(); hedgeEnabled != nil { 1594 var hedgeIdx int32 1595 hedgeIdx, doc = bsoncore.AppendDocumentElementStart(doc, "hedge") 1596 doc = bsoncore.AppendBooleanElement(doc, "enabled", *hedgeEnabled) 1597 doc, err = bsoncore.AppendDocumentEnd(doc, hedgeIdx) 1598 if err != nil { 1599 return nil, fmt.Errorf("error creating hedge document: %v", err) 1600 } 1601 } 1602 1603 doc, _ = bsoncore.AppendDocumentEnd(doc, idx) 1604 return doc, nil 1605 } 1606 1607 func (op Operation) secondaryOK(desc description.SelectedServer) wiremessage.QueryFlag { 1608 if desc.Kind == description.Single && desc.Server.Kind != description.Mongos { 1609 return wiremessage.SecondaryOK 1610 } 1611 1612 if rp := op.ReadPreference; rp != nil && rp.Mode() != readpref.PrimaryMode { 1613 return wiremessage.SecondaryOK 1614 } 1615 1616 return 0 1617 } 1618 1619 func (Operation) canCompress(cmd string) bool { 1620 if cmd == internal.LegacyHello || cmd == "hello" || cmd == "saslStart" || cmd == "saslContinue" || cmd == "getnonce" || cmd == "authenticate" || 1621 cmd == "createUser" || cmd == "updateUser" || cmd == "copydbSaslStart" || cmd == "copydbgetnonce" || cmd == "copydb" { 1622 return false 1623 } 1624 return true 1625 } 1626 1627 // decodeOpReply extracts the necessary information from an OP_REPLY wire message. 1628 // Returns the decoded OP_REPLY. If the err field of the returned opReply is non-nil, an error occurred while decoding 1629 // or validating the response and the other fields are undefined. 1630 func (Operation) decodeOpReply(wm []byte) opReply { 1631 var reply opReply 1632 var ok bool 1633 1634 reply.responseFlags, wm, ok = wiremessage.ReadReplyFlags(wm) 1635 if !ok { 1636 reply.err = errors.New("malformed OP_REPLY: missing flags") 1637 return reply 1638 } 1639 reply.cursorID, wm, ok = wiremessage.ReadReplyCursorID(wm) 1640 if !ok { 1641 reply.err = errors.New("malformed OP_REPLY: missing cursorID") 1642 return reply 1643 } 1644 reply.startingFrom, wm, ok = wiremessage.ReadReplyStartingFrom(wm) 1645 if !ok { 1646 reply.err = errors.New("malformed OP_REPLY: missing startingFrom") 1647 return reply 1648 } 1649 reply.numReturned, wm, ok = wiremessage.ReadReplyNumberReturned(wm) 1650 if !ok { 1651 reply.err = errors.New("malformed OP_REPLY: missing numberReturned") 1652 return reply 1653 } 1654 reply.documents, _, ok = wiremessage.ReadReplyDocuments(wm) 1655 if !ok { 1656 reply.err = errors.New("malformed OP_REPLY: could not read documents from reply") 1657 } 1658 1659 if reply.responseFlags&wiremessage.QueryFailure == wiremessage.QueryFailure { 1660 reply.err = QueryFailureError{ 1661 Message: "command failure", 1662 Response: reply.documents[0], 1663 } 1664 return reply 1665 } 1666 if reply.responseFlags&wiremessage.CursorNotFound == wiremessage.CursorNotFound { 1667 reply.err = ErrCursorNotFound 1668 return reply 1669 } 1670 if reply.numReturned != int32(len(reply.documents)) { 1671 reply.err = ErrReplyDocumentMismatch 1672 return reply 1673 } 1674 1675 return reply 1676 } 1677 1678 func (op Operation) decodeResult(opcode wiremessage.OpCode, wm []byte) (bsoncore.Document, error) { 1679 switch opcode { 1680 case wiremessage.OpReply: 1681 reply := op.decodeOpReply(wm) 1682 if reply.err != nil { 1683 return nil, reply.err 1684 } 1685 if reply.numReturned == 0 { 1686 return nil, ErrNoDocCommandResponse 1687 } 1688 if reply.numReturned > 1 { 1689 return nil, ErrMultiDocCommandResponse 1690 } 1691 rdr := reply.documents[0] 1692 if err := rdr.Validate(); err != nil { 1693 return nil, NewCommandResponseError("malformed OP_REPLY: invalid document", err) 1694 } 1695 1696 return rdr, ExtractErrorFromServerResponse(rdr) 1697 case wiremessage.OpMsg: 1698 _, wm, ok := wiremessage.ReadMsgFlags(wm) 1699 if !ok { 1700 return nil, errors.New("malformed wire message: missing OP_MSG flags") 1701 } 1702 1703 var res bsoncore.Document 1704 for len(wm) > 0 { 1705 var stype wiremessage.SectionType 1706 stype, wm, ok = wiremessage.ReadMsgSectionType(wm) 1707 if !ok { 1708 return nil, errors.New("malformed wire message: insuffienct bytes to read section type") 1709 } 1710 1711 switch stype { 1712 case wiremessage.SingleDocument: 1713 res, wm, ok = wiremessage.ReadMsgSectionSingleDocument(wm) 1714 if !ok { 1715 return nil, errors.New("malformed wire message: insufficient bytes to read single document") 1716 } 1717 case wiremessage.DocumentSequence: 1718 // TODO(GODRIVER-617): Implement document sequence returns. 1719 _, _, wm, ok = wiremessage.ReadMsgSectionDocumentSequence(wm) 1720 if !ok { 1721 return nil, errors.New("malformed wire message: insufficient bytes to read document sequence") 1722 } 1723 default: 1724 return nil, fmt.Errorf("malformed wire message: uknown section type %v", stype) 1725 } 1726 } 1727 1728 err := res.Validate() 1729 if err != nil { 1730 return nil, NewCommandResponseError("malformed OP_MSG: invalid document", err) 1731 } 1732 1733 return res, ExtractErrorFromServerResponse(res) 1734 default: 1735 return nil, fmt.Errorf("cannot decode result from %s", opcode) 1736 } 1737 } 1738 1739 // getCommandName returns the name of the command from the given BSON document. 1740 func (op Operation) getCommandName(doc []byte) string { 1741 // skip 4 bytes for document length and 1 byte for element type 1742 idx := bytes.IndexByte(doc[5:], 0x00) // look for the 0 byte after the command name 1743 return string(doc[5 : idx+5]) 1744 } 1745 1746 func (op *Operation) redactCommand(cmd string, doc bsoncore.Document) bool { 1747 if cmd == "authenticate" || cmd == "saslStart" || cmd == "saslContinue" || cmd == "getnonce" || cmd == "createUser" || 1748 cmd == "updateUser" || cmd == "copydbgetnonce" || cmd == "copydbsaslstart" || cmd == "copydb" { 1749 1750 return true 1751 } 1752 if strings.ToLower(cmd) != internal.LegacyHelloLowercase && cmd != "hello" { 1753 return false 1754 } 1755 1756 // A hello without speculative authentication can be monitored. 1757 _, err := doc.LookupErr("speculativeAuthenticate") 1758 return err == nil 1759 } 1760 1761 // canLogCommandMessage returns true if the command can be logged. 1762 func (op Operation) canLogCommandMessage() bool { 1763 return op.Logger != nil && op.Logger.LevelComponentEnabled(logger.LevelDebug, logger.ComponentCommand) 1764 } 1765 1766 func (op Operation) canPublishStartedEvent() bool { 1767 return op.CommandMonitor != nil && op.CommandMonitor.Started != nil 1768 } 1769 1770 // publishStartedEvent publishes a CommandStartedEvent to the operation's command monitor if possible. If the command is 1771 // an unacknowledged write, a CommandSucceededEvent will be published as well. If started events are not being monitored, 1772 // no events are published. 1773 func (op Operation) publishStartedEvent(ctx context.Context, info startedInformation) { 1774 // If logging is enabled for the command component at the debug level, log the command response. 1775 if op.canLogCommandMessage() { 1776 host, port, _ := net.SplitHostPort(info.serverAddress.String()) 1777 1778 redactedCmd := redactStartedInformationCmd(op, info).String() 1779 formattedCmd := logger.FormatMessage(redactedCmd, op.Logger.MaxDocumentLength) 1780 1781 op.Logger.Print(logger.LevelDebug, 1782 logger.ComponentCommand, 1783 logger.CommandStarted, 1784 logger.SerializeCommand(logger.Command{ 1785 DriverConnectionID: info.driverConnectionID, 1786 Message: logger.CommandStarted, 1787 Name: info.cmdName, 1788 RequestID: int64(info.requestID), 1789 ServerConnectionID: info.serverConnID, 1790 ServerHost: host, 1791 ServerPort: port, 1792 ServiceID: info.serviceID, 1793 }, 1794 logger.KeyCommand, formattedCmd, 1795 logger.KeyDatabaseName, op.Database)...) 1796 1797 } 1798 1799 if op.canPublishStartedEvent() { 1800 started := &event.CommandStartedEvent{ 1801 Command: redactStartedInformationCmd(op, info), 1802 DatabaseName: op.Database, 1803 CommandName: info.cmdName, 1804 RequestID: int64(info.requestID), 1805 ConnectionID: info.connID, 1806 ServerConnectionID: convertInt64PtrToInt32Ptr(info.serverConnID), 1807 ServerConnectionID64: info.serverConnID, 1808 ServiceID: info.serviceID, 1809 } 1810 op.CommandMonitor.Started(ctx, started) 1811 } 1812 } 1813 1814 // canPublishSucceededEvent returns true if a CommandSucceededEvent can be 1815 // published for the given command. This is true if the command is not an 1816 // unacknowledged write and the command monitor is monitoring succeeded events. 1817 func (op Operation) canPublishFinishedEvent(info finishedInformation) bool { 1818 success := info.success() 1819 1820 return op.CommandMonitor != nil && 1821 (!success || op.CommandMonitor.Succeeded != nil) && 1822 (success || op.CommandMonitor.Failed != nil) 1823 } 1824 1825 // publishFinishedEvent publishes either a CommandSucceededEvent or a CommandFailedEvent to the operation's command 1826 // monitor if possible. If success/failure events aren't being monitored, no events are published. 1827 func (op Operation) publishFinishedEvent(ctx context.Context, info finishedInformation) { 1828 if op.canLogCommandMessage() && info.success() { 1829 host, port, _ := net.SplitHostPort(info.serverAddress.String()) 1830 1831 redactedReply := redactFinishedInformationResponse(info).String() 1832 formattedReply := logger.FormatMessage(redactedReply, op.Logger.MaxDocumentLength) 1833 1834 op.Logger.Print(logger.LevelDebug, 1835 logger.ComponentCommand, 1836 logger.CommandSucceeded, 1837 logger.SerializeCommand(logger.Command{ 1838 DriverConnectionID: info.driverConnectionID, 1839 Message: logger.CommandSucceeded, 1840 Name: info.cmdName, 1841 RequestID: int64(info.requestID), 1842 ServerConnectionID: info.serverConnID, 1843 ServerHost: host, 1844 ServerPort: port, 1845 ServiceID: info.serviceID, 1846 }, 1847 logger.KeyDurationMS, info.duration.Milliseconds(), 1848 logger.KeyReply, formattedReply)...) 1849 } 1850 1851 if op.canLogCommandMessage() && !info.success() { 1852 host, port, _ := net.SplitHostPort(info.serverAddress.String()) 1853 1854 formattedReply := logger.FormatMessage(info.cmdErr.Error(), op.Logger.MaxDocumentLength) 1855 1856 op.Logger.Print(logger.LevelDebug, 1857 logger.ComponentCommand, 1858 logger.CommandFailed, 1859 logger.SerializeCommand(logger.Command{ 1860 DriverConnectionID: info.driverConnectionID, 1861 Message: logger.CommandFailed, 1862 Name: info.cmdName, 1863 RequestID: int64(info.requestID), 1864 ServerConnectionID: info.serverConnID, 1865 ServerHost: host, 1866 ServerPort: port, 1867 ServiceID: info.serviceID, 1868 }, 1869 logger.KeyDurationMS, info.duration.Milliseconds(), 1870 logger.KeyFailure, formattedReply)...) 1871 } 1872 1873 // If the finished event cannot be published, return early. 1874 if !op.canPublishFinishedEvent(info) { 1875 return 1876 } 1877 1878 finished := event.CommandFinishedEvent{ 1879 CommandName: info.cmdName, 1880 RequestID: int64(info.requestID), 1881 ConnectionID: info.connID, 1882 Duration: info.duration, 1883 DurationNanos: info.duration.Nanoseconds(), 1884 ServerConnectionID: convertInt64PtrToInt32Ptr(info.serverConnID), 1885 ServerConnectionID64: info.serverConnID, 1886 ServiceID: info.serviceID, 1887 } 1888 1889 if info.success() { 1890 successEvent := &event.CommandSucceededEvent{ 1891 Reply: redactFinishedInformationResponse(info), 1892 CommandFinishedEvent: finished, 1893 } 1894 op.CommandMonitor.Succeeded(ctx, successEvent) 1895 1896 return 1897 } 1898 1899 failedEvent := &event.CommandFailedEvent{ 1900 Failure: info.cmdErr.Error(), 1901 CommandFinishedEvent: finished, 1902 } 1903 op.CommandMonitor.Failed(ctx, failedEvent) 1904 } 1905 1906 // sessionsSupported returns true of the given server version indicates that it supports sessions. 1907 func sessionsSupported(wireVersion *description.VersionRange) bool { 1908 return wireVersion != nil && wireVersion.Max >= 6 1909 } 1910 1911 // retryWritesSupported returns true if this description represents a server that supports retryable writes. 1912 func retryWritesSupported(s description.Server) bool { 1913 return s.SessionTimeoutMinutes != 0 && s.Kind != description.Standalone 1914 }