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