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