1 // db.js
  2 
  3 if ( typeof DB == "undefined" ){                     
  4     DB = function( mongo , name ){
  5         this._mongo = mongo;
  6         this._name = name;
  7     }
  8 }
  9 
 10 DB.prototype.getMongo = function(){
 11     assert( this._mongo , "why no mongo!" );
 12     return this._mongo;
 13 }
 14 
 15 DB.prototype.getSisterDB = function( name ){
 16     return this.getMongo().getDB( name );
 17 }
 18 
 19 DB.prototype.getName = function(){
 20     return this._name;
 21 }
 22 
 23 DB.prototype.stats = function(){
 24     return this.runCommand( { dbstats : 1 } );
 25 }
 26 
 27 DB.prototype.getCollection = function( name ){
 28     return new DBCollection( this._mongo , this , name , this._name + "." + name );
 29 }
 30 
 31 DB.prototype.commandHelp = function( name ){
 32     var c = {};
 33     c[name] = 1;
 34     c.help = true;
 35     return this.runCommand( c ).help;
 36 }
 37 
 38 DB.prototype.runCommand = function( obj ){
 39     if ( typeof( obj ) == "string" ){
 40         var n = {};
 41         n[obj] = 1;
 42         obj = n;
 43     }
 44     return this.getCollection( "$cmd" ).findOne( obj );
 45 }
 46 
 47 DB.prototype._dbCommand = DB.prototype.runCommand;
 48 
 49 DB.prototype._adminCommand = function( obj ){
 50     if ( this._name == "admin" )
 51         return this.runCommand( obj );
 52     return this.getSisterDB( "admin" ).runCommand( obj );
 53 }
 54 
 55 DB.prototype.addUser = function( username , pass, readOnly ){
 56     readOnly = readOnly || false;
 57     var c = this.getCollection( "system.users" );
 58     
 59     var u = c.findOne( { user : username } ) || { user : username };
 60     u.readOnly = readOnly;
 61     u.pwd = hex_md5( username + ":mongo:" + pass );
 62     print( tojson( u ) );
 63 
 64     c.save( u );
 65 }
 66 
 67 DB.prototype.removeUser = function( username ){
 68     this.getCollection( "system.users" ).remove( { user : username } );
 69 }
 70 
 71 DB.prototype.__pwHash = function( nonce, username, pass ) {
 72     return hex_md5( nonce + username + hex_md5( username + ":mongo:" + pass ) );
 73 }
 74 
 75 DB.prototype.auth = function( username , pass ){
 76     var n = this.runCommand( { getnonce : 1 } );
 77 
 78     var a = this.runCommand( 
 79         { 
 80             authenticate : 1 , 
 81             user : username , 
 82             nonce : n.nonce , 
 83             key : this.__pwHash( n.nonce, username, pass )
 84         }
 85     );
 86 
 87     return a.ok;
 88 }
 89 
 90 /**
 91   Create a new collection in the database.  Normally, collection creation is automatic.  You would
 92    use this function if you wish to specify special options on creation.
 93 
 94    If the collection already exists, no action occurs.
 95    
 96    <p>Options:</p>
 97    <ul>
 98    	<li>
 99      size: desired initial extent size for the collection.  Must be <= 1000000000.
100            for fixed size (capped) collections, this size is the total/max size of the 
101            collection.
102     </li>
103     <li>
104      capped: if true, this is a capped collection (where old data rolls out).
105     </li>
106     <li> max: maximum number of objects if capped (optional).</li>
107     </ul>
108 
109    <p>Example: </p>
110    
111    <code>db.createCollection("movies", { size: 10 * 1024 * 1024, capped:true } );</code>
112  
113  * @param {String} name Name of new collection to create 
114  * @param {Object} options Object with options for call.  Options are listed above.
115  * @return SOMETHING_FIXME
116 */
117 DB.prototype.createCollection = function(name, opt) {
118     var options = opt || {};
119     var cmd = { create: name, capped: options.capped, size: options.size, max: options.max };
120     var res = this._dbCommand(cmd);
121     return res;
122 }
123 
124 /**
125  *  Returns the current profiling level of this database
126  *  @return SOMETHING_FIXME or null on error
127  */
128  DB.prototype.getProfilingLevel  = function() { 
129     var res = this._dbCommand( { profile: -1 } );
130     return res ? res.was : null;
131 }
132 
133 
134 /**
135   Erase the entire database.  (!)
136  
137  * @return Object returned has member ok set to true if operation succeeds, false otherwise.
138 */
139 DB.prototype.dropDatabase = function() { 	
140     if ( arguments.length )
141         throw "dropDatabase doesn't take arguments";
142     return this._dbCommand( { dropDatabase: 1 } );
143 }
144 
145 
146 DB.prototype.shutdownServer = function() { 
147     if( "admin" != this._name ){
148 	return "shutdown command only works with the admin database; try 'use admin'";
149     }
150 
151     try {
152         var res = this._dbCommand("shutdown");
153 	if( res ) 
154 	    throw "shutdownServer failed: " + res.errmsg;
155 	throw "shutdownServer failed";
156     }
157     catch ( e ){
158         assert( tojson( e ).indexOf( "error doing query: failed" ) >= 0 , "unexpected error: " + tojson( e ) );
159         print( "server should be down..." );
160     }
161 }
162 
163 /**
164   Clone database on another server to here.
165   <p>
166   Generally, you should dropDatabase() first as otherwise the cloned information will MERGE 
167   into whatever data is already present in this database.  (That is however a valid way to use 
168   clone if you are trying to do something intentionally, such as union three non-overlapping
169   databases into one.)
170   <p>
171   This is a low level administrative function will is not typically used.
172 
173  * @param {String} from Where to clone from (dbhostname[:port]).  May not be this database 
174                    (self) as you cannot clone to yourself.
175  * @return Object returned has member ok set to true if operation succeeds, false otherwise.
176  * See also: db.copyDatabase()
177 */
178 DB.prototype.cloneDatabase = function(from) { 
179     assert( isString(from) && from.length );
180     //this.resetIndexCache();
181     return this._dbCommand( { clone: from } );
182 }
183 
184 
185 /**
186  Clone collection on another server to here.
187  <p>
188  Generally, you should drop() first as otherwise the cloned information will MERGE 
189  into whatever data is already present in this collection.  (That is however a valid way to use 
190  clone if you are trying to do something intentionally, such as union three non-overlapping
191  collections into one.)
192  <p>
193  This is a low level administrative function is not typically used.
194  
195  * @param {String} from mongod instance from which to clnoe (dbhostname:port).  May
196  not be this mongod instance, as clone from self is not allowed.
197  * @param {String} collection name of collection to clone.
198  * @param {Object} query query specifying which elements of collection are to be cloned.
199  * @return Object returned has member ok set to true if operation succeeds, false otherwise.
200  * See also: db.cloneDatabase()
201  */
202 DB.prototype.cloneCollection = function(from, collection, query) { 
203     assert( isString(from) && from.length );
204     assert( isString(collection) && collection.length );
205     collection = this._name + "." + collection;
206     query = query || {};
207     //this.resetIndexCache();
208     return this._dbCommand( { cloneCollection:collection, from:from, query:query } );
209 }
210 
211 
212 /**
213   Copy database from one server or name to another server or name.
214 
215   Generally, you should dropDatabase() first as otherwise the copied information will MERGE 
216   into whatever data is already present in this database (and you will get duplicate objects 
217   in collections potentially.)
218 
219   For security reasons this function only works when executed on the "admin" db.  However, 
220   if you have access to said db, you can copy any database from one place to another.
221 
222   This method provides a way to "rename" a database by copying it to a new db name and 
223   location.  Additionally, it effectively provides a repair facility.
224 
225   * @param {String} fromdb database name from which to copy.
226   * @param {String} todb database name to copy to.
227   * @param {String} fromhost hostname of the database (and optionally, ":port") from which to 
228                     copy the data.  default if unspecified is to copy from self.
229   * @return Object returned has member ok set to true if operation succeeds, false otherwise.
230   * See also: db.clone()
231 */
232 DB.prototype.copyDatabase = function(fromdb, todb, fromhost, username, password) { 
233     assert( isString(fromdb) && fromdb.length );
234     assert( isString(todb) && todb.length );
235     fromhost = fromhost || "";
236     if ( username && password ) {
237         var n = this._adminCommand( { copydbgetnonce : 1, fromhost:fromhost } );
238         return this._adminCommand( { copydb:1, fromhost:fromhost, fromdb:fromdb, todb:todb, username:username, nonce:n.nonce, key:this.__pwHash( n.nonce, username, password ) } );
239     } else {
240         return this._adminCommand( { copydb:1, fromhost:fromhost, fromdb:fromdb, todb:todb } );
241     }
242 }
243 
244 /**
245   Repair database.
246  
247  * @return Object returned has member ok set to true if operation succeeds, false otherwise.
248 */
249 DB.prototype.repairDatabase = function() {
250     return this._dbCommand( { repairDatabase: 1 } );
251 }
252 
253 
254 DB.prototype.help = function() {
255     print("DB methods:");
256     print("\tdb.addUser(username, password[, readOnly=false])");
257     print("\tdb.auth(username, password)");
258     print("\tdb.cloneDatabase(fromhost)");
259     print("\tdb.commandHelp(name) returns the help for the command");
260     print("\tdb.copyDatabase(fromdb, todb, fromhost)");
261     print("\tdb.createCollection(name, { size : ..., capped : ..., max : ... } )");
262     print("\tdb.currentOp() displays the current operation in the db");
263     print("\tdb.dropDatabase()");
264     print("\tdb.eval(func, args) run code server-side");
265     print("\tdb.getCollection(cname) same as db['cname'] or db.cname");
266     print("\tdb.getCollectionNames()");
267     print("\tdb.getLastError() - just returns the err msg string");
268     print("\tdb.getLastErrorObj() - return full status object");
269     print("\tdb.getMongo() get the server connection object");
270     print("\tdb.getMongo().setSlaveOk() allow this connection to read from the nonmaster member of a replica pair");
271     print("\tdb.getName()");
272     print("\tdb.getPrevError()");
273     print("\tdb.getProfilingLevel()");
274     print("\tdb.getReplicationInfo()");
275     print("\tdb.getSisterDB(name) get the db at the same server as this onew");
276     print("\tdb.killOp(opid) kills the current operation in the db");
277     print("\tdb.listCommands() lists all the db commands");
278     print("\tdb.printCollectionStats()");
279     print("\tdb.printReplicationInfo()");
280     print("\tdb.printSlaveReplicationInfo()");
281     print("\tdb.printShardingStatus()");
282     print("\tdb.removeUser(username)");
283     print("\tdb.repairDatabase()");
284     print("\tdb.resetError()");
285     print("\tdb.runCommand(cmdObj) run a database command.  if cmdObj is a string, turns it into { cmdObj : 1 }");
286     print("\tdb.serverStatus()");
287     print("\tdb.setProfilingLevel(level,<slowms>) 0=off 1=slow 2=all");
288     print("\tdb.shutdownServer()");
289     print("\tdb.stats()");
290     print("\tdb.version() current version of the server");
291     print("\tdb.getMongo().setSlaveOk() allow queries on a replication slave server");
292 
293     return __magicNoPrint;
294 }
295 
296 DB.prototype.printCollectionStats = function(){
297     var mydb = this;
298     this.getCollectionNames().forEach(
299         function(z){
300             print( z );
301             printjson( mydb.getCollection(z).stats() );
302             print( "---" );
303         }
304     );
305 }
306 
307 /**
308  * <p> Set profiling level for your db.  Profiling gathers stats on query performance. </p>
309  * 
310  * <p>Default is off, and resets to off on a database restart -- so if you want it on,
311  *    turn it on periodically. </p>
312  *  
313  *  <p>Levels :</p>
314  *   <ul>
315  *    <li>0=off</li>
316  *    <li>1=log very slow operations; optional argument slowms specifies slowness threshold</li>
317  *    <li>2=log all</li>
318  *  @param {String} level Desired level of profiling
319  *  @param {String} slowms For slow logging, query duration that counts as slow (default 100ms)
320  *  @return SOMETHING_FIXME or null on error
321  */
322 DB.prototype.setProfilingLevel = function(level,slowms) {
323     
324     if (level < 0 || level > 2) { 
325         throw { dbSetProfilingException : "input level " + level + " is out of range [0..2]" };        
326     }
327 
328     var cmd = { profile: level };
329     if ( slowms )
330         cmd["slowms"] = slowms;
331     return this._dbCommand( cmd );
332 }
333 
334 
335 /**
336  *  <p> Evaluate a js expression at the database server.</p>
337  * 
338  * <p>Useful if you need to touch a lot of data lightly; in such a scenario
339  *  the network transfer of the data could be a bottleneck.  A good example
340  *  is "select count(*)" -- can be done server side via this mechanism.
341  * </p>
342  *
343  * <p>
344  * If the eval fails, an exception is thrown of the form:
345  * </p>
346  * <code>{ dbEvalException: { retval: functionReturnValue, ok: num [, errno: num] [, errmsg: str] } }</code>
347  * 
348  * <p>Example: </p>
349  * <code>print( "mycount: " + db.eval( function(){db.mycoll.find({},{_id:ObjId()}).length();} );</code>
350  *
351  * @param {Function} jsfunction Javascript function to run on server.  Note this it not a closure, but rather just "code".
352  * @return result of your function, or null if error
353  * 
354  */
355 DB.prototype.eval = function(jsfunction) {
356     var cmd = { $eval : jsfunction };
357     if ( arguments.length > 1 ) {
358 	cmd.args = argumentsToArray( arguments ).slice(1);
359     }
360     
361     var res = this._dbCommand( cmd );
362     
363     if (!res.ok)
364     	throw tojson( res );
365     
366     return res.retval;
367 }
368 
369 DB.prototype.dbEval = DB.prototype.eval;
370 
371 
372 /**
373  * 
374  *  <p>
375  *   Similar to SQL group by.  For example: </p>
376  *
377  *  <code>select a,b,sum(c) csum from coll where active=1 group by a,b</code>
378  *
379  *  <p>
380  *    corresponds to the following in 10gen:
381  *  </p>
382  * 
383  *  <code>
384      db.group(
385        {
386          ns: "coll",
387          key: { a:true, b:true },
388 	 // keyf: ...,
389 	 cond: { active:1 },
390 	 reduce: function(obj,prev) { prev.csum += obj.c; } ,
391 	 initial: { csum: 0 }
392 	 });
393 	 </code>
394  *
395  * 
396  * <p>
397  *  An array of grouped items is returned.  The array must fit in RAM, thus this function is not
398  * suitable when the return set is extremely large.
399  * </p>
400  * <p>
401  * To order the grouped data, simply sort it client side upon return.
402  * <p>
403    Defaults
404      cond may be null if you want to run against all rows in the collection
405      keyf is a function which takes an object and returns the desired key.  set either key or keyf (not both).
406  * </p>
407 */
408 DB.prototype.groupeval = function(parmsObj) {
409 	
410     var groupFunction = function() {
411 	var parms = args[0];
412     	var c = db[parms.ns].find(parms.cond||{});
413     	var map = new Map();
414         var pks = parms.key ? Object.keySet( parms.key ) : null;
415         var pkl = pks ? pks.length : 0;
416         var key = {};
417         
418     	while( c.hasNext() ) {
419 	    var obj = c.next();
420 	    if ( pks ) {
421 	    	for( var i=0; i<pkl; i++ ){
422                     var k = pks[i];
423 		    key[k] = obj[k];
424                 }
425 	    }
426 	    else {
427 	    	key = parms.$keyf(obj);
428 	    }
429 
430 	    var aggObj = map.get(key);
431 	    if( aggObj == null ) {
432 		var newObj = Object.extend({}, key); // clone
433 	    	aggObj = Object.extend(newObj, parms.initial)
434                 map.put( key , aggObj );
435 	    }
436 	    parms.$reduce(obj, aggObj);
437 	}
438         
439 	return map.values();
440     }
441     
442     return this.eval(groupFunction, this._groupFixParms( parmsObj ));
443 }
444 
445 DB.prototype.groupcmd = function( parmsObj ){
446     var ret = this.runCommand( { "group" : this._groupFixParms( parmsObj ) } );
447     if ( ! ret.ok ){
448         throw "group command failed: " + tojson( ret );
449     }
450     return ret.retval;
451 }
452 
453 DB.prototype.group = DB.prototype.groupcmd;
454 
455 DB.prototype._groupFixParms = function( parmsObj ){
456     var parms = Object.extend({}, parmsObj);
457     
458     if( parms.reduce ) {
459 	parms.$reduce = parms.reduce; // must have $ to pass to db
460 	delete parms.reduce;
461     }
462     
463     if( parms.keyf ) {
464 	parms.$keyf = parms.keyf;
465 	delete parms.keyf;
466     }
467     
468     return parms;
469 }
470 
471 DB.prototype.resetError = function(){
472     return this.runCommand( { reseterror : 1 } );
473 }
474 
475 DB.prototype.forceError = function(){
476     return this.runCommand( { forceerror : 1 } );
477 }
478 
479 DB.prototype.getLastError = function( w , wtimeout ){
480     var cmd = { getlasterror : 1 };
481     if ( w ){
482         cmd.w = w;
483         if ( wtimeout )
484             cmd.wtimeout = wtimeout;
485     }
486     var res = this.runCommand( cmd );
487     if ( ! res.ok )
488         throw "getlasterror failed: " + tojson( res );
489     return res.err;
490 }
491 DB.prototype.getLastErrorObj = function(){
492     var res = this.runCommand( { getlasterror : 1 } );
493     if ( ! res.ok )
494         throw "getlasterror failed: " + tojson( res );
495     return res;
496 }
497 DB.prototype.getLastErrorCmd = DB.prototype.getLastErrorObj;
498 
499 
500 /* Return the last error which has occurred, even if not the very last error.
501 
502    Returns: 
503     { err : <error message>, nPrev : <how_many_ops_back_occurred>, ok : 1 }
504 
505    result.err will be null if no error has occurred.
506  */
507 DB.prototype.getPrevError = function(){
508     return this.runCommand( { getpreverror : 1 } );
509 }
510 
511 DB.prototype.getCollectionNames = function(){
512     var all = [];
513 
514     var nsLength = this._name.length + 1;
515     
516     this.getCollection( "system.namespaces" ).find().sort({name:1}).forEach(
517         function(z){
518             var name = z.name;
519             
520             if ( name.indexOf( "$" ) >= 0 && name != "local.oplog.$main" )
521                 return;
522             
523             all.push( name.substring( nsLength ) );
524         }
525     );
526     return all;
527 }
528 
529 DB.prototype.tojson = function(){
530     return this._name;
531 }
532 
533 DB.prototype.toString = function(){
534     return this._name;
535 }
536 
537 DB.prototype.currentOp = function(){
538     return db.$cmd.sys.inprog.findOne();
539 }
540 DB.prototype.currentOP = DB.prototype.currentOp;
541 
542 DB.prototype.killOp = function(op) {
543     if( !op ) 
544         throw "no opNum to kill specified";
545     return db.$cmd.sys.killop.findOne({'op':op});
546 }
547 DB.prototype.killOP = DB.prototype.killOp;
548 
549 DB.tsToSeconds = function(x){
550     if ( x.t && x.i )
551         return x.t / 1000;
552     return x / 4294967296; // low 32 bits are ordinal #s within a second
553 }
554 
555 /** 
556   Get a replication log information summary.
557   <p>
558   This command is for the database/cloud administer and not applicable to most databases.
559   It is only used with the local database.  One might invoke from the JS shell:
560   <pre>
561        use local
562        db.getReplicationInfo();
563   </pre>
564   It is assumed that this database is a replication master -- the information returned is 
565   about the operation log stored at local.oplog.$main on the replication master.  (It also 
566   works on a machine in a replica pair: for replica pairs, both machines are "masters" from 
567   an internal database perspective.
568   <p>
569   * @return Object timeSpan: time span of the oplog from start to end  if slave is more out 
570   *                          of date than that, it can't recover without a complete resync
571 */
572 DB.prototype.getReplicationInfo = function() { 
573     var db = this.getSisterDB("local");
574 
575     var result = { };
576     var ol = db.system.namespaces.findOne({name:"local.oplog.$main"});
577     if( ol && ol.options ) {
578 	result.logSizeMB = ol.options.size / 1000 / 1000;
579     } else {
580 	result.errmsg  = "local.oplog.$main, or its options, not found in system.namespaces collection (not --master?)";
581 	return result;
582     }
583 
584     var firstc = db.oplog.$main.find().sort({$natural:1}).limit(1);
585     var lastc = db.oplog.$main.find().sort({$natural:-1}).limit(1);
586     if( !firstc.hasNext() || !lastc.hasNext() ) { 
587 	result.errmsg = "objects not found in local.oplog.$main -- is this a new and empty db instance?";
588 	result.oplogMainRowCount = db.oplog.$main.count();
589 	return result;
590     }
591 
592     var first = firstc.next();
593     var last = lastc.next();
594     {
595 	var tfirst = first.ts;
596 	var tlast = last.ts;
597         
598 	if( tfirst && tlast ) { 
599 	    tfirst = DB.tsToSeconds( tfirst ); 
600 	    tlast = DB.tsToSeconds( tlast );
601 	    result.timeDiff = tlast - tfirst;
602 	    result.timeDiffHours = Math.round(result.timeDiff / 36)/100;
603 	    result.tFirst = (new Date(tfirst*1000)).toString();
604 	    result.tLast  = (new Date(tlast*1000)).toString();
605 	    result.now = Date();
606 	}
607 	else { 
608 	    result.errmsg = "ts element not found in oplog objects";
609 	}
610     }
611 
612     return result;
613 }
614 DB.prototype.printReplicationInfo = function() {
615     var result = this.getReplicationInfo();
616     if( result.errmsg ) { 
617 	print(tojson(result));
618 	return;
619     }
620     print("configured oplog size:   " + result.logSizeMB + "MB");
621     print("log length start to end: " + result.timeDiff + "secs (" + result.timeDiffHours + "hrs)");
622     print("oplog first event time:  " + result.tFirst);
623     print("oplog last event time:   " + result.tLast);
624     print("now:                     " + result.now);
625 }
626 
627 DB.prototype.printSlaveReplicationInfo = function() {
628     function g(x) {
629         print("source:   " + x.host);
630         var st = new Date( DB.tsToSeconds( x.syncedTo ) * 1000 );
631         var now = new Date();
632         print("syncedTo: " + st.toString() );
633         var ago = (now-st)/1000;
634         var hrs = Math.round(ago/36)/100;
635         print("          = " + Math.round(ago) + "secs ago (" + hrs + "hrs)"); 
636     }
637     var L = this.getSisterDB("local");
638     if( L.sources.count() == 0 ) { 
639         print("local.sources is empty; is this db a --slave?");
640         return;
641     }
642     L.sources.find().forEach(g);
643 }
644 
645 DB.prototype.serverBuildInfo = function(){
646     return this._adminCommand( "buildinfo" );
647 }
648 
649 DB.prototype.serverStatus = function(){
650     return this._adminCommand( "serverStatus" );
651 }
652 
653 DB.prototype.version = function(){
654     return this.serverBuildInfo().version;
655 }
656 
657 DB.prototype.listCommands = function(){
658     var x = this.runCommand( "listCommands" );
659     for ( var name in x.commands ){
660         var c = x.commands[name];
661 
662         var s = name + " lock: ";
663         
664         switch ( c.lockType ){
665         case -1: s += "read"; break;
666         case 0: s += "node"; break;
667         case 1: s += "write"; break;
668         default: s += c.lockType;
669         }
670         
671         s += " adminOnly: " + c.adminOnly;
672         s += " slaveOk: " + c.slaveOk;
673         s += " " + c.help;
674         
675         print( s )
676     }
677 }
678 
679 DB.prototype.printShardingStatus = function(){
680     printShardingStatus( this.getSisterDB( "config" ) );
681 }
682