mongoose 5.4.23 → 5.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@ module.exports = applyHooks;
14
14
  */
15
15
 
16
16
  applyHooks.middlewareFunctions = [
17
+ 'deleteOne',
17
18
  'save',
18
19
  'validate',
19
20
  'remove',
@@ -71,7 +72,7 @@ function applyHooks(model, schema, options) {
71
72
 
72
73
  const middleware = schema.s.hooks.
73
74
  filter(hook => {
74
- if (hook.name === 'updateOne') {
75
+ if (hook.name === 'updateOne' || hook.name === 'deleteOne') {
75
76
  return !!hook['document'];
76
77
  }
77
78
  if (hook.name === 'remove') {
@@ -96,6 +97,8 @@ function applyHooks(model, schema, options) {
96
97
  createWrapper('validate', objToDecorate.$__validate, null, kareemOptions);
97
98
  objToDecorate.$__remove = middleware.
98
99
  createWrapper('remove', objToDecorate.$__remove, null, kareemOptions);
100
+ objToDecorate.$__deleteOne = middleware.
101
+ createWrapper('deleteOne', objToDecorate.$__deleteOne, null, kareemOptions);
99
102
  objToDecorate.$__init = middleware.
100
103
  createWrapperSync('init', objToDecorate.$__init, null, kareemOptions);
101
104
 
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ const utils = require('../../utils');
4
+
5
+ module.exports = function applyStaticHooks(model, hooks, statics) {
6
+ const kareemOptions = {
7
+ useErrorHandlers: true,
8
+ numCallbackParams: 1
9
+ };
10
+
11
+ hooks = hooks.filter(hook => hook.model !== false);
12
+
13
+ model.$__insertMany = hooks.createWrapper('insertMany',
14
+ model.$__insertMany, model, kareemOptions);
15
+
16
+ for (const key of Object.keys(statics)) {
17
+ if (hooks.hasHooks(key)) {
18
+ const original = model[key];
19
+
20
+ model[key] = function() {
21
+ const numArgs = arguments.length;
22
+ const lastArg = numArgs > 0 ? arguments[numArgs - 1] : null;
23
+ const cb = typeof lastArg === 'function' ? lastArg : null;
24
+ const args = Array.prototype.slice.
25
+ call(arguments, 0, cb == null ? numArgs : numArgs - 1);
26
+ // Special case: can't use `Kareem#wrap()` because it doesn't currently
27
+ // support wrapped functions that return a promise.
28
+ return utils.promiseOrCallback(cb, callback => {
29
+ hooks.execPre(key, model, args, function(err) {
30
+ if (err != null) {
31
+ return callback(err);
32
+ }
33
+
34
+ let postCalled = 0;
35
+ const ret = original.apply(model, args.concat(post));
36
+ if (ret != null && typeof ret.then === 'function') {
37
+ ret.then(res => post(null, res), err => post(err));
38
+ }
39
+
40
+ function post(error, res) {
41
+ if (postCalled++ > 0) {
42
+ return;
43
+ }
44
+
45
+ if (error != null) {
46
+ return callback(error);
47
+ }
48
+
49
+ hooks.execPost(key, model, [res], function(error) {
50
+ if (error != null) {
51
+ return callback(error);
52
+ }
53
+ callback(null, res);
54
+ });
55
+ }
56
+ });
57
+ }, model.events);
58
+ };
59
+ }
60
+ }
61
+ };
@@ -6,6 +6,7 @@ const get = require('../get');
6
6
  const getVirtual = require('./getVirtual');
7
7
  const leanPopulateMap = require('./leanPopulateMap');
8
8
  const mpath = require('mpath');
9
+ const sift = require('sift').default;
9
10
  const utils = require('../../utils');
10
11
 
11
12
  module.exports = function assignVals(o) {
@@ -48,7 +49,16 @@ module.exports = function assignVals(o) {
48
49
  continue;
49
50
  }
50
51
 
51
- let valueToSet = count ? numDocs(rawIds[i]) : rawIds[i];
52
+ let valueToSet;
53
+ if (count) {
54
+ valueToSet = numDocs(rawIds[i]);
55
+ } else if (Array.isArray(o.match)) {
56
+ valueToSet = Array.isArray(rawIds[i]) ?
57
+ sift(o.match[i], rawIds[i]) :
58
+ sift(o.match[i], [rawIds[i]])[0];
59
+ } else {
60
+ valueToSet = rawIds[i];
61
+ }
52
62
 
53
63
  // If we're populating a map, the existing value will be an object, so
54
64
  // we need to transform again
@@ -15,6 +15,7 @@ applyQueryMiddleware.middlewareFunctions = [
15
15
  'countDocuments',
16
16
  'deleteMany',
17
17
  'deleteOne',
18
+ 'distinct',
18
19
  'estimatedDocumentCount',
19
20
  'find',
20
21
  'findOne',
@@ -44,7 +45,7 @@ function applyQueryMiddleware(Query, model) {
44
45
  };
45
46
 
46
47
  const middleware = model.hooks.filter(hook => {
47
- if (hook.name === 'updateOne') {
48
+ if (hook.name === 'updateOne' || hook.name === 'deleteOne') {
48
49
  return hook.query == null || !!hook.query;
49
50
  }
50
51
  if (hook.name === 'remove') {
@@ -56,9 +57,12 @@ function applyQueryMiddleware(Query, model) {
56
57
  // `update()` thunk has a different name because `_update` was already taken
57
58
  Query.prototype._execUpdate = middleware.createWrapper('update',
58
59
  Query.prototype._execUpdate, null, kareemOptions);
60
+ // `distinct()` thunk has a different name because `_distinct` was already taken
61
+ Query.prototype.__distinct = middleware.createWrapper('distinct',
62
+ Query.prototype.__distinct, null, kareemOptions);
59
63
 
60
64
  applyQueryMiddleware.middlewareFunctions.
61
- filter(v => v !== 'update').
65
+ filter(v => v !== 'update' && v !== 'distinct').
62
66
  forEach(fn => {
63
67
  Query.prototype[`_${fn}`] = middleware.createWrapper(fn,
64
68
  Query.prototype[`_${fn}`], null, kareemOptions);
@@ -392,8 +392,6 @@ function castUpdateVal(schema, val, op, $conditional, context, path) {
392
392
  tmp = tmp[0];
393
393
  }
394
394
  return tmp;
395
- } else if (cond && op === '$set') {
396
- return schema.cast(val);
397
395
  }
398
396
 
399
397
  if (op in noCastOps) {
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ module.exports = function applyPlugins(schema, plugins, options, cacheKey) {
4
+ if (schema[cacheKey]) {
5
+ return;
6
+ }
7
+ schema[cacheKey] = true;
8
+
9
+ if (!options || !options.skipTopLevel) {
10
+ for (let i = 0; i < plugins.length; ++i) {
11
+ schema.plugin(plugins[i][0], plugins[i][1]);
12
+ }
13
+ }
14
+
15
+ options = Object.assign({}, options);
16
+ delete options.skipTopLevel;
17
+
18
+ for (const path of Object.keys(schema.paths)) {
19
+ const type = schema.paths[path];
20
+ if (type.schema != null) {
21
+ applyPlugins(type.schema, plugins, options, cacheKey);
22
+
23
+ // Recompile schema because plugins may have changed it, see gh-7572
24
+ type.caster.prototype.$__setSchema(type.schema);
25
+ }
26
+ }
27
+
28
+ const discriminators = schema.discriminators;
29
+ if (discriminators == null) {
30
+ return;
31
+ }
32
+
33
+ const applyPluginsToDiscriminators = options.applyPluginsToDiscriminators;
34
+
35
+ const keys = Object.keys(discriminators);
36
+ for (let i = 0; i < keys.length; ++i) {
37
+ const discriminatorKey = keys[i];
38
+ const discriminatorSchema = discriminators[discriminatorKey];
39
+
40
+ applyPlugins(discriminatorSchema, plugins,
41
+ { skipTopLevel: !applyPluginsToDiscriminators }, cacheKey);
42
+ }
43
+ };
@@ -1,13 +1,9 @@
1
1
  'use strict';
2
2
 
3
- exports.validatorErrorSymbol = Symbol.for('mongoose:validatorError');
4
-
3
+ exports.arrayParentSymbol = Symbol('mongoose#Array#_parent');
5
4
  exports.documentArrayParent = Symbol.for('mongoose:documentArrayParent');
6
-
7
- exports.modelSymbol = Symbol.for('mongoose#Model');
8
-
9
5
  exports.getSymbol = Symbol.for('mongoose#Document#get');
10
-
6
+ exports.modelSymbol = Symbol.for('mongoose#Model');
11
7
  exports.objectIdSymbol = Symbol.for('mongoose#ObjectId');
12
-
13
- exports.schemaTypeSymbol = Symbol.for('mongoose#schemaType');
8
+ exports.schemaTypeSymbol = Symbol.for('mongoose#schemaType');
9
+ exports.validatorErrorSymbol = Symbol.for('mongoose:validatorError');
package/lib/index.js CHANGED
@@ -5,7 +5,12 @@
5
5
  */
6
6
 
7
7
  if (global.MONGOOSE_DRIVER_PATH) {
8
- require('./driver').set(require(global.MONGOOSE_DRIVER_PATH));
8
+ const deprecationWarning = 'The `MONGOOSE_DRIVER_PATH` global property is ' +
9
+ 'deprecated. Use `mongoose.driver.set()` instead.';
10
+ const setDriver = require('util').deprecate(function() {
11
+ require('./driver').set(require(global.MONGOOSE_DRIVER_PATH));
12
+ }, deprecationWarning);
13
+ setDriver();
9
14
  } else {
10
15
  require('./driver').set(require('./drivers/node-mongodb-native'));
11
16
  }
@@ -19,6 +24,7 @@ const Types = require('./types');
19
24
  const Query = require('./query');
20
25
  const Model = require('./model');
21
26
  const Document = require('./document');
27
+ const applyPlugins = require('./helpers/schema/applyPlugins');
22
28
  const get = require('./helpers/get');
23
29
  const legacyPluralize = require('mongoose-legacy-pluralize');
24
30
  const utils = require('./utils');
@@ -103,6 +109,18 @@ function Mongoose(options) {
103
109
  */
104
110
  Mongoose.prototype.STATES = STATES;
105
111
 
112
+ /**
113
+ * The underlying driver this Mongoose instance uses to communicate with
114
+ * the database. A driver is a Mongoose-specific interface that defines functions
115
+ * like `find()`.
116
+ *
117
+ * @memberOf Mongoose
118
+ * @property driver
119
+ * @api public
120
+ */
121
+
122
+ Mongoose.prototype.driver = require('./driver');
123
+
106
124
  /**
107
125
  * Sets mongoose options
108
126
  *
@@ -553,42 +571,10 @@ Mongoose.prototype.modelNames = function() {
553
571
  */
554
572
 
555
573
  Mongoose.prototype._applyPlugins = function(schema, options) {
556
- if (schema.$globalPluginsApplied) {
557
- return;
558
- }
559
- schema.$globalPluginsApplied = true;
560
-
561
- if (!options || !options.skipTopLevel) {
562
- for (let i = 0; i < this.plugins.length; ++i) {
563
- schema.plugin(this.plugins[i][0], this.plugins[i][1]);
564
- }
565
- }
566
-
567
- for (const path of Object.keys(schema.paths)) {
568
- const type = schema.paths[path];
569
- if (type.schema != null) {
570
- this._applyPlugins(type.schema);
571
-
572
- // Recompile schema because plugins may have changed it, see gh-7572
573
- type.caster.prototype.$__setSchema(type.schema);
574
- }
575
- }
576
-
577
- const discriminators = schema.discriminators;
578
- if (discriminators == null) {
579
- return;
580
- }
581
-
582
- const applyPluginsToDiscriminators = get(this,
574
+ options = options || {};
575
+ options.applyPluginsToDiscriminators = get(this,
583
576
  'options.applyPluginsToDiscriminators', false);
584
-
585
- const keys = Object.keys(discriminators);
586
- for (let i = 0; i < keys.length; ++i) {
587
- const discriminatorKey = keys[i];
588
- const discriminatorSchema = discriminators[discriminatorKey];
589
-
590
- this._applyPlugins(discriminatorSchema, { skipTopLevel: !applyPluginsToDiscriminators });
591
- }
577
+ applyPlugins(schema, this.plugins, options, '$globalPluginsApplied');
592
578
  };
593
579
 
594
580
  /**
package/lib/internal.js CHANGED
@@ -30,6 +30,7 @@ function InternalCache() {
30
30
  this.pathsToScopes = {};
31
31
  this.cachedRequired = {};
32
32
  this.session = null;
33
+ this.$setCalled = new Set();
33
34
 
34
35
  // embedded docs
35
36
  this.ownerDocument = undefined;
package/lib/model.js CHANGED
@@ -11,6 +11,7 @@ const DocumentNotFoundError = require('./error').DocumentNotFoundError;
11
11
  const DivergentArrayError = require('./error').DivergentArrayError;
12
12
  const Error = require('./error');
13
13
  const EventEmitter = require('events').EventEmitter;
14
+ const MongooseBuffer = require('./types/buffer');
14
15
  const OverwriteModelError = require('./error').OverwriteModelError;
15
16
  const PromiseProvider = require('./promise_provider');
16
17
  const Query = require('./query');
@@ -21,6 +22,7 @@ const ParallelSaveError = require('./error').ParallelSaveError;
21
22
  const applyQueryMiddleware = require('./helpers/query/applyQueryMiddleware');
22
23
  const applyHooks = require('./helpers/model/applyHooks');
23
24
  const applyMethods = require('./helpers/model/applyMethods');
25
+ const applyStaticHooks = require('./helpers/model/applyStaticHooks');
24
26
  const applyStatics = require('./helpers/model/applyStatics');
25
27
  const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
26
28
  const assignVals = require('./helpers/populate/assignVals');
@@ -689,7 +691,7 @@ Model.prototype.$__delta = function() {
689
691
  } else if (value._path && value._atomics) {
690
692
  // arrays and other custom types (support plugins etc)
691
693
  handleAtomics(this, where, delta, data, value);
692
- } else if (value._path && Buffer.isBuffer(value)) {
694
+ } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) {
693
695
  // MongooseBuffer
694
696
  value = value.toObject();
695
697
  operand(this, where, delta, data, value);
@@ -905,6 +907,37 @@ Model.prototype.remove = function remove(options, fn) {
905
907
 
906
908
  Model.prototype.delete = Model.prototype.remove;
907
909
 
910
+ /**
911
+ * Removes this document from the db. Equivalent to `.remove()`.
912
+ *
913
+ * ####Example:
914
+ * product = await product.deleteOne();
915
+ * await Product.findById(product._id); // null
916
+ *
917
+ * @param {function(err,product)} [fn] optional callback
918
+ * @return {Promise} Promise
919
+ * @api public
920
+ */
921
+
922
+ Model.prototype.deleteOne = function deleteOne(options, fn) {
923
+ if (typeof options === 'function') {
924
+ fn = options;
925
+ options = undefined;
926
+ }
927
+
928
+ if (!options) {
929
+ options = {};
930
+ }
931
+
932
+ if (fn) {
933
+ fn = this.constructor.$wrapCallback(fn);
934
+ }
935
+
936
+ return utils.promiseOrCallback(fn, cb => {
937
+ this.$__deleteOne(options, cb);
938
+ }, this.constructor.events);
939
+ };
940
+
908
941
  /*!
909
942
  * ignore
910
943
  */
@@ -940,6 +973,12 @@ Model.prototype.$__remove = function $__remove(options, cb) {
940
973
  });
941
974
  };
942
975
 
976
+ /*!
977
+ * ignore
978
+ */
979
+
980
+ Model.prototype.$__deleteOne = Model.prototype.$__remove;
981
+
943
982
  /**
944
983
  * Returns another Model instance.
945
984
  *
@@ -1081,14 +1120,6 @@ Model.init = function init(callback) {
1081
1120
  return this.$init;
1082
1121
  }
1083
1122
 
1084
- // If `dropDatabase()` is called, this model's collection will not be
1085
- // init-ed. It is sufficiently common to call `dropDatabase()` after
1086
- // `mongoose.connect()` but before creating models that we want to
1087
- // support this. See gh-6967
1088
- this.db.$internalEmitter.once('dropDatabase', () => {
1089
- delete this.$init;
1090
- });
1091
-
1092
1123
  const Promise = PromiseProvider.get();
1093
1124
  const autoIndex = this.schema.options.autoIndex == null ?
1094
1125
  this.db.config.autoIndex :
@@ -2803,6 +2834,20 @@ Model.create = function create(doc, options, callback) {
2803
2834
  } else {
2804
2835
  args = utils.args(arguments);
2805
2836
  }
2837
+
2838
+ if (args.length === 2 &&
2839
+ args[0] != null &&
2840
+ args[1] != null &&
2841
+ args[0].session == null &&
2842
+ last.session != null &&
2843
+ last.session.constructor.name === 'ClientSession' &&
2844
+ !this.schema.path('session')) {
2845
+ // Probably means the user is running into the common mistake of trying
2846
+ // to use a spread to specify options, see gh-7535
2847
+ console.warn('WARNING: to pass a `session` to `Model.create()` in ' +
2848
+ 'Mongoose, you **must** pass an array as the first argument. See: ' +
2849
+ 'https://mongoosejs.com/docs/api.html#model_Model.create');
2850
+ }
2806
2851
  }
2807
2852
 
2808
2853
  if (cb) {
@@ -2844,6 +2889,11 @@ Model.create = function create(doc, options, callback) {
2844
2889
  }
2845
2890
  }
2846
2891
 
2892
+ // Make sure session is available in middleware
2893
+ if (options.session != null) {
2894
+ toSave.$session(options.session);
2895
+ }
2896
+
2847
2897
  toSave.save(options, callbackWrapper);
2848
2898
  });
2849
2899
  });
@@ -3118,7 +3168,32 @@ Model.$__insertMany = function(arr, options, callback) {
3118
3168
  * console.log(res.insertedCount, res.modifiedCount, res.deletedCount);
3119
3169
  * });
3120
3170
  *
3171
+ * The [supported operations](https://docs.mongodb.com/manual/reference/method/db.collection.bulkWrite/#db.collection.bulkWrite) are:
3172
+ *
3173
+ * - `insertOne`
3174
+ * - `updateOne`
3175
+ * - `updateMany`
3176
+ * - `deleteOne`
3177
+ * - `deleteMany`
3178
+ * - `replaceOne`
3179
+ *
3121
3180
  * @param {Array} ops
3181
+ * @param {Object} [ops.insertOne.document] The document to insert
3182
+ * @param {Object} [opts.updateOne.filter] Update the first document that matches this filter
3183
+ * @param {Object} [opts.updateOne.update] An object containing [update operators](https://docs.mongodb.com/manual/reference/operator/update/)
3184
+ * @param {Boolean} [opts.updateOne.upsert=false] If true, insert a doc if none match
3185
+ * @param {Object} [opts.updateOne.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
3186
+ * @param {Array} [opts.updateOne.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
3187
+ * @param {Object} [opts.updateMany.filter] Update all the documents that match this filter
3188
+ * @param {Object} [opts.updateMany.update] An object containing [update operators](https://docs.mongodb.com/manual/reference/operator/update/)
3189
+ * @param {Boolean} [opts.updateMany.upsert=false] If true, insert a doc if no documents match `filter`
3190
+ * @param {Object} [opts.updateMany.collation] The [MongoDB collation](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-34-collations) to use
3191
+ * @param {Array} [opts.updateMany.arrayFilters] The [array filters](https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters.html) used in `update`
3192
+ * @param {Object} [opts.deleteOne.filter] Delete the first document that matches this filter
3193
+ * @param {Object} [opts.deleteMany.filter] Delete all documents that match this filter
3194
+ * @param {Object} [opts.replaceOne.filter] Replace the first document that matches this filter
3195
+ * @param {Object} [opts.replaceOne.replacement] The replacement document
3196
+ * @param {Boolean} [opts.replaceOne.upsert=false] If true, insert a doc if no documents match `filter`
3122
3197
  * @param {Object} [options]
3123
3198
  * @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.
3124
3199
  * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](/docs/transactions.html).
@@ -3255,9 +3330,17 @@ Model.hydrate = function(obj) {
3255
3330
  * @param {Object} conditions
3256
3331
  * @param {Object} doc
3257
3332
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
3333
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
3334
+ * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3335
+ * @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)
3336
+ * @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.
3337
+ * @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.
3338
+ * @param {Function} [callback] params are (error, writeOpResult)
3258
3339
  * @param {Function} [callback]
3259
3340
  * @return {Query}
3260
3341
  * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
3342
+ * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
3343
+ * @see Query docs https://mongoosejs.com/docs/queries.html
3261
3344
  * @api public
3262
3345
  */
3263
3346
 
@@ -3286,10 +3369,15 @@ Model.update = function update(conditions, doc, options, callback) {
3286
3369
  * @param {Object} doc
3287
3370
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
3288
3371
  * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
3372
+ * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3373
+ * @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)
3289
3374
  * @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.
3290
- * @param {Function} [callback]
3375
+ * @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.
3376
+ * @param {Function} [callback] `function(error, res) {}` where `res` has 3 properties: `n`, `nModified`, `ok`.
3291
3377
  * @return {Query}
3378
+ * @see Query docs https://mongoosejs.com/docs/queries.html
3292
3379
  * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
3380
+ * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
3293
3381
  * @api public
3294
3382
  */
3295
3383
 
@@ -3317,10 +3405,15 @@ Model.updateMany = function updateMany(conditions, doc, options, callback) {
3317
3405
  * @param {Object} doc
3318
3406
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
3319
3407
  * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
3408
+ * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3409
+ * @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)
3320
3410
  * @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.
3321
- * @param {Function} [callback]
3411
+ * @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.
3412
+ * @param {Function} [callback] params are (error, writeOpResult)
3322
3413
  * @return {Query}
3414
+ * @see Query docs https://mongoosejs.com/docs/queries.html
3323
3415
  * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
3416
+ * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
3324
3417
  * @api public
3325
3418
  */
3326
3419
 
@@ -3345,8 +3438,14 @@ Model.updateOne = function updateOne(conditions, doc, options, callback) {
3345
3438
  * @param {Object} doc
3346
3439
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
3347
3440
  * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
3441
+ * @param {Boolean} [options.upsert=false] if true, and no documents found, insert a new document
3442
+ * @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)
3348
3443
  * @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.
3349
- * @param {Function} [callback]
3444
+ * @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.
3445
+ * @param {Function} [callback] `function(error, res) {}` where `res` has 3 properties: `n`, `nModified`, `ok`.
3446
+ * @return {Query}
3447
+ * @see Query docs https://mongoosejs.com/docs/queries.html
3448
+ * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult
3350
3449
  * @return {Query}
3351
3450
  * @api public
3352
3451
  */
@@ -3728,6 +3827,7 @@ Model.geoSearch = function(conditions, options, callback) {
3728
3827
  * @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.
3729
3828
  * @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).
3730
3829
  * @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.
3830
+ * @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.
3731
3831
  * @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
3732
3832
  * @return {Promise}
3733
3833
  * @api public
@@ -3827,15 +3927,7 @@ function populate(model, docs, options, callback) {
3827
3927
  for (let i = 0; i < len; ++i) {
3828
3928
  mod = modelsMap[i];
3829
3929
  select = mod.options.select;
3830
-
3831
- if (mod.options.match) {
3832
- match = utils.object.shallowCopy(mod.options.match);
3833
- } else if (get(mod, 'options.options.match')) {
3834
- match = utils.object.shallowCopy(mod.options.options.match);
3835
- delete mod.options.options.match;
3836
- } else {
3837
- match = {};
3838
- }
3930
+ match = _formatMatch(mod.match);
3839
3931
 
3840
3932
  let ids = utils.array.flatten(mod.ids, flatten);
3841
3933
  ids = utils.array.unique(ids);
@@ -3852,10 +3944,16 @@ function populate(model, docs, options, callback) {
3852
3944
  match[foreignField] = { $in: ids };
3853
3945
  }
3854
3946
  } else {
3855
- match.$or = [];
3947
+ const $or = [];
3948
+ if (Array.isArray(match.$or)) {
3949
+ match.$and = [{ $or: match.$or }, { $or: $or }];
3950
+ delete match.$or;
3951
+ } else {
3952
+ match.$or = $or;
3953
+ }
3856
3954
  for (const foreignField of mod.foreignField) {
3857
3955
  if (foreignField !== '_id' || !match['_id']) {
3858
- match.$or.push({ [foreignField]: { $in: ids } });
3956
+ $or.push({ [foreignField]: { $in: ids } });
3859
3957
  }
3860
3958
  }
3861
3959
  }
@@ -4029,11 +4127,27 @@ function populate(model, docs, options, callback) {
4029
4127
  allOptions: mod,
4030
4128
  lean: lean,
4031
4129
  virtual: mod.virtual,
4032
- count: mod.count
4130
+ count: mod.count,
4131
+ match: mod.match
4033
4132
  });
4034
4133
  }
4035
4134
  }
4036
4135
 
4136
+ /*!
4137
+ * Format `mod.match` given that it may be an array that we need to $or if
4138
+ * the client has multiple docs with match functions
4139
+ */
4140
+
4141
+ function _formatMatch(match) {
4142
+ if (Array.isArray(match)) {
4143
+ if (match.length > 1) {
4144
+ return { $or: [].concat(match.map(m => Object.assign({}, m))) };
4145
+ }
4146
+ return Object.assign({}, match[0]);
4147
+ }
4148
+ return Object.assign({}, match);
4149
+ }
4150
+
4037
4151
  function getModelsMapForPopulate(model, docs, options) {
4038
4152
  let i;
4039
4153
  let doc;
@@ -4187,6 +4301,15 @@ function getModelsMapForPopulate(model, docs, options) {
4187
4301
  const id = String(utils.getValue(foreignField, doc));
4188
4302
  options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
4189
4303
 
4304
+ let match = get(options, 'match', null) ||
4305
+ get(currentOptions, 'match', null) ||
4306
+ get(options, 'virtual.options.options.match', null);
4307
+
4308
+ const hasMatchFunction = typeof match === 'function';
4309
+ if (hasMatchFunction) {
4310
+ match = match.call(doc, doc);
4311
+ }
4312
+
4190
4313
  let k = modelNames.length;
4191
4314
  while (k--) {
4192
4315
  modelName = modelNames[k];
@@ -4225,6 +4348,7 @@ function getModelsMapForPopulate(model, docs, options) {
4225
4348
  available[modelName] = {
4226
4349
  model: Model,
4227
4350
  options: currentOptions,
4351
+ match: hasMatchFunction ? [match] : match,
4228
4352
  docs: [doc],
4229
4353
  ids: [ids],
4230
4354
  allIds: [ret],
@@ -4242,6 +4366,9 @@ function getModelsMapForPopulate(model, docs, options) {
4242
4366
  available[modelName].docs.push(doc);
4243
4367
  available[modelName].ids.push(ids);
4244
4368
  available[modelName].allIds.push(ret);
4369
+ if (hasMatchFunction) {
4370
+ available[modelName].match.push(match);
4371
+ }
4245
4372
  }
4246
4373
  }
4247
4374
  }
@@ -4360,7 +4487,7 @@ function convertTo_id(val) {
4360
4487
  }
4361
4488
  }
4362
4489
  if (val.isMongooseArray && val._schema) {
4363
- return val._schema.cast(val, val._parent);
4490
+ return val._schema.cast(val, val.$parent());
4364
4491
  }
4365
4492
 
4366
4493
  return [].concat(val);
@@ -4469,7 +4596,8 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
4469
4596
 
4470
4597
  const collectionOptions = {
4471
4598
  bufferCommands: bufferCommands,
4472
- capped: schema.options.capped
4599
+ capped: schema.options.capped,
4600
+ Promise: model.base.Promise
4473
4601
  };
4474
4602
 
4475
4603
  model.prototype.collection = connection.collection(
@@ -4482,6 +4610,7 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
4482
4610
  applyMethods(model, schema);
4483
4611
  applyStatics(model, schema);
4484
4612
  applyHooks(model, schema);
4613
+ applyStaticHooks(model, schema.s.hooks, schema.statics);
4485
4614
 
4486
4615
  model.schema = model.prototype.schema;
4487
4616
  model.collection = model.prototype.collection;
@@ -4495,13 +4624,6 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
4495
4624
  applyQueryMiddleware(model.Query, model);
4496
4625
  applyQueryMethods(model, schema.query);
4497
4626
 
4498
- const kareemOptions = {
4499
- useErrorHandlers: true,
4500
- numCallbackParams: 1
4501
- };
4502
- model.$__insertMany = model.hooks.createWrapper('insertMany',
4503
- model.$__insertMany, model, kareemOptions);
4504
-
4505
4627
  return model;
4506
4628
  };
4507
4629