1 // collection.js
  2 
  3 
  4 if ( ( typeof  DBCollection ) == "undefined" ){
  5     DBCollection = function( mongo , db , shortName , fullName ){
  6         this._mongo = mongo;
  7         this._db = db;
  8         this._shortName = shortName;
  9         this._fullName = fullName;
 10 
 11         this.verify();
 12     }
 13 }
 14 
 15 DBCollection.prototype.verify = function(){
 16     assert( this._fullName , "no fullName" );
 17     assert( this._shortName , "no shortName" );
 18     assert( this._db , "no db" );
 19 
 20     assert.eq( this._fullName , this._db._name + "." + this._shortName , "name mismatch" );
 21 
 22     assert( this._mongo , "no mongo in DBCollection" );
 23 }
 24 
 25 DBCollection.prototype.getName = function(){
 26     return this._shortName;
 27 }
 28 
 29 DBCollection.prototype.help = function () {
 30     var shortName = this.getName();
 31     print("DBCollection help");
 32     print("\tdb." + shortName + ".find().help() - show DBCursor help");
 33     print("\tdb." + shortName + ".count()");
 34     print("\tdb." + shortName + ".dataSize()");
 35     print("\tdb." + shortName + ".distinct( key ) - eg. db." + shortName + ".distinct( 'x' )");
 36     print("\tdb." + shortName + ".drop() drop the collection");
 37     print("\tdb." + shortName + ".dropIndex(name)");
 38     print("\tdb." + shortName + ".dropIndexes()");
 39     print("\tdb." + shortName + ".ensureIndex(keypattern,options) - options should be an object with these possible fields: name, unique, dropDups");
 40     print("\tdb." + shortName + ".reIndex()");
 41     print("\tdb." + shortName + ".find( [query] , [fields]) - first parameter is an optional query filter. second parameter is optional set of fields to return.");
 42     print("\t                                   e.g. db." + shortName + ".find( { x : 77 } , { name : 1 , x : 1 } )");
 43     print("\tdb." + shortName + ".find(...).count()");
 44     print("\tdb." + shortName + ".find(...).limit(n)");
 45     print("\tdb." + shortName + ".find(...).skip(n)");
 46     print("\tdb." + shortName + ".find(...).sort(...)");
 47     print("\tdb." + shortName + ".findOne([query])");
 48     print("\tdb." + shortName + ".findAndModify( { update : ... , remove : bool [, query: {}, sort: {}, 'new': false] } )");
 49     print("\tdb." + shortName + ".getDB() get DB object associated with collection");
 50     print("\tdb." + shortName + ".getIndexes()");
 51     print("\tdb." + shortName + ".group( { key : ..., initial: ..., reduce : ...[, cond: ...] } )");
 52     print("\tdb." + shortName + ".mapReduce( mapFunction , reduceFunction , <optional params> )");
 53     print("\tdb." + shortName + ".remove(query)");
 54     print("\tdb." + shortName + ".renameCollection( newName , <dropTarget> ) renames the collection.");
 55     print("\tdb." + shortName + ".runCommand( name , <options> ) runs a db command with the given name where the 1st param is the colleciton name");
 56     print("\tdb." + shortName + ".save(obj)");
 57     print("\tdb." + shortName + ".stats()");
 58     print("\tdb." + shortName + ".storageSize() - includes free space allocated to this collection");
 59     print("\tdb." + shortName + ".totalIndexSize() - size in bytes of all the indexes");
 60     print("\tdb." + shortName + ".totalSize() - storage allocated for all data and indexes");
 61     print("\tdb." + shortName + ".update(query, object[, upsert_bool, multi_bool])");
 62     print("\tdb." + shortName + ".validate() - SLOW");
 63     print("\tdb." + shortName + ".getShardVersion() - only for use with sharding");
 64     return __magicNoPrint;
 65 }
 66 
 67 DBCollection.prototype.getFullName = function(){
 68     return this._fullName;
 69 }
 70 DBCollection.prototype.getDB = function(){
 71     return this._db;
 72 }
 73 
 74 DBCollection.prototype._dbCommand = function( cmd , params ){
 75     if ( typeof( cmd ) == "object" )
 76         return this._db._dbCommand( cmd );
 77     
 78     var c = {};
 79     c[cmd] = this.getName();
 80     if ( params )
 81         Object.extend( c , params );
 82     return this._db._dbCommand( c );    
 83 }
 84 
 85 DBCollection.prototype.runCommand = DBCollection.prototype._dbCommand;
 86 
 87 DBCollection.prototype._massageObject = function( q ){
 88     if ( ! q )
 89         return {};
 90 
 91     var type = typeof q;
 92 
 93     if ( type == "function" )
 94         return { $where : q };
 95 
 96     if ( q.isObjectId )
 97         return { _id : q };
 98 
 99     if ( type == "object" )
100         return q;
101 
102     if ( type == "string" ){
103         if ( q.length == 24 )
104             return { _id : q };
105 
106         return { $where : q };
107     }
108 
109     throw "don't know how to massage : " + type;
110 
111 }
112 
113 
114 DBCollection.prototype._validateObject = function( o ){
115     if ( o._ensureSpecial && o._checkModify )
116         throw "can't save a DBQuery object";
117 }
118 
119 DBCollection._allowedFields = { $id : 1 , $ref : 1 };
120 
121 DBCollection.prototype._validateForStorage = function( o ){
122     this._validateObject( o );
123     for ( var k in o ){
124         if ( k.indexOf( "." ) >= 0 ) {
125             throw "can't have . in field names [" + k + "]" ;
126         }
127 
128         if ( k.indexOf( "$" ) == 0 && ! DBCollection._allowedFields[k] ) {
129             throw "field names cannot start with $ [" + k + "]";
130         }
131 
132         if ( o[k] !== null && typeof( o[k] ) === "object" ) {
133             this._validateForStorage( o[k] );
134         }
135     }
136 };
137 
138 
139 DBCollection.prototype.find = function( query , fields , limit , skip ){
140     return new DBQuery( this._mongo , this._db , this ,
141                         this._fullName , this._massageObject( query ) , fields , limit , skip );
142 }
143 
144 DBCollection.prototype.findOne = function( query , fields ){
145     var cursor = this._mongo.find( this._fullName , this._massageObject( query ) || {} , fields , -1 , 0 , 0 );
146     if ( ! cursor.hasNext() )
147         return null;
148     var ret = cursor.next();
149     if ( cursor.hasNext() ) throw "findOne has more than 1 result!";
150     if ( ret.$err )
151         throw "error " + tojson( ret );
152     return ret;
153 }
154 
155 DBCollection.prototype.insert = function( obj , _allow_dot ){
156     if ( ! obj )
157         throw "no object passed to insert!";
158     if ( ! _allow_dot ) {
159         this._validateForStorage( obj );
160     }
161     if ( typeof( obj._id ) == "undefined" ){
162         var tmp = obj; // don't want to modify input
163         obj = {_id: new ObjectId()};
164         for (var key in tmp){
165             obj[key] = tmp[key];
166         }
167     }
168     this._mongo.insert( this._fullName , obj );
169     this._lastID = obj._id;
170 }
171 
172 DBCollection.prototype.remove = function( t ){
173     this._mongo.remove( this._fullName , this._massageObject( t ) );
174 }
175 
176 DBCollection.prototype.update = function( query , obj , upsert , multi ){
177     assert( query , "need a query" );
178     assert( obj , "need an object" );
179     this._validateObject( obj );
180     this._mongo.update( this._fullName , query , obj , upsert ? true : false , multi ? true : false );
181 }
182 
183 DBCollection.prototype.save = function( obj ){
184     if ( obj == null || typeof( obj ) == "undefined" ) 
185         throw "can't save a null";
186 
187     if ( typeof( obj._id ) == "undefined" ){
188         obj._id = new ObjectId();
189         return this.insert( obj );
190     }
191     else {
192         return this.update( { _id : obj._id } , obj , true );
193     }
194 }
195 
196 DBCollection.prototype._genIndexName = function( keys ){
197     var name = "";
198     for ( var k in keys ){
199         var v = keys[k];
200         if ( typeof v == "function" )
201             continue;
202         
203         if ( name.length > 0 )
204             name += "_";
205         name += k + "_";
206 
207         if ( typeof v == "number" )
208             name += v;
209     }
210     return name;
211 }
212 
213 DBCollection.prototype._indexSpec = function( keys, options ) {
214     var ret = { ns : this._fullName , key : keys , name : this._genIndexName( keys ) };
215 
216     if ( ! options ){
217     }
218     else if ( typeof ( options ) == "string" )
219         ret.name = options;
220     else if ( typeof ( options ) == "boolean" )
221         ret.unique = true;
222     else if ( typeof ( options ) == "object" ){
223         if ( options.length ){
224             var nb = 0;
225             for ( var i=0; i<options.length; i++ ){
226                 if ( typeof ( options[i] ) == "string" )
227                     ret.name = options[i];
228                 else if ( typeof( options[i] ) == "boolean" ){
229                     if ( options[i] ){
230                         if ( nb == 0 )
231                             ret.unique = true;
232                         if ( nb == 1 )
233                             ret.dropDups = true;
234                     }
235                     nb++;
236                 }
237             }
238         }
239         else {
240             Object.extend( ret , options );
241         }
242     }
243     else {
244         throw "can't handle: " + typeof( options );
245     }
246     /*
247         return ret;
248 
249     var name;
250     var nTrue = 0;
251     
252     if ( ! isObject( options ) ) {
253         options = [ options ];
254     }
255     
256     if ( options.length ){
257         for( var i = 0; i < options.length; ++i ) {
258             var o = options[ i ];
259             if ( isString( o ) ) {
260                 ret.name = o;
261             } else if ( typeof( o ) == "boolean" ) {
262 	        if ( o ) {
263 		    ++nTrue;
264 	        }
265             }
266         }
267         if ( nTrue > 0 ) {
268 	    ret.unique = true;
269         }
270         if ( nTrue > 1 ) {
271 	    ret.dropDups = true;
272         }
273     }
274 */
275     return ret;
276 }
277 
278 DBCollection.prototype.createIndex = function( keys , options ){
279     var o = this._indexSpec( keys, options );
280     this._db.getCollection( "system.indexes" ).insert( o , true );
281 }
282 
283 DBCollection.prototype.ensureIndex = function( keys , options ){
284     var name = this._indexSpec( keys, options ).name;
285     this._indexCache = this._indexCache || {};
286     if ( this._indexCache[ name ] ){
287         return;
288     }
289 
290     this.createIndex( keys , options );
291     if ( this.getDB().getLastError() == "" ) {
292 	this._indexCache[name] = true;
293     }
294 }
295 
296 DBCollection.prototype.resetIndexCache = function(){
297     this._indexCache = {};
298 }
299 
300 DBCollection.prototype.reIndex = function() {
301     return this._db.runCommand({ reIndex: this.getName() });
302 }
303 
304 DBCollection.prototype.dropIndexes = function(){
305     this.resetIndexCache();
306 
307     var res = this._db.runCommand( { deleteIndexes: this.getName(), index: "*" } );
308     assert( res , "no result from dropIndex result" );
309     if ( res.ok )
310         return res;
311 
312     if ( res.errmsg.match( /not found/ ) )
313         return res;
314 
315     throw "error dropping indexes : " + tojson( res );
316 }
317 
318 
319 DBCollection.prototype.drop = function(){
320     this.resetIndexCache();
321     var ret = this._db.runCommand( { drop: this.getName() } );
322     if ( ! ret.ok ){
323         if ( ret.errmsg == "ns not found" )
324             return false;
325         throw "drop failed: " + tojson( ret );
326     }
327     return true;
328 }
329 
330 DBCollection.prototype.findAndModify = function(args){
331     var cmd = { findandmodify: this.getName() };
332     for (var key in args){
333         cmd[key] = args[key];
334     }
335 
336     var ret = this._db.runCommand( cmd );
337     if ( ! ret.ok ){
338         if (ret.errmsg == "No matching object found"){
339             return {};
340         }
341         throw "findAndModifyFailed failed: " + tojson( ret.errmsg );
342     }
343     return ret.value;
344 }
345 
346 DBCollection.prototype.renameCollection = function( newName , dropTarget ){
347     return this._db._adminCommand( { renameCollection : this._fullName , 
348                                      to : this._db._name + "." + newName , 
349                                      dropTarget : dropTarget } )
350 }
351 
352 DBCollection.prototype.validate = function() {
353     var res = this._db.runCommand( { validate: this.getName() } );
354 
355     res.valid = false;
356 
357     if ( res.result ){
358         var str = "-" + tojson( res.result );
359         res.valid = ! ( str.match( /exception/ ) || str.match( /corrupt/ ) );
360 
361         var p = /lastExtentSize:(\d+)/;
362         var r = p.exec( str );
363         if ( r ){
364             res.lastExtentSize = Number( r[1] );
365         }
366     }
367 
368     return res;
369 }
370 
371 DBCollection.prototype.getShardVersion = function(){
372     return this._db._adminCommand( { getShardVersion : this._fullName } );
373 }
374 
375 DBCollection.prototype.getIndexes = function(){
376     return this.getDB().getCollection( "system.indexes" ).find( { ns : this.getFullName() } ).toArray();
377 }
378 
379 DBCollection.prototype.getIndices = DBCollection.prototype.getIndexes;
380 DBCollection.prototype.getIndexSpecs = DBCollection.prototype.getIndexes;
381 
382 DBCollection.prototype.getIndexKeys = function(){
383     return this.getIndexes().map(
384         function(i){
385             return i.key;
386         }
387     );
388 }
389 
390 
391 DBCollection.prototype.count = function( x ){
392     return this.find( x ).count();
393 }
394 
395 /**
396  *  Drop free lists. Normally not used.
397  *  Note this only does the collection itself, not the namespaces of its indexes (see cleanAll).
398  */
399 DBCollection.prototype.clean = function() {
400     return this._dbCommand( { clean: this.getName() } );
401 }
402 
403 
404 
405 /**
406  * <p>Drop a specified index.</p>
407  *
408  * <p>
409  * Name is the name of the index in the system.indexes name field. (Run db.system.indexes.find() to
410  *  see example data.)
411  * </p>
412  *
413  * <p>Note :  alpha: space is not reclaimed </p>
414  * @param {String} name of index to delete.
415  * @return A result object.  result.ok will be true if successful.
416  */
417 DBCollection.prototype.dropIndex =  function(index) {
418     assert(index , "need to specify index to dropIndex" );
419 
420     if ( ! isString( index ) && isObject( index ) )
421     	index = this._genIndexName( index );
422 
423     var res = this._dbCommand( "deleteIndexes" ,{ index: index } );
424     this.resetIndexCache();
425     return res;
426 }
427 
428 DBCollection.prototype.copyTo = function( newName ){
429     return this.getDB().eval(
430         function( collName , newName ){
431             var from = db[collName];
432             var to = db[newName];
433             to.ensureIndex( { _id : 1 } );
434             var count = 0;
435 
436             var cursor = from.find();
437             while ( cursor.hasNext() ){
438                 var o = cursor.next();
439                 count++;
440                 to.save( o );
441             }
442 
443             return count;
444         } , this.getName() , newName
445     );
446 }
447 
448 DBCollection.prototype.getCollection = function( subName ){
449     return this._db.getCollection( this._shortName + "." + subName );
450 }
451 
452 DBCollection.prototype.stats = function( scale ){
453     return this._db.runCommand( { collstats : this._shortName , scale : scale } );
454 }
455 
456 DBCollection.prototype.dataSize = function(){
457     return this.stats().size;
458 }
459 
460 DBCollection.prototype.storageSize = function(){
461     return this.stats().storageSize;
462 }
463 
464 DBCollection.prototype.totalIndexSize = function( verbose ){
465     var stats = this.stats();
466     if (verbose){
467         for (var ns in stats.indexSizes){
468             print( ns + "\t" + stats.indexSizes[ns] );
469         }
470     }
471     return stats.totalIndexSize;
472 }
473 
474 
475 DBCollection.prototype.totalSize = function(){
476     var total = this.storageSize();
477     var mydb = this._db;
478     var shortName = this._shortName;
479     this.getIndexes().forEach(
480         function( spec ){
481             var coll = mydb.getCollection( shortName + ".$" + spec.name );
482             var mysize = coll.storageSize();
483             //print( coll + "\t" + mysize + "\t" + tojson( coll.validate() ) );
484             total += coll.dataSize();
485         }
486     );
487     return total;
488 }
489 
490 
491 DBCollection.prototype.convertToCapped = function( bytes ){
492     if ( ! bytes )
493         throw "have to specify # of bytes";
494     return this._dbCommand( { convertToCapped : this._shortName , size : bytes } )
495 }
496 
497 DBCollection.prototype.exists = function(){
498     return this._db.system.namespaces.findOne( { name : this._fullName } );
499 }
500 
501 DBCollection.prototype.isCapped = function(){
502     var e = this.exists();
503     return ( e && e.options && e.options.capped ) ? true : false;
504 }
505 
506 DBCollection.prototype.distinct = function( keyString , query ){
507     var res = this._dbCommand( { distinct : this._shortName , key : keyString , query : query || {} } );
508     if ( ! res.ok )
509         throw "distinct failed: " + tojson( res );
510     return res.values;
511 }
512 
513 DBCollection.prototype.group = function( params ){
514     params.ns = this._shortName;
515     return this._db.group( params );
516 }
517 
518 DBCollection.prototype.groupcmd = function( params ){
519     params.ns = this._shortName;
520     return this._db.groupcmd( params );
521 }
522 
523 MapReduceResult = function( db , o ){
524     Object.extend( this , o );
525     this._o = o;
526     this._keys = Object.keySet( o );
527     this._db = db;
528     this._coll = this._db.getCollection( this.result );
529 }
530 
531 MapReduceResult.prototype._simpleKeys = function(){
532     return this._o;
533 }
534 
535 MapReduceResult.prototype.find = function(){
536     return DBCollection.prototype.find.apply( this._coll , arguments );
537 }
538 
539 MapReduceResult.prototype.drop = function(){
540     return this._coll.drop();
541 }
542 
543 /**
544 * just for debugging really
545 */
546 MapReduceResult.prototype.convertToSingleObject = function(){
547     var z = {};
548     this._coll.find().forEach( function(a){ z[a._id] = a.value; } );
549     return z;
550 }
551 
552 /**
553 * @param optional object of optional fields;
554 */
555 DBCollection.prototype.mapReduce = function( map , reduce , optional ){
556     var c = { mapreduce : this._shortName , map : map , reduce : reduce };
557     if ( optional )
558         Object.extend( c , optional );
559     var raw = this._db.runCommand( c );
560     if ( ! raw.ok )
561         throw "map reduce failed: " + tojson( raw );
562     return new MapReduceResult( this._db , raw );
563 
564 }
565 
566 DBCollection.prototype.toString = function(){
567     return this.getFullName();
568 }
569 
570 DBCollection.prototype.toString = function(){
571     return this.getFullName();
572 }
573 
574 
575 DBCollection.prototype.tojson = DBCollection.prototype.toString;
576 
577 DBCollection.prototype.shellPrint = DBCollection.prototype.toString;
578 
579 
580 
581