@@ -11,7 +11,7 @@
const DivergentArrayError = require('./error').DivergentArrayError;
const Error = require('./error');
const EventEmitter = require('events').EventEmitter;
-const MongooseMap = require('./types/map');
+const MongooseBuffer = require('./types/buffer');
const OverwriteModelError = require('./error').OverwriteModelError;
const PromiseProvider = require('./promise_provider');
const Query = require('./query');
@@ -22,9 +22,10 @@
const applyQueryMiddleware = require('./helpers/query/applyQueryMiddleware');
const applyHooks = require('./helpers/model/applyHooks');
const applyMethods = require('./helpers/model/applyMethods');
+const applyStaticHooks = require('./helpers/model/applyStaticHooks');
const applyStatics = require('./helpers/model/applyStatics');
const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
-const assignRawDocsToIdStructure = require('./helpers/populate/assignRawDocsToIdStructure');
+const assignVals = require('./helpers/populate/assignVals');
const castBulkWrite = require('./helpers/model/castBulkWrite');
const discriminator = require('./helpers/model/discriminator');
const getDiscriminatorByValue = require('./queryhelpers').getDiscriminatorByValue;
@@ -37,7 +38,6 @@
const getVirtual = require('./helpers/populate/getVirtual');
const leanPopulateMap = require('./helpers/populate/leanPopulateMap');
const modifiedPaths = require('./helpers/update/modifiedPaths');
-const mpath = require('mpath');
const normalizeRefPath = require('./helpers/populate/normalizeRefPath');
const parallel = require('async/parallel');
const parallelLimit = require('async/parallelLimit');
@@ -407,22 +407,18 @@
* ####Example:
*
* product.sold = Date.now();
- * product.save(function (err, product) {
- * if (err) ..
- * })
- *
- * The callback will receive two parameters
+ * product = await product.save();
*
- * 1. `err` if an error occurred
- * 2. `product` which is the saved `product`
+ * If save is successful, the returned promise will fulfill with the document
+ * saved.
*
- * As an extra measure of flow control, save will return a Promise.
* ####Example:
- * product.save().then(function(product) {
- * ...
- * });
+ *
+ * const newProduct = await product.save();
+ * newProduct === product; // true
*
* @param {Object} [options] options optional options
+ * @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).
* @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe). Use the `w` option instead.
* @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
* @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)
@@ -430,8 +426,8 @@
* @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).
* @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)
* @param {Boolean} [options.timestamps=true] if `false` and [timestamps](./guide.html#timestamps) are enabled, skip timestamps for this `save()`.
- * @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).
* @param {Function} [fn] optional callback
+ * @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).
* @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise.
* @api public
* @see middleware http://mongoosejs.com/docs/middleware.html
@@ -695,7 +691,7 @@
} else if (value._path && value._atomics) {
// arrays and other custom types (support plugins etc)
handleAtomics(this, where, delta, data, value);
- } else if (value._path && Buffer.isBuffer(value)) {
+ } else if (value[MongooseBuffer.pathSymbol] && Buffer.isBuffer(value)) {
// MongooseBuffer
value = value.toObject();
operand(this, where, delta, data, value);
@@ -704,6 +700,7 @@
depopulate: true,
transform: false,
virtuals: false,
+ getters: false,
_isNested: true
});
operand(this, where, delta, data, value);
@@ -910,6 +907,37 @@
Model.prototype.delete = Model.prototype.remove;
+/**
+ * Removes this document from the db. Equivalent to `.remove()`.
+ *
+ * ####Example:
+ * product = await product.deleteOne();
+ * await Product.findById(product._id); // null
+ *
+ * @param {function(err,product)} [fn] optional callback
+ * @return {Promise} Promise
+ * @api public
+ */
+
+Model.prototype.deleteOne = function deleteOne(options, fn) {
+ if (typeof options === 'function') {
+ fn = options;
+ options = undefined;
+ }
+
+ if (!options) {
+ options = {};
+ }
+
+ if (fn) {
+ fn = this.constructor.$wrapCallback(fn);
+ }
+
+ return utils.promiseOrCallback(fn, cb => {
+ this.$__deleteOne(options, cb);
+ }, this.constructor.events);
+};
+
/*!
* ignore
*/
@@ -945,6 +973,12 @@
});
};
+/*!
+ * ignore
+ */
+
+Model.prototype.$__deleteOne = Model.prototype.$__remove;
+
/**
* Returns another Model instance.
*
@@ -1500,8 +1534,14 @@
if (model.baseModelName != null && indexOptions.unique &&
!('partialFilterExpression' in indexOptions) &&
!('sparse' in indexOptions)) {
+
+ const value = (
+ model.schema.discriminatorMapping &&
+ model.schema.discriminatorMapping.value
+ ) || model.modelName;
+
indexOptions.partialFilterExpression = {
- [model.schema.options.discriminatorKey]: model.modelName
+ [model.schema.options.discriminatorKey]: value
};
}
return indexOptions;
@@ -1673,13 +1713,14 @@
*
* ####Example:
*
- * Character.remove({ name: 'Eddard Stark' }, function (err) {});
+ * const res = await Character.remove({ name: 'Eddard Stark' });
+ * res.deletedCount; // Number of documents removed
*
* ####Note:
*
* This method sends a remove command directly to MongoDB, no Mongoose documents
- * are involved. Because no Mongoose documents are involved, _no middleware
- * (hooks) are executed_.
+ * are involved. Because no Mongoose documents are involved, Mongoose does
+ * not execute [document middleware](/docs/middleware.html#types-of-middleware).
*
* @param {Object} conditions
* @param {Function} [callback]
@@ -1895,7 +1936,7 @@
* @param {Function} [callback]
* @return {Query}
* @see field selection #query_Query-select
- * @see lean queries #query_Query-lean
+ * @see lean queries /docs/tutorials/lean.html
* @api public
*/
@@ -1949,7 +1990,7 @@
* @param {Function} [callback]
* @return {Query}
* @see field selection #query_Query-select
- * @see lean queries #query_Query-lean
+ * @see lean queries /docs/tutorials/lean.html
* @api public
*/
@@ -2243,7 +2284,9 @@
* @param {Object} [conditions]
* @param {Object} [update]
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @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).
+ * @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).
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @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.
* @param {Function} [callback]
* @return {Query}
* @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
@@ -2378,7 +2421,9 @@
* @param {Object|Number|String} id value of `_id` to query by
* @param {Object} [update]
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
- * @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).
+ * @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).
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @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.
* @param {Function} [callback]
* @return {Query}
* @see Model.findOneAndUpdate #model_Model.findOneAndUpdate
@@ -2464,6 +2509,7 @@
*
* @param {Object} conditions
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -2509,6 +2555,7 @@
*
* @param {Object|Number|String} id value of `_id` to query by
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
* @param {Function} [callback]
* @return {Query}
* @see Model.findOneAndRemove #model_Model.findOneAndRemove
@@ -2566,6 +2613,9 @@
*
* @param {Object} conditions
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @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).
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @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.
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -2648,6 +2698,7 @@
*
* @param {Object} conditions
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
* @param {Function} [callback]
* @return {Query}
* @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
@@ -2711,6 +2762,7 @@
*
* @param {Object|Number|String} id value of `_id` to query by
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
* @param {Function} [callback]
* @return {Query}
* @see Model.findOneAndRemove #model_Model.findOneAndRemove
@@ -2790,6 +2842,20 @@
} else {
args = utils.args(arguments);
}
+
+ if (args.length === 2 &&
+ args[0] != null &&
+ args[1] != null &&
+ args[0].session == null &&
+ last.session != null &&
+ last.session.constructor.name === 'ClientSession' &&
+ !this.schema.path('session')) {
+ // Probably means the user is running into the common mistake of trying
+ // to use a spread to specify options, see gh-7535
+ console.warn('WARNING: to pass a `session` to `Model.create()` in ' +
+ 'Mongoose, you **must** pass an array as the first argument. See: ' +
+ 'https://mongoosejs.com/docs/api.html#model_Model.create');
+ }
}
if (cb) {
@@ -3177,10 +3243,12 @@
* ####Examples:
*
* MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
- * MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, function (err, raw) {
- * if (err) return handleError(err);
- * console.log('The raw response from Mongo was ', raw);
- * });
+ *
+ * const res = await MyModel.update({ name: 'Tobi' }, { ferret: true });
+ * res.n; // Number of documents that matched `{ name: 'Tobi' }`
+ * // Number of documents that were changed. If every doc matched already
+ * // had `ferret` set to `true`, `nModified` will be 0.
+ * res.nModified;
*
* ####Valid options:
*
@@ -3206,10 +3274,10 @@
* ####Example:
*
* var query = { name: 'borne' };
- * Model.update(query, { name: 'jason bourne' }, options, callback)
+ * Model.update(query, { name: 'jason bourne' }, options, callback);
*
* // is sent as
- * Model.update(query, { $set: { name: 'jason bourne' }}, options, callback)
+ * Model.update(query, { $set: { name: 'jason bourne' }}, options, function(err, res));
* // if overwrite option is false. If overwrite is true, sent without the $set wrapper.
*
* This helps prevent accidentally overwriting all documents in your collection with `{ name: 'jason bourne' }`.
@@ -3242,6 +3310,7 @@
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
* @param {Function} [callback]
* @return {Query}
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -3257,6 +3326,11 @@
* **Note** updateMany will _not_ fire update middleware. Use `pre('updateMany')`
* and `post('updateMany')` instead.
*
+ * ####Example:
+ * const res = await Person.updateMany({ name: /Stark$/ }, { isDeleted: true });
+ * res.n; // Number of documents matched
+ * res.nModified; // Number of documents modified
+ *
* This function triggers the following middleware.
*
* - `updateMany()`
@@ -3264,8 +3338,11 @@
* @param {Object} conditions
* @param {Object} doc
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @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.
* @param {Function} [callback]
* @return {Query}
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -3280,6 +3357,11 @@
* - MongoDB will update _only_ the first document that matches `criteria` regardless of the value of the `multi` option.
* - Use `replaceOne()` if you want to overwrite an entire document rather than using atomic operators like `$set`.
*
+ * ####Example:
+ * const res = await Person.updateOne({ name: 'Jean-Luc Picard' }, { ship: 'USS Enterprise' });
+ * res.n; // Number of documents matched
+ * res.nModified; // Number of documents modified
+ *
* This function triggers the following middleware.
*
* - `updateOne()`
@@ -3287,8 +3369,11 @@
* @param {Object} conditions
* @param {Object} doc
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @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.
* @param {Function} [callback]
* @return {Query}
+ * @see MongoDB docs https://docs.mongodb.com/manual/reference/command/update/#update-command-output
* @api public
*/
@@ -3300,6 +3385,11 @@
* Same as `update()`, except MongoDB replace the existing document with the
* given document (no atomic operators like `$set`).
*
+ * ####Example:
+ * const res = await Person.replaceOne({ _id: 24601 }, { name: 'Jean Valjean' });
+ * res.n; // Number of documents matched
+ * res.nModified; // Number of documents modified
+ *
* This function triggers the following middleware.
*
* - `replaceOne()`
@@ -3307,6 +3397,8 @@
* @param {Object} conditions
* @param {Object} doc
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
+ * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict)
+ * @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.
* @param {Function} [callback]
* @return {Query}
* @api public
@@ -3384,7 +3476,7 @@
* - `{reduce: 'collectionName'}` add the results to collectionName: if dups are detected, uses the reducer / finalize functions
* - `{merge: 'collectionName'}` add the results to collectionName: if dups exist the new docs overwrite the old
*
- * If `options.out` is set to `replace`, `merge`, or `reduce`, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the `lean` option; meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).
+ * 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).
*
* ####Example:
*
@@ -3554,11 +3646,11 @@
* - `near` {Array} x,y point to search for
* - `maxDistance` {Number} the maximum distance from the point near that a result can be
* - `limit` {Number} The maximum number of results to return
- * - `lean` {Boolean} return the raw object instead of the Mongoose Model
+ * - `lean` {Object|Boolean} return the raw object instead of the Mongoose Model
*
* @param {Object} conditions an object that specifies the match condition (required)
* @param {Object} options for the geoSearch, some (near, maxDistance) are required
- * @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).
+ * @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).
* @param {Function} [callback] optional callback
* @return {Promise}
* @see http://docs.mongodb.org/manual/reference/command/geoSearch/
@@ -3689,6 +3781,7 @@
* @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.
* @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).
* @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.
+ * @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.
* @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
* @return {Promise}
* @api public
@@ -3788,15 +3881,7 @@
for (let i = 0; i < len; ++i) {
mod = modelsMap[i];
select = mod.options.select;
-
- if (mod.options.match) {
- match = utils.object.shallowCopy(mod.options.match);
- } else if (get(mod, 'options.options.match')) {
- match = utils.object.shallowCopy(mod.options.options.match);
- delete mod.options.options.match;
- } else {
- match = {};
- }
+ match = _formatMatch(mod.match);
let ids = utils.array.flatten(mod.ids, flatten);
ids = utils.array.unique(ids);
@@ -3813,10 +3898,16 @@
match[foreignField] = { $in: ids };
}
} else {
- match.$or = [];
+ const $or = [];
+ if (Array.isArray(match.$or)) {
+ match.$and = [{ $or: match.$or }, { $or: $or }];
+ delete match.$or;
+ } else {
+ match.$or = $or;
+ }
for (const foreignField of mod.foreignField) {
if (foreignField !== '_id' || !match['_id']) {
- match.$or.push({ [foreignField]: { $in: ids } });
+ $or.push({ [foreignField]: { $in: ids } });
}
}
}
@@ -3838,30 +3929,6 @@
}
}
- // If just setting count, skip everything else
- if (mod.count) {
- mod.model.countDocuments(match, function(err, count) {
- if (err != null) {
- return callback(err);
- }
-
- for (const doc of docs) {
- try {
- if (doc.$__ != null) {
- doc.set(mod.options.path, count);
- } else {
- utils.setValue(mod.options.path, count, doc);
- }
- } catch (err) {
- return callback(err);
- }
- }
-
- callback(null);
- });
- continue;
- }
-
if (mod.options.options && mod.options.options.limit) {
assignmentOpts.originalLimit = mod.options.options.limit;
mod.options.options.limit = mod.options.options.limit * ids.length;
@@ -3871,6 +3938,14 @@
const query = mod.model.find(match, select, mod.options.options);
+ // If using count, still need the `foreignField` so we can match counts
+ // to documents, otherwise we would need a separate `count()` for every doc.
+ if (mod.count) {
+ for (const foreignField of mod.foreignField) {
+ query.select(foreignField);
+ }
+ }
+
// If we're doing virtual populate and projection is inclusive and foreign
// field is not selected, automatically select it because mongoose needs it.
// If projection is exclusive and client explicitly unselected the foreign
@@ -4005,112 +4080,26 @@
isVirtual: mod.isVirtual,
allOptions: mod,
lean: lean,
- virtual: mod.virtual
+ virtual: mod.virtual,
+ count: mod.count,
+ match: mod.match
});
}
}
/*!
- * Assigns documents returned from a population query back
- * to the original document path.
+ * Format `mod.match` given that it may be an array that we need to $or if
+ * the client has multiple docs with match functions
*/
-function assignVals(o) {
- // Options that aren't explicitly listed in `populateOptions`
- const userOptions = get(o, 'allOptions.options.options');
- // `o.options` contains options explicitly listed in `populateOptions`, like
- // `match` and `limit`.
- const populateOptions = Object.assign({}, o.options, userOptions, {
- justOne: o.justOne
- });
-
- const originalIds = [].concat(o.rawIds);
-
- // replace the original ids in our intermediate _ids structure
- // with the documents found by query
- assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, populateOptions);
-
- // now update the original documents being populated using the
- // result structure that contains real documents.
- const docs = o.docs;
- const rawIds = o.rawIds;
- const options = o.options;
-
- function setValue(val) {
- return valueFilter(val, options, populateOptions);
- }
-
- for (let i = 0; i < docs.length; ++i) {
- const existingVal = utils.getValue(o.path, docs[i]);
- if (existingVal == null && !getVirtual(o.originalModel.schema, o.path)) {
- continue;
- }
-
- // If we're populating a map, the existing value will be an object, so
- // we need to transform again
- const originalSchema = o.originalModel.schema;
- let isMap = isModel(docs[i]) ?
- existingVal instanceof Map :
- utils.isPOJO(existingVal);
- // If we pass the first check, also make sure the local field's schematype
- // is map (re: gh-6460)
- isMap = isMap && get(originalSchema._getSchema(o.path), '$isSchemaMap');
- if (!o.isVirtual && isMap) {
- const _keys = existingVal instanceof Map ?
- Array.from(existingVal.keys()) :
- Object.keys(existingVal);
- rawIds[i] = rawIds[i].reduce((cur, v, i) => {
- // Avoid casting because that causes infinite recursion
- cur.$init(_keys[i], v);
- return cur;
- }, new MongooseMap({}, docs[i]));
- }
-
- if (o.isVirtual && docs[i] instanceof Model) {
- docs[i].populated(o.path, o.justOne ? originalIds[0] : originalIds, o.allOptions);
- // If virtual populate and doc is already init-ed, need to walk through
- // the actual doc to set rather than setting `_doc` directly
- mpath.set(o.path, rawIds[i], docs[i], setValue);
- continue;
- }
-
- const parts = o.path.split('.');
- let cur = docs[i];
- for (let j = 0; j < parts.length - 1; ++j) {
- if (cur[parts[j]] == null) {
- cur[parts[j]] = {};
- }
- cur = cur[parts[j]];
- // If the property in MongoDB is a primitive, we won't be able to populate
- // the nested path, so skip it. See gh-7545
- if (typeof cur !== 'object') {
- return;
- }
- }
- if (docs[i].$__) {
- docs[i].populated(o.path, o.allIds[i], o.allOptions);
- }
-
- // If lean, need to check that each individual virtual respects
- // `justOne`, because you may have a populated virtual with `justOne`
- // underneath an array. See gh-6867
- utils.setValue(o.path, rawIds[i], docs[i], function(v) {
- if (o.justOne === true && Array.isArray(v)) {
- return setValue(v[0]);
- } else if (o.justOne === false && !Array.isArray(v)) {
- return setValue([v]);
+function _formatMatch(match) {
+ if (Array.isArray(match)) {
+ if (match.length > 1) {
+ return { $or: [].concat(match.map(m => Object.assign({}, m))) };
}
- return setValue(v);
- }, false);
+ return Object.assign({}, match[0]);
}
-}
-
-/*!
- * Check if obj is a document
- */
-
-function isModel(obj) {
- return get(obj, '$__') != null;
+ return Object.assign({}, match);
}
function getModelsMapForPopulate(model, docs, options) {
@@ -4266,6 +4255,15 @@
const id = String(utils.getValue(foreignField, doc));
options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
+ let match = get(options, 'match', null) ||
+ get(currentOptions, 'match', null) ||
+ get(options, 'virtual.options.options.match', null);
+
+ const hasMatchFunction = typeof match === 'function';
+ if (hasMatchFunction) {
+ match = match.call(doc, doc);
+ }
+
let k = modelNames.length;
while (k--) {
modelName = modelNames[k];
@@ -4304,6 +4302,7 @@
available[modelName] = {
model: Model,
options: currentOptions,
+ match: hasMatchFunction ? [match] : match,
docs: [doc],
ids: [ids],
allIds: [ret],
@@ -4321,6 +4320,9 @@
available[modelName].docs.push(doc);
available[modelName].ids.push(ids);
available[modelName].allIds.push(ret);
+ if (hasMatchFunction) {
+ available[modelName].match.push(match);
+ }
}
}
}
@@ -4464,98 +4466,6 @@
}
/*!
- * 1) Apply backwards compatible find/findOne behavior to sub documents
- *
- * find logic:
- * a) filter out non-documents
- * b) remove _id from sub docs when user specified
- *
- * findOne
- * a) if no doc found, set to null
- * b) remove _id from sub docs when user specified
- *
- * 2) Remove _ids when specified by users query.
- *
- * background:
- * _ids are left in the query even when user excludes them so
- * that population mapping can occur.
- */
-
-function valueFilter(val, assignmentOpts, populateOptions) {
- if (Array.isArray(val)) {
- // find logic
- const ret = [];
- const numValues = val.length;
- for (let i = 0; i < numValues; ++i) {
- const subdoc = val[i];
- if (!isPopulatedObject(subdoc) && (!populateOptions.retainNullValues || subdoc != null)) {
- continue;
- }
- maybeRemoveId(subdoc, assignmentOpts);
- ret.push(subdoc);
- if (assignmentOpts.originalLimit &&
- ret.length >= assignmentOpts.originalLimit) {
- break;
- }
- }
-
- // Since we don't want to have to create a new mongoosearray, make sure to
- // modify the array in place
- while (val.length > ret.length) {
- Array.prototype.pop.apply(val, []);
- }
- for (let i = 0; i < ret.length; ++i) {
- val[i] = ret[i];
- }
- return val;
- }
-
- // findOne
- if (isPopulatedObject(val)) {
- maybeRemoveId(val, assignmentOpts);
- return val;
- }
-
- if (populateOptions.justOne === true) {
- return (val == null ? val : null);
- }
- if (populateOptions.justOne === false) {
- return [];
- }
- return val;
-}
-
-/*!
- * Remove _id from `subdoc` if user specified "lean" query option
- */
-
-function maybeRemoveId(subdoc, assignmentOpts) {
- if (assignmentOpts.excludeId) {
- if (typeof subdoc.setValue === 'function') {
- delete subdoc._doc._id;
- } else {
- delete subdoc._id;
- }
- }
-}
-
-/*!
- * Determine if `obj` is something we can set a populated path to. Can be a
- * document, a lean document, or an array/map that contains docs.
- */
-
-function isPopulatedObject(obj) {
- if (obj == null) {
- return false;
- }
-
- return Array.isArray(obj) ||
- obj.$isMongooseMap ||
- obj.$__ != null ||
- leanPopulateMap.has(obj);
-}
-
-/*!
* Compiler utility.
*
* @param {String|Function} name model name or class extending Model
@@ -4590,6 +4500,17 @@
if (!(this instanceof model)) {
return new model(doc, fields, skipId);
}
+ const discriminatorKey = model.schema.options.discriminatorKey;
+
+ // If discriminator key is set, use the discriminator instead (gh-7586)
+ if (model.discriminators != null &&
+ doc != null &&
+ doc[discriminatorKey] != null &&
+ model.discriminators[doc[discriminatorKey]] != null) {
+ return new model.discriminators[doc[discriminatorKey]](doc, fields, skipId);
+ }
+
+ // Otherwise, just use the top-level model
Model.call(this, doc, fields, skipId);
};
}
@@ -4642,6 +4563,7 @@
applyMethods(model, schema);
applyStatics(model, schema);
applyHooks(model, schema);
+ applyStaticHooks(model, schema.s.hooks, schema.statics);
model.schema = model.prototype.schema;
model.collection = model.prototype.collection;
@@ -4655,13 +4577,6 @@
applyQueryMiddleware(model.Query, model);
applyQueryMethods(model, schema.query);
- const kareemOptions = {
- useErrorHandlers: true,
- numCallbackParams: 1
- };
- model.$__insertMany = model.hooks.createWrapper('insertMany',
- model.$__insertMany, model, kareemOptions);
-
return model;
};