1 | ;
|
2 |
|
3 | /*!
|
4 | * Module dependencies.
|
5 | */
|
6 |
|
7 | const Aggregate = require('./aggregate');
|
8 | const ChangeStream = require('./cursor/changeStream');
|
9 | const Document = require('./document');
|
10 | const DocumentNotFoundError = require('./error/notFound');
|
11 | const EventEmitter = require('events').EventEmitter;
|
12 | const Kareem = require('kareem');
|
13 | const MongooseError = require('./error/index');
|
14 | const ObjectParameterError = require('./error/objectParameter');
|
15 | const OverwriteModelError = require('./error/overwriteModel');
|
16 | const Query = require('./query');
|
17 | const SaveOptions = require('./options/saveOptions');
|
18 | const Schema = require('./schema');
|
19 | const ValidationError = require('./error/validation');
|
20 | const VersionError = require('./error/version');
|
21 | const ParallelSaveError = require('./error/parallelSave');
|
22 | const applyDefaultsHelper = require('./helpers/document/applyDefaults');
|
23 | const applyDefaultsToPOJO = require('./helpers/model/applyDefaultsToPOJO');
|
24 | const applyEmbeddedDiscriminators = require('./helpers/discriminator/applyEmbeddedDiscriminators');
|
25 | const applyHooks = require('./helpers/model/applyHooks');
|
26 | const applyMethods = require('./helpers/model/applyMethods');
|
27 | const applyProjection = require('./helpers/projection/applyProjection');
|
28 | const applyReadConcern = require('./helpers/schema/applyReadConcern');
|
29 | const applySchemaCollation = require('./helpers/indexes/applySchemaCollation');
|
30 | const applyStaticHooks = require('./helpers/model/applyStaticHooks');
|
31 | const applyStatics = require('./helpers/model/applyStatics');
|
32 | const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
|
33 | const assignVals = require('./helpers/populate/assignVals');
|
34 | const castBulkWrite = require('./helpers/model/castBulkWrite');
|
35 | const clone = require('./helpers/clone');
|
36 | const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter');
|
37 | const decorateUpdateWithVersionKey = require('./helpers/update/decorateUpdateWithVersionKey');
|
38 | const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
|
39 | const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue');
|
40 | const discriminator = require('./helpers/model/discriminator');
|
41 | const each = require('./helpers/each');
|
42 | const get = require('./helpers/get');
|
43 | const getConstructorName = require('./helpers/getConstructorName');
|
44 | const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue');
|
45 | const getModelsMapForPopulate = require('./helpers/populate/getModelsMapForPopulate');
|
46 | const immediate = require('./helpers/immediate');
|
47 | const internalToObjectOptions = require('./options').internalToObjectOptions;
|
48 | const isDefaultIdIndex = require('./helpers/indexes/isDefaultIdIndex');
|
49 | const isIndexEqual = require('./helpers/indexes/isIndexEqual');
|
50 | const {
|
51 | getRelatedDBIndexes,
|
52 | getRelatedSchemaIndexes
|
53 | } = require('./helpers/indexes/getRelatedIndexes');
|
54 | const decorateDiscriminatorIndexOptions = require('./helpers/indexes/decorateDiscriminatorIndexOptions');
|
55 | const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
|
56 | const leanPopulateMap = require('./helpers/populate/leanPopulateMap');
|
57 | const parallelLimit = require('./helpers/parallelLimit');
|
58 | const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
|
59 | const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
|
60 | const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
|
61 | const setDottedPath = require('./helpers/path/setDottedPath');
|
62 | const STATES = require('./connectionState');
|
63 | const util = require('util');
|
64 | const utils = require('./utils');
|
65 | const MongooseBulkWriteError = require('./error/bulkWriteError');
|
66 | const minimize = require('./helpers/minimize');
|
67 |
|
68 | const modelCollectionSymbol = Symbol('mongoose#Model#collection');
|
69 | const modelDbSymbol = Symbol('mongoose#Model#db');
|
70 | const modelSymbol = require('./helpers/symbols').modelSymbol;
|
71 | const subclassedSymbol = Symbol('mongoose#Model#subclassed');
|
72 |
|
73 | const { VERSION_INC, VERSION_WHERE, VERSION_ALL } = Document;
|
74 |
|
75 | const saveToObjectOptions = Object.assign({}, internalToObjectOptions, {
|
76 | bson: true,
|
77 | flattenObjectIds: false
|
78 | });
|
79 |
|
80 | /**
|
81 | * A Model is a class that's your primary tool for interacting with MongoDB.
|
82 | * An instance of a Model is called a [Document](https://mongoosejs.com/docs/api/document.html#Document).
|
83 | *
|
84 | * In Mongoose, the term "Model" refers to subclasses of the `mongoose.Model`
|
85 | * class. You should not use the `mongoose.Model` class directly. The
|
86 | * [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) and
|
87 | * [`connection.model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()) functions
|
88 | * create subclasses of `mongoose.Model` as shown below.
|
89 | *
|
90 | * #### Example:
|
91 | *
|
92 | * // `UserModel` is a "Model", a subclass of `mongoose.Model`.
|
93 | * const UserModel = mongoose.model('User', new Schema({ name: String }));
|
94 | *
|
95 | * // You can use a Model to create new documents using `new`:
|
96 | * const userDoc = new UserModel({ name: 'Foo' });
|
97 | * await userDoc.save();
|
98 | *
|
99 | * // You also use a model to create queries:
|
100 | * const userFromDb = await UserModel.findOne({ name: 'Foo' });
|
101 | *
|
102 | * @param {Object} doc values for initial set
|
103 | * @param {Object} [fields] optional object containing the fields that were selected in the query which returned this document. You do **not** need to set this parameter to ensure Mongoose handles your [query projection](https://mongoosejs.com/docs/api/query.html#Query.prototype.select()).
|
104 | * @param {Boolean} [skipId=false] optional boolean. If true, mongoose doesn't add an `_id` field to the document.
|
105 | * @inherits Document https://mongoosejs.com/docs/api/document.html
|
106 | * @event `error`: If listening to this event, 'error' is emitted when a document was saved and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model.
|
107 | * @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event.
|
108 | * @event `index-single-start`: Emitted when an individual index starts within `Model#ensureIndexes`. The fields and options being used to build the index are also passed with the event.
|
109 | * @event `index-single-done`: Emitted when an individual index finishes within `Model#ensureIndexes`. If an error occurred it is passed with the event. The fields, options, and index name are also passed.
|
110 | * @api public
|
111 | */
|
112 |
|
113 | function Model(doc, fields, skipId) {
|
114 | if (fields instanceof Schema) {
|
115 | throw new TypeError('2nd argument to `Model` constructor must be a POJO or string, ' +
|
116 | '**not** a schema. Make sure you\'re calling `mongoose.model()`, not ' +
|
117 | '`mongoose.Model()`.');
|
118 | }
|
119 | if (typeof doc === 'string') {
|
120 | throw new TypeError('First argument to `Model` constructor must be an object, ' +
|
121 | '**not** a string. Make sure you\'re calling `mongoose.model()`, not ' +
|
122 | '`mongoose.Model()`.');
|
123 | }
|
124 | Document.call(this, doc, fields, skipId);
|
125 | }
|
126 |
|
127 | /**
|
128 | * Inherits from Document.
|
129 | *
|
130 | * All Model.prototype features are available on
|
131 | * top level (non-sub) documents.
|
132 | * @api private
|
133 | */
|
134 |
|
135 | Object.setPrototypeOf(Model.prototype, Document.prototype);
|
136 | Model.prototype.$isMongooseModelPrototype = true;
|
137 |
|
138 | /**
|
139 | * Connection the model uses.
|
140 | *
|
141 | * @api public
|
142 | * @property db
|
143 | * @memberOf Model
|
144 | * @instance
|
145 | */
|
146 |
|
147 | Model.prototype.db;
|
148 |
|
149 | /**
|
150 | * The collection instance this model uses.
|
151 | * A Mongoose collection is a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)).
|
152 | * Using `Model.collection` means you bypass Mongoose middleware, validation, and casting.
|
153 | *
|
154 | * This property is read-only. Modifying this property is a no-op.
|
155 | *
|
156 | * @api public
|
157 | * @property collection
|
158 | * @memberOf Model
|
159 | * @instance
|
160 | */
|
161 |
|
162 | Model.prototype.collection;
|
163 |
|
164 | /**
|
165 | * Internal collection the model uses.
|
166 | *
|
167 | * This property is read-only. Modifying this property is a no-op.
|
168 | *
|
169 | * @api private
|
170 | * @property collection
|
171 | * @memberOf Model
|
172 | * @instance
|
173 | */
|
174 |
|
175 |
|
176 | Model.prototype.$__collection;
|
177 |
|
178 | /**
|
179 | * The name of the model
|
180 | *
|
181 | * @api public
|
182 | * @property modelName
|
183 | * @memberOf Model
|
184 | * @instance
|
185 | */
|
186 |
|
187 | Model.prototype.modelName;
|
188 |
|
189 | /**
|
190 | * Additional properties to attach to the query when calling `save()` and
|
191 | * `isNew` is false.
|
192 | *
|
193 | * @api public
|
194 | * @property $where
|
195 | * @memberOf Model
|
196 | * @instance
|
197 | */
|
198 |
|
199 | Model.prototype.$where;
|
200 |
|
201 | /**
|
202 | * If this is a discriminator model, `baseModelName` is the name of
|
203 | * the base model.
|
204 | *
|
205 | * @api public
|
206 | * @property baseModelName
|
207 | * @memberOf Model
|
208 | * @instance
|
209 | */
|
210 |
|
211 | Model.prototype.baseModelName;
|
212 |
|
213 | /**
|
214 | * Event emitter that reports any errors that occurred. Useful for global error
|
215 | * handling.
|
216 | *
|
217 | * #### Example:
|
218 | *
|
219 | * MyModel.events.on('error', err => console.log(err.message));
|
220 | *
|
221 | * // Prints a 'CastError' because of the above handler
|
222 | * await MyModel.findOne({ _id: 'Not a valid ObjectId' }).catch(noop);
|
223 | *
|
224 | * @api public
|
225 | * @property events
|
226 | * @fires error whenever any query or model function errors
|
227 | * @memberOf Model
|
228 | * @static
|
229 | */
|
230 |
|
231 | Model.events;
|
232 |
|
233 | /**
|
234 | * Compiled middleware for this model. Set in `applyHooks()`.
|
235 | *
|
236 | * @api private
|
237 | * @property _middleware
|
238 | * @memberOf Model
|
239 | * @static
|
240 | */
|
241 |
|
242 | Model._middleware;
|
243 |
|
244 | /*!
|
245 | * ignore
|
246 | */
|
247 |
|
248 | function _applyCustomWhere(doc, where) {
|
249 | if (doc.$where == null) {
|
250 | return;
|
251 | }
|
252 | for (const key of Object.keys(doc.$where)) {
|
253 | where[key] = doc.$where[key];
|
254 | }
|
255 | }
|
256 |
|
257 | /*!
|
258 | * ignore
|
259 | */
|
260 |
|
261 | Model.prototype.$__handleSave = function(options, callback) {
|
262 | const saveOptions = {};
|
263 |
|
264 | applyWriteConcern(this.$__schema, options);
|
265 | if (typeof options.writeConcern !== 'undefined') {
|
266 | saveOptions.writeConcern = {};
|
267 | if ('w' in options.writeConcern) {
|
268 | saveOptions.writeConcern.w = options.writeConcern.w;
|
269 | }
|
270 | if ('j' in options.writeConcern) {
|
271 | saveOptions.writeConcern.j = options.writeConcern.j;
|
272 | }
|
273 | if ('wtimeout' in options.writeConcern) {
|
274 | saveOptions.writeConcern.wtimeout = options.writeConcern.wtimeout;
|
275 | }
|
276 | } else {
|
277 | if ('w' in options) {
|
278 | saveOptions.w = options.w;
|
279 | }
|
280 | if ('j' in options) {
|
281 | saveOptions.j = options.j;
|
282 | }
|
283 | if ('wtimeout' in options) {
|
284 | saveOptions.wtimeout = options.wtimeout;
|
285 | }
|
286 | }
|
287 | if ('checkKeys' in options) {
|
288 | saveOptions.checkKeys = options.checkKeys;
|
289 | }
|
290 |
|
291 | const session = this.$session();
|
292 | const asyncLocalStorage = this[modelDbSymbol].base.transactionAsyncLocalStorage?.getStore();
|
293 | if (session != null) {
|
294 | saveOptions.session = session;
|
295 | } else if (!options.hasOwnProperty('session') && asyncLocalStorage?.session != null) {
|
296 | // Only set session from asyncLocalStorage if `session` option wasn't originally passed in options
|
297 | saveOptions.session = asyncLocalStorage.session;
|
298 | }
|
299 | if (this.$isNew) {
|
300 | // send entire doc
|
301 | const obj = this.toObject(saveToObjectOptions);
|
302 | if ((obj || {})._id === void 0) {
|
303 | // documents must have an _id else mongoose won't know
|
304 | // what to update later if more changes are made. the user
|
305 | // wouldn't know what _id was generated by mongodb either
|
306 | // nor would the ObjectId generated by mongodb necessarily
|
307 | // match the schema definition.
|
308 | immediate(function() {
|
309 | callback(new MongooseError('document must have an _id before saving'));
|
310 | });
|
311 | return;
|
312 | }
|
313 |
|
314 | this.$__version(true, obj);
|
315 | this[modelCollectionSymbol].insertOne(obj, saveOptions).then(
|
316 | ret => callback(null, ret),
|
317 | err => {
|
318 | _setIsNew(this, true);
|
319 |
|
320 | callback(err, null);
|
321 | }
|
322 | );
|
323 |
|
324 | this.$__reset();
|
325 | _setIsNew(this, false);
|
326 | // Make it possible to retry the insert
|
327 | this.$__.inserting = true;
|
328 | return;
|
329 | }
|
330 |
|
331 | // Make sure we don't treat it as a new object on error,
|
332 | // since it already exists
|
333 | this.$__.inserting = false;
|
334 | const delta = this.$__delta();
|
335 |
|
336 | if (options.pathsToSave) {
|
337 | for (const key in delta[1]['$set']) {
|
338 | if (options.pathsToSave.includes(key)) {
|
339 | continue;
|
340 | } else if (options.pathsToSave.some(pathToSave => key.slice(0, pathToSave.length) === pathToSave && key.charAt(pathToSave.length) === '.')) {
|
341 | continue;
|
342 | } else {
|
343 | delete delta[1]['$set'][key];
|
344 | }
|
345 | }
|
346 | }
|
347 | if (delta) {
|
348 | if (delta instanceof MongooseError) {
|
349 | callback(delta);
|
350 | return;
|
351 | }
|
352 |
|
353 | const where = this.$__where(delta[0]);
|
354 | if (where instanceof MongooseError) {
|
355 | callback(where);
|
356 | return;
|
357 | }
|
358 |
|
359 | _applyCustomWhere(this, where);
|
360 |
|
361 | const update = delta[1];
|
362 | if (this.$__schema.options.minimize) {
|
363 | for (const updateOp of Object.values(update)) {
|
364 | if (updateOp == null) {
|
365 | continue;
|
366 | }
|
367 | for (const key of Object.keys(updateOp)) {
|
368 | if (updateOp[key] == null || typeof updateOp[key] !== 'object') {
|
369 | continue;
|
370 | }
|
371 | if (!utils.isPOJO(updateOp[key])) {
|
372 | continue;
|
373 | }
|
374 | minimize(updateOp[key]);
|
375 | if (Object.keys(updateOp[key]).length === 0) {
|
376 | delete updateOp[key];
|
377 | update.$unset = update.$unset || {};
|
378 | update.$unset[key] = 1;
|
379 | }
|
380 | }
|
381 | }
|
382 | }
|
383 |
|
384 | this[modelCollectionSymbol].updateOne(where, update, saveOptions).then(
|
385 | ret => {
|
386 | ret.$where = where;
|
387 | callback(null, ret);
|
388 | },
|
389 | err => {
|
390 | this.$__undoReset();
|
391 |
|
392 | callback(err);
|
393 | }
|
394 | );
|
395 | } else {
|
396 | handleEmptyUpdate.call(this);
|
397 | return;
|
398 | }
|
399 |
|
400 | // store the modified paths before the document is reset
|
401 | this.$__.modifiedPaths = this.modifiedPaths();
|
402 | this.$__reset();
|
403 |
|
404 | _setIsNew(this, false);
|
405 |
|
406 | function handleEmptyUpdate() {
|
407 | const optionsWithCustomValues = Object.assign({}, options, saveOptions);
|
408 | const where = this.$__where();
|
409 | const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
|
410 | if (optimisticConcurrency && !Array.isArray(optimisticConcurrency)) {
|
411 | const key = this.$__schema.options.versionKey;
|
412 | const val = this.$__getValue(key);
|
413 | if (val != null) {
|
414 | where[key] = val;
|
415 | }
|
416 | }
|
417 |
|
418 | applyReadConcern(this.$__schema, optionsWithCustomValues);
|
419 | this.constructor.collection.findOne(where, optionsWithCustomValues)
|
420 | .then(documentExists => {
|
421 | const matchedCount = !documentExists ? 0 : 1;
|
422 | callback(null, { $where: where, matchedCount });
|
423 | })
|
424 | .catch(callback);
|
425 | }
|
426 | };
|
427 |
|
428 | /*!
|
429 | * ignore
|
430 | */
|
431 |
|
432 | Model.prototype.$__save = function(options, callback) {
|
433 | this.$__handleSave(options, (error, result) => {
|
434 | if (error) {
|
435 | const hooks = this.$__schema.s.hooks;
|
436 | return hooks.execPost('save:error', this, [this], { error: error }, (error) => {
|
437 | callback(error, this);
|
438 | });
|
439 | }
|
440 | let numAffected = 0;
|
441 | const writeConcern = options != null ?
|
442 | options.writeConcern != null ?
|
443 | options.writeConcern.w :
|
444 | options.w :
|
445 | 0;
|
446 | if (writeConcern !== 0) {
|
447 | // Skip checking if write succeeded if writeConcern is set to
|
448 | // unacknowledged writes, because otherwise `numAffected` will always be 0
|
449 | if (result != null) {
|
450 | if (Array.isArray(result)) {
|
451 | numAffected = result.length;
|
452 | } else if (result.matchedCount != null) {
|
453 | numAffected = result.matchedCount;
|
454 | } else {
|
455 | numAffected = result;
|
456 | }
|
457 | }
|
458 |
|
459 | const versionBump = this.$__.version;
|
460 | // was this an update that required a version bump?
|
461 | if (versionBump && !this.$__.inserting) {
|
462 | const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
|
463 | this.$__.version = undefined;
|
464 | const key = this.$__schema.options.versionKey;
|
465 | const version = this.$__getValue(key) || 0;
|
466 | if (numAffected <= 0) {
|
467 | // the update failed. pass an error back
|
468 | this.$__undoReset();
|
469 | const err = this.$__.$versionError ||
|
470 | new VersionError(this, version, this.$__.modifiedPaths);
|
471 | return callback(err);
|
472 | }
|
473 |
|
474 | // increment version if was successful
|
475 | if (doIncrement) {
|
476 | this.$__setValue(key, version + 1);
|
477 | }
|
478 | }
|
479 | if (result != null && numAffected <= 0) {
|
480 | this.$__undoReset();
|
481 | error = new DocumentNotFoundError(result.$where,
|
482 | this.constructor.modelName, numAffected, result);
|
483 | const hooks = this.$__schema.s.hooks;
|
484 | return hooks.execPost('save:error', this, [this], { error: error }, (error) => {
|
485 | callback(error, this);
|
486 | });
|
487 | }
|
488 | }
|
489 | this.$__.saving = undefined;
|
490 | this.$__.savedState = {};
|
491 | this.$emit('save', this, numAffected);
|
492 | this.constructor.emit('save', this, numAffected);
|
493 | callback(null, this);
|
494 | });
|
495 | };
|
496 |
|
497 | /*!
|
498 | * ignore
|
499 | */
|
500 |
|
501 | function generateVersionError(doc, modifiedPaths) {
|
502 | const key = doc.$__schema.options.versionKey;
|
503 | if (!key) {
|
504 | return null;
|
505 | }
|
506 | const version = doc.$__getValue(key) || 0;
|
507 | return new VersionError(doc, version, modifiedPaths);
|
508 | }
|
509 |
|
510 | /**
|
511 | * Saves this document by inserting a new document into the database if [document.isNew](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) is `true`,
|
512 | * or sends an [updateOne](https://mongoosejs.com/docs/api/document.html#Document.prototype.updateOne()) operation with just the modified paths if `isNew` is `false`.
|
513 | *
|
514 | * #### Example:
|
515 | *
|
516 | * product.sold = Date.now();
|
517 | * product = await product.save();
|
518 | *
|
519 | * If save is successful, the returned promise will fulfill with the document
|
520 | * saved.
|
521 | *
|
522 | * #### Example:
|
523 | *
|
524 | * const newProduct = await product.save();
|
525 | * newProduct === product; // true
|
526 | *
|
527 | * @param {Object} [options] options optional options
|
528 | * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.session()).
|
529 | * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead.
|
530 | * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
|
531 | * @param {Boolean} [options.validateModifiedOnly=false] if `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
|
532 | * @param {Number|String} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)
|
533 | * @param {Boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)
|
534 | * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern).
|
535 | * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#mongodb-limit-Restrictions-on-Field-Names)
|
536 | * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
|
537 | * @param {Array} [options.pathsToSave] An array of paths that tell mongoose to only validate and save the paths in `pathsToSave`.
|
538 | * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
|
539 | * @return {Promise}
|
540 | * @api public
|
541 | * @see middleware https://mongoosejs.com/docs/middleware.html
|
542 | */
|
543 |
|
544 | Model.prototype.save = async function save(options) {
|
545 | if (typeof options === 'function' || typeof arguments[1] === 'function') {
|
546 | throw new MongooseError('Model.prototype.save() no longer accepts a callback');
|
547 | }
|
548 |
|
549 | let parallelSave;
|
550 | this.$op = 'save';
|
551 |
|
552 | if (this.$__.saving) {
|
553 | parallelSave = new ParallelSaveError(this);
|
554 | } else {
|
555 | this.$__.saving = new ParallelSaveError(this);
|
556 | }
|
557 |
|
558 | options = new SaveOptions(options);
|
559 | if (options.hasOwnProperty('session')) {
|
560 | this.$session(options.session);
|
561 | }
|
562 | if (this.$__.timestamps != null) {
|
563 | options.timestamps = this.$__.timestamps;
|
564 | }
|
565 | this.$__.$versionError = generateVersionError(this, this.modifiedPaths());
|
566 |
|
567 | if (parallelSave) {
|
568 | this.$__handleReject(parallelSave);
|
569 | throw parallelSave;
|
570 | }
|
571 |
|
572 | this.$__.saveOptions = options;
|
573 |
|
574 | await new Promise((resolve, reject) => {
|
575 | this.$__save(options, error => {
|
576 | this.$__.saving = null;
|
577 | this.$__.saveOptions = null;
|
578 | this.$__.$versionError = null;
|
579 | this.$op = null;
|
580 | if (error != null) {
|
581 | this.$__handleReject(error);
|
582 | return reject(error);
|
583 | }
|
584 |
|
585 | resolve();
|
586 | });
|
587 | });
|
588 |
|
589 | return this;
|
590 | };
|
591 |
|
592 | Model.prototype.$save = Model.prototype.save;
|
593 |
|
594 | /**
|
595 | * Appends versioning to the where and update clauses.
|
596 | *
|
597 | * @api private
|
598 | * @method $__version
|
599 | * @memberOf Model
|
600 | * @instance
|
601 | */
|
602 |
|
603 | Model.prototype.$__version = function(where, delta) {
|
604 | const key = this.$__schema.options.versionKey;
|
605 | if (where === true) {
|
606 | // this is an insert
|
607 | if (key) {
|
608 | setDottedPath(delta, key, 0);
|
609 | this.$__setValue(key, 0);
|
610 | }
|
611 | return;
|
612 | }
|
613 |
|
614 | if (key === false) {
|
615 | return;
|
616 | }
|
617 |
|
618 | // updates
|
619 |
|
620 | // only apply versioning if our versionKey was selected. else
|
621 | // there is no way to select the correct version. we could fail
|
622 | // fast here and force them to include the versionKey but
|
623 | // thats a bit intrusive. can we do this automatically?
|
624 |
|
625 | if (!this.$__isSelected(key)) {
|
626 | return;
|
627 | }
|
628 |
|
629 | // $push $addToSet don't need the where clause set
|
630 | if (VERSION_WHERE === (VERSION_WHERE & this.$__.version)) {
|
631 | const value = this.$__getValue(key);
|
632 | if (value != null) where[key] = value;
|
633 | }
|
634 |
|
635 | if (VERSION_INC === (VERSION_INC & this.$__.version)) {
|
636 | if (get(delta.$set, key, null) != null) {
|
637 | // Version key is getting set, means we'll increment the doc's version
|
638 | // after a successful save, so we should set the incremented version so
|
639 | // future saves don't fail (gh-5779)
|
640 | ++delta.$set[key];
|
641 | } else {
|
642 | delta.$inc = delta.$inc || {};
|
643 | delta.$inc[key] = 1;
|
644 | }
|
645 | }
|
646 | };
|
647 |
|
648 | /**
|
649 | * Signal that we desire an increment of this documents version.
|
650 | *
|
651 | * #### Example:
|
652 | *
|
653 | * const doc = await Model.findById(id);
|
654 | * doc.increment();
|
655 | * await doc.save();
|
656 | *
|
657 | * @see versionKeys https://mongoosejs.com/docs/guide.html#versionKey
|
658 | * @memberOf Model
|
659 | * @method increment
|
660 | * @api public
|
661 | */
|
662 |
|
663 | Model.prototype.increment = function increment() {
|
664 | this.$__.version = VERSION_ALL;
|
665 | return this;
|
666 | };
|
667 |
|
668 | /**
|
669 | * Returns a query object
|
670 | *
|
671 | * @api private
|
672 | * @method $__where
|
673 | * @memberOf Model
|
674 | * @instance
|
675 | */
|
676 |
|
677 | Model.prototype.$__where = function _where(where) {
|
678 | where || (where = {});
|
679 |
|
680 | if (!where._id) {
|
681 | where._id = this._doc._id;
|
682 | }
|
683 |
|
684 | if (this._doc._id === void 0) {
|
685 | return new MongooseError('No _id found on document!');
|
686 | }
|
687 |
|
688 | return where;
|
689 | };
|
690 |
|
691 | /**
|
692 | * Delete this document from the db.
|
693 | *
|
694 | * #### Example:
|
695 | *
|
696 | * await product.deleteOne();
|
697 | * await Product.findById(product._id); // null
|
698 | *
|
699 | * @return {Query} Query
|
700 | * @api public
|
701 | */
|
702 |
|
703 | Model.prototype.deleteOne = function deleteOne(options) {
|
704 | if (typeof options === 'function' ||
|
705 | typeof arguments[1] === 'function') {
|
706 | throw new MongooseError('Model.prototype.deleteOne() no longer accepts a callback');
|
707 | }
|
708 |
|
709 | if (!options) {
|
710 | options = {};
|
711 | }
|
712 |
|
713 | if (options.hasOwnProperty('session')) {
|
714 | this.$session(options.session);
|
715 | }
|
716 |
|
717 | const self = this;
|
718 | const where = this.$__where();
|
719 | if (where instanceof Error) {
|
720 | throw where;
|
721 | }
|
722 | const query = self.constructor.deleteOne(where, options);
|
723 |
|
724 | if (this.$session() != null) {
|
725 | if (!('session' in query.options)) {
|
726 | query.options.session = this.$session();
|
727 | }
|
728 | }
|
729 |
|
730 | query.pre(function queryPreDeleteOne(cb) {
|
731 | self.constructor._middleware.execPre('deleteOne', self, [self], cb);
|
732 | });
|
733 | query.pre(function callSubdocPreHooks(cb) {
|
734 | each(self.$getAllSubdocs(), (subdoc, cb) => {
|
735 | subdoc.constructor._middleware.execPre('deleteOne', subdoc, [subdoc], cb);
|
736 | }, cb);
|
737 | });
|
738 | query.pre(function skipIfAlreadyDeleted(cb) {
|
739 | if (self.$__.isDeleted) {
|
740 | return cb(Kareem.skipWrappedFunction());
|
741 | }
|
742 | return cb();
|
743 | });
|
744 | query.post(function callSubdocPostHooks(cb) {
|
745 | each(self.$getAllSubdocs(), (subdoc, cb) => {
|
746 | subdoc.constructor._middleware.execPost('deleteOne', subdoc, [subdoc], {}, cb);
|
747 | }, cb);
|
748 | });
|
749 | query.post(function queryPostDeleteOne(cb) {
|
750 | self.constructor._middleware.execPost('deleteOne', self, [self], {}, cb);
|
751 | });
|
752 |
|
753 | return query;
|
754 | };
|
755 |
|
756 | /**
|
757 | * Returns the model instance used to create this document if no `name` specified.
|
758 | * If `name` specified, returns the model with the given `name`.
|
759 | *
|
760 | * #### Example:
|
761 | *
|
762 | * const doc = new Tank({});
|
763 | * doc.$model() === Tank; // true
|
764 | * await doc.$model('User').findById(id);
|
765 | *
|
766 | * @param {String} [name] model name
|
767 | * @method $model
|
768 | * @api public
|
769 | * @return {Model}
|
770 | */
|
771 |
|
772 | Model.prototype.$model = function $model(name) {
|
773 | if (arguments.length === 0) {
|
774 | return this.constructor;
|
775 | }
|
776 | return this[modelDbSymbol].model(name);
|
777 | };
|
778 |
|
779 | /**
|
780 | * Returns the model instance used to create this document if no `name` specified.
|
781 | * If `name` specified, returns the model with the given `name`.
|
782 | *
|
783 | * #### Example:
|
784 | *
|
785 | * const doc = new Tank({});
|
786 | * doc.$model() === Tank; // true
|
787 | * await doc.$model('User').findById(id);
|
788 | *
|
789 | * @param {String} [name] model name
|
790 | * @method model
|
791 | * @api public
|
792 | * @return {Model}
|
793 | */
|
794 |
|
795 | Model.prototype.model = Model.prototype.$model;
|
796 |
|
797 | /**
|
798 | * Returns a document with `_id` only if at least one document exists in the database that matches
|
799 | * the given `filter`, and `null` otherwise.
|
800 | *
|
801 | * Under the hood, `MyModel.exists({ answer: 42 })` is equivalent to
|
802 | * `MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean()`
|
803 | *
|
804 | * #### Example:
|
805 | *
|
806 | * await Character.deleteMany({});
|
807 | * await Character.create({ name: 'Jean-Luc Picard' });
|
808 | *
|
809 | * await Character.exists({ name: /picard/i }); // { _id: ... }
|
810 | * await Character.exists({ name: /riker/i }); // null
|
811 | *
|
812 | * This function triggers the following middleware.
|
813 | *
|
814 | * - `findOne()`
|
815 | *
|
816 | * @param {Object} filter
|
817 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
818 | * @return {Query}
|
819 | */
|
820 |
|
821 | Model.exists = function exists(filter, options) {
|
822 | _checkContext(this, 'exists');
|
823 | if (typeof arguments[2] === 'function') {
|
824 | throw new MongooseError('Model.exists() no longer accepts a callback');
|
825 | }
|
826 |
|
827 | const query = this.findOne(filter).
|
828 | select({ _id: 1 }).
|
829 | lean().
|
830 | setOptions(options);
|
831 |
|
832 | return query;
|
833 | };
|
834 |
|
835 | /**
|
836 | * Adds a discriminator type.
|
837 | *
|
838 | * #### Example:
|
839 | *
|
840 | * function BaseSchema() {
|
841 | * Schema.apply(this, arguments);
|
842 | *
|
843 | * this.add({
|
844 | * name: String,
|
845 | * createdAt: Date
|
846 | * });
|
847 | * }
|
848 | * util.inherits(BaseSchema, Schema);
|
849 | *
|
850 | * const PersonSchema = new BaseSchema();
|
851 | * const BossSchema = new BaseSchema({ department: String });
|
852 | *
|
853 | * const Person = mongoose.model('Person', PersonSchema);
|
854 | * const Boss = Person.discriminator('Boss', BossSchema);
|
855 | * new Boss().__t; // "Boss". `__t` is the default `discriminatorKey`
|
856 | *
|
857 | * const employeeSchema = new Schema({ boss: ObjectId });
|
858 | * const Employee = Person.discriminator('Employee', employeeSchema, 'staff');
|
859 | * new Employee().__t; // "staff" because of 3rd argument above
|
860 | *
|
861 | * @param {String} name discriminator model name
|
862 | * @param {Schema} schema discriminator model schema
|
863 | * @param {Object|String} [options] If string, same as `options.value`.
|
864 | * @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
|
865 | * @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning.
|
866 | * @param {Boolean} [options.overwriteModels=false] by default, Mongoose does not allow you to define a discriminator with the same name as another discriminator. Set this to allow overwriting discriminators with the same name.
|
867 | * @param {Boolean} [options.mergeHooks=true] By default, Mongoose merges the base schema's hooks with the discriminator schema's hooks. Set this option to `false` to make Mongoose use the discriminator schema's hooks instead.
|
868 | * @param {Boolean} [options.mergePlugins=true] By default, Mongoose merges the base schema's plugins with the discriminator schema's plugins. Set this option to `false` to make Mongoose use the discriminator schema's plugins instead.
|
869 | * @return {Model} The newly created discriminator model
|
870 | * @api public
|
871 | */
|
872 |
|
873 | Model.discriminator = function(name, schema, options) {
|
874 | let model;
|
875 | if (typeof name === 'function') {
|
876 | model = name;
|
877 | name = utils.getFunctionName(model);
|
878 | if (!(model.prototype instanceof Model)) {
|
879 | throw new MongooseError('The provided class ' + name + ' must extend Model');
|
880 | }
|
881 | }
|
882 |
|
883 | options = options || {};
|
884 | const value = utils.isPOJO(options) ? options.value : options;
|
885 | const clone = typeof options.clone === 'boolean' ? options.clone : true;
|
886 | const mergePlugins = typeof options.mergePlugins === 'boolean' ? options.mergePlugins : true;
|
887 | const overwriteModels = typeof options.overwriteModels === 'boolean' ? options.overwriteModels : false;
|
888 |
|
889 | _checkContext(this, 'discriminator');
|
890 |
|
891 | if (utils.isObject(schema) && !schema.instanceOfSchema) {
|
892 | schema = new Schema(schema);
|
893 | }
|
894 | if (schema instanceof Schema && clone) {
|
895 | schema = schema.clone();
|
896 | }
|
897 |
|
898 | schema = discriminator(this, name, schema, value, mergePlugins, options.mergeHooks, overwriteModels);
|
899 | if (this.db.models[name] && !schema.options.overwriteModels && !overwriteModels) {
|
900 | throw new OverwriteModelError(name);
|
901 | }
|
902 |
|
903 | schema.$isRootDiscriminator = true;
|
904 | schema.$globalPluginsApplied = true;
|
905 |
|
906 | model = this.db.model(model || name, schema, this.$__collection.name);
|
907 | this.discriminators[name] = model;
|
908 | const d = this.discriminators[name];
|
909 | Object.setPrototypeOf(d.prototype, this.prototype);
|
910 | Object.defineProperty(d, 'baseModelName', {
|
911 | value: this.modelName,
|
912 | configurable: true,
|
913 | writable: false
|
914 | });
|
915 |
|
916 | // apply methods and statics
|
917 | applyMethods(d, schema);
|
918 | applyStatics(d, schema);
|
919 |
|
920 | if (this[subclassedSymbol] != null) {
|
921 | for (const submodel of this[subclassedSymbol]) {
|
922 | submodel.discriminators = submodel.discriminators || {};
|
923 | submodel.discriminators[name] =
|
924 | model.__subclass(model.db, schema, submodel.collection.name);
|
925 | }
|
926 | }
|
927 |
|
928 | return d;
|
929 | };
|
930 |
|
931 | /**
|
932 | * Make sure `this` is a model
|
933 | * @api private
|
934 | */
|
935 |
|
936 | function _checkContext(ctx, fnName) {
|
937 | // Check context, because it is easy to mistakenly type
|
938 | // `new Model.discriminator()` and get an incomprehensible error
|
939 | if (ctx == null || ctx === global) {
|
940 | throw new MongooseError('`Model.' + fnName + '()` cannot run without a ' +
|
941 | 'model as `this`. Make sure you are calling `MyModel.' + fnName + '()` ' +
|
942 | 'where `MyModel` is a Mongoose model.');
|
943 | } else if (ctx[modelSymbol] == null) {
|
944 | throw new MongooseError('`Model.' + fnName + '()` cannot run without a ' +
|
945 | 'model as `this`. Make sure you are not calling ' +
|
946 | '`new Model.' + fnName + '()`');
|
947 | }
|
948 | }
|
949 |
|
950 | // Model (class) features
|
951 |
|
952 | /*!
|
953 | * Give the constructor the ability to emit events.
|
954 | */
|
955 |
|
956 | for (const i in EventEmitter.prototype) {
|
957 | Model[i] = EventEmitter.prototype[i];
|
958 | }
|
959 |
|
960 | /**
|
961 | * This function is responsible for initializing the underlying connection in MongoDB based on schema options.
|
962 | * This function performs the following operations:
|
963 | *
|
964 | * - `createCollection()` unless [`autoCreate`](https://mongoosejs.com/docs/guide.html#autoCreate) option is turned off
|
965 | * - `ensureIndexes()` unless [`autoIndex`](https://mongoosejs.com/docs/guide.html#autoIndex) option is turned off
|
966 | * - `createSearchIndex()` on all schema search indexes if `autoSearchIndex` is enabled.
|
967 | *
|
968 | * Mongoose calls this function automatically when a model is a created using
|
969 | * [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) or
|
970 | * [`connection.model()`](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.model()), so you
|
971 | * don't need to call `init()` to trigger index builds.
|
972 | *
|
973 | * However, you _may_ need to call `init()` to get back a promise that will resolve when your indexes are finished.
|
974 | * Calling `await Model.init()` is helpful if you need to wait for indexes to build before continuing.
|
975 | * For example, if you want to wait for unique indexes to build before continuing with a test case.
|
976 | *
|
977 | * #### Example:
|
978 | *
|
979 | * const eventSchema = new Schema({ thing: { type: 'string', unique: true } })
|
980 | * // This calls `Event.init()` implicitly, so you don't need to call
|
981 | * // `Event.init()` on your own.
|
982 | * const Event = mongoose.model('Event', eventSchema);
|
983 | *
|
984 | * await Event.init();
|
985 | * console.log('Indexes are done building!');
|
986 | *
|
987 | * @api public
|
988 | * @returns {Promise}
|
989 | */
|
990 |
|
991 | Model.init = function init() {
|
992 | _checkContext(this, 'init');
|
993 | if (typeof arguments[0] === 'function') {
|
994 | throw new MongooseError('Model.init() no longer accepts a callback');
|
995 | }
|
996 |
|
997 | this.schema.emit('init', this);
|
998 |
|
999 | if (this.$init != null) {
|
1000 | return this.$init;
|
1001 | }
|
1002 |
|
1003 | const conn = this.db;
|
1004 | const _ensureIndexes = async() => {
|
1005 | const autoIndex = utils.getOption(
|
1006 | 'autoIndex',
|
1007 | this.schema.options,
|
1008 | conn.config,
|
1009 | conn.base.options
|
1010 | );
|
1011 | if (!autoIndex) {
|
1012 | return;
|
1013 | }
|
1014 | return await this.ensureIndexes({ _automatic: true });
|
1015 | };
|
1016 | const _createSearchIndexes = async() => {
|
1017 | const autoSearchIndex = utils.getOption(
|
1018 | 'autoSearchIndex',
|
1019 | this.schema.options,
|
1020 | conn.config,
|
1021 | conn.base.options
|
1022 | );
|
1023 | if (!autoSearchIndex) {
|
1024 | return;
|
1025 | }
|
1026 |
|
1027 | const results = [];
|
1028 | for (const searchIndex of this.schema._searchIndexes) {
|
1029 | results.push(await this.createSearchIndex(searchIndex));
|
1030 | }
|
1031 | return results;
|
1032 | };
|
1033 | const _createCollection = async() => {
|
1034 | if ((conn.readyState === STATES.connecting || conn.readyState === STATES.disconnected) && conn._shouldBufferCommands()) {
|
1035 | await new Promise(resolve => {
|
1036 | conn._queue.push({ fn: resolve });
|
1037 | });
|
1038 | }
|
1039 | const autoCreate = utils.getOption(
|
1040 | 'autoCreate',
|
1041 | this.schema.options,
|
1042 | conn.config,
|
1043 | conn.base.options
|
1044 | );
|
1045 | if (!autoCreate) {
|
1046 | return;
|
1047 | }
|
1048 | return await this.createCollection();
|
1049 | };
|
1050 |
|
1051 | this.$init = _createCollection().
|
1052 | then(() => _ensureIndexes()).
|
1053 | then(() => _createSearchIndexes());
|
1054 |
|
1055 | const _catch = this.$init.catch;
|
1056 | const _this = this;
|
1057 | this.$init.catch = function() {
|
1058 | _this.$caught = true;
|
1059 | return _catch.apply(_this.$init, arguments);
|
1060 | };
|
1061 |
|
1062 | return this.$init;
|
1063 | };
|
1064 |
|
1065 |
|
1066 | /**
|
1067 | * Create the collection for this model. By default, if no indexes are specified,
|
1068 | * mongoose will not create the collection for the model until any documents are
|
1069 | * created. Use this method to create the collection explicitly.
|
1070 | *
|
1071 | * Note 1: You may need to call this before starting a transaction
|
1072 | * See https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
|
1073 | *
|
1074 | * Note 2: You don't have to call this if your schema contains index or unique field.
|
1075 | * In that case, just use `Model.init()`
|
1076 | *
|
1077 | * #### Example:
|
1078 | *
|
1079 | * const userSchema = new Schema({ name: String })
|
1080 | * const User = mongoose.model('User', userSchema);
|
1081 | *
|
1082 | * User.createCollection().then(function(collection) {
|
1083 | * console.log('Collection is created!');
|
1084 | * });
|
1085 | *
|
1086 | * @api public
|
1087 | * @param {Object} [options] see [MongoDB driver docs](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createCollection)
|
1088 | * @returns {Promise}
|
1089 | */
|
1090 |
|
1091 | Model.createCollection = async function createCollection(options) {
|
1092 | _checkContext(this, 'createCollection');
|
1093 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
|
1094 | throw new MongooseError('Model.createCollection() no longer accepts a callback');
|
1095 | }
|
1096 |
|
1097 | const shouldSkip = await new Promise((resolve, reject) => {
|
1098 | this.hooks.execPre('createCollection', this, [options], (err) => {
|
1099 | if (err != null) {
|
1100 | if (err instanceof Kareem.skipWrappedFunction) {
|
1101 | return resolve(true);
|
1102 | }
|
1103 | return reject(err);
|
1104 | }
|
1105 | resolve();
|
1106 | });
|
1107 | });
|
1108 |
|
1109 | const collectionOptions = this &&
|
1110 | this.schema &&
|
1111 | this.schema.options &&
|
1112 | this.schema.options.collectionOptions;
|
1113 | if (collectionOptions != null) {
|
1114 | options = Object.assign({}, collectionOptions, options);
|
1115 | }
|
1116 |
|
1117 | const schemaCollation = this &&
|
1118 | this.schema &&
|
1119 | this.schema.options &&
|
1120 | this.schema.options.collation;
|
1121 | if (schemaCollation != null) {
|
1122 | options = Object.assign({ collation: schemaCollation }, options);
|
1123 | }
|
1124 | const capped = this &&
|
1125 | this.schema &&
|
1126 | this.schema.options &&
|
1127 | this.schema.options.capped;
|
1128 | if (capped != null) {
|
1129 | if (typeof capped === 'number') {
|
1130 | options = Object.assign({ capped: true, size: capped }, options);
|
1131 | } else if (typeof capped === 'object') {
|
1132 | options = Object.assign({ capped: true }, capped, options);
|
1133 | }
|
1134 | }
|
1135 | const timeseries = this &&
|
1136 | this.schema &&
|
1137 | this.schema.options &&
|
1138 | this.schema.options.timeseries;
|
1139 | if (timeseries != null) {
|
1140 | options = Object.assign({ timeseries }, options);
|
1141 | if (options.expireAfterSeconds != null) {
|
1142 | // do nothing
|
1143 | } else if (options.expires != null) {
|
1144 | utils.expires(options);
|
1145 | } else if (this.schema.options.expireAfterSeconds != null) {
|
1146 | options.expireAfterSeconds = this.schema.options.expireAfterSeconds;
|
1147 | } else if (this.schema.options.expires != null) {
|
1148 | options.expires = this.schema.options.expires;
|
1149 | utils.expires(options);
|
1150 | }
|
1151 | }
|
1152 |
|
1153 | const clusteredIndex = this &&
|
1154 | this.schema &&
|
1155 | this.schema.options &&
|
1156 | this.schema.options.clusteredIndex;
|
1157 | if (clusteredIndex != null) {
|
1158 | options = Object.assign({ clusteredIndex: { ...clusteredIndex, unique: true } }, options);
|
1159 | }
|
1160 |
|
1161 | try {
|
1162 | if (!shouldSkip) {
|
1163 | await this.db.createCollection(this.$__collection.collectionName, options);
|
1164 | }
|
1165 | } catch (err) {
|
1166 | if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
|
1167 | await new Promise((resolve, reject) => {
|
1168 | const _opts = { error: err };
|
1169 | this.hooks.execPost('createCollection', this, [null], _opts, (err) => {
|
1170 | if (err != null) {
|
1171 | return reject(err);
|
1172 | }
|
1173 | resolve();
|
1174 | });
|
1175 | });
|
1176 | }
|
1177 | }
|
1178 |
|
1179 | await new Promise((resolve, reject) => {
|
1180 | this.hooks.execPost('createCollection', this, [this.$__collection], (err) => {
|
1181 | if (err != null) {
|
1182 | return reject(err);
|
1183 | }
|
1184 | resolve();
|
1185 | });
|
1186 | });
|
1187 |
|
1188 | return this.$__collection;
|
1189 | };
|
1190 |
|
1191 | /**
|
1192 | * Makes the indexes in MongoDB match the indexes defined in this model's
|
1193 | * schema. This function will drop any indexes that are not defined in
|
1194 | * the model's schema except the `_id` index, and build any indexes that
|
1195 | * are in your schema but not in MongoDB.
|
1196 | *
|
1197 | * See the [introductory blog post](https://thecodebarbarian.com/whats-new-in-mongoose-5-2-syncindexes)
|
1198 | * for more information.
|
1199 | *
|
1200 | * #### Example:
|
1201 | *
|
1202 | * const schema = new Schema({ name: { type: String, unique: true } });
|
1203 | * const Customer = mongoose.model('Customer', schema);
|
1204 | * await Customer.collection.createIndex({ age: 1 }); // Index is not in schema
|
1205 | * // Will drop the 'age' index and create an index on `name`
|
1206 | * await Customer.syncIndexes();
|
1207 | *
|
1208 | * You should be careful about running `syncIndexes()` on production applications under heavy load,
|
1209 | * because index builds are expensive operations, and unexpected index drops can lead to degraded
|
1210 | * performance. Before running `syncIndexes()`, you can use the [`diffIndexes()` function](#Model.diffIndexes())
|
1211 | * to check what indexes `syncIndexes()` will drop and create.
|
1212 | *
|
1213 | * #### Example:
|
1214 | *
|
1215 | * const { toDrop, toCreate } = await Model.diffIndexes();
|
1216 | * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop
|
1217 | * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create
|
1218 | *
|
1219 | * @param {Object} [options] options to pass to `ensureIndexes()`
|
1220 | * @param {Boolean} [options.background=null] if specified, overrides each index's `background` property
|
1221 | * @return {Promise}
|
1222 | * @api public
|
1223 | */
|
1224 |
|
1225 | Model.syncIndexes = async function syncIndexes(options) {
|
1226 | _checkContext(this, 'syncIndexes');
|
1227 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
|
1228 | throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
|
1229 | }
|
1230 |
|
1231 | const model = this;
|
1232 |
|
1233 | try {
|
1234 | await model.createCollection();
|
1235 | } catch (err) {
|
1236 | if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
|
1237 | throw err;
|
1238 | }
|
1239 | }
|
1240 |
|
1241 | const diffIndexesResult = await model.diffIndexes();
|
1242 | const dropped = await model.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop });
|
1243 | await model.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate });
|
1244 |
|
1245 | return dropped;
|
1246 | };
|
1247 |
|
1248 | /**
|
1249 | * Create an [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
|
1250 | * This function only works when connected to MongoDB Atlas.
|
1251 | *
|
1252 | * #### Example:
|
1253 | *
|
1254 | * const schema = new Schema({ name: { type: String, unique: true } });
|
1255 | * const Customer = mongoose.model('Customer', schema);
|
1256 | * await Customer.createSearchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
|
1257 | *
|
1258 | * @param {Object} description index options, including `name` and `definition`
|
1259 | * @param {String} description.name
|
1260 | * @param {Object} description.definition
|
1261 | * @return {Promise}
|
1262 | * @api public
|
1263 | */
|
1264 |
|
1265 | Model.createSearchIndex = async function createSearchIndex(description) {
|
1266 | _checkContext(this, 'createSearchIndex');
|
1267 |
|
1268 | return await this.$__collection.createSearchIndex(description);
|
1269 | };
|
1270 |
|
1271 | /**
|
1272 | * Update an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/).
|
1273 | * This function only works when connected to MongoDB Atlas.
|
1274 | *
|
1275 | * #### Example:
|
1276 | *
|
1277 | * const schema = new Schema({ name: { type: String, unique: true } });
|
1278 | * const Customer = mongoose.model('Customer', schema);
|
1279 | * await Customer.updateSearchIndex('test', { mappings: { dynamic: true } });
|
1280 | *
|
1281 | * @param {String} name
|
1282 | * @param {Object} definition
|
1283 | * @return {Promise}
|
1284 | * @api public
|
1285 | */
|
1286 |
|
1287 | Model.updateSearchIndex = async function updateSearchIndex(name, definition) {
|
1288 | _checkContext(this, 'updateSearchIndex');
|
1289 |
|
1290 | return await this.$__collection.updateSearchIndex(name, definition);
|
1291 | };
|
1292 |
|
1293 | /**
|
1294 | * Delete an existing [Atlas search index](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) by name.
|
1295 | * This function only works when connected to MongoDB Atlas.
|
1296 | *
|
1297 | * #### Example:
|
1298 | *
|
1299 | * const schema = new Schema({ name: { type: String, unique: true } });
|
1300 | * const Customer = mongoose.model('Customer', schema);
|
1301 | * await Customer.dropSearchIndex('test');
|
1302 | *
|
1303 | * @param {String} name
|
1304 | * @return {Promise}
|
1305 | * @api public
|
1306 | */
|
1307 |
|
1308 | Model.dropSearchIndex = async function dropSearchIndex(name) {
|
1309 | _checkContext(this, 'dropSearchIndex');
|
1310 |
|
1311 | return await this.$__collection.dropSearchIndex(name);
|
1312 | };
|
1313 |
|
1314 | /**
|
1315 | * List all [Atlas search indexes](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) on this model's collection.
|
1316 | * This function only works when connected to MongoDB Atlas.
|
1317 | *
|
1318 | * #### Example:
|
1319 | *
|
1320 | * const schema = new Schema({ name: { type: String, unique: true } });
|
1321 | * const Customer = mongoose.model('Customer', schema);
|
1322 | *
|
1323 | * await Customer.createSearchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
|
1324 | * const res = await Customer.listSearchIndexes(); // Includes `[{ name: 'test' }]`
|
1325 | *
|
1326 | * @param {Object} [options]
|
1327 | * @return {Promise<Array>}
|
1328 | * @api public
|
1329 | */
|
1330 |
|
1331 | Model.listSearchIndexes = async function listSearchIndexes(options) {
|
1332 | _checkContext(this, 'listSearchIndexes');
|
1333 |
|
1334 | const cursor = await this.$__collection.listSearchIndexes(options);
|
1335 |
|
1336 | return await cursor.toArray();
|
1337 | };
|
1338 |
|
1339 | /**
|
1340 | * Does a dry-run of `Model.syncIndexes()`, returning the indexes that `syncIndexes()` would drop and create if you were to run `syncIndexes()`.
|
1341 | *
|
1342 | * #### Example:
|
1343 | *
|
1344 | * const { toDrop, toCreate } = await Model.diffIndexes();
|
1345 | * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop
|
1346 | * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create
|
1347 | *
|
1348 | * @param {Object} [options]
|
1349 | * @return {Promise<Object>} contains the indexes that would be dropped in MongoDB and indexes that would be created in MongoDB as `{ toDrop: string[], toCreate: string[] }`.
|
1350 | */
|
1351 |
|
1352 | Model.diffIndexes = async function diffIndexes() {
|
1353 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
|
1354 | throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
|
1355 | }
|
1356 |
|
1357 | const model = this;
|
1358 |
|
1359 | let dbIndexes = await model.listIndexes().catch(err => {
|
1360 | if (err.codeName == 'NamespaceNotFound') {
|
1361 | return undefined;
|
1362 | }
|
1363 | throw err;
|
1364 | });
|
1365 | if (dbIndexes === undefined) {
|
1366 | dbIndexes = [];
|
1367 | }
|
1368 | dbIndexes = getRelatedDBIndexes(model, dbIndexes);
|
1369 |
|
1370 | const schema = model.schema;
|
1371 | const schemaIndexes = getRelatedSchemaIndexes(model, schema.indexes());
|
1372 |
|
1373 | const toDrop = getIndexesToDrop(schema, schemaIndexes, dbIndexes);
|
1374 | const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop);
|
1375 |
|
1376 | return { toDrop, toCreate };
|
1377 | };
|
1378 |
|
1379 | function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
|
1380 | const toCreate = [];
|
1381 |
|
1382 | for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
|
1383 | let found = false;
|
1384 |
|
1385 | const options = decorateDiscriminatorIndexOptions(schema, clone(schemaIndexOptions));
|
1386 |
|
1387 | for (const index of dbIndexes) {
|
1388 | if (isDefaultIdIndex(index)) {
|
1389 | continue;
|
1390 | }
|
1391 | if (
|
1392 | isIndexEqual(schemaIndexKeysObject, options, index) &&
|
1393 | !toDrop.includes(index.name)
|
1394 | ) {
|
1395 | found = true;
|
1396 | break;
|
1397 | }
|
1398 | }
|
1399 |
|
1400 | if (!found) {
|
1401 | toCreate.push(schemaIndexKeysObject);
|
1402 | }
|
1403 | }
|
1404 |
|
1405 | return toCreate;
|
1406 | }
|
1407 |
|
1408 | function getIndexesToDrop(schema, schemaIndexes, dbIndexes) {
|
1409 | const toDrop = [];
|
1410 |
|
1411 | for (const dbIndex of dbIndexes) {
|
1412 | let found = false;
|
1413 | // Never try to drop `_id` index, MongoDB server doesn't allow it
|
1414 | if (isDefaultIdIndex(dbIndex)) {
|
1415 | continue;
|
1416 | }
|
1417 |
|
1418 | for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
|
1419 | const options = decorateDiscriminatorIndexOptions(schema, clone(schemaIndexOptions));
|
1420 | applySchemaCollation(schemaIndexKeysObject, options, schema.options);
|
1421 |
|
1422 | if (isIndexEqual(schemaIndexKeysObject, options, dbIndex)) {
|
1423 | found = true;
|
1424 | break;
|
1425 | }
|
1426 | }
|
1427 |
|
1428 | if (!found) {
|
1429 | toDrop.push(dbIndex.name);
|
1430 | }
|
1431 | }
|
1432 |
|
1433 | return toDrop;
|
1434 | }
|
1435 | /**
|
1436 | * Deletes all indexes that aren't defined in this model's schema. Used by
|
1437 | * `syncIndexes()`.
|
1438 | *
|
1439 | * The returned promise resolves to a list of the dropped indexes' names as an array
|
1440 | *
|
1441 | * @param {Function} [callback] optional callback
|
1442 | * @return {Promise|undefined} Returns `undefined` if callback is specified, returns a promise if no callback.
|
1443 | * @api public
|
1444 | */
|
1445 |
|
1446 | Model.cleanIndexes = async function cleanIndexes(options) {
|
1447 | _checkContext(this, 'cleanIndexes');
|
1448 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
|
1449 | throw new MongooseError('Model.cleanIndexes() no longer accepts a callback');
|
1450 | }
|
1451 | const model = this;
|
1452 |
|
1453 | const collection = model.$__collection;
|
1454 |
|
1455 | if (Array.isArray(options && options.toDrop)) {
|
1456 | const res = await _dropIndexes(options.toDrop, collection);
|
1457 | return res;
|
1458 | }
|
1459 |
|
1460 | const res = await model.diffIndexes();
|
1461 | return await _dropIndexes(res.toDrop, collection);
|
1462 | };
|
1463 |
|
1464 | async function _dropIndexes(toDrop, collection) {
|
1465 | if (toDrop.length === 0) {
|
1466 | return [];
|
1467 | }
|
1468 |
|
1469 | await Promise.all(toDrop.map(indexName => collection.dropIndex(indexName)));
|
1470 | return toDrop;
|
1471 | }
|
1472 |
|
1473 | /**
|
1474 | * Lists the indexes currently defined in MongoDB. This may or may not be
|
1475 | * the same as the indexes defined in your schema depending on whether you
|
1476 | * use the [`autoIndex` option](https://mongoosejs.com/docs/guide.html#autoIndex) and if you
|
1477 | * build indexes manually.
|
1478 | *
|
1479 | * @return {Promise}
|
1480 | * @api public
|
1481 | */
|
1482 |
|
1483 | Model.listIndexes = async function listIndexes() {
|
1484 | _checkContext(this, 'listIndexes');
|
1485 | if (typeof arguments[0] === 'function') {
|
1486 | throw new MongooseError('Model.listIndexes() no longer accepts a callback');
|
1487 | }
|
1488 |
|
1489 | if (this.$__collection.buffer) {
|
1490 | await new Promise(resolve => {
|
1491 | this.$__collection.addQueue(resolve);
|
1492 | });
|
1493 | }
|
1494 |
|
1495 | return this.$__collection.listIndexes().toArray();
|
1496 | };
|
1497 |
|
1498 | /**
|
1499 | * Sends `createIndex` commands to mongo for each index declared in the schema.
|
1500 | * The `createIndex` commands are sent in series.
|
1501 | *
|
1502 | * #### Example:
|
1503 | *
|
1504 | * await Event.ensureIndexes();
|
1505 | *
|
1506 | * After completion, an `index` event is emitted on this `Model` passing an error if one occurred.
|
1507 | *
|
1508 | * #### Example:
|
1509 | *
|
1510 | * const eventSchema = new Schema({ thing: { type: 'string', unique: true } })
|
1511 | * const Event = mongoose.model('Event', eventSchema);
|
1512 | *
|
1513 | * Event.on('index', function (err) {
|
1514 | * if (err) console.error(err); // error occurred during index creation
|
1515 | * });
|
1516 | *
|
1517 | * _NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution._
|
1518 | *
|
1519 | * @param {Object} [options] internal options
|
1520 | * @return {Promise}
|
1521 | * @api public
|
1522 | */
|
1523 |
|
1524 | Model.ensureIndexes = async function ensureIndexes(options) {
|
1525 | _checkContext(this, 'ensureIndexes');
|
1526 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
|
1527 | throw new MongooseError('Model.ensureIndexes() no longer accepts a callback');
|
1528 | }
|
1529 |
|
1530 | await new Promise((resolve, reject) => {
|
1531 | _ensureIndexes(this, options, (err) => {
|
1532 | if (err != null) {
|
1533 | return reject(err);
|
1534 | }
|
1535 | resolve();
|
1536 | });
|
1537 | });
|
1538 | };
|
1539 |
|
1540 | /**
|
1541 | * Similar to `ensureIndexes()`, except for it uses the [`createIndex`](https://mongodb.github.io/node-mongodb-native/4.9/classes/Db.html#createIndex)
|
1542 | * function.
|
1543 | *
|
1544 | * @param {Object} [options] internal options
|
1545 | * @return {Promise}
|
1546 | * @api public
|
1547 | */
|
1548 |
|
1549 | Model.createIndexes = async function createIndexes(options) {
|
1550 | _checkContext(this, 'createIndexes');
|
1551 |
|
1552 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
|
1553 | throw new MongooseError('Model.createIndexes() no longer accepts a callback');
|
1554 | }
|
1555 |
|
1556 | return this.ensureIndexes(options);
|
1557 | };
|
1558 |
|
1559 |
|
1560 | /*!
|
1561 | * ignore
|
1562 | */
|
1563 |
|
1564 | function _ensureIndexes(model, options, callback) {
|
1565 | const indexes = model.schema.indexes();
|
1566 | let indexError;
|
1567 |
|
1568 | options = options || {};
|
1569 | const done = function(err) {
|
1570 | if (err && !model.$caught) {
|
1571 | model.emit('error', err);
|
1572 | }
|
1573 | model.emit('index', err || indexError);
|
1574 | callback && callback(err || indexError);
|
1575 | };
|
1576 |
|
1577 | for (const index of indexes) {
|
1578 | if (isDefaultIdIndex(index)) {
|
1579 | utils.warn('mongoose: Cannot specify a custom index on `_id` for ' +
|
1580 | 'model name "' + model.modelName + '", ' +
|
1581 | 'MongoDB does not allow overwriting the default `_id` index. See ' +
|
1582 | 'https://bit.ly/mongodb-id-index');
|
1583 | }
|
1584 | }
|
1585 |
|
1586 | if (!indexes.length) {
|
1587 | immediate(function() {
|
1588 | done();
|
1589 | });
|
1590 | return;
|
1591 | }
|
1592 | // Indexes are created one-by-one to support how MongoDB < 2.4 deals
|
1593 | // with background indexes.
|
1594 |
|
1595 | const indexSingleDone = function(err, fields, options, name) {
|
1596 | model.emit('index-single-done', err, fields, options, name);
|
1597 | };
|
1598 | const indexSingleStart = function(fields, options) {
|
1599 | model.emit('index-single-start', fields, options);
|
1600 | };
|
1601 |
|
1602 | const baseSchema = model.schema._baseSchema;
|
1603 | const baseSchemaIndexes = baseSchema ? baseSchema.indexes() : [];
|
1604 |
|
1605 | immediate(function() {
|
1606 | // If buffering is off, do this manually.
|
1607 | if (options._automatic && !model.collection.collection) {
|
1608 | model.collection.addQueue(create, []);
|
1609 | } else {
|
1610 | create();
|
1611 | }
|
1612 | });
|
1613 |
|
1614 |
|
1615 | function create() {
|
1616 | if (options._automatic) {
|
1617 | if (model.schema.options.autoIndex === false ||
|
1618 | (model.schema.options.autoIndex == null && model.db.config.autoIndex === false)) {
|
1619 | return done();
|
1620 | }
|
1621 | }
|
1622 |
|
1623 | const index = indexes.shift();
|
1624 | if (!index) {
|
1625 | return done();
|
1626 | }
|
1627 | if (options._automatic && index[1]._autoIndex === false) {
|
1628 | return create();
|
1629 | }
|
1630 |
|
1631 | if (baseSchemaIndexes.find(i => utils.deepEqual(i, index))) {
|
1632 | return create();
|
1633 | }
|
1634 |
|
1635 | const indexFields = clone(index[0]);
|
1636 | const indexOptions = clone(index[1]);
|
1637 |
|
1638 | delete indexOptions._autoIndex;
|
1639 | decorateDiscriminatorIndexOptions(model.schema, indexOptions);
|
1640 | applyWriteConcern(model.schema, indexOptions);
|
1641 | applySchemaCollation(indexFields, indexOptions, model.schema.options);
|
1642 |
|
1643 | indexSingleStart(indexFields, options);
|
1644 |
|
1645 | if ('background' in options) {
|
1646 | indexOptions.background = options.background;
|
1647 | }
|
1648 |
|
1649 | if ('toCreate' in options) {
|
1650 | if (options.toCreate.length === 0) {
|
1651 | return done();
|
1652 | }
|
1653 | }
|
1654 |
|
1655 | model.collection.createIndex(indexFields, indexOptions).then(
|
1656 | name => {
|
1657 | indexSingleDone(null, indexFields, indexOptions, name);
|
1658 | create();
|
1659 | },
|
1660 | err => {
|
1661 | if (!indexError) {
|
1662 | indexError = err;
|
1663 | }
|
1664 | if (!model.$caught) {
|
1665 | model.emit('error', err);
|
1666 | }
|
1667 |
|
1668 | indexSingleDone(err, indexFields, indexOptions);
|
1669 | create();
|
1670 | }
|
1671 | );
|
1672 | }
|
1673 | }
|
1674 |
|
1675 | /**
|
1676 | * Schema the model uses.
|
1677 | *
|
1678 | * @property schema
|
1679 | * @static
|
1680 | * @api public
|
1681 | * @memberOf Model
|
1682 | */
|
1683 |
|
1684 | Model.schema;
|
1685 |
|
1686 | /**
|
1687 | * Connection instance the model uses.
|
1688 | *
|
1689 | * @property db
|
1690 | * @static
|
1691 | * @api public
|
1692 | * @memberOf Model
|
1693 | */
|
1694 |
|
1695 | Model.db;
|
1696 |
|
1697 | /**
|
1698 | * Collection the model uses.
|
1699 | *
|
1700 | * @property collection
|
1701 | * @api public
|
1702 | * @memberOf Model
|
1703 | */
|
1704 |
|
1705 | Model.collection;
|
1706 |
|
1707 | /**
|
1708 | * Internal collection the model uses.
|
1709 | *
|
1710 | * @property collection
|
1711 | * @api private
|
1712 | * @memberOf Model
|
1713 | */
|
1714 | Model.$__collection;
|
1715 |
|
1716 | /**
|
1717 | * Base Mongoose instance the model uses.
|
1718 | *
|
1719 | * @property base
|
1720 | * @api public
|
1721 | * @memberOf Model
|
1722 | */
|
1723 |
|
1724 | Model.base;
|
1725 |
|
1726 | /**
|
1727 | * Registered discriminators for this model.
|
1728 | *
|
1729 | * @property discriminators
|
1730 | * @api public
|
1731 | * @memberOf Model
|
1732 | */
|
1733 |
|
1734 | Model.discriminators;
|
1735 |
|
1736 | /**
|
1737 | * Translate any aliases fields/conditions so the final query or document object is pure
|
1738 | *
|
1739 | * #### Example:
|
1740 | *
|
1741 | * await Character.find(Character.translateAliases({
|
1742 | * '名': 'Eddard Stark' // Alias for 'name'
|
1743 | * });
|
1744 | *
|
1745 | * By default, `translateAliases()` overwrites raw fields with aliased fields.
|
1746 | * So if `n` is an alias for `name`, `{ n: 'alias', name: 'raw' }` will resolve to `{ name: 'alias' }`.
|
1747 | * However, you can set the `errorOnDuplicates` option to throw an error if there are potentially conflicting paths.
|
1748 | * The `translateAliases` option for queries uses `errorOnDuplicates`.
|
1749 | *
|
1750 | * #### Note:
|
1751 | *
|
1752 | * Only translate arguments of object type anything else is returned raw
|
1753 | *
|
1754 | * @param {Object} fields fields/conditions that may contain aliased keys
|
1755 | * @param {Boolean} [errorOnDuplicates] if true, throw an error if there's both a key and an alias for that key in `fields`
|
1756 | * @return {Object} the translated 'pure' fields/conditions
|
1757 | */
|
1758 | Model.translateAliases = function translateAliases(fields, errorOnDuplicates) {
|
1759 | _checkContext(this, 'translateAliases');
|
1760 |
|
1761 | const translate = (key, value) => {
|
1762 | let alias;
|
1763 | const translated = [];
|
1764 | const fieldKeys = key.split('.');
|
1765 | let currentSchema = this.schema;
|
1766 | for (const i in fieldKeys) {
|
1767 | const name = fieldKeys[i];
|
1768 | if (currentSchema && currentSchema.aliases[name]) {
|
1769 | alias = currentSchema.aliases[name];
|
1770 | if (errorOnDuplicates && alias in fields) {
|
1771 | throw new MongooseError(`Provided object has both field "${name}" and its alias "${alias}"`);
|
1772 | }
|
1773 | // Alias found,
|
1774 | translated.push(alias);
|
1775 | } else {
|
1776 | alias = name;
|
1777 | // Alias not found, so treat as un-aliased key
|
1778 | translated.push(name);
|
1779 | }
|
1780 |
|
1781 | // Check if aliased path is a schema
|
1782 | if (currentSchema && currentSchema.paths[alias]) {
|
1783 | currentSchema = currentSchema.paths[alias].schema;
|
1784 | }
|
1785 | else
|
1786 | currentSchema = null;
|
1787 | }
|
1788 |
|
1789 | const translatedKey = translated.join('.');
|
1790 | if (fields instanceof Map)
|
1791 | fields.set(translatedKey, value);
|
1792 | else
|
1793 | fields[translatedKey] = value;
|
1794 |
|
1795 | if (translatedKey !== key) {
|
1796 | // We'll be using the translated key instead
|
1797 | if (fields instanceof Map) {
|
1798 | // Delete from map
|
1799 | fields.delete(key);
|
1800 | } else {
|
1801 | // Delete from object
|
1802 | delete fields[key]; // We'll be using the translated key instead
|
1803 | }
|
1804 | }
|
1805 | return fields;
|
1806 | };
|
1807 |
|
1808 | if (typeof fields === 'object') {
|
1809 | // Fields is an object (query conditions or document fields)
|
1810 | if (fields instanceof Map) {
|
1811 | // A Map was supplied
|
1812 | for (const field of new Map(fields)) {
|
1813 | fields = translate(field[0], field[1]);
|
1814 | }
|
1815 | } else {
|
1816 | // Infer a regular object was supplied
|
1817 | for (const key of Object.keys(fields)) {
|
1818 | fields = translate(key, fields[key]);
|
1819 | if (key[0] === '$') {
|
1820 | if (Array.isArray(fields[key])) {
|
1821 | for (const i in fields[key]) {
|
1822 | // Recursively translate nested queries
|
1823 | fields[key][i] = this.translateAliases(fields[key][i]);
|
1824 | }
|
1825 | } else {
|
1826 | this.translateAliases(fields[key]);
|
1827 | }
|
1828 | }
|
1829 | }
|
1830 | }
|
1831 |
|
1832 | return fields;
|
1833 | } else {
|
1834 | // Don't know typeof fields
|
1835 | return fields;
|
1836 | }
|
1837 | };
|
1838 |
|
1839 | /**
|
1840 | * Deletes the first document that matches `conditions` from the collection.
|
1841 | * It returns an object with the property `deletedCount` indicating how many documents were deleted.
|
1842 | * Behaves like `remove()`, but deletes at most one document regardless of the
|
1843 | * `single` option.
|
1844 | *
|
1845 | * #### Example:
|
1846 | *
|
1847 | * await Character.deleteOne({ name: 'Eddard Stark' }); // returns {deletedCount: 1}
|
1848 | *
|
1849 | * #### Note:
|
1850 | *
|
1851 | * This function triggers `deleteOne` query hooks. Read the
|
1852 | * [middleware docs](https://mongoosejs.com/docs/middleware.html#naming) to learn more.
|
1853 | *
|
1854 | * @param {Object} conditions
|
1855 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
1856 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
1857 | * @return {Query}
|
1858 | * @api public
|
1859 | */
|
1860 |
|
1861 | Model.deleteOne = function deleteOne(conditions, options) {
|
1862 | _checkContext(this, 'deleteOne');
|
1863 |
|
1864 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
|
1865 | throw new MongooseError('Model.prototype.deleteOne() no longer accepts a callback');
|
1866 | }
|
1867 |
|
1868 | const mq = new this.Query({}, {}, this, this.$__collection);
|
1869 | mq.setOptions(options);
|
1870 |
|
1871 | return mq.deleteOne(conditions);
|
1872 | };
|
1873 |
|
1874 | /**
|
1875 | * Deletes all of the documents that match `conditions` from the collection.
|
1876 | * It returns an object with the property `deletedCount` containing the number of documents deleted.
|
1877 | * Behaves like `remove()`, but deletes all documents that match `conditions`
|
1878 | * regardless of the `single` option.
|
1879 | *
|
1880 | * #### Example:
|
1881 | *
|
1882 | * await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }); // returns {deletedCount: x} where x is the number of documents deleted.
|
1883 | *
|
1884 | * #### Note:
|
1885 | *
|
1886 | * This function triggers `deleteMany` query hooks. Read the
|
1887 | * [middleware docs](https://mongoosejs.com/docs/middleware.html#naming) to learn more.
|
1888 | *
|
1889 | * @param {Object} conditions
|
1890 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
1891 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
1892 | * @return {Query}
|
1893 | * @api public
|
1894 | */
|
1895 |
|
1896 | Model.deleteMany = function deleteMany(conditions, options) {
|
1897 | _checkContext(this, 'deleteMany');
|
1898 |
|
1899 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
|
1900 | throw new MongooseError('Model.deleteMany() no longer accepts a callback');
|
1901 | }
|
1902 |
|
1903 | const mq = new this.Query({}, {}, this, this.$__collection);
|
1904 | mq.setOptions(options);
|
1905 |
|
1906 | return mq.deleteMany(conditions);
|
1907 | };
|
1908 |
|
1909 | /**
|
1910 | * Finds documents.
|
1911 | *
|
1912 | * Mongoose casts the `filter` to match the model's schema before the command is sent.
|
1913 | * See our [query casting tutorial](https://mongoosejs.com/docs/tutorials/query_casting.html) for
|
1914 | * more information on how Mongoose casts `filter`.
|
1915 | *
|
1916 | * #### Example:
|
1917 | *
|
1918 | * // find all documents
|
1919 | * await MyModel.find({});
|
1920 | *
|
1921 | * // find all documents named john and at least 18
|
1922 | * await MyModel.find({ name: 'john', age: { $gte: 18 } }).exec();
|
1923 | *
|
1924 | * // executes, name LIKE john and only selecting the "name" and "friends" fields
|
1925 | * await MyModel.find({ name: /john/i }, 'name friends').exec();
|
1926 | *
|
1927 | * // passing options
|
1928 | * await MyModel.find({ name: /john/i }, null, { skip: 10 }).exec();
|
1929 | *
|
1930 | * @param {Object|ObjectId} filter
|
1931 | * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
|
1932 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
1933 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
1934 | * @return {Query}
|
1935 | * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
|
1936 | * @see query casting https://mongoosejs.com/docs/tutorials/query_casting.html
|
1937 | * @api public
|
1938 | */
|
1939 |
|
1940 | Model.find = function find(conditions, projection, options) {
|
1941 | _checkContext(this, 'find');
|
1942 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
|
1943 | throw new MongooseError('Model.find() no longer accepts a callback');
|
1944 | }
|
1945 |
|
1946 | const mq = new this.Query({}, {}, this, this.$__collection);
|
1947 | mq.select(projection);
|
1948 | mq.setOptions(options);
|
1949 |
|
1950 | return mq.find(conditions);
|
1951 | };
|
1952 |
|
1953 | /**
|
1954 | * Finds a single document by its _id field. `findById(id)` is almost*
|
1955 | * equivalent to `findOne({ _id: id })`. If you want to query by a document's
|
1956 | * `_id`, use `findById()` instead of `findOne()`.
|
1957 | *
|
1958 | * The `id` is cast based on the Schema before sending the command.
|
1959 | *
|
1960 | * This function triggers the following middleware.
|
1961 | *
|
1962 | * - `findOne()`
|
1963 | *
|
1964 | * \* Except for how it treats `undefined`. If you use `findOne()`, you'll see
|
1965 | * that `findOne(undefined)` and `findOne({ _id: undefined })` are equivalent
|
1966 | * to `findOne({})` and return arbitrary documents. However, mongoose
|
1967 | * translates `findById(undefined)` into `findOne({ _id: null })`.
|
1968 | *
|
1969 | * #### Example:
|
1970 | *
|
1971 | * // Find the adventure with the given `id`, or `null` if not found
|
1972 | * await Adventure.findById(id).exec();
|
1973 | *
|
1974 | * // select only the adventures name and length
|
1975 | * await Adventure.findById(id, 'name length').exec();
|
1976 | *
|
1977 | * @param {Any} id value of `_id` to query by
|
1978 | * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
|
1979 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
1980 | * @return {Query}
|
1981 | * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
|
1982 | * @see lean queries https://mongoosejs.com/docs/tutorials/lean.html
|
1983 | * @see findById in Mongoose https://masteringjs.io/tutorials/mongoose/find-by-id
|
1984 | * @api public
|
1985 | */
|
1986 |
|
1987 | Model.findById = function findById(id, projection, options) {
|
1988 | _checkContext(this, 'findById');
|
1989 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
|
1990 | throw new MongooseError('Model.findById() no longer accepts a callback');
|
1991 | }
|
1992 |
|
1993 | if (typeof id === 'undefined') {
|
1994 | id = null;
|
1995 | }
|
1996 |
|
1997 | return this.findOne({ _id: id }, projection, options);
|
1998 | };
|
1999 |
|
2000 | /**
|
2001 | * Finds one document.
|
2002 | *
|
2003 | * The `conditions` are cast to their respective SchemaTypes before the command is sent.
|
2004 | *
|
2005 | * *Note:* `conditions` is optional, and if `conditions` is null or undefined,
|
2006 | * mongoose will send an empty `findOne` command to MongoDB, which will return
|
2007 | * an arbitrary document. If you're querying by `_id`, use `findById()` instead.
|
2008 | *
|
2009 | * #### Example:
|
2010 | *
|
2011 | * // Find one adventure whose `country` is 'Croatia', otherwise `null`
|
2012 | * await Adventure.findOne({ country: 'Croatia' }).exec();
|
2013 | *
|
2014 | * // Model.findOne() no longer accepts a callback
|
2015 | *
|
2016 | * // Select only the adventures name and length
|
2017 | * await Adventure.findOne({ country: 'Croatia' }, 'name length').exec();
|
2018 | *
|
2019 | * @param {Object} [conditions]
|
2020 | * @param {Object|String|String[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
|
2021 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
2022 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
2023 | * @return {Query}
|
2024 | * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
|
2025 | * @see lean queries https://mongoosejs.com/docs/tutorials/lean.html
|
2026 | * @api public
|
2027 | */
|
2028 |
|
2029 | Model.findOne = function findOne(conditions, projection, options) {
|
2030 | _checkContext(this, 'findOne');
|
2031 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
|
2032 | throw new MongooseError('Model.findOne() no longer accepts a callback');
|
2033 | }
|
2034 |
|
2035 | const mq = new this.Query({}, {}, this, this.$__collection);
|
2036 | mq.select(projection);
|
2037 | mq.setOptions(options);
|
2038 |
|
2039 | return mq.findOne(conditions);
|
2040 | };
|
2041 |
|
2042 | /**
|
2043 | * Estimates the number of documents in the MongoDB collection. Faster than
|
2044 | * using `countDocuments()` for large collections because
|
2045 | * `estimatedDocumentCount()` uses collection metadata rather than scanning
|
2046 | * the entire collection.
|
2047 | *
|
2048 | * #### Example:
|
2049 | *
|
2050 | * const numAdventures = await Adventure.estimatedDocumentCount();
|
2051 | *
|
2052 | * @param {Object} [options]
|
2053 | * @return {Query}
|
2054 | * @api public
|
2055 | */
|
2056 |
|
2057 | Model.estimatedDocumentCount = function estimatedDocumentCount(options) {
|
2058 | _checkContext(this, 'estimatedDocumentCount');
|
2059 |
|
2060 | const mq = new this.Query({}, {}, this, this.$__collection);
|
2061 |
|
2062 | return mq.estimatedDocumentCount(options);
|
2063 | };
|
2064 |
|
2065 | /**
|
2066 | * Counts number of documents matching `filter` in a database collection.
|
2067 | *
|
2068 | * #### Example:
|
2069 | *
|
2070 | * Adventure.countDocuments({ type: 'jungle' }, function (err, count) {
|
2071 | * console.log('there are %d jungle adventures', count);
|
2072 | * });
|
2073 | *
|
2074 | * If you want to count all documents in a large collection,
|
2075 | * use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount())
|
2076 | * instead. If you call `countDocuments({})`, MongoDB will always execute
|
2077 | * a full collection scan and **not** use any indexes.
|
2078 | *
|
2079 | * The `countDocuments()` function is similar to `count()`, but there are a
|
2080 | * [few operators that `countDocuments()` does not support](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#countDocuments).
|
2081 | * Below are the operators that `count()` supports but `countDocuments()` does not,
|
2082 | * and the suggested replacement:
|
2083 | *
|
2084 | * - `$where`: [`$expr`](https://www.mongodb.com/docs/manual/reference/operator/query/expr/)
|
2085 | * - `$near`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$center`](https://www.mongodb.com/docs/manual/reference/operator/query/center/#op._S_center)
|
2086 | * - `$nearSphere`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$centerSphere`](https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/#op._S_centerSphere)
|
2087 | *
|
2088 | * @param {Object} filter
|
2089 | * @return {Query}
|
2090 | * @api public
|
2091 | */
|
2092 |
|
2093 | Model.countDocuments = function countDocuments(conditions, options) {
|
2094 | _checkContext(this, 'countDocuments');
|
2095 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
|
2096 | throw new MongooseError('Model.countDocuments() no longer accepts a callback');
|
2097 | }
|
2098 |
|
2099 | const mq = new this.Query({}, {}, this, this.$__collection);
|
2100 | if (options != null) {
|
2101 | mq.setOptions(options);
|
2102 | }
|
2103 |
|
2104 | return mq.countDocuments(conditions);
|
2105 | };
|
2106 |
|
2107 |
|
2108 | /**
|
2109 | * Creates a Query for a `distinct` operation.
|
2110 | *
|
2111 | * #### Example:
|
2112 | *
|
2113 | * const query = Link.distinct('url');
|
2114 | * query.exec();
|
2115 | *
|
2116 | * @param {String} field
|
2117 | * @param {Object} [conditions] optional
|
2118 | * @return {Query}
|
2119 | * @api public
|
2120 | */
|
2121 |
|
2122 | Model.distinct = function distinct(field, conditions) {
|
2123 | _checkContext(this, 'distinct');
|
2124 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
|
2125 | throw new MongooseError('Model.distinct() no longer accepts a callback');
|
2126 | }
|
2127 |
|
2128 | const mq = new this.Query({}, {}, this, this.$__collection);
|
2129 |
|
2130 | return mq.distinct(field, conditions);
|
2131 | };
|
2132 |
|
2133 | /**
|
2134 | * Creates a Query, applies the passed conditions, and returns the Query.
|
2135 | *
|
2136 | * For example, instead of writing:
|
2137 | *
|
2138 | * User.find({ age: { $gte: 21, $lte: 65 } });
|
2139 | *
|
2140 | * we can instead write:
|
2141 | *
|
2142 | * User.where('age').gte(21).lte(65).exec();
|
2143 | *
|
2144 | * Since the Query class also supports `where` you can continue chaining
|
2145 | *
|
2146 | * User
|
2147 | * .where('age').gte(21).lte(65)
|
2148 | * .where('name', /^b/i)
|
2149 | * ... etc
|
2150 | *
|
2151 | * @param {String} path
|
2152 | * @param {Object} [val] optional value
|
2153 | * @return {Query}
|
2154 | * @api public
|
2155 | */
|
2156 |
|
2157 | Model.where = function where(path, val) {
|
2158 | _checkContext(this, 'where');
|
2159 |
|
2160 | void val; // eslint
|
2161 | const mq = new this.Query({}, {}, this, this.$__collection).find({});
|
2162 | return mq.where.apply(mq, arguments);
|
2163 | };
|
2164 |
|
2165 | /**
|
2166 | * Creates a `Query` and specifies a `$where` condition.
|
2167 | *
|
2168 | * Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via `find({ $where: javascript })`, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.
|
2169 | *
|
2170 | * Blog.$where('this.username.indexOf("val") !== -1').exec(function (err, docs) {});
|
2171 | *
|
2172 | * @param {String|Function} argument is a javascript string or anonymous function
|
2173 | * @method $where
|
2174 | * @memberOf Model
|
2175 | * @return {Query}
|
2176 | * @see Query.$where https://mongoosejs.com/docs/api/query.html#Query.prototype.$where
|
2177 | * @api public
|
2178 | */
|
2179 |
|
2180 | Model.$where = function $where() {
|
2181 | _checkContext(this, '$where');
|
2182 |
|
2183 | const mq = new this.Query({}, {}, this, this.$__collection).find({});
|
2184 | return mq.$where.apply(mq, arguments);
|
2185 | };
|
2186 |
|
2187 | /**
|
2188 | * Issues a mongodb findOneAndUpdate command.
|
2189 | *
|
2190 | * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes if `callback` is passed else a Query object is returned.
|
2191 | *
|
2192 | * #### Example:
|
2193 | *
|
2194 | * A.findOneAndUpdate(conditions, update, options) // returns Query
|
2195 | * A.findOneAndUpdate(conditions, update) // returns Query
|
2196 | * A.findOneAndUpdate() // returns Query
|
2197 | *
|
2198 | * #### Note:
|
2199 | *
|
2200 | * All top level update keys which are not `atomic` operation names are treated as set operations:
|
2201 | *
|
2202 | * #### Example:
|
2203 | *
|
2204 | * const query = { name: 'borne' };
|
2205 | * Model.findOneAndUpdate(query, { name: 'jason bourne' }, options)
|
2206 | *
|
2207 | * // is sent as
|
2208 | * Model.findOneAndUpdate(query, { $set: { name: 'jason bourne' }}, options)
|
2209 | *
|
2210 | * #### Note:
|
2211 | *
|
2212 | * `findOneAndX` and `findByIdAndX` functions support limited validation that
|
2213 | * you can enable by setting the `runValidators` option.
|
2214 | *
|
2215 | * If you need full-fledged validation, use the traditional approach of first
|
2216 | * retrieving the document.
|
2217 | *
|
2218 | * const doc = await Model.findById(id);
|
2219 | * doc.name = 'jason bourne';
|
2220 | * await doc.save();
|
2221 | *
|
2222 | * @param {Object} [conditions]
|
2223 | * @param {Object} [update]
|
2224 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
2225 | * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
|
2226 | * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
|
2227 | * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
|
2228 | * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
2229 | * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
|
2230 | * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
|
2231 | * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
|
2232 | * @param {Boolean} [options.new=false] if true, return the modified document rather than the original
|
2233 | * @param {Object|String} [options.fields] Field selection. Equivalent to `.select(fields).findOneAndUpdate()`
|
2234 | * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
|
2235 | * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
|
2236 | * @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema
|
2237 | * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created
|
2238 | * @param {Boolean} [options.includeResultMetadata] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html)
|
2239 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
2240 | * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
|
2241 | * @return {Query}
|
2242 | * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
|
2243 | * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
|
2244 | * @api public
|
2245 | */
|
2246 |
|
2247 | Model.findOneAndUpdate = function(conditions, update, options) {
|
2248 | _checkContext(this, 'findOneAndUpdate');
|
2249 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
|
2250 | throw new MongooseError('Model.findOneAndUpdate() no longer accepts a callback');
|
2251 | }
|
2252 |
|
2253 | if (arguments.length === 1) {
|
2254 | update = conditions;
|
2255 | conditions = null;
|
2256 | options = null;
|
2257 | }
|
2258 |
|
2259 | let fields;
|
2260 | if (options) {
|
2261 | fields = options.fields || options.projection;
|
2262 | }
|
2263 |
|
2264 | update = clone(update, {
|
2265 | depopulate: true,
|
2266 | _isNested: true
|
2267 | });
|
2268 |
|
2269 | decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey);
|
2270 |
|
2271 | const mq = new this.Query({}, {}, this, this.$__collection);
|
2272 | mq.select(fields);
|
2273 |
|
2274 | return mq.findOneAndUpdate(conditions, update, options);
|
2275 | };
|
2276 |
|
2277 | /**
|
2278 | * Issues a mongodb findOneAndUpdate command by a document's _id field.
|
2279 | * `findByIdAndUpdate(id, ...)` is equivalent to `findOneAndUpdate({ _id: id }, ...)`.
|
2280 | *
|
2281 | * Finds a matching document, updates it according to the `update` arg,
|
2282 | * passing any `options`, and returns the found document (if any).
|
2283 | *
|
2284 | * This function triggers the following middleware.
|
2285 | *
|
2286 | * - `findOneAndUpdate()`
|
2287 | *
|
2288 | * #### Example:
|
2289 | *
|
2290 | * A.findByIdAndUpdate(id, update, options) // returns Query
|
2291 | * A.findByIdAndUpdate(id, update) // returns Query
|
2292 | * A.findByIdAndUpdate() // returns Query
|
2293 | *
|
2294 | * #### Note:
|
2295 | *
|
2296 | * All top level update keys which are not `atomic` operation names are treated as set operations:
|
2297 | *
|
2298 | * #### Example:
|
2299 | *
|
2300 | * Model.findByIdAndUpdate(id, { name: 'jason bourne' }, options)
|
2301 | *
|
2302 | * // is sent as
|
2303 | * Model.findByIdAndUpdate(id, { $set: { name: 'jason bourne' }}, options)
|
2304 | *
|
2305 | * #### Note:
|
2306 | *
|
2307 | * `findOneAndX` and `findByIdAndX` functions support limited validation. You can
|
2308 | * enable validation by setting the `runValidators` option.
|
2309 | *
|
2310 | * If you need full-fledged validation, use the traditional approach of first
|
2311 | * retrieving the document.
|
2312 | *
|
2313 | * const doc = await Model.findById(id)
|
2314 | * doc.name = 'jason bourne';
|
2315 | * await doc.save();
|
2316 | *
|
2317 | * @param {Object|Number|String} id value of `_id` to query by
|
2318 | * @param {Object} [update]
|
2319 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
2320 | * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
|
2321 | * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
|
2322 | * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
|
2323 | * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
2324 | * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
|
2325 | * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
|
2326 | * @param {Boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema
|
2327 | * @param {Boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created
|
2328 | * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
|
2329 | * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
|
2330 | * @param {Boolean} [options.new=false] if true, return the modified document rather than the original
|
2331 | * @param {Object|String} [options.select] sets the document fields to return.
|
2332 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
2333 | * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
|
2334 | * @return {Query}
|
2335 | * @see Model.findOneAndUpdate https://mongoosejs.com/docs/api/model.html#Model.findOneAndUpdate()
|
2336 | * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
|
2337 | * @api public
|
2338 | */
|
2339 |
|
2340 | Model.findByIdAndUpdate = function(id, update, options) {
|
2341 | _checkContext(this, 'findByIdAndUpdate');
|
2342 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
|
2343 | throw new MongooseError('Model.findByIdAndUpdate() no longer accepts a callback');
|
2344 | }
|
2345 |
|
2346 | // if a model is passed in instead of an id
|
2347 | if (id instanceof Document) {
|
2348 | id = id._id;
|
2349 | }
|
2350 |
|
2351 | return this.findOneAndUpdate.call(this, { _id: id }, update, options);
|
2352 | };
|
2353 |
|
2354 | /**
|
2355 | * Issue a MongoDB `findOneAndDelete()` command.
|
2356 | *
|
2357 | * Finds a matching document, removes it, and returns the found document (if any).
|
2358 | *
|
2359 | * This function triggers the following middleware.
|
2360 | *
|
2361 | * - `findOneAndDelete()`
|
2362 | *
|
2363 | * #### Example:
|
2364 | *
|
2365 | * A.findOneAndDelete(conditions, options) // return Query
|
2366 | * A.findOneAndDelete(conditions) // returns Query
|
2367 | * A.findOneAndDelete() // returns Query
|
2368 | *
|
2369 | * `findOneAndX` and `findByIdAndX` functions support limited validation. You can
|
2370 | * enable validation by setting the `runValidators` option.
|
2371 | *
|
2372 | * If you need full-fledged validation, use the traditional approach of first
|
2373 | * retrieving the document.
|
2374 | *
|
2375 | * const doc = await Model.findById(id)
|
2376 | * doc.name = 'jason bourne';
|
2377 | * await doc.save();
|
2378 | *
|
2379 | * @param {Object} conditions
|
2380 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
2381 | * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
2382 | * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
|
2383 | * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
|
2384 | * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
|
2385 | * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
|
2386 | * @param {Object|String} [options.select] sets the document fields to return.
|
2387 | * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
|
2388 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
2389 | * @return {Query}
|
2390 | * @api public
|
2391 | */
|
2392 |
|
2393 | Model.findOneAndDelete = function(conditions, options) {
|
2394 | _checkContext(this, 'findOneAndDelete');
|
2395 |
|
2396 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
|
2397 | throw new MongooseError('Model.findOneAndDelete() no longer accepts a callback');
|
2398 | }
|
2399 |
|
2400 | let fields;
|
2401 | if (options) {
|
2402 | fields = options.select;
|
2403 | options.select = undefined;
|
2404 | }
|
2405 |
|
2406 | const mq = new this.Query({}, {}, this, this.$__collection);
|
2407 | mq.select(fields);
|
2408 |
|
2409 | return mq.findOneAndDelete(conditions, options);
|
2410 | };
|
2411 |
|
2412 | /**
|
2413 | * Issue a MongoDB `findOneAndDelete()` command by a document's _id field.
|
2414 | * In other words, `findByIdAndDelete(id)` is a shorthand for
|
2415 | * `findOneAndDelete({ _id: id })`.
|
2416 | *
|
2417 | * This function triggers the following middleware.
|
2418 | *
|
2419 | * - `findOneAndDelete()`
|
2420 | *
|
2421 | * @param {Object|Number|String} id value of `_id` to query by
|
2422 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
2423 | * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
2424 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
2425 | * @return {Query}
|
2426 | * @see Model.findOneAndDelete https://mongoosejs.com/docs/api/model.html#Model.findOneAndDelete()
|
2427 | * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
|
2428 | */
|
2429 |
|
2430 | Model.findByIdAndDelete = function(id, options) {
|
2431 | _checkContext(this, 'findByIdAndDelete');
|
2432 |
|
2433 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
|
2434 | throw new MongooseError('Model.findByIdAndDelete() no longer accepts a callback');
|
2435 | }
|
2436 |
|
2437 | return this.findOneAndDelete({ _id: id }, options);
|
2438 | };
|
2439 |
|
2440 | /**
|
2441 | * Issue a MongoDB `findOneAndReplace()` command.
|
2442 | *
|
2443 | * Finds a matching document, replaces it with the provided doc, and returns the document.
|
2444 | *
|
2445 | * This function triggers the following query middleware.
|
2446 | *
|
2447 | * - `findOneAndReplace()`
|
2448 | *
|
2449 | * #### Example:
|
2450 | *
|
2451 | * A.findOneAndReplace(filter, replacement, options) // return Query
|
2452 | * A.findOneAndReplace(filter, replacement) // returns Query
|
2453 | * A.findOneAndReplace() // returns Query
|
2454 | *
|
2455 | * @param {Object} filter Replace the first document that matches this filter
|
2456 | * @param {Object} [replacement] Replace with this document
|
2457 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
2458 | * @param {String} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
|
2459 | * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
|
2460 | * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
|
2461 | * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
2462 | * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
|
2463 | * @param {Object|String|String[]} [options.projection=null] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
|
2464 | * @param {Object|String} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
|
2465 | * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
|
2466 | * @param {Object|String} [options.select] sets the document fields to return.
|
2467 | * @param {Number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
|
2468 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
2469 | * @return {Query}
|
2470 | * @api public
|
2471 | */
|
2472 |
|
2473 | Model.findOneAndReplace = function(filter, replacement, options) {
|
2474 | _checkContext(this, 'findOneAndReplace');
|
2475 |
|
2476 | if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
|
2477 | throw new MongooseError('Model.findOneAndReplace() no longer accepts a callback');
|
2478 | }
|
2479 |
|
2480 | let fields;
|
2481 | if (options) {
|
2482 | fields = options.select;
|
2483 | options.select = undefined;
|
2484 | }
|
2485 |
|
2486 | const mq = new this.Query({}, {}, this, this.$__collection);
|
2487 | mq.select(fields);
|
2488 |
|
2489 | return mq.findOneAndReplace(filter, replacement, options);
|
2490 | };
|
2491 |
|
2492 | /**
|
2493 | * Shortcut for saving one or more documents to the database.
|
2494 | * `MyModel.create(docs)` does `new MyModel(doc).save()` for every doc in
|
2495 | * docs.
|
2496 | *
|
2497 | * This function triggers the following middleware.
|
2498 | *
|
2499 | * - `save()`
|
2500 | *
|
2501 | * #### Example:
|
2502 | *
|
2503 | * // Insert one new `Character` document
|
2504 | * await Character.create({ name: 'Jean-Luc Picard' });
|
2505 | *
|
2506 | * // Insert multiple new `Character` documents
|
2507 | * await Character.create([{ name: 'Will Riker' }, { name: 'Geordi LaForge' }]);
|
2508 | *
|
2509 | * // Create a new character within a transaction. Note that you **must**
|
2510 | * // pass an array as the first parameter to `create()` if you want to
|
2511 | * // specify options.
|
2512 | * await Character.create([{ name: 'Jean-Luc Picard' }], { session });
|
2513 | *
|
2514 | * @param {Array|Object} docs Documents to insert, as a spread or array
|
2515 | * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread. See [Model.save](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) for available options.
|
2516 | * @param {Boolean} [options.ordered] saves the docs in series rather than parallel.
|
2517 | * @param {Boolean} [options.aggregateErrors] Aggregate Errors instead of throwing the first one that occurs. Default: false
|
2518 | * @return {Promise}
|
2519 | * @api public
|
2520 | */
|
2521 |
|
2522 | Model.create = async function create(doc, options) {
|
2523 | if (typeof options === 'function' ||
|
2524 | typeof arguments[2] === 'function') {
|
2525 | throw new MongooseError('Model.create() no longer accepts a callback');
|
2526 | }
|
2527 |
|
2528 | _checkContext(this, 'create');
|
2529 |
|
2530 | let args;
|
2531 | const discriminatorKey = this.schema.options.discriminatorKey;
|
2532 |
|
2533 | if (Array.isArray(doc)) {
|
2534 | args = doc;
|
2535 | options = options != null && typeof options === 'object' ? options : {};
|
2536 | } else {
|
2537 | const last = arguments[arguments.length - 1];
|
2538 | options = {};
|
2539 | const hasCallback = typeof last === 'function' ||
|
2540 | typeof options === 'function' ||
|
2541 | typeof arguments[2] === 'function';
|
2542 | if (hasCallback) {
|
2543 | throw new MongooseError('Model.create() no longer accepts a callback');
|
2544 | } else {
|
2545 | args = [...arguments];
|
2546 | // For backwards compatibility with 6.x, because of gh-5061 Mongoose 6.x and
|
2547 | // older would treat a falsy last arg as a callback. We don't want to throw
|
2548 | // an error here, because it would look strange if `Test.create({}, void 0)`
|
2549 | // threw a callback error. But we also don't want to create an unnecessary document.
|
2550 | if (args.length > 1 && !last) {
|
2551 | args.pop();
|
2552 | }
|
2553 | }
|
2554 |
|
2555 | if (args.length === 2 &&
|
2556 | args[0] != null &&
|
2557 | args[1] != null &&
|
2558 | args[0].session == null &&
|
2559 | last &&
|
2560 | getConstructorName(last.session) === 'ClientSession' &&
|
2561 | !this.schema.path('session')) {
|
2562 | // Probably means the user is running into the common mistake of trying
|
2563 | // to use a spread to specify options, see gh-7535
|
2564 | utils.warn('WARNING: to pass a `session` to `Model.create()` in ' +
|
2565 | 'Mongoose, you **must** pass an array as the first argument. See: ' +
|
2566 | 'https://mongoosejs.com/docs/api/model.html#Model.create()');
|
2567 | }
|
2568 | }
|
2569 |
|
2570 | if (args.length === 0) {
|
2571 | return Array.isArray(doc) ? [] : null;
|
2572 | }
|
2573 | let res = [];
|
2574 | const immediateError = typeof options.aggregateErrors === 'boolean' ? !options.aggregateErrors : true;
|
2575 |
|
2576 | delete options.aggregateErrors; // dont pass on the option to "$save"
|
2577 |
|
2578 | if (options.ordered) {
|
2579 | for (let i = 0; i < args.length; i++) {
|
2580 | try {
|
2581 | const doc = args[i];
|
2582 | const Model = this.discriminators && doc[discriminatorKey] != null ?
|
2583 | this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
|
2584 | this;
|
2585 | if (Model == null) {
|
2586 | throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
|
2587 | `found for model "${this.modelName}"`);
|
2588 | }
|
2589 | let toSave = doc;
|
2590 | if (!(toSave instanceof Model)) {
|
2591 | toSave = new Model(toSave);
|
2592 | }
|
2593 |
|
2594 | await toSave.$save(options);
|
2595 | res.push(toSave);
|
2596 | } catch (err) {
|
2597 | if (!immediateError) {
|
2598 | res.push(err);
|
2599 | } else {
|
2600 | throw err;
|
2601 | }
|
2602 | }
|
2603 | }
|
2604 | return res;
|
2605 | } else if (!immediateError) {
|
2606 | res = await Promise.allSettled(args.map(async doc => {
|
2607 | const Model = this.discriminators && doc[discriminatorKey] != null ?
|
2608 | this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
|
2609 | this;
|
2610 | if (Model == null) {
|
2611 | throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
|
2612 | `found for model "${this.modelName}"`);
|
2613 | }
|
2614 | let toSave = doc;
|
2615 |
|
2616 | if (!(toSave instanceof Model)) {
|
2617 | toSave = new Model(toSave);
|
2618 | }
|
2619 |
|
2620 | await toSave.$save(options);
|
2621 |
|
2622 | return toSave;
|
2623 | }));
|
2624 | res = res.map(result => result.status === 'fulfilled' ? result.value : result.reason);
|
2625 | } else {
|
2626 | let firstError = null;
|
2627 | res = await Promise.all(args.map(async doc => {
|
2628 | const Model = this.discriminators && doc[discriminatorKey] != null ?
|
2629 | this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
|
2630 | this;
|
2631 | if (Model == null) {
|
2632 | throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
|
2633 | `found for model "${this.modelName}"`);
|
2634 | }
|
2635 | try {
|
2636 | let toSave = doc;
|
2637 |
|
2638 | if (!(toSave instanceof Model)) {
|
2639 | toSave = new Model(toSave);
|
2640 | }
|
2641 |
|
2642 | await toSave.$save(options);
|
2643 |
|
2644 | return toSave;
|
2645 | } catch (err) {
|
2646 | if (!firstError) {
|
2647 | firstError = err;
|
2648 | }
|
2649 | }
|
2650 | }));
|
2651 | if (firstError) {
|
2652 | throw firstError;
|
2653 | }
|
2654 | }
|
2655 |
|
2656 |
|
2657 | if (!Array.isArray(doc) && args.length === 1) {
|
2658 | return res[0];
|
2659 | }
|
2660 |
|
2661 | return res;
|
2662 | };
|
2663 |
|
2664 | /**
|
2665 | * _Requires a replica set running MongoDB >= 3.6.0._ Watches the
|
2666 | * underlying collection for changes using
|
2667 | * [MongoDB change streams](https://www.mongodb.com/docs/manual/changeStreams/).
|
2668 | *
|
2669 | * This function does **not** trigger any middleware. In particular, it
|
2670 | * does **not** trigger aggregate middleware.
|
2671 | *
|
2672 | * The ChangeStream object is an event emitter that emits the following events:
|
2673 | *
|
2674 | * - 'change': A change occurred, see below example
|
2675 | * - 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow [this GitHub issue](https://github.com/Automattic/mongoose/issues/6799) for updates.
|
2676 | * - 'end': Emitted if the underlying stream is closed
|
2677 | * - 'close': Emitted if the underlying stream is closed
|
2678 | *
|
2679 | * #### Example:
|
2680 | *
|
2681 | * const doc = await Person.create({ name: 'Ned Stark' });
|
2682 | * const changeStream = Person.watch().on('change', change => console.log(change));
|
2683 | * // Will print from the above `console.log()`:
|
2684 | * // { _id: { _data: ... },
|
2685 | * // operationType: 'delete',
|
2686 | * // ns: { db: 'mydb', coll: 'Person' },
|
2687 | * // documentKey: { _id: 5a51b125c5500f5aa094c7bd } }
|
2688 | * await doc.remove();
|
2689 | *
|
2690 | * @param {Array} [pipeline]
|
2691 | * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#watch)
|
2692 | * @param {Boolean} [options.hydrate=false] if true and `fullDocument: 'updateLookup'` is set, Mongoose will automatically hydrate `fullDocument` into a fully fledged Mongoose document
|
2693 | * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
|
2694 | * @api public
|
2695 | */
|
2696 |
|
2697 | Model.watch = function(pipeline, options) {
|
2698 | _checkContext(this, 'watch');
|
2699 |
|
2700 | const changeStreamThunk = cb => {
|
2701 | pipeline = pipeline || [];
|
2702 | prepareDiscriminatorPipeline(pipeline, this.schema, 'fullDocument');
|
2703 | if (this.$__collection.buffer) {
|
2704 | this.$__collection.addQueue(() => {
|
2705 | if (this.closed) {
|
2706 | return;
|
2707 | }
|
2708 | const driverChangeStream = this.$__collection.watch(pipeline, options);
|
2709 | cb(null, driverChangeStream);
|
2710 | });
|
2711 | } else {
|
2712 | const driverChangeStream = this.$__collection.watch(pipeline, options);
|
2713 | cb(null, driverChangeStream);
|
2714 | }
|
2715 | };
|
2716 |
|
2717 | options = options || {};
|
2718 | options.model = this;
|
2719 |
|
2720 | return new ChangeStream(changeStreamThunk, pipeline, options);
|
2721 | };
|
2722 |
|
2723 | /**
|
2724 | * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://www.mongodb.com/docs/manual/release-notes/3.6/#client-sessions)
|
2725 | * for benefits like causal consistency, [retryable writes](https://www.mongodb.com/docs/manual/core/retryable-writes/),
|
2726 | * and [transactions](https://thecodebarbarian.com/a-node-js-perspective-on-mongodb-4-transactions.html).
|
2727 | *
|
2728 | * Calling `MyModel.startSession()` is equivalent to calling `MyModel.db.startSession()`.
|
2729 | *
|
2730 | * This function does not trigger any middleware.
|
2731 | *
|
2732 | * #### Example:
|
2733 | *
|
2734 | * const session = await Person.startSession();
|
2735 | * let doc = await Person.findOne({ name: 'Ned Stark' }, null, { session });
|
2736 | * await doc.remove();
|
2737 | * // `doc` will always be null, even if reading from a replica set
|
2738 | * // secondary. Without causal consistency, it is possible to
|
2739 | * // get a doc back from the below query if the query reads from a
|
2740 | * // secondary that is experiencing replication lag.
|
2741 | * doc = await Person.findOne({ name: 'Ned Stark' }, null, { session, readPreference: 'secondary' });
|
2742 | *
|
2743 | * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/MongoClient.html#startSession)
|
2744 | * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
|
2745 | * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
|
2746 | * @api public
|
2747 | */
|
2748 |
|
2749 | Model.startSession = function() {
|
2750 | _checkContext(this, 'startSession');
|
2751 |
|
2752 | return this.db.startSession.apply(this.db, arguments);
|
2753 | };
|
2754 |
|
2755 | /**
|
2756 | * Shortcut for validating an array of documents and inserting them into
|
2757 | * MongoDB if they're all valid. This function is faster than `.create()`
|
2758 | * because it only sends one operation to the server, rather than one for each
|
2759 | * document.
|
2760 | *
|
2761 | * Mongoose always validates each document **before** sending `insertMany`
|
2762 | * to MongoDB. So if one document has a validation error, no documents will
|
2763 | * be saved, unless you set
|
2764 | * [the `ordered` option to false](https://www.mongodb.com/docs/manual/reference/method/db.collection.insertMany/#error-handling).
|
2765 | *
|
2766 | * This function does **not** trigger save middleware.
|
2767 | *
|
2768 | * This function triggers the following middleware.
|
2769 | *
|
2770 | * - `insertMany()`
|
2771 | *
|
2772 | * #### Example:
|
2773 | *
|
2774 | * const docs = await Movies.insertMany([
|
2775 | * { name: 'Star Wars' },
|
2776 | * { name: 'The Empire Strikes Back' }
|
2777 | * ]);
|
2778 | * docs[0].name; // 'Star Wars'
|
2779 | *
|
2780 | * // Return raw result from MongoDB
|
2781 | * const result = await Movies.insertMany([
|
2782 | * { name: 'Star Wars' },
|
2783 | * { name: 'The Empire Strikes Back' }
|
2784 | * ], { rawResult: true });
|
2785 | *
|
2786 | * @param {Array|Object|*} doc(s)
|
2787 | * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany)
|
2788 | * @param {Boolean} [options.ordered=true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
|
2789 | * @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` and `results` if this is an unordered `insertMany`.
|
2790 | * @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast, validate, or apply defaults to any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](https://mongoosejs.com/docs/api/model.html#Model.castObject()) and [`applyDefaults()`](https://mongoosejs.com/docs/api/model.html#Model.applyDefaults()).
|
2791 | * @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory.
|
2792 | * @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set.
|
2793 | * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully.
|
2794 | * @return {Promise} resolving to the raw result from the MongoDB driver if `options.rawResult` was `true`, or the documents that passed validation, otherwise
|
2795 | * @api public
|
2796 | */
|
2797 |
|
2798 | Model.insertMany = async function insertMany(arr, options) {
|
2799 | _checkContext(this, 'insertMany');
|
2800 | if (typeof options === 'function' ||
|
2801 | typeof arguments[2] === 'function') {
|
2802 | throw new MongooseError('Model.insertMany() no longer accepts a callback');
|
2803 | }
|
2804 |
|
2805 | return new Promise((resolve, reject) => {
|
2806 | this.$__insertMany(arr, options, (err, res) => {
|
2807 | if (err != null) {
|
2808 | return reject(err);
|
2809 | }
|
2810 | resolve(res);
|
2811 | });
|
2812 | });
|
2813 | };
|
2814 |
|
2815 | /**
|
2816 | * ignore
|
2817 | *
|
2818 | * @param {Array} arr
|
2819 | * @param {Object} options
|
2820 | * @param {Function} callback
|
2821 | * @api private
|
2822 | * @memberOf Model
|
2823 | * @method $__insertMany
|
2824 | * @static
|
2825 | */
|
2826 |
|
2827 | Model.$__insertMany = function(arr, options, callback) {
|
2828 | const _this = this;
|
2829 | if (typeof options === 'function') {
|
2830 | callback = options;
|
2831 | options = null;
|
2832 | }
|
2833 |
|
2834 | callback = callback || utils.noop;
|
2835 | options = options || {};
|
2836 | const limit = options.limit || 1000;
|
2837 | const rawResult = !!options.rawResult;
|
2838 | const ordered = typeof options.ordered === 'boolean' ? options.ordered : true;
|
2839 | const throwOnValidationError = typeof options.throwOnValidationError === 'boolean' ? options.throwOnValidationError : false;
|
2840 | const lean = !!options.lean;
|
2841 |
|
2842 | const asyncLocalStorage = this.db.base.transactionAsyncLocalStorage?.getStore();
|
2843 | if ((!options || !options.hasOwnProperty('session')) && asyncLocalStorage?.session != null) {
|
2844 | options = { ...options, session: asyncLocalStorage.session };
|
2845 | }
|
2846 |
|
2847 | if (!Array.isArray(arr)) {
|
2848 | arr = [arr];
|
2849 | }
|
2850 |
|
2851 | const validationErrors = [];
|
2852 | const validationErrorsToOriginalOrder = new Map();
|
2853 | const results = ordered ? null : new Array(arr.length);
|
2854 | const toExecute = arr.map((doc, index) =>
|
2855 | callback => {
|
2856 | // If option `lean` is set to true bypass validation and hydration
|
2857 | if (lean) {
|
2858 | // we have to execute callback at the nextTick to be compatible
|
2859 | // with parallelLimit, as `results` variable has TDZ issue if we
|
2860 | // execute the callback synchronously
|
2861 | return immediate(() => callback(null, doc));
|
2862 | }
|
2863 | let createdNewDoc = false;
|
2864 | if (!(doc instanceof _this)) {
|
2865 | if (doc != null && typeof doc !== 'object') {
|
2866 | return callback(new ObjectParameterError(doc, 'arr.' + index, 'insertMany'));
|
2867 | }
|
2868 | try {
|
2869 | doc = new _this(doc);
|
2870 | createdNewDoc = true;
|
2871 | } catch (err) {
|
2872 | return callback(err);
|
2873 | }
|
2874 | }
|
2875 |
|
2876 | if (options.session != null) {
|
2877 | doc.$session(options.session);
|
2878 | }
|
2879 | // If option `lean` is set to true bypass validation
|
2880 | if (lean) {
|
2881 | // we have to execute callback at the nextTick to be compatible
|
2882 | // with parallelLimit, as `results` variable has TDZ issue if we
|
2883 | // execute the callback synchronously
|
2884 | return immediate(() => callback(null, doc));
|
2885 | }
|
2886 | doc.$validate(createdNewDoc ? { _skipParallelValidateCheck: true } : null).then(
|
2887 | () => { callback(null, doc); },
|
2888 | error => {
|
2889 | if (ordered === false) {
|
2890 | validationErrors.push(error);
|
2891 | validationErrorsToOriginalOrder.set(error, index);
|
2892 | results[index] = error;
|
2893 | return callback(null, null);
|
2894 | }
|
2895 | callback(error);
|
2896 | }
|
2897 | );
|
2898 | });
|
2899 |
|
2900 | parallelLimit(toExecute, limit, function(error, docs) {
|
2901 | if (error) {
|
2902 | callback(error, null);
|
2903 | return;
|
2904 | }
|
2905 |
|
2906 | const originalDocIndex = new Map();
|
2907 | const validDocIndexToOriginalIndex = new Map();
|
2908 | for (let i = 0; i < docs.length; ++i) {
|
2909 | originalDocIndex.set(docs[i], i);
|
2910 | }
|
2911 |
|
2912 | // We filter all failed pre-validations by removing nulls
|
2913 | const docAttributes = docs.filter(function(doc) {
|
2914 | return doc != null;
|
2915 | });
|
2916 | for (let i = 0; i < docAttributes.length; ++i) {
|
2917 | validDocIndexToOriginalIndex.set(i, originalDocIndex.get(docAttributes[i]));
|
2918 | }
|
2919 |
|
2920 | // Make sure validation errors are in the same order as the
|
2921 | // original documents, so if both doc1 and doc2 both fail validation,
|
2922 | // `Model.insertMany([doc1, doc2])` will always have doc1's validation
|
2923 | // error before doc2's. Re: gh-12791.
|
2924 | if (validationErrors.length > 0) {
|
2925 | validationErrors.sort((err1, err2) => {
|
2926 | return validationErrorsToOriginalOrder.get(err1) - validationErrorsToOriginalOrder.get(err2);
|
2927 | });
|
2928 | }
|
2929 |
|
2930 | // Quickly escape while there aren't any valid docAttributes
|
2931 | if (docAttributes.length === 0) {
|
2932 | if (throwOnValidationError) {
|
2933 | return callback(new MongooseBulkWriteError(
|
2934 | validationErrors,
|
2935 | results,
|
2936 | null,
|
2937 | 'insertMany'
|
2938 | ));
|
2939 | }
|
2940 | if (rawResult) {
|
2941 | const res = {
|
2942 | acknowledged: true,
|
2943 | insertedCount: 0,
|
2944 | insertedIds: {},
|
2945 | mongoose: {
|
2946 | validationErrors: validationErrors
|
2947 | }
|
2948 | };
|
2949 | return callback(null, res);
|
2950 | }
|
2951 | callback(null, []);
|
2952 | return;
|
2953 | }
|
2954 | const docObjects = lean ? docAttributes : docAttributes.map(function(doc) {
|
2955 | if (doc.$__schema.options.versionKey) {
|
2956 | doc[doc.$__schema.options.versionKey] = 0;
|
2957 | }
|
2958 | const shouldSetTimestamps = (!options || options.timestamps !== false) && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
|
2959 | if (shouldSetTimestamps) {
|
2960 | doc.initializeTimestamps();
|
2961 | }
|
2962 | if (doc.$__hasOnlyPrimitiveValues()) {
|
2963 | return doc.$__toObjectShallow();
|
2964 | }
|
2965 | return doc.toObject(internalToObjectOptions);
|
2966 | });
|
2967 |
|
2968 | _this.$__collection.insertMany(docObjects, options).then(
|
2969 | res => {
|
2970 | if (!lean) {
|
2971 | for (const attribute of docAttributes) {
|
2972 | attribute.$__reset();
|
2973 | _setIsNew(attribute, false);
|
2974 | }
|
2975 | }
|
2976 |
|
2977 | if (ordered === false && throwOnValidationError && validationErrors.length > 0) {
|
2978 | for (let i = 0; i < results.length; ++i) {
|
2979 | if (results[i] === void 0) {
|
2980 | results[i] = docs[i];
|
2981 | }
|
2982 | }
|
2983 | return callback(new MongooseBulkWriteError(
|
2984 | validationErrors,
|
2985 | results,
|
2986 | res,
|
2987 | 'insertMany'
|
2988 | ));
|
2989 | }
|
2990 |
|
2991 | if (rawResult) {
|
2992 | if (ordered === false) {
|
2993 | for (let i = 0; i < results.length; ++i) {
|
2994 | if (results[i] === void 0) {
|
2995 | results[i] = docs[i];
|
2996 | }
|
2997 | }
|
2998 |
|
2999 | // Decorate with mongoose validation errors in case of unordered,
|
3000 | // because then still do `insertMany()`
|
3001 | res.mongoose = {
|
3002 | validationErrors: validationErrors,
|
3003 | results: results
|
3004 | };
|
3005 | }
|
3006 | return callback(null, res);
|
3007 | }
|
3008 |
|
3009 | if (options.populate != null) {
|
3010 | return _this.populate(docAttributes, options.populate).then(
|
3011 | docs => { callback(null, docs); },
|
3012 | err => {
|
3013 | if (err != null) {
|
3014 | err.insertedDocs = docAttributes;
|
3015 | }
|
3016 | throw err;
|
3017 | }
|
3018 | );
|
3019 | }
|
3020 |
|
3021 | callback(null, docAttributes);
|
3022 | },
|
3023 | error => {
|
3024 | // `writeErrors` is a property reported by the MongoDB driver,
|
3025 | // just not if there's only 1 error.
|
3026 | if (error.writeErrors == null &&
|
3027 | (error.result && error.result.result && error.result.result.writeErrors) != null) {
|
3028 | error.writeErrors = error.result.result.writeErrors;
|
3029 | }
|
3030 |
|
3031 | // `insertedDocs` is a Mongoose-specific property
|
3032 | const hasWriteErrors = error && error.writeErrors;
|
3033 | const erroredIndexes = new Set((error && error.writeErrors || []).map(err => err.index));
|
3034 |
|
3035 | if (error.writeErrors != null) {
|
3036 | for (let i = 0; i < error.writeErrors.length; ++i) {
|
3037 | const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
|
3038 | error.writeErrors[i] = {
|
3039 | ...error.writeErrors[i],
|
3040 | index: originalIndex
|
3041 | };
|
3042 | if (!ordered) {
|
3043 | results[originalIndex] = error.writeErrors[i];
|
3044 | }
|
3045 | }
|
3046 | }
|
3047 |
|
3048 | if (!ordered) {
|
3049 | for (let i = 0; i < results.length; ++i) {
|
3050 | if (results[i] === void 0) {
|
3051 | results[i] = docs[i];
|
3052 | }
|
3053 | }
|
3054 |
|
3055 | error.results = results;
|
3056 | }
|
3057 |
|
3058 | let firstErroredIndex = -1;
|
3059 | error.insertedDocs = docAttributes.
|
3060 | filter((doc, i) => {
|
3061 | const isErrored = !hasWriteErrors || erroredIndexes.has(i);
|
3062 |
|
3063 | if (ordered) {
|
3064 | if (firstErroredIndex > -1) {
|
3065 | return i < firstErroredIndex;
|
3066 | }
|
3067 |
|
3068 | if (isErrored) {
|
3069 | firstErroredIndex = i;
|
3070 | }
|
3071 | }
|
3072 |
|
3073 | return !isErrored;
|
3074 | }).
|
3075 | map(function setIsNewForInsertedDoc(doc) {
|
3076 | if (lean) {
|
3077 | return doc;
|
3078 | }
|
3079 | doc.$__reset();
|
3080 | _setIsNew(doc, false);
|
3081 | return doc;
|
3082 | });
|
3083 |
|
3084 | if (rawResult && ordered === false) {
|
3085 | error.mongoose = {
|
3086 | validationErrors: validationErrors,
|
3087 | results: results
|
3088 | };
|
3089 | }
|
3090 |
|
3091 | callback(error, null);
|
3092 | }
|
3093 | );
|
3094 | });
|
3095 | };
|
3096 |
|
3097 | /*!
|
3098 | * ignore
|
3099 | */
|
3100 |
|
3101 | function _setIsNew(doc, val) {
|
3102 | doc.$isNew = val;
|
3103 | doc.$emit('isNew', val);
|
3104 | doc.constructor.emit('isNew', val);
|
3105 |
|
3106 | const subdocs = doc.$getAllSubdocs();
|
3107 | for (const subdoc of subdocs) {
|
3108 | subdoc.$isNew = val;
|
3109 | subdoc.$emit('isNew', val);
|
3110 | }
|
3111 | }
|
3112 |
|
3113 | /**
|
3114 | * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`,
|
3115 | * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one
|
3116 | * command. This is faster than sending multiple independent operations (e.g.
|
3117 | * if you use `create()`) because with `bulkWrite()` there is only one round
|
3118 | * trip to MongoDB.
|
3119 | *
|
3120 | * Mongoose will perform casting on all operations you provide.
|
3121 | * The only exception is [setting the `update` operator for `updateOne` or `updateMany` to a pipeline](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#updateone-and-updatemany): Mongoose does **not** cast update pipelines.
|
3122 | *
|
3123 | * This function does **not** trigger any middleware, neither `save()`, nor `update()`.
|
3124 | * If you need to trigger
|
3125 | * `save()` middleware for every document use [`create()`](https://mongoosejs.com/docs/api/model.html#Model.create()) instead.
|
3126 | *
|
3127 | * #### Example:
|
3128 | *
|
3129 | * Character.bulkWrite([
|
3130 | * {
|
3131 | * insertOne: {
|
3132 | * document: {
|
3133 | * name: 'Eddard Stark',
|
3134 | * title: 'Warden of the North'
|
3135 | * }
|
3136 | * }
|
3137 | * },
|
3138 | * {
|
3139 | * updateOne: {
|
3140 | * filter: { name: 'Eddard Stark' },
|
3141 | * // If you were using the MongoDB driver directly, you'd need to do
|
3142 | * // `update: { $set: { title: ... } }` but mongoose adds $set for
|
3143 | * // you.
|
3144 | * update: { title: 'Hand of the King' }
|
3145 | * }
|
3146 | * },
|
3147 | * {
|
3148 | * deleteOne: {
|
3149 | * filter: { name: 'Eddard Stark' }
|
3150 | * }
|
3151 | * }
|
3152 | * ]).then(res => {
|
3153 | * // Prints "1 1 1"
|
3154 | * console.log(res.insertedCount, res.modifiedCount, res.deletedCount);
|
3155 | * });
|
3156 | *
|
3157 | * // Mongoose does **not** cast update pipelines, so no casting for the `update` option below.
|
3158 | * // Mongoose does still cast `filter`
|
3159 | * await Character.bulkWrite([{
|
3160 | * updateOne: {
|
3161 | * filter: { name: 'Annika Hansen' },
|
3162 | * update: [{ $set: { name: 7 } }] // Array means update pipeline, so Mongoose skips casting
|
3163 | * }
|
3164 | * }]);
|
3165 | *
|
3166 | * The [supported operations](https://www.mongodb.com/docs/manual/reference/method/db.collection.bulkWrite/#db.collection.bulkWrite) are:
|
3167 | *
|
3168 | * - `insertOne`
|
3169 | * - `updateOne`
|
3170 | * - `updateMany`
|
3171 | * - `deleteOne`
|
3172 | * - `deleteMany`
|
3173 | * - `replaceOne`
|
3174 | *
|
3175 | * @param {Array} ops
|
3176 | * @param {Object} [ops.insertOne.document] The document to insert
|
3177 | * @param {Object} [ops.insertOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
|
3178 | * @param {Object} [ops.updateOne.filter] Update the first document that matches this filter
|
3179 | * @param {Object} [ops.updateOne.update] An object containing [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/)
|
3180 | * @param {Boolean} [ops.updateOne.upsert=false] If true, insert a doc if none match
|
3181 | * @param {Boolean} [ops.updateOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
|
3182 | * @param {Object} [ops.updateOne.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
|
3183 | * @param {Array} [ops.updateOne.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
|
3184 | * @param {Object} [ops.updateMany.filter] Update all the documents that match this filter
|
3185 | * @param {Object} [ops.updateMany.update] An object containing [update operators](https://www.mongodb.com/docs/manual/reference/operator/update/)
|
3186 | * @param {Boolean} [ops.updateMany.upsert=false] If true, insert a doc if no documents match `filter`
|
3187 | * @param {Boolean} [ops.updateMany.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
|
3188 | * @param {Object} [ops.updateMany.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
|
3189 | * @param {Array} [ops.updateMany.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
|
3190 | * @param {Object} [ops.deleteOne.filter] Delete the first document that matches this filter
|
3191 | * @param {Object} [ops.deleteMany.filter] Delete all documents that match this filter
|
3192 | * @param {Object} [ops.replaceOne.filter] Replace the first document that matches this filter
|
3193 | * @param {Object} [ops.replaceOne.replacement] The replacement document
|
3194 | * @param {Boolean} [ops.replaceOne.upsert=false] If true, insert a doc if no documents match `filter`
|
3195 | * @param {Object} [ops.replaceOne.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to the operation
|
3196 | * @param {Object} [options]
|
3197 | * @param {Boolean} [options.ordered=true] If true, execute writes in order and stop at the first error. If false, execute writes in parallel and continue until all writes have either succeeded or errored.
|
3198 | * @param {Boolean} [options.timestamps=true] If false, do not apply [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) to any operations. Can be overridden at the operation-level.
|
3199 | * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
|
3200 | * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()) for more information.
|
3201 | * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout).
|
3202 | * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
|
3203 | * @param {Boolean} [options.skipValidation=false] Set to true to skip Mongoose schema validation on bulk write operations. Mongoose currently runs validation on `insertOne` and `replaceOne` operations by default.
|
3204 | * @param {Boolean} [options.bypassDocumentValidation=false] If true, disable [MongoDB server-side schema validation](https://www.mongodb.com/docs/manual/core/schema-validation/) for all writes in this bulk.
|
3205 | * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully.
|
3206 | * @param {Boolean} [options.strict=null] Overwrites the [`strict` option](https://mongoosejs.com/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk.
|
3207 | * @return {Promise} resolves to a [`BulkWriteOpResult`](https://mongodb.github.io/node-mongodb-native/4.9/classes/BulkWriteResult.html) if the operation succeeds
|
3208 | * @api public
|
3209 | */
|
3210 |
|
3211 | Model.bulkWrite = async function bulkWrite(ops, options) {
|
3212 | _checkContext(this, 'bulkWrite');
|
3213 |
|
3214 | if (typeof options === 'function' ||
|
3215 | typeof arguments[2] === 'function') {
|
3216 | throw new MongooseError('Model.bulkWrite() no longer accepts a callback');
|
3217 | }
|
3218 | options = options || {};
|
3219 |
|
3220 | const shouldSkip = await new Promise((resolve, reject) => {
|
3221 | this.hooks.execPre('bulkWrite', this, [ops, options], (err) => {
|
3222 | if (err != null) {
|
3223 | if (err instanceof Kareem.skipWrappedFunction) {
|
3224 | return resolve(err);
|
3225 | }
|
3226 | return reject(err);
|
3227 | }
|
3228 | resolve();
|
3229 | });
|
3230 | });
|
3231 |
|
3232 | if (shouldSkip) {
|
3233 | return shouldSkip.args[0];
|
3234 | }
|
3235 |
|
3236 | const ordered = options.ordered == null ? true : options.ordered;
|
3237 |
|
3238 | if (ops.length === 0) {
|
3239 | return getDefaultBulkwriteResult();
|
3240 | }
|
3241 |
|
3242 | const validations = ops.map(op => castBulkWrite(this, op, options));
|
3243 | const asyncLocalStorage = this.db.base.transactionAsyncLocalStorage?.getStore();
|
3244 | if ((!options || !options.hasOwnProperty('session')) && asyncLocalStorage?.session != null) {
|
3245 | options = { ...options, session: asyncLocalStorage.session };
|
3246 | }
|
3247 |
|
3248 | let res = null;
|
3249 | if (ordered) {
|
3250 | await new Promise((resolve, reject) => {
|
3251 | each(validations, (fn, cb) => fn(cb), error => {
|
3252 | if (error) {
|
3253 | return reject(error);
|
3254 | }
|
3255 |
|
3256 | resolve();
|
3257 | });
|
3258 | });
|
3259 |
|
3260 | try {
|
3261 | res = await this.$__collection.bulkWrite(ops, options);
|
3262 | } catch (error) {
|
3263 | await new Promise((resolve, reject) => {
|
3264 | const _opts = { error: error };
|
3265 | this.hooks.execPost('bulkWrite', this, [null], _opts, (err) => {
|
3266 | if (err != null) {
|
3267 | return reject(err);
|
3268 | }
|
3269 | resolve();
|
3270 | });
|
3271 | });
|
3272 | }
|
3273 | } else {
|
3274 | let remaining = validations.length;
|
3275 | let validOps = [];
|
3276 | let validationErrors = [];
|
3277 | const results = [];
|
3278 | await new Promise((resolve) => {
|
3279 | for (let i = 0; i < validations.length; ++i) {
|
3280 | validations[i]((err) => {
|
3281 | if (err == null) {
|
3282 | validOps.push(i);
|
3283 | } else {
|
3284 | validationErrors.push({ index: i, error: err });
|
3285 | results[i] = err;
|
3286 | }
|
3287 | if (--remaining <= 0) {
|
3288 | resolve();
|
3289 | }
|
3290 | });
|
3291 | }
|
3292 | });
|
3293 |
|
3294 | validationErrors = validationErrors.
|
3295 | sort((v1, v2) => v1.index - v2.index).
|
3296 | map(v => v.error);
|
3297 |
|
3298 | const validOpIndexes = validOps;
|
3299 | validOps = validOps.sort().map(index => ops[index]);
|
3300 |
|
3301 | if (validOps.length === 0) {
|
3302 | if (options.throwOnValidationError && validationErrors.length) {
|
3303 | throw new MongooseBulkWriteError(
|
3304 | validationErrors,
|
3305 | results,
|
3306 | res,
|
3307 | 'bulkWrite'
|
3308 | );
|
3309 | }
|
3310 | return getDefaultBulkwriteResult();
|
3311 | }
|
3312 |
|
3313 | let error;
|
3314 | [res, error] = await this.$__collection.bulkWrite(validOps, options).
|
3315 | then(res => ([res, null])).
|
3316 | catch(err => ([null, err]));
|
3317 |
|
3318 | if (error) {
|
3319 | if (validationErrors.length > 0) {
|
3320 | error.mongoose = error.mongoose || {};
|
3321 | error.mongoose.validationErrors = validationErrors;
|
3322 | }
|
3323 |
|
3324 | await new Promise((resolve, reject) => {
|
3325 | const _opts = { error: error };
|
3326 | this.hooks.execPost('bulkWrite', this, [null], _opts, (err) => {
|
3327 | if (err != null) {
|
3328 | return reject(err);
|
3329 | }
|
3330 | resolve();
|
3331 | });
|
3332 | });
|
3333 | }
|
3334 |
|
3335 | for (let i = 0; i < validOpIndexes.length; ++i) {
|
3336 | results[validOpIndexes[i]] = null;
|
3337 | }
|
3338 | if (validationErrors.length > 0) {
|
3339 | if (options.throwOnValidationError) {
|
3340 | throw new MongooseBulkWriteError(
|
3341 | validationErrors,
|
3342 | results,
|
3343 | res,
|
3344 | 'bulkWrite'
|
3345 | );
|
3346 | } else {
|
3347 | res.mongoose = res.mongoose || {};
|
3348 | res.mongoose.validationErrors = validationErrors;
|
3349 | res.mongoose.results = results;
|
3350 | }
|
3351 | }
|
3352 | }
|
3353 |
|
3354 | await new Promise((resolve, reject) => {
|
3355 | this.hooks.execPost('bulkWrite', this, [res], (err) => {
|
3356 | if (err != null) {
|
3357 | return reject(err);
|
3358 | }
|
3359 | resolve();
|
3360 | });
|
3361 | });
|
3362 |
|
3363 | return res;
|
3364 | };
|
3365 |
|
3366 | /**
|
3367 | * takes an array of documents, gets the changes and inserts/updates documents in the database
|
3368 | * according to whether or not the document is new, or whether it has changes or not.
|
3369 | *
|
3370 | * `bulkSave` uses `bulkWrite` under the hood, so it's mostly useful when dealing with many documents (10K+)
|
3371 | *
|
3372 | * @param {Array<Document>} documents
|
3373 | * @param {Object} [options] options passed to the underlying `bulkWrite()`
|
3374 | * @param {Boolean} [options.timestamps] defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
|
3375 | * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
|
3376 | * @param {String|number} [options.w=1] The [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/). See [`Query#w()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()) for more information.
|
3377 | * @param {number} [options.wtimeout=null] The [write concern timeout](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout).
|
3378 | * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
|
3379 | *
|
3380 | */
|
3381 | Model.bulkSave = async function bulkSave(documents, options) {
|
3382 | options = options || {};
|
3383 |
|
3384 | if (options.timestamps != null) {
|
3385 | for (const document of documents) {
|
3386 | document.$__.saveOptions = document.$__.saveOptions || {};
|
3387 | document.$__.saveOptions.timestamps = options.timestamps;
|
3388 | }
|
3389 | } else {
|
3390 | for (const document of documents) {
|
3391 | if (document.$__.timestamps != null) {
|
3392 | document.$__.saveOptions = document.$__.saveOptions || {};
|
3393 | document.$__.saveOptions.timestamps = document.$__.timestamps;
|
3394 | }
|
3395 | }
|
3396 | }
|
3397 |
|
3398 | await Promise.all(documents.map(buildPreSavePromise));
|
3399 |
|
3400 | const writeOperations = this.buildBulkWriteOperations(documents, { skipValidation: true, timestamps: options.timestamps });
|
3401 |
|
3402 | const { bulkWriteResult, bulkWriteError } = await this.bulkWrite(writeOperations, options).then(
|
3403 | (res) => ({ bulkWriteResult: res, bulkWriteError: null }),
|
3404 | (err) => ({ bulkWriteResult: null, bulkWriteError: err })
|
3405 | );
|
3406 |
|
3407 | await Promise.all(
|
3408 | documents.map(async(document) => {
|
3409 | const documentError = bulkWriteError && bulkWriteError.writeErrors.find(writeError => {
|
3410 | const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
|
3411 | return writeErrorDocumentId.toString() === document._id.toString();
|
3412 | });
|
3413 |
|
3414 | if (documentError == null) {
|
3415 | await handleSuccessfulWrite(document);
|
3416 | }
|
3417 | })
|
3418 | );
|
3419 |
|
3420 | if (bulkWriteError && bulkWriteError.writeErrors && bulkWriteError.writeErrors.length) {
|
3421 | throw bulkWriteError;
|
3422 | }
|
3423 |
|
3424 | return bulkWriteResult;
|
3425 | };
|
3426 |
|
3427 | function buildPreSavePromise(document) {
|
3428 | return new Promise((resolve, reject) => {
|
3429 | document.schema.s.hooks.execPre('save', document, (err) => {
|
3430 | if (err) {
|
3431 | reject(err);
|
3432 | return;
|
3433 | }
|
3434 | resolve();
|
3435 | });
|
3436 | });
|
3437 | }
|
3438 |
|
3439 | function handleSuccessfulWrite(document) {
|
3440 | return new Promise((resolve, reject) => {
|
3441 | if (document.$isNew) {
|
3442 | _setIsNew(document, false);
|
3443 | }
|
3444 |
|
3445 | document.$__reset();
|
3446 | document.schema.s.hooks.execPost('save', document, [document], {}, (err) => {
|
3447 | if (err) {
|
3448 | reject(err);
|
3449 | return;
|
3450 | }
|
3451 | resolve();
|
3452 | });
|
3453 |
|
3454 | });
|
3455 | }
|
3456 |
|
3457 | /**
|
3458 | * Apply defaults to the given document or POJO.
|
3459 | *
|
3460 | * @param {Object|Document} obj object or document to apply defaults on
|
3461 | * @returns {Object|Document}
|
3462 | * @api public
|
3463 | */
|
3464 |
|
3465 | Model.applyDefaults = function applyDefaults(doc) {
|
3466 | if (doc.$__ != null) {
|
3467 | applyDefaultsHelper(doc, doc.$__.fields, doc.$__.exclude);
|
3468 |
|
3469 | for (const subdoc of doc.$getAllSubdocs()) {
|
3470 | applyDefaults(subdoc, subdoc.$__.fields, subdoc.$__.exclude);
|
3471 | }
|
3472 |
|
3473 | return doc;
|
3474 | }
|
3475 |
|
3476 | applyDefaultsToPOJO(doc, this.schema);
|
3477 |
|
3478 | return doc;
|
3479 | };
|
3480 |
|
3481 | /**
|
3482 | * Cast the given POJO to the model's schema
|
3483 | *
|
3484 | * #### Example:
|
3485 | *
|
3486 | * const Test = mongoose.model('Test', Schema({ num: Number }));
|
3487 | *
|
3488 | * const obj = Test.castObject({ num: '42' });
|
3489 | * obj.num; // 42 as a number
|
3490 | *
|
3491 | * Test.castObject({ num: 'not a number' }); // Throws a ValidationError
|
3492 | *
|
3493 | * @param {Object} obj object or document to cast
|
3494 | * @param {Object} options options passed to castObject
|
3495 | * @param {Boolean} options.ignoreCastErrors If set to `true` will not throw a ValidationError and only return values that were successfully cast.
|
3496 | * @returns {Object} POJO casted to the model's schema
|
3497 | * @throws {ValidationError} if casting failed for at least one path
|
3498 | * @api public
|
3499 | */
|
3500 |
|
3501 | Model.castObject = function castObject(obj, options) {
|
3502 | options = options || {};
|
3503 | const ret = {};
|
3504 |
|
3505 | const schema = this.schema;
|
3506 | const paths = Object.keys(schema.paths);
|
3507 |
|
3508 | for (const path of paths) {
|
3509 | const schemaType = schema.path(path);
|
3510 | if (!schemaType || !schemaType.$isMongooseArray) {
|
3511 | continue;
|
3512 | }
|
3513 |
|
3514 | const val = get(obj, path);
|
3515 | pushNestedArrayPaths(paths, val, path);
|
3516 | }
|
3517 |
|
3518 | let error = null;
|
3519 |
|
3520 | for (const path of paths) {
|
3521 | const schemaType = schema.path(path);
|
3522 | if (schemaType == null) {
|
3523 | continue;
|
3524 | }
|
3525 |
|
3526 | let val = get(obj, path, void 0);
|
3527 |
|
3528 | if (val == null) {
|
3529 | continue;
|
3530 | }
|
3531 |
|
3532 | const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
|
3533 | let cur = ret;
|
3534 | for (let i = 0; i < pieces.length - 1; ++i) {
|
3535 | if (cur[pieces[i]] == null) {
|
3536 | cur[pieces[i]] = isNaN(pieces[i + 1]) ? {} : [];
|
3537 | }
|
3538 | cur = cur[pieces[i]];
|
3539 | }
|
3540 |
|
3541 | if (schemaType.$isMongooseDocumentArray) {
|
3542 | continue;
|
3543 | }
|
3544 | if (schemaType.$isSingleNested || schemaType.$isMongooseDocumentArrayElement) {
|
3545 | try {
|
3546 | val = Model.castObject.call(schemaType.caster, val);
|
3547 | } catch (err) {
|
3548 | if (!options.ignoreCastErrors) {
|
3549 | error = error || new ValidationError();
|
3550 | error.addError(path, err);
|
3551 | }
|
3552 | continue;
|
3553 | }
|
3554 |
|
3555 | cur[pieces[pieces.length - 1]] = val;
|
3556 | continue;
|
3557 | }
|
3558 |
|
3559 | try {
|
3560 | val = schemaType.cast(val);
|
3561 | cur[pieces[pieces.length - 1]] = val;
|
3562 | } catch (err) {
|
3563 | if (!options.ignoreCastErrors) {
|
3564 | error = error || new ValidationError();
|
3565 | error.addError(path, err);
|
3566 | }
|
3567 |
|
3568 | continue;
|
3569 | }
|
3570 | }
|
3571 |
|
3572 | if (error != null) {
|
3573 | throw error;
|
3574 | }
|
3575 |
|
3576 | return ret;
|
3577 | };
|
3578 |
|
3579 | /**
|
3580 | * Build bulk write operations for `bulkSave()`.
|
3581 | *
|
3582 | * @param {Array<Document>} documents The array of documents to build write operations of
|
3583 | * @param {Object} options
|
3584 | * @param {Boolean} options.skipValidation defaults to `false`, when set to true, building the write operations will bypass validating the documents.
|
3585 | * @param {Boolean} options.timestamps defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
|
3586 | * @return {Array<Promise>} Returns a array of all Promises the function executes to be awaited.
|
3587 | * @api private
|
3588 | */
|
3589 |
|
3590 | Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, options) {
|
3591 | if (!Array.isArray(documents)) {
|
3592 | throw new Error(`bulkSave expects an array of documents to be passed, received \`${documents}\` instead`);
|
3593 | }
|
3594 |
|
3595 | setDefaultOptions();
|
3596 | const discriminatorKey = this.schema.options.discriminatorKey;
|
3597 |
|
3598 | const writeOperations = documents.reduce((accumulator, document, i) => {
|
3599 | if (!options.skipValidation) {
|
3600 | if (!(document instanceof Document)) {
|
3601 | throw new Error(`documents.${i} was not a mongoose document, documents must be an array of mongoose documents (instanceof mongoose.Document).`);
|
3602 | }
|
3603 | const validationError = document.validateSync();
|
3604 | if (validationError) {
|
3605 | throw validationError;
|
3606 | }
|
3607 | }
|
3608 |
|
3609 | const isANewDocument = document.isNew;
|
3610 | if (isANewDocument) {
|
3611 | const writeOperation = { insertOne: { document } };
|
3612 | utils.injectTimestampsOption(writeOperation.insertOne, options.timestamps);
|
3613 | accumulator.push(writeOperation);
|
3614 |
|
3615 | return accumulator;
|
3616 | }
|
3617 |
|
3618 | const delta = document.$__delta();
|
3619 | const isDocumentWithChanges = delta != null && !utils.isEmptyObject(delta[0]);
|
3620 |
|
3621 | if (isDocumentWithChanges) {
|
3622 | const where = document.$__where(delta[0]);
|
3623 | const changes = delta[1];
|
3624 |
|
3625 | _applyCustomWhere(document, where);
|
3626 |
|
3627 | // If shard key is set, add shard keys to _filter_ condition to right shard is targeted
|
3628 | const shardKey = this.schema.options.shardKey;
|
3629 | if (shardKey) {
|
3630 | const paths = Object.keys(shardKey);
|
3631 | const len = paths.length;
|
3632 |
|
3633 | for (let i = 0; i < len; ++i) {
|
3634 | where[paths[i]] = document[paths[i]];
|
3635 | }
|
3636 | }
|
3637 |
|
3638 | // Set the discriminator key, so bulk write casting knows which
|
3639 | // schema to use re: gh-13907
|
3640 | if (document[discriminatorKey] != null && !(discriminatorKey in where)) {
|
3641 | where[discriminatorKey] = document[discriminatorKey];
|
3642 | }
|
3643 |
|
3644 | document.$__version(where, delta);
|
3645 | const writeOperation = { updateOne: { filter: where, update: changes } };
|
3646 | utils.injectTimestampsOption(writeOperation.updateOne, options.timestamps);
|
3647 | accumulator.push(writeOperation);
|
3648 |
|
3649 | return accumulator;
|
3650 | }
|
3651 |
|
3652 | return accumulator;
|
3653 | }, []);
|
3654 |
|
3655 | return writeOperations;
|
3656 |
|
3657 |
|
3658 | function setDefaultOptions() {
|
3659 | options = options || {};
|
3660 | if (options.skipValidation == null) {
|
3661 | options.skipValidation = false;
|
3662 | }
|
3663 | }
|
3664 | };
|
3665 |
|
3666 |
|
3667 | /**
|
3668 | * Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
|
3669 | * The document returned has no paths marked as modified initially.
|
3670 | *
|
3671 | * #### Example:
|
3672 | *
|
3673 | * // hydrate previous data into a Mongoose document
|
3674 | * const mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });
|
3675 | *
|
3676 | * @param {Object} obj
|
3677 | * @param {Object|String|String[]} [projection] optional projection containing which fields should be selected for this document
|
3678 | * @param {Object} [options] optional options
|
3679 | * @param {Boolean} [options.setters=false] if true, apply schema setters when hydrating
|
3680 | * @param {Boolean} [options.hydratedPopulatedDocs=false] if true, populates the docs if passing pre-populated data
|
3681 | * @return {Document} document instance
|
3682 | * @api public
|
3683 | */
|
3684 |
|
3685 | Model.hydrate = function(obj, projection, options) {
|
3686 | _checkContext(this, 'hydrate');
|
3687 |
|
3688 | if (projection != null) {
|
3689 | if (obj != null && obj.$__ != null) {
|
3690 | obj = obj.toObject(internalToObjectOptions);
|
3691 | }
|
3692 | obj = applyProjection(obj, projection);
|
3693 | }
|
3694 | const document = require('./queryHelpers').createModel(this, obj, projection);
|
3695 | document.$init(obj, options);
|
3696 | return document;
|
3697 | };
|
3698 |
|
3699 | /**
|
3700 | * Same as `updateOne()`, except MongoDB will update _all_ documents that match
|
3701 | * `filter` (as opposed to just the first one) regardless of the value of
|
3702 | * the `multi` option.
|
3703 | *
|
3704 | * **Note** updateMany will _not_ fire update middleware. Use `pre('updateMany')`
|
3705 | * and `post('updateMany')` instead.
|
3706 | *
|
3707 | * #### Example:
|
3708 | *
|
3709 | * const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
|
3710 | * res.matchedCount; // Number of documents matched
|
3711 | * res.modifiedCount; // Number of documents modified
|
3712 | * res.acknowledged; // Boolean indicating everything went smoothly.
|
3713 | * res.upsertedId; // null or an id containing a document that had to be upserted.
|
3714 | * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
|
3715 | *
|
3716 | * This function triggers the following middleware.
|
3717 | *
|
3718 | * - `updateMany()`
|
3719 | *
|
3720 | * @param {Object} filter
|
3721 | * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
|
3722 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
3723 | * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
3724 | * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
|
3725 | * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
|
3726 | * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
|
3727 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
3728 | * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
|
3729 | * @return {Query}
|
3730 | * @see Query docs https://mongoosejs.com/docs/queries.html
|
3731 | * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
|
3732 | * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
|
3733 | * @api public
|
3734 | */
|
3735 |
|
3736 | Model.updateMany = function updateMany(conditions, doc, options) {
|
3737 | _checkContext(this, 'updateMany');
|
3738 |
|
3739 | return _update(this, 'updateMany', conditions, doc, options);
|
3740 | };
|
3741 |
|
3742 | /**
|
3743 | * Update _only_ the first document that matches `filter`.
|
3744 | *
|
3745 | * - Use `replaceOne()` if you want to overwrite an entire document rather than using atomic operators like `$set`.
|
3746 | *
|
3747 | * #### Example:
|
3748 | *
|
3749 | * const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
|
3750 | * res.matchedCount; // Number of documents matched
|
3751 | * res.modifiedCount; // Number of documents modified
|
3752 | * res.acknowledged; // Boolean indicating everything went smoothly.
|
3753 | * res.upsertedId; // null or an id containing a document that had to be upserted.
|
3754 | * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
|
3755 | *
|
3756 | * This function triggers the following middleware.
|
3757 | *
|
3758 | * - `updateOne()`
|
3759 | *
|
3760 | * @param {Object} filter
|
3761 | * @param {Object|Array} update. If array, this update will be treated as an update pipeline and not casted.
|
3762 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
3763 | * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
3764 | * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
|
3765 | * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
|
3766 | * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
|
3767 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
3768 | * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
|
3769 | * @return {Query}
|
3770 | * @see Query docs https://mongoosejs.com/docs/queries.html
|
3771 | * @see MongoDB docs https://www.mongodb.com/docs/manual/reference/command/update/#update-command-output
|
3772 | * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
|
3773 | * @api public
|
3774 | */
|
3775 |
|
3776 | Model.updateOne = function updateOne(conditions, doc, options) {
|
3777 | _checkContext(this, 'updateOne');
|
3778 |
|
3779 | return _update(this, 'updateOne', conditions, doc, options);
|
3780 | };
|
3781 |
|
3782 | /**
|
3783 | * Replace the existing document with the given document (no atomic operators like `$set`).
|
3784 | *
|
3785 | * #### Example:
|
3786 | *
|
3787 | * const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
|
3788 | * res.matchedCount; // Number of documents matched
|
3789 | * res.modifiedCount; // Number of documents modified
|
3790 | * res.acknowledged; // Boolean indicating everything went smoothly.
|
3791 | * res.upsertedId; // null or an id containing a document that had to be upserted.
|
3792 | * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
|
3793 | *
|
3794 | * This function triggers the following middleware.
|
3795 | *
|
3796 | * - `replaceOne()`
|
3797 | *
|
3798 | * @param {Object} filter
|
3799 | * @param {Object} doc
|
3800 | * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
3801 | * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
3802 | * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
|
3803 | * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
|
3804 | * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
|
3805 | * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
3806 | * @return {Query}
|
3807 | * @see Query docs https://mongoosejs.com/docs/queries.html
|
3808 | * @see UpdateResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/UpdateResult.html
|
3809 | * @return {Query}
|
3810 | * @api public
|
3811 | */
|
3812 |
|
3813 | Model.replaceOne = function replaceOne(conditions, doc, options) {
|
3814 | _checkContext(this, 'replaceOne');
|
3815 |
|
3816 | const versionKey = this && this.schema && this.schema.options && this.schema.options.versionKey || null;
|
3817 | if (versionKey && !doc[versionKey]) {
|
3818 | doc[versionKey] = 0;
|
3819 | }
|
3820 |
|
3821 | return _update(this, 'replaceOne', conditions, doc, options);
|
3822 | };
|
3823 |
|
3824 | /**
|
3825 | * Common code for `updateOne()`, `updateMany()`, `replaceOne()`, and `update()`
|
3826 | * because they need to do the same thing
|
3827 | * @api private
|
3828 | */
|
3829 |
|
3830 | function _update(model, op, conditions, doc, options) {
|
3831 | const mq = new model.Query({}, {}, model, model.collection);
|
3832 |
|
3833 | // gh-2406
|
3834 | // make local deep copy of conditions
|
3835 | if (conditions instanceof Document) {
|
3836 | conditions = conditions.toObject();
|
3837 | } else {
|
3838 | conditions = clone(conditions);
|
3839 | }
|
3840 | options = typeof options === 'function' ? options : clone(options);
|
3841 |
|
3842 | const versionKey = model &&
|
3843 | model.schema &&
|
3844 | model.schema.options &&
|
3845 | model.schema.options.versionKey || null;
|
3846 | decorateUpdateWithVersionKey(doc, options, versionKey);
|
3847 |
|
3848 | return mq[op](conditions, doc, options);
|
3849 | }
|
3850 |
|
3851 | /**
|
3852 | * Performs [aggregations](https://www.mongodb.com/docs/manual/aggregation/) on the models collection.
|
3853 | *
|
3854 | * If a `callback` is passed, the `aggregate` is executed and a `Promise` is returned. If a callback is not passed, the `aggregate` itself is returned.
|
3855 | *
|
3856 | * This function triggers the following middleware.
|
3857 | *
|
3858 | * - `aggregate()`
|
3859 | *
|
3860 | * #### Example:
|
3861 | *
|
3862 | * // Find the max balance of all accounts
|
3863 | * const res = await Users.aggregate([
|
3864 | * { $group: { _id: null, maxBalance: { $max: '$balance' }}},
|
3865 | * { $project: { _id: 0, maxBalance: 1 }}
|
3866 | * ]);
|
3867 | *
|
3868 | * console.log(res); // [ { maxBalance: 98000 } ]
|
3869 | *
|
3870 | * // Or use the aggregation pipeline builder.
|
3871 | * const res = await Users.aggregate().
|
3872 | * group({ _id: null, maxBalance: { $max: '$balance' } }).
|
3873 | * project('-id maxBalance').
|
3874 | * exec();
|
3875 | * console.log(res); // [ { maxBalance: 98 } ]
|
3876 | *
|
3877 | * #### Note:
|
3878 | *
|
3879 | * - Mongoose does **not** cast aggregation pipelines to the model's schema because `$project` and `$group` operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format. You can use the [mongoose-cast-aggregation plugin](https://github.com/AbdelrahmanHafez/mongoose-cast-aggregation) to enable minimal casting for aggregation pipelines.
|
3880 | * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
|
3881 | *
|
3882 | * #### More About Aggregations:
|
3883 | *
|
3884 | * - [Mongoose `Aggregate`](https://mongoosejs.com/docs/api/aggregate.html)
|
3885 | * - [An Introduction to Mongoose Aggregate](https://masteringjs.io/tutorials/mongoose/aggregate)
|
3886 | * - [MongoDB Aggregation docs](https://www.mongodb.com/docs/manual/applications/aggregation/)
|
3887 | *
|
3888 | * @see Aggregate https://mongoosejs.com/docs/api/aggregate.html#Aggregate()
|
3889 | * @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/
|
3890 | * @param {Array} [pipeline] aggregation pipeline as an array of objects
|
3891 | * @param {Object} [options] aggregation options
|
3892 | * @return {Aggregate}
|
3893 | * @api public
|
3894 | */
|
3895 |
|
3896 | Model.aggregate = function aggregate(pipeline, options) {
|
3897 | _checkContext(this, 'aggregate');
|
3898 |
|
3899 | if (typeof options === 'function' || typeof arguments[2] === 'function') {
|
3900 | throw new MongooseError('Model.aggregate() no longer accepts a callback');
|
3901 | }
|
3902 |
|
3903 | const aggregate = new Aggregate(pipeline || []);
|
3904 | aggregate.model(this);
|
3905 | if (options != null) {
|
3906 | aggregate.option(options);
|
3907 | }
|
3908 |
|
3909 | if (typeof callback === 'undefined') {
|
3910 | return aggregate;
|
3911 | }
|
3912 |
|
3913 | return aggregate;
|
3914 | };
|
3915 |
|
3916 | /**
|
3917 | * Casts and validates the given object against this model's schema, passing the
|
3918 | * given `context` to custom validators.
|
3919 | *
|
3920 | * #### Example:
|
3921 | *
|
3922 | * const Model = mongoose.model('Test', Schema({
|
3923 | * name: { type: String, required: true },
|
3924 | * age: { type: Number, required: true }
|
3925 | * });
|
3926 | *
|
3927 | * try {
|
3928 | * await Model.validate({ name: null }, ['name'])
|
3929 | * } catch (err) {
|
3930 | * err instanceof mongoose.Error.ValidationError; // true
|
3931 | * Object.keys(err.errors); // ['name']
|
3932 | * }
|
3933 | *
|
3934 | * @param {Object} obj
|
3935 | * @param {Object|Array|String} pathsOrOptions
|
3936 | * @param {Object} [context]
|
3937 | * @return {Promise<Object>} casted and validated copy of `obj` if validation succeeded
|
3938 | * @api public
|
3939 | */
|
3940 |
|
3941 | Model.validate = async function validate(obj, pathsOrOptions, context) {
|
3942 | if ((arguments.length < 3) || (arguments.length === 3 && typeof arguments[2] === 'function')) {
|
3943 | // For convenience, if we're validating a document or an object, make `context` default to
|
3944 | // the model so users don't have to always pass `context`, re: gh-10132, gh-10346
|
3945 | context = obj;
|
3946 | }
|
3947 | if (typeof context === 'function' || typeof arguments[3] === 'function') {
|
3948 | throw new MongooseError('Model.validate() no longer accepts a callback');
|
3949 | }
|
3950 |
|
3951 | let schema = this.schema;
|
3952 | const discriminatorKey = schema.options.discriminatorKey;
|
3953 | if (schema.discriminators != null && obj != null && obj[discriminatorKey] != null) {
|
3954 | schema = getSchemaDiscriminatorByValue(schema, obj[discriminatorKey]) || schema;
|
3955 | }
|
3956 | let paths = Object.keys(schema.paths);
|
3957 |
|
3958 | if (pathsOrOptions != null) {
|
3959 | const _pathsToValidate = typeof pathsOrOptions === 'string' ? new Set(pathsOrOptions.split(' ')) : Array.isArray(pathsOrOptions) ? new Set(pathsOrOptions) : new Set(paths);
|
3960 | paths = paths.filter(p => {
|
3961 | if (pathsOrOptions.pathsToSkip) {
|
3962 | if (Array.isArray(pathsOrOptions.pathsToSkip)) {
|
3963 | if (pathsOrOptions.pathsToSkip.find(x => x == p)) {
|
3964 | return false;
|
3965 | }
|
3966 | } else if (typeof pathsOrOptions.pathsToSkip == 'string') {
|
3967 | if (pathsOrOptions.pathsToSkip.includes(p)) {
|
3968 | return false;
|
3969 | }
|
3970 | }
|
3971 | }
|
3972 | const pieces = p.split('.');
|
3973 | let cur = pieces[0];
|
3974 |
|
3975 | for (const piece of pieces) {
|
3976 | if (_pathsToValidate.has(cur)) {
|
3977 | return true;
|
3978 | }
|
3979 | cur += '.' + piece;
|
3980 | }
|
3981 |
|
3982 | return _pathsToValidate.has(p);
|
3983 | });
|
3984 | }
|
3985 |
|
3986 | for (const path of paths) {
|
3987 | const schemaType = schema.path(path);
|
3988 | if (!schemaType || !schemaType.$isMongooseArray || schemaType.$isMongooseDocumentArray) {
|
3989 | continue;
|
3990 | }
|
3991 |
|
3992 | const val = get(obj, path);
|
3993 | pushNestedArrayPaths(paths, val, path);
|
3994 | }
|
3995 |
|
3996 | let error = null;
|
3997 | paths = new Set(paths);
|
3998 |
|
3999 | try {
|
4000 | obj = this.castObject(obj);
|
4001 | } catch (err) {
|
4002 | error = err;
|
4003 | for (const key of Object.keys(error.errors || {})) {
|
4004 | paths.delete(key);
|
4005 | }
|
4006 | }
|
4007 |
|
4008 | let remaining = paths.size;
|
4009 |
|
4010 | return new Promise((resolve, reject) => {
|
4011 | for (const path of paths) {
|
4012 | const schemaType = schema.path(path);
|
4013 | if (schemaType == null) {
|
4014 | _checkDone();
|
4015 | continue;
|
4016 | }
|
4017 |
|
4018 | const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
|
4019 | let cur = obj;
|
4020 | for (let i = 0; i < pieces.length - 1; ++i) {
|
4021 | cur = cur[pieces[i]];
|
4022 | }
|
4023 |
|
4024 | const val = get(obj, path, void 0);
|
4025 |
|
4026 | schemaType.doValidate(val, err => {
|
4027 | if (err) {
|
4028 | error = error || new ValidationError();
|
4029 | error.addError(path, err);
|
4030 | }
|
4031 | _checkDone();
|
4032 | }, context, { path: path });
|
4033 | }
|
4034 |
|
4035 | function _checkDone() {
|
4036 | if (--remaining <= 0) {
|
4037 | if (error) {
|
4038 | reject(error);
|
4039 | } else {
|
4040 | resolve(obj);
|
4041 | }
|
4042 | }
|
4043 | }
|
4044 | });
|
4045 | };
|
4046 |
|
4047 | /**
|
4048 | * Populates document references.
|
4049 | *
|
4050 | * Changed in Mongoose 6: the model you call `populate()` on should be the
|
4051 | * "local field" model, **not** the "foreign field" model.
|
4052 | *
|
4053 | * #### Available top-level options:
|
4054 | *
|
4055 | * - path: space delimited path(s) to populate
|
4056 | * - select: optional fields to select
|
4057 | * - match: optional query conditions to match
|
4058 | * - model: optional name of the model to use for population
|
4059 | * - options: optional query options like sort, limit, etc
|
4060 | * - justOne: optional boolean, if true Mongoose will always set `path` to a document, or `null` if no document was found. If false, Mongoose will always set `path` to an array, which will be empty if no documents are found. Inferred from schema by default.
|
4061 | * - strictPopulate: optional boolean, set to `false` to allow populating paths that aren't in the schema.
|
4062 | *
|
4063 | * #### Example:
|
4064 | *
|
4065 | * const Dog = mongoose.model('Dog', new Schema({ name: String, breed: String }));
|
4066 | * const Person = mongoose.model('Person', new Schema({
|
4067 | * name: String,
|
4068 | * pet: { type: mongoose.ObjectId, ref: 'Dog' }
|
4069 | * }));
|
4070 | *
|
4071 | * const pets = await Pet.create([
|
4072 | * { name: 'Daisy', breed: 'Beagle' },
|
4073 | * { name: 'Einstein', breed: 'Catalan Sheepdog' }
|
4074 | * ]);
|
4075 | *
|
4076 | * // populate many plain objects
|
4077 | * const users = [
|
4078 | * { name: 'John Wick', dog: pets[0]._id },
|
4079 | * { name: 'Doc Brown', dog: pets[1]._id }
|
4080 | * ];
|
4081 | * await User.populate(users, { path: 'dog', select: 'name' });
|
4082 | * users[0].dog.name; // 'Daisy'
|
4083 | * users[0].dog.breed; // undefined because of `select`
|
4084 | *
|
4085 | * @param {Document|Array} docs Either a single document or array of documents to populate.
|
4086 | * @param {Object|String} options Either the paths to populate or an object specifying all parameters
|
4087 | * @param {string} [options.path=null] The path to populate.
|
4088 | * @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](https://mongoosejs.com/docs/populate.html#deep-populate).
|
4089 | * @param {boolean} [options.retainNullValues=false] By default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries.
|
4090 | * @param {boolean} [options.getters=false] If true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](https://mongoosejs.com/docs/schematypes.html#schematype-options).
|
4091 | * @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them.
|
4092 | * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object.
|
4093 | * @param {Boolean} [options.skipInvalidIds=false] By default, Mongoose throws a cast error if `localField` and `foreignField` schemas don't line up. If you enable this option, Mongoose will instead filter out any `localField` properties that cannot be casted to `foreignField`'s schema type.
|
4094 | * @param {Number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results because it only executes a single query for every document being populated. If you set `perDocumentLimit`, Mongoose will ensure correct `limit` per document by executing a separate query for each document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` will execute 2 additional queries if `.find()` returns 2 documents.
|
4095 | * @param {Boolean} [options.strictPopulate=true] Set to false to allow populating paths that aren't defined in the given model's schema.
|
4096 | * @param {Object} [options.options=null] Additional options like `limit` and `lean`.
|
4097 | * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
|
4098 | * @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
|
4099 | * @return {Promise}
|
4100 | * @api public
|
4101 | */
|
4102 |
|
4103 | Model.populate = async function populate(docs, paths) {
|
4104 | _checkContext(this, 'populate');
|
4105 | if (typeof paths === 'function' || typeof arguments[2] === 'function') {
|
4106 | throw new MongooseError('Model.populate() no longer accepts a callback');
|
4107 | }
|
4108 | const _this = this;
|
4109 | // normalized paths
|
4110 | paths = utils.populate(paths);
|
4111 | // data that should persist across subPopulate calls
|
4112 | const cache = {};
|
4113 |
|
4114 | return new Promise((resolve, reject) => {
|
4115 | _populate(_this, docs, paths, cache, (err, res) => {
|
4116 | if (err) {
|
4117 | return reject(err);
|
4118 | }
|
4119 | resolve(res);
|
4120 | });
|
4121 | });
|
4122 | };
|
4123 |
|
4124 | /**
|
4125 | * Populate helper
|
4126 | *
|
4127 | * @param {Model} model the model to use
|
4128 | * @param {Document|Array} docs Either a single document or array of documents to populate.
|
4129 | * @param {Object} paths
|
4130 | * @param {never} cache Unused
|
4131 | * @param {Function} [callback] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
|
4132 | * @return {Function}
|
4133 | * @api private
|
4134 | */
|
4135 |
|
4136 | function _populate(model, docs, paths, cache, callback) {
|
4137 | let pending = paths.length;
|
4138 | if (paths.length === 0) {
|
4139 | return callback(null, docs);
|
4140 | }
|
4141 | // each path has its own query options and must be executed separately
|
4142 | for (const path of paths) {
|
4143 | populate(model, docs, path, next);
|
4144 | }
|
4145 |
|
4146 | function next(err) {
|
4147 | if (err) {
|
4148 | return callback(err, null);
|
4149 | }
|
4150 | if (--pending) {
|
4151 | return;
|
4152 | }
|
4153 | callback(null, docs);
|
4154 | }
|
4155 | }
|
4156 |
|
4157 | /*!
|
4158 | * Populates `docs`
|
4159 | */
|
4160 | const excludeIdReg = /\s?-_id\s?/;
|
4161 | const excludeIdRegGlobal = /\s?-_id\s?/g;
|
4162 |
|
4163 | function populate(model, docs, options, callback) {
|
4164 | const populateOptions = options;
|
4165 | if (options.strictPopulate == null) {
|
4166 | if (options._localModel != null && options._localModel.schema._userProvidedOptions.strictPopulate != null) {
|
4167 | populateOptions.strictPopulate = options._localModel.schema._userProvidedOptions.strictPopulate;
|
4168 | } else if (options._localModel != null && model.base.options.strictPopulate != null) {
|
4169 | populateOptions.strictPopulate = model.base.options.strictPopulate;
|
4170 | } else if (model.base.options.strictPopulate != null) {
|
4171 | populateOptions.strictPopulate = model.base.options.strictPopulate;
|
4172 | }
|
4173 | }
|
4174 |
|
4175 | // normalize single / multiple docs passed
|
4176 | if (!Array.isArray(docs)) {
|
4177 | docs = [docs];
|
4178 | }
|
4179 | if (docs.length === 0 || docs.every(utils.isNullOrUndefined)) {
|
4180 | return callback();
|
4181 | }
|
4182 |
|
4183 | const modelsMap = getModelsMapForPopulate(model, docs, populateOptions);
|
4184 |
|
4185 | if (modelsMap instanceof MongooseError) {
|
4186 | return immediate(function() {
|
4187 | callback(modelsMap);
|
4188 | });
|
4189 | }
|
4190 | const len = modelsMap.length;
|
4191 | let vals = [];
|
4192 |
|
4193 | function flatten(item) {
|
4194 | // no need to include undefined values in our query
|
4195 | return undefined !== item;
|
4196 | }
|
4197 |
|
4198 | let _remaining = len;
|
4199 | let hasOne = false;
|
4200 | const params = [];
|
4201 | for (let i = 0; i < len; ++i) {
|
4202 | const mod = modelsMap[i];
|
4203 | let select = mod.options.select;
|
4204 | let ids = utils.array.flatten(mod.ids, flatten);
|
4205 | ids = utils.array.unique(ids);
|
4206 |
|
4207 | const assignmentOpts = {};
|
4208 | assignmentOpts.sort = mod &&
|
4209 | mod.options &&
|
4210 | mod.options.options &&
|
4211 | mod.options.options.sort || void 0;
|
4212 | assignmentOpts.excludeId = excludeIdReg.test(select) || (select && select._id === 0);
|
4213 |
|
4214 | // Lean transform may delete `_id`, which would cause assignment
|
4215 | // to fail. So delay running lean transform until _after_
|
4216 | // `_assign()`
|
4217 | if (mod.options &&
|
4218 | mod.options.options &&
|
4219 | mod.options.options.lean &&
|
4220 | mod.options.options.lean.transform) {
|
4221 | mod.options.options._leanTransform = mod.options.options.lean.transform;
|
4222 | mod.options.options.lean = true;
|
4223 | }
|
4224 |
|
4225 | if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) {
|
4226 | // Ensure that we set to 0 or empty array even
|
4227 | // if we don't actually execute a query to make sure there's a value
|
4228 | // and we know this path was populated for future sets. See gh-7731, gh-8230
|
4229 | --_remaining;
|
4230 | _assign(model, [], mod, assignmentOpts);
|
4231 | continue;
|
4232 | }
|
4233 |
|
4234 | hasOne = true;
|
4235 | if (typeof populateOptions.foreignField === 'string') {
|
4236 | mod.foreignField.clear();
|
4237 | mod.foreignField.add(populateOptions.foreignField);
|
4238 | }
|
4239 | const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
|
4240 | if (assignmentOpts.excludeId) {
|
4241 | // override the exclusion from the query so we can use the _id
|
4242 | // for document matching during assignment. we'll delete the
|
4243 | // _id back off before returning the result.
|
4244 | if (typeof select === 'string') {
|
4245 | select = select.replace(excludeIdRegGlobal, ' ');
|
4246 | } else if (Array.isArray(select)) {
|
4247 | select = select.filter(field => field !== '-_id');
|
4248 | } else {
|
4249 | // preserve original select conditions by copying
|
4250 | select = { ...select };
|
4251 | delete select._id;
|
4252 | }
|
4253 | }
|
4254 |
|
4255 | if (mod.options.options && mod.options.options.limit != null) {
|
4256 | assignmentOpts.originalLimit = mod.options.options.limit;
|
4257 | } else if (mod.options.limit != null) {
|
4258 | assignmentOpts.originalLimit = mod.options.limit;
|
4259 | }
|
4260 | params.push([mod, match, select, assignmentOpts, _next]);
|
4261 | }
|
4262 | if (!hasOne) {
|
4263 | // If models but no docs, skip further deep populate.
|
4264 | if (modelsMap.length !== 0) {
|
4265 | return callback();
|
4266 | }
|
4267 | // If no models to populate but we have a nested populate,
|
4268 | // keep trying, re: gh-8946
|
4269 | if (populateOptions.populate != null) {
|
4270 | const opts = utils.populate(populateOptions.populate).map(pop => Object.assign({}, pop, {
|
4271 | path: populateOptions.path + '.' + pop.path
|
4272 | }));
|
4273 | model.populate(docs, opts).then(res => { callback(null, res); }, err => { callback(err); });
|
4274 | return;
|
4275 | }
|
4276 | return callback();
|
4277 | }
|
4278 |
|
4279 | for (const arr of params) {
|
4280 | _execPopulateQuery.apply(null, arr);
|
4281 | }
|
4282 | function _next(err, valsFromDb) {
|
4283 | if (err != null) {
|
4284 | return callback(err, null);
|
4285 | }
|
4286 | vals = vals.concat(valsFromDb);
|
4287 | if (--_remaining === 0) {
|
4288 | _done();
|
4289 | }
|
4290 | }
|
4291 |
|
4292 | function _done() {
|
4293 | for (const arr of params) {
|
4294 | const mod = arr[0];
|
4295 | const assignmentOpts = arr[3];
|
4296 | for (const val of vals) {
|
4297 | mod.options._childDocs.push(val);
|
4298 | }
|
4299 | try {
|
4300 | _assign(model, vals, mod, assignmentOpts);
|
4301 | } catch (err) {
|
4302 | return callback(err);
|
4303 | }
|
4304 | }
|
4305 |
|
4306 | for (const arr of params) {
|
4307 | removeDeselectedForeignField(arr[0].foreignField, arr[0].options, vals);
|
4308 | }
|
4309 | for (const arr of params) {
|
4310 | const mod = arr[0];
|
4311 | if (mod.options && mod.options.options && mod.options.options._leanTransform) {
|
4312 | for (const doc of vals) {
|
4313 | mod.options.options._leanTransform(doc);
|
4314 | }
|
4315 | }
|
4316 | }
|
4317 | callback();
|
4318 | }
|
4319 | }
|
4320 |
|
4321 | /*!
|
4322 | * ignore
|
4323 | */
|
4324 |
|
4325 | function _execPopulateQuery(mod, match, select, assignmentOpts, callback) {
|
4326 | let subPopulate = clone(mod.options.populate);
|
4327 | const queryOptions = Object.assign({
|
4328 | skip: mod.options.skip,
|
4329 | limit: mod.options.limit,
|
4330 | perDocumentLimit: mod.options.perDocumentLimit
|
4331 | }, mod.options.options);
|
4332 |
|
4333 | if (mod.count) {
|
4334 | delete queryOptions.skip;
|
4335 | }
|
4336 |
|
4337 | if (queryOptions.perDocumentLimit != null) {
|
4338 | queryOptions.limit = queryOptions.perDocumentLimit;
|
4339 | delete queryOptions.perDocumentLimit;
|
4340 | } else if (queryOptions.limit != null) {
|
4341 | queryOptions.limit = queryOptions.limit * mod.ids.length;
|
4342 | }
|
4343 |
|
4344 | const query = mod.model.find(match, select, queryOptions);
|
4345 | // If we're doing virtual populate and projection is inclusive and foreign
|
4346 | // field is not selected, automatically select it because mongoose needs it.
|
4347 | // If projection is exclusive and client explicitly unselected the foreign
|
4348 | // field, that's the client's fault.
|
4349 | for (const foreignField of mod.foreignField) {
|
4350 | if (foreignField !== '_id' &&
|
4351 | query.selectedInclusively() &&
|
4352 | !isPathSelectedInclusive(query._fields, foreignField)) {
|
4353 | query.select(foreignField);
|
4354 | }
|
4355 | }
|
4356 |
|
4357 | // If using count, still need the `foreignField` so we can match counts
|
4358 | // to documents, otherwise we would need a separate `count()` for every doc.
|
4359 | if (mod.count) {
|
4360 | for (const foreignField of mod.foreignField) {
|
4361 | query.select(foreignField);
|
4362 | }
|
4363 | }
|
4364 |
|
4365 | // If we need to sub-populate, call populate recursively
|
4366 | if (subPopulate) {
|
4367 | // If subpopulating on a discriminator, skip check for non-existent
|
4368 | // paths. Because the discriminator may not have the path defined.
|
4369 | if (mod.model.baseModelName != null) {
|
4370 | if (Array.isArray(subPopulate)) {
|
4371 | subPopulate.forEach(pop => { pop.strictPopulate = false; });
|
4372 | } else if (typeof subPopulate === 'string') {
|
4373 | subPopulate = { path: subPopulate, strictPopulate: false };
|
4374 | } else {
|
4375 | subPopulate.strictPopulate = false;
|
4376 | }
|
4377 | }
|
4378 | const basePath = mod.options._fullPath || mod.options.path;
|
4379 |
|
4380 | if (Array.isArray(subPopulate)) {
|
4381 | for (const pop of subPopulate) {
|
4382 | pop._fullPath = basePath + '.' + pop.path;
|
4383 | }
|
4384 | } else if (typeof subPopulate === 'object') {
|
4385 | subPopulate._fullPath = basePath + '.' + subPopulate.path;
|
4386 | }
|
4387 |
|
4388 | query.populate(subPopulate);
|
4389 | }
|
4390 |
|
4391 | query.exec().then(
|
4392 | docs => {
|
4393 | for (const val of docs) {
|
4394 | leanPopulateMap.set(val, mod.model);
|
4395 | }
|
4396 | callback(null, docs);
|
4397 | },
|
4398 | err => {
|
4399 | callback(err);
|
4400 | }
|
4401 | );
|
4402 | }
|
4403 |
|
4404 | /*!
|
4405 | * ignore
|
4406 | */
|
4407 |
|
4408 | function _assign(model, vals, mod, assignmentOpts) {
|
4409 | const options = mod.options;
|
4410 | const isVirtual = mod.isVirtual;
|
4411 | const justOne = mod.justOne;
|
4412 | let _val;
|
4413 | const lean = options &&
|
4414 | options.options &&
|
4415 | options.options.lean || false;
|
4416 | const len = vals.length;
|
4417 | const rawOrder = {};
|
4418 | const rawDocs = {};
|
4419 | let key;
|
4420 | let val;
|
4421 |
|
4422 | // Clone because `assignRawDocsToIdStructure` will mutate the array
|
4423 | const allIds = clone(mod.allIds);
|
4424 | // optimization:
|
4425 | // record the document positions as returned by
|
4426 | // the query result.
|
4427 | for (let i = 0; i < len; i++) {
|
4428 | val = vals[i];
|
4429 | if (val == null) {
|
4430 | continue;
|
4431 | }
|
4432 | for (const foreignField of mod.foreignField) {
|
4433 | _val = utils.getValue(foreignField, val);
|
4434 | if (Array.isArray(_val)) {
|
4435 | _val = utils.array.unique(utils.array.flatten(_val));
|
4436 |
|
4437 | for (let __val of _val) {
|
4438 | if (__val instanceof Document) {
|
4439 | __val = __val._id;
|
4440 | }
|
4441 | key = String(__val);
|
4442 | if (rawDocs[key]) {
|
4443 | if (Array.isArray(rawDocs[key])) {
|
4444 | rawDocs[key].push(val);
|
4445 | rawOrder[key].push(i);
|
4446 | } else {
|
4447 | rawDocs[key] = [rawDocs[key], val];
|
4448 | rawOrder[key] = [rawOrder[key], i];
|
4449 | }
|
4450 | } else {
|
4451 | if (isVirtual && !justOne) {
|
4452 | rawDocs[key] = [val];
|
4453 | rawOrder[key] = [i];
|
4454 | } else {
|
4455 | rawDocs[key] = val;
|
4456 | rawOrder[key] = i;
|
4457 | }
|
4458 | }
|
4459 | }
|
4460 | } else {
|
4461 | if (_val instanceof Document) {
|
4462 | _val = _val._doc._id;
|
4463 | }
|
4464 | key = String(_val);
|
4465 | if (rawDocs[key]) {
|
4466 | if (Array.isArray(rawDocs[key])) {
|
4467 | rawDocs[key].push(val);
|
4468 | rawOrder[key].push(i);
|
4469 | } else if (isVirtual ||
|
4470 | rawDocs[key].constructor !== val.constructor ||
|
4471 | (rawDocs[key] instanceof Document ? String(rawDocs[key]._doc._id) : String(rawDocs[key]._id)) !== (val instanceof Document ? String(val._doc._id) : String(val._id))) {
|
4472 | // May need to store multiple docs with the same id if there's multiple models
|
4473 | // if we have discriminators or a ref function. But avoid converting to an array
|
4474 | // if we have multiple queries on the same model because of `perDocumentLimit` re: gh-9906
|
4475 | rawDocs[key] = [rawDocs[key], val];
|
4476 | rawOrder[key] = [rawOrder[key], i];
|
4477 | }
|
4478 | } else {
|
4479 | rawDocs[key] = val;
|
4480 | rawOrder[key] = i;
|
4481 | }
|
4482 | }
|
4483 | // flag each as result of population
|
4484 | if (!lean) {
|
4485 | val.$__.wasPopulated = val.$__.wasPopulated || { value: _val };
|
4486 | }
|
4487 | }
|
4488 | }
|
4489 |
|
4490 | assignVals({
|
4491 | originalModel: model,
|
4492 | // If virtual, make sure to not mutate original field
|
4493 | rawIds: mod.isVirtual ? allIds : mod.allIds,
|
4494 | allIds: allIds,
|
4495 | unpopulatedValues: mod.unpopulatedValues,
|
4496 | foreignField: mod.foreignField,
|
4497 | rawDocs: rawDocs,
|
4498 | rawOrder: rawOrder,
|
4499 | docs: mod.docs,
|
4500 | path: options.path,
|
4501 | options: assignmentOpts,
|
4502 | justOne: mod.justOne,
|
4503 | isVirtual: mod.isVirtual,
|
4504 | allOptions: mod,
|
4505 | populatedModel: mod.model,
|
4506 | lean: lean,
|
4507 | virtual: mod.virtual,
|
4508 | count: mod.count,
|
4509 | match: mod.match
|
4510 | });
|
4511 | }
|
4512 |
|
4513 | /**
|
4514 | * Compiler utility.
|
4515 | *
|
4516 | * @param {String|Function} name model name or class extending Model
|
4517 | * @param {Schema} schema
|
4518 | * @param {String} collectionName
|
4519 | * @param {Connection} connection
|
4520 | * @param {Mongoose} base mongoose instance
|
4521 | * @api private
|
4522 | */
|
4523 |
|
4524 | Model.compile = function compile(name, schema, collectionName, connection, base) {
|
4525 | const versioningEnabled = schema.options.versionKey !== false;
|
4526 |
|
4527 | if (versioningEnabled && !schema.paths[schema.options.versionKey]) {
|
4528 | // add versioning to top level documents only
|
4529 | const o = {};
|
4530 | o[schema.options.versionKey] = Number;
|
4531 | schema.add(o);
|
4532 | }
|
4533 | let model;
|
4534 | if (typeof name === 'function' && name.prototype instanceof Model) {
|
4535 | model = name;
|
4536 | name = model.name;
|
4537 | schema.loadClass(model, false);
|
4538 | model.prototype.$isMongooseModelPrototype = true;
|
4539 | } else {
|
4540 | // generate new class
|
4541 | model = function model(doc, fields, skipId) {
|
4542 | model.hooks.execPreSync('createModel', doc);
|
4543 | if (!(this instanceof model)) {
|
4544 | return new model(doc, fields, skipId);
|
4545 | }
|
4546 | const discriminatorKey = model.schema.options.discriminatorKey;
|
4547 |
|
4548 | if (model.discriminators == null || doc == null || doc[discriminatorKey] == null) {
|
4549 | Model.call(this, doc, fields, skipId);
|
4550 | return;
|
4551 | }
|
4552 |
|
4553 | // If discriminator key is set, use the discriminator instead (gh-7586)
|
4554 | const Discriminator = model.discriminators[doc[discriminatorKey]] ||
|
4555 | getDiscriminatorByValue(model.discriminators, doc[discriminatorKey]);
|
4556 | if (Discriminator != null) {
|
4557 | return new Discriminator(doc, fields, skipId);
|
4558 | }
|
4559 |
|
4560 | // Otherwise, just use the top-level model
|
4561 | Model.call(this, doc, fields, skipId);
|
4562 | };
|
4563 | }
|
4564 |
|
4565 | model.hooks = schema.s.hooks.clone();
|
4566 | model.base = base;
|
4567 | model.modelName = name;
|
4568 |
|
4569 | if (!(model.prototype instanceof Model)) {
|
4570 | Object.setPrototypeOf(model, Model);
|
4571 | Object.setPrototypeOf(model.prototype, Model.prototype);
|
4572 | }
|
4573 | model.model = function model(name) {
|
4574 | return this.db.model(name);
|
4575 | };
|
4576 |
|
4577 | model.db = connection;
|
4578 | model.prototype.db = connection;
|
4579 | model.prototype[modelDbSymbol] = connection;
|
4580 | model.discriminators = model.prototype.discriminators = undefined;
|
4581 | model[modelSymbol] = true;
|
4582 | model.events = new EventEmitter();
|
4583 |
|
4584 | schema._preCompile();
|
4585 |
|
4586 | const _userProvidedOptions = schema._userProvidedOptions || {};
|
4587 |
|
4588 | const collectionOptions = {
|
4589 | schemaUserProvidedOptions: _userProvidedOptions,
|
4590 | capped: schema.options.capped,
|
4591 | Promise: model.base.Promise,
|
4592 | modelName: name
|
4593 | };
|
4594 | if (schema.options.autoCreate !== void 0) {
|
4595 | collectionOptions.autoCreate = schema.options.autoCreate;
|
4596 | }
|
4597 |
|
4598 | const collection = connection.collection(
|
4599 | collectionName,
|
4600 | collectionOptions
|
4601 | );
|
4602 |
|
4603 | model.prototype.collection = collection;
|
4604 | model.prototype.$collection = collection;
|
4605 | model.prototype[modelCollectionSymbol] = collection;
|
4606 |
|
4607 | model.prototype.$__setSchema(schema);
|
4608 |
|
4609 | // apply methods and statics
|
4610 | applyMethods(model, schema);
|
4611 | applyStatics(model, schema);
|
4612 | applyHooks(model, schema);
|
4613 | applyStaticHooks(model, schema.s.hooks, schema.statics);
|
4614 |
|
4615 | model.schema = model.prototype.$__schema;
|
4616 | model.collection = collection;
|
4617 | model.$__collection = collection;
|
4618 |
|
4619 | // Create custom query constructor
|
4620 | model.Query = function() {
|
4621 | Query.apply(this, arguments);
|
4622 | };
|
4623 | Object.setPrototypeOf(model.Query.prototype, Query.prototype);
|
4624 | model.Query.base = Query.base;
|
4625 | model.Query.prototype.constructor = Query;
|
4626 | model._applyQueryMiddleware();
|
4627 | applyQueryMethods(model, schema.query);
|
4628 |
|
4629 | return model;
|
4630 | };
|
4631 |
|
4632 | /**
|
4633 | * Update this model to use the new connection, including updating all internal
|
4634 | * references and creating a new `Collection` instance using the new connection.
|
4635 | * Not for external use, only used by `setDriver()` to ensure that you can still
|
4636 | * call `setDriver()` after creating a model using `mongoose.model()`.
|
4637 | *
|
4638 | * @param {Connection} newConnection the new connection to use
|
4639 | * @api private
|
4640 | */
|
4641 |
|
4642 | Model.$__updateConnection = function $__updateConnection(newConnection) {
|
4643 | this.db = newConnection;
|
4644 | this.prototype.db = newConnection;
|
4645 | this.prototype[modelDbSymbol] = newConnection;
|
4646 |
|
4647 | const collection = newConnection.collection(
|
4648 | this.collection.collectionName,
|
4649 | this.collection.opts
|
4650 | );
|
4651 |
|
4652 | this.prototype.collection = collection;
|
4653 | this.prototype.$collection = collection;
|
4654 | this.prototype[modelCollectionSymbol] = collection;
|
4655 |
|
4656 | this.collection = collection;
|
4657 | this.$__collection = collection;
|
4658 | };
|
4659 |
|
4660 | /**
|
4661 | * Register custom query methods for this model
|
4662 | *
|
4663 | * @param {Model} model
|
4664 | * @param {Schema} schema
|
4665 | * @api private
|
4666 | */
|
4667 |
|
4668 | function applyQueryMethods(model, methods) {
|
4669 | for (const i in methods) {
|
4670 | model.Query.prototype[i] = methods[i];
|
4671 | }
|
4672 | }
|
4673 |
|
4674 | /**
|
4675 | * Subclass this model with `conn`, `schema`, and `collection` settings.
|
4676 | *
|
4677 | * @param {Connection} conn
|
4678 | * @param {Schema} [schema]
|
4679 | * @param {String} [collection]
|
4680 | * @return {Model}
|
4681 | * @api private
|
4682 | * @memberOf Model
|
4683 | * @static
|
4684 | * @method __subclass
|
4685 | */
|
4686 |
|
4687 | Model.__subclass = function subclass(conn, schema, collection) {
|
4688 | // subclass model using this connection and collection name
|
4689 | const _this = this;
|
4690 |
|
4691 | const Model = function Model(doc, fields, skipId) {
|
4692 | if (!(this instanceof Model)) {
|
4693 | return new Model(doc, fields, skipId);
|
4694 | }
|
4695 | _this.call(this, doc, fields, skipId);
|
4696 | };
|
4697 |
|
4698 | Object.setPrototypeOf(Model, _this);
|
4699 | Object.setPrototypeOf(Model.prototype, _this.prototype);
|
4700 | Model.db = conn;
|
4701 | Model.prototype.db = conn;
|
4702 | Model.prototype[modelDbSymbol] = conn;
|
4703 |
|
4704 | _this[subclassedSymbol] = _this[subclassedSymbol] || [];
|
4705 | _this[subclassedSymbol].push(Model);
|
4706 | if (_this.discriminators != null) {
|
4707 | Model.discriminators = {};
|
4708 | for (const key of Object.keys(_this.discriminators)) {
|
4709 | Model.discriminators[key] = _this.discriminators[key].
|
4710 | __subclass(_this.db, _this.discriminators[key].schema, collection);
|
4711 | }
|
4712 | }
|
4713 |
|
4714 | const s = schema && typeof schema !== 'string'
|
4715 | ? schema
|
4716 | : _this.prototype.$__schema;
|
4717 |
|
4718 | const options = s.options || {};
|
4719 | const _userProvidedOptions = s._userProvidedOptions || {};
|
4720 |
|
4721 | if (!collection) {
|
4722 | collection = _this.prototype.$__schema.get('collection') ||
|
4723 | utils.toCollectionName(_this.modelName, this.base.pluralize());
|
4724 | }
|
4725 |
|
4726 | const collectionOptions = {
|
4727 | schemaUserProvidedOptions: _userProvidedOptions,
|
4728 | capped: s && options.capped
|
4729 | };
|
4730 |
|
4731 | Model.prototype.collection = conn.collection(collection, collectionOptions);
|
4732 | Model.prototype.$collection = Model.prototype.collection;
|
4733 | Model.prototype[modelCollectionSymbol] = Model.prototype.collection;
|
4734 | Model.collection = Model.prototype.collection;
|
4735 | Model.$__collection = Model.collection;
|
4736 | // Errors handled internally, so ignore
|
4737 | Model.init().catch(() => {});
|
4738 | return Model;
|
4739 | };
|
4740 |
|
4741 | /**
|
4742 | * Apply changes made to this model's schema after this model was compiled.
|
4743 | * By default, adding virtuals and other properties to a schema after the model is compiled does nothing.
|
4744 | * Call this function to apply virtuals and properties that were added later.
|
4745 | *
|
4746 | * #### Example:
|
4747 | *
|
4748 | * const schema = new mongoose.Schema({ field: String });
|
4749 | * const TestModel = mongoose.model('Test', schema);
|
4750 | * TestModel.schema.virtual('myVirtual').get(function() {
|
4751 | * return this.field + ' from myVirtual';
|
4752 | * });
|
4753 | * const doc = new TestModel({ field: 'Hello' });
|
4754 | * doc.myVirtual; // undefined
|
4755 | *
|
4756 | * TestModel.recompileSchema();
|
4757 | * doc.myVirtual; // 'Hello from myVirtual'
|
4758 | *
|
4759 | * @return {undefined}
|
4760 | * @api public
|
4761 | * @memberOf Model
|
4762 | * @static
|
4763 | * @method recompileSchema
|
4764 | */
|
4765 |
|
4766 | Model.recompileSchema = function recompileSchema() {
|
4767 | this.prototype.$__setSchema(this.schema);
|
4768 |
|
4769 | if (this.schema._applyDiscriminators != null) {
|
4770 | for (const disc of this.schema._applyDiscriminators.keys()) {
|
4771 | this.discriminator(disc, this.schema._applyDiscriminators.get(disc));
|
4772 | }
|
4773 | }
|
4774 |
|
4775 | delete this.schema._defaultToObjectOptionsMap;
|
4776 |
|
4777 | applyEmbeddedDiscriminators(this.schema, new WeakSet(), true);
|
4778 | };
|
4779 |
|
4780 | /**
|
4781 | * Helper for console.log. Given a model named 'MyModel', returns the string
|
4782 | * `'Model { MyModel }'`.
|
4783 | *
|
4784 | * #### Example:
|
4785 | *
|
4786 | * const MyModel = mongoose.model('Test', Schema({ name: String }));
|
4787 | * MyModel.inspect(); // 'Model { Test }'
|
4788 | * console.log(MyModel); // Prints 'Model { Test }'
|
4789 | *
|
4790 | * @api public
|
4791 | */
|
4792 |
|
4793 | Model.inspect = function() {
|
4794 | return `Model { ${this.modelName} }`;
|
4795 | };
|
4796 |
|
4797 | if (util.inspect.custom) {
|
4798 | // Avoid Node deprecation warning DEP0079
|
4799 | Model[util.inspect.custom] = Model.inspect;
|
4800 | }
|
4801 |
|
4802 | /*!
|
4803 | * Applies query middleware from this model's schema to this model's
|
4804 | * Query constructor.
|
4805 | */
|
4806 |
|
4807 | Model._applyQueryMiddleware = function _applyQueryMiddleware() {
|
4808 | const Query = this.Query;
|
4809 | const queryMiddleware = this.schema.s.hooks.filter(hook => {
|
4810 | const contexts = _getContexts(hook);
|
4811 | if (hook.name === 'validate') {
|
4812 | return !!contexts.query;
|
4813 | }
|
4814 | if (hook.name === 'deleteOne' || hook.name === 'updateOne') {
|
4815 | return !!contexts.query || Object.keys(contexts).length === 0;
|
4816 | }
|
4817 | if (hook.query != null || hook.document != null) {
|
4818 | return !!hook.query;
|
4819 | }
|
4820 | return true;
|
4821 | });
|
4822 |
|
4823 | Query.prototype._queryMiddleware = queryMiddleware;
|
4824 | };
|
4825 |
|
4826 | function _getContexts(hook) {
|
4827 | const ret = {};
|
4828 | if (hook.hasOwnProperty('query')) {
|
4829 | ret.query = hook.query;
|
4830 | }
|
4831 | if (hook.hasOwnProperty('document')) {
|
4832 | ret.document = hook.document;
|
4833 | }
|
4834 | return ret;
|
4835 | }
|
4836 |
|
4837 | /*!
|
4838 | * Module exports.
|
4839 | */
|
4840 |
|
4841 | module.exports = exports = Model;
|