You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2623 lines
66 KiB

  1. /*!
  2. * Module dependencies.
  3. */
  4. var utils = require('./utils')
  5. , merge = utils.merge
  6. , Promise = require('./promise')
  7. , Document = require('./document')
  8. , Types = require('./schema/index')
  9. , inGroupsOf = utils.inGroupsOf
  10. , tick = utils.tick
  11. , QueryStream = require('./querystream')
  12. , helpers = require('./queryhelpers')
  13. , ReadPref = require('mongodb').ReadPreference
  14. /**
  15. * Query constructor used for building queries.
  16. *
  17. * ####Example:
  18. *
  19. * var query = Model.find();
  20. * query.where('age').gte(21).exec(callback);
  21. *
  22. * @param {Object} criteria
  23. * @param {Object} options
  24. * @api public
  25. */
  26. function Query (criteria, options) {
  27. this.setOptions(options, true);
  28. this._conditions = {};
  29. this._updateArg = {};
  30. this._fields = undefined;
  31. this._geoComparison = undefined;
  32. if (criteria) this.find(criteria);
  33. }
  34. /**
  35. * Sets query options.
  36. *
  37. * ####Options:
  38. *
  39. * - [tailable](http://www.mongodb.org/display/DOCS/Tailable+Cursors) *
  40. * - [sort](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsort(\)%7D%7D) *
  41. * - [limit](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D) *
  42. * - [skip](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D) *
  43. * - [maxscan](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24maxScan) *
  44. * - [batchSize](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D) *
  45. * - [comment](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment) *
  46. * - [snapshot](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsnapshot%28%29%7D%7D) *
  47. * - [hint](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint) *
  48. * - [slaveOk](http://docs.mongodb.org/manual/applications/replication/#read-preference) *
  49. * - [lean](./api.html#query_Query-lean) *
  50. *
  51. * All other [options](http://mongodb.github.io/node-mongodb-native/api-generated/cursor.html#constructor) specified will be passed unaltered to the driver.
  52. *
  53. * _* denotes a query helper method is also available_
  54. *
  55. * @param {Object} options
  56. * @api public
  57. */
  58. Query.prototype.setOptions = function (options, overwrite) {
  59. // overwrite is internal use only
  60. if (overwrite) {
  61. options = this.options = options || {};
  62. this.safe = options.safe;
  63. if ('populate' in options) {
  64. this.populate(this.options.populate);
  65. }
  66. return this;
  67. }
  68. if (!(options && 'Object' == options.constructor.name))
  69. return this;
  70. if ('safe' in options)
  71. this.safe = options.safe;
  72. // set arbitrary options
  73. var methods = Object.keys(options)
  74. , i = methods.length
  75. , method
  76. while (i--) {
  77. method = methods[i];
  78. // use methods if exist (safer option manipulation)
  79. if ('function' == typeof this[method]) {
  80. var args = Array.isArray(options[method])
  81. ? options[method]
  82. : [options[method]];
  83. this[method].apply(this, args)
  84. } else {
  85. this.options[method] = options[method];
  86. }
  87. }
  88. return this;
  89. }
  90. /**
  91. * Binds this query to a model.
  92. *
  93. * @param {Model} model the model to which the query is bound
  94. * @param {String} op the operation to execute
  95. * @param {Object} updateArg used in update methods
  96. * @return {Query}
  97. * @api private
  98. */
  99. Query.prototype.bind = function bind (model, op, updateArg) {
  100. this.model = model;
  101. this.op = op;
  102. if (model._mapreduce) this.options.lean = true;
  103. if (op == 'update' || op == 'findOneAndUpdate') {
  104. merge(this._updateArg, updateArg || {});
  105. }
  106. return this;
  107. };
  108. /**
  109. * Executes the query
  110. *
  111. * ####Examples
  112. *
  113. * query.exec();
  114. * query.exec(callback);
  115. * query.exec('update');
  116. * query.exec('find', callback);
  117. *
  118. * @param {String|Function} [operation]
  119. * @param {Function} [callback]
  120. * @return {Promise}
  121. * @api public
  122. */
  123. Query.prototype.exec = function exec (op, callback) {
  124. var promise = new Promise();
  125. switch (typeof op) {
  126. case 'function':
  127. callback = op;
  128. op = null;
  129. break;
  130. case 'string':
  131. this.op = op;
  132. break;
  133. }
  134. if (callback) promise.addBack(callback);
  135. if (!this.op) {
  136. promise.complete();
  137. return promise;
  138. }
  139. if ('update' == this.op) {
  140. this[this.op](this._updateArg, promise.resolve.bind(promise));
  141. return promise;
  142. }
  143. if ('distinct' == this.op) {
  144. this.distinct(this._distinctArg, promise.resolve.bind(promise));
  145. return promise;
  146. }
  147. this[this.op](promise.resolve.bind(promise));
  148. return promise;
  149. };
  150. /**
  151. * Finds documents.
  152. *
  153. * When no `callback` is passed, the query is not executed.
  154. *
  155. * ####Example
  156. *
  157. * query.find({ name: 'Los Pollos Hermanos' }).find(callback)
  158. *
  159. * @param {Object} [criteria] mongodb selector
  160. * @param {Function} [callback]
  161. * @return {Query} this
  162. * @api public
  163. */
  164. Query.prototype.find = function (criteria, callback) {
  165. this.op = 'find';
  166. if ('function' === typeof criteria) {
  167. callback = criteria;
  168. criteria = {};
  169. } else if (criteria instanceof Query) {
  170. // TODO Merge options, too
  171. merge(this._conditions, criteria._conditions);
  172. } else if (criteria instanceof Document) {
  173. merge(this._conditions, criteria.toObject());
  174. } else if (criteria && 'Object' === criteria.constructor.name) {
  175. merge(this._conditions, criteria);
  176. }
  177. if (!callback) return this;
  178. return this.execFind(callback);
  179. };
  180. /**
  181. * Casts this query to the schema of `model`
  182. *
  183. * ####Note
  184. *
  185. * If `obj` is present, it is cast instead of this query.
  186. *
  187. * @param {Model} model
  188. * @param {Object} [obj]
  189. * @return {Object}
  190. * @api public
  191. */
  192. Query.prototype.cast = function (model, obj) {
  193. obj || (obj= this._conditions);
  194. var schema = model.schema
  195. , paths = Object.keys(obj)
  196. , i = paths.length
  197. , any$conditionals
  198. , schematype
  199. , nested
  200. , path
  201. , type
  202. , val;
  203. while (i--) {
  204. path = paths[i];
  205. val = obj[path];
  206. if ('$or' === path || '$nor' === path || '$and' === path) {
  207. var k = val.length
  208. , orComponentQuery;
  209. while (k--) {
  210. orComponentQuery = new Query(val[k]);
  211. orComponentQuery.cast(model);
  212. val[k] = orComponentQuery._conditions;
  213. }
  214. } else if (path === '$where') {
  215. type = typeof val;
  216. if ('string' !== type && 'function' !== type) {
  217. throw new Error("Must have a string or function for $where");
  218. }
  219. if ('function' === type) {
  220. obj[path] = val.toString();
  221. }
  222. continue;
  223. } else {
  224. if (!schema) {
  225. // no casting for Mixed types
  226. continue;
  227. }
  228. schematype = schema.path(path);
  229. if (!schematype) {
  230. // Handle potential embedded array queries
  231. var split = path.split('.')
  232. , j = split.length
  233. , pathFirstHalf
  234. , pathLastHalf
  235. , remainingConds
  236. , castingQuery;
  237. // Find the part of the var path that is a path of the Schema
  238. while (j--) {
  239. pathFirstHalf = split.slice(0, j).join('.');
  240. schematype = schema.path(pathFirstHalf);
  241. if (schematype) break;
  242. }
  243. // If a substring of the input path resolves to an actual real path...
  244. if (schematype) {
  245. // Apply the casting; similar code for $elemMatch in schema/array.js
  246. if (schematype.caster && schematype.caster.schema) {
  247. remainingConds = {};
  248. pathLastHalf = split.slice(j).join('.');
  249. remainingConds[pathLastHalf] = val;
  250. castingQuery = new Query(remainingConds);
  251. castingQuery.cast(schematype.caster);
  252. obj[path] = castingQuery._conditions[pathLastHalf];
  253. } else {
  254. obj[path] = val;
  255. }
  256. continue;
  257. }
  258. if (utils.isObject(val)) {
  259. // handle geo schemas that use object notation
  260. // { loc: { long: Number, lat: Number }
  261. var geo = val.$near ? '$near' :
  262. val.$nearSphere ? '$nearSphere' :
  263. val.$within ? '$within' :
  264. val.$geoIntersects ? '$geoIntersects' : '';
  265. if (!geo) {
  266. continue;
  267. }
  268. var numbertype = new Types.Number('__QueryCasting__')
  269. var value = val[geo];
  270. if (val.$maxDistance) {
  271. val.$maxDistance = numbertype.castForQuery(val.$maxDistance);
  272. }
  273. if ('$within' == geo) {
  274. var withinType = value.$center
  275. || value.$centerSphere
  276. || value.$box
  277. || value.$polygon;
  278. if (!withinType) {
  279. throw new Error('Bad $within paramater: ' + JSON.stringify(val));
  280. }
  281. value = withinType;
  282. } else if ('$near' == geo &&
  283. 'string' == typeof value.type && Array.isArray(value.coordinates)) {
  284. // geojson; cast the coordinates
  285. value = value.coordinates;
  286. } else if (('$near' == geo || '$geoIntersects' == geo) &&
  287. value.$geometry && 'string' == typeof value.$geometry.type &&
  288. Array.isArray(value.$geometry.coordinates)) {
  289. // geojson; cast the coordinates
  290. value = value.$geometry.coordinates;
  291. }
  292. ;(function _cast (val) {
  293. if (Array.isArray(val)) {
  294. val.forEach(function (item, i) {
  295. if (Array.isArray(item) || utils.isObject(item)) {
  296. return _cast(item);
  297. }
  298. val[i] = numbertype.castForQuery(item);
  299. });
  300. } else {
  301. var nearKeys= Object.keys(val);
  302. var nearLen = nearKeys.length;
  303. while (nearLen--) {
  304. var nkey = nearKeys[nearLen];
  305. var item = val[nkey];
  306. if (Array.isArray(item) || utils.isObject(item)) {
  307. _cast(item);
  308. val[nkey] = item;
  309. } else {
  310. val[nkey] = numbertype.castForQuery(item);
  311. }
  312. }
  313. }
  314. })(value);
  315. }
  316. } else if (val === null || val === undefined) {
  317. continue;
  318. } else if ('Object' === val.constructor.name) {
  319. any$conditionals = Object.keys(val).some(function (k) {
  320. return k.charAt(0) === '$' && k !== '$id' && k !== '$ref';
  321. });
  322. if (!any$conditionals) {
  323. obj[path] = schematype.castForQuery(val);
  324. } else {
  325. var ks = Object.keys(val)
  326. , k = ks.length
  327. , $cond;
  328. while (k--) {
  329. $cond = ks[k];
  330. nested = val[$cond];
  331. if ('$exists' === $cond) {
  332. if ('boolean' !== typeof nested) {
  333. throw new Error("$exists parameter must be Boolean");
  334. }
  335. continue;
  336. }
  337. if ('$type' === $cond) {
  338. if ('number' !== typeof nested) {
  339. throw new Error("$type parameter must be Number");
  340. }
  341. continue;
  342. }
  343. if ('$not' === $cond) {
  344. this.cast(model, nested);
  345. } else {
  346. val[$cond] = schematype.castForQuery($cond, nested);
  347. }
  348. }
  349. }
  350. } else {
  351. obj[path] = schematype.castForQuery(val);
  352. }
  353. }
  354. }
  355. return obj;
  356. };
  357. /**
  358. * Returns default options.
  359. * @param {Model} model
  360. * @api private
  361. */
  362. Query.prototype._optionsForExec = function (model) {
  363. var options = utils.clone(this.options, { retainKeyOrder: true });
  364. delete options.populate;
  365. if (!('safe' in options))
  366. options.safe = model.schema.options.safe;
  367. if (!('readPreference' in options) && model.schema.options.read)
  368. options.readPreference = model.schema.options.read;
  369. return options;
  370. };
  371. /**
  372. * Applies schematype selected options to this query.
  373. * @api private
  374. */
  375. Query.prototype._applyPaths = function applyPaths () {
  376. // determine if query is selecting or excluding fields
  377. var fields = this._fields
  378. , exclude
  379. , keys
  380. , ki
  381. if (fields) {
  382. keys = Object.keys(fields);
  383. ki = keys.length;
  384. while (ki--) {
  385. if ('+' == keys[ki][0]) continue;
  386. exclude = 0 === fields[keys[ki]];
  387. break;
  388. }
  389. }
  390. // if selecting, apply default schematype select:true fields
  391. // if excluding, apply schematype select:false fields
  392. var selected = []
  393. , excluded = []
  394. , seen = [];
  395. analyzeSchema(this.model.schema);
  396. switch (exclude) {
  397. case true:
  398. excluded.length && this.select('-' + excluded.join(' -'));
  399. break;
  400. case false:
  401. selected.length && this.select(selected.join(' '));
  402. break;
  403. case undefined:
  404. // user didn't specify fields, implies returning all fields.
  405. // only need to apply excluded fields
  406. excluded.length && this.select('-' + excluded.join(' -'));
  407. break;
  408. }
  409. return seen = excluded = selected = keys = fields = null;
  410. function analyzeSchema (schema, prefix) {
  411. prefix || (prefix = '');
  412. // avoid recursion
  413. if (~seen.indexOf(schema)) return;
  414. seen.push(schema);
  415. schema.eachPath(function (path, type) {
  416. if (prefix) path = prefix + '.' + path;
  417. analyzePath(path, type);
  418. // array of subdocs?
  419. if (type.schema) {
  420. analyzeSchema(type.schema, path);
  421. }
  422. });
  423. }
  424. function analyzePath (path, type) {
  425. if ('boolean' != typeof type.selected) return;
  426. var plusPath = '+' + path;
  427. if (fields && plusPath in fields) {
  428. // forced inclusion
  429. delete fields[plusPath];
  430. // if there are other fields being included, add this one
  431. // if no other included fields, leave this out (implied inclusion)
  432. if (false === exclude && keys.length > 1 && !~keys.indexOf(path)) {
  433. fields[path] = 1;
  434. }
  435. return
  436. };
  437. // check for parent exclusions
  438. var root = path.split('.')[0];
  439. if (~excluded.indexOf(root)) return;
  440. ;(type.selected ? selected : excluded).push(path);
  441. }
  442. }
  443. /**
  444. * Specifies a javascript function or expression to pass to MongoDBs query system.
  445. *
  446. * ####Example
  447. *
  448. * query.$where('this.comments.length > 10 || this.name.length > 5')
  449. *
  450. * // or
  451. *
  452. * query.$where(function () {
  453. * return this.comments.length > 10 || this.name.length > 5;
  454. * })
  455. *
  456. * ####NOTE:
  457. *
  458. * Only use `$where` when you have a condition that cannot be met using other MongoDB operators like `$lt`.
  459. * **Be sure to read about all of [its caveats](http://docs.mongodb.org/manual/reference/operator/where/) before using.**
  460. *
  461. * @see $where http://docs.mongodb.org/manual/reference/operator/where/
  462. * @param {String|Function} js javascript string or function
  463. * @return {Query} this
  464. * @memberOf Query
  465. * @method $where
  466. * @api public
  467. */
  468. Query.prototype.$where = function (js) {
  469. this._conditions['$where'] = js;
  470. return this;
  471. };
  472. /**
  473. * Specifies a `path` for use with chaining.
  474. *
  475. * ####Example
  476. *
  477. * // instead of writing:
  478. * User.find({age: {$gte: 21, $lte: 65}}, callback);
  479. *
  480. * // we can instead write:
  481. * User.where('age').gte(21).lte(65);
  482. *
  483. * // Moreover, you can also chain a bunch of these together:
  484. *
  485. * User
  486. * .where('age').gte(21).lte(65)
  487. * .where('name', /^b/i)
  488. * .where('friends').slice(10)
  489. * .exec(callback)
  490. *
  491. * @param {String} [path]
  492. * @param {Object} [val]
  493. * @return {Query} this
  494. * @api public
  495. */
  496. Query.prototype.where = function (path, val) {
  497. if (!arguments.length) return this;
  498. if ('string' != typeof path) {
  499. throw new TypeError('path must be a string');
  500. }
  501. this._currPath = path;
  502. if (2 === arguments.length) {
  503. this._conditions[path] = val;
  504. }
  505. return this;
  506. };
  507. /**
  508. * Specifies the complementary comparison value for paths specified with `where()`
  509. *
  510. * ####Example
  511. *
  512. * User.where('age').equals(49);
  513. *
  514. * // is the same as
  515. *
  516. * User.where('age', 49);
  517. *
  518. * @param {Object} val
  519. * @return {Query} this
  520. * @api public
  521. */
  522. Query.prototype.equals = function equals (val) {
  523. var path = this._currPath;
  524. if (!path) throw new Error('equals() must be used after where()');
  525. this._conditions[path] = val;
  526. return this;
  527. }
  528. /**
  529. * Specifies arguments for an `$or` condition.
  530. *
  531. * ####Example
  532. *
  533. * query.or([{ color: 'red' }, { status: 'emergency' }])
  534. *
  535. * @see $or http://docs.mongodb.org/manual/reference/operator/or/
  536. * @param {Array} array array of conditions
  537. * @return {Query} this
  538. * @api public
  539. */
  540. Query.prototype.or = function or (array) {
  541. var or = this._conditions.$or || (this._conditions.$or = []);
  542. if (!Array.isArray(array)) array = [array];
  543. or.push.apply(or, array);
  544. return this;
  545. }
  546. /**
  547. * Specifies arguments for a `$nor` condition.
  548. *
  549. * ####Example
  550. *
  551. * query.nor([{ color: 'green' }, { status: 'ok' }])
  552. *
  553. * @see $nor http://docs.mongodb.org/manual/reference/operator/nor/
  554. * @param {Array} array array of conditions
  555. * @return {Query} this
  556. * @api public
  557. */
  558. Query.prototype.nor = function nor (array) {
  559. var nor = this._conditions.$nor || (this._conditions.$nor = []);
  560. if (!Array.isArray(array)) array = [array];
  561. nor.push.apply(nor, array);
  562. return this;
  563. }
  564. /**
  565. * Specifies arguments for a `$and` condition.
  566. *
  567. * ####Example
  568. *
  569. * query.and([{ color: 'green' }, { status: 'ok' }])
  570. *
  571. * @see $and http://docs.mongodb.org/manual/reference/operator/and/
  572. * @param {Array} array array of conditions
  573. * @return {Query} this
  574. * @api public
  575. */
  576. Query.prototype.and = function and (array) {
  577. var and = this._conditions.$and || (this._conditions.$and = []);
  578. if (!Array.isArray(array)) array = [array];
  579. and.push.apply(and, array);
  580. return this;
  581. }
  582. /**
  583. * Specifies a $gt query condition.
  584. *
  585. * When called with one argument, the most recent path passed to `where()` is used.
  586. *
  587. * ####Example
  588. *
  589. * Thing.find().where('age').gt(21)
  590. *
  591. * // or
  592. * Thing.find().gt('age', 21)
  593. *
  594. * @see $gt http://docs.mongodb.org/manual/reference/operator/gt/
  595. * @method gt
  596. * @memberOf Query
  597. * @param {String} path
  598. * @param {Number} val
  599. * @api public
  600. */
  601. /**
  602. * Specifies a $gte query condition.
  603. *
  604. * When called with one argument, the most recent path passed to `where()` is used.
  605. *
  606. * @see $gte http://docs.mongodb.org/manual/reference/operator/gte/
  607. * @method gte
  608. * @memberOf Query
  609. * @param {String} path
  610. * @param {Number} val
  611. * @api public
  612. */
  613. /**
  614. * Specifies a $lt query condition.
  615. *
  616. * When called with one argument, the most recent path passed to `where()` is used.
  617. *
  618. * @see $lt http://docs.mongodb.org/manual/reference/operator/lt/
  619. * @method lt
  620. * @memberOf Query
  621. * @param {String} path
  622. * @param {Number} val
  623. * @api public
  624. */
  625. /**
  626. * Specifies a $lte query condition.
  627. *
  628. * When called with one argument, the most recent path passed to `where()` is used.
  629. *
  630. * @see $lte http://docs.mongodb.org/manual/reference/operator/lte/
  631. * @method lte
  632. * @memberOf Query
  633. * @param {String} path
  634. * @param {Number} val
  635. * @api public
  636. */
  637. /**
  638. * Specifies a $ne query condition.
  639. *
  640. * When called with one argument, the most recent path passed to `where()` is used.
  641. *
  642. * @see $ne http://docs.mongodb.org/manual/reference/operator/ne/
  643. * @method ne
  644. * @memberOf Query
  645. * @param {String} path
  646. * @param {Number} val
  647. * @api public
  648. */
  649. /**
  650. * Specifies an $in query condition.
  651. *
  652. * When called with one argument, the most recent path passed to `where()` is used.
  653. *
  654. * @see $in http://docs.mongodb.org/manual/reference/operator/in/
  655. * @method in
  656. * @memberOf Query
  657. * @param {String} path
  658. * @param {Number} val
  659. * @api public
  660. */
  661. /**
  662. * Specifies an $nin query condition.
  663. *
  664. * When called with one argument, the most recent path passed to `where()` is used.
  665. *
  666. * @see $nin http://docs.mongodb.org/manual/reference/operator/nin/
  667. * @method nin
  668. * @memberOf Query
  669. * @param {String} path
  670. * @param {Number} val
  671. * @api public
  672. */
  673. /**
  674. * Specifies an $all query condition.
  675. *
  676. * When called with one argument, the most recent path passed to `where()` is used.
  677. *
  678. * @see $all http://docs.mongodb.org/manual/reference/operator/all/
  679. * @method all
  680. * @memberOf Query
  681. * @param {String} path
  682. * @param {Number} val
  683. * @api public
  684. */
  685. /**
  686. * Specifies an $size query condition.
  687. *
  688. * ####Example
  689. *
  690. * MyModel.where('tags').size(0).exec(function (err, docs) {
  691. * if (err) return handleError(err);
  692. *
  693. * assert(Array.isArray(docs));
  694. * console.log('documents with 0 tags', docs);
  695. * })
  696. *
  697. * When called with one argument, the most recent path passed to `where()` is used.
  698. *
  699. * @see $size http://docs.mongodb.org/manual/reference/operator/size/
  700. * @method size
  701. * @memberOf Query
  702. * @param {String} path
  703. * @param {Number} val
  704. * @api public
  705. */
  706. /**
  707. * Specifies a $regex query condition.
  708. *
  709. * When called with one argument, the most recent path passed to `where()` is used.
  710. *
  711. * @see $regex http://docs.mongodb.org/manual/reference/operator/regex/
  712. * @method regex
  713. * @memberOf Query
  714. * @param {String} path
  715. * @param {Number} val
  716. * @api public
  717. */
  718. /**
  719. * Specifies a $maxDistance query condition.
  720. *
  721. * When called with one argument, the most recent path passed to `where()` is used.
  722. *
  723. * @see $maxDistance http://docs.mongodb.org/manual/reference/operator/maxDistance/
  724. * @method maxDistance
  725. * @memberOf Query
  726. * @param {String} path
  727. * @param {Number} val
  728. * @api public
  729. */
  730. /*!
  731. * gt, gte, lt, lte, ne, in, nin, all, regex, size, maxDistance
  732. *
  733. * Thing.where('type').nin(array)
  734. */
  735. 'gt gte lt lte ne in nin all regex size maxDistance'.split(' ').forEach(function ($conditional) {
  736. Query.prototype[$conditional] = function (path, val) {
  737. if (arguments.length === 1) {
  738. val = path;
  739. path = this._currPath
  740. }
  741. var conds = this._conditions[path] || (this._conditions[path] = {});
  742. conds['$' + $conditional] = val;
  743. return this;
  744. };
  745. });
  746. /**
  747. * Specifies a `$near` condition
  748. *
  749. * query.near('loc', [10, 20])
  750. * query.near([10, 20])
  751. *
  752. * _NOTE: does not currently support GeoJSON._
  753. *
  754. * @param {String} path
  755. * @param {Array} val
  756. * @return {Query} this
  757. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  758. * @see $near http://docs.mongodb.org/manual/reference/operator/near/
  759. * @api public
  760. */
  761. Query.prototype.near = function (path, val) {
  762. if (arguments.length === 1) {
  763. val = path;
  764. path = this._currPath
  765. } else if (arguments.length === 2 && !Array.isArray(val)) {
  766. val = utils.args(arguments);
  767. path = this._currPath;
  768. } else if (arguments.length === 3) {
  769. val = utils.args(arguments, 1);
  770. }
  771. var conds = this._conditions[path] || (this._conditions[path] = {});
  772. conds.$near = val;
  773. return this;
  774. }
  775. /**
  776. * Specifies a `$nearSphere` condition.
  777. *
  778. * query.nearSphere('loc', [10, 20])
  779. * query.nearSphere([10, 20])
  780. *
  781. * _NOTE: does not currently support GeoJSON._
  782. *
  783. * @param {String} path
  784. * @param {Array} val
  785. * @return {Query} this
  786. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  787. * @see $nearSphere http://docs.mongodb.org/manual/reference/operator/nearSphere/
  788. * @api public
  789. */
  790. Query.prototype.nearSphere = function (path, val) {
  791. if (arguments.length === 1) {
  792. val = path;
  793. path = this._currPath
  794. } else if (arguments.length === 2 && !Array.isArray(val)) {
  795. val = utils.args(arguments);
  796. path = this._currPath;
  797. } else if (arguments.length === 3) {
  798. val = utils.args(arguments, 1);
  799. }
  800. var conds = this._conditions[path] || (this._conditions[path] = {});
  801. conds.$nearSphere = val;
  802. return this;
  803. }
  804. /**
  805. * Specifies a `$mod` condition
  806. *
  807. * @param {String} path
  808. * @param {Number} val
  809. * @return {Query} this
  810. * @see $mod http://docs.mongodb.org/manual/reference/operator/mod/
  811. * @api public
  812. */
  813. Query.prototype.mod = function (path, val) {
  814. if (arguments.length === 1) {
  815. val = path;
  816. path = this._currPath
  817. } else if (arguments.length === 2 && !Array.isArray(val)) {
  818. val = utils.args(arguments);
  819. path = this._currPath;
  820. } else if (arguments.length === 3) {
  821. val = utils.args(arguments, 1);
  822. }
  823. var conds = this._conditions[path] || (this._conditions[path] = {});
  824. conds.$mod = val;
  825. return this;
  826. }
  827. /**
  828. * Specifies an `$exists` condition
  829. *
  830. * @param {String} path
  831. * @param {Number} val
  832. * @return {Query} this
  833. * @see $exists http://docs.mongodb.org/manual/reference/operator/exists/
  834. * @api public
  835. */
  836. Query.prototype.exists = function (path, val) {
  837. if (arguments.length === 0) {
  838. path = this._currPath
  839. val = true;
  840. } else if (arguments.length === 1) {
  841. if ('boolean' === typeof path) {
  842. val = path;
  843. path = this._currPath;
  844. } else {
  845. val = true;
  846. }
  847. }
  848. var conds = this._conditions[path] || (this._conditions[path] = {});
  849. conds['$exists'] = val;
  850. return this;
  851. };
  852. /**
  853. * Specifies an `$elemMatch` condition
  854. *
  855. * ####Example
  856. *
  857. * query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}})
  858. *
  859. * query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}})
  860. *
  861. * query.elemMatch('comment', function (elem) {
  862. * elem.where('author').equals('autobot');
  863. * elem.where('votes').gte(5);
  864. * })
  865. *
  866. * query.where('comment').elemMatch(function (elem) {
  867. * elem.where('author').equals('autobot');
  868. * elem.where('votes').gte(5);
  869. * })
  870. *
  871. * @param {String|Object|Function} path
  872. * @param {Object|Function} criteria
  873. * @return {Query} this
  874. * @see $elemMatch http://docs.mongodb.org/manual/reference/operator/elemMatch/
  875. * @api public
  876. */
  877. Query.prototype.elemMatch = function (path, criteria) {
  878. var block;
  879. if ('Object' === path.constructor.name) {
  880. criteria = path;
  881. path = this._currPath;
  882. } else if ('function' === typeof path) {
  883. block = path;
  884. path = this._currPath;
  885. } else if ('Object' === criteria.constructor.name) {
  886. } else if ('function' === typeof criteria) {
  887. block = criteria;
  888. } else {
  889. throw new Error("Argument error");
  890. }
  891. var conds = this._conditions[path] || (this._conditions[path] = {});
  892. if (block) {
  893. criteria = new Query();
  894. block(criteria);
  895. conds['$elemMatch'] = criteria._conditions;
  896. } else {
  897. conds['$elemMatch'] = criteria;
  898. }
  899. return this;
  900. };
  901. // Spatial queries
  902. /**
  903. * Defines a $within query for `box()`, `center()`, etc
  904. *
  905. * ####Example
  906. *
  907. * query.within.box()
  908. * query.within.center()
  909. * query.within.geometry()
  910. *
  911. * @property within
  912. * @memberOf Query
  913. * @see Query#box #query_Query-box
  914. * @see Query#center #query_Query-center
  915. * @see Query#centerSphere #query_Query-centerSphere
  916. * @see Query#polygon #query_Query-polygon
  917. * @see Query#geometry #query_Query-geometry
  918. * @see $geoWithin http://docs.mongodb.org/manual/reference/operator/within/
  919. * @return {Query} this
  920. * @api public
  921. */
  922. Object.defineProperty(Query.prototype, 'within', {
  923. get: function () {
  924. this._geoComparison = '$within';
  925. return this
  926. }
  927. });
  928. /**
  929. * Declares an intersects query for `geometry()`.
  930. *
  931. * ####Example
  932. *
  933. * query.intersects.geometry({
  934. * type: 'LineString'
  935. * , coordinates: [[180.0, 11.0], [180, 9.0]]
  936. * })
  937. *
  938. * @property intersects
  939. * @see Query#geometry #query_Query-geometry
  940. * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/
  941. * @see geoIntersects http://docs.mongodb.org/manual/reference/operator/geoIntersects/
  942. * @memberOf Query
  943. * @return {Query} this
  944. * @api public
  945. */
  946. Object.defineProperty(Query.prototype, 'intersects', {
  947. get: function () {
  948. this._geoComparison = '$geoIntersects';
  949. return this
  950. }
  951. });
  952. /**
  953. * Specifies a $box condition
  954. *
  955. * ####Example
  956. *
  957. * var lowerLeft = [40.73083, -73.99756]
  958. * var upperRight= [40.741404, -73.988135]
  959. * query.where('loc').within.box({ ll: lowerLeft , ur: upperRight })
  960. *
  961. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  962. * @see Query#within #query_Query-within
  963. * @see $box http://docs.mongodb.org/manual/reference/operator/box/
  964. * @param {String} path
  965. * @param {Object} val
  966. * @return {Query} this
  967. * @api public
  968. */
  969. Query.prototype.box = function (path, val) {
  970. if (arguments.length === 1) {
  971. val = path;
  972. path = this._currPath;
  973. }
  974. var conds = this._conditions[path] || (this._conditions[path] = {});
  975. conds['$within'] = { '$box': [val.ll, val.ur] };
  976. return this;
  977. };
  978. /**
  979. * Specifies a $center condition
  980. *
  981. * ####Example
  982. *
  983. * var area = { center: [50, 50], radius: 10 }
  984. * query.where('loc').within.center(area)
  985. *
  986. * @param {String} path
  987. * @param {Object} val
  988. * @param {Object} [opts] options e.g. { $uniqueDocs: true }
  989. * @return {Query} this
  990. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  991. * @see $center http://docs.mongodb.org/manual/reference/operator/center/
  992. * @api public
  993. */
  994. Query.prototype.center = function (path, val, opts) {
  995. if (arguments.length === 1) {
  996. val = path;
  997. path = this._currPath;
  998. }
  999. var conds = this._conditions[path] || (this._conditions[path] = {});
  1000. conds['$within'] = { '$center': [val.center, val.radius] };
  1001. // copy any options
  1002. if (opts && 'Object' == opts.constructor.name) {
  1003. utils.options(opts, conds.$within);
  1004. }
  1005. return this;
  1006. };
  1007. /**
  1008. * Specifies a $centerSphere condition
  1009. *
  1010. * ####Example
  1011. *
  1012. * var area = { center: [50, 50], radius: 10 }
  1013. * query.where('loc').within.centerSphere(area)
  1014. *
  1015. * @param {String} [path]
  1016. * @param {Object} val
  1017. * @return {Query} this
  1018. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  1019. * @see $centerSphere http://docs.mongodb.org/manual/reference/operator/centerSphere/
  1020. * @api public
  1021. */
  1022. Query.prototype.centerSphere = function (path, val) {
  1023. if (arguments.length === 1) {
  1024. val = path;
  1025. path = this._currPath;
  1026. }
  1027. var conds = this._conditions[path] || (this._conditions[path] = {});
  1028. conds['$within'] = { '$centerSphere': [val.center, val.radius] };
  1029. return this;
  1030. };
  1031. /**
  1032. * Specifies a $polygon condition
  1033. *
  1034. * ####Example
  1035. *
  1036. * var polyA = [ [ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ] ]
  1037. * query.where('loc').within.polygon(polyA)
  1038. *
  1039. * // or
  1040. * var polyB = { a : { x : 10, y : 20 }, b : { x : 15, y : 25 }, c : { x : 20, y : 20 } }
  1041. * query.where('loc').within.polygon(polyB)
  1042. *
  1043. * @param {String} [path]
  1044. * @param {Array|Object} val
  1045. * @return {Query} this
  1046. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  1047. * @see $polygon http://docs.mongodb.org/manual/reference/operator/polygon/
  1048. * @api public
  1049. */
  1050. Query.prototype.polygon = function (path, val) {
  1051. if (arguments.length === 1) {
  1052. val = path;
  1053. path = this._currPath;
  1054. }
  1055. var conds = this._conditions[path] || (this._conditions[path] = {});
  1056. conds['$within'] = { '$polygon': val };
  1057. return this;
  1058. };
  1059. /**
  1060. * Specifies a $geometry condition
  1061. *
  1062. * ####Example
  1063. *
  1064. * var polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]]
  1065. * query.where('loc').within.geometry({ type: 'Polygon', coordinates: polyA })
  1066. *
  1067. * // or
  1068. * var polyB = [[ 0, 0 ], [ 1, 1 ]]
  1069. * query.where('loc').within.geometry({ type: 'LineString', coordinates: polyB })
  1070. *
  1071. * // or
  1072. * var polyC = [ 0, 0 ]
  1073. * query.where('loc').within.geometry({ type: 'Point', coordinates: polyC })
  1074. *
  1075. * // or
  1076. * var polyC = [ 0, 0 ]
  1077. * query.where('loc').intersects.geometry({ type: 'Point', coordinates: polyC })
  1078. *
  1079. * ####NOTE:
  1080. *
  1081. * `geometry()` **must** come after either `intersects` or `within`.
  1082. *
  1083. * The `object` argument must contain `type` and `coordinates` properties.
  1084. * - type {String}
  1085. * - coordinates {Array}
  1086. *
  1087. * When called with one argument, the most recent path passed to `where()` is used.
  1088. *
  1089. * @param {String} [path] Optional name of a path to match against
  1090. * @param {Object} object Must contain a `type` property which is a String and a `coordinates` property which is an Array. See the example.
  1091. * @return {Query} this
  1092. * @see http://docs.mongodb.org/manual/release-notes/2.4/#new-geospatial-indexes-with-geojson-and-improved-spherical-geometry
  1093. * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing
  1094. * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/
  1095. * @api public
  1096. */
  1097. Query.prototype.geometry = function (path, val) {
  1098. if (arguments.length === 1) {
  1099. val = path;
  1100. path = this._currPath;
  1101. }
  1102. var conds = this._conditions[path] || (this._conditions[path] = {});
  1103. if (!this._geoComparison) {
  1104. throw new Error('query.geometry() must come after either `within` or `intersects`');
  1105. }
  1106. conds[this._geoComparison] = { $geometry: val };
  1107. return this;
  1108. };
  1109. /**
  1110. * Specifies which document fields to include or exclude
  1111. *
  1112. * 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).
  1113. *
  1114. * ####Example
  1115. *
  1116. * // include a and b, exclude c
  1117. * query.select('a b -c');
  1118. *
  1119. * // or you may use object notation, useful when
  1120. * // you have keys already prefixed with a "-"
  1121. * query.select({a: 1, b: 1, c: 0});
  1122. *
  1123. * // force inclusion of field excluded at schema level
  1124. * query.select('+path')
  1125. *
  1126. * ####NOTE:
  1127. *
  1128. * _v2 had slightly different syntax such as allowing arrays of field names. This support was removed in v3._
  1129. *
  1130. * @param {Object|String} arg
  1131. * @return {Query} this
  1132. * @see SchemaType
  1133. * @api public
  1134. */
  1135. Query.prototype.select = function select (arg) {
  1136. if (!arg) return this;
  1137. var fields = this._fields || (this._fields = {});
  1138. if ('Object' === arg.constructor.name) {
  1139. Object.keys(arg).forEach(function (field) {
  1140. fields[field] = arg[field];
  1141. });
  1142. } else if (1 === arguments.length && 'string' == typeof arg) {
  1143. arg.split(/\s+/).forEach(function (field) {
  1144. if (!field) return;
  1145. var include = '-' == field[0] ? 0 : 1;
  1146. if (include === 0) field = field.substring(1);
  1147. fields[field] = include;
  1148. });
  1149. } else {
  1150. throw new TypeError('Invalid select() argument. Must be a string or object.');
  1151. }
  1152. return this;
  1153. };
  1154. /**
  1155. * Specifies a $slice projection for an array.
  1156. *
  1157. * ####Example
  1158. *
  1159. * query.slice('comments', 5)
  1160. * query.slice('comments', -5)
  1161. * query.slice('comments', [10, 5])
  1162. * query.where('comments').slice(5)
  1163. * query.where('comments').slice([-10, 5])
  1164. *
  1165. * @param {String} path
  1166. * @param {Number} val number of elements to slice
  1167. * @return {Query} this
  1168. * @see mongodb http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields#RetrievingaSubsetofFields-RetrievingaSubrangeofArrayElements
  1169. * @see $slice http://docs.mongodb.org/manual/reference/projection/slice/#prj._S_slice
  1170. * @api public
  1171. */
  1172. Query.prototype.slice = function (path, val) {
  1173. if (arguments.length === 1) {
  1174. val = path;
  1175. path = this._currPath
  1176. } else if (arguments.length === 2) {
  1177. if ('number' === typeof path) {
  1178. val = [path, val];
  1179. path = this._currPath;
  1180. }
  1181. } else if (arguments.length === 3) {
  1182. val = utils.args(arguments, 1);
  1183. }
  1184. var myFields = this._fields || (this._fields = {});
  1185. myFields[path] = { '$slice': val };
  1186. return this;
  1187. };
  1188. /**
  1189. * Sets the sort order
  1190. *
  1191. * If an object is passed, values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`.
  1192. *
  1193. * 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.
  1194. *
  1195. * ####Example
  1196. *
  1197. * // sort by "field" ascending and "test" descending
  1198. * query.sort({ field: 'asc', test: -1 });
  1199. *
  1200. * // equivalent
  1201. * query.sort('field -test');
  1202. *
  1203. * @param {Object|String} arg
  1204. * @return {Query} this
  1205. * @see cursor.sort http://docs.mongodb.org/manual/reference/method/cursor.sort/
  1206. * @api public
  1207. */
  1208. Query.prototype.sort = function (arg) {
  1209. if (!arg) return this;
  1210. var sort = this.options.sort || (this.options.sort = []);
  1211. if ('Object' === arg.constructor.name) {
  1212. Object.keys(arg).forEach(function (field) {
  1213. push(sort, field, arg[field]);
  1214. });
  1215. } else if (1 === arguments.length && 'string' == typeof arg) {
  1216. arg.split(/\s+/).forEach(function (field) {
  1217. if (!field) return;
  1218. var ascend = '-' == field[0] ? -1 : 1;
  1219. if (ascend === -1) field = field.substring(1);
  1220. push(sort, field, ascend);
  1221. });
  1222. } else {
  1223. throw new TypeError('Invalid sort() argument. Must be a string or object.');
  1224. }
  1225. return this;
  1226. };
  1227. /*!
  1228. * @ignore
  1229. */
  1230. function push (arr, field, value) {
  1231. var val = String(value || 1).toLowerCase();
  1232. if (!/^(?:ascending|asc|descending|desc|1|-1)$/.test(val)) {
  1233. if (Array.isArray(value)) value = '['+value+']';
  1234. throw new TypeError('Invalid sort value: {' + field + ': ' + value + ' }');
  1235. }
  1236. arr.push([field, value]);
  1237. }
  1238. /**
  1239. * Specifies the maximum number of documents the query will return.
  1240. *
  1241. * ####Example
  1242. *
  1243. * Kitten.find().limit(20).exec(callback)
  1244. *
  1245. * @method limit
  1246. * @memberOf Query
  1247. * @param {Number} val
  1248. * @api public
  1249. */
  1250. /**
  1251. * Specifies the number of documents to skip.
  1252. *
  1253. * ####Example
  1254. *
  1255. * Kitten.find().skip(100).limit(20)
  1256. *
  1257. * @method skip
  1258. * @memberOf Query
  1259. * @param {Number} val
  1260. * @see cursor.skip http://docs.mongodb.org/manual/reference/method/cursor.skip/
  1261. * @api public
  1262. */
  1263. /**
  1264. * Specifies the maxscan option.
  1265. *
  1266. * ####Example
  1267. *
  1268. * Kitten.find().maxscan(100)
  1269. *
  1270. * @method maxscan
  1271. * @memberOf Query
  1272. * @param {Number} val
  1273. * @see maxScan http://docs.mongodb.org/manual/reference/operator/maxScan/
  1274. * @api public
  1275. */
  1276. /**
  1277. * Specifies the batchSize option.
  1278. *
  1279. * ####Example
  1280. *
  1281. * Kitten.find().batchSize(100)
  1282. *
  1283. * @method batchSize
  1284. * @memberOf Query
  1285. * @param {Number} val
  1286. * @see batchSize http://docs.mongodb.org/manual/reference/method/cursor.batchSize/
  1287. * @api public
  1288. */
  1289. /**
  1290. * Specifies the `comment` option.
  1291. *
  1292. * ####Example
  1293. *
  1294. * Kitten.findOne(condition).comment('login query')
  1295. *
  1296. * @method comment
  1297. * @memberOf Query
  1298. * @param {Number} val
  1299. * @see comment http://docs.mongodb.org/manual/reference/operator/comment/
  1300. * @api public
  1301. */
  1302. /*!
  1303. * limit, skip, maxscan, batchSize, comment
  1304. *
  1305. * Sets these associated options.
  1306. *
  1307. * query.comment('feed query');
  1308. */
  1309. ;['limit', 'skip', 'maxscan', 'batchSize', 'comment'].forEach(function (method) {
  1310. Query.prototype[method] = function (v) {
  1311. this.options[method] = v;
  1312. return this;
  1313. };
  1314. });
  1315. /**
  1316. * Specifies this query as a `snapshot` query.
  1317. *
  1318. * ####Example
  1319. *
  1320. * Kitten.find().snapshot()
  1321. *
  1322. * @see snapshot http://docs.mongodb.org/manual/reference/operator/snapshot/
  1323. * @return {Query} this
  1324. * @api public
  1325. */
  1326. Query.prototype.snapshot = function () {
  1327. this.options.snapshot = true;
  1328. return this;
  1329. };
  1330. /**
  1331. * Sets query hints.
  1332. *
  1333. * ####Example
  1334. *
  1335. * Model.find().hint({ indexA: 1, indexB: -1})
  1336. *
  1337. * @param {Object} val a hint object
  1338. * @return {Query} this
  1339. * @see $hint http://docs.mongodb.org/manual/reference/operator/hint/
  1340. * @api public
  1341. */
  1342. Query.prototype.hint = function (val) {
  1343. if (!val) return this;
  1344. var hint = this.options.hint || (this.options.hint = {});
  1345. if ('Object' === val.constructor.name) {
  1346. // must keep object keys in order so don't use Object.keys()
  1347. for (var k in val) {
  1348. hint[k] = val[k];
  1349. }
  1350. } else {
  1351. throw new TypeError('Invalid hint. ' + val);
  1352. }
  1353. return this;
  1354. };
  1355. /**
  1356. * Sets the slaveOk option. _Deprecated_ in MongoDB 2.2 in favor of [read preferences](#query_Query-read).
  1357. *
  1358. * ####Example:
  1359. *
  1360. * new Query().slaveOk() // true
  1361. * new Query().slaveOk(true)
  1362. * new Query().slaveOk(false)
  1363. *
  1364. * @param {Boolean} v defaults to true
  1365. * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference
  1366. * @see slaveOk http://docs.mongodb.org/manual/reference/method/rs.slaveOk/
  1367. * @return {Query} this
  1368. * @api public
  1369. */
  1370. Query.prototype.slaveOk = function (v) {
  1371. this.options.slaveOk = arguments.length ? !!v : true;
  1372. return this;
  1373. }
  1374. /**
  1375. * Determines the MongoDB nodes from which to read.
  1376. *
  1377. * ####Preferences:
  1378. *
  1379. * primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags.
  1380. * secondary Read from secondary if available, otherwise error.
  1381. * primaryPreferred Read from primary if available, otherwise a secondary.
  1382. * secondaryPreferred Read from a secondary if available, otherwise read from the primary.
  1383. * 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 selection.
  1384. *
  1385. * Aliases
  1386. *
  1387. * p primary
  1388. * pp primaryPreferred
  1389. * s secondary
  1390. * sp secondaryPreferred
  1391. * n nearest
  1392. *
  1393. * ####Example:
  1394. *
  1395. * new Query().read('primary')
  1396. * new Query().read('p') // same as primary
  1397. *
  1398. * new Query().read('primaryPreferred')
  1399. * new Query().read('pp') // same as primaryPreferred
  1400. *
  1401. * new Query().read('secondary')
  1402. * new Query().read('s') // same as secondary
  1403. *
  1404. * new Query().read('secondaryPreferred')
  1405. * new Query().read('sp') // same as secondaryPreferred
  1406. *
  1407. * new Query().read('nearest')
  1408. * new Query().read('n') // same as nearest
  1409. *
  1410. * // read from secondaries with matching tags
  1411. * new Query().read('secondary', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }])
  1412. *
  1413. * 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).
  1414. *
  1415. * @param {String} pref one of the listed preference options or aliases
  1416. * @param {Array} [tags] optional tags for this query
  1417. * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference
  1418. * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences
  1419. * @return {Query} this
  1420. * @api public
  1421. */
  1422. Query.prototype.read = function (pref, tags) {
  1423. this.options.readPreference = utils.readPref(pref, tags);
  1424. return this;
  1425. }
  1426. /**
  1427. * Sets the lean option.
  1428. *
  1429. * 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.
  1430. *
  1431. * ####Example:
  1432. *
  1433. * new Query().lean() // true
  1434. * new Query().lean(true)
  1435. * new Query().lean(false)
  1436. *
  1437. * Model.find().lean().exec(function (err, docs) {
  1438. * docs[0] instanceof mongoose.Document // false
  1439. * });
  1440. *
  1441. * 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).
  1442. *
  1443. * @param {Boolean} bool defaults to true
  1444. * @return {Query} this
  1445. * @api public
  1446. */
  1447. Query.prototype.lean = function (v) {
  1448. this.options.lean = arguments.length ? !!v : true;
  1449. return this;
  1450. }
  1451. /**
  1452. * Sets the tailable option (for use with capped collections).
  1453. *
  1454. * ####Example
  1455. *
  1456. * Kitten.find().tailable() // true
  1457. * Kitten.find().tailable(true)
  1458. * Kitten.find().tailable(false)
  1459. *
  1460. * @param {Boolean} bool defaults to true
  1461. * @see tailable http://docs.mongodb.org/manual/tutorial/create-tailable-cursor/
  1462. * @api public
  1463. */
  1464. Query.prototype.tailable = function (v) {
  1465. this.options.tailable = arguments.length ? !!v : true;
  1466. return this;
  1467. };
  1468. /**
  1469. * Executes the query as a find() operation.
  1470. *
  1471. * @param {Function} callback
  1472. * @return {Query} this
  1473. * @api private
  1474. */
  1475. Query.prototype.execFind = function (callback) {
  1476. var model = this.model
  1477. , promise = new Promise(callback);
  1478. try {
  1479. this.cast(model);
  1480. } catch (err) {
  1481. promise.error(err);
  1482. return this;
  1483. }
  1484. // apply default schematype path selections
  1485. this._applyPaths();
  1486. var self = this
  1487. , castQuery = this._conditions
  1488. , options = this._optionsForExec(model)
  1489. , fields = utils.clone(this._fields)
  1490. options.fields = this._castFields(fields);
  1491. if (options.fields instanceof Error) {
  1492. promise.error(options.fields);
  1493. return this;
  1494. }
  1495. model.collection.find(castQuery, options, function (err, cursor) {
  1496. if (err) return promise.error(err);
  1497. cursor.toArray(tick(cb));
  1498. });
  1499. function cb (err, docs) {
  1500. if (err) return promise.error(err);
  1501. if (0 === docs.length) {
  1502. return promise.complete(docs);
  1503. }
  1504. if (!self.options.populate) {
  1505. return true === options.lean
  1506. ? promise.complete(docs)
  1507. : completeMany(model, docs, fields, self, null, promise);
  1508. }
  1509. var pop = helpers.preparePopulationOptions(self, options);
  1510. model.populate(docs, pop, function (err, docs) {
  1511. if (err) return promise.error(err);
  1512. return true === options.lean
  1513. ? promise.complete(docs)
  1514. : completeMany(model, docs, fields, self, pop, promise);
  1515. });
  1516. }
  1517. return this;
  1518. }
  1519. /*!
  1520. * hydrates many documents
  1521. *
  1522. * @param {Model} model
  1523. * @param {Array} docs
  1524. * @param {Object} fields
  1525. * @param {Query} self
  1526. * @param {Array} [pop] array of paths used in population
  1527. * @param {Promise} promise
  1528. */
  1529. function completeMany (model, docs, fields, self, pop, promise) {
  1530. var arr = [];
  1531. var count = docs.length;
  1532. var len = count;
  1533. var i = 0;
  1534. var opts = pop ?
  1535. { populated: pop }
  1536. : undefined;
  1537. for (; i < len; ++i) {
  1538. arr[i] = new model(undefined, fields, true);
  1539. arr[i].init(docs[i], opts, function (err) {
  1540. if (err) return promise.error(err);
  1541. --count || promise.complete(arr);
  1542. });
  1543. }
  1544. }
  1545. /**
  1546. * Executes the query as a findOne() operation, passing the first found document to the callback.
  1547. *
  1548. * ####Example
  1549. *
  1550. * var query = Kitten.find({ color: 'white'});
  1551. *
  1552. * query.findOne(function (err, kitten) {
  1553. * if (err) return handleError(err);
  1554. *
  1555. * // kitten may be null if no document matched
  1556. * if (kitten) {
  1557. * ...
  1558. * }
  1559. * })
  1560. *
  1561. * @param {Function} callback
  1562. * @return {Query} this
  1563. * @see findOne http://docs.mongodb.org/manual/reference/method/db.collection.findOne/
  1564. * @api public
  1565. */
  1566. Query.prototype.findOne = function (callback) {
  1567. this.op = 'findOne';
  1568. if (!callback) return this;
  1569. var model = this.model;
  1570. var promise = new Promise(callback);
  1571. try {
  1572. this.cast(model);
  1573. } catch (err) {
  1574. promise.error(err);
  1575. return this;
  1576. }
  1577. // apply default schematype path selections
  1578. this._applyPaths();
  1579. var self = this
  1580. , castQuery = this._conditions
  1581. , options = this._optionsForExec(model)
  1582. , fields = utils.clone(this._fields)
  1583. options.fields = this._castFields(fields);
  1584. if (options.fields instanceof Error) {
  1585. promise.error(options.fields);
  1586. return this;
  1587. }
  1588. model.collection.findOne(castQuery, options, tick(function (err, doc) {
  1589. if (err) return promise.error(err);
  1590. if (!doc) return promise.complete(null);
  1591. if (!self.options.populate) {
  1592. return true === options.lean
  1593. ? promise.complete(doc)
  1594. : completeOne(model, doc, fields, self, null, promise);
  1595. }
  1596. var pop = helpers.preparePopulationOptions(self, options);
  1597. model.populate(doc, pop, function (err, doc) {
  1598. if (err) return promise.error(err);
  1599. return true === options.lean
  1600. ? promise.complete(doc)
  1601. : completeOne(model, doc, fields, self, pop, promise);
  1602. })
  1603. }));
  1604. return this;
  1605. }
  1606. /*!
  1607. * hydrates a document
  1608. *
  1609. * @param {Model} model
  1610. * @param {Document} doc
  1611. * @param {Object} fields
  1612. * @param {Query} self
  1613. * @param {Array} [pop] array of paths used in population
  1614. * @param {Promise} promise
  1615. */
  1616. function completeOne (model, doc, fields, self, pop, promise) {
  1617. var opts = pop ?
  1618. { populated: pop }
  1619. : undefined;
  1620. var casted = new model(undefined, fields, true);
  1621. casted.init(doc, opts, function (err) {
  1622. if (err) return promise.error(err);
  1623. promise.complete(casted);
  1624. });
  1625. }
  1626. /**
  1627. * Exectues the query as a count() operation.
  1628. *
  1629. * ####Example
  1630. *
  1631. * Kitten.where('color', 'black').count(function (err, count) {
  1632. * if (err) return handleError(err);
  1633. * console.log('there are %d black kittens', count);
  1634. * })
  1635. *
  1636. * @param {Function} callback
  1637. * @return {Query} this
  1638. * @see count http://docs.mongodb.org/manual/reference/method/db.collection.count/
  1639. * @api public
  1640. */
  1641. Query.prototype.count = function (callback) {
  1642. this.op = 'count';
  1643. if (!callback) return this;
  1644. var model = this.model;
  1645. try {
  1646. this.cast(model);
  1647. } catch (err) {
  1648. return callback(err);
  1649. }
  1650. var castQuery = this._conditions;
  1651. model.collection.count(castQuery, tick(callback));
  1652. return this;
  1653. };
  1654. /**
  1655. * Executes this query as a distict() operation.
  1656. *
  1657. * ####Example
  1658. *
  1659. * Link.find({ clicks: { $gt: 100 }}).distinct('url', function (err, result) {
  1660. * if (err) return handleError(err);
  1661. *
  1662. * assert(Array.isArray(result));
  1663. * console.log('unique urls with more than 100 clicks', result);
  1664. * })
  1665. *
  1666. * @param {String} field
  1667. * @param {Function} callback
  1668. * @return {Query} this
  1669. * @see distinct http://docs.mongodb.org/manual/reference/method/db.collection.distinct/
  1670. * @api public
  1671. */
  1672. Query.prototype.distinct = function (field, callback) {
  1673. this.op = 'distinct';
  1674. var model = this.model;
  1675. try {
  1676. this.cast(model);
  1677. } catch (err) {
  1678. return callback(err);
  1679. }
  1680. var castQuery = this._conditions;
  1681. model.collection.distinct(field, castQuery, tick(callback));
  1682. return this;
  1683. };
  1684. /*!
  1685. * These operators require casting docs
  1686. * to real Documents for Update operations.
  1687. */
  1688. var castOps = {
  1689. $push: 1
  1690. , $pushAll: 1
  1691. , $addToSet: 1
  1692. , $set: 1
  1693. };
  1694. /*!
  1695. * These operators should be cast to numbers instead
  1696. * of their path schema type.
  1697. */
  1698. var numberOps = {
  1699. $pop: 1
  1700. , $unset: 1
  1701. , $inc: 1
  1702. }
  1703. /**
  1704. * Executes this query as an update() operation.
  1705. *
  1706. * _All paths passed that are not $atomic operations will become $set ops so we retain backwards compatibility._
  1707. *
  1708. * ####Example
  1709. *
  1710. * Model.update({..}, { title: 'remove words' }, ...)
  1711. *
  1712. * // becomes
  1713. *
  1714. * Model.update({..}, { $set: { title: 'remove words' }}, ...)
  1715. *
  1716. * ####Note
  1717. *
  1718. * Passing an empty object `{}` as the doc will result in a no-op. The update operation will be ignored and the callback executed without sending the command to MongoDB so as to prevent accidently overwritting the collection.
  1719. *
  1720. * @param {Object} doc the update conditions
  1721. * @param {Function} callback
  1722. * @return {Query} this
  1723. * @api public
  1724. * @see Model.update #model_Model.update
  1725. * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/
  1726. */
  1727. Query.prototype.update = function update (doc, callback) {
  1728. this.op = 'update';
  1729. this._updateArg = doc;
  1730. var model = this.model
  1731. , options = this._optionsForExec(model)
  1732. , fn = 'function' == typeof callback
  1733. , castedQuery
  1734. , castedDoc
  1735. castedQuery = castQuery(this);
  1736. if (castedQuery instanceof Error) {
  1737. if (fn) {
  1738. process.nextTick(callback.bind(null, castedQuery));
  1739. return this;
  1740. }
  1741. throw castedQuery;
  1742. }
  1743. castedDoc = castDoc(this);
  1744. if (!castedDoc) {
  1745. fn && process.nextTick(callback.bind(null, null, 0));
  1746. return this;
  1747. }
  1748. if (castedDoc instanceof Error) {
  1749. if (fn) {
  1750. process.nextTick(callback.bind(null, castedDoc));
  1751. return this;
  1752. }
  1753. throw castedDoc;
  1754. }
  1755. if (!fn) {
  1756. options.safe = { w: 0 };
  1757. }
  1758. model.collection.update(castedQuery, castedDoc, options, tick(callback));
  1759. return this;
  1760. };
  1761. /**
  1762. * Casts obj for an update command.
  1763. *
  1764. * @param {Object} obj
  1765. * @return {Object} obj after casting its values
  1766. * @api private
  1767. */
  1768. Query.prototype._castUpdate = function _castUpdate (obj) {
  1769. var ops = Object.keys(obj)
  1770. , i = ops.length
  1771. , ret = {}
  1772. , hasKeys
  1773. , val
  1774. while (i--) {
  1775. var op = ops[i];
  1776. if ('$' !== op[0]) {
  1777. // fix up $set sugar
  1778. if (!ret.$set) {
  1779. if (obj.$set) {
  1780. ret.$set = obj.$set;
  1781. } else {
  1782. ret.$set = {};
  1783. }
  1784. }
  1785. ret.$set[op] = obj[op];
  1786. ops.splice(i, 1);
  1787. if (!~ops.indexOf('$set')) ops.push('$set');
  1788. } else if ('$set' === op) {
  1789. if (!ret.$set) {
  1790. ret[op] = obj[op];
  1791. }
  1792. } else {
  1793. ret[op] = obj[op];
  1794. }
  1795. }
  1796. // cast each value
  1797. i = ops.length;
  1798. while (i--) {
  1799. op = ops[i];
  1800. val = ret[op];
  1801. if ('Object' === val.constructor.name) {
  1802. hasKeys |= this._walkUpdatePath(val, op);
  1803. } else {
  1804. var msg = 'Invalid atomic update value for ' + op + '. '
  1805. + 'Expected an object, received ' + typeof val;
  1806. throw new Error(msg);
  1807. }
  1808. }
  1809. return hasKeys && ret;
  1810. }
  1811. /**
  1812. * Walk each path of obj and cast its values
  1813. * according to its schema.
  1814. *
  1815. * @param {Object} obj - part of a query
  1816. * @param {String} op - the atomic operator ($pull, $set, etc)
  1817. * @param {String} pref - path prefix (internal only)
  1818. * @return {Bool} true if this path has keys to update
  1819. * @api private
  1820. */
  1821. Query.prototype._walkUpdatePath = function _walkUpdatePath (obj, op, pref) {
  1822. var prefix = pref ? pref + '.' : ''
  1823. , keys = Object.keys(obj)
  1824. , i = keys.length
  1825. , hasKeys = false
  1826. , schema
  1827. , key
  1828. , val
  1829. var strict = 'strict' in this.options
  1830. ? this.options.strict
  1831. : this.model.schema.options.strict;
  1832. while (i--) {
  1833. key = keys[i];
  1834. val = obj[key];
  1835. if (val && 'Object' === val.constructor.name) {
  1836. // watch for embedded doc schemas
  1837. schema = this._getSchema(prefix + key);
  1838. if (schema && schema.caster && op in castOps) {
  1839. // embedded doc schema
  1840. if (strict && !schema) {
  1841. // path is not in our strict schema
  1842. if ('throw' == strict) {
  1843. throw new Error('Field `' + key + '` is not in schema.');
  1844. } else {
  1845. // ignore paths not specified in schema
  1846. delete obj[key];
  1847. }
  1848. } else {
  1849. hasKeys = true;
  1850. if ('$each' in val) {
  1851. obj[key] = {
  1852. $each: this._castUpdateVal(schema, val.$each, op)
  1853. }
  1854. if (val.$slice) {
  1855. obj[key].$slice = val.$slice | 0;
  1856. }
  1857. if (val.$sort) {
  1858. obj[key].$sort = val.$sort;
  1859. }
  1860. } else {
  1861. obj[key] = this._castUpdateVal(schema, val, op);
  1862. }
  1863. }
  1864. } else {
  1865. hasKeys |= this._walkUpdatePath(val, op, prefix + key);
  1866. }
  1867. } else {
  1868. schema = '$each' === key
  1869. ? this._getSchema(pref)
  1870. : this._getSchema(prefix + key);
  1871. var skip = strict &&
  1872. !schema &&
  1873. !/real|nested/.test(this.model.schema.pathType(prefix + key));
  1874. if (skip) {
  1875. if ('throw' == strict) {
  1876. throw new Error('Field `' + prefix + key + '` is not in schema.');
  1877. } else {
  1878. delete obj[key];
  1879. }
  1880. } else {
  1881. hasKeys = true;
  1882. obj[key] = this._castUpdateVal(schema, val, op, key);
  1883. }
  1884. }
  1885. }
  1886. return hasKeys;
  1887. }
  1888. /**
  1889. * Casts `val` according to `schema` and atomic `op`.
  1890. *
  1891. * @param {Schema} schema
  1892. * @param {Object} val
  1893. * @param {String} op - the atomic operator ($pull, $set, etc)
  1894. * @param {String} [$conditional]
  1895. * @api private
  1896. */
  1897. Query.prototype._castUpdateVal = function _castUpdateVal (schema, val, op, $conditional) {
  1898. if (!schema) {
  1899. // non-existing schema path
  1900. return op in numberOps
  1901. ? Number(val)
  1902. : val
  1903. }
  1904. if (schema.caster && op in castOps &&
  1905. (utils.isObject(val) || Array.isArray(val))) {
  1906. // Cast values for ops that add data to MongoDB.
  1907. // Ensures embedded documents get ObjectIds etc.
  1908. var tmp = schema.cast(val);
  1909. if (Array.isArray(val)) {
  1910. val = tmp;
  1911. } else {
  1912. val = tmp[0];
  1913. }
  1914. }
  1915. if (op in numberOps) return Number(val);
  1916. if (/^\$/.test($conditional)) return schema.castForQuery($conditional, val);
  1917. return schema.castForQuery(val)
  1918. }
  1919. /**
  1920. * Finds the schema for `path`. This is different than
  1921. * calling `schema.path` as it also resolves paths with
  1922. * positional selectors (something.$.another.$.path).
  1923. *
  1924. * @param {String} path
  1925. * @api private
  1926. */
  1927. Query.prototype._getSchema = function _getSchema (path) {
  1928. return this.model._getSchema(path);
  1929. }
  1930. /**
  1931. * Casts selected field arguments for field selection with mongo 2.2
  1932. *
  1933. * query.select({ ids: { $elemMatch: { $in: [hexString] }})
  1934. *
  1935. * @param {Object} fields
  1936. * @see https://github.com/LearnBoost/mongoose/issues/1091
  1937. * @see http://docs.mongodb.org/manual/reference/projection/elemMatch/
  1938. * @api private
  1939. */
  1940. Query.prototype._castFields = function _castFields (fields) {
  1941. var selected
  1942. , elemMatchKeys
  1943. , keys
  1944. , key
  1945. , out
  1946. , i
  1947. if (fields) {
  1948. keys = Object.keys(fields);
  1949. elemMatchKeys = [];
  1950. i = keys.length;
  1951. // collect $elemMatch args
  1952. while (i--) {
  1953. key = keys[i];
  1954. if (fields[key].$elemMatch) {
  1955. selected || (selected = {});
  1956. selected[key] = fields[key];
  1957. elemMatchKeys.push(key);
  1958. }
  1959. }
  1960. }
  1961. if (selected) {
  1962. // they passed $elemMatch, cast em
  1963. try {
  1964. out = this.cast(this.model, selected);
  1965. } catch (err) {
  1966. return err;
  1967. }
  1968. // apply the casted field args
  1969. i = elemMatchKeys.length;
  1970. while (i--) {
  1971. key = elemMatchKeys[i];
  1972. fields[key] = out[key];
  1973. }
  1974. }
  1975. return fields;
  1976. }
  1977. /**
  1978. * Executes this query as a remove() operation.
  1979. *
  1980. * ####Example
  1981. *
  1982. * Cassette.where('artist').equals('Anne Murray').remove(callback)
  1983. *
  1984. * @param {Function} callback
  1985. * @api public
  1986. * @see remove http://docs.mongodb.org/manual/reference/method/db.collection.remove/
  1987. */
  1988. Query.prototype.remove = function (callback) {
  1989. this.op = 'remove';
  1990. var model = this.model
  1991. , options = this._optionsForExec(model)
  1992. , cb = 'function' == typeof callback
  1993. try {
  1994. this.cast(model);
  1995. } catch (err) {
  1996. if (cb) return callback(err);
  1997. throw err;
  1998. }
  1999. if (!cb) {
  2000. options.safe = { w: 0 };
  2001. }
  2002. var castQuery = this._conditions;
  2003. model.collection.remove(castQuery, options, tick(callback));
  2004. return this;
  2005. };
  2006. /**
  2007. * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) update command.
  2008. *
  2009. * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes immediately if `callback` is passed else a Query object is returned.
  2010. *
  2011. * ####Available options
  2012. *
  2013. * - `new`: bool - true to return the modified document rather than the original. defaults to true
  2014. * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
  2015. * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
  2016. *
  2017. * ####Examples
  2018. *
  2019. * query.findOneAndUpdate(conditions, update, options, callback) // executes
  2020. * query.findOneAndUpdate(conditions, update, options) // returns Query
  2021. * query.findOneAndUpdate(conditions, update, callback) // executes
  2022. * query.findOneAndUpdate(conditions, update) // returns Query
  2023. * query.findOneAndUpdate(callback) // executes
  2024. * query.findOneAndUpdate() // returns Query
  2025. *
  2026. * @param {Object} [query]
  2027. * @param {Object} [doc]
  2028. * @param {Object} [options]
  2029. * @param {Function} [callback]
  2030. * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
  2031. * @return {Query} this
  2032. * @api public
  2033. */
  2034. Query.prototype.findOneAndUpdate = function (query, doc, options, callback) {
  2035. this.op = 'findOneAndUpdate';
  2036. switch (arguments.length) {
  2037. case 3:
  2038. if ('function' == typeof options)
  2039. callback = options, options = {};
  2040. break;
  2041. case 2:
  2042. if ('function' == typeof doc) {
  2043. callback = doc;
  2044. doc = query;
  2045. query = undefined;
  2046. }
  2047. options = undefined;
  2048. break;
  2049. case 1:
  2050. if ('function' == typeof query) {
  2051. callback = query;
  2052. query = options = doc = undefined;
  2053. } else {
  2054. doc = query;
  2055. query = options = undefined;
  2056. }
  2057. }
  2058. // apply query
  2059. if (query) {
  2060. if ('Object' === query.constructor.name) {
  2061. merge(this._conditions, query);
  2062. } else if (query instanceof Query) {
  2063. merge(this._conditions, query._conditions);
  2064. } else if (query instanceof Document) {
  2065. merge(this._conditions, query.toObject());
  2066. }
  2067. }
  2068. // apply doc
  2069. if (doc) {
  2070. merge(this._updateArg, doc);
  2071. }
  2072. // apply options
  2073. options && this.setOptions(options);
  2074. if (!callback) return this;
  2075. return this._findAndModify('update', callback);
  2076. }
  2077. /**
  2078. * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) remove command.
  2079. *
  2080. * Finds a matching document, removes it, passing the found document (if any) to the callback. Executes immediately if `callback` is passed else a Query object is returned.
  2081. *
  2082. * ####Available options
  2083. *
  2084. * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
  2085. *
  2086. * ####Examples
  2087. *
  2088. * A.where().findOneAndRemove(conditions, options, callback) // executes
  2089. * A.where().findOneAndRemove(conditions, options) // return Query
  2090. * A.where().findOneAndRemove(conditions, callback) // executes
  2091. * A.where().findOneAndRemove(conditions) // returns Query
  2092. * A.where().findOneAndRemove(callback) // executes
  2093. * A.where().findOneAndRemove() // returns Query
  2094. *
  2095. * @param {Object} [conditions]
  2096. * @param {Object} [options]
  2097. * @param {Function} [callback]
  2098. * @return {Query} this
  2099. * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
  2100. * @api public
  2101. */
  2102. Query.prototype.findOneAndRemove = function (conditions, options, callback) {
  2103. this.op = 'findOneAndRemove';
  2104. if ('function' == typeof options) {
  2105. callback = options;
  2106. options = undefined;
  2107. } else if ('function' == typeof conditions) {
  2108. callback = conditions;
  2109. conditions = undefined;
  2110. }
  2111. // apply conditions
  2112. if (conditions) {
  2113. if ('Object' === conditions.constructor.name) {
  2114. merge(this._conditions, conditions);
  2115. } else if (conditions instanceof Query) {
  2116. merge(this._conditions, conditions._conditions);
  2117. } else if (conditions instanceof Document) {
  2118. merge(this._conditions, conditions.toObject());
  2119. }
  2120. }
  2121. // apply options
  2122. options && this.setOptions(options);
  2123. if (!callback) return this;
  2124. return this._findAndModify('remove', callback);
  2125. }
  2126. /**
  2127. * _findAndModify
  2128. *
  2129. * @param {String} type - either "remove" or "update"
  2130. * @param {Function} callback
  2131. * @api private
  2132. */
  2133. Query.prototype._findAndModify = function (type, callback) {
  2134. var model = this.model
  2135. , promise = new Promise(callback)
  2136. , self = this
  2137. , castedQuery
  2138. , castedDoc
  2139. , fields
  2140. , sort
  2141. , opts
  2142. castedQuery = castQuery(this);
  2143. if (castedQuery instanceof Error) {
  2144. process.nextTick(promise.error.bind(promise, castedQuery));
  2145. return promise;
  2146. }
  2147. opts = this._optionsForExec(model);
  2148. if ('remove' == type) {
  2149. opts.remove = true;
  2150. } else {
  2151. if (!('new' in opts)) opts.new = true;
  2152. if (!('upsert' in opts)) opts.upsert = false;
  2153. castedDoc = castDoc(this);
  2154. if (!castedDoc) {
  2155. if (opts.upsert) {
  2156. // still need to do the upsert to empty doc
  2157. castedDoc = { $set: {} };
  2158. } else {
  2159. return this.findOne(callback);
  2160. }
  2161. } else if (castedDoc instanceof Error) {
  2162. process.nextTick(promise.error.bind(promise, castedDoc));
  2163. return promise;
  2164. }
  2165. }
  2166. this._applyPaths();
  2167. if (this._fields) {
  2168. fields = utils.clone(this._fields)
  2169. opts.fields = this._castFields(fields);
  2170. if (opts.fields instanceof Error) {
  2171. process.nextTick(promise.error.bind(promise, opts.fields));
  2172. return promise;
  2173. }
  2174. }
  2175. // the driver needs a default
  2176. sort = opts.sort || [];
  2177. model
  2178. .collection
  2179. .findAndModify(castedQuery, sort, castedDoc, opts, tick(function (err, doc) {
  2180. if (err) return promise.error(err);
  2181. if (!doc || (utils.isObject(doc) && Object.keys(doc).length === 0)) {
  2182. return promise.complete(null);
  2183. }
  2184. if (!self.options.populate) {
  2185. return true === opts.lean
  2186. ? promise.complete(doc)
  2187. : completeOne(model, doc, fields, self, null, promise);
  2188. }
  2189. var pop = helpers.preparePopulationOptions(self, opts);
  2190. model.populate(doc, pop, function (err, doc) {
  2191. if (err) return promise.error(err);
  2192. return true === opts.lean
  2193. ? promise.complete(doc)
  2194. : completeOne(model, doc, fields, self, pop, promise);
  2195. })
  2196. }));
  2197. return promise;
  2198. }
  2199. /**
  2200. * Specifies paths which should be populated with other documents.
  2201. *
  2202. * ####Example:
  2203. *
  2204. * Kitten.findOne().populate('owner').exec(function (err, kitten) {
  2205. * console.log(kitten.owner.name) // Max
  2206. * })
  2207. *
  2208. * Kitten.find().populate({
  2209. * path: 'owner'
  2210. * , select: 'name'
  2211. * , match: { color: 'black' }
  2212. * , options: { sort: { name: -1 }}
  2213. * }).exec(function (err, kittens) {
  2214. * console.log(kittens[0].owner.name) // Zoopa
  2215. * })
  2216. *
  2217. * // alternatively
  2218. * Kitten.find().populate('owner', 'name', null, {sort: { name: -1 }}).exec(function (err, kittens) {
  2219. * console.log(kittens[0].owner.name) // Zoopa
  2220. * })
  2221. *
  2222. * Paths are populated after the query executes and a response is received. A separate query is then executed for each path specified for population. After a response for each query has also been returned, the results are passed to the callback.
  2223. *
  2224. * @param {Object|String} path either the path to populate or an object specifying all parameters
  2225. * @param {Object|String} [select] Field selection for the population query
  2226. * @param {Model} [model] The name of the model you wish to use for population. If not specified, the name is looked up from the Schema ref.
  2227. * @param {Object} [match] Conditions for the population query
  2228. * @param {Object} [options] Options for the population query (sort, etc)
  2229. * @see population ./populate.html
  2230. * @see Query#select #query_Query-select
  2231. * @see Model.populate #model_Model.populate
  2232. * @return {Query} this
  2233. * @api public
  2234. */
  2235. Query.prototype.populate = function populate () {
  2236. var res = utils.populate.apply(null, arguments);
  2237. var opts = this.options;
  2238. if (!utils.isObject(opts.populate)) {
  2239. opts.populate = {};
  2240. }
  2241. for (var i = 0; i < res.length; ++i) {
  2242. opts.populate[res[i].path] = res[i];
  2243. }
  2244. return this;
  2245. }
  2246. /**
  2247. * Returns a Node.js 0.8 style [read stream](http://nodejs.org/docs/v0.8.21/api/stream.html#stream_readable_stream) interface.
  2248. *
  2249. * ####Example
  2250. *
  2251. * // follows the nodejs 0.8 stream api
  2252. * Thing.find({ name: /^hello/ }).stream().pipe(res)
  2253. *
  2254. * // manual streaming
  2255. * var stream = Thing.find({ name: /^hello/ }).stream();
  2256. *
  2257. * stream.on('data', function (doc) {
  2258. * // do something with the mongoose document
  2259. * }).on('error', function (err) {
  2260. * // handle the error
  2261. * }).on('close', function () {
  2262. * // the stream is closed
  2263. * });
  2264. *
  2265. * ####Valid options
  2266. *
  2267. * - `transform`: optional function which accepts a mongoose document. The return value of the function will be emitted on `data`.
  2268. *
  2269. * ####Example
  2270. *
  2271. * // JSON.stringify all documents before emitting
  2272. * var stream = Thing.find().stream({ transform: JSON.stringify });
  2273. * stream.pipe(writeStream);
  2274. *
  2275. * @return {QueryStream}
  2276. * @param {Object} [options]
  2277. * @see QueryStream
  2278. * @api public
  2279. */
  2280. Query.prototype.stream = function stream (opts) {
  2281. return new QueryStream(this, opts);
  2282. }
  2283. // helpers
  2284. /*!
  2285. * castDoc
  2286. * @api private
  2287. */
  2288. function castDoc (query) {
  2289. try {
  2290. return query._castUpdate(query._updateArg);
  2291. } catch (err) {
  2292. return err;
  2293. }
  2294. }
  2295. /*!
  2296. * castQuery
  2297. * @api private
  2298. */
  2299. function castQuery (query) {
  2300. try {
  2301. return query.cast(query.model);
  2302. } catch (err) {
  2303. return err;
  2304. }
  2305. }
  2306. /*!
  2307. * Exports.
  2308. */
  2309. module.exports = Query;
  2310. module.exports.QueryStream = QueryStream;