UNPKG

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