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