UNPKG

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