UNPKG

mongoose

Version:
1,881 lines (1,683 loc) 109 kB
/*! * Module dependencies. */ var CastError = require('./error/cast'); var ObjectParameterError = require('./error/objectParameter'); var PromiseProvider = require('./promise_provider'); var QueryCursor = require('./cursor/QueryCursor'); var QueryStream = require('./querystream'); var cast = require('./cast'); var castUpdate = require('./services/query/castUpdate'); var hasDollarKeys = require('./services/query/hasDollarKeys'); var helpers = require('./queryhelpers'); var isInclusive = require('./services/projection/isInclusive'); var mquery = require('mquery'); var readPref = require('./drivers').ReadPreference; var selectPopulatedFields = require('./services/query/selectPopulatedFields'); var setDefaultsOnInsert = require('./services/setDefaultsOnInsert'); var slice = require('sliced'); var updateValidators = require('./services/updateValidators'); var util = require('util'); var utils = require('./utils'); /** * Query constructor used for building queries. * * ####Example: * * var query = new Query(); * query.setOptions({ lean : true }); * query.collection(model.collection); * query.where('age').gte(21).exec(callback); * * @param {Object} [options] * @param {Object} [model] * @param {Object} [conditions] * @param {Object} [collection] Mongoose collection * @api private */ function Query(conditions, options, model, collection) { // this stuff is for dealing with custom queries created by #toConstructor if (!this._mongooseOptions) { this._mongooseOptions = {}; } // 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 if (options) { var keys = Object.keys(options); for (var i = 0; i < keys.length; ++i) { var k = keys[i]; this._mongooseOptions[k] = options[k]; } } 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, this.mongooseCollection, options); if (conditions) { this.find(conditions); } this.options = this.options || {}; if (this.schema != null && this.schema.options.collation != null) { this.options.collation = this.schema.options.collation; } if (this.schema) { var kareemOptions = { useErrorHandlers: true, numCallbackParams: 1, nullResultByDefault: true }; this._count = this.model.hooks.createWrapper('count', Query.prototype._count, this, kareemOptions); this._execUpdate = this.model.hooks.createWrapper('update', Query.prototype._execUpdate, this, kareemOptions); this._find = this.model.hooks.createWrapper('find', Query.prototype._find, this, kareemOptions); this._findOne = this.model.hooks.createWrapper('findOne', Query.prototype._findOne, this, kareemOptions); this._findOneAndRemove = this.model.hooks.createWrapper('findOneAndRemove', Query.prototype._findOneAndRemove, this, kareemOptions); this._findOneAndUpdate = this.model.hooks.createWrapper('findOneAndUpdate', Query.prototype._findOneAndUpdate, this, kareemOptions); this._replaceOne = this.model.hooks.createWrapper('replaceOne', Query.prototype._replaceOne, this, kareemOptions); this._updateMany = this.model.hooks.createWrapper('updateMany', Query.prototype._updateMany, this, kareemOptions); this._updateOne = this.model.hooks.createWrapper('updateOne', Query.prototype._updateOne, this, kareemOptions); } } /*! * inherit mquery */ Query.prototype = new mquery; Query.prototype.constructor = Query; Query.base = mquery.prototype; /** * Flag to opt out of using `$geoWithin`. * * 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 http://docs.mongodb.org/manual/reference/operator/geoWithin/ * @default true * @property use$geoWithin * @memberOf Query * @receiver Query * @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. * var query = Movie.find({ tags: 'adventure' }).read('primaryPreferred'); * * // create a custom Query constructor based off these settings * var Adventure = query.toConstructor(); * * // Adventure is now a subclass of mongoose.Query and works the same way but with the * // default query parameters and options set. * Adventure().exec(callback) * * // further narrow down our query results while still using the previous settings * Adventure().where({ name: /^Life/ }).exec(callback); * * // 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; * } * }) * Adventure().highlyRated.startsWith('Life').exec(callback) * * New in 3.7.3 * * @return {Query} subclass-of-Query * @api public */ Query.prototype.toConstructor = function toConstructor() { var model = this.model; var coll = this.mongooseCollection; var CustomQuery = function(criteria, options) { if (!(this instanceof CustomQuery)) { return new CustomQuery(criteria, options); } this._mongooseOptions = utils.clone(p._mongooseOptions); Query.call(this, criteria, options || null, model, coll); }; util.inherits(CustomQuery, Query); // set inherited defaults var p = CustomQuery.prototype; p.options = {}; p.setOptions(this.options); p.op = this.op; p._conditions = utils.clone(this._conditions, { retainKeyOrder: true }); p._fields = utils.clone(this._fields); p._update = utils.clone(this._update, { flattenDecimals: false, retainKeyOrder: true }); p._path = this._path; p._distinct = this._distinct; p._collection = this._collection; p._mongooseOptions = this._mongooseOptions; return CustomQuery; }; /** * 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](http://docs.mongodb.org/manual/reference/operator/where/) before using.** * * @see $where http://docs.mongodb.org/manual/reference/operator/where/ * @method $where * @param {String|Function} js javascript string or function * @return {Query} this * @memberOf Query * @method $where * @api public */ /** * Specifies a `path` for use with chaining. * * ####Example * * // instead of writing: * User.find({age: {$gte: 21, $lte: 65}}, callback); * * // 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(callback) * * @method where * @memberOf Query * @param {String|Object} [path] * @param {any} [val] * @return {Query} this * @api public */ Query.prototype.slice = function() { if (arguments.length === 0) { return this; } this._validate('slice'); var path; var val; if (arguments.length === 1) { var arg = arguments[0]; if (typeof arg === 'object' && !Array.isArray(arg)) { var keys = Object.keys(arg); var numKeys = keys.length; for (var 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 = slice(arguments); } else { path = arguments[0]; val = arguments[1]; } } else if (arguments.length === 3) { path = arguments[0]; val = slice(arguments, 1); } var p = {}; p[path] = { $slice: val }; return this.select(p); }; /** * 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 * @param {Object} val * @return {Query} this * @api public */ /** * Specifies arguments for an `$or` condition. * * ####Example * * query.or([{ color: 'red' }, { status: 'emergency' }]) * * @see $or http://docs.mongodb.org/manual/reference/operator/or/ * @method or * @memberOf Query * @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 http://docs.mongodb.org/manual/reference/operator/nor/ * @method nor * @memberOf Query * @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 * @see $and http://docs.mongodb.org/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 * @param {String} [path] * @param {Number} val * @see $gt http://docs.mongodb.org/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 * @param {String} [path] * @param {Number} val * @see $gte http://docs.mongodb.org/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 * @param {String} [path] * @param {Number} val * @see $lt http://docs.mongodb.org/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 http://docs.mongodb.org/manual/reference/operator/lte/ * @memberOf Query * @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 http://docs.mongodb.org/manual/reference/operator/ne/ * @method ne * @memberOf Query * @param {String} [path] * @param {Number} val * @api public */ /** * Specifies an $in query condition. * * When called with one argument, the most recent path passed to `where()` is used. * * @see $in http://docs.mongodb.org/manual/reference/operator/in/ * @method in * @memberOf Query * @param {String} [path] * @param {Number} val * @api public */ /** * Specifies an $nin query condition. * * When called with one argument, the most recent path passed to `where()` is used. * * @see $nin http://docs.mongodb.org/manual/reference/operator/nin/ * @method nin * @memberOf Query * @param {String} [path] * @param {Number} val * @api public */ /** * Specifies an $all query condition. * * When called with one argument, the most recent path passed to `where()` is used. * * @see $all http://docs.mongodb.org/manual/reference/operator/all/ * @method all * @memberOf Query * @param {String} [path] * @param {Number} val * @api public */ /** * Specifies a $size query condition. * * When called with one argument, the most recent path passed to `where()` is used. * * ####Example * * MyModel.where('tags').size(0).exec(function (err, docs) { * if (err) return handleError(err); * * assert(Array.isArray(docs)); * console.log('documents with 0 tags', docs); * }) * * @see $size http://docs.mongodb.org/manual/reference/operator/size/ * @method size * @memberOf Query * @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 http://docs.mongodb.org/manual/reference/operator/regex/ * @method regex * @memberOf Query * @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 http://docs.mongodb.org/manual/reference/operator/maxDistance/ * @method maxDistance * @memberOf Query * @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 * @param {String} [path] * @param {Array} val must be of length 2, first element is `divisor`, 2nd element is `remainder`. * @return {Query} this * @see $mod http://docs.mongodb.org/manual/reference/operator/mod/ * @api public */ Query.prototype.mod = function() { var val; var 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 = slice(arguments); path = this._path; } else if (arguments.length === 3) { val = slice(arguments, 1); path = arguments[0]; } else { val = arguments[1]; path = arguments[0]; } var 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 * @param {String} [path] * @param {Number} val * @return {Query} this * @see $exists http://docs.mongodb.org/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 * @param {String|Object|Function} path * @param {Object|Function} criteria * @return {Query} this * @see $elemMatch http://docs.mongodb.org/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](#query_Query-use%2524geoWithin). * * ####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 http://docs.mongodb.org/manual/reference/operator/polygon/ * @see $box http://docs.mongodb.org/manual/reference/operator/box/ * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/ * @see $center http://docs.mongodb.org/manual/reference/operator/center/ * @see $centerSphere http://docs.mongodb.org/manual/reference/operator/centerSphere/ * @memberOf Query * @return {Query} this * @api public */ /** * Specifies a $slice projection for an array. * * ####Example * * query.slice('comments', 5) * query.slice('comments', -5) * query.slice('comments', [10, 5]) * query.where('comments').slice(5) * query.where('comments').slice([-10, 5]) * * @method slice * @memberOf Query * @param {String} [path] * @param {Number} val number/range of elements to slice * @return {Query} this * @see mongodb http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields#RetrievingaSubsetofFields-RetrievingaSubrangeofArrayElements * @see $slice http://docs.mongodb.org/manual/reference/projection/slice/#prj._S_slice * @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 * @param {Number} val * @api public */ /** * Specifies the number of documents to skip. * * ####Example * * query.skip(100).limit(20) * * ####Note * * Cannot be used with `distinct()` * * @method skip * @memberOf Query * @param {Number} val * @see cursor.skip http://docs.mongodb.org/manual/reference/method/cursor.skip/ * @api public */ /** * Specifies the maxScan option. * * ####Example * * query.maxScan(100) * * ####Note * * Cannot be used with `distinct()` * * @method maxScan * @memberOf Query * @param {Number} val * @see maxScan http://docs.mongodb.org/manual/reference/operator/maxScan/ * @api public */ /** * Specifies the batchSize option. * * ####Example * * query.batchSize(100) * * ####Note * * Cannot be used with `distinct()` * * @method batchSize * @memberOf Query * @param {Number} val * @see batchSize http://docs.mongodb.org/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 * @param {Number} val * @see comment http://docs.mongodb.org/manual/reference/operator/comment/ * @api public */ /** * Specifies this query as a `snapshot` query. * * ####Example * * query.snapshot() // true * query.snapshot(true) * query.snapshot(false) * * ####Note * * Cannot be used with `distinct()` * * @method snapshot * @memberOf Query * @see snapshot http://docs.mongodb.org/manual/reference/operator/snapshot/ * @return {Query} this * @api public */ /** * Sets query hints. * * ####Example * * query.hint({ indexA: 1, indexB: -1}) * * ####Note * * Cannot be used with `distinct()` * * @method hint * @memberOf Query * @param {Object} val a hint object * @return {Query} this * @see $hint http://docs.mongodb.org/manual/reference/operator/hint/ * @api public */ /** * 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](/docs/api.html#schematype_SchemaType-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://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/#suppress-id-field). * * ####Example * * // include a and b, exclude other fields * query.select('a b'); * * // exclude c and d, include other fields * query.select('-c -d'); * * // 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 }); * * // force inclusion of field excluded at schema level * query.select('+path') * * @method select * @memberOf Query * @param {Object|String} arg * @return {Query} this * @see SchemaType * @api public */ Query.prototype.select = function select() { var arg = arguments[0]; if (!arg) return this; var i; var len; if (arguments.length !== 1) { throw new Error('Invalid select: select only takes 1 argument'); } this._validate('select'); var fields = this._fields || (this._fields = {}); var userProvidedFields = this._userProvidedFields || (this._userProvidedFields = {}); var type = typeof arg; if (('string' == type || Object.prototype.toString.call(arg) === '[object Arguments]') && 'number' == typeof arg.length || Array.isArray(arg)) { if ('string' == type) arg = arg.split(/\s+/); for (i = 0, len = arg.length; i < len; ++i) { var field = arg[i]; if (!field) continue; var include = '-' == field[0] ? 0 : 1; if (include === 0) field = field.substring(1); fields[field] = include; userProvidedFields[field] = include; } return this; } if (utils.isObject(arg)) { var keys = Object.keys(arg); for (i = 0; i < keys.length; ++i) { fields[keys[i]] = arg[keys[i]]; userProvidedFields[keys[i]] = arg[keys[i]]; } return this; } throw new TypeError('Invalid select() argument. Must be string or object.'); }; /** * _DEPRECATED_ Sets the slaveOk option. * * **Deprecated** in MongoDB 2.2 in favor of [read preferences](#query_Query-read). * * ####Example: * * query.slaveOk() // true * query.slaveOk(true) * query.slaveOk(false) * * @method slaveOk * @memberOf Query * @deprecated use read() preferences instead if on mongodb >= 2.2 * @param {Boolean} v defaults to true * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference * @see slaveOk http://docs.mongodb.org/manual/reference/method/rs.slaveOk/ * @see read() #query_Query-read * @return {Query} this * @api public */ /** * 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 preferrences [here](http://docs.mongodb.org/manual/applications/replication/#read-preference) and [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences). * * @method read * @memberOf Query * @param {String} pref one of the listed preference options or aliases * @param {Array} [tags] optional tags for this query * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences * @return {Query} this * @api public */ Query.prototype.read = function read(pref, tags) { // first cast into a ReadPreference object to support tags var read = readPref.call(readPref, pref, tags); this.options.readPreference = read; return this; }; /** * Merges another Query or conditions object into this one. * * When a Query is passed, conditions, field selection and options are merged. * * New in 3.7.0 * * @method merge * @memberOf Query * @param {Query|Object} source * @return {Query} this */ /** * Sets query options. Some options only make sense for certain operations. * * ####Options: * * The following options are only for `find()`: * - [tailable](http://www.mongodb.org/display/DOCS/Tailable+Cursors) * - [sort](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsort(\)%7D%7D) * - [limit](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D) * - [skip](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D) * - [maxscan](https://docs.mongodb.org/v3.2/reference/operator/meta/maxScan/#metaOp._S_maxScan) * - [batchSize](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D) * - [comment](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment) * - [snapshot](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsnapshot%28%29%7D%7D) * - [readPreference](http://docs.mongodb.org/manual/applications/replication/#read-preference) * - [hint](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint) * * The following options are only for `update()`, `updateOne()`, `updateMany()`, `replaceOne()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`: * - [upsert](https://docs.mongodb.com/manual/reference/method/db.collection.update/) * - [writeConcern](https://docs.mongodb.com/manual/reference/method/db.collection.update/) * * The following options are only for `find()`, `findOne()`, `findById()`, `findOneAndUpdate()`, and `findByIdAndUpdate()`: * - [lean](./api.html#query_Query-lean) * * The following options are only for all operations **except** `update()`, `updateOne()`, `updateMany()`, `remove()`, `deleteOne()`, and `deleteMany()`: * - [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) * * The following options are for all operations: * - [collation](https://docs.mongodb.com/manual/reference/collation/) * * @param {Object} options * @api public */ Query.prototype.setOptions = function(options, overwrite) { // overwrite is only for internal use if (overwrite) { // ensure that _mongooseOptions & options are two different objects this._mongooseOptions = (options && utils.clone(options)) || {}; this.options = options || {}; if ('populate' in options) { this.populate(this._mongooseOptions); } return this; } if (options == null) { return this; } if (Array.isArray(options.populate)) { var populate = options.populate; delete options.populate; var _numPopulate = populate.length; for (var i = 0; i < _numPopulate; ++i) { this.populate(populate[i]); } } return Query.base.setOptions.call(this, options); }; /** * Returns the current query conditions as a JSON object. * * ####Example: * * var query = new Query(); * query.find({ a: 1 }).where('b').gt(2); * query.getQuery(); // { a: 1, b: { $gt: 2 } } * * @return {Object} current query conditions * @api public */ Query.prototype.getQuery = function() { return this._conditions; }; /** * Returns the current update operations as a JSON object. * * ####Example: * * var query = new Query(); * query.update({}, { $set: { a: 5 } }); * query.getUpdate(); // { $set: { a: 5 } } * * @return {Object} current update operations * @api public */ Query.prototype.getUpdate = function() { return this._update; }; /** * Returns fields selection for this query. * * @method _fieldsForExec * @return {Object} * @api private * @receiver Query */ /** * Return an update document with corrected $set operations. * * @method _updateForExec * @api private * @receiver Query */ Query.prototype._updateForExec = function() { var update = utils.clone(this._update, { retainKeyOrder: true, transform: false, depopulate: true }); var ops = Object.keys(update); var i = ops.length; var ret = {}; while (i--) { var op = ops[i]; if (this.options.overwrite) { ret[op] = update[op]; continue; } if ('$' !== op[0]) { // fix up $set sugar if (!ret.$set) { if (update.$set) { ret.$set = update.$set; } else { ret.$set = {}; } } ret.$set[op] = update[op]; ops.splice(i, 1); if (!~ops.indexOf('$set')) ops.push('$set'); } else if ('$set' === op) { if (!ret.$set) { ret[op] = update[op]; } } else { ret[op] = update[op]; } } return ret; }; /** * Makes sure _path is set. * * @method _ensurePath * @param {String} method * @api private * @receiver Query */ /** * Determines if `conds` can be merged using `mquery().merge()` * * @method canMerge * @memberOf Query * @param {Object} conds * @return {Boolean} * @api private */ /** * Returns default options for this query. * * @param {Model} model * @api private */ Query.prototype._optionsForExec = function(model) { var options = Query.base._optionsForExec.call(this); delete options.populate; delete options.retainKeyOrder; model = model || this.model; if (!model) { return options; } if (!('safe' in options) && model.schema.options.safe) { options.safe = model.schema.options.safe; } if (!('readPreference' in options) && model.schema.options.read) { options.readPreference = model.schema.options.read; } if (options.upsert !== void 0) { options.upsert = !!options.upsert; } return options; }; /** * Sets the lean option. * * Documents returned from queries with the `lean` option enabled are plain javascript objects, not [MongooseDocuments](#document-js). They have no `save` method, getters/setters or other Mongoose magic applied. * * ####Example: * * new Query().lean() // true * new Query().lean(true) * new Query().lean(false) * * Model.find().lean().exec(function (err, docs) { * docs[0] instanceof mongoose.Document // false * }); * * This is a [great](https://groups.google.com/forum/#!topic/mongoose-orm/u2_DzDydcnA/discussion) option in high-performance read-only scenarios, especially when combined with [stream](#query_Query-stream). * * @param {Boolean|Object} bool defaults to true * @return {Query} this * @api public */ Query.prototype.lean = function(v) { this._mongooseOptions.lean = arguments.length ? v : true; return this; }; /** * Gets/sets the error flag on this query. If this flag is not null or * undefined, the `exec()` promise will reject without executing. * * ####Example: * * Query().error(); // Get current error value * Query().error(null); // Unset the current error * Query().error(new Error('test')); // `exec()` will resolve with test * Schema.pre('find', function() { * if (!this.getQuery().userId) { * this.error(new Error('Not allowed to query without setting userId')); * } * }); * * Note that query casting runs **after** hooks, so cast errors will override * custom errors. * * ####Example: * var TestSchema = new Schema({ num: Number }); * var TestModel = db.model('Test', TestSchema); * TestModel.find({ num: 'not a number' }).error(new Error('woops')).exec(function(error) { * // `error` will be a cast error because `num` failed to cast * }); * * @param {Error|null} err if set, `exec()` will fail fast before sending the query to MongoDB * @returns {Query} this * @api public */ Query.prototype.error = function error(err) { if (arguments.length === 0) { return this._error; } this._error = err; return this; }; /*! * ignore */ Query.prototype._unsetCastError = function _unsetCastError() { if (this._error != null && !(this._error instanceof CastError)) { return; } return this.error(null); }; /** * Getter/setter around the current mongoose-specific options for this query * (populate, lean, etc.) * * @param {Object} options if specified, overwrites the current options * @returns {Object} the options * @api public */ Query.prototype.mongooseOptions = function(v) { if (arguments.length > 0) { this._mongooseOptions = v; } return this._mongooseOptions; }; /*! * ignore */ Query.prototype._castConditions = function() { try { this.cast(this.model); this._unsetCastError(); } catch (err) { this.error(err); } }; /** * Thunk around find() * * @param {Function} [callback] * @return {Query} this * @api private */ Query.prototype._find = function(callback) { this._castConditions(); if (this.error() != null) { callback(this.error()); return this; } this._applyPaths(); this._fields = this._castFields(this._fields); var fields = this._fieldsForExec(); var mongooseOptions = this._mongooseOptions; var _this = this; var userProvidedFields = _this._userProvidedFields || {}; var cb = function(err, docs) { if (err) { return callback(err); } if (docs.length === 0) { return callback(null, docs); } if (!mongooseOptions.populate) { return !!mongooseOptions.lean === true ? callback(null, docs) : completeMany(_this.model, docs, fields, userProvidedFields, null, callback); } var pop = helpers.preparePopulationOptionsMQ(_this, mongooseOptions); pop.__noPromise = true; _this.model.populate(docs, pop, function(err, docs) { if (err) return callback(err); return !!mongooseOptions.lean === true ? callback(null, docs) : completeMany(_this.model, docs, fields, userProvidedFields, pop, callback); }); }; var options = this._optionsForExec(); options.fields = this._fieldsForExec(); var filter = this._conditions; return this._collection.find(filter, options, cb); }; /** * Finds documents. * * When no `callback` is passed, the query is not executed. When the query is executed, the result will be an array of documents. * * ####Example * * query.find({ name: 'Los Pollos Hermanos' }).find(callback) * * @param {Object} [filter] mongodb selector * @param {Function} [callback] * @return {Query} this * @api public */ Query.prototype.find = function(conditions, callback) { if (typeof conditions === 'function') { callback = conditions; conditions = {}; } conditions = utils.toObject(conditions); if (mquery.canMerge(conditions)) { this.merge(conditions); prepareDiscriminatorCriteria(this); } else if (conditions != null) { this.error(new ObjectParameterError(conditions, 'filter', 'find')); } // if we don't have a callback, then just return the query object if (!callback) { return Query.base.find.call(this); } this._find(callback); return this; }; /** * Merges another Query or conditions object into this one. * * When a Query is passed, conditions, field selection and options are merged. * * @param {Query|Object} source * @return {Query} this */ Query.prototype.merge = function(source) { if (!source) { return this; } var opts = { retainKeyOrder: this.options.retainKeyOrder, overwrite: true }; if (source instanceof Query) { // if source has a feature, apply it to ourselves if (source._conditions) { utils.merge(this._conditions, source._conditions, opts); } if (source._fields) { this._fields || (this._fields = {}); utils.merge(this._fields, source._fields, opts); } if (source.options) { this.options || (this.options = {}); utils.merge(this.options, source.options, opts); } if (source._update) { this._update || (this._update = {}); utils.mergeClone(this._update, source._update); } if (source._distinct) { this._distinct = source._distinct; } return this; } // plain object utils.merge(this._conditions, source, opts); return this; }; /*! * hydrates many documents * * @param {Model} model * @param {Array} docs * @param {Object} fields * @param {Query} self * @param {Array} [pop] array of paths used in population * @param {Function} callback */ function completeMany(model, docs, fields, userProvidedFields, pop, callback) { var arr = []; var count = docs.length; var len = count; var opts = pop ? { populated: pop } : undefined; var error = null; function init(_error) { if (error != null) { return; } if (_error != null) { error = _error; return callback(error); } --count || callback(null, arr); } for (var i = 0; i < len; ++i) { arr[i] = helpers.createModel(model, docs[i], fields, userProvidedFields); arr[i].init(docs[i], opts, init); } } /** * Adds a collation to this op (MongoDB 3.4 and up) * * @param {Object} value * @return {Query} this * @see MongoDB docs https://docs.mongodb.com/manual/reference/method/cursor.collation/#cursor.collation * @api public */ Query.prototype.collation = function(value) { if (this.options == null) { this.options = {}; } this.options.collation = value; return this; }; /** * Thunk around findOne() * * @param {Function} [callback] * @see findOne http://docs.mongodb.org/manual/reference/method/db.collection.findOne/ * @api private */ Query.prototype._findOne = function(callback) { this._castConditions(); if (this.error()) { return callback(this.error()); } this._applyPaths(); this._fields = this._castFields(this._fields); var options = this._mongooseOptions; var projection = this._fieldsForExec(); var userProvidedFields = this._userProvidedFields || {}; var _this = this; // don't pass in the conditions because we already merged them in Query.base.findOne.call(_this, {}, function(err, doc) { if (err) { return callback(err); } if (!doc) { return callback(null, null); } if (!options.populate) { return !!options.lean === true ? callback(null, doc) : completeOne(_this.model, doc, null, {}, projection, userProvidedFields, null, callback); } var pop = helpers.preparePopulationOptionsMQ(_this, options); pop.__noPromise = true; _this.model.populate(doc, pop, function(err, doc) { if (err) { return callback(err); } return !!options.lean === true ? callback(null, doc) : completeOne(_this.model, doc, null, {}, projection, userProvidedFields, pop, callback); }); }); }; /** * Declares the query a findOne operation. When executed, the first found document is passed to the callback. * * Passing a `callback` executes the query. The result of the query is a single document. * * * *Note:* `conditions` is optional, and if `conditions` is null or undefined, * mongoose will send an empty `findOne` command to MongoDB, which will return * an arbitrary document. If you're querying by `_id`, use `Model.findById()` * instead. * * This function triggers the following middleware: * - `findOne()` * * ####Example * * var query = Kitten.where({ color: 'white' }); * query.findOne(function (err, kitten) { * if (err) return handleError(err); * if (kitten) { * // doc may be null if no document matched * } * }); * * @param {Object} [filter] mongodb selector * @param {Object} [projection] optional fields to return * @param {Object} [options] see [`setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions) * @param {Function} [callback] optional params are (error, document) * @return {Query} this * @see findOne http://docs.mongodb.org/manual/reference/method/db.collection.findOne/ * @see Query.select #query_Query-select * @api public */ Query.prototype.findOne = function(conditions, projection, options, callback) { if (typeof conditions === 'function') { callback = conditions; conditions = null; projection = null; options = null; } else if (typeof projection === 'function') { callback = projection; options = null; projection = null; } else if (typeof options === 'function') { callback = options; options = null; } // make sure we don't send in the whole Document to merge() conditions = utils.toObject(conditions); this.op = 'findOne'; if (options) { this.setOptions(options); } if (projection) { this.select(projection); } if (mquery.canMerge(conditions)) { this.merge(conditions); prepareDiscriminatorCriteria(this); try { this.cast(this.model); this.error(null); } catch (err) { this.error(err); } } else if (conditions != null) { this.error(new ObjectParameterError(conditions, 'filter', 'findOne')); } if (!callback) { // already merged in the conditions, don't need to send them in. return Query.base.findOne.call(this); } this._findOne(callback); return this; }; /** * Thunk around count() * * @param {Function} [callback] * @see count http://docs.mongodb.org/manual/reference/method/db.collection.count/ * @api private */ Query.prototype._count = function(callback) { try { this.cast(this.model); } catch (err) { this.error(err); } if (this.error()) { return callback(this.error()); } var conds = this._conditions; var options = this._optionsForExec(); this._collection.count(conds, options, utils.tick(callback)); }; /** * Specifying this query as a `count` query. * * Passing a `callback` executes the query. * * This function triggers the following middleware: * - `count()` * * ####Example: * * var countQuery = model.where({ 'color': 'black' }).count(); * * query.count({ color: 'black' }).count(callback) * * query.count({ color: 'black' }, callback) * * query.where('color', 'black').count(function (err, count) { * if (err) return handleError(err); * console.log('there are %d kittens', count); * }) * * @param {Object} [criteria] mongodb selector * @param {Function} [callback] optional params are (error, count) * @return {Query} this * @see count http://docs.mongodb.org/manual/reference/method/db.collection.count/ * @api public */ Query.prototype.count = function(conditions, callback) { if (typeof conditions === 'function') { callback = conditions; conditions = undefined; } if (mquery.canMerge(conditions)) { this.merge(conditions); } this.op = 'count'; if (!callback) { return this; } this._count(callback); return this; }; /** * Declares or executes a distict() operation. * * Passing a `callback` executes the query. * * This function does not trigger any middleware. * * ####Example * * distinct(field, conditions, callback) * distinct(field, conditions) * distinct(field, callback) * distinct(field) * distinct(callback) * distinct() * * @param {String} [field] * @param {Object|Query} [criteria] * @param {Function} [callback] optional params are (error, arr) * @return {Query} this * @see distinct http://docs.mongodb.org/manual/reference/method/db.collection.distinct/ * @api public */ Query.prototype.distinct = function(field, conditions, callback) { if (!callback) { if (typeof conditions === 'function') { callback = conditions; conditions = undefined; } else if (typeof field === 'function') { callback = field; field = undefined; conditions = undefined; } } conditions = utils.toObject(conditions); if (mquery.canMerge(conditions)) { this.merge(conditions); } try { this.cast(this.model); } catch (err) { if (!callback) { throw err; } callback(err); return this; } return Query.base.distinct.call(this, {}, field, callback); }; /** * Sets the sort order * * If an object is passed, values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`. * * If a string is passed, it must be a space delimited list of path names. The * sort order of each path is ascending unless the path name is prefixed with `-` * which will be treated as descending. * * ####Example * * // sort by "field" ascending and "test" descending * query.sort({ field: 'asc', test: -1 }); * * // equivalent * query.sort('field -test'); * * ####Note * * Cannot be used with `distinct()` * * @param {Object|String} arg * @return {Query} this * @see cursor.sort http://docs.mongodb.org/manual/reference/method/cursor.sort/ * @api public */ Query.prototype.sort = function(arg) { if (arguments.length > 1) { throw new Error('sort() only takes 1 Argument'); } return Query.base.sort.call(this, arg); }; /** * Declare and/or execute this query as a remove() operation. * * This function does not trigger any middleware * * ####Example * * Model.remove({ artist: 'Anne Murray' }, callback) * * ####Note * * The operation is only executed when a callback is passed. To force execution without a callback, you must first call `remove()` and then execute it by using the `exec()` method. * * // not executed * var query = Model.find().remove({ name: 'Anne Murray' }) * * // executed * query.remove({ name: 'Anne Murray' }, callback) * query.remove({ name: 'Anne Murray' }).remove(callback) * * // executed without a callback * query.exec() * * // summary * query.remove(conds, fn); // executes * query.remove(conds) * query.remove(fn) // executes * query.remove() * * @param {Object|Query} [filter] mongodb selector * @param {Function} [callback] optional params are (error, writeOpResult) * @return {Query} this * @see writeOpResult http://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html#~WriteOpResult * @see remove http://docs.mongodb.org/manual/reference/method/db.collection.remove/ * @api public */ Query.prototype.remove = function(filter, callback) { if (typeof filter === 'function') { callback = filter; filter = null; } filter = utils.toObject(filter, { retainKeyOrder: true }); try { this.cast(this.model, filter); this.merge(filter); } catch (err) { this.error(err); } prepareDiscriminatorCriteria(this); if (!callback) { return Query.base.remove.call(this); } return this._remove(callback); }; /*! * ignore */ Query.prototype._remove = function(callback) { if (this.error() != null) { callback(this.error()); return this; } return Query.base.remove.call(this, callback); }; /** * Declare and/or execute this query as a `deleteOne()` operation. Works like * remove, except it deletes at most one document regardless of the `single` * option. * * This function does not trigger any middleware. * * ####Example * * Character.deleteOne({ name: 'Eddard Stark' }, ca