UNPKG

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