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