UNPKG

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