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