mongoose
Version:
Mongoose MongoDB ODM
1,649 lines (1,529 loc) • 174 kB
JavaScript
'use strict';
/*!
* Module dependencies.
*/
const CastError = require('./error/cast');
const DocumentNotFoundError = require('./error/notFound');
const Kareem = require('kareem');
const MongooseError = require('./error/mongooseError');
const ObjectParameterError = require('./error/objectParameter');
const QueryCursor = require('./cursor/queryCursor');
const ValidationError = require('./error/validation');
const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/applyGlobalOption');
const handleReadPreferenceAliases = require('./helpers/query/handleReadPreferenceAliases');
const applyReadConcern = require('./helpers/schema/applyReadConcern');
const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
const cast = require('./cast');
const castArrayFilters = require('./helpers/update/castArrayFilters');
const castNumber = require('./cast/number');
const castUpdate = require('./helpers/query/castUpdate');
const clone = require('./helpers/clone');
const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue');
const helpers = require('./queryHelpers');
const internalToObjectOptions = require('./options').internalToObjectOptions;
const isExclusive = require('./helpers/projection/isExclusive');
const isInclusive = require('./helpers/projection/isInclusive');
const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
const isSubpath = require('./helpers/projection/isSubpath');
const mpath = require('mpath');
const mquery = require('mquery');
const parseProjection = require('./helpers/projection/parseProjection');
const removeUnusedArrayFilters = require('./helpers/update/removeUnusedArrayFilters');
const sanitizeFilter = require('./helpers/query/sanitizeFilter');
const sanitizeProjection = require('./helpers/query/sanitizeProjection');
const selectPopulatedFields = require('./helpers/query/selectPopulatedFields');
const setDefaultsOnInsert = require('./helpers/setDefaultsOnInsert');
const specialProperties = require('./helpers/specialProperties');
const updateValidators = require('./helpers/updateValidators');
const util = require('util');
const utils = require('./utils');
const queryMiddlewareFunctions = require('./constants').queryMiddlewareFunctions;
const queryOptionMethods = new Set([
'allowDiskUse',
'batchSize',
'collation',
'comment',
'explain',
'hint',
'j',
'lean',
'limit',
'maxTimeMS',
'populate',
'projection',
'read',
'select',
'skip',
'slice',
'sort',
'tailable',
'w',
'writeConcern',
'wtimeout'
]);
// Map from operation name to the name of the function that executes the actual operation against MongoDB.
// Called a thunk for legacy reasons, "thunk" means function that takes exactly 1 param, a callback.
// Currently `_countDocuments()`, etc. are async functions that take no params.
const opToThunk = new Map([
['countDocuments', '_countDocuments'],
['distinct', '__distinct'],
['estimatedDocumentCount', '_estimatedDocumentCount'],
['find', '_find'],
['findOne', '_findOne'],
['findOneAndReplace', '_findOneAndReplace'],
['findOneAndUpdate', '_findOneAndUpdate'],
['replaceOne', '_replaceOne'],
['updateMany', '_updateMany'],
['updateOne', '_updateOne'],
['deleteMany', '_deleteMany'],
['deleteOne', '_deleteOne'],
['findOneAndDelete', '_findOneAndDelete']
]);
/**
* Query constructor used for building queries. You do not need
* to instantiate a `Query` directly. Instead use Model functions like
* [`Model.find()`](https://mongoosejs.com/docs/api/model.html#Model.find()).
*
* #### Example:
*
* const query = MyModel.find(); // `query` is an instance of `Query`
* query.setOptions({ lean : true });
* query.collection(MyModel.collection);
* query.where('age').gte(21).exec(callback);
*
* // You can instantiate a query directly. There is no need to do
* // this unless you're an advanced user with a very good reason to.
* const query = new mongoose.Query();
*
* @param {Object} [options]
* @param {Object} [model]
* @param {Object} [conditions]
* @param {Object} [collection] Mongoose collection
* @api public
*/
function Query(conditions, options, model, collection) {
// this stuff is for dealing with custom queries created by #toConstructor
if (!this._mongooseOptions) {
this._mongooseOptions = {};
}
options = options || {};
this._transforms = [];
this._hooks = new Kareem();
this._executionStack = null;
// this is the case where we have a CustomQuery, we need to check if we got
// options passed in, and if we did, merge them in
const keys = Object.keys(options);
for (const key of keys) {
this._mongooseOptions[key] = options[key];
}
if (collection) {
this.mongooseCollection = collection;
}
if (model) {
this.model = model;
this.schema = model.schema;
}
// this is needed because map reduce returns a model that can be queried, but
// all of the queries on said model should be lean
if (this.model && this.model._mapreduce) {
this.lean();
}
// inherit mquery
mquery.call(this, null, options);
if (collection) {
this.collection(collection);
}
if (conditions) {
this.find(conditions);
}
this.options = this.options || {};
// For gh-6880. mquery still needs to support `fields` by default for old
// versions of MongoDB
this.$useProjection = true;
const collation = this &&
this.schema &&
this.schema.options &&
this.schema.options.collation || null;
if (collation != null) {
this.options.collation = collation;
}
}
/*!
* inherit mquery
*/
Query.prototype = new mquery();
Query.prototype.constructor = Query;
// Remove some legacy methods that we removed in Mongoose 8, but
// are still in mquery 5.
Query.prototype.count = undefined;
Query.prototype.findOneAndRemove = undefined;
Query.base = mquery.prototype;
/*!
* Overwrite mquery's `_distinct`, because Mongoose uses that name
* to store the field to apply distinct on.
*/
Object.defineProperty(Query.prototype, '_distinct', {
configurable: true,
writable: true,
enumerable: true,
value: undefined
});
/**
* Flag to opt out of using `$geoWithin`.
*
* ```javascript
* mongoose.Query.use$geoWithin = false;
* ```
*
* MongoDB 2.4 deprecated the use of `$within`, replacing it with `$geoWithin`. Mongoose uses `$geoWithin` by default (which is 100% backward compatible with `$within`). If you are running an older version of MongoDB, set this flag to `false` so your `within()` queries continue to work.
*
* @see geoWithin https://www.mongodb.com/docs/manual/reference/operator/geoWithin/
* @default true
* @property use$geoWithin
* @memberOf Query
* @static
* @api public
*/
Query.use$geoWithin = mquery.use$geoWithin;
/**
* Converts this query to a customized, reusable query constructor with all arguments and options retained.
*
* #### Example:
*
* // Create a query for adventure movies and read from the primary
* // node in the replica-set unless it is down, in which case we'll
* // read from a secondary node.
* const query = Movie.find({ tags: 'adventure' }).read('primaryPreferred');
*
* // create a custom Query constructor based off these settings
* const Adventure = query.toConstructor();
*
* // further narrow down our query results while still using the previous settings
* await Adventure().where({ name: /^Life/ }).exec();
*
* // since Adventure is a stand-alone constructor we can also add our own
* // helper methods and getters without impacting global queries
* Adventure.prototype.startsWith = function (prefix) {
* this.where({ name: new RegExp('^' + prefix) })
* return this;
* }
* Object.defineProperty(Adventure.prototype, 'highlyRated', {
* get: function () {
* this.where({ rating: { $gt: 4.5 }});
* return this;
* }
* })
* await Adventure().highlyRated.startsWith('Life').exec();
*
* @return {Query} subclass-of-Query
* @api public
*/
Query.prototype.toConstructor = function toConstructor() {
const model = this.model;
const coll = this.mongooseCollection;
const CustomQuery = function(criteria, options) {
if (!(this instanceof CustomQuery)) {
return new CustomQuery(criteria, options);
}
this._mongooseOptions = clone(p._mongooseOptions);
Query.call(this, criteria, options || null, model, coll);
};
util.inherits(CustomQuery, model.Query);
// set inherited defaults
const p = CustomQuery.prototype;
p.options = {};
// Need to handle `sort()` separately because entries-style `sort()` syntax
// `sort([['prop1', 1]])` confuses mquery into losing the outer nested array.
// See gh-8159
const options = Object.assign({}, this.options);
if (options.sort != null) {
p.sort(options.sort);
delete options.sort;
}
p.setOptions(options);
p.op = this.op;
p._validateOp();
p._conditions = clone(this._conditions);
p._fields = clone(this._fields);
p._update = clone(this._update, {
flattenDecimals: false
});
p._path = this._path;
p._distinct = this._distinct;
p._collection = this._collection;
p._mongooseOptions = this._mongooseOptions;
return CustomQuery;
};
/**
* Make a copy of this query so you can re-execute it.
*
* #### Example:
*
* const q = Book.findOne({ title: 'Casino Royale' });
* await q.exec();
* await q.exec(); // Throws an error because you can't execute a query twice
*
* await q.clone().exec(); // Works
*
* @method clone
* @return {Query} copy
* @memberOf Query
* @instance
* @api public
*/
Query.prototype.clone = function() {
const model = this.model;
const collection = this.mongooseCollection;
const q = new this.model.Query({}, {}, model, collection);
// Need to handle `sort()` separately because entries-style `sort()` syntax
// `sort([['prop1', 1]])` confuses mquery into losing the outer nested array.
// See gh-8159
const options = Object.assign({}, this.options);
if (options.sort != null) {
q.sort(options.sort);
delete options.sort;
}
q.setOptions(options);
q.op = this.op;
q._validateOp();
q._conditions = clone(this._conditions);
q._fields = clone(this._fields);
q._update = clone(this._update, {
flattenDecimals: false
});
q._path = this._path;
q._distinct = this._distinct;
q._collection = this._collection;
q._mongooseOptions = this._mongooseOptions;
return q;
};
/**
* Specifies a javascript function or expression to pass to MongoDBs query system.
*
* #### Example:
*
* query.$where('this.comments.length === 10 || this.name.length === 5')
*
* // or
*
* query.$where(function () {
* return this.comments.length === 10 || this.name.length === 5;
* })
*
* #### Note:
*
* Only use `$where` when you have a condition that cannot be met using other MongoDB operators like `$lt`.
* **Be sure to read about all of [its caveats](https://www.mongodb.com/docs/manual/reference/operator/where/) before using.**
*
* @see $where https://www.mongodb.com/docs/manual/reference/operator/where/
* @method $where
* @param {String|Function} js javascript string or function
* @return {Query} this
* @memberOf Query
* @instance
* @method $where
* @api public
*/
/**
* Specifies a `path` for use with chaining.
*
* #### Example:
*
* // instead of writing:
* User.find({age: {$gte: 21, $lte: 65}});
*
* // we can instead write:
* User.where('age').gte(21).lte(65);
*
* // passing query conditions is permitted
* User.find().where({ name: 'vonderful' })
*
* // chaining
* User
* .where('age').gte(21).lte(65)
* .where('name', /^vonderful/i)
* .where('friends').slice(10)
* .exec()
*
* @method where
* @memberOf Query
* @instance
* @param {String|Object} [path]
* @param {any} [val]
* @return {Query} this
* @api public
*/
/**
* Specifies a `$slice` projection for an array.
*
* #### Example:
*
* query.slice('comments', 5); // Returns the first 5 comments
* query.slice('comments', -5); // Returns the last 5 comments
* query.slice('comments', [10, 5]); // Returns the first 5 comments after the 10-th
* query.where('comments').slice(5); // Returns the first 5 comments
* query.where('comments').slice([-10, 5]); // Returns the first 5 comments after the 10-th to last
*
* **Note:** If the absolute value of the number of elements to be sliced is greater than the number of elements in the array, all array elements will be returned.
*
* // Given `arr`: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
* query.slice('arr', 20); // Returns [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
* query.slice('arr', -20); // Returns [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
*
* **Note:** If the number of elements to skip is positive and greater than the number of elements in the array, an empty array will be returned.
*
* // Given `arr`: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
* query.slice('arr', [20, 5]); // Returns []
*
* **Note:** If the number of elements to skip is negative and its absolute value is greater than the number of elements in the array, the starting position is the start of the array.
*
* // Given `arr`: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
* query.slice('arr', [-20, 5]); // Returns [1, 2, 3, 4, 5]
*
* @method slice
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Number|Array} val number of elements to slice or array with number of elements to skip and number of elements to slice
* @return {Query} this
* @see mongodb https://www.mongodb.com/docs/manual/tutorial/query-documents/#projection
* @see $slice https://www.mongodb.com/docs/manual/reference/projection/slice/#prj._S_slice
* @api public
*/
Query.prototype.slice = function() {
if (arguments.length === 0) {
return this;
}
this._validate('slice');
let path;
let val;
if (arguments.length === 1) {
const arg = arguments[0];
if (typeof arg === 'object' && !Array.isArray(arg)) {
const keys = Object.keys(arg);
const numKeys = keys.length;
for (let i = 0; i < numKeys; ++i) {
this.slice(keys[i], arg[keys[i]]);
}
return this;
}
this._ensurePath('slice');
path = this._path;
val = arguments[0];
} else if (arguments.length === 2) {
if ('number' === typeof arguments[0]) {
this._ensurePath('slice');
path = this._path;
val = [arguments[0], arguments[1]];
} else {
path = arguments[0];
val = arguments[1];
}
} else if (arguments.length === 3) {
path = arguments[0];
val = [arguments[1], arguments[2]];
}
const p = {};
p[path] = { $slice: val };
this.select(p);
return this;
};
/*!
* ignore
*/
const validOpsSet = new Set(queryMiddlewareFunctions);
Query.prototype._validateOp = function() {
if (this.op != null && !validOpsSet.has(this.op)) {
this.error(new Error('Query has invalid `op`: "' + this.op + '"'));
}
};
/**
* Specifies the complementary comparison value for paths specified with `where()`
*
* #### Example:
*
* User.where('age').equals(49);
*
* // is the same as
*
* User.where('age', 49);
*
* @method equals
* @memberOf Query
* @instance
* @param {Object} val
* @return {Query} this
* @api public
*/
/**
* Specifies arguments for an `$or` condition.
*
* #### Example:
*
* query.or([{ color: 'red' }, { status: 'emergency' }]);
*
* @see $or https://www.mongodb.com/docs/manual/reference/operator/or/
* @method or
* @memberOf Query
* @instance
* @param {Array} array array of conditions
* @return {Query} this
* @api public
*/
/**
* Specifies arguments for a `$nor` condition.
*
* #### Example:
*
* query.nor([{ color: 'green' }, { status: 'ok' }]);
*
* @see $nor https://www.mongodb.com/docs/manual/reference/operator/nor/
* @method nor
* @memberOf Query
* @instance
* @param {Array} array array of conditions
* @return {Query} this
* @api public
*/
/**
* Specifies arguments for a `$and` condition.
*
* #### Example:
*
* query.and([{ color: 'green' }, { status: 'ok' }])
*
* @method and
* @memberOf Query
* @instance
* @see $and https://www.mongodb.com/docs/manual/reference/operator/and/
* @param {Array} array array of conditions
* @return {Query} this
* @api public
*/
/**
* Specifies a `$gt` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* #### Example:
*
* Thing.find().where('age').gt(21);
*
* // or
* Thing.find().gt('age', 21);
*
* @method gt
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Number} val
* @see $gt https://www.mongodb.com/docs/manual/reference/operator/gt/
* @api public
*/
/**
* Specifies a `$gte` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* @method gte
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Number} val
* @see $gte https://www.mongodb.com/docs/manual/reference/operator/gte/
* @api public
*/
/**
* Specifies a `$lt` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* @method lt
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Number} val
* @see $lt https://www.mongodb.com/docs/manual/reference/operator/lt/
* @api public
*/
/**
* Specifies a `$lte` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* @method lte
* @see $lte https://www.mongodb.com/docs/manual/reference/operator/lte/
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Number} val
* @api public
*/
/**
* Specifies a `$ne` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* @see $ne https://www.mongodb.com/docs/manual/reference/operator/ne/
* @method ne
* @memberOf Query
* @instance
* @param {String} [path]
* @param {any} val
* @api public
*/
/**
* Specifies an `$in` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* @see $in https://www.mongodb.com/docs/manual/reference/operator/in/
* @method in
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Array} val
* @api public
*/
/**
* Specifies an `$nin` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* @see $nin https://www.mongodb.com/docs/manual/reference/operator/nin/
* @method nin
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Array} val
* @api public
*/
/**
* Specifies an `$all` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* #### Example:
*
* MyModel.find().where('pets').all(['dog', 'cat', 'ferret']);
* // Equivalent:
* MyModel.find().all('pets', ['dog', 'cat', 'ferret']);
*
* @see $all https://www.mongodb.com/docs/manual/reference/operator/all/
* @method all
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Array} val
* @api public
*/
/**
* Specifies a `$size` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* #### Example:
*
* const docs = await MyModel.where('tags').size(0).exec();
* assert(Array.isArray(docs));
* console.log('documents with 0 tags', docs);
*
* @see $size https://www.mongodb.com/docs/manual/reference/operator/size/
* @method size
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Number} val
* @api public
*/
/**
* Specifies a `$regex` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* @see $regex https://www.mongodb.com/docs/manual/reference/operator/regex/
* @method regex
* @memberOf Query
* @instance
* @param {String} [path]
* @param {String|RegExp} val
* @api public
*/
/**
* Specifies a `maxDistance` query condition.
*
* When called with one argument, the most recent path passed to `where()` is used.
*
* @see $maxDistance https://www.mongodb.com/docs/manual/reference/operator/maxDistance/
* @method maxDistance
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Number} val
* @api public
*/
/**
* Specifies a `$mod` condition, filters documents for documents whose
* `path` property is a number that is equal to `remainder` modulo `divisor`.
*
* #### Example:
*
* // All find products whose inventory is odd
* Product.find().mod('inventory', [2, 1]);
* Product.find().where('inventory').mod([2, 1]);
* // This syntax is a little strange, but supported.
* Product.find().where('inventory').mod(2, 1);
*
* @method mod
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Array} val must be of length 2, first element is `divisor`, 2nd element is `remainder`.
* @return {Query} this
* @see $mod https://www.mongodb.com/docs/manual/reference/operator/mod/
* @api public
*/
Query.prototype.mod = function() {
let val;
let path;
if (arguments.length === 1) {
this._ensurePath('mod');
val = arguments[0];
path = this._path;
} else if (arguments.length === 2 && !Array.isArray(arguments[1])) {
this._ensurePath('mod');
val = [arguments[0], arguments[1]];
path = this._path;
} else if (arguments.length === 3) {
val = [arguments[1], arguments[2]];
path = arguments[0];
} else {
val = arguments[1];
path = arguments[0];
}
const conds = this._conditions[path] || (this._conditions[path] = {});
conds.$mod = val;
return this;
};
/**
* Specifies an `$exists` condition
*
* #### Example:
*
* // { name: { $exists: true }}
* Thing.where('name').exists()
* Thing.where('name').exists(true)
* Thing.find().exists('name')
*
* // { name: { $exists: false }}
* Thing.where('name').exists(false);
* Thing.find().exists('name', false);
*
* @method exists
* @memberOf Query
* @instance
* @param {String} [path]
* @param {Boolean} val
* @return {Query} this
* @see $exists https://www.mongodb.com/docs/manual/reference/operator/exists/
* @api public
*/
/**
* Specifies an `$elemMatch` condition
*
* #### Example:
*
* query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}})
*
* query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}})
*
* query.elemMatch('comment', function (elem) {
* elem.where('author').equals('autobot');
* elem.where('votes').gte(5);
* })
*
* query.where('comment').elemMatch(function (elem) {
* elem.where({ author: 'autobot' });
* elem.where('votes').gte(5);
* })
*
* @method elemMatch
* @memberOf Query
* @instance
* @param {String|Object|Function} path
* @param {Object|Function} filter
* @return {Query} this
* @see $elemMatch https://www.mongodb.com/docs/manual/reference/operator/elemMatch/
* @api public
*/
/**
* Defines a `$within` or `$geoWithin` argument for geo-spatial queries.
*
* #### Example:
*
* query.where(path).within().box()
* query.where(path).within().circle()
* query.where(path).within().geometry()
*
* query.where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true });
* query.where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] });
* query.where('loc').within({ polygon: [[],[],[],[]] });
*
* query.where('loc').within([], [], []) // polygon
* query.where('loc').within([], []) // box
* query.where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry
*
* **MUST** be used after `where()`.
*
* #### Note:
*
* As of Mongoose 3.7, `$geoWithin` is always used for queries. To change this behavior, see [Query.use$geoWithin](https://mongoosejs.com/docs/api/query.html#Query.prototype.use$geoWithin).
*
* #### Note:
*
* In Mongoose 3.7, `within` changed from a getter to a function. If you need the old syntax, use [this](https://github.com/ebensing/mongoose-within).
*
* @method within
* @see $polygon https://www.mongodb.com/docs/manual/reference/operator/polygon/
* @see $box https://www.mongodb.com/docs/manual/reference/operator/box/
* @see $geometry https://www.mongodb.com/docs/manual/reference/operator/geometry/
* @see $center https://www.mongodb.com/docs/manual/reference/operator/center/
* @see $centerSphere https://www.mongodb.com/docs/manual/reference/operator/centerSphere/
* @memberOf Query
* @instance
* @return {Query} this
* @api public
*/
/**
* Specifies the maximum number of documents the query will return.
*
* #### Example:
*
* query.limit(20);
*
* #### Note:
*
* Cannot be used with `distinct()`
*
* @method limit
* @memberOf Query
* @instance
* @param {Number} val
* @api public
*/
Query.prototype.limit = function limit(v) {
this._validate('limit');
if (typeof v === 'string') {
try {
v = castNumber(v);
} catch (err) {
throw new CastError('Number', v, 'limit');
}
}
this.options.limit = v;
return this;
};
/**
* Specifies the number of documents to skip.
*
* #### Example:
*
* query.skip(100).limit(20);
*
* #### Note:
*
* Cannot be used with `distinct()`
*
* @method skip
* @memberOf Query
* @instance
* @param {Number} val
* @see cursor.skip https://www.mongodb.com/docs/manual/reference/method/cursor.skip/
* @api public
*/
Query.prototype.skip = function skip(v) {
this._validate('skip');
if (typeof v === 'string') {
try {
v = castNumber(v);
} catch (err) {
throw new CastError('Number', v, 'skip');
}
}
this.options.skip = v;
return this;
};
/**
* Specifies the batchSize option.
*
* #### Example:
*
* query.batchSize(100)
*
* #### Note:
*
* Cannot be used with `distinct()`
*
* @method batchSize
* @memberOf Query
* @instance
* @param {Number} val
* @see batchSize https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/
* @api public
*/
/**
* Specifies the `comment` option.
*
* #### Example:
*
* query.comment('login query')
*
* #### Note:
*
* Cannot be used with `distinct()`
*
* @method comment
* @memberOf Query
* @instance
* @param {String} val
* @see comment https://www.mongodb.com/docs/manual/reference/operator/comment/
* @api public
*/
/**
* Sets query hints.
*
* #### Example:
*
* query.hint({ indexA: 1, indexB: -1 });
*
* #### Note:
*
* Cannot be used with `distinct()`
*
* @method hint
* @memberOf Query
* @instance
* @param {Object} val a hint object
* @return {Query} this
* @see $hint https://www.mongodb.com/docs/manual/reference/operator/hint/
* @api public
*/
/**
* Get/set the current projection (AKA fields). Pass `null` to remove the
* current projection.
*
* Unlike `projection()`, the `select()` function modifies the current
* projection in place. This function overwrites the existing projection.
*
* #### Example:
*
* const q = Model.find();
* q.projection(); // null
*
* q.select('a b');
* q.projection(); // { a: 1, b: 1 }
*
* q.projection({ c: 1 });
* q.projection(); // { c: 1 }
*
* q.projection(null);
* q.projection(); // null
*
*
* @method projection
* @memberOf Query
* @instance
* @param {Object|null} arg
* @return {Object} the current projection
* @api public
*/
Query.prototype.projection = function(arg) {
if (arguments.length === 0) {
return this._fields;
}
this._fields = {};
this._userProvidedFields = {};
this.select(arg);
return this._fields;
};
/**
* Specifies which document fields to include or exclude (also known as the query "projection")
*
* When using string syntax, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included. Lastly, if a path is prefixed with `+`, it forces inclusion of the path, which is useful for paths excluded at the [schema level](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.select()).
*
* A projection _must_ be either inclusive or exclusive. In other words, you must
* either list the fields to include (which excludes all others), or list the fields
* to exclude (which implies all other fields are included). The [`_id` field is the only exception because MongoDB includes it by default](https://www.mongodb.com/docs/manual/tutorial/project-fields-from-query-results/#suppress-id-field).
*
* #### Example:
*
* // include a and b, exclude other fields
* query.select('a b');
* // Equivalent syntaxes:
* query.select(['a', 'b']);
* query.select({ a: 1, b: 1 });
*
* // exclude c and d, include other fields
* query.select('-c -d');
*
* // Use `+` to override schema-level `select: false` without making the
* // projection inclusive.
* const schema = new Schema({
* foo: { type: String, select: false },
* bar: String
* });
* // ...
* query.select('+foo'); // Override foo's `select: false` without excluding `bar`
*
* // or you may use object notation, useful when
* // you have keys already prefixed with a "-"
* query.select({ a: 1, b: 1 });
* query.select({ c: 0, d: 0 });
*
* Additional calls to select can override the previous selection:
* query.select({ a: 1, b: 1 }).select({ b: 0 }); // selection is now { a: 1 }
* query.select({ a: 0, b: 0 }).select({ b: 1 }); // selection is now { a: 0 }
*
*
* @method select
* @memberOf Query
* @instance
* @param {Object|String|String[]} arg
* @return {Query} this
* @see SchemaType https://mongoosejs.com/docs/api/schematype.html
* @api public
*/
Query.prototype.select = function select() {
let arg = arguments[0];
if (!arg) return this;
if (arguments.length !== 1) {
throw new Error('Invalid select: select only takes 1 argument');
}
this._validate('select');
const fields = this._fields || (this._fields = {});
const userProvidedFields = this._userProvidedFields || (this._userProvidedFields = {});
let sanitizeProjection = undefined;
if (this.model != null && utils.hasUserDefinedProperty(this.model.db.options, 'sanitizeProjection')) {
sanitizeProjection = this.model.db.options.sanitizeProjection;
} else if (this.model != null && utils.hasUserDefinedProperty(this.model.base.options, 'sanitizeProjection')) {
sanitizeProjection = this.model.base.options.sanitizeProjection;
} else {
sanitizeProjection = this._mongooseOptions.sanitizeProjection;
}
function sanitizeValue(value) {
return typeof value === 'string' && sanitizeProjection ? value = 1 : value;
}
arg = parseProjection(arg, true); // we want to keep the minus and pluses, so add boolean arg.
if (utils.isObject(arg)) {
if (this.selectedInclusively()) {
Object.entries(arg).forEach(([key, value]) => {
if (value) {
// Add the field to the projection
if (fields['-' + key] != null) {
delete fields['-' + key];
}
fields[key] = userProvidedFields[key] = sanitizeValue(value);
} else {
// Remove the field from the projection
Object.keys(userProvidedFields).forEach(field => {
if (isSubpath(key, field)) {
delete fields[field];
delete userProvidedFields[field];
}
});
}
});
} else if (this.selectedExclusively()) {
Object.entries(arg).forEach(([key, value]) => {
if (!value) {
// Add the field to the projection
if (fields['+' + key] != null) {
delete fields['+' + key];
}
fields[key] = userProvidedFields[key] = sanitizeValue(value);
} else {
// Remove the field from the projection
Object.keys(userProvidedFields).forEach(field => {
if (isSubpath(key, field)) {
delete fields[field];
delete userProvidedFields[field];
}
});
}
});
} else {
const keys = Object.keys(arg);
for (let i = 0; i < keys.length; ++i) {
const value = arg[keys[i]];
const key = keys[i];
fields[key] = sanitizeValue(value);
userProvidedFields[key] = sanitizeValue(value);
}
}
return this;
}
throw new TypeError('Invalid select() argument. Must be string or object.');
};
/**
* Enable or disable schema level projections for this query. Enabled by default.
* Set to `false` to include fields with `select: false` in the query result by default.
*
* #### Example:
*
* const userSchema = new Schema({
* email: { type: String, required: true },
* passwordHash: { type: String, select: false, required: true }
* });
* const UserModel = mongoose.model('User', userSchema);
*
* const doc = await UserModel.findOne().orFail().schemaLevelProjections(false);
*
* // Contains password hash, because `schemaLevelProjections()` overrides `select: false`
* doc.passwordHash;
*
* @method schemaLevelProjections
* @memberOf Query
* @instance
* @param {Boolean} value
* @return {Query} this
* @see SchemaTypeOptions https://mongoosejs.com/docs/schematypes.html#all-schema-types
* @api public
*/
Query.prototype.schemaLevelProjections = function schemaLevelProjections(value) {
this._mongooseOptions.schemaLevelProjections = value;
return this;
};
/**
* Sets this query's `sanitizeProjection` option. If set, `sanitizeProjection` does
* two things:
*
* 1. Enforces that projection values are numbers, not strings.
* 2. Prevents using `+` syntax to override properties that are deselected by default.
*
* With `sanitizeProjection()`, you can pass potentially untrusted user data to `.select()`.
*
* #### Example
*
* const userSchema = new Schema({
* name: String,
* password: { type: String, select: false }
* });
* const UserModel = mongoose.model('User', userSchema);
* const { _id } = await UserModel.create({ name: 'John', password: 'secret' })
*
* // The MongoDB server has special handling for string values that start with '$'
* // in projections, which can lead to unexpected leaking of sensitive data.
* let doc = await UserModel.findOne().select({ name: '$password' });
* doc.name; // 'secret'
* doc.password; // undefined
*
* // With `sanitizeProjection`, Mongoose forces all projection values to be numbers
* doc = await UserModel.findOne().sanitizeProjection(true).select({ name: '$password' });
* doc.name; // 'John'
* doc.password; // undefined
*
* // By default, Mongoose supports projecting in `password` using `+password`
* doc = await UserModel.findOne().select('+password');
* doc.password; // 'secret'
*
* // With `sanitizeProjection`, Mongoose prevents projecting in `password` and other
* // fields that have `select: false` in the schema.
* doc = await UserModel.findOne().sanitizeProjection(true).select('+password');
* doc.password; // undefined
*
* @method sanitizeProjection
* @memberOf Query
* @instance
* @param {Boolean} value
* @return {Query} this
* @see sanitizeProjection https://thecodebarbarian.com/whats-new-in-mongoose-5-13-sanitizeprojection.html
* @api public
*/
Query.prototype.sanitizeProjection = function sanitizeProjection(value) {
this._mongooseOptions.sanitizeProjection = value;
return this;
};
/**
* Determines the MongoDB nodes from which to read.
*
* #### Preferences:
*
* ```
* primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags.
* secondary Read from secondary if available, otherwise error.
* primaryPreferred Read from primary if available, otherwise a secondary.
* secondaryPreferred Read from a secondary if available, otherwise read from the primary.
* nearest All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection.
* ```
*
* Aliases
*
* ```
* p primary
* pp primaryPreferred
* s secondary
* sp secondaryPreferred
* n nearest
* ```
*
* #### Example:
*
* new Query().read('primary')
* new Query().read('p') // same as primary
*
* new Query().read('primaryPreferred')
* new Query().read('pp') // same as primaryPreferred
*
* new Query().read('secondary')
* new Query().read('s') // same as secondary
*
* new Query().read('secondaryPreferred')
* new Query().read('sp') // same as secondaryPreferred
*
* new Query().read('nearest')
* new Query().read('n') // same as nearest
*
* // read from secondaries with matching tags
* new Query().read('s', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }])
*
* Read more about how to use read preferences [here](https://www.mongodb.com/docs/manual/applications/replication/#read-preference).
*
* @method read
* @memberOf Query
* @instance
* @param {String} mode one of the listed preference options or aliases
* @param {Array} [tags] optional tags for this query
* @see mongodb https://www.mongodb.com/docs/manual/applications/replication/#read-preference
* @return {Query} this
* @api public
*/
Query.prototype.read = function read(mode, tags) {
if (typeof mode === 'string') {
mode = handleReadPreferenceAliases(mode);
this.options.readPreference = { mode, tags };
} else {
this.options.readPreference = mode;
}
return this;
};
/**
* Overwrite default `.toString` to make logging more useful
*
* @memberOf Query
* @instance
* @method toString
* @api private
*/
Query.prototype.toString = function toString() {
if (this.op === 'count' ||
this.op === 'countDocuments' ||
this.op === 'find' ||
this.op === 'findOne' ||
this.op === 'deleteMany' ||
this.op === 'deleteOne' ||
this.op === 'findOneAndDelete' ||
this.op === 'remove') {
return `${this.model.modelName}.${this.op}(${util.inspect(this._conditions)})`;
}
if (this.op === 'distinct') {
return `${this.model.modelName}.distinct('${this._distinct}', ${util.inspect(this._conditions)})`;
}
if (this.op === 'findOneAndReplace' ||
this.op === 'findOneAndUpdate' ||
this.op === 'replaceOne' ||
this.op === 'update' ||
this.op === 'updateMany' ||
this.op === 'updateOne') {
return `${this.model.modelName}.${this.op}(${util.inspect(this._conditions)}, ${util.inspect(this._update)})`;
}
// 'estimatedDocumentCount' or any others
return `${this.model.modelName}.${this.op}()`;
};
/**
* Sets the [MongoDB session](https://www.mongodb.com/docs/manual/reference/server-sessions/)
* associated with this query. Sessions are how you mark a query as part of a
* [transaction](https://mongoosejs.com/docs/transactions.html).
*
* Calling `session(null)` removes the session from this query.
*
* #### Example:
*
* const s = await mongoose.startSession();
* await mongoose.model('Person').findOne({ name: 'Axl Rose' }).session(s);
*
* @method session
* @memberOf Query
* @instance
* @param {ClientSession} [session] from `await conn.startSession()`
* @see Connection.prototype.startSession() https://mongoosejs.com/docs/api/connection.html#Connection.prototype.startSession()
* @see mongoose.startSession() https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.startSession()
* @return {Query} this
* @api public
*/
Query.prototype.session = function session(v) {
if (v == null) {
delete this.options.session;
}
this.options.session = v;
return this;
};
/**
* Sets the 3 write concern parameters for this query:
*
* - `w`: Sets the specified number of `mongod` servers, or tag set of `mongod` servers, that must acknowledge this write before this write is considered successful.
* - `j`: Boolean, set to `true` to request acknowledgement that this operation has been persisted to MongoDB's on-disk journal.
* - `wtimeout`: If [`w > 1`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()), the maximum amount of time to wait for this write to propagate through the replica set before this operation fails. The default is `0`, which means no timeout.
*
* This option is only valid for operations that write to the database:
*
* - `deleteOne()`
* - `deleteMany()`
* - `findOneAndDelete()`
* - `findOneAndReplace()`
* - `findOneAndUpdate()`
* - `updateOne()`
* - `updateMany()`
*
* Defaults to the schema's [`writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)
*
* #### Example:
*
* // The 'majority' option means the `deleteOne()` promise won't resolve
* // until the `deleteOne()` has propagated to the majority of the replica set
* await mongoose.model('Person').
* deleteOne({ name: 'Ned Stark' }).
* writeConcern({ w: 'majority' });
*
* @method writeConcern
* @memberOf Query
* @instance
* @param {Object} writeConcern the write concern value to set
* @see WriteConcernSettings https://mongodb.github.io/node-mongodb-native/4.9/interfaces/WriteConcernSettings.html
* @return {Query} this
* @api public
*/
Query.prototype.writeConcern = function writeConcern(val) {
if (val == null) {
delete this.options.writeConcern;
return this;
}
this.options.writeConcern = val;
return this;
};
/**
* Sets the specified number of `mongod` servers, or tag set of `mongod` servers,
* that must acknowledge this write before this write is considered successful.
* This option is only valid for operations that write to the database:
*
* - `deleteOne()`
* - `deleteMany()`
* - `findOneAndDelete()`
* - `findOneAndReplace()`
* - `findOneAndUpdate()`
* - `updateOne()`
* - `updateMany()`
*
* Defaults to the schema's [`writeConcern.w` option](https://mongoosejs.com/docs/guide.html#writeConcern)
*
* #### Example:
*
* // The 'majority' option means the `deleteOne()` promise won't resolve
* // until the `deleteOne()` has propagated to the majority of the replica set
* await mongoose.model('Person').
* deleteOne({ name: 'Ned Stark' }).
* w('majority');
*
* @method w
* @memberOf Query
* @instance
* @param {String|number} val 0 for fire-and-forget, 1 for acknowledged by one server, 'majority' for majority of the replica set, or [any of the more advanced options](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option).
* @see mongodb https://www.mongodb.com/docs/manual/reference/write-concern/#w-option
* @return {Query} this
* @api public
*/
Query.prototype.w = function w(val) {
if (val == null) {
delete this.options.w;
}
if (this.options.writeConcern != null) {
this.options.writeConcern.w = val;
} else {
this.options.w = val;
}
return this;
};
/**
* Requests acknowledgement that this operation has been persisted to MongoDB's
* on-disk journal.
* This option is only valid for operations that write to the database:
*
* - `deleteOne()`
* - `deleteMany()`
* - `findOneAndDelete()`
* - `findOneAndReplace()`
* - `findOneAndUpdate()`
* - `updateOne()`
* - `updateMany()`
*
* Defaults to the schema's [`writeConcern.j` option](https://mongoosejs.com/docs/guide.html#writeConcern)
*
* #### Example:
*
* await mongoose.model('Person').deleteOne({ name: 'Ned Stark' }).j(true);
*
* @method j
* @memberOf Query
* @instance
* @param {boolean} val
* @see mongodb https://www.mongodb.com/docs/manual/reference/write-concern/#j-option
* @return {Query} this
* @api public
*/
Query.prototype.j = function j(val) {
if (val == null) {
delete this.options.j;
}
if (this.options.writeConcern != null) {
this.options.writeConcern.j = val;
} else {
this.options.j = val;
}
return this;
};
/**
* If [`w > 1`](https://mongoosejs.com/docs/api/query.html#Query.prototype.w()), the maximum amount of time to
* wait for this write to propagate through the replica set before this
* operation fails. The default is `0`, which means no timeout.
*
* This option is only valid for operations that write to the database:
*
* - `deleteOne()`
* - `deleteMany()`
* - `findOneAndDelete()`
* - `findOneAndReplace()`
* - `findOneAndUpdate()`
* - `updateOne()`
* - `updateMany()`
*
* Defaults to the schema's [`writeConcern.wtimeout` option](https://mongoosejs.com/docs/guide.html#writeConcern)
*
* #### Example:
*
* // The `deleteOne()` promise won't resolve until this `deleteOne()` has
* // propagated to at least `w = 2` members of the replica set. If it takes
* // longer than 1 second, this `deleteOne()` will fail.
* await mongoose.model('Person').
* deleteOne({ name: 'Ned Stark' }).
* w(2).
* wtimeout(1000);
*
* @method wtimeout
* @memberOf Query
* @instance
* @param {number} ms number of milliseconds to wait
* @see mongodb https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout
* @return {Query} this
* @api public
*/
Query.prototype.wtimeout = function wtimeout(ms) {
if (ms == null) {
delete this.options.wtimeout;
}
if (this.options.writeConcern != null) {
this.options.writeConcern.wtimeout = ms;
} else {
this.options.wtimeout = ms;
}
return this;
};
/**
* Sets the readConcern option for the query.
*
* #### Example:
*
* new Query().readConcern('local')
* new Query().readConcern('l') // same as local
*
* new Query().readConcern('available')
* new Query().readConcern('a') // same as available
*
* new Query().readConcern('majority')
* new Query().readConcern('m') // same as majority
*
* new Query().readConcern('linearizable')
* new Query().readConcern('lz') // same as linearizable
*
* new Query().readConcern('snapshot')
* new Query().readConcern('s') // same as snapshot
*
*
* #### Read Concern Level:
*
* ```
* local MongoDB 3.2+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
* available MongoDB 3.6+ The query returns from the instance with no guarantee guarantee that the data has been written to a majority of the replica set members (i.e. may be rolled back).
* majority MongoDB 3.2+ The query returns the data that has been acknowledged by a majority of the replica set members. The documents returned by the read operation are durable, even in the event of failure.
* linearizable MongoDB 3.4+ The query returns data that reflects all successful majority-acknowledged writes that completed prior to the start of the read operation. The query may wait for concurrently executing writes to propagate to a majority of replica set members before returning results.
* snapshot MongoDB 4.0+ Only available for operations within multi-document transactions. Upon transaction commit with write concern "majority", the transaction operations are guaranteed to have read from a snapshot of majority-committed data.
* ```
*
* Aliases
*
* ```
* l local
* a available
* m majority
* lz linearizable
* s snapshot
* ```
*
* Read more about how to use read concern [here](https://www.mongodb.com/docs/manual/reference/read-concern/).
*
* @memberOf Query
* @method readConcern
* @param {String} level one of the listed read concern level or their aliases
* @see mongodb https://www.mongodb.com/docs/manual/reference/read-concern/
* @return {Query} this
* @api public
*/
/**
* Gets query options.
*
* #### Example:
*
* const query = new Query();
* query.limit(10);
* query.setOptions({ maxTimeMS: 1000 });
* query.getOptions(); // { limit: 10, maxTimeMS: 1000 }
*
* @return {Object} the options
* @api public
*/
Query.prototype.getOptions = function() {
return this.options;
};
/**
* Sets query options. Some options only make sense for certain operations.
*
* #### Options:
*
* The following options are only for `find()`:
*
* - [tailable](https://www.mongodb.com/docs/manual/core/tailable-cursors/)
* - [limit](https://www.mongodb.com/docs/manual/reference/method/cursor.limit/)
* - [skip](https://www.mongodb.com/docs/manual/reference/method/cursor.skip/)
* - [allowDiskUse](https://www.mongodb.com/docs/manual/reference/method/cursor.allowDiskUse/)
* - [batchSize](https://www.mongodb.com/docs/manual/reference/method/cursor.batchSize/)
* - [readPreference](https://www.mongodb.com/docs/manual/applications/replication/#read-preference)
* - [hint](https://www.mongodb.com/docs/manual/reference/method/cursor.hint/)
* - [comment](https://www.mongodb.com/docs/manual/reference/method/cursor.comment/)
*
* The following options are only for write operations: `updateOne()`, `updateMany()`, `replaceOne()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`:
*
* - [upsert](https://www.mongodb.com/docs/manual/reference/method/db.collection.update/)
* - [writeConcern](https://www.mongodb.com/docs/manual/reference/method/db.collection.update/)
* - [timestamps](https://mongoosejs.com/docs/guide.html#timestamps): If `timestamps` is set in the schema, set this option to `false` to skip timestamps for that particular update. Has no effect if `timestamps` is not enabled in the schema options.
* - overwriteDiscriminatorKey: allow setting the discriminator key in the update. Will use the correct discriminator schema if the update changes the discriminator key.
* - overwriteImmutable: allow overwriting properties that are set to `immutable` in the schema. Defaults to false.
*
* The following options are only for `find()`, `findOne()`, `findById