UNPKG

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