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// NOTE: This file is maintained by hand because operationgen cannot generate it.
8
9package operation
10
11import (
12	"context"
13	"errors"
14	"fmt"
15
16	"go.mongodb.org/mongo-driver/bson"
17	"go.mongodb.org/mongo-driver/event"
18	"go.mongodb.org/mongo-driver/mongo/description"
19	"go.mongodb.org/mongo-driver/mongo/writeconcern"
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// Update performs an update operation.
26type Update struct {
27	bypassDocumentValidation *bool
28	ordered                  *bool
29	updates                  []bsoncore.Document
30	session                  *session.Client
31	clock                    *session.ClusterClock
32	collection               string
33	monitor                  *event.CommandMonitor
34	database                 string
35	deployment               driver.Deployment
36	hint                     *bool
37	arrayFilters             *bool
38	selector                 description.ServerSelector
39	writeConcern             *writeconcern.WriteConcern
40	retry                    *driver.RetryMode
41	result                   UpdateResult
42	crypt                    *driver.Crypt
43}
44
45// Upsert contains the information for an upsert in an Update operation.
46type Upsert struct {
47	Index int64
48	ID    interface{} `bson:"_id"`
49}
50
51// UpdateResult contains information for the result of an Update operation.
52type UpdateResult struct {
53	// Number of documents matched.
54	N int32
55	// Number of documents modified.
56	NModified int32
57	// Information about upserted documents.
58	Upserted []Upsert
59}
60
61func buildUpdateResult(response bsoncore.Document, srvr driver.Server) (UpdateResult, error) {
62	elements, err := response.Elements()
63	if err != nil {
64		return UpdateResult{}, err
65	}
66	ur := UpdateResult{}
67	for _, element := range elements {
68		switch element.Key() {
69
70		case "nModified":
71			var ok bool
72			ur.NModified, ok = element.Value().Int32OK()
73			if !ok {
74				err = fmt.Errorf("response field 'nModified' is type int32, but received BSON type %s", element.Value().Type)
75			}
76
77		case "n":
78			var ok bool
79			ur.N, ok = element.Value().Int32OK()
80			if !ok {
81				err = fmt.Errorf("response field 'n' is type int32, but received BSON type %s", element.Value().Type)
82			}
83
84		case "upserted":
85			arr, ok := element.Value().ArrayOK()
86			if !ok {
87				err = fmt.Errorf("response field 'upserted' is type array, but received BSON type %s", element.Value().Type)
88				break
89			}
90
91			var values []bsoncore.Value
92			values, err = arr.Values()
93			if err != nil {
94				break
95			}
96
97			for _, val := range values {
98				valDoc, ok := val.DocumentOK()
99				if !ok {
100					err = fmt.Errorf("upserted value is type document, but received BSON type %s", val.Type)
101					break
102				}
103				var upsert Upsert
104				if err = bson.Unmarshal(valDoc, &upsert); err != nil {
105					break
106				}
107				ur.Upserted = append(ur.Upserted, upsert)
108			}
109		}
110	}
111	return ur, nil
112}
113
114// NewUpdate constructs and returns a new Update.
115func NewUpdate(updates ...bsoncore.Document) *Update {
116	return &Update{
117		updates: updates,
118	}
119}
120
121// Result returns the result of executing this operation.
122func (u *Update) Result() UpdateResult { return u.result }
123
124func (u *Update) processResponse(response bsoncore.Document, srvr driver.Server, desc description.Server, currIndex int) error {
125	ur, err := buildUpdateResult(response, srvr)
126
127	u.result.N += ur.N
128	u.result.NModified += ur.NModified
129	if currIndex > 0 {
130		for ind := range ur.Upserted {
131			ur.Upserted[ind].Index += int64(currIndex)
132		}
133	}
134	u.result.Upserted = append(u.result.Upserted, ur.Upserted...)
135	return err
136
137}
138
139// Execute runs this operations and returns an error if the operaiton did not execute successfully.
140func (u *Update) Execute(ctx context.Context) error {
141	if u.deployment == nil {
142		return errors.New("the Update operation must have a Deployment set before Execute can be called")
143	}
144	batches := &driver.Batches{
145		Identifier: "updates",
146		Documents:  u.updates,
147		Ordered:    u.ordered,
148	}
149
150	return driver.Operation{
151		CommandFn:         u.command,
152		ProcessResponseFn: u.processResponse,
153		Batches:           batches,
154		RetryMode:         u.retry,
155		Type:              driver.Write,
156		Client:            u.session,
157		Clock:             u.clock,
158		CommandMonitor:    u.monitor,
159		Database:          u.database,
160		Deployment:        u.deployment,
161		Selector:          u.selector,
162		WriteConcern:      u.writeConcern,
163		Crypt:             u.crypt,
164	}.Execute(ctx, nil)
165
166}
167
168func (u *Update) command(dst []byte, desc description.SelectedServer) ([]byte, error) {
169	dst = bsoncore.AppendStringElement(dst, "update", u.collection)
170	if u.bypassDocumentValidation != nil &&
171		(desc.WireVersion != nil && desc.WireVersion.Includes(4)) {
172
173		dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *u.bypassDocumentValidation)
174	}
175	if u.ordered != nil {
176
177		dst = bsoncore.AppendBooleanElement(dst, "ordered", *u.ordered)
178	}
179	if u.hint != nil && *u.hint {
180
181		if desc.WireVersion == nil || !desc.WireVersion.Includes(5) {
182			return nil, errors.New("the 'hint' command parameter requires a minimum server wire version of 5")
183		}
184		if !u.writeConcern.Acknowledged() {
185			return nil, errUnacknowledgedHint
186		}
187	}
188	if u.arrayFilters != nil && *u.arrayFilters {
189		if desc.WireVersion == nil || !desc.WireVersion.Includes(6) {
190			return nil, errors.New("the 'arrayFilters' command parameter requires a minimum server wire version of 6")
191		}
192	}
193
194	return dst, nil
195}
196
197// BypassDocumentValidation allows the operation to opt-out of document level validation. Valid
198// for server versions >= 3.2. For servers < 3.2, this setting is ignored.
199func (u *Update) BypassDocumentValidation(bypassDocumentValidation bool) *Update {
200	if u == nil {
201		u = new(Update)
202	}
203
204	u.bypassDocumentValidation = &bypassDocumentValidation
205	return u
206}
207
208// Hint is a flag to indicate that the update document contains a hint. Hint is only supported by
209// servers >= 4.2. Older servers >= 3.4 will report an error for using the hint option. For servers <
210// 3.4, the driver will return an error if the hint option is used.
211func (u *Update) Hint(hint bool) *Update {
212	if u == nil {
213		u = new(Update)
214	}
215
216	u.hint = &hint
217	return u
218}
219
220// ArrayFilters is a flag to indicate that the update document contains an arrayFilters field. This option is only
221// supported on server versions 3.6 and higher. For servers < 3.6, the driver will return an error.
222func (u *Update) ArrayFilters(arrayFilters bool) *Update {
223	if u == nil {
224		u = new(Update)
225	}
226
227	u.arrayFilters = &arrayFilters
228	return u
229}
230
231// Ordered sets ordered. If true, when a write fails, the operation will return the error, when
232// false write failures do not stop execution of the operation.
233func (u *Update) Ordered(ordered bool) *Update {
234	if u == nil {
235		u = new(Update)
236	}
237
238	u.ordered = &ordered
239	return u
240}
241
242// Updates specifies an array of update statements to perform when this operation is executed.
243// Each update document must have the following structure:
244// {q: <query>, u: <update>, multi: <boolean>, collation: Optional<Document>, arrayFitlers: Optional<Array>, hint: Optional<string/Document>}.
245func (u *Update) Updates(updates ...bsoncore.Document) *Update {
246	if u == nil {
247		u = new(Update)
248	}
249
250	u.updates = updates
251	return u
252}
253
254// Session sets the session for this operation.
255func (u *Update) Session(session *session.Client) *Update {
256	if u == nil {
257		u = new(Update)
258	}
259
260	u.session = session
261	return u
262}
263
264// ClusterClock sets the cluster clock for this operation.
265func (u *Update) ClusterClock(clock *session.ClusterClock) *Update {
266	if u == nil {
267		u = new(Update)
268	}
269
270	u.clock = clock
271	return u
272}
273
274// Collection sets the collection that this command will run against.
275func (u *Update) Collection(collection string) *Update {
276	if u == nil {
277		u = new(Update)
278	}
279
280	u.collection = collection
281	return u
282}
283
284// CommandMonitor sets the monitor to use for APM events.
285func (u *Update) CommandMonitor(monitor *event.CommandMonitor) *Update {
286	if u == nil {
287		u = new(Update)
288	}
289
290	u.monitor = monitor
291	return u
292}
293
294// Database sets the database to run this operation against.
295func (u *Update) Database(database string) *Update {
296	if u == nil {
297		u = new(Update)
298	}
299
300	u.database = database
301	return u
302}
303
304// Deployment sets the deployment to use for this operation.
305func (u *Update) Deployment(deployment driver.Deployment) *Update {
306	if u == nil {
307		u = new(Update)
308	}
309
310	u.deployment = deployment
311	return u
312}
313
314// ServerSelector sets the selector used to retrieve a server.
315func (u *Update) ServerSelector(selector description.ServerSelector) *Update {
316	if u == nil {
317		u = new(Update)
318	}
319
320	u.selector = selector
321	return u
322}
323
324// WriteConcern sets the write concern for this operation.
325func (u *Update) WriteConcern(writeConcern *writeconcern.WriteConcern) *Update {
326	if u == nil {
327		u = new(Update)
328	}
329
330	u.writeConcern = writeConcern
331	return u
332}
333
334// Retry enables retryable writes for this operation. Retries are not handled automatically,
335// instead a boolean is returned from Execute and SelectAndExecute that indicates if the
336// operation can be retried. Retrying is handled by calling RetryExecute.
337func (u *Update) Retry(retry driver.RetryMode) *Update {
338	if u == nil {
339		u = new(Update)
340	}
341
342	u.retry = &retry
343	return u
344}
345
346// Crypt sets the Crypt object to use for automatic encryption and decryption.
347func (u *Update) Crypt(crypt *driver.Crypt) *Update {
348	if u == nil {
349		u = new(Update)
350	}
351
352	u.crypt = crypt
353	return u
354}
355