/
find_and_modify.ts
239 lines (200 loc) · 7.3 KB
/
find_and_modify.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import { ReadPreference } from '../read_preference';
import {
maxWireVersion,
applyRetryableWrites,
decorateWithCollation,
applyWriteConcern,
formattedOrderClause,
hasAtomicOperators,
Callback
} from '../utils';
import { MongoError } from '../error';
import { CommandOperation, CommandOperationOptions } from './command';
import { defineAspects, Aspect } from './operation';
import type { Document } from '../bson';
import type { Server } from '../sdam/server';
import type { Collection } from '../collection';
import type { Sort } from './find';
/** @public */
export interface FindAndModifyOptions extends CommandOperationOptions {
/** When false, returns the updated document rather than the original. The default is true. */
returnOriginal?: boolean;
/** Upsert the document if it does not exist. */
upsert?: boolean;
/** Limits the fields to return for all matching documents. */
projection?: Document;
/** @deprecated use `projection` instead */
fields?: Document;
/** Determines which document the operation modifies if the query selects multiple documents. */
sort?: Sort;
/** Optional list of array filters referenced in filtered positional operators */
arrayFilters?: Document[];
/** Allow driver to bypass schema validation in MongoDB 3.2 or higher. */
bypassDocumentValidation?: boolean;
/** An optional hint for query optimization. See the {@link https://docs.mongodb.com/manual/reference/command/update/#update-command-hint|update command} reference for more information.*/
hint?: Document;
// NOTE: These types are a misuse of options, can we think of a way to remove them?
update?: boolean;
remove?: boolean;
new?: boolean;
}
/** @internal */
export class FindAndModifyOperation extends CommandOperation<FindAndModifyOptions, Document> {
collection: Collection;
query: Document;
sort?: Sort;
doc?: Document;
constructor(
collection: Collection,
query: Document,
sort: Sort | undefined,
doc: Document | undefined,
options?: FindAndModifyOptions
) {
super(collection, options);
// force primary read preference
this.readPreference = ReadPreference.primary;
this.collection = collection;
this.query = query;
this.sort = sort;
this.doc = doc;
}
execute(server: Server, callback: Callback<Document>): void {
const coll = this.collection;
const query = this.query;
const sort = formattedOrderClause(this.sort);
const doc = this.doc;
let options = this.options;
// Create findAndModify command object
const cmd: Document = {
findAndModify: coll.collectionName,
query: query
};
if (sort) {
cmd.sort = sort;
}
cmd.new = options.new ? true : false;
cmd.remove = options.remove ? true : false;
cmd.upsert = options.upsert ? true : false;
const projection = options.projection || options.fields;
if (projection) {
cmd.fields = projection;
}
if (options.arrayFilters) {
cmd.arrayFilters = options.arrayFilters;
}
if (doc && !options.remove) {
cmd.update = doc;
}
if (options.maxTimeMS) {
cmd.maxTimeMS = options.maxTimeMS;
}
// Either use override on the function, or go back to default on either the collection
// level or db
options.serializeFunctions = options.serializeFunctions || coll.s.serializeFunctions;
// No check on the documents
options.checkKeys = false;
// Final options for retryable writes and write concern
options = applyRetryableWrites(options, coll.s.db);
options = applyWriteConcern(options, { db: coll.s.db, collection: coll }, options);
// Decorate the findAndModify command with the write Concern
if (options.writeConcern) {
cmd.writeConcern = options.writeConcern;
}
// Have we specified bypassDocumentValidation
if (options.bypassDocumentValidation === true) {
cmd.bypassDocumentValidation = options.bypassDocumentValidation;
}
// Have we specified collation
try {
decorateWithCollation(cmd, coll, options);
} catch (err) {
return callback(err);
}
if (options.hint) {
// TODO: once this method becomes a CommandOperation we will have the server
// in place to check.
const unacknowledgedWrite = this.writeConcern?.w === 0;
if (unacknowledgedWrite || maxWireVersion(server) < 8) {
callback(
new MongoError('The current topology does not support a hint on findAndModify commands')
);
return;
}
cmd.hint = options.hint;
}
// Execute the command
super.executeCommand(server, cmd, (err, result) => {
if (err) return callback(err);
return callback(undefined, result);
});
}
}
/** @internal */
export class FindOneAndDeleteOperation extends FindAndModifyOperation {
constructor(collection: Collection, filter: Document, options: FindAndModifyOptions) {
// Final options
const finalOptions = Object.assign({}, options);
finalOptions.fields = options.projection;
finalOptions.remove = true;
// Basic validation
if (filter == null || typeof filter !== 'object') {
throw new TypeError('Filter parameter must be an object');
}
super(collection, filter, finalOptions.sort, undefined, finalOptions);
}
}
/** @internal */
export class FindOneAndReplaceOperation extends FindAndModifyOperation {
constructor(
collection: Collection,
filter: Document,
replacement: Document,
options: FindAndModifyOptions
) {
// Final options
const finalOptions = Object.assign({}, options);
finalOptions.fields = options.projection;
finalOptions.update = true;
finalOptions.new = options.returnOriginal !== void 0 ? !options.returnOriginal : false;
finalOptions.upsert = options.upsert !== void 0 ? !!options.upsert : false;
if (filter == null || typeof filter !== 'object') {
throw new TypeError('Filter parameter must be an object');
}
if (replacement == null || typeof replacement !== 'object') {
throw new TypeError('Replacement parameter must be an object');
}
if (hasAtomicOperators(replacement)) {
throw new TypeError('Replacement document must not contain atomic operators');
}
super(collection, filter, finalOptions.sort, replacement, finalOptions);
}
}
/** @internal */
export class FindOneAndUpdateOperation extends FindAndModifyOperation {
constructor(
collection: Collection,
filter: Document,
update: Document,
options: FindAndModifyOptions
) {
// Final options
const finalOptions = Object.assign({}, options);
finalOptions.fields = options.projection;
finalOptions.update = true;
finalOptions.new =
typeof options.returnOriginal === 'boolean' ? !options.returnOriginal : false;
finalOptions.upsert = typeof options.upsert === 'boolean' ? options.upsert : false;
if (filter == null || typeof filter !== 'object') {
throw new TypeError('Filter parameter must be an object');
}
if (update == null || typeof update !== 'object') {
throw new TypeError('Update parameter must be an object');
}
if (!hasAtomicOperators(update)) {
throw new TypeError('Update document requires atomic operators');
}
super(collection, filter, finalOptions.sort, update, finalOptions);
}
}
defineAspects(FindAndModifyOperation, [Aspect.WRITE_OPERATION, Aspect.RETRYABLE]);