arnaucube
arnaucube/thoughtsPublic Self-hosted repository
thoughts/node_modules/mongoose/lib/model.js
node_modules/mongoose/lib/model.jsRaw
   1/*!
   2 * Module dependencies.
   3 */
   4
   5var Document = require('./document');
   6var MongooseError = require('./error');
   7var VersionError = MongooseError.VersionError;
   8var DivergentArrayError = MongooseError.DivergentArrayError;
   9var Query = require('./query');
  10var Aggregate = require('./aggregate');
  11var Schema = require('./schema');
  12var utils = require('./utils');
  13var hasOwnProperty = utils.object.hasOwnProperty;
  14var isMongooseObject = utils.isMongooseObject;
  15var EventEmitter = require('events').EventEmitter;
  16var util = require('util');
  17var tick = utils.tick;
  18
  19var async = require('async');
  20var PromiseProvider = require('./promise_provider');
  21
  22var VERSION_WHERE = 1,
  23    VERSION_INC = 2,
  24    VERSION_ALL = VERSION_WHERE | VERSION_INC;
  25
  26/**
  27 * Model constructor
  28 *
  29 * Provides the interface to MongoDB collections as well as creates document instances.
  30 *
  31 * @param {Object} doc values with which to create the document
  32 * @inherits Document http://mongoosejs.com/docs/api.html#document-js
  33 * @event `error`: If listening to this event, it is emitted when a document was saved without passing a callback and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model.
  34 * @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event.
  35 * @event `index-single-start`: Emitted when an individual index starts within `Model#ensureIndexes`. The fields and options being used to build the index are also passed with the event.
  36 * @event `index-single-done`: Emitted when an individual index finishes within `Model#ensureIndexes`. If an error occurred it is passed with the event. The fields, options, and index name are also passed.
  37 * @api public
  38 */
  39
  40function Model(doc, fields, skipId) {
  41  Document.call(this, doc, fields, skipId);
  42}
  43
  44/*!
  45 * Inherits from Document.
  46 *
  47 * All Model.prototype features are available on
  48 * top level (non-sub) documents.
  49 */
  50
  51Model.prototype.__proto__ = Document.prototype;
  52
  53/**
  54 * Connection the model uses.
  55 *
  56 * @api public
  57 * @property db
  58 */
  59
  60Model.prototype.db;
  61
  62/**
  63 * Collection the model uses.
  64 *
  65 * @api public
  66 * @property collection
  67 */
  68
  69Model.prototype.collection;
  70
  71/**
  72 * The name of the model
  73 *
  74 * @api public
  75 * @property modelName
  76 */
  77
  78Model.prototype.modelName;
  79
  80/**
  81 * If this is a discriminator model, `baseModelName` is the name of
  82 * the base model.
  83 *
  84 * @api public
  85 * @property baseModelName
  86 */
  87
  88Model.prototype.baseModelName;
  89
  90Model.prototype.$__handleSave = function(options, callback) {
  91  var _this = this;
  92  if (!options.safe && this.schema.options.safe) {
  93    options.safe = this.schema.options.safe;
  94  }
  95  if (typeof options.safe === 'boolean') {
  96    options.safe = null;
  97  }
  98
  99  if (this.isNew) {
 100    // send entire doc
 101    var toObjectOptions = {};
 102    if (this.schema.options.toObject &&
 103        this.schema.options.toObject.retainKeyOrder) {
 104      toObjectOptions.retainKeyOrder = true;
 105    }
 106
 107    toObjectOptions.depopulate = 1;
 108    toObjectOptions._skipDepopulateTopLevel = true;
 109    toObjectOptions.transform = false;
 110
 111    var obj = this.toObject(toObjectOptions);
 112
 113    if (!utils.object.hasOwnProperty(obj || {}, '_id')) {
 114      // documents must have an _id else mongoose won't know
 115      // what to update later if more changes are made. the user
 116      // wouldn't know what _id was generated by mongodb either
 117      // nor would the ObjectId generated my mongodb necessarily
 118      // match the schema definition.
 119      setTimeout(function() {
 120        callback(new Error('document must have an _id before saving'));
 121      }, 0);
 122      return;
 123    }
 124
 125    this.$__version(true, obj);
 126    this.collection.insert(obj, options.safe, function(err, ret) {
 127      if (err) {
 128        _this.isNew = true;
 129        _this.emit('isNew', true);
 130
 131        callback(err);
 132        return;
 133      }
 134
 135      callback(null, ret);
 136    });
 137    this.$__reset();
 138    this.isNew = false;
 139    this.emit('isNew', false);
 140    // Make it possible to retry the insert
 141    this.$__.inserting = true;
 142  } else {
 143    // Make sure we don't treat it as a new object on error,
 144    // since it already exists
 145    this.$__.inserting = false;
 146
 147    var delta = this.$__delta();
 148
 149    if (delta) {
 150      if (delta instanceof Error) {
 151        callback(delta);
 152        return;
 153      }
 154      var where = this.$__where(delta[0]);
 155
 156      if (where instanceof Error) {
 157        callback(where);
 158        return;
 159      }
 160
 161      this.collection.update(where, delta[1], options.safe, function(err, ret) {
 162        if (err) {
 163          callback(err);
 164          return;
 165        }
 166        callback(null, ret);
 167      });
 168    } else {
 169      this.$__reset();
 170      callback();
 171      return;
 172    }
 173
 174    this.emit('isNew', false);
 175  }
 176};
 177
 178/*!
 179 * ignore
 180 */
 181
 182Model.prototype.$__save = function(options, callback) {
 183  var _this = this;
 184
 185  _this.$__handleSave(options, function(error, result) {
 186    if (error) {
 187      return _this.schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) {
 188        callback(error);
 189      });
 190    }
 191
 192    _this.$__reset();
 193    _this.$__storeShard();
 194
 195    var numAffected = 0;
 196    if (result) {
 197      if (Array.isArray(result)) {
 198        numAffected = result.length;
 199      } else if (result.result && result.result.n !== undefined) {
 200        numAffected = result.result.n;
 201      } else if (result.result && result.result.nModified !== undefined) {
 202        numAffected = result.result.nModified;
 203      } else {
 204        numAffected = result;
 205      }
 206    }
 207
 208    // was this an update that required a version bump?
 209    if (_this.$__.version && !_this.$__.inserting) {
 210      var doIncrement = VERSION_INC === (VERSION_INC & _this.$__.version);
 211      _this.$__.version = undefined;
 212
 213      if (numAffected <= 0) {
 214        // the update failed. pass an error back
 215        var err = new VersionError(_this);
 216        return callback(err);
 217      }
 218
 219      // increment version if was successful
 220      if (doIncrement) {
 221        var key = _this.schema.options.versionKey;
 222        var version = _this.getValue(key) | 0;
 223        _this.setValue(key, version + 1);
 224      }
 225    }
 226
 227    _this.emit('save', _this, numAffected);
 228    callback(null, _this, numAffected);
 229  });
 230};
 231
 232/**
 233 * Saves this document.
 234 *
 235 * ####Example:
 236 *
 237 *     product.sold = Date.now();
 238 *     product.save(function (err, product, numAffected) {
 239 *       if (err) ..
 240 *     })
 241 *
 242 * The callback will receive three parameters
 243 *
 244 * 1. `err` if an error occurred
 245 * 2. `product` which is the saved `product`
 246 * 3. `numAffected` will be 1 when the document was successfully persisted to MongoDB, otherwise 0. Unless you tweak mongoose's internals, you don't need to worry about checking this parameter for errors - checking `err` is sufficient to make sure your document was properly saved.
 247 *
 248 * As an extra measure of flow control, save will return a Promise.
 249 * ####Example:
 250 *     product.save().then(function(product) {
 251 *        ...
 252 *     });
 253 *
 254 * For legacy reasons, mongoose stores object keys in reverse order on initial
 255 * save. That is, `{ a: 1, b: 2 }` will be saved as `{ b: 2, a: 1 }` in
 256 * MongoDB. To override this behavior, set
 257 * [the `toObject.retainKeyOrder` option](http://mongoosejs.com/docs/api.html#document_Document-toObject)
 258 * to true on your schema.
 259 *
 260 * @param {Object} [options] options optional options
 261 * @param {Object} [options.safe] overrides [schema's safe option](http://mongoosejs.com//docs/guide.html#safe)
 262 * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
 263 * @param {Function} [fn] optional callback
 264 * @return {Promise} Promise
 265 * @api public
 266 * @see middleware http://mongoosejs.com/docs/middleware.html
 267 */
 268
 269Model.prototype.save = function(options, fn) {
 270  if (typeof options === 'function') {
 271    fn = options;
 272    options = undefined;
 273  }
 274
 275  if (!options) {
 276    options = {};
 277  }
 278
 279  if (fn) {
 280    fn = this.constructor.$wrapCallback(fn);
 281  }
 282
 283  return this.$__save(options, fn);
 284};
 285
 286/*!
 287 * Determines whether versioning should be skipped for the given path
 288 *
 289 * @param {Document} self
 290 * @param {String} path
 291 * @return {Boolean} true if versioning should be skipped for the given path
 292 */
 293function shouldSkipVersioning(self, path) {
 294  var skipVersioning = self.schema.options.skipVersioning;
 295  if (!skipVersioning) return false;
 296
 297  // Remove any array indexes from the path
 298  path = path.replace(/\.\d+\./, '.');
 299
 300  return skipVersioning[path];
 301}
 302
 303/*!
 304 * Apply the operation to the delta (update) clause as
 305 * well as track versioning for our where clause.
 306 *
 307 * @param {Document} self
 308 * @param {Object} where
 309 * @param {Object} delta
 310 * @param {Object} data
 311 * @param {Mixed} val
 312 * @param {String} [operation]
 313 */
 314
 315function operand(self, where, delta, data, val, op) {
 316  // delta
 317  op || (op = '$set');
 318  if (!delta[op]) delta[op] = {};
 319  delta[op][data.path] = val;
 320
 321  // disabled versioning?
 322  if (self.schema.options.versionKey === false) return;
 323
 324  // path excluded from versioning?
 325  if (shouldSkipVersioning(self, data.path)) return;
 326
 327  // already marked for versioning?
 328  if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return;
 329
 330  switch (op) {
 331    case '$set':
 332    case '$unset':
 333    case '$pop':
 334    case '$pull':
 335    case '$pullAll':
 336    case '$push':
 337    case '$pushAll':
 338    case '$addToSet':
 339      break;
 340    default:
 341      // nothing to do
 342      return;
 343  }
 344
 345  // ensure updates sent with positional notation are
 346  // editing the correct array element.
 347  // only increment the version if an array position changes.
 348  // modifying elements of an array is ok if position does not change.
 349
 350  if (op === '$push' || op === '$pushAll' || op === '$addToSet') {
 351    self.$__.version = VERSION_INC;
 352  } else if (/^\$p/.test(op)) {
 353    // potentially changing array positions
 354    self.increment();
 355  } else if (Array.isArray(val)) {
 356    // $set an array
 357    self.increment();
 358  } else if (/\.\d+\.|\.\d+$/.test(data.path)) {
 359    // now handling $set, $unset
 360    // subpath of array
 361    self.$__.version = VERSION_WHERE;
 362  }
 363}
 364
 365/*!
 366 * Compiles an update and where clause for a `val` with _atomics.
 367 *
 368 * @param {Document} self
 369 * @param {Object} where
 370 * @param {Object} delta
 371 * @param {Object} data
 372 * @param {Array} value
 373 */
 374
 375function handleAtomics(self, where, delta, data, value) {
 376  if (delta.$set && delta.$set[data.path]) {
 377    // $set has precedence over other atomics
 378    return;
 379  }
 380
 381  if (typeof value.$__getAtomics === 'function') {
 382    value.$__getAtomics().forEach(function(atomic) {
 383      var op = atomic[0];
 384      var val = atomic[1];
 385      operand(self, where, delta, data, val, op);
 386    });
 387    return;
 388  }
 389
 390  // legacy support for plugins
 391
 392  var atomics = value._atomics,
 393      ops = Object.keys(atomics),
 394      i = ops.length,
 395      val,
 396      op;
 397
 398  if (i === 0) {
 399    // $set
 400
 401    if (isMongooseObject(value)) {
 402      value = value.toObject({depopulate: 1});
 403    } else if (value.valueOf) {
 404      value = value.valueOf();
 405    }
 406
 407    return operand(self, where, delta, data, value);
 408  }
 409
 410  function iter(mem) {
 411    return isMongooseObject(mem)
 412        ? mem.toObject({depopulate: 1})
 413        : mem;
 414  }
 415
 416  while (i--) {
 417    op = ops[i];
 418    val = atomics[op];
 419
 420    if (isMongooseObject(val)) {
 421      val = val.toObject({depopulate: 1});
 422    } else if (Array.isArray(val)) {
 423      val = val.map(iter);
 424    } else if (val.valueOf) {
 425      val = val.valueOf();
 426    }
 427
 428    if (op === '$addToSet') {
 429      val = {$each: val};
 430    }
 431
 432    operand(self, where, delta, data, val, op);
 433  }
 434}
 435
 436/**
 437 * Produces a special query document of the modified properties used in updates.
 438 *
 439 * @api private
 440 * @method $__delta
 441 * @memberOf Model
 442 */
 443
 444Model.prototype.$__delta = function() {
 445  var dirty = this.$__dirty();
 446  if (!dirty.length && VERSION_ALL !== this.$__.version) return;
 447
 448  var where = {},
 449      delta = {},
 450      len = dirty.length,
 451      divergent = [],
 452      d = 0;
 453
 454  where._id = this._doc._id;
 455
 456  for (; d < len; ++d) {
 457    var data = dirty[d];
 458    var value = data.value;
 459
 460    var match = checkDivergentArray(this, data.path, value);
 461    if (match) {
 462      divergent.push(match);
 463      continue;
 464    }
 465
 466    var pop = this.populated(data.path, true);
 467    if (!pop && this.$__.selected) {
 468      // If any array was selected using an $elemMatch projection, we alter the path and where clause
 469      // NOTE: MongoDB only supports projected $elemMatch on top level array.
 470      var pathSplit = data.path.split('.');
 471      var top = pathSplit[0];
 472      if (this.$__.selected[top] && this.$__.selected[top].$elemMatch) {
 473        // If the selected array entry was modified
 474        if (pathSplit.length > 1 && pathSplit[1] == 0 && typeof where[top] === 'undefined') {
 475          where[top] = this.$__.selected[top];
 476          pathSplit[1] = '$';
 477          data.path = pathSplit.join('.');
 478        }
 479        // if the selected array was modified in any other way throw an error
 480        else {
 481          divergent.push(data.path);
 482          continue;
 483        }
 484      }
 485    }
 486
 487    if (divergent.length) continue;
 488
 489    if (undefined === value) {
 490      operand(this, where, delta, data, 1, '$unset');
 491    } else if (value === null) {
 492      operand(this, where, delta, data, null);
 493    } else if (value._path && value._atomics) {
 494      // arrays and other custom types (support plugins etc)
 495      handleAtomics(this, where, delta, data, value);
 496    } else if (value._path && Buffer.isBuffer(value)) {
 497      // MongooseBuffer
 498      value = value.toObject();
 499      operand(this, where, delta, data, value);
 500    } else {
 501      value = utils.clone(value, {depopulate: 1});
 502      operand(this, where, delta, data, value);
 503    }
 504  }
 505
 506  if (divergent.length) {
 507    return new DivergentArrayError(divergent);
 508  }
 509
 510  if (this.$__.version) {
 511    this.$__version(where, delta);
 512  }
 513
 514  return [where, delta];
 515};
 516
 517/*!
 518 * Determine if array was populated with some form of filter and is now
 519 * being updated in a manner which could overwrite data unintentionally.
 520 *
 521 * @see https://github.com/Automattic/mongoose/issues/1334
 522 * @param {Document} doc
 523 * @param {String} path
 524 * @return {String|undefined}
 525 */
 526
 527function checkDivergentArray(doc, path, array) {
 528  // see if we populated this path
 529  var pop = doc.populated(path, true);
 530
 531  if (!pop && doc.$__.selected) {
 532    // If any array was selected using an $elemMatch projection, we deny the update.
 533    // NOTE: MongoDB only supports projected $elemMatch on top level array.
 534    var top = path.split('.')[0];
 535    if (doc.$__.selected[top + '.$']) {
 536      return top;
 537    }
 538  }
 539
 540  if (!(pop && array && array.isMongooseArray)) return;
 541
 542  // If the array was populated using options that prevented all
 543  // documents from being returned (match, skip, limit) or they
 544  // deselected the _id field, $pop and $set of the array are
 545  // not safe operations. If _id was deselected, we do not know
 546  // how to remove elements. $pop will pop off the _id from the end
 547  // of the array in the db which is not guaranteed to be the
 548  // same as the last element we have here. $set of the entire array
 549  // would be similarily destructive as we never received all
 550  // elements of the array and potentially would overwrite data.
 551  var check = pop.options.match ||
 552      pop.options.options && hasOwnProperty(pop.options.options, 'limit') || // 0 is not permitted
 553      pop.options.options && pop.options.options.skip || // 0 is permitted
 554      pop.options.select && // deselected _id?
 555      (pop.options.select._id === 0 ||
 556      /\s?-_id\s?/.test(pop.options.select));
 557
 558  if (check) {
 559    var atomics = array._atomics;
 560    if (Object.keys(atomics).length === 0 || atomics.$set || atomics.$pop) {
 561      return path;
 562    }
 563  }
 564}
 565
 566/**
 567 * Appends versioning to the where and update clauses.
 568 *
 569 * @api private
 570 * @method $__version
 571 * @memberOf Model
 572 */
 573
 574Model.prototype.$__version = function(where, delta) {
 575  var key = this.schema.options.versionKey;
 576
 577  if (where === true) {
 578    // this is an insert
 579    if (key) this.setValue(key, delta[key] = 0);
 580    return;
 581  }
 582
 583  // updates
 584
 585  // only apply versioning if our versionKey was selected. else
 586  // there is no way to select the correct version. we could fail
 587  // fast here and force them to include the versionKey but
 588  // thats a bit intrusive. can we do this automatically?
 589  if (!this.isSelected(key)) {
 590    return;
 591  }
 592
 593  // $push $addToSet don't need the where clause set
 594  if (VERSION_WHERE === (VERSION_WHERE & this.$__.version)) {
 595    where[key] = this.getValue(key);
 596  }
 597
 598  if (VERSION_INC === (VERSION_INC & this.$__.version)) {
 599    if (!delta.$set || typeof delta.$set[key] === 'undefined') {
 600      delta.$inc || (delta.$inc = {});
 601      delta.$inc[key] = 1;
 602    }
 603  }
 604};
 605
 606/**
 607 * Signal that we desire an increment of this documents version.
 608 *
 609 * ####Example:
 610 *
 611 *     Model.findById(id, function (err, doc) {
 612 *       doc.increment();
 613 *       doc.save(function (err) { .. })
 614 *     })
 615 *
 616 * @see versionKeys http://mongoosejs.com/docs/guide.html#versionKey
 617 * @api public
 618 */
 619
 620Model.prototype.increment = function increment() {
 621  this.$__.version = VERSION_ALL;
 622  return this;
 623};
 624
 625/**
 626 * Returns a query object which applies shardkeys if they exist.
 627 *
 628 * @api private
 629 * @method $__where
 630 * @memberOf Model
 631 */
 632
 633Model.prototype.$__where = function _where(where) {
 634  where || (where = {});
 635
 636  var paths,
 637      len;
 638
 639  if (!where._id) {
 640    where._id = this._doc._id;
 641  }
 642
 643  if (this.$__.shardval) {
 644    paths = Object.keys(this.$__.shardval);
 645    len = paths.length;
 646
 647    for (var i = 0; i < len; ++i) {
 648      where[paths[i]] = this.$__.shardval[paths[i]];
 649    }
 650  }
 651
 652  if (this._doc._id == null) {
 653    return new Error('No _id found on document!');
 654  }
 655
 656  return where;
 657};
 658
 659/**
 660 * Removes this document from the db.
 661 *
 662 * ####Example:
 663 *     product.remove(function (err, product) {
 664 *       if (err) return handleError(err);
 665 *       Product.findById(product._id, function (err, product) {
 666 *         console.log(product) // null
 667 *       })
 668 *     })
 669 *
 670 *
 671 * As an extra measure of flow control, remove will return a Promise (bound to `fn` if passed) so it could be chained, or hooked to recive errors
 672 *
 673 * ####Example:
 674 *     product.remove().then(function (product) {
 675 *        ...
 676 *     }).onRejected(function (err) {
 677 *        assert.ok(err)
 678 *     })
 679 *
 680 * @param {function(err,product)} [fn] optional callback
 681 * @return {Promise} Promise
 682 * @api public
 683 */
 684
 685Model.prototype.remove = function remove(options, fn) {
 686  if (typeof options === 'function') {
 687    fn = options;
 688    options = undefined;
 689  }
 690
 691  if (!options) {
 692    options = {};
 693  }
 694
 695  if (this.$__.removing) {
 696    if (fn) {
 697      this.$__.removing.then(
 698          function(res) { fn(null, res); },
 699          function(err) { fn(err); });
 700    }
 701    return this;
 702  }
 703
 704  var _this = this;
 705  var Promise = PromiseProvider.get();
 706
 707  if (fn) {
 708    fn = this.constructor.$wrapCallback(fn);
 709  }
 710
 711  this.$__.removing = new Promise.ES6(function(resolve, reject) {
 712    var where = _this.$__where();
 713    if (where instanceof Error) {
 714      reject(where);
 715      fn && fn(where);
 716      return;
 717    }
 718
 719    if (!options.safe && _this.schema.options.safe) {
 720      options.safe = _this.schema.options.safe;
 721    }
 722
 723    _this.collection.remove(where, options, function(err) {
 724      if (!err) {
 725        _this.emit('remove', _this);
 726        resolve(_this);
 727        fn && fn(null, _this);
 728        return;
 729      }
 730      reject(err);
 731      fn && fn(err);
 732    });
 733  });
 734  return this.$__.removing;
 735};
 736
 737/**
 738 * Returns another Model instance.
 739 *
 740 * ####Example:
 741 *
 742 *     var doc = new Tank;
 743 *     doc.model('User').findById(id, callback);
 744 *
 745 * @param {String} name model name
 746 * @api public
 747 */
 748
 749Model.prototype.model = function model(name) {
 750  return this.db.model(name);
 751};
 752
 753/**
 754 * Adds a discriminator type.
 755 *
 756 * ####Example:
 757 *
 758 *     function BaseSchema() {
 759 *       Schema.apply(this, arguments);
 760 *
 761 *       this.add({
 762 *         name: String,
 763 *         createdAt: Date
 764 *       });
 765 *     }
 766 *     util.inherits(BaseSchema, Schema);
 767 *
 768 *     var PersonSchema = new BaseSchema();
 769 *     var BossSchema = new BaseSchema({ department: String });
 770 *
 771 *     var Person = mongoose.model('Person', PersonSchema);
 772 *     var Boss = Person.discriminator('Boss', BossSchema);
 773 *
 774 * @param {String} name   discriminator model name
 775 * @param {Schema} schema discriminator model schema
 776 * @api public
 777 */
 778
 779Model.discriminator = function discriminator(name, schema) {
 780  var CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
 781    toJSON: true,
 782    toObject: true,
 783    _id: true,
 784    id: true
 785  };
 786
 787  if (!(schema && schema.instanceOfSchema)) {
 788    throw new Error('You must pass a valid discriminator Schema');
 789  }
 790
 791  if (this.schema.discriminatorMapping && !this.schema.discriminatorMapping.isRoot) {
 792    throw new Error('Discriminator "' + name +
 793        '" can only be a discriminator of the root model');
 794  }
 795
 796  var key = this.schema.options.discriminatorKey;
 797  if (schema.path(key)) {
 798    throw new Error('Discriminator "' + name +
 799        '" cannot have field with name "' + key + '"');
 800  }
 801
 802  function merge(schema, baseSchema) {
 803    utils.merge(schema, baseSchema);
 804
 805    var obj = {};
 806    obj[key] = {
 807      default: name,
 808      set: function() {
 809        throw new Error('Can\'t set discriminator key "' + key + '"');
 810      }
 811    };
 812    obj[key][schema.options.typeKey] = String;
 813    schema.add(obj);
 814    schema.discriminatorMapping = {key: key, value: name, isRoot: false};
 815
 816    if (baseSchema.options.collection) {
 817      schema.options.collection = baseSchema.options.collection;
 818    }
 819
 820    var toJSON = schema.options.toJSON;
 821    var toObject = schema.options.toObject;
 822    var _id = schema.options._id;
 823    var id = schema.options.id;
 824
 825    var keys = Object.keys(schema.options);
 826
 827    for (var i = 0; i < keys.length; ++i) {
 828      var _key = keys[i];
 829      if (!CUSTOMIZABLE_DISCRIMINATOR_OPTIONS[_key]) {
 830        if (!utils.deepEqual(schema.options[_key], baseSchema.options[_key])) {
 831          throw new Error('Can\'t customize discriminator option ' + _key +
 832              ' (can only modify ' +
 833              Object.keys(CUSTOMIZABLE_DISCRIMINATOR_OPTIONS).join(', ') +
 834              ')');
 835        }
 836      }
 837    }
 838
 839    schema.options = utils.clone(baseSchema.options);
 840    if (toJSON) schema.options.toJSON = toJSON;
 841    if (toObject) schema.options.toObject = toObject;
 842    if (typeof _id !== 'undefined') {
 843      schema.options._id = _id;
 844    }
 845    schema.options.id = id;
 846
 847    schema.callQueue = baseSchema.callQueue.concat(schema.callQueue.slice(schema._defaultMiddleware.length));
 848    schema._requiredpaths = undefined; // reset just in case Schema#requiredPaths() was called on either schema
 849  }
 850
 851  // merges base schema into new discriminator schema and sets new type field.
 852  merge(schema, this.schema);
 853
 854  if (!this.discriminators) {
 855    this.discriminators = {};
 856  }
 857
 858  if (!this.schema.discriminatorMapping) {
 859    this.schema.discriminatorMapping = {key: key, value: null, isRoot: true};
 860  }
 861
 862  if (this.discriminators[name]) {
 863    throw new Error('Discriminator with name "' + name + '" already exists');
 864  }
 865  if (this.db.models[name]) {
 866    throw new MongooseError.OverwriteModelError(name);
 867  }
 868
 869  this.discriminators[name] = this.db.model(name, schema, this.collection.name);
 870  this.discriminators[name].prototype.__proto__ = this.prototype;
 871  Object.defineProperty(this.discriminators[name], 'baseModelName', {
 872    value: this.modelName,
 873    configurable: true,
 874    writable: false
 875  });
 876
 877  // apply methods and statics
 878  applyMethods(this.discriminators[name], schema);
 879  applyStatics(this.discriminators[name], schema);
 880
 881  return this.discriminators[name];
 882};
 883
 884// Model (class) features
 885
 886/*!
 887 * Give the constructor the ability to emit events.
 888 */
 889
 890for (var i in EventEmitter.prototype) {
 891  Model[i] = EventEmitter.prototype[i];
 892}
 893
 894/**
 895 * Called when the model compiles.
 896 *
 897 * @api private
 898 */
 899
 900Model.init = function init() {
 901  if ((this.schema.options.autoIndex) ||
 902      (this.schema.options.autoIndex === null && this.db.config.autoIndex)) {
 903    this.ensureIndexes({ __noPromise: true });
 904  }
 905
 906  this.schema.emit('init', this);
 907};
 908
 909/**
 910 * Sends `ensureIndex` commands to mongo for each index declared in the schema.
 911 *
 912 * ####Example:
 913 *
 914 *     Event.ensureIndexes(function (err) {
 915 *       if (err) return handleError(err);
 916 *     });
 917 *
 918 * After completion, an `index` event is emitted on this `Model` passing an error if one occurred.
 919 *
 920 * ####Example:
 921 *
 922 *     var eventSchema = new Schema({ thing: { type: 'string', unique: true }})
 923 *     var Event = mongoose.model('Event', eventSchema);
 924 *
 925 *     Event.on('index', function (err) {
 926 *       if (err) console.error(err); // error occurred during index creation
 927 *     })
 928 *
 929 * _NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution._
 930 *
 931 * The `ensureIndex` commands are not sent in parallel. This is to avoid the `MongoError: cannot add index with a background operation in progress` error. See [this ticket](https://github.com/Automattic/mongoose/issues/1365) for more information.
 932 *
 933 * @param {Object} [options] internal options
 934 * @param {Function} [cb] optional callback
 935 * @return {Promise}
 936 * @api public
 937 */
 938
 939Model.ensureIndexes = function ensureIndexes(options, callback) {
 940  if (typeof options === 'function') {
 941    callback = options;
 942    options = null;
 943  }
 944
 945  if (options && options.__noPromise) {
 946    _ensureIndexes(this, callback);
 947    return;
 948  }
 949
 950  if (callback) {
 951    callback = this.$wrapCallback(callback);
 952  }
 953
 954  var _this = this;
 955  var Promise = PromiseProvider.get();
 956  return new Promise.ES6(function(resolve, reject) {
 957    _ensureIndexes(_this, function(error) {
 958      if (error) {
 959        callback && callback(error);
 960        reject(error);
 961      }
 962      callback && callback();
 963      resolve();
 964    });
 965  });
 966};
 967
 968function _ensureIndexes(model, callback) {
 969  var indexes = model.schema.indexes();
 970  if (!indexes.length) {
 971    setImmediate(function() {
 972      callback && callback();
 973    });
 974    return;
 975  }
 976  // Indexes are created one-by-one to support how MongoDB < 2.4 deals
 977  // with background indexes.
 978
 979  var done = function(err) {
 980    if (err && model.schema.options.emitIndexErrors) {
 981      model.emit('error', err);
 982    }
 983    model.emit('index', err);
 984    callback && callback(err);
 985  };
 986
 987  var indexSingleDone = function(err, fields, options, name) {
 988    model.emit('index-single-done', err, fields, options, name);
 989  };
 990  var indexSingleStart = function(fields, options) {
 991    model.emit('index-single-start', fields, options);
 992  };
 993
 994  var create = function() {
 995    var index = indexes.shift();
 996    if (!index) return done();
 997
 998    var indexFields = index[0];
 999    var options = index[1];
1000    _handleSafe(options);
1001
1002    indexSingleStart(indexFields, options);
1003
1004    model.collection.ensureIndex(indexFields, options, tick(function(err, name) {
1005      indexSingleDone(err, indexFields, options, name);
1006      if (err) {
1007        return done(err);
1008      }
1009      create();
1010    }));
1011  };
1012
1013  setImmediate(function() {
1014    create();
1015  });
1016}
1017
1018function _handleSafe(options) {
1019  if (options.safe) {
1020    if (typeof options.safe === 'boolean') {
1021      options.w = options.safe;
1022      delete options.safe;
1023    }
1024    if (typeof options.safe === 'object') {
1025      options.w = options.safe.w;
1026      options.j = options.safe.j;
1027      options.wtimeout = options.safe.wtimeout;
1028      delete options.safe;
1029    }
1030  }
1031}
1032
1033/**
1034 * Schema the model uses.
1035 *
1036 * @property schema
1037 * @receiver Model
1038 * @api public
1039 */
1040
1041Model.schema;
1042
1043/*!
1044 * Connection instance the model uses.
1045 *
1046 * @property db
1047 * @receiver Model
1048 * @api public
1049 */
1050
1051Model.db;
1052
1053/*!
1054 * Collection the model uses.
1055 *
1056 * @property collection
1057 * @receiver Model
1058 * @api public
1059 */
1060
1061Model.collection;
1062
1063/**
1064 * Base Mongoose instance the model uses.
1065 *
1066 * @property base
1067 * @receiver Model
1068 * @api public
1069 */
1070
1071Model.base;
1072
1073/**
1074 * Registered discriminators for this model.
1075 *
1076 * @property discriminators
1077 * @receiver Model
1078 * @api public
1079 */
1080
1081Model.discriminators;
1082
1083/**
1084 * Removes documents from the collection.
1085 *
1086 * ####Example:
1087 *
1088 *     Comment.remove({ title: 'baby born from alien father' }, function (err) {
1089 *
1090 *     });
1091 *
1092 * ####Note:
1093 *
1094 * To remove documents without waiting for a response from MongoDB, do not pass a `callback`, then call `exec` on the returned [Query](#query-js):
1095 *
1096 *     var query = Comment.remove({ _id: id });
1097 *     query.exec();
1098 *
1099 * ####Note:
1100 *
1101 * This method sends a remove command directly to MongoDB, no Mongoose documents are involved. Because no Mongoose documents are involved, _no middleware (hooks) are executed_.
1102 *
1103 * @param {Object} conditions
1104 * @param {Function} [callback]
1105 * @return {Query}
1106 * @api public
1107 */
1108
1109Model.remove = function remove(conditions, callback) {
1110  if (typeof conditions === 'function') {
1111    callback = conditions;
1112    conditions = {};
1113  }
1114
1115  // get the mongodb collection object
1116  var mq = new this.Query(conditions, {}, this, this.collection);
1117
1118  if (callback) {
1119    callback = this.$wrapCallback(callback);
1120  }
1121
1122  return mq.remove(callback);
1123};
1124
1125/**
1126 * Finds documents
1127 *
1128 * The `conditions` are cast to their respective SchemaTypes before the command is sent.
1129 *
1130 * ####Examples:
1131 *
1132 *     // named john and at least 18
1133 *     MyModel.find({ name: 'john', age: { $gte: 18 }});
1134 *
1135 *     // executes immediately, passing results to callback
1136 *     MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {});
1137 *
1138 *     // name LIKE john and only selecting the "name" and "friends" fields, executing immediately
1139 *     MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { })
1140 *
1141 *     // passing options
1142 *     MyModel.find({ name: /john/i }, null, { skip: 10 })
1143 *
1144 *     // passing options and executing immediately
1145 *     MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {});
1146 *
1147 *     // executing a query explicitly
1148 *     var query = MyModel.find({ name: /john/i }, null, { skip: 10 })
1149 *     query.exec(function (err, docs) {});
1150 *
1151 *     // using the promise returned from executing a query
1152 *     var query = MyModel.find({ name: /john/i }, null, { skip: 10 });
1153 *     var promise = query.exec();
1154 *     promise.addBack(function (err, docs) {});
1155 *
1156 * @param {Object} conditions
1157 * @param {Object} [projection] optional fields to return (http://bit.ly/1HotzBo)
1158 * @param {Object} [options] optional
1159 * @param {Function} [callback]
1160 * @return {Query}
1161 * @see field selection #query_Query-select
1162 * @see promise #promise-js
1163 * @api public
1164 */
1165
1166Model.find = function find(conditions, projection, options, callback) {
1167  if (typeof conditions === 'function') {
1168    callback = conditions;
1169    conditions = {};
1170    projection = null;
1171    options = null;
1172  } else if (typeof projection === 'function') {
1173    callback = projection;
1174    projection = null;
1175    options = null;
1176  } else if (typeof options === 'function') {
1177    callback = options;
1178    options = null;
1179  }
1180
1181  var mq = new this.Query({}, {}, this, this.collection);
1182  mq.select(projection);
1183  mq.setOptions(options);
1184  if (this.schema.discriminatorMapping && mq.selectedInclusively()) {
1185    mq.select(this.schema.options.discriminatorKey);
1186  }
1187
1188  if (callback) {
1189    callback = this.$wrapCallback(callback);
1190  }
1191
1192  return mq.find(conditions, callback);
1193};
1194
1195/**
1196 * Finds a single document by its _id field. `findById(id)` is almost*
1197 * equivalent to `findOne({ _id: id })`.
1198 *
1199 * The `id` is cast based on the Schema before sending the command.
1200 *
1201 * Note: `findById()` triggers `findOne` hooks.
1202 *
1203 * * Except for how it treats `undefined`. Because the MongoDB driver
1204 * deletes keys that have value `undefined`, `findById(undefined)` gets
1205 * translated to `findById({ _id: null })`.
1206 *
1207 * ####Example:
1208 *
1209 *     // find adventure by id and execute immediately
1210 *     Adventure.findById(id, function (err, adventure) {});
1211 *
1212 *     // same as above
1213 *     Adventure.findById(id).exec(callback);
1214 *
1215 *     // select only the adventures name and length
1216 *     Adventure.findById(id, 'name length', function (err, adventure) {});
1217 *
1218 *     // same as above
1219 *     Adventure.findById(id, 'name length').exec(callback);
1220 *
1221 *     // include all properties except for `length`
1222 *     Adventure.findById(id, '-length').exec(function (err, adventure) {});
1223 *
1224 *     // passing options (in this case return the raw js objects, not mongoose documents by passing `lean`
1225 *     Adventure.findById(id, 'name', { lean: true }, function (err, doc) {});
1226 *
1227 *     // same as above
1228 *     Adventure.findById(id, 'name').lean().exec(function (err, doc) {});
1229 *
1230 * @param {Object|String|Number} id value of `_id` to query by
1231 * @param {Object} [projection] optional fields to return (http://bit.ly/1HotzBo)
1232 * @param {Object} [options] optional
1233 * @param {Function} [callback]
1234 * @return {Query}
1235 * @see field selection #query_Query-select
1236 * @see lean queries #query_Query-lean
1237 * @api public
1238 */
1239
1240Model.findById = function findById(id, projection, options, callback) {
1241  if (typeof id === 'undefined') {
1242    id = null;
1243  }
1244
1245  if (callback) {
1246    callback = this.$wrapCallback(callback);
1247  }
1248
1249  return this.findOne({_id: id}, projection, options, callback);
1250};
1251
1252/**
1253 * Finds one document.
1254 *
1255 * The `conditions` are cast to their respective SchemaTypes before the command is sent.
1256 *
1257 * ####Example:
1258 *
1259 *     // find one iphone adventures - iphone adventures??
1260 *     Adventure.findOne({ type: 'iphone' }, function (err, adventure) {});
1261 *
1262 *     // same as above
1263 *     Adventure.findOne({ type: 'iphone' }).exec(function (err, adventure) {});
1264 *
1265 *     // select only the adventures name
1266 *     Adventure.findOne({ type: 'iphone' }, 'name', function (err, adventure) {});
1267 *
1268 *     // same as above
1269 *     Adventure.findOne({ type: 'iphone' }, 'name').exec(function (err, adventure) {});
1270 *
1271 *     // specify options, in this case lean
1272 *     Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }, callback);
1273 *
1274 *     // same as above
1275 *     Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }).exec(callback);
1276 *
1277 *     // chaining findOne queries (same as above)
1278 *     Adventure.findOne({ type: 'iphone' }).select('name').lean().exec(callback);
1279 *
1280 * @param {Object} [conditions]
1281 * @param {Object} [projection] optional fields to return (http://bit.ly/1HotzBo)
1282 * @param {Object} [options] optional
1283 * @param {Function} [callback]
1284 * @return {Query}
1285 * @see field selection #query_Query-select
1286 * @see lean queries #query_Query-lean
1287 * @api public
1288 */
1289
1290Model.findOne = function findOne(conditions, projection, options, callback) {
1291  if (typeof options === 'function') {
1292    callback = options;
1293    options = null;
1294  } else if (typeof projection === 'function') {
1295    callback = projection;
1296    projection = null;
1297    options = null;
1298  } else if (typeof conditions === 'function') {
1299    callback = conditions;
1300    conditions = {};
1301    projection = null;
1302    options = null;
1303  }
1304
1305  // get the mongodb collection object
1306  var mq = new this.Query({}, {}, this, this.collection);
1307  mq.select(projection);
1308  mq.setOptions(options);
1309  if (this.schema.discriminatorMapping && mq.selectedInclusively()) {
1310    mq.select(this.schema.options.discriminatorKey);
1311  }
1312
1313  if (callback) {
1314    callback = this.$wrapCallback(callback);
1315  }
1316
1317  return mq.findOne(conditions, callback);
1318};
1319
1320/**
1321 * Counts number of matching documents in a database collection.
1322 *
1323 * ####Example:
1324 *
1325 *     Adventure.count({ type: 'jungle' }, function (err, count) {
1326 *       if (err) ..
1327 *       console.log('there are %d jungle adventures', count);
1328 *     });
1329 *
1330 * @param {Object} conditions
1331 * @param {Function} [callback]
1332 * @return {Query}
1333 * @api public
1334 */
1335
1336Model.count = function count(conditions, callback) {
1337  if (typeof conditions === 'function') {
1338    callback = conditions;
1339    conditions = {};
1340  }
1341
1342  // get the mongodb collection object
1343  var mq = new this.Query({}, {}, this, this.collection);
1344
1345  if (callback) {
1346    callback = this.$wrapCallback(callback);
1347  }
1348
1349  return mq.count(conditions, callback);
1350};
1351
1352/**
1353 * Creates a Query for a `distinct` operation.
1354 *
1355 * Passing a `callback` immediately executes the query.
1356 *
1357 * ####Example
1358 *
1359 *     Link.distinct('url', { clicks: {$gt: 100}}, function (err, result) {
1360 *       if (err) return handleError(err);
1361 *
1362 *       assert(Array.isArray(result));
1363 *       console.log('unique urls with more than 100 clicks', result);
1364 *     })
1365 *
1366 *     var query = Link.distinct('url');
1367 *     query.exec(callback);
1368 *
1369 * @param {String} field
1370 * @param {Object} [conditions] optional
1371 * @param {Function} [callback]
1372 * @return {Query}
1373 * @api public
1374 */
1375
1376Model.distinct = function distinct(field, conditions, callback) {
1377  // get the mongodb collection object
1378  var mq = new this.Query({}, {}, this, this.collection);
1379
1380  if (typeof conditions === 'function') {
1381    callback = conditions;
1382    conditions = {};
1383  }
1384  if (callback) {
1385    callback = this.$wrapCallback(callback);
1386  }
1387
1388  return mq.distinct(field, conditions, callback);
1389};
1390
1391/**
1392 * Creates a Query, applies the passed conditions, and returns the Query.
1393 *
1394 * For example, instead of writing:
1395 *
1396 *     User.find({age: {$gte: 21, $lte: 65}}, callback);
1397 *
1398 * we can instead write:
1399 *
1400 *     User.where('age').gte(21).lte(65).exec(callback);
1401 *
1402 * Since the Query class also supports `where` you can continue chaining
1403 *
1404 *     User
1405 *     .where('age').gte(21).lte(65)
1406 *     .where('name', /^b/i)
1407 *     ... etc
1408 *
1409 * @param {String} path
1410 * @param {Object} [val] optional value
1411 * @return {Query}
1412 * @api public
1413 */
1414
1415Model.where = function where(path, val) {
1416  void val; // eslint
1417  // get the mongodb collection object
1418  var mq = new this.Query({}, {}, this, this.collection).find({});
1419  return mq.where.apply(mq, arguments);
1420};
1421
1422/**
1423 * Creates a `Query` and specifies a `$where` condition.
1424 *
1425 * Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via `find({ $where: javascript })`, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.
1426 *
1427 *     Blog.$where('this.username.indexOf("val") !== -1').exec(function (err, docs) {});
1428 *
1429 * @param {String|Function} argument is a javascript string or anonymous function
1430 * @method $where
1431 * @memberOf Model
1432 * @return {Query}
1433 * @see Query.$where #query_Query-%24where
1434 * @api public
1435 */
1436
1437Model.$where = function $where() {
1438  var mq = new this.Query({}, {}, this, this.collection).find({});
1439  return mq.$where.apply(mq, arguments);
1440};
1441
1442/**
1443 * Issues a mongodb findAndModify update command.
1444 *
1445 * 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.
1446 *
1447 * ####Options:
1448 *
1449 * - `new`: bool - if true, return the modified document rather than the original. defaults to false (changed in 4.0)
1450 * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
1451 * - `fields`: {Object|String} - Field selection. Equivalent to `.select(fields).findOneAndUpdate()`
1452 * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
1453 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1454 * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
1455 * - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/).
1456 * - `passRawResult`: if true, passes the [raw result from the MongoDB driver as the third callback parameter](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
1457 *
1458 *
1459 * ####Examples:
1460 *
1461 *     A.findOneAndUpdate(conditions, update, options, callback) // executes
1462 *     A.findOneAndUpdate(conditions, update, options)  // returns Query
1463 *     A.findOneAndUpdate(conditions, update, callback) // executes
1464 *     A.findOneAndUpdate(conditions, update)           // returns Query
1465 *     A.findOneAndUpdate()                             // returns Query
1466 *
1467 * ####Note:
1468 *
1469 * All top level update keys which are not `atomic` operation names are treated as set operations:
1470 *
1471 * ####Example:
1472 *
1473 *     var query = { name: 'borne' };
1474 *     Model.findOneAndUpdate(query, { name: 'jason borne' }, options, callback)
1475 *
1476 *     // is sent as
1477 *     Model.findOneAndUpdate(query, { $set: { name: 'jason borne' }}, options, callback)
1478 *
1479 * This helps prevent accidentally overwriting your document with `{ name: 'jason borne' }`.
1480 *
1481 * ####Note:
1482 *
1483 * Values are cast to their appropriate types when using the findAndModify helpers.
1484 * However, the below are never executed.
1485 *
1486 * - defaults
1487 * - setters
1488 *
1489 * `findAndModify` helpers support limited defaults and validation. You can
1490 * enable these by setting the `setDefaultsOnInsert` and `runValidators` options,
1491 * respectively.
1492 *
1493 * If you need full-fledged validation, use the traditional approach of first
1494 * retrieving the document.
1495 *
1496 *     Model.findById(id, function (err, doc) {
1497 *       if (err) ..
1498 *       doc.name = 'jason borne';
1499 *       doc.save(callback);
1500 *     });
1501 *
1502 * @param {Object} [conditions]
1503 * @param {Object} [update]
1504 * @param {Object} [options]
1505 * @param {Function} [callback]
1506 * @return {Query}
1507 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1508 * @api public
1509 */
1510
1511Model.findOneAndUpdate = function(conditions, update, options, callback) {
1512  if (typeof options === 'function') {
1513    callback = options;
1514    options = null;
1515  } else if (arguments.length === 1) {
1516    if (typeof conditions === 'function') {
1517      var msg = 'Model.findOneAndUpdate(): First argument must not be a function.\n\n'
1518          + '  ' + this.modelName + '.findOneAndUpdate(conditions, update, options, callback)\n'
1519          + '  ' + this.modelName + '.findOneAndUpdate(conditions, update, options)\n'
1520          + '  ' + this.modelName + '.findOneAndUpdate(conditions, update)\n'
1521          + '  ' + this.modelName + '.findOneAndUpdate(update)\n'
1522          + '  ' + this.modelName + '.findOneAndUpdate()\n';
1523      throw new TypeError(msg);
1524    }
1525    update = conditions;
1526    conditions = undefined;
1527  }
1528  if (callback) {
1529    callback = this.$wrapCallback(callback);
1530  }
1531
1532  var fields;
1533  if (options && options.fields) {
1534    fields = options.fields;
1535  }
1536
1537  update = utils.clone(update, {depopulate: 1});
1538  if (this.schema.options.versionKey && options && options.upsert) {
1539    if (!update.$setOnInsert) {
1540      update.$setOnInsert = {};
1541    }
1542    update.$setOnInsert[this.schema.options.versionKey] = 0;
1543  }
1544
1545  var mq = new this.Query({}, {}, this, this.collection);
1546  mq.select(fields);
1547
1548  return mq.findOneAndUpdate(conditions, update, options, callback);
1549};
1550
1551/**
1552 * Issues a mongodb findAndModify update command by a document's _id field.
1553 * `findByIdAndUpdate(id, ...)` is equivalent to `findOneAndUpdate({ _id: id }, ...)`.
1554 *
1555 * Finds a matching document, updates it according to the `update` arg,
1556 * passing any `options`, and returns the found document (if any) to the
1557 * callback. The query executes immediately if `callback` is passed else a
1558 * Query object is returned.
1559 *
1560 * This function triggers `findOneAndUpdate` middleware.
1561 *
1562 * ####Options:
1563 *
1564 * - `new`: bool - true to return the modified document rather than the original. defaults to false
1565 * - `upsert`: bool - creates the object if it doesn't exist. defaults to false.
1566 * - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
1567 * - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/).
1568 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1569 * - `select`: sets the document fields to return
1570 *
1571 * ####Examples:
1572 *
1573 *     A.findByIdAndUpdate(id, update, options, callback) // executes
1574 *     A.findByIdAndUpdate(id, update, options)  // returns Query
1575 *     A.findByIdAndUpdate(id, update, callback) // executes
1576 *     A.findByIdAndUpdate(id, update)           // returns Query
1577 *     A.findByIdAndUpdate()                     // returns Query
1578 *
1579 * ####Note:
1580 *
1581 * All top level update keys which are not `atomic` operation names are treated as set operations:
1582 *
1583 * ####Example:
1584 *
1585 *     Model.findByIdAndUpdate(id, { name: 'jason borne' }, options, callback)
1586 *
1587 *     // is sent as
1588 *     Model.findByIdAndUpdate(id, { $set: { name: 'jason borne' }}, options, callback)
1589 *
1590 * This helps prevent accidentally overwriting your document with `{ name: 'jason borne' }`.
1591 *
1592 * ####Note:
1593 *
1594 * Values are cast to their appropriate types when using the findAndModify helpers.
1595 * However, the below are never executed.
1596 *
1597 * - defaults
1598 * - setters
1599 *
1600 * `findAndModify` helpers support limited defaults and validation. You can
1601 * enable these by setting the `setDefaultsOnInsert` and `runValidators` options,
1602 * respectively.
1603 *
1604 * If you need full-fledged validation, use the traditional approach of first
1605 * retrieving the document.
1606 *
1607 *     Model.findById(id, function (err, doc) {
1608 *       if (err) ..
1609 *       doc.name = 'jason borne';
1610 *       doc.save(callback);
1611 *     });
1612 *
1613 * @param {Object|Number|String} id value of `_id` to query by
1614 * @param {Object} [update]
1615 * @param {Object} [options]
1616 * @param {Function} [callback]
1617 * @return {Query}
1618 * @see Model.findOneAndUpdate #model_Model.findOneAndUpdate
1619 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1620 * @api public
1621 */
1622
1623Model.findByIdAndUpdate = function(id, update, options, callback) {
1624  if (callback) {
1625    callback = this.$wrapCallback(callback);
1626  }
1627  if (arguments.length === 1) {
1628    if (typeof id === 'function') {
1629      var msg = 'Model.findByIdAndUpdate(): First argument must not be a function.\n\n'
1630          + '  ' + this.modelName + '.findByIdAndUpdate(id, callback)\n'
1631          + '  ' + this.modelName + '.findByIdAndUpdate(id)\n'
1632          + '  ' + this.modelName + '.findByIdAndUpdate()\n';
1633      throw new TypeError(msg);
1634    }
1635    return this.findOneAndUpdate({_id: id}, undefined);
1636  }
1637
1638  // if a model is passed in instead of an id
1639  if (id instanceof Document) {
1640    id = id._id;
1641  }
1642
1643  return this.findOneAndUpdate.call(this, {_id: id}, update, options, callback);
1644};
1645
1646/**
1647 * Issue a mongodb findAndModify remove command.
1648 *
1649 * Finds a matching document, removes it, passing the found document (if any) to the callback.
1650 *
1651 * Executes immediately if `callback` is passed else a Query object is returned.
1652 *
1653 * ####Options:
1654 *
1655 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1656 * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
1657 * - `select`: sets the document fields to return
1658 *
1659 * ####Examples:
1660 *
1661 *     A.findOneAndRemove(conditions, options, callback) // executes
1662 *     A.findOneAndRemove(conditions, options)  // return Query
1663 *     A.findOneAndRemove(conditions, callback) // executes
1664 *     A.findOneAndRemove(conditions) // returns Query
1665 *     A.findOneAndRemove()           // returns Query
1666 *
1667 * Values are cast to their appropriate types when using the findAndModify helpers.
1668 * However, the below are never executed.
1669 *
1670 * - defaults
1671 * - setters
1672 *
1673 * `findAndModify` helpers support limited defaults and validation. You can
1674 * enable these by setting the `setDefaultsOnInsert` and `runValidators` options,
1675 * respectively.
1676 *
1677 * If you need full-fledged validation, use the traditional approach of first
1678 * retrieving the document.
1679 *
1680 *     Model.findById(id, function (err, doc) {
1681 *       if (err) ..
1682 *       doc.name = 'jason borne';
1683 *       doc.save(callback);
1684 *     });
1685 *
1686 * @param {Object} conditions
1687 * @param {Object} [options]
1688 * @param {Function} [callback]
1689 * @return {Query}
1690 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1691 * @api public
1692 */
1693
1694Model.findOneAndRemove = function(conditions, options, callback) {
1695  if (arguments.length === 1 && typeof conditions === 'function') {
1696    var msg = 'Model.findOneAndRemove(): First argument must not be a function.\n\n'
1697        + '  ' + this.modelName + '.findOneAndRemove(conditions, callback)\n'
1698        + '  ' + this.modelName + '.findOneAndRemove(conditions)\n'
1699        + '  ' + this.modelName + '.findOneAndRemove()\n';
1700    throw new TypeError(msg);
1701  }
1702
1703  if (typeof options === 'function') {
1704    callback = options;
1705    options = undefined;
1706  }
1707  if (callback) {
1708    callback = this.$wrapCallback(callback);
1709  }
1710
1711  var fields;
1712  if (options) {
1713    fields = options.select;
1714    options.select = undefined;
1715  }
1716
1717  var mq = new this.Query({}, {}, this, this.collection);
1718  mq.select(fields);
1719
1720  return mq.findOneAndRemove(conditions, options, callback);
1721};
1722
1723/**
1724 * Issue a mongodb findAndModify remove command by a document's _id field. `findByIdAndRemove(id, ...)` is equivalent to `findOneAndRemove({ _id: id }, ...)`.
1725 *
1726 * Finds a matching document, removes it, passing the found document (if any) to the callback.
1727 *
1728 * Executes immediately if `callback` is passed, else a `Query` object is returned.
1729 *
1730 * ####Options:
1731 *
1732 * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1733 * - `select`: sets the document fields to return
1734 *
1735 * ####Examples:
1736 *
1737 *     A.findByIdAndRemove(id, options, callback) // executes
1738 *     A.findByIdAndRemove(id, options)  // return Query
1739 *     A.findByIdAndRemove(id, callback) // executes
1740 *     A.findByIdAndRemove(id) // returns Query
1741 *     A.findByIdAndRemove()           // returns Query
1742 *
1743 * @param {Object|Number|String} id value of `_id` to query by
1744 * @param {Object} [options]
1745 * @param {Function} [callback]
1746 * @return {Query}
1747 * @see Model.findOneAndRemove #model_Model.findOneAndRemove
1748 * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1749 */
1750
1751Model.findByIdAndRemove = function(id, options, callback) {
1752  if (arguments.length === 1 && typeof id === 'function') {
1753    var msg = 'Model.findByIdAndRemove(): First argument must not be a function.\n\n'
1754        + '  ' + this.modelName + '.findByIdAndRemove(id, callback)\n'
1755        + '  ' + this.modelName + '.findByIdAndRemove(id)\n'
1756        + '  ' + this.modelName + '.findByIdAndRemove()\n';
1757    throw new TypeError(msg);
1758  }
1759  if (callback) {
1760    callback = this.$wrapCallback(callback);
1761  }
1762
1763  return this.findOneAndRemove({_id: id}, options, callback);
1764};
1765
1766/**
1767 * Shortcut for saving one or more documents to the database.
1768 * `MyModel.create(docs)` does `new MyModel(doc).save()` for every doc in
1769 * docs.
1770 *
1771 * Hooks Triggered:
1772 * - `save()`
1773 *
1774 * ####Example:
1775 *
1776 *     // pass individual docs
1777 *     Candy.create({ type: 'jelly bean' }, { type: 'snickers' }, function (err, jellybean, snickers) {
1778 *       if (err) // ...
1779 *     });
1780 *
1781 *     // pass an array
1782 *     var array = [{ type: 'jelly bean' }, { type: 'snickers' }];
1783 *     Candy.create(array, function (err, candies) {
1784 *       if (err) // ...
1785 *
1786 *       var jellybean = candies[0];
1787 *       var snickers = candies[1];
1788 *       // ...
1789 *     });
1790 *
1791 *     // callback is optional; use the returned promise if you like:
1792 *     var promise = Candy.create({ type: 'jawbreaker' });
1793 *     promise.then(function (jawbreaker) {
1794 *       // ...
1795 *     })
1796 *
1797 * @param {Array|Object|*} doc(s)
1798 * @param {Function} [callback] callback
1799 * @return {Promise}
1800 * @api public
1801 */
1802
1803Model.create = function create(doc, callback) {
1804  var args;
1805  var cb;
1806
1807  if (Array.isArray(doc)) {
1808    args = doc;
1809    cb = callback;
1810  } else {
1811    var last = arguments[arguments.length - 1];
1812    if (typeof last === 'function') {
1813      cb = last;
1814      args = utils.args(arguments, 0, arguments.length - 1);
1815    } else {
1816      args = utils.args(arguments);
1817    }
1818  }
1819
1820  var Promise = PromiseProvider.get();
1821  var _this = this;
1822  if (cb) {
1823    cb = this.$wrapCallback(cb);
1824  }
1825
1826  var promise = new Promise.ES6(function(resolve, reject) {
1827    if (args.length === 0) {
1828      setImmediate(function() {
1829        cb && cb(null);
1830        resolve(null);
1831      });
1832      return;
1833    }
1834
1835    var toExecute = [];
1836    args.forEach(function(doc) {
1837      toExecute.push(function(callback) {
1838        var toSave = new _this(doc);
1839        var callbackWrapper = function(error, doc) {
1840          if (error) {
1841            return callback(error);
1842          }
1843          callback(null, doc);
1844        };
1845
1846        // Hack to avoid getting a promise because of
1847        // $__registerHooksFromSchema
1848        if (toSave.$__original_save) {
1849          toSave.$__original_save({ __noPromise: true }, callbackWrapper);
1850        } else {
1851          toSave.save({ __noPromise: true }, callbackWrapper);
1852        }
1853      });
1854    });
1855
1856    async.parallel(toExecute, function(error, savedDocs) {
1857      if (error) {
1858        cb && cb(error);
1859        reject(error);
1860        return;
1861      }
1862
1863      if (doc instanceof Array) {
1864        resolve(savedDocs);
1865        cb && cb.call(_this, null, savedDocs);
1866      } else {
1867        resolve.apply(promise, savedDocs);
1868        if (cb) {
1869          savedDocs.unshift(null);
1870          cb.apply(_this, savedDocs);
1871        }
1872      }
1873    });
1874  });
1875
1876  return promise;
1877};
1878
1879/**
1880 * Shortcut for validating an array of documents and inserting them into
1881 * MongoDB if they're all valid. This function is faster than `.create()`
1882 * because it only sends one operation to the server, rather than one for each
1883 * document.
1884 *
1885 * This function does **not** trigger save middleware.
1886 *
1887 * ####Example:
1888 *
1889 *     var arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }];
1890 *     Movies.insertMany(arr, function(error, docs) {});
1891 *
1892 * @param {Array|Object|*} doc(s)
1893 * @param {Function} [callback] callback
1894 * @return {Promise}
1895 * @api public
1896 */
1897
1898Model.insertMany = function(arr, callback) {
1899  var _this = this;
1900  if (callback) {
1901    callback = this.$wrapCallback(callback);
1902  }
1903
1904  var toExecute = [];
1905  arr.forEach(function(doc) {
1906    toExecute.push(function(callback) {
1907      doc = new _this(doc);
1908      doc.validate({ __noPromise: true }, function(error) {
1909        if (error) {
1910          return callback(error);
1911        }
1912        callback(null, doc);
1913      });
1914    });
1915  });
1916
1917  async.parallel(toExecute, function(error, docs) {
1918    if (error) {
1919      callback && callback(error);
1920      return;
1921    }
1922    var docObjects = docs.map(function(doc) {
1923      if (doc.initializeTimestamps) {
1924        return doc.initializeTimestamps().toObject({ virtuals: false });
1925      }
1926      return doc.toObject({ virtuals: false });
1927    });
1928    _this.collection.insertMany(docObjects, function(error) {
1929      if (error) {
1930        callback && callback(error);
1931        return;
1932      }
1933      for (var i = 0; i < docs.length; ++i) {
1934        docs[i].isNew = false;
1935        docs[i].emit('isNew', false);
1936      }
1937      callback && callback(null, docs);
1938    });
1939  });
1940};
1941
1942/**
1943 * Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
1944 * The document returned has no paths marked as modified initially.
1945 *
1946 * ####Example:
1947 *
1948 *     // hydrate previous data into a Mongoose document
1949 *     var mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });
1950 *
1951 * @param {Object} obj
1952 * @return {Document}
1953 * @api public
1954 */
1955
1956Model.hydrate = function(obj) {
1957  var model = require('./queryhelpers').createModel(this, obj);
1958  model.init(obj);
1959  return model;
1960};
1961
1962/**
1963 * Updates documents in the database without returning them.
1964 *
1965 * ####Examples:
1966 *
1967 *     MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
1968 *     MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, function (err, raw) {
1969 *       if (err) return handleError(err);
1970 *       console.log('The raw response from Mongo was ', raw);
1971 *     });
1972 *
1973 * ####Valid options:
1974 *
1975 *  - `safe` (boolean) safe mode (defaults to value set in schema (true))
1976 *  - `upsert` (boolean) whether to create the doc if it doesn't match (false)
1977 *  - `multi` (boolean) whether multiple documents should be updated (false)
1978 *  - `runValidators`: if true, runs [update validators](/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
1979 *  - `setDefaultsOnInsert`: if this and `upsert` are true, mongoose will apply the [defaults](http://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created. This option only works on MongoDB >= 2.4 because it relies on [MongoDB's `$setOnInsert` operator](https://docs.mongodb.org/v2.4/reference/operator/update/setOnInsert/).
1980 *  - `strict` (boolean) overrides the `strict` option for this update
1981 *  - `overwrite` (boolean) disables update-only mode, allowing you to overwrite the doc (false)
1982 *
1983 * All `update` values are cast to their appropriate SchemaTypes before being sent.
1984 *
1985 * The `callback` function receives `(err, rawResponse)`.
1986 *
1987 * - `err` is the error if any occurred
1988 * - `rawResponse` is the full response from Mongo
1989 *
1990 * ####Note:
1991 *
1992 * All top level keys which are not `atomic` operation names are treated as set operations:
1993 *
1994 * ####Example:
1995 *
1996 *     var query = { name: 'borne' };
1997 *     Model.update(query, { name: 'jason borne' }, options, callback)
1998 *
1999 *     // is sent as
2000 *     Model.update(query, { $set: { name: 'jason borne' }}, options, callback)
2001 *     // if overwrite option is false. If overwrite is true, sent without the $set wrapper.
2002 *
2003 * This helps prevent accidentally overwriting all documents in your collection with `{ name: 'jason borne' }`.
2004 *
2005 * ####Note:
2006 *
2007 * Be careful to not use an existing model instance for the update clause (this won't work and can cause weird behavior like infinite loops). Also, ensure that the update clause does not have an _id property, which causes Mongo to return a "Mod on _id not allowed" error.
2008 *
2009 * ####Note:
2010 *
2011 * To update documents without waiting for a response from MongoDB, do not pass a `callback`, then call `exec` on the returned [Query](#query-js):
2012 *
2013 *     Comment.update({ _id: id }, { $set: { text: 'changed' }}).exec();
2014 *
2015 * ####Note:
2016 *
2017 * Although values are casted to their appropriate types when using update, the following are *not* applied:
2018 *
2019 * - defaults
2020 * - setters
2021 * - validators
2022 * - middleware
2023 *
2024 * If you need those features, use the traditional approach of first retrieving the document.
2025 *
2026 *     Model.findOne({ name: 'borne' }, function (err, doc) {
2027 *       if (err) ..
2028 *       doc.name = 'jason borne';
2029 *       doc.save(callback);
2030 *     })
2031 *
2032 * @see strict http://mongoosejs.com/docs/guide.html#strict
2033 * @see response http://docs.mongodb.org/v2.6/reference/command/update/#output
2034 * @param {Object} conditions
2035 * @param {Object} doc
2036 * @param {Object} [options]
2037 * @param {Function} [callback]
2038 * @return {Query}
2039 * @api public
2040 */
2041
2042Model.update = function update(conditions, doc, options, callback) {
2043  var mq = new this.Query({}, {}, this, this.collection);
2044  if (callback) {
2045    callback = this.$wrapCallback(callback);
2046  }
2047  // gh-2406
2048  // make local deep copy of conditions
2049  if (conditions instanceof Document) {
2050    conditions = conditions.toObject();
2051  } else {
2052    conditions = utils.clone(conditions, {retainKeyOrder: true});
2053  }
2054  options = typeof options === 'function' ? options : utils.clone(options);
2055
2056  return mq.update(conditions, doc, options, callback);
2057};
2058
2059/**
2060 * Executes a mapReduce command.
2061 *
2062 * `o` is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation. See [node-mongodb-native mapReduce() documentation](http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#mapreduce) for more detail about options.
2063 *
2064 * ####Example:
2065 *
2066 *     var o = {};
2067 *     o.map = function () { emit(this.name, 1) }
2068 *     o.reduce = function (k, vals) { return vals.length }
2069 *     User.mapReduce(o, function (err, results) {
2070 *       console.log(results)
2071 *     })
2072 *
2073 * ####Other options:
2074 *
2075 * - `query` {Object} query filter object.
2076 * - `sort` {Object} sort input objects using this key
2077 * - `limit` {Number} max number of documents
2078 * - `keeptemp` {Boolean, default:false} keep temporary data
2079 * - `finalize` {Function} finalize function
2080 * - `scope` {Object} scope variables exposed to map/reduce/finalize during execution
2081 * - `jsMode` {Boolean, default:false} it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X
2082 * - `verbose` {Boolean, default:false} provide statistics on job execution time.
2083 * - `readPreference` {String}
2084 * - `out*` {Object, default: {inline:1}} sets the output target for the map reduce job.
2085 *
2086 * ####* out options:
2087 *
2088 * - `{inline:1}` the results are returned in an array
2089 * - `{replace: 'collectionName'}` add the results to collectionName: the results replace the collection
2090 * - `{reduce: 'collectionName'}` add the results to collectionName: if dups are detected, uses the reducer / finalize functions
2091 * - `{merge: 'collectionName'}` add the results to collectionName: if dups exist the new docs overwrite the old
2092 *
2093 * If `options.out` is set to `replace`, `merge`, or `reduce`, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the `lean` option; meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc).
2094 *
2095 * ####Example:
2096 *
2097 *     var o = {};
2098 *     o.map = function () { emit(this.name, 1) }
2099 *     o.reduce = function (k, vals) { return vals.length }
2100 *     o.out = { replace: 'createdCollectionNameForResults' }
2101 *     o.verbose = true;
2102 *
2103 *     User.mapReduce(o, function (err, model, stats) {
2104 *       console.log('map reduce took %d ms', stats.processtime)
2105 *       model.find().where('value').gt(10).exec(function (err, docs) {
2106 *         console.log(docs);
2107 *       });
2108 *     })
2109 *
2110 *     // a promise is returned so you may instead write
2111 *     var promise = User.mapReduce(o);
2112 *     promise.then(function (model, stats) {
2113 *       console.log('map reduce took %d ms', stats.processtime)
2114 *       return model.find().where('value').gt(10).exec();
2115 *     }).then(function (docs) {
2116 *        console.log(docs);
2117 *     }).then(null, handleError).end()
2118 *
2119 * @param {Object} o an object specifying map-reduce options
2120 * @param {Function} [callback] optional callback
2121 * @see http://www.mongodb.org/display/DOCS/MapReduce
2122 * @return {Promise}
2123 * @api public
2124 */
2125
2126Model.mapReduce = function mapReduce(o, callback) {
2127  var _this = this;
2128  if (callback) {
2129    callback = this.$wrapCallback(callback);
2130  }
2131  var Promise = PromiseProvider.get();
2132  return new Promise.ES6(function(resolve, reject) {
2133    if (!Model.mapReduce.schema) {
2134      var opts = {noId: true, noVirtualId: true, strict: false};
2135      Model.mapReduce.schema = new Schema({}, opts);
2136    }
2137
2138    if (!o.out) o.out = {inline: 1};
2139    if (o.verbose !== false) o.verbose = true;
2140
2141    o.map = String(o.map);
2142    o.reduce = String(o.reduce);
2143
2144    if (o.query) {
2145      var q = new _this.Query(o.query);
2146      q.cast(_this);
2147      o.query = q._conditions;
2148      q = undefined;
2149    }
2150
2151    _this.collection.mapReduce(null, null, o, function(err, ret, stats) {
2152      if (err) {
2153        callback && callback(err);
2154        reject(err);
2155        return;
2156      }
2157
2158      if (ret.findOne && ret.mapReduce) {
2159        // returned a collection, convert to Model
2160        var model = Model.compile(
2161            '_mapreduce_' + ret.collectionName
2162            , Model.mapReduce.schema
2163            , ret.collectionName
2164            , _this.db
2165            , _this.base);
2166
2167        model._mapreduce = true;
2168
2169        callback && callback(null, model, stats);
2170        return resolve(model, stats);
2171      }
2172
2173      callback && callback(null, ret, stats);
2174      resolve(ret, stats);
2175    });
2176  });
2177};
2178
2179/**
2180 * geoNear support for Mongoose
2181 *
2182 * ####Options:
2183 * - `lean` {Boolean} return the raw object
2184 * - All options supported by the driver are also supported
2185 *
2186 * ####Example:
2187 *
2188 *     // Legacy point
2189 *     Model.geoNear([1,3], { maxDistance : 5, spherical : true }, function(err, results, stats) {
2190 *        console.log(results);
2191 *     });
2192 *
2193 *     // geoJson
2194 *     var point = { type : "Point", coordinates : [9,9] };
2195 *     Model.geoNear(point, { maxDistance : 5, spherical : true }, function(err, results, stats) {
2196 *        console.log(results);
2197 *     });
2198 *
2199 * @param {Object|Array} GeoJSON point or legacy coordinate pair [x,y] to search near
2200 * @param {Object} options for the qurery
2201 * @param {Function} [callback] optional callback for the query
2202 * @return {Promise}
2203 * @see http://docs.mongodb.org/manual/core/2dsphere/
2204 * @see http://mongodb.github.io/node-mongodb-native/api-generated/collection.html?highlight=geonear#geoNear
2205 * @api public
2206 */
2207
2208Model.geoNear = function(near, options, callback) {
2209  if (typeof options === 'function') {
2210    callback = options;
2211    options = {};
2212  }
2213
2214  if (callback) {
2215    callback = this.$wrapCallback(callback);
2216  }
2217
2218  var _this = this;
2219  var Promise = PromiseProvider.get();
2220  if (!near) {
2221    return new Promise.ES6(function(resolve, reject) {
2222      var error = new Error('Must pass a near option to geoNear');
2223      reject(error);
2224      callback && callback(error);
2225    });
2226  }
2227
2228  var x, y;
2229
2230  return new Promise.ES6(function(resolve, reject) {
2231    var handler = function(err, res) {
2232      if (err) {
2233        reject(err);
2234        callback && callback(err);
2235        return;
2236      }
2237      if (options.lean) {
2238        resolve(res.results, res.stats);
2239        callback && callback(null, res.results, res.stats);
2240        return;
2241      }
2242
2243      var count = res.results.length;
2244      // if there are no results, fulfill the promise now
2245      if (count === 0) {
2246        resolve(res.results, res.stats);
2247        callback && callback(null, res.results, res.stats);
2248        return;
2249      }
2250
2251      var errSeen = false;
2252
2253      function init(err) {
2254        if (err && !errSeen) {
2255          errSeen = true;
2256          reject(err);
2257          callback && callback(err);
2258          return;
2259        }
2260        if (--count <= 0) {
2261          resolve(res.results, res.stats);
2262          callback && callback(null, res.results, res.stats);
2263        }
2264      }
2265
2266      for (var i = 0; i < res.results.length; i++) {
2267        var temp = res.results[i].obj;
2268        res.results[i].obj = new _this();
2269        res.results[i].obj.init(temp, init);
2270      }
2271    };
2272
2273    if (Array.isArray(near)) {
2274      if (near.length !== 2) {
2275        var error = new Error('If using legacy coordinates, must be an array ' +
2276            'of size 2 for geoNear');
2277        reject(error);
2278        callback && callback(error);
2279        return;
2280      }
2281      x = near[0];
2282      y = near[1];
2283      _this.collection.geoNear(x, y, options, handler);
2284    } else {
2285      if (near.type !== 'Point' || !Array.isArray(near.coordinates)) {
2286        error = new Error('Must pass either a legacy coordinate array or ' +
2287            'GeoJSON Point to geoNear');
2288        reject(error);
2289        callback && callback(error);
2290        return;
2291      }
2292
2293      _this.collection.geoNear(near, options, handler);
2294    }
2295  });
2296};
2297
2298/**
2299 * Performs [aggregations](http://docs.mongodb.org/manual/applications/aggregation/) on the models collection.
2300 *
2301 * If a `callback` is passed, the `aggregate` is executed and a `Promise` is returned. If a callback is not passed, the `aggregate` itself is returned.
2302 *
2303 * ####Example:
2304 *
2305 *     // Find the max balance of all accounts
2306 *     Users.aggregate(
2307 *         { $group: { _id: null, maxBalance: { $max: '$balance' }}}
2308 *       , { $project: { _id: 0, maxBalance: 1 }}
2309 *       , function (err, res) {
2310 *       if (err) return handleError(err);
2311 *       console.log(res); // [ { maxBalance: 98000 } ]
2312 *     });
2313 *
2314 *     // Or use the aggregation pipeline builder.
2315 *     Users.aggregate()
2316 *       .group({ _id: null, maxBalance: { $max: '$balance' } })
2317 *       .select('-id maxBalance')
2318 *       .exec(function (err, res) {
2319 *         if (err) return handleError(err);
2320 *         console.log(res); // [ { maxBalance: 98 } ]
2321 *     });
2322 *
2323 * ####NOTE:
2324 *
2325 * - Arguments are not cast to the model's schema because `$project` operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format.
2326 * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned).
2327 * - Requires MongoDB >= 2.1
2328 *
2329 * @see Aggregate #aggregate_Aggregate
2330 * @see MongoDB http://docs.mongodb.org/manual/applications/aggregation/
2331 * @param {Object|Array} [...] aggregation pipeline operator(s) or operator array
2332 * @param {Function} [callback]
2333 * @return {Aggregate|Promise}
2334 * @api public
2335 */
2336
2337Model.aggregate = function aggregate() {
2338  var args = [].slice.call(arguments),
2339      aggregate,
2340      callback;
2341
2342  if (typeof args[args.length - 1] === 'function') {
2343    callback = args.pop();
2344  }
2345
2346  if (args.length === 1 && util.isArray(args[0])) {
2347    aggregate = new Aggregate(args[0]);
2348  } else {
2349    aggregate = new Aggregate(args);
2350  }
2351
2352  aggregate.model(this);
2353
2354  if (typeof callback === 'undefined') {
2355    return aggregate;
2356  }
2357
2358  if (callback) {
2359    callback = this.$wrapCallback(callback);
2360  }
2361
2362  aggregate.exec(callback);
2363};
2364
2365/**
2366 * Implements `$geoSearch` functionality for Mongoose
2367 *
2368 * ####Example:
2369 *
2370 *     var options = { near: [10, 10], maxDistance: 5 };
2371 *     Locations.geoSearch({ type : "house" }, options, function(err, res) {
2372 *       console.log(res);
2373 *     });
2374 *
2375 * ####Options:
2376 * - `near` {Array} x,y point to search for
2377 * - `maxDistance` {Number} the maximum distance from the point near that a result can be
2378 * - `limit` {Number} The maximum number of results to return
2379 * - `lean` {Boolean} return the raw object instead of the Mongoose Model
2380 *
2381 * @param {Object} conditions an object that specifies the match condition (required)
2382 * @param {Object} options for the geoSearch, some (near, maxDistance) are required
2383 * @param {Function} [callback] optional callback
2384 * @return {Promise}
2385 * @see http://docs.mongodb.org/manual/reference/command/geoSearch/
2386 * @see http://docs.mongodb.org/manual/core/geohaystack/
2387 * @api public
2388 */
2389
2390Model.geoSearch = function(conditions, options, callback) {
2391  if (typeof options === 'function') {
2392    callback = options;
2393    options = {};
2394  }
2395  if (callback) {
2396    callback = this.$wrapCallback(callback);
2397  }
2398
2399  var _this = this;
2400  var Promise = PromiseProvider.get();
2401  return new Promise.ES6(function(resolve, reject) {
2402    var error;
2403    if (conditions === undefined || !utils.isObject(conditions)) {
2404      error = new Error('Must pass conditions to geoSearch');
2405    } else if (!options.near) {
2406      error = new Error('Must specify the near option in geoSearch');
2407    } else if (!Array.isArray(options.near)) {
2408      error = new Error('near option must be an array [x, y]');
2409    }
2410
2411    if (error) {
2412      callback && callback(error);
2413      reject(error);
2414      return;
2415    }
2416
2417    // send the conditions in the options object
2418    options.search = conditions;
2419
2420    _this.collection.geoHaystackSearch(options.near[0], options.near[1], options, function(err, res) {
2421      // have to deal with driver problem. Should be fixed in a soon-ish release
2422      // (7/8/2013)
2423      if (err) {
2424        callback && callback(err);
2425        reject(err);
2426        return;
2427      }
2428
2429      var count = res.results.length;
2430      if (options.lean || count === 0) {
2431        callback && callback(null, res.results, res.stats);
2432        resolve(res.results, res.stats);
2433        return;
2434      }
2435
2436      var errSeen = false;
2437
2438      function init(err) {
2439        if (err && !errSeen) {
2440          callback && callback(err);
2441          reject(err);
2442          return;
2443        }
2444
2445        if (!--count && !errSeen) {
2446          callback && callback(null, res.results, res.stats);
2447          resolve(res.results, res.stats);
2448        }
2449      }
2450
2451      for (var i = 0; i < res.results.length; i++) {
2452        var temp = res.results[i];
2453        res.results[i] = new _this();
2454        res.results[i].init(temp, {}, init);
2455      }
2456    });
2457  });
2458};
2459
2460/**
2461 * Populates document references.
2462 *
2463 * ####Available options:
2464 *
2465 * - path: space delimited path(s) to populate
2466 * - select: optional fields to select
2467 * - match: optional query conditions to match
2468 * - model: optional name of the model to use for population
2469 * - options: optional query options like sort, limit, etc
2470 *
2471 * ####Examples:
2472 *
2473 *     // populates a single object
2474 *     User.findById(id, function (err, user) {
2475 *       var opts = [
2476 *           { path: 'company', match: { x: 1 }, select: 'name' }
2477 *         , { path: 'notes', options: { limit: 10 }, model: 'override' }
2478 *       ]
2479 *
2480 *       User.populate(user, opts, function (err, user) {
2481 *         console.log(user);
2482 *       })
2483 *     })
2484 *
2485 *     // populates an array of objects
2486 *     User.find(match, function (err, users) {
2487 *       var opts = [{ path: 'company', match: { x: 1 }, select: 'name' }]
2488 *
2489 *       var promise = User.populate(users, opts);
2490 *       promise.then(console.log).end();
2491 *     })
2492 *
2493 *     // imagine a Weapon model exists with two saved documents:
2494 *     //   { _id: 389, name: 'whip' }
2495 *     //   { _id: 8921, name: 'boomerang' }
2496 *
2497 *     var user = { name: 'Indiana Jones', weapon: 389 }
2498 *     Weapon.populate(user, { path: 'weapon', model: 'Weapon' }, function (err, user) {
2499 *       console.log(user.weapon.name) // whip
2500 *     })
2501 *
2502 *     // populate many plain objects
2503 *     var users = [{ name: 'Indiana Jones', weapon: 389 }]
2504 *     users.push({ name: 'Batman', weapon: 8921 })
2505 *     Weapon.populate(users, { path: 'weapon' }, function (err, users) {
2506 *       users.forEach(function (user) {
2507 *         console.log('%s uses a %s', users.name, user.weapon.name)
2508 *         // Indiana Jones uses a whip
2509 *         // Batman uses a boomerang
2510 *       })
2511 *     })
2512 *     // Note that we didn't need to specify the Weapon model because
2513 *     // we were already using it's populate() method.
2514 *
2515 * @param {Document|Array} docs Either a single document or array of documents to populate.
2516 * @param {Object} options A hash of key/val (path, options) used for population.
2517 * @param {Function} [callback(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
2518 * @return {Promise}
2519 * @api public
2520 */
2521
2522Model.populate = function(docs, paths, callback) {
2523  var _this = this;
2524  if (callback) {
2525    callback = this.$wrapCallback(callback);
2526  }
2527
2528  // normalized paths
2529  var noPromise = paths && !!paths.__noPromise;
2530  paths = utils.populate(paths);
2531
2532  // data that should persist across subPopulate calls
2533  var cache = {};
2534
2535  if (noPromise) {
2536    _populate(this, docs, paths, cache, callback);
2537  } else {
2538    var Promise = PromiseProvider.get();
2539    return new Promise.ES6(function(resolve, reject) {
2540      _populate(_this, docs, paths, cache, function(error, docs) {
2541        if (error) {
2542          callback && callback(error);
2543          reject(error);
2544        } else {
2545          callback && callback(null, docs);
2546          resolve(docs);
2547        }
2548      });
2549    });
2550  }
2551};
2552
2553/*!
2554 * Populate helper
2555 *
2556 * @param {Model} model the model to use
2557 * @param {Document|Array} docs Either a single document or array of documents to populate.
2558 * @param {Object} paths
2559 * @param {Function} [cb(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`.
2560 * @return {Function}
2561 * @api private
2562 */
2563
2564function _populate(model, docs, paths, cache, callback) {
2565  var pending = paths.length;
2566
2567  if (pending === 0) {
2568    return callback(null, docs);
2569  }
2570
2571  // each path has its own query options and must be executed separately
2572  var i = pending;
2573  var path;
2574  while (i--) {
2575    path = paths[i];
2576    populate(model, docs, path, next);
2577  }
2578
2579  function next(err) {
2580    if (err) {
2581      return callback(err);
2582    }
2583    if (--pending) {
2584      return;
2585    }
2586    callback(null, docs);
2587  }
2588}
2589
2590/*!
2591 * Populates `docs`
2592 */
2593var excludeIdReg = /\s?-_id\s?/,
2594    excludeIdRegGlobal = /\s?-_id\s?/g;
2595
2596function populate(model, docs, options, callback) {
2597  var modelsMap;
2598
2599  // normalize single / multiple docs passed
2600  if (!Array.isArray(docs)) {
2601    docs = [docs];
2602  }
2603
2604  if (docs.length === 0 || docs.every(utils.isNullOrUndefined)) {
2605    return callback();
2606  }
2607
2608  modelsMap = getModelsMapForPopulate(model, docs, options);
2609
2610  var i, len = modelsMap.length,
2611      mod, match, select, vals = [];
2612
2613  function flatten(item) {
2614    // no need to include undefined values in our query
2615    return undefined !== item;
2616  }
2617
2618  var _remaining = len;
2619  var hasOne = false;
2620  for (i = 0; i < len; i++) {
2621    mod = modelsMap[i];
2622    select = mod.options.select;
2623
2624    if (mod.options.match) {
2625      match = utils.object.shallowCopy(mod.options.match);
2626    } else {
2627      match = {};
2628    }
2629
2630    var ids = utils.array.flatten(mod.ids, flatten);
2631    ids = utils.array.unique(ids);
2632
2633    if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) {
2634      --_remaining;
2635      continue;
2636    }
2637
2638    hasOne = true;
2639    if (mod.foreignField !== '_id' || !match['_id']) {
2640      match[mod.foreignField] = { $in: ids };
2641    }
2642
2643    var assignmentOpts = {};
2644    assignmentOpts.sort = mod.options.options && mod.options.options.sort || undefined;
2645    assignmentOpts.excludeId = excludeIdReg.test(select) || (select && select._id === 0);
2646
2647    if (assignmentOpts.excludeId) {
2648      // override the exclusion from the query so we can use the _id
2649      // for document matching during assignment. we'll delete the
2650      // _id back off before returning the result.
2651      if (typeof select === 'string') {
2652        select = select.replace(excludeIdRegGlobal, ' ');
2653      } else {
2654        // preserve original select conditions by copying
2655        select = utils.object.shallowCopy(select);
2656        delete select._id;
2657      }
2658    }
2659
2660    if (mod.options.options && mod.options.options.limit) {
2661      assignmentOpts.originalLimit = mod.options.options.limit;
2662      mod.options.options.limit = mod.options.options.limit * ids.length;
2663    }
2664
2665    var subPopulate = mod.options.populate;
2666    var query = mod.Model.find(match, select, mod.options.options);
2667    if (subPopulate) {
2668      query.populate(subPopulate);
2669    }
2670    query.exec(next.bind(this, mod, assignmentOpts));
2671  }
2672
2673  if (!hasOne) {
2674    return callback();
2675  }
2676
2677  function next(options, assignmentOpts, err, valsFromDb) {
2678    if (err) return callback(err);
2679    vals = vals.concat(valsFromDb);
2680    _assign(null, vals, options, assignmentOpts);
2681    if (--_remaining === 0) {
2682      callback();
2683    }
2684  }
2685
2686  function _assign(err, vals, mod, assignmentOpts) {
2687    if (err) return callback(err);
2688
2689    var options = mod.options;
2690    var _val;
2691    var lean = options.options && options.options.lean,
2692        len = vals.length,
2693        rawOrder = {}, rawDocs = {}, key, val;
2694
2695    // optimization:
2696    // record the document positions as returned by
2697    // the query result.
2698    for (var i = 0; i < len; i++) {
2699      val = vals[i];
2700      if (val) {
2701        _val = utils.getValue(mod.foreignField, val);
2702        if (Array.isArray(_val)) {
2703          var _valLength = _val.length;
2704          for (var j = 0; j < _valLength; ++j) {
2705            key = String(_val[j]);
2706            if (rawDocs[key]) {
2707              if (Array.isArray(rawDocs[key])) {
2708                rawDocs[key].push(val);
2709                rawOrder[key].push(i);
2710              } else {
2711                rawDocs[key] = [rawDocs[key], val];
2712                rawOrder[key] = [rawOrder[key], i];
2713              }
2714            } else {
2715              rawDocs[key] = val;
2716              rawOrder[key] = i;
2717            }
2718          }
2719        } else {
2720          key = String(utils.getValue(mod.foreignField, val));
2721          if (rawDocs[key]) {
2722            if (Array.isArray(rawDocs[key])) {
2723              rawDocs[key].push(val);
2724              rawOrder[key].push(i);
2725            } else {
2726              rawDocs[key] = [rawDocs[key], val];
2727              rawOrder[key] = [rawOrder[key], i];
2728            }
2729          } else {
2730            rawDocs[key] = val;
2731            rawOrder[key] = i;
2732          }
2733        }
2734        // flag each as result of population
2735        if (!lean) {
2736          val.$__.wasPopulated = true;
2737        }
2738      }
2739    }
2740
2741    assignVals({
2742      originalModel: model,
2743      rawIds: mod.ids,
2744      localField: mod.localField,
2745      foreignField: mod.foreignField,
2746      rawDocs: rawDocs,
2747      rawOrder: rawOrder,
2748      docs: mod.docs,
2749      path: options.path,
2750      options: assignmentOpts,
2751      justOne: mod.justOne,
2752      isVirtual: mod.isVirtual
2753    });
2754  }
2755}
2756
2757/*!
2758 * Assigns documents returned from a population query back
2759 * to the original document path.
2760 */
2761
2762function assignVals(o) {
2763  // replace the original ids in our intermediate _ids structure
2764  // with the documents found by query
2765  assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, o.options,
2766    o.localField, o.foreignField);
2767
2768  // now update the original documents being populated using the
2769  // result structure that contains real documents.
2770
2771  var docs = o.docs;
2772  var rawIds = o.rawIds;
2773  var options = o.options;
2774
2775  function setValue(val) {
2776    return valueFilter(val, options);
2777  }
2778
2779  for (var i = 0; i < docs.length; ++i) {
2780    if (utils.getValue(o.path, docs[i]) == null &&
2781      !o.originalModel.schema.virtuals[o.path]) {
2782      continue;
2783    }
2784
2785    if (o.isVirtual && !o.justOne && !Array.isArray(rawIds[i])) {
2786      rawIds[i] = [rawIds[i]];
2787    }
2788    utils.setValue(o.path, rawIds[i], docs[i], setValue);
2789  }
2790}
2791
2792/*!
2793 * Assign `vals` returned by mongo query to the `rawIds`
2794 * structure returned from utils.getVals() honoring
2795 * query sort order if specified by user.
2796 *
2797 * This can be optimized.
2798 *
2799 * Rules:
2800 *
2801 *   if the value of the path is not an array, use findOne rules, else find.
2802 *   for findOne the results are assigned directly to doc path (including null results).
2803 *   for find, if user specified sort order, results are assigned directly
2804 *   else documents are put back in original order of array if found in results
2805 *
2806 * @param {Array} rawIds
2807 * @param {Array} vals
2808 * @param {Boolean} sort
2809 * @api private
2810 */
2811
2812function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, localFields, foreignFields, recursed) {
2813  // honor user specified sort order
2814  var newOrder = [];
2815  var sorting = options.sort && rawIds.length > 1;
2816  var doc;
2817  var sid;
2818  var id;
2819
2820  for (var i = 0; i < rawIds.length; ++i) {
2821    id = rawIds[i];
2822
2823    if (Array.isArray(id)) {
2824      // handle [ [id0, id2], [id3] ]
2825      assignRawDocsToIdStructure(id, resultDocs, resultOrder, options, localFields, foreignFields, true);
2826      newOrder.push(id);
2827      continue;
2828    }
2829
2830    if (id === null && !sorting) {
2831      // keep nulls for findOne unless sorting, which always
2832      // removes them (backward compat)
2833      newOrder.push(id);
2834      continue;
2835    }
2836
2837    sid = String(id);
2838
2839    if (recursed) {
2840      // apply find behavior
2841
2842      // assign matching documents in original order unless sorting
2843      doc = resultDocs[sid];
2844      if (doc) {
2845        if (sorting) {
2846          newOrder[resultOrder[sid]] = doc;
2847        } else {
2848          newOrder.push(doc);
2849        }
2850      } else {
2851        newOrder.push(id);
2852      }
2853    } else {
2854      // apply findOne behavior - if document in results, assign, else assign null
2855      newOrder[i] = doc = resultDocs[sid] || null;
2856    }
2857  }
2858
2859  rawIds.length = 0;
2860  if (newOrder.length) {
2861    // reassign the documents based on corrected order
2862
2863    // forEach skips over sparse entries in arrays so we
2864    // can safely use this to our advantage dealing with sorted
2865    // result sets too.
2866    newOrder.forEach(function(doc, i) {
2867      if (!doc) {
2868        return;
2869      }
2870      rawIds[i] = doc;
2871    });
2872  }
2873}
2874
2875function getModelsMapForPopulate(model, docs, options) {
2876  var i, doc, len = docs.length,
2877      available = {},
2878      map = [],
2879      modelNameFromQuery = options.model && options.model.modelName || options.model,
2880      schema, refPath, Model, currentOptions, modelNames, modelName, discriminatorKey, modelForFindSchema;
2881
2882  var originalOptions = utils.clone(options);
2883  var isVirtual = false;
2884
2885  schema = model._getSchema(options.path);
2886
2887  if (schema && schema.caster) {
2888    schema = schema.caster;
2889  }
2890
2891  if (!schema && model.discriminators) {
2892    discriminatorKey = model.schema.discriminatorMapping.key;
2893  }
2894
2895  refPath = schema && schema.options && schema.options.refPath;
2896
2897  for (i = 0; i < len; i++) {
2898    doc = docs[i];
2899
2900    if (refPath) {
2901      modelNames = utils.getValue(refPath, doc);
2902    } else {
2903      if (!modelNameFromQuery) {
2904        var modelForCurrentDoc = model;
2905        var schemaForCurrentDoc;
2906
2907        if (!schema && discriminatorKey) {
2908          modelForFindSchema = utils.getValue(discriminatorKey, doc);
2909
2910          if (modelForFindSchema) {
2911            modelForCurrentDoc = model.db.model(modelForFindSchema);
2912            schemaForCurrentDoc = modelForCurrentDoc._getSchema(options.path);
2913
2914            if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
2915              schemaForCurrentDoc = schemaForCurrentDoc.caster;
2916            }
2917          }
2918        } else {
2919          schemaForCurrentDoc = schema;
2920        }
2921        var virtual = modelForCurrentDoc.schema.virtuals[options.path];
2922
2923        if (schemaForCurrentDoc && schemaForCurrentDoc.options && schemaForCurrentDoc.options.ref) {
2924          modelNames = [schemaForCurrentDoc.options.ref];
2925        } else if (virtual && virtual.options && virtual.options.ref) {
2926          modelNames = [virtual && virtual.options && virtual.options.ref];
2927          isVirtual = true;
2928        } else {
2929          modelNames = [model.modelName];
2930        }
2931      } else {
2932        modelNames = [modelNameFromQuery];  // query options
2933      }
2934    }
2935
2936    if (!modelNames) {
2937      continue;
2938    }
2939
2940    if (!Array.isArray(modelNames)) {
2941      modelNames = [modelNames];
2942    }
2943
2944    virtual = model.schema.virtuals[options.path];
2945    var localField = virtual && virtual.options ?
2946      virtual.options.localField :
2947      options.path;
2948    var foreignField = virtual && virtual.options ?
2949      virtual.options.foreignField :
2950      '_id';
2951    var justOne = virtual && virtual.options && virtual.options.justOne;
2952    if (virtual && virtual.options && virtual.options.ref) {
2953      isVirtual = true;
2954    }
2955
2956    var ret = convertTo_id(utils.getValue(localField, doc));
2957    var id = String(utils.getValue(foreignField, doc));
2958    options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
2959    if (doc.$__) {
2960      doc.populated(options.path, options._docs[id], options);
2961    }
2962
2963    var k = modelNames.length;
2964    while (k--) {
2965      modelName = modelNames[k];
2966      Model = originalOptions.model && originalOptions.model.modelName ?
2967        originalOptions.model :
2968        model.db.model(modelName);
2969
2970      if (!available[modelName]) {
2971        currentOptions = {
2972          model: Model
2973        };
2974
2975        utils.merge(currentOptions, options);
2976        if (schema && !discriminatorKey) {
2977          currentOptions.model = Model;
2978        }
2979        options.model = Model;
2980
2981        available[modelName] = {
2982          Model: Model,
2983          options: currentOptions,
2984          docs: [doc],
2985          ids: [ret],
2986          // Assume only 1 localField + foreignField
2987          localField: localField,
2988          foreignField: foreignField,
2989          justOne: justOne,
2990          isVirtual: isVirtual
2991        };
2992        map.push(available[modelName]);
2993      } else {
2994        available[modelName].docs.push(doc);
2995        available[modelName].ids.push(ret);
2996      }
2997    }
2998  }
2999
3000  return map;
3001}
3002
3003/*!
3004 * Retrieve the _id of `val` if a Document or Array of Documents.
3005 *
3006 * @param {Array|Document|Any} val
3007 * @return {Array|Document|Any}
3008 */
3009
3010function convertTo_id(val) {
3011  if (val instanceof Model) return val._id;
3012
3013  if (Array.isArray(val)) {
3014    for (var i = 0; i < val.length; ++i) {
3015      if (val[i] instanceof Model) {
3016        val[i] = val[i]._id;
3017      }
3018    }
3019    return val;
3020  }
3021
3022  return val;
3023}
3024
3025/*!
3026 * 1) Apply backwards compatible find/findOne behavior to sub documents
3027 *
3028 *    find logic:
3029 *      a) filter out non-documents
3030 *      b) remove _id from sub docs when user specified
3031 *
3032 *    findOne
3033 *      a) if no doc found, set to null
3034 *      b) remove _id from sub docs when user specified
3035 *
3036 * 2) Remove _ids when specified by users query.
3037 *
3038 * background:
3039 * _ids are left in the query even when user excludes them so
3040 * that population mapping can occur.
3041 */
3042
3043function valueFilter(val, assignmentOpts) {
3044  if (Array.isArray(val)) {
3045    // find logic
3046    var ret = [];
3047    var numValues = val.length;
3048    for (var i = 0; i < numValues; ++i) {
3049      var subdoc = val[i];
3050      if (!isDoc(subdoc)) continue;
3051      maybeRemoveId(subdoc, assignmentOpts);
3052      ret.push(subdoc);
3053      if (assignmentOpts.originalLimit &&
3054          ret.length >= assignmentOpts.originalLimit) {
3055        break;
3056      }
3057    }
3058
3059    // Since we don't want to have to create a new mongoosearray, make sure to
3060    // modify the array in place
3061    while (val.length > ret.length) {
3062      Array.prototype.pop.apply(val, []);
3063    }
3064    for (i = 0; i < ret.length; ++i) {
3065      val[i] = ret[i];
3066    }
3067    return val;
3068  }
3069
3070  // findOne
3071  if (isDoc(val)) {
3072    maybeRemoveId(val, assignmentOpts);
3073    return val;
3074  }
3075
3076  return null;
3077}
3078
3079/*!
3080 * Remove _id from `subdoc` if user specified "lean" query option
3081 */
3082
3083function maybeRemoveId(subdoc, assignmentOpts) {
3084  if (assignmentOpts.excludeId) {
3085    if (typeof subdoc.setValue === 'function') {
3086      delete subdoc._doc._id;
3087    } else {
3088      delete subdoc._id;
3089    }
3090  }
3091}
3092
3093/*!
3094 * Determine if `doc` is a document returned
3095 * by a populate query.
3096 */
3097
3098function isDoc(doc) {
3099  if (doc == null) {
3100    return false;
3101  }
3102
3103  var type = typeof doc;
3104  if (type === 'string') {
3105    return false;
3106  }
3107
3108  if (type === 'number') {
3109    return false;
3110  }
3111
3112  if (Buffer.isBuffer(doc)) {
3113    return false;
3114  }
3115
3116  if (doc.constructor.name === 'ObjectID') {
3117    return false;
3118  }
3119
3120  // only docs
3121  return true;
3122}
3123
3124/**
3125 * Finds the schema for `path`. This is different than
3126 * calling `schema.path` as it also resolves paths with
3127 * positional selectors (something.$.another.$.path).
3128 *
3129 * @param {String} path
3130 * @return {Schema}
3131 * @api private
3132 */
3133
3134Model._getSchema = function _getSchema(path) {
3135  return this.schema._getSchema(path);
3136};
3137
3138/*!
3139 * Compiler utility.
3140 *
3141 * @param {String} name model name
3142 * @param {Schema} schema
3143 * @param {String} collectionName
3144 * @param {Connection} connection
3145 * @param {Mongoose} base mongoose instance
3146 */
3147
3148Model.compile = function compile(name, schema, collectionName, connection, base) {
3149  var versioningEnabled = schema.options.versionKey !== false;
3150
3151  if (versioningEnabled && !schema.paths[schema.options.versionKey]) {
3152    // add versioning to top level documents only
3153    var o = {};
3154    o[schema.options.versionKey] = Number;
3155    schema.add(o);
3156  }
3157
3158  // generate new class
3159  function model(doc, fields, skipId) {
3160    if (!(this instanceof model)) {
3161      return new model(doc, fields, skipId);
3162    }
3163    Model.call(this, doc, fields, skipId);
3164  }
3165
3166  model.hooks = schema.s.hooks.clone();
3167  model.base = base;
3168  model.modelName = name;
3169  model.__proto__ = Model;
3170  model.prototype.__proto__ = Model.prototype;
3171  model.model = Model.prototype.model;
3172  model.db = model.prototype.db = connection;
3173  model.discriminators = model.prototype.discriminators = undefined;
3174
3175  model.prototype.$__setSchema(schema);
3176
3177  var collectionOptions = {
3178    bufferCommands: schema.options.bufferCommands,
3179    capped: schema.options.capped
3180  };
3181
3182  model.prototype.collection = connection.collection(
3183      collectionName
3184      , collectionOptions
3185  );
3186
3187  // apply methods and statics
3188  applyMethods(model, schema);
3189  applyStatics(model, schema);
3190
3191  model.schema = model.prototype.schema;
3192  model.collection = model.prototype.collection;
3193
3194  // Create custom query constructor
3195  model.Query = function() {
3196    Query.apply(this, arguments);
3197  };
3198  model.Query.prototype = Object.create(Query.prototype);
3199  model.Query.base = Query.base;
3200  applyQueryMethods(model, schema.query);
3201
3202  var kareemOptions = { useErrorHandlers: true };
3203  model.$__insertMany = model.hooks.createWrapper('insertMany',
3204    model.insertMany, model, kareemOptions);
3205  model.insertMany = function(arr, callback) {
3206    var Promise = PromiseProvider.get();
3207    return new Promise.ES6(function(resolve, reject) {
3208      model.$__insertMany(arr, function(error, result) {
3209        if (error) {
3210          callback && callback(error);
3211          return reject(error);
3212        }
3213        callback && callback(null, result);
3214        resolve(result);
3215      });
3216    });
3217  };
3218
3219  return model;
3220};
3221
3222/*!
3223 * Register methods for this model
3224 *
3225 * @param {Model} model
3226 * @param {Schema} schema
3227 */
3228var applyMethods = function(model, schema) {
3229  function apply(method, schema) {
3230    Object.defineProperty(model.prototype, method, {
3231      get: function() {
3232        var h = {};
3233        for (var k in schema.methods[method]) {
3234          h[k] = schema.methods[method][k].bind(this);
3235        }
3236        return h;
3237      },
3238      configurable: true
3239    });
3240  }
3241  for (var method in schema.methods) {
3242    if (typeof schema.methods[method] === 'function') {
3243      model.prototype[method] = schema.methods[method];
3244    } else {
3245      apply(method, schema);
3246    }
3247  }
3248};
3249
3250/*!
3251 * Register statics for this model
3252 * @param {Model} model
3253 * @param {Schema} schema
3254 */
3255var applyStatics = function(model, schema) {
3256  for (var i in schema.statics) {
3257    model[i] = schema.statics[i];
3258  }
3259};
3260
3261/*!
3262 * Register custom query methods for this model
3263 *
3264 * @param {Model} model
3265 * @param {Schema} schema
3266 */
3267
3268function applyQueryMethods(model, methods) {
3269  for (var i in methods) {
3270    model.Query.prototype[i] = methods[i];
3271  }
3272}
3273
3274/*!
3275 * Subclass this model with `conn`, `schema`, and `collection` settings.
3276 *
3277 * @param {Connection} conn
3278 * @param {Schema} [schema]
3279 * @param {String} [collection]
3280 * @return {Model}
3281 */
3282
3283Model.__subclass = function subclass(conn, schema, collection) {
3284  // subclass model using this connection and collection name
3285  var _this = this;
3286
3287  var Model = function Model(doc, fields, skipId) {
3288    if (!(this instanceof Model)) {
3289      return new Model(doc, fields, skipId);
3290    }
3291    _this.call(this, doc, fields, skipId);
3292  };
3293
3294  Model.__proto__ = _this;
3295  Model.prototype.__proto__ = _this.prototype;
3296  Model.db = Model.prototype.db = conn;
3297
3298  var s = schema && typeof schema !== 'string'
3299      ? schema
3300      : _this.prototype.schema;
3301
3302  var options = s.options || {};
3303
3304  if (!collection) {
3305    collection = _this.prototype.schema.get('collection')
3306        || utils.toCollectionName(_this.modelName, options);
3307  }
3308
3309  var collectionOptions = {
3310    bufferCommands: s ? options.bufferCommands : true,
3311    capped: s && options.capped
3312  };
3313
3314  Model.prototype.collection = conn.collection(collection, collectionOptions);
3315  Model.collection = Model.prototype.collection;
3316  Model.init();
3317  return Model;
3318};
3319
3320Model.$wrapCallback = function(callback) {
3321  var _this = this;
3322  return function() {
3323    try {
3324      callback.apply(null, arguments);
3325    } catch (error) {
3326      _this.emit('error', error);
3327    }
3328  };
3329};
3330
3331/*!
3332 * Module exports.
3333 */
3334
3335module.exports = exports = Model;