1 // query.js
  2 
  3 if ( typeof DBQuery == "undefined" ){
  4     DBQuery = function( mongo , db , collection , ns , query , fields , limit , skip , batchSize ){
  5         
  6         this._mongo = mongo; // 0
  7         this._db = db; // 1
  8         this._collection = collection; // 2
  9         this._ns = ns; // 3
 10         
 11         this._query = query || {}; // 4
 12         this._fields = fields; // 5
 13         this._limit = limit || 0; // 6
 14         this._skip = skip || 0; // 7
 15         this._batchSize = batchSize || 0;
 16 
 17         this._cursor = null;
 18         this._numReturned = 0;
 19         this._special = false;
 20         this._prettyShell = false;
 21     }
 22     print( "DBQuery probably won't have array access " );
 23 }
 24 
 25 DBQuery.prototype.help = function () {
 26     print("find() modifiers")
 27     print("\t.sort( {...} )")
 28     print("\t.limit( n )")
 29     print("\t.skip( n )")
 30     print("\t.count() - total # of objects matching query, ignores skip,limit")
 31     print("\t.size() - total # of objects cursor would return, honors skip,limit")
 32     print("\t.explain([verbose])")
 33     print("\t.hint(...)")
 34     print("\t.showDiskLoc() - adds a $diskLoc field to each returned object")
 35     print("\nCursor methods");
 36     print("\t.forEach( func )")
 37     print("\t.print() - output to console in full pretty format")
 38     print("\t.map( func )")
 39     print("\t.hasNext()")
 40     print("\t.next()")
 41 }
 42 
 43 DBQuery.prototype.clone = function(){
 44     var q =  new DBQuery( this._mongo , this._db , this._collection , this._ns , 
 45         this._query , this._fields , 
 46         this._limit , this._skip , this._batchSize );
 47     q._special = this._special;
 48     return q;
 49 }
 50 
 51 DBQuery.prototype._ensureSpecial = function(){
 52     if ( this._special )
 53         return;
 54     
 55     var n = { query : this._query };
 56     this._query = n;
 57     this._special = true;
 58 }
 59 
 60 DBQuery.prototype._checkModify = function(){
 61     if ( this._cursor )
 62         throw "query already executed";
 63 }
 64 
 65 DBQuery.prototype._exec = function(){
 66     if ( ! this._cursor ){
 67         assert.eq( 0 , this._numReturned );
 68         this._cursor = this._mongo.find( this._ns , this._query , this._fields , this._limit , this._skip , this._batchSize );
 69         this._cursorSeen = 0;
 70     }
 71     return this._cursor;
 72 }
 73 
 74 DBQuery.prototype.limit = function( limit ){
 75     this._checkModify();
 76     this._limit = limit;
 77     return this;
 78 }
 79 
 80 DBQuery.prototype.batchSize = function( batchSize ){
 81     this._checkModify();
 82     this._batchSize = batchSize;
 83     return this;
 84 }
 85 
 86 
 87 DBQuery.prototype.skip = function( skip ){
 88     this._checkModify();
 89     this._skip = skip;
 90     return this;
 91 }
 92 
 93 DBQuery.prototype.hasNext = function(){
 94     this._exec();
 95 
 96     if ( this._limit > 0 && this._cursorSeen >= this._limit )
 97         return false;
 98     var o = this._cursor.hasNext();
 99     return o;
100 }
101 
102 DBQuery.prototype.next = function(){
103     this._exec();
104     
105     var o = this._cursor.hasNext();
106     if ( o )
107         this._cursorSeen++;
108     else
109         throw "error hasNext: " + o;
110     
111     var ret = this._cursor.next();
112     if ( ret.$err && this._numReturned == 0 && ! this.hasNext() )
113         throw "error: " + tojson( ret );
114 
115     this._numReturned++;
116     return ret;
117 }
118 
119 DBQuery.prototype.toArray = function(){
120     if ( this._arr )
121         return this._arr;
122     
123     var a = [];
124     while ( this.hasNext() )
125         a.push( this.next() );
126     this._arr = a;
127     return a;
128 }
129 
130 DBQuery.prototype.count = function( applySkipLimit ){
131     var cmd = { count: this._collection.getName() };
132     if ( this._query ){
133         if ( this._special )
134             cmd.query = this._query.query;
135         else 
136             cmd.query = this._query;
137     }
138     cmd.fields = this._fields || {};
139 
140     if ( applySkipLimit ){
141         if ( this._limit )
142             cmd.limit = this._limit;
143         if ( this._skip )
144             cmd.skip = this._skip;
145     }
146     
147     var res = this._db.runCommand( cmd );
148     if( res && res.n != null ) return res.n;
149     throw "count failed: " + tojson( res );
150 }
151 
152 DBQuery.prototype.size = function(){
153     return this.count( true );
154 }
155 
156 DBQuery.prototype.countReturn = function(){
157     var c = this.count();
158 
159     if ( this._skip )
160         c = c - this._skip;
161 
162     if ( this._limit > 0 && this._limit < c )
163         return this._limit;
164     
165     return c;
166 }
167 
168 /**
169 * iterative count - only for testing
170 */
171 DBQuery.prototype.itcount = function(){
172     var num = 0;
173     while ( this.hasNext() ){
174         num++;
175         this.next();
176     }
177     return num;
178 }
179 
180 DBQuery.prototype.length = function(){
181     return this.toArray().length;
182 }
183 
184 DBQuery.prototype._addSpecial = function( name , value ){
185     this._ensureSpecial();
186     this._query[name] = value;
187     return this;
188 }
189 
190 DBQuery.prototype.sort = function( sortBy ){
191     return this._addSpecial( "orderby" , sortBy );
192 }
193 
194 DBQuery.prototype.hint = function( hint ){
195     return this._addSpecial( "$hint" , hint );
196 }
197 
198 DBQuery.prototype.min = function( min ) {
199     return this._addSpecial( "$min" , min );
200 }
201 
202 DBQuery.prototype.max = function( max ) {
203     return this._addSpecial( "$max" , max );
204 }
205 
206 DBQuery.prototype.showDiskLoc = function() {
207     return this._addSpecial( "$showDiskLoc" , true);
208 }
209 
210 DBQuery.prototype.forEach = function( func ){
211     while ( this.hasNext() )
212         func( this.next() );
213 }
214 
215 DBQuery.prototype.map = function( func ){
216     var a = [];
217     while ( this.hasNext() )
218         a.push( func( this.next() ) );
219     return a;
220 }
221 
222 DBQuery.prototype.arrayAccess = function( idx ){
223     return this.toArray()[idx];
224 }
225 
226 DBQuery.prototype.explain = function (verbose) {
227     /* verbose=true --> include allPlans, oldPlan fields */
228     var n = this.clone();
229     n._ensureSpecial();
230     n._query.$explain = true;
231     n._limit = n._limit * -1;
232     var e = n.next();
233     if (!verbose) {
234         delete e.allPlans;
235         delete e.oldPlan;
236     }
237     return e;
238 }
239 
240 DBQuery.prototype.snapshot = function(){
241     this._ensureSpecial();
242     this._query.$snapshot = true;
243     return this;
244 }
245 
246 DBQuery.prototype.pretty = function(){
247     this._prettyShell = true;
248     return this;
249 }
250 
251 DBQuery.prototype.shellPrint = function(){
252     try {
253         var n = 0;
254         while ( this.hasNext() && n < DBQuery.shellBatchSize ){
255             var s = this._prettyShell ? tojson( this.next() ) : tojson( this.next() , "" , true );
256             print( s );
257             n++;
258         }
259         if ( this.hasNext() ){
260             print( "has more" );
261             ___it___  = this;
262         }
263         else {
264             ___it___  = null;
265         }
266     }
267     catch ( e ){
268         print( e );
269     }
270     
271 }
272 
273 DBQuery.prototype.toString = function(){
274     return "DBQuery: " + this._ns + " -> " + tojson( this.query );
275 }
276 
277 DBQuery.shellBatchSize = 20;
278