UNPKG

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