UNPKG

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