...

Source file src/go.mongodb.org/mongo-driver/x/mongo/driver/operation/find_and_modify.go

Documentation: go.mongodb.org/mongo-driver/x/mongo/driver/operation

     1  // Copyright (C) MongoDB, Inc. 2019-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 operation
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"time"
    14  
    15  	"go.mongodb.org/mongo-driver/bson"
    16  	"go.mongodb.org/mongo-driver/bson/bsontype"
    17  	"go.mongodb.org/mongo-driver/event"
    18  	"go.mongodb.org/mongo-driver/internal/driverutil"
    19  	"go.mongodb.org/mongo-driver/mongo/description"
    20  	"go.mongodb.org/mongo-driver/mongo/writeconcern"
    21  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    22  	"go.mongodb.org/mongo-driver/x/mongo/driver"
    23  	"go.mongodb.org/mongo-driver/x/mongo/driver/session"
    24  )
    25  
    26  // FindAndModify performs a findAndModify operation.
    27  type FindAndModify struct {
    28  	arrayFilters             bsoncore.Array
    29  	bypassDocumentValidation *bool
    30  	collation                bsoncore.Document
    31  	comment                  bsoncore.Value
    32  	fields                   bsoncore.Document
    33  	maxTime                  *time.Duration
    34  	newDocument              *bool
    35  	query                    bsoncore.Document
    36  	remove                   *bool
    37  	sort                     bsoncore.Document
    38  	update                   bsoncore.Value
    39  	upsert                   *bool
    40  	session                  *session.Client
    41  	clock                    *session.ClusterClock
    42  	collection               string
    43  	monitor                  *event.CommandMonitor
    44  	database                 string
    45  	deployment               driver.Deployment
    46  	selector                 description.ServerSelector
    47  	writeConcern             *writeconcern.WriteConcern
    48  	retry                    *driver.RetryMode
    49  	crypt                    driver.Crypt
    50  	hint                     bsoncore.Value
    51  	serverAPI                *driver.ServerAPIOptions
    52  	let                      bsoncore.Document
    53  	timeout                  *time.Duration
    54  
    55  	result FindAndModifyResult
    56  }
    57  
    58  // LastErrorObject represents information about updates and upserts returned by the server.
    59  type LastErrorObject struct {
    60  	// True if an update modified an existing document
    61  	UpdatedExisting bool
    62  	// Object ID of the upserted document.
    63  	Upserted interface{}
    64  }
    65  
    66  // FindAndModifyResult represents a findAndModify result returned by the server.
    67  type FindAndModifyResult struct {
    68  	// Either the old or modified document, depending on the value of the new parameter.
    69  	Value bsoncore.Document
    70  	// Contains information about updates and upserts.
    71  	LastErrorObject LastErrorObject
    72  }
    73  
    74  func buildFindAndModifyResult(response bsoncore.Document) (FindAndModifyResult, error) {
    75  	elements, err := response.Elements()
    76  	if err != nil {
    77  		return FindAndModifyResult{}, err
    78  	}
    79  	famr := FindAndModifyResult{}
    80  	for _, element := range elements {
    81  		switch element.Key() {
    82  		case "value":
    83  			var ok bool
    84  			famr.Value, ok = element.Value().DocumentOK()
    85  
    86  			// The 'value' field returned by a FindAndModify can be null in the case that no document was found.
    87  			if element.Value().Type != bsontype.Null && !ok {
    88  				return famr, fmt.Errorf("response field 'value' is type document or null, but received BSON type %s", element.Value().Type)
    89  			}
    90  		case "lastErrorObject":
    91  			valDoc, ok := element.Value().DocumentOK()
    92  			if !ok {
    93  				return famr, fmt.Errorf("response field 'lastErrorObject' is type document, but received BSON type %s", element.Value().Type)
    94  			}
    95  
    96  			var leo LastErrorObject
    97  			if err = bson.Unmarshal(valDoc, &leo); err != nil {
    98  				return famr, err
    99  			}
   100  			famr.LastErrorObject = leo
   101  		}
   102  	}
   103  	return famr, nil
   104  }
   105  
   106  // NewFindAndModify constructs and returns a new FindAndModify.
   107  func NewFindAndModify(query bsoncore.Document) *FindAndModify {
   108  	return &FindAndModify{
   109  		query: query,
   110  	}
   111  }
   112  
   113  // Result returns the result of executing this operation.
   114  func (fam *FindAndModify) Result() FindAndModifyResult { return fam.result }
   115  
   116  func (fam *FindAndModify) processResponse(info driver.ResponseInfo) error {
   117  	var err error
   118  
   119  	fam.result, err = buildFindAndModifyResult(info.ServerResponse)
   120  	return err
   121  
   122  }
   123  
   124  // Execute runs this operations and returns an error if the operation did not execute successfully.
   125  func (fam *FindAndModify) Execute(ctx context.Context) error {
   126  	if fam.deployment == nil {
   127  		return errors.New("the FindAndModify operation must have a Deployment set before Execute can be called")
   128  	}
   129  
   130  	return driver.Operation{
   131  		CommandFn:         fam.command,
   132  		ProcessResponseFn: fam.processResponse,
   133  
   134  		RetryMode:      fam.retry,
   135  		Type:           driver.Write,
   136  		Client:         fam.session,
   137  		Clock:          fam.clock,
   138  		CommandMonitor: fam.monitor,
   139  		Database:       fam.database,
   140  		Deployment:     fam.deployment,
   141  		MaxTime:        fam.maxTime,
   142  		Selector:       fam.selector,
   143  		WriteConcern:   fam.writeConcern,
   144  		Crypt:          fam.crypt,
   145  		ServerAPI:      fam.serverAPI,
   146  		Timeout:        fam.timeout,
   147  		Name:           driverutil.FindAndModifyOp,
   148  	}.Execute(ctx)
   149  
   150  }
   151  
   152  func (fam *FindAndModify) command(dst []byte, desc description.SelectedServer) ([]byte, error) {
   153  	dst = bsoncore.AppendStringElement(dst, "findAndModify", fam.collection)
   154  	if fam.arrayFilters != nil {
   155  
   156  		if desc.WireVersion == nil || !desc.WireVersion.Includes(6) {
   157  			return nil, errors.New("the 'arrayFilters' command parameter requires a minimum server wire version of 6")
   158  		}
   159  		dst = bsoncore.AppendArrayElement(dst, "arrayFilters", fam.arrayFilters)
   160  	}
   161  	if fam.bypassDocumentValidation != nil {
   162  
   163  		dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *fam.bypassDocumentValidation)
   164  	}
   165  	if fam.collation != nil {
   166  
   167  		if desc.WireVersion == nil || !desc.WireVersion.Includes(5) {
   168  			return nil, errors.New("the 'collation' command parameter requires a minimum server wire version of 5")
   169  		}
   170  		dst = bsoncore.AppendDocumentElement(dst, "collation", fam.collation)
   171  	}
   172  	if fam.comment.Type != bsontype.Type(0) {
   173  		dst = bsoncore.AppendValueElement(dst, "comment", fam.comment)
   174  	}
   175  	if fam.fields != nil {
   176  
   177  		dst = bsoncore.AppendDocumentElement(dst, "fields", fam.fields)
   178  	}
   179  	if fam.newDocument != nil {
   180  
   181  		dst = bsoncore.AppendBooleanElement(dst, "new", *fam.newDocument)
   182  	}
   183  	if fam.query != nil {
   184  
   185  		dst = bsoncore.AppendDocumentElement(dst, "query", fam.query)
   186  	}
   187  	if fam.remove != nil {
   188  
   189  		dst = bsoncore.AppendBooleanElement(dst, "remove", *fam.remove)
   190  	}
   191  	if fam.sort != nil {
   192  
   193  		dst = bsoncore.AppendDocumentElement(dst, "sort", fam.sort)
   194  	}
   195  	if fam.update.Data != nil {
   196  		dst = bsoncore.AppendValueElement(dst, "update", fam.update)
   197  	}
   198  	if fam.upsert != nil {
   199  
   200  		dst = bsoncore.AppendBooleanElement(dst, "upsert", *fam.upsert)
   201  	}
   202  	if fam.hint.Type != bsontype.Type(0) {
   203  
   204  		if desc.WireVersion == nil || !desc.WireVersion.Includes(8) {
   205  			return nil, errors.New("the 'hint' command parameter requires a minimum server wire version of 8")
   206  		}
   207  		if !fam.writeConcern.Acknowledged() {
   208  			return nil, errUnacknowledgedHint
   209  		}
   210  		dst = bsoncore.AppendValueElement(dst, "hint", fam.hint)
   211  	}
   212  	if fam.let != nil {
   213  		dst = bsoncore.AppendDocumentElement(dst, "let", fam.let)
   214  	}
   215  
   216  	return dst, nil
   217  }
   218  
   219  // ArrayFilters specifies an array of filter documents that determines which array elements to modify for an update operation on an array field.
   220  func (fam *FindAndModify) ArrayFilters(arrayFilters bsoncore.Array) *FindAndModify {
   221  	if fam == nil {
   222  		fam = new(FindAndModify)
   223  	}
   224  
   225  	fam.arrayFilters = arrayFilters
   226  	return fam
   227  }
   228  
   229  // BypassDocumentValidation specifies if document validation can be skipped when executing the operation.
   230  func (fam *FindAndModify) BypassDocumentValidation(bypassDocumentValidation bool) *FindAndModify {
   231  	if fam == nil {
   232  		fam = new(FindAndModify)
   233  	}
   234  
   235  	fam.bypassDocumentValidation = &bypassDocumentValidation
   236  	return fam
   237  }
   238  
   239  // Collation specifies a collation to be used.
   240  func (fam *FindAndModify) Collation(collation bsoncore.Document) *FindAndModify {
   241  	if fam == nil {
   242  		fam = new(FindAndModify)
   243  	}
   244  
   245  	fam.collation = collation
   246  	return fam
   247  }
   248  
   249  // Comment sets a value to help trace an operation.
   250  func (fam *FindAndModify) Comment(comment bsoncore.Value) *FindAndModify {
   251  	if fam == nil {
   252  		fam = new(FindAndModify)
   253  	}
   254  
   255  	fam.comment = comment
   256  	return fam
   257  }
   258  
   259  // Fields specifies a subset of fields to return.
   260  func (fam *FindAndModify) Fields(fields bsoncore.Document) *FindAndModify {
   261  	if fam == nil {
   262  		fam = new(FindAndModify)
   263  	}
   264  
   265  	fam.fields = fields
   266  	return fam
   267  }
   268  
   269  // MaxTime specifies the maximum amount of time to allow the operation to run on the server.
   270  func (fam *FindAndModify) MaxTime(maxTime *time.Duration) *FindAndModify {
   271  	if fam == nil {
   272  		fam = new(FindAndModify)
   273  	}
   274  
   275  	fam.maxTime = maxTime
   276  	return fam
   277  }
   278  
   279  // NewDocument specifies whether to return the modified document or the original. Defaults to false (return original).
   280  func (fam *FindAndModify) NewDocument(newDocument bool) *FindAndModify {
   281  	if fam == nil {
   282  		fam = new(FindAndModify)
   283  	}
   284  
   285  	fam.newDocument = &newDocument
   286  	return fam
   287  }
   288  
   289  // Query specifies the selection criteria for the modification.
   290  func (fam *FindAndModify) Query(query bsoncore.Document) *FindAndModify {
   291  	if fam == nil {
   292  		fam = new(FindAndModify)
   293  	}
   294  
   295  	fam.query = query
   296  	return fam
   297  }
   298  
   299  // Remove specifies that the matched document should be removed. Defaults to false.
   300  func (fam *FindAndModify) Remove(remove bool) *FindAndModify {
   301  	if fam == nil {
   302  		fam = new(FindAndModify)
   303  	}
   304  
   305  	fam.remove = &remove
   306  	return fam
   307  }
   308  
   309  // Sort determines which document the operation modifies if the query matches multiple documents.The first document matched by the sort order will be modified.
   310  func (fam *FindAndModify) Sort(sort bsoncore.Document) *FindAndModify {
   311  	if fam == nil {
   312  		fam = new(FindAndModify)
   313  	}
   314  
   315  	fam.sort = sort
   316  	return fam
   317  }
   318  
   319  // Update specifies the update document to perform on the matched document.
   320  func (fam *FindAndModify) Update(update bsoncore.Value) *FindAndModify {
   321  	if fam == nil {
   322  		fam = new(FindAndModify)
   323  	}
   324  
   325  	fam.update = update
   326  	return fam
   327  }
   328  
   329  // Upsert specifies whether or not to create a new document if no documents match the query when doing an update. Defaults to false.
   330  func (fam *FindAndModify) Upsert(upsert bool) *FindAndModify {
   331  	if fam == nil {
   332  		fam = new(FindAndModify)
   333  	}
   334  
   335  	fam.upsert = &upsert
   336  	return fam
   337  }
   338  
   339  // Session sets the session for this operation.
   340  func (fam *FindAndModify) Session(session *session.Client) *FindAndModify {
   341  	if fam == nil {
   342  		fam = new(FindAndModify)
   343  	}
   344  
   345  	fam.session = session
   346  	return fam
   347  }
   348  
   349  // ClusterClock sets the cluster clock for this operation.
   350  func (fam *FindAndModify) ClusterClock(clock *session.ClusterClock) *FindAndModify {
   351  	if fam == nil {
   352  		fam = new(FindAndModify)
   353  	}
   354  
   355  	fam.clock = clock
   356  	return fam
   357  }
   358  
   359  // Collection sets the collection that this command will run against.
   360  func (fam *FindAndModify) Collection(collection string) *FindAndModify {
   361  	if fam == nil {
   362  		fam = new(FindAndModify)
   363  	}
   364  
   365  	fam.collection = collection
   366  	return fam
   367  }
   368  
   369  // CommandMonitor sets the monitor to use for APM events.
   370  func (fam *FindAndModify) CommandMonitor(monitor *event.CommandMonitor) *FindAndModify {
   371  	if fam == nil {
   372  		fam = new(FindAndModify)
   373  	}
   374  
   375  	fam.monitor = monitor
   376  	return fam
   377  }
   378  
   379  // Database sets the database to run this operation against.
   380  func (fam *FindAndModify) Database(database string) *FindAndModify {
   381  	if fam == nil {
   382  		fam = new(FindAndModify)
   383  	}
   384  
   385  	fam.database = database
   386  	return fam
   387  }
   388  
   389  // Deployment sets the deployment to use for this operation.
   390  func (fam *FindAndModify) Deployment(deployment driver.Deployment) *FindAndModify {
   391  	if fam == nil {
   392  		fam = new(FindAndModify)
   393  	}
   394  
   395  	fam.deployment = deployment
   396  	return fam
   397  }
   398  
   399  // ServerSelector sets the selector used to retrieve a server.
   400  func (fam *FindAndModify) ServerSelector(selector description.ServerSelector) *FindAndModify {
   401  	if fam == nil {
   402  		fam = new(FindAndModify)
   403  	}
   404  
   405  	fam.selector = selector
   406  	return fam
   407  }
   408  
   409  // WriteConcern sets the write concern for this operation.
   410  func (fam *FindAndModify) WriteConcern(writeConcern *writeconcern.WriteConcern) *FindAndModify {
   411  	if fam == nil {
   412  		fam = new(FindAndModify)
   413  	}
   414  
   415  	fam.writeConcern = writeConcern
   416  	return fam
   417  }
   418  
   419  // Retry enables retryable writes for this operation. Retries are not handled automatically,
   420  // instead a boolean is returned from Execute and SelectAndExecute that indicates if the
   421  // operation can be retried. Retrying is handled by calling RetryExecute.
   422  func (fam *FindAndModify) Retry(retry driver.RetryMode) *FindAndModify {
   423  	if fam == nil {
   424  		fam = new(FindAndModify)
   425  	}
   426  
   427  	fam.retry = &retry
   428  	return fam
   429  }
   430  
   431  // Crypt sets the Crypt object to use for automatic encryption and decryption.
   432  func (fam *FindAndModify) Crypt(crypt driver.Crypt) *FindAndModify {
   433  	if fam == nil {
   434  		fam = new(FindAndModify)
   435  	}
   436  
   437  	fam.crypt = crypt
   438  	return fam
   439  }
   440  
   441  // Hint specifies the index to use.
   442  func (fam *FindAndModify) Hint(hint bsoncore.Value) *FindAndModify {
   443  	if fam == nil {
   444  		fam = new(FindAndModify)
   445  	}
   446  
   447  	fam.hint = hint
   448  	return fam
   449  }
   450  
   451  // ServerAPI sets the server API version for this operation.
   452  func (fam *FindAndModify) ServerAPI(serverAPI *driver.ServerAPIOptions) *FindAndModify {
   453  	if fam == nil {
   454  		fam = new(FindAndModify)
   455  	}
   456  
   457  	fam.serverAPI = serverAPI
   458  	return fam
   459  }
   460  
   461  // Let specifies the let document to use. This option is only valid for server versions 5.0 and above.
   462  func (fam *FindAndModify) Let(let bsoncore.Document) *FindAndModify {
   463  	if fam == nil {
   464  		fam = new(FindAndModify)
   465  	}
   466  
   467  	fam.let = let
   468  	return fam
   469  }
   470  
   471  // Timeout sets the timeout for this operation.
   472  func (fam *FindAndModify) Timeout(timeout *time.Duration) *FindAndModify {
   473  	if fam == nil {
   474  		fam = new(FindAndModify)
   475  	}
   476  
   477  	fam.timeout = timeout
   478  	return fam
   479  }
   480  

View as plain text