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