Skip to content

Instantly share code, notes, and snippets.

@mikejakobsen
Last active May 23, 2016 16:59
Show Gist options
  • Save mikejakobsen/ee388986e00c4622dd66591662e108c2 to your computer and use it in GitHub Desktop.
Save mikejakobsen/ee388986e00c4622dd66591662e108c2 to your computer and use it in GitHub Desktop.
EDITOR="VIM"
mongo_hacker_config = {
verbose_shell: true, // additional verbosity
index_paranoia: false, // querytime explain
enhance_api: true, // additonal api extensions
indent: 2, // number of spaces for indent
sort_keys: false, // sort the keys in documents when displayed
uuid_type: 'default', // 'java', 'c#', 'python' or 'default'
banner_message: 'Mongo-Hacker ', // banner message
version: '0.0.13', // current mongo-hacker version
show_banner: true, // show mongo-hacker version banner on startup
windows_warning: true, // show warning banner for windows
force_color: false, // force color highlighting for Windows users
count_deltas: false, // "count documents" shows deltas with previous counts
column_separator: '→', // separator used when printing padded/aligned columns
value_separator: '/', // separator used when merging padded/aligned values
dbref: {
extended_info: true, // enable more informations on DBRef
plain: false, // print DBRef as plain JSON object
db_if_differs: false // include $db only if is different than current one
},
// Shell Color Settings
// Colors available: red, green, yellow, blue, magenta, cyan
colors: {
'key': { color: 'gray' },
'number': { color: 'red' },
'boolean': { color: 'blue', bright: true },
'null': { color: 'red', bright: true },
'undefined': { color: 'magenta', bright: true },
'objectid': { color: 'yellow', underline: true },
'string': { color: 'green' },
'binData': { color: 'green', bright: true },
'function': { color: 'magenta' },
'date': { color: 'blue' },
'uuid': { color: 'cyan' },
'databaseNames': { color: 'green', bright: true },
'collectionNames': { color: 'blue', bright: true }
}
}
if (mongo_hacker_config['show_banner']) {
print(mongo_hacker_config['banner_message'] + mongo_hacker_config['version']);
}
if (_isWindows() && mongo_hacker_config['windows_warning']) {
print("\nMongoDB Shell Enhancements for Hackers does not support color highlighting in ");
print("the default Windows Command Prompt. If you are using an alternative console ");
print("such as ConEmu (https://github.com/Maximus5/ConEmu) you may wish to try enabling");
print("highlighting in your mongo_hacker config by setting:");
print("\n\tforce_color: true\n");
print("You can hide this startup warning by setting:");
print("\n\twindows_warning: false\n");
}
if (typeof db !== 'undefined') {
var current_version = parseFloat(db.serverBuildInfo().version).toFixed(2)
if (current_version < 2.4) {
print("Sorry! MongoDB Shell Enhancements for Hackers is only compatible with Mongo 2.4+\n");
}
}
//----------------------------------------------------------------------------
// Aggregation API Extensions
//----------------------------------------------------------------------------
// Inject aggregation extension while supporting base API
DBCollection.prototype.aggregate = function( ops, extraOpts ){
if (hasDollar(ops) || (ops instanceof Array && hasDollar(ops[0]))){
var arr = ops;
if (!ops.length) {
arr = [];
for (var i=0; i<arguments.length; i++) {
arr.push(arguments[i]);
}
}
if (extraOpts === undefined) {
extraOpts = {};
}
var cmd = {pipeline: arr};
Object.extend(cmd, extraOpts);
var res = this.runCommand("aggregate", cmd);
if (!res.ok) {
printStackTrace();
throw "aggregate failed: " + tojson(res);
}
return res;
} else {
return new Aggregation( this ).match( ops || {} );
}
};
// Helper method for determining if parameter has dollar signs
function hasDollar(fields){
for (k in fields){
if(k.indexOf('$') !== -1){
return true;
};
};
return false;
}
//----------------------------------------------------------------------------
// Aggregation Object
//----------------------------------------------------------------------------
Aggregation = function( collection, fields ){
this._collection = collection;
this._pipeline = [];
this._options = {};
this._shellBatchSize = 20;
};
Aggregation.prototype.has_next = function() {
return (this._index < this._results.length);
};
Aggregation.prototype.next = function() {
var next = this._results[this._index];
this._index += 1;
return next
};
Aggregation.prototype.execute = function() {
// build the command
var aggregation = { pipeline: this._pipeline };
if ( this._readPreference ) {
aggregation["$readPreference"] = this.readPreference;
}
Object.extend(aggregation, this._options);
// run the command
var res = this._collection.runCommand(
"aggregate", aggregation
);
// check result
if ( ! res.ok ) {
printStackTrace();
throw "aggregation failed: " + tojson(res);
}
// setup results as pseudo cursor
this._index = 0;
if (this._options["explain"] === true) {
this._results = res.stages
} else {
this._results = res.result;
}
return this._results;
};
Aggregation.prototype.shellPrint = function() {
if (this._results == undefined) {
this.execute();
}
try {
var i = 0;
while (this.has_next() && i < this._shellBatchSize) {
var result = this.next();
printjson( result );
i++;
}
if ( this.has_next() ) {
print ( "Type \"it\" for more" );
___it___ = this;
}
else {
___it___ = null;
}
}
catch ( e ) {
print( e );
}
};
Aggregation.prototype.project = function( fields ) {
if ( ! fields ) {
throw "project needs fields";
}
this._pipeline.push({ "$project": fields });
return this;
};
Aggregation.prototype.find = function( criteria ) {
if ( ! criteria ) {
throw "match needs a query object";
}
this._pipeline.push({ "$match": criteria });
return this;
};
Aggregation.prototype.match = function( criteria ) {
if ( ! criteria ) {
throw "match needs a query object";
}
this._pipeline.push({ "$match": criteria });
return this;
};
Aggregation.prototype.limit = function( limit ) {
if ( ! limit ) {
throw "limit needs an integer indicating the limit";
}
this._pipeline.push({ "$limit": limit });
return this;
};
Aggregation.prototype.skip = function( skip ) {
if ( ! skip ) {
throw "skip needs an integer indicating the number to skip";
}
this._pipeline.push({ "$skip": skip });
return this;
};
Aggregation.prototype.unwind = function( field ) {
if ( ! field ) {
throw "unwind needs the key of an array field to unwind";
}
this._pipeline.push({ "$unwind": "$" + field });
return this;
};
Aggregation.prototype.group = function( group_expression ) {
if ( ! group_expression ) {
throw "group needs an group expression";
}
this._pipeline.push({ "$group": group_expression });
return this;
};
Aggregation.prototype.sort = function( sort ) {
if ( ! sort ) {
throw "sort needs a sort document";
}
this._pipeline.push({ "$sort": sort });
return this;
};
Aggregation.prototype.geoNear = function( options ) {
if ( ! options ) {
throw "geo near requires options"
}
this._pipeline.push({ "$geoNear": options });
return this;
};
Aggregation.prototype.readPreference = function( mode ) {
this._readPreference = mode;
return this;
};
Aggregation.prototype.explain = function( ) {
this._options['explain'] = true;
return this;
};
//----------------------------------------------------------------------------
// API Additions
//----------------------------------------------------------------------------
DBQuery.prototype.fields = function( fields ) {
this._fields = fields;
return this;
};
DBQuery.prototype.select = function( fields ){
this._fields = fields;
return this;
};
DBQuery.prototype.one = function(){
return this.limit(1)[0];
};
DBQuery.prototype.first = function(field){
var field = field || "$natural";
var sortBy = {};
sortBy[field] = 1;
return this.sort(sortBy).one();
}
DBQuery.prototype.reverse = function( field ){
var field = field || "$natural";
var sortBy = {};
sortBy[field] = -1;
return this.sort(sortBy);
}
DBQuery.prototype.last = function( field ){
var field = field || "$natural";
return this.reverse(field).one();
}
DB.prototype.rename = function(newName) {
if(newName == this.getName() || newName.length === 0)
return;
this.copyDatabase(this.getName(), newName, "localhost");
this.dropDatabase();
db = this.getSiblingDB(newName);
};
DB.prototype.indexStats = function(collectionFilter, details){
details = details || false;
collectionNames = db.getCollectionNames().filter(function (collectionName) {
// exclude "system" collections from "count" operation
if (!collectionFilter) {
return !collectionName.startsWith('system.');
}
if (collectionName == collectionFilter) {
return !collectionName.startsWith('system.');
}
});
documentIndexes = collectionNames.map(function (collectionName) {
var count = db.getCollection(collectionName).count();
return (count.commify() + " document(s)");
});
columnSeparator = mongo_hacker_config['column_separator'];
assert(collectionNames.length == documentIndexes.length);
maxKeyLength = maxLength(collectionNames);
maxValueLength = maxLength(documentIndexes);
for (i = 0; i < collectionNames.length; i++) {
print(
colorize(collectionNames[i].pad(maxKeyLength, true), mongo_hacker_config['colors']['collectionNames'])
+ " " + columnSeparator + " "
+ documentIndexes[i].pad(maxValueLength)
);
var stats = db.getCollection(collectionNames[i]).stats();
var totalIndexSize = (Math.round((stats.totalIndexSize / 1024 / 1024) * 10) / 10) + " MB";
var indexNames = [];
var indexSizes = [];
for (indexName in stats.indexSizes) {
indexSizes.push((Math.round((stats.indexSizes[indexName] / 1024 / 1024) * 10) / 10) + " MB");
indexNames.push(" " + indexName);
}
maxIndexKeyLength = maxLength(indexNames);
maxIndexValueLength = maxLength(indexSizes);
print(
colorize("totalIndexSize".pad(maxKeyLength, true), mongo_hacker_config['colors']['string'])
+ " " + columnSeparator + " "
+ colorize(totalIndexSize.pad(maxValueLength), mongo_hacker_config['colors']['number'])
);
if (details) {
for (var j = 0; j < indexSizes.length; j++) {
print(
colorize("" + indexNames[j].pad(maxIndexKeyLength, true), mongo_hacker_config['colors']['string'])
+ " " + columnSeparator + " "
+ colorize(indexSizes[j].pad(maxIndexValueLength), mongo_hacker_config['colors']['binData'])
);
};
}
}
return "";
}
Mongo.prototype.getDatabaseNames = function() {
// this API addition gives us the following convenience function:
//
// db.getMongo().getDatabaseNames()
//
// which is similar in use to:
//
// db.getCollectionNames()
//
// mongo-hacker FTW :-)
return this.getDBs().databases.reduce(function(names, db) {
return names.concat(db.name);
}, []);
}
//----------------------------------------------------------------------------
// API Modifications (additions and changes)
//----------------------------------------------------------------------------
// Add upsert method which has upsert set as true and multi as false
DBQuery.prototype.upsert = function( upsert ){
assert( upsert , "need an upsert object" );
this._validate(upsert);
this._db._initExtraInfo();
this._mongo.update( this._ns , this._query , upsert , true , false );
this._db._getExtraInfo("Upserted");
};
// Updates are always multi and never an upsert
DBQuery.prototype.update = function( update ){
assert( update , "need an update object" );
this._checkMulti();
this._validate(update);
this._db._initExtraInfo();
this._mongo.update( this._ns , this._query , update , false , true );
this._db._getExtraInfo("Updated");
};
// Replace one document
DBQuery.prototype.replace = function( replacement ){
assert( replacement , "need an update object" );
this._validate(replacement);
this._db._initExtraInfo();
this._mongo.update( this._ns , this._query , replacement , false , false );
this._db._getExtraInfo("Replaced");
};
// Remove is always multi
DBQuery.prototype.remove = function(){
for ( var k in this._query ){
if ( k == "_id" && typeof( this._query[k] ) == "undefined" ){
throw "can't have _id set to undefined in a remove expression";
}
}
this._checkMulti();
this._db._initExtraInfo();
this._mongo.remove( this._ns , this._query , false );
this._db._getExtraInfo("Removed");
};
//----------------------------------------------------------------------------
// Full Text Search
//----------------------------------------------------------------------------
DBQuery.prototype.textSearch = function( search ) {
var text = {
text: this._collection.getName(),
search: search,
filter: this._query,
project: this._fields,
limit: this._limit
}
var result = this._db.runCommand( text );
return result.results;
};
function listDbs(){
return db.adminCommand("listDatabases").databases.map(function(d){return d.name});
}
this.__proto__.constructor.autocomplete = listDbs;shellHelper.find = function (query) {
assert(typeof query == "string");
var args = query.split( /\s+/ );
query = args[0];
args = args.splice(1);
if (query !== "") {
var regexp = new RegExp(query, "i");
var result = db.runCommand("listCommands");
for (var command in result.commands) {
var commandObj = result.commands[command];
var help = commandObj.help;
if (commandObj.help.indexOf('\n') != -1 ) {
help = commandObj.help.substring(0, commandObj.help.lastIndexOf('\n'));
}
if (regexp.test(command) || regexp.test(help)) {
var numSpaces = 30 - command.length;
print(colorize(command, {color: 'green'}), Array(numSpaces).join(" "), "-", help);
}
}
}
};
//----------------------------------------------------------------------------
// Color Functions
//----------------------------------------------------------------------------
__ansi = {
csi: String.fromCharCode(0x1B) + '[',
reset: '0',
text_prop: 'm',
foreground: '3',
bright: '1',
underline: '4',
colors: {
black: '0',
red: '1',
green: '2',
yellow: '3',
blue: '4',
magenta: '5',
cyan: '6',
gray: '7',
}
};
function controlCode( parameters ) {
if ( parameters === undefined ) {
parameters = "";
}
else if (typeof(parameters) == 'object' && (parameters instanceof Array)) {
parameters = parameters.join(';');
}
return __ansi.csi + String(parameters) + String(__ansi.text_prop);
};
function applyColorCode( string, properties, nocolor ) {
// Allow global __colorize default to be overriden
var applyColor = (null == nocolor) ? __colorize : !nocolor;
return applyColor ? controlCode(properties) + String(string) + controlCode() : String(string);
};
function colorize( string, color, nocolor ) {
var params = [];
var code = __ansi.foreground + __ansi.colors[color.color];
params.push(code);
if ( color.bright === true ) params.push(__ansi.bright);
if ( color.underline === true ) params.push(__ansi.underline);
return applyColorCode( string, params, nocolor );
};
function colorizeAll( strings, color, nocolor ) {
return strings.map(function(string) {
return colorize( string, color, nocolor );
});
};
__indent = Array(mongo_hacker_config.indent + 1).join(' ');
__colorize = (_isWindows() && !mongo_hacker_config['force_color']) ? false : true;
ObjectId.prototype.toString = function() {
return this.str;
};
ObjectId.prototype.tojson = function(indent, nolint) {
return tojson(this);
};
var dateToJson = Date.prototype.tojson;
Date.prototype.tojson = function(indent, nolint, nocolor) {
var isoDateString = dateToJson.call(this);
var dateString = isoDateString.substring(8, isoDateString.length-1);
var isodate = colorize(dateString, mongo_hacker_config.colors.date, nocolor);
return 'ISODate(' + isodate + ')';
};
Array.tojson = function( a , indent , nolint, nocolor ){
var lineEnding = nolint ? " " : "\n";
if (!indent)
indent = "";
if ( nolint )
indent = "";
if (a.length === 0) {
return "[ ]";
}
var s = "[" + lineEnding;
indent += __indent;
for ( var i=0; i<a.length; i++){
s += indent + tojson( a[i], indent , nolint, nocolor );
if ( i < a.length - 1 ){
s += "," + lineEnding;
}
}
if ( a.length === 0 ) {
s += indent;
}
indent = indent.substring(__indent.length);
s += lineEnding+indent+"]";
return s;
};
function surround(name, inside) {
return [name, '(', inside, ')'].join('');
}
Number.prototype.commify = function() {
// http://stackoverflow.com/questions/2901102
return this.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
NumberLong.prototype.tojson = function(indent, nolint, nocolor) {
var color = mongo_hacker_config.colors.number;
var output = colorize('"' + this.toString().match(/-?\d+/)[0] + '"', color, nocolor);
return surround('NumberLong', output);
};
NumberInt.prototype.tojson = function(indent, nolint, nocolor) {
var color = mongo_hacker_config.colors.number;
var output = colorize('"' + this.toString().match(/-?\d+/)[0] + '"', color, nocolor);
return surround('NumberInt', output);
};
BinData.prototype.tojson = function(indent , nolint, nocolor) {
var uuidType = mongo_hacker_config.uuid_type;
var uuidColor = mongo_hacker_config.colors.uuid;
var binDataColor = mongo_hacker_config.colors.binData;
if (this.subtype() === 3) {
var output = colorize('"' + uuidToString(this) + '"', uuidColor, nocolor) + ', '
output += colorize('"' + uuidType + '"', uuidColor)
return surround('UUID', output);
} else if (this.subtype() === 4) {
var output = colorize('"' + uuidToString(this, "default") + '"', uuidColor, nocolor) + ')'
return surround('UUID', output);
} else {
var output = colorize(this.subtype(), {color: 'red'}) + ', '
output += colorize('"' + this.base64() + '"', binDataColor, nocolor)
return surround('BinData', output);
}
};
DBQuery.prototype.shellPrint = function(){
try {
var start = new Date().getTime();
var n = 0;
while ( this.hasNext() && n < DBQuery.shellBatchSize ){
var s = this._prettyShell ? tojson( this.next() ) : tojson( this.next() , "" , true );
print( s );
n++;
}
var output = [];
if (typeof _verboseShell !== 'undefined' && _verboseShell) {
var time = new Date().getTime() - start;
var slowms = getSlowms();
var fetched = "Fetched " + n + " record(s) in ";
if (time > slowms) {
fetched += colorize(time + "ms", { color: "red", bright: true });
} else {
fetched += colorize(time + "ms", { color: "green", bright: true });
}
output.push(fetched);
}
var paranoia = mongo_hacker_config.index_paranoia;
if (typeof paranoia !== 'undefined' && paranoia) {
var explain = this.clone();
explain._ensureSpecial();
explain._query.$explain = true;
explain._limit = Math.abs(n) * -1;
var result = explain.next();
if (current_version < 3) {
var type = result.cursor;
if (type !== undefined) {
var index_use = "Index[";
if (type == "BasicCursor") {
index_use += colorize( "none", { color: "red", bright: true });
} else {
index_use += colorize( result.cursor.substring(12), { color: "green", bright: true });
}
index_use += "]";
output.push(index_use);
}
} else {
var winningPlan = result.queryPlanner.winningPlan;
var winningInputStage = winningPlan.inputStage.inputStage;
if (winningPlan !== undefined) {
var index_use = "Index[";
if (winningPlan.inputStage.stage === "COLLSCAN" || (winningInputStage !== undefined && winningInputStage.stage !== "IXSCAN")) {
index_use += colorize( "none", { color: "red", bright: true });
} else {
var fullScan = false;
for (index in winningInputStage.keyPattern) {
if (winningInputStage.indexBounds[index][0] == "[MinKey, MaxKey]") {
fullScan = true;
}
}
if (fullScan) {
index_use += colorize( winningInputStage.indexName + " (full scan)", { color: "yellow", bright: true });
} else {
index_use += colorize( winningInputStage.indexName, { color: "green", bright: true });
}
}
index_use += "]";
output.push(index_use);
}
}
}
if ( this.hasNext() ) {
___it___ = this;
output.push("More[" + colorize("true", { color: "green", bright: true }) + "]");
}
print(output.join(" -- "));
}
catch ( e ){
print( e );
}
};
function isInArray(array, value) {
return array.indexOf(value) > -1;
}
tojsonObject = function( x, indent, nolint, nocolor, sort_keys ) {
var lineEnding = nolint ? " " : "\n";
var tabSpace = nolint ? "" : __indent;
var sortKeys = (null == sort_keys) ? mongo_hacker_config.sort_keys : sort_keys;
assert.eq( ( typeof x ) , "object" , "tojsonObject needs object, not [" + ( typeof x ) + "]" );
if (!indent)
indent = "";
if ( typeof( x.tojson ) == "function" && x.tojson != tojson ) {
return x.tojson( indent, nolint, nocolor );
}
if ( x.constructor && typeof( x.constructor.tojson ) == "function" && x.constructor.tojson != tojson ) {
return x.constructor.tojson( x, indent , nolint, nocolor );
}
if ( x.toString() == "[object MaxKey]" )
return "{ $maxKey : 1 }";
if ( x.toString() == "[object MinKey]" )
return "{ $minKey : 1 }";
var s = "{" + lineEnding;
// push one level of indent
indent += tabSpace;
var total = 0;
for ( var k in x ) total++;
if ( total === 0 ) {
s += indent + lineEnding;
}
var keys = x;
if ( typeof( x._simpleKeys ) == "function" )
keys = x._simpleKeys();
var num = 1;
var keylist=[];
for(var key in keys)
keylist.push(key);
if ( sortKeys ) {
// Disable sorting if this object looks like an index spec
if ( (isInArray(keylist, "v") && isInArray(keylist, "key") && isInArray(keylist, "name") && isInArray(keylist, "ns")) ) {
sortKeys = false;
} else {
keylist.sort();
}
}
for ( var i=0; i<keylist.length; i++) {
var key=keylist[i];
var val = x[key];
if ( val == DB.prototype || val == DBCollection.prototype )
continue;
var color = mongo_hacker_config.colors.key;
s += indent + colorize("\"" + key + "\"", color, nocolor) + ": " + tojson( val, indent , nolint, nocolor, sortKeys );
if (num != total) {
s += ",";
num++;
}
s += lineEnding;
}
// pop one level of indent
indent = indent.substring(__indent.length);
return s + indent + "}";
};
tojson = function( x, indent , nolint, nocolor, sort_keys ) {
var sortKeys = (null == sort_keys) ? mongo_hacker_config.sort_keys : sort_keys;
if (null == tojson.caller) {
// Unknonwn caller context, so assume this is from C++ code
nocolor = true;
}
if ( x === null )
return colorize("null", mongo_hacker_config.colors['null'], nocolor);
if ( x === undefined )
return colorize("undefined", mongo_hacker_config.colors['undefined'], nocolor);
if ( x.isObjectId ) {
var color = mongo_hacker_config.colors['objectid'];
return surround('ObjectId', colorize('"' + x.str + '"', color, nocolor));
}
if (!indent)
indent = "";
var s;
switch ( typeof x ) {
case "string": {
s = "\"";
for ( var i=0; i<x.length; i++ ){
switch (x[i]){
case '"': s += '\\"'; break;
case '\\': s += '\\\\'; break;
case '\b': s += '\\b'; break;
case '\f': s += '\\f'; break;
case '\n': s += '\\n'; break;
case '\r': s += '\\r'; break;
case '\t': s += '\\t'; break;
default: {
var code = x.charCodeAt(i);
if (code < 0x20){
s += (code < 0x10 ? '\\u000' : '\\u00') + code.toString(16);
} else {
s += x[i];
}
}
}
}
s += "\"";
return colorize(s, mongo_hacker_config.colors.string, nocolor);
}
case "number":
return colorize(x, mongo_hacker_config.colors.number, nocolor);
case "boolean":
return colorize("" + x, mongo_hacker_config.colors['boolean'], nocolor);
case "object": {
s = tojsonObject( x, indent , nolint, nocolor, sortKeys );
if ( ( nolint === null || nolint === true ) && s.length < 80 && ( indent === null || indent.length === 0 ) ){
s = s.replace( /[\s\r\n ]+/gm , " " );
}
return s;
}
case "function":
return colorize(x.toString(), mongo_hacker_config.colors['function'], nocolor);
default:
throw "tojson can't handle type " + ( typeof x );
}
};
DBQuery.prototype._validate = function( o ){
var firstKey = null;
for (var k in o) { firstKey = k; break; }
if (firstKey !== null && firstKey[0] == '$') {
// for mods we only validate partially, for example keys may have dots
this._validateObject( o );
} else {
// we're basically inserting a brand new object, do full validation
this._validateForStorage( o );
}
};
DBQuery.prototype._validateObject = function( o ){
if (typeof(o) != "object")
throw "attempted to save a " + typeof(o) + " value. document expected.";
if ( o._ensureSpecial && o._checkModify )
throw "can't save a DBQuery object";
};
DBQuery.prototype._validateForStorage = function( o ){
this._validateObject( o );
for ( var k in o ){
if ( k.indexOf( "." ) >= 0 ) {
throw "can't have . in field names [" + k + "]" ;
}
if ( k.indexOf( "$" ) === 0 && ! DBCollection._allowedFields[k] ) {
throw "field names cannot start with $ [" + k + "]";
}
if ( o[k] !== null && typeof( o[k] ) === "object" ) {
this._validateForStorage( o[k] );
}
}
};
DBQuery.prototype._checkMulti = function(){
if(this._limit > 0 || this._skip > 0){
var ids = this.clone().select({_id: 1}).map(function(o){return o._id;});
this._query['_id'] = {'$in': ids};
return true;
} else {
return false;
}
};
DBQuery.prototype.ugly = function(){
this._prettyShell = false;
return this;
}
DB.prototype.shutdownServer = function(opts) {
if( "admin" != this._name ){
return "shutdown command only works with the admin database; try 'use admin'";
}
cmd = {"shutdown" : 1};
opts = opts || {};
for (var o in opts) {
cmd[o] = opts[o];
}
try {
var res = this.runCommand(cmd);
if( res )
throw "shutdownServer failed: " + res.errmsg;
throw "shutdownServer failed";
}
catch ( e ){
assert( e.message.indexOf( "error doing query: failed" ) >= 0 , "unexpected error: " + tojson( e ) );
print( "server should be down..." );
}
}
// helper function to format delta counts
function delta(currentCount, previousCount) {
var delta = Number(currentCount - previousCount);
var formatted_delta;
if (isNaN(delta)) {
formatted_delta = colorize("(first count)", { color: 'blue' });
} else if (delta == 0) {
formatted_delta = colorize("(=)", { color: 'blue' });
} else if (delta > 0) {
formatted_delta = colorize("(+" + delta.commify() + ")", { color: 'green' });
} else if (delta < 0) {
formatted_delta = colorize("(" + delta.commify() + ")", { color: 'red' });
} else {
formatted_delta = (delta + " not supported");
}
return formatted_delta;
}
// global variable (to ensure "persistence" of document counts)
shellHelper.previousDocumentCount = {};
// "count documents", a bit akin to "show collections"
shellHelper.count = function (what) {
assert(typeof what == "string");
var args = what.split( /\s+/ );
what = args[0]
args = args.splice(1)
if (what == "collections" || what == "tables") {
databaseNames = db.getMongo().getDatabaseNames();
collectionCounts = databaseNames.map(function (databaseName) {
var count = db.getMongo().getDB(databaseName).getCollectionNames().length;
return (count.commify() + " collection(s)");
});
databaseNames = colorizeAll(databaseNames, mongo_hacker_config['colors']['databaseNames']);
printPaddedColumns(databaseNames, collectionCounts);
return "";
}
if (what == "documents" || what == "docs") {
collectionNames = db.getCollectionNames().filter(function (collectionName) {
// exclude "system" collections from "count" operation
return !collectionName.startsWith('system.');
});
documentCounts = collectionNames.map(function (collectionName) {
var count = db.getCollection(collectionName).count();
return (count.commify() + " document(s)");
});
deltaCounts = collectionNames.map(function (collectionName) {
// retrieve the previous document count for this collection
var previous = shellHelper.previousDocumentCount[collectionName];
// determine the current document count for this collection
var current = db.getCollection(collectionName).count();
// update the stored document count for this collection
shellHelper.previousDocumentCount[collectionName] = current;
// format the delta since last count
return delta(current, previous);
});
collectionNames = colorizeAll(collectionNames, mongo_hacker_config['colors']['collectionNames']);
if (mongo_hacker_config['count_deltas']) {
printPaddedColumns(collectionNames, documentCounts, deltaCounts);
} else {
printPaddedColumns(collectionNames, documentCounts);
}
return "";
}
if (what == "index" || what == "indexes") {
db.indexStats("", 1);
return ""
}
throw "don't know how to count [" + what + "]";
}
DBRef.prototype.__toString = DBRef.prototype.toString;
DBRef.prototype.toString = function () {
var org = this.__toString();
var config = mongo_hacker_config.dbref;
if (!config.extended_info) {
return org;
}
var additional = {};
var o = this;
for (var p in o) {
if (typeof o[p] === 'function') {
continue;
}
if (!config.plain && (p === '$ref' || p === '$id')) {
continue;
}
if (config.db_if_differs && p === '$db' && o[p] === db.getName()) {
continue;
}
additional[p] = o[p];
}
if (config.plain) {
return tojsonObject(additional, undefined, true);
}
return Object.keys(additional).length
? (org.slice(0, -1) + ", " + tojsonObject(additional, undefined, true) + ")")
: org;
};
//----------------------------------------------------------------------------
// findAndModify Helper
//----------------------------------------------------------------------------
DBQuery.prototype._findAndModify = function( options ) {
var findAndModify = {
'findandmodify': this._collection.getName(),
'query': this._query,
'new': true,
'fields': this._fields,
'upsert': this._upsert || false,
'sort': this._query.orderby || {},
};
for (var key in options){
findAndModify[key] = options[key];
};
var result = this._db.runCommand( findAndModify );
if ( ! result.ok ){
throw "findAndModifyFailed failed: " + tojson( result );
};
return result.value;
};
//----------------------------------------------------------------------------
// findAndModify Terminal Variants
//----------------------------------------------------------------------------
DBQuery.prototype.updateAndGet = function( update ) {
return this._findAndModify({ 'update': update });
};
DBQuery.prototype.getAndUpdate = function( update ) {
return this._findAndModify({ 'update': update, 'new': false });
};
DBQuery.prototype.replaceAndGet = function( replacement ) {
return this._findAndModify({ 'update': replacement });
};
DBQuery.prototype.getAndReplace = function( replacement ) {
return this._findAndModify({ 'update': replacement, 'new': false });
};
DBQuery.prototype.getAndRemove = function() {
return this._findAndModify({ 'remove': true })
};function runMatch(cmd, args, regexp) {
clearRawMongoProgramOutput();
if (args) {
run(cmd, args);
} else {
run(cmd);
}
var output = rawMongoProgramOutput();
return output.match(regexp);
};
function getEnv(env_var) {
var env_regex = new RegExp(' ' + env_var + '=(.*)');
return runMatch('env', '', env_regex)[1];
};
function getVersion() {
var regexp = /version: (\d).(\d).(\d)/;
return runMatch('mongo', '--version', regexp).slice(1, 4);
};
function isMongos() {
return db.isMaster().msg == 'isdbgrid';
};
function getSlowms(){
if(!isMongos()){
return db.getProfilingStatus().slowms;
} else {
return 100;
}
};
function maxLength(listOfNames) {
return listOfNames.reduce(function(maxLength, name) {
return (name.length > maxLength) ? name.length : maxLength ;
}, 0);
};
function printPaddedColumns() {
var columnWidths = Array.prototype.map.call(
arguments,
function(column) {
return maxLength(column);
}
);
for (i = 0; i < arguments[0].length; i++) {
row = "";
for (j = 0; j < arguments.length; j++) {
row += arguments[j][i].toString().pad(columnWidths[j], (j == 0));
if (j < (arguments.length - 1)) {
separator = ((j == 0) ?
mongo_hacker_config['column_separator'] :
mongo_hacker_config['value_separator']
);
row += " " + separator + " ";
}
}
print(row);
}
return null;
};
// Override group because map/reduce style is deprecated
DBCollection.prototype.agg_group = function( name, group_field, operation, op_value, filter ) {
var ops = [];
var group_op = { $group: { _id: '$' + group_field } };
if (filter !== undefined) {
ops.push({ '$match': filter });
}
group_op['$group'][name] = { };
group_op['$group'][name]['$' + operation] = op_value;
ops.push(group_op);
return this.aggregate(ops);
};
// Function that groups and counts by group after applying filter
DBCollection.prototype.gcount = function( group_field, filter ) {
return this.agg_group('count', group_field, 'sum', 1, filter);
};
// Function that groups and sums sum_field after applying filter
DBCollection.prototype.gsum = function( group_field, sum_field, filter ) {
return this.agg_group('sum', group_field, 'sum', '$' + sum_field, filter);
};
// Function that groups and averages avg_feld after applying filter
DBCollection.prototype.gavg = function( group_field, avg_field, filter ) {
return this.agg_group('avg', group_field, 'avg', '$' + avg_field, filter);
};
// Improve the default prompt with hostname, process type, and version
prompt = function() {
var serverstatus = db.serverStatus();
var host = serverstatus.host.split('.')[0];
var process = serverstatus.process;
var version = db.serverBuildInfo().version;
var repl_set = db._adminCommand({"replSetGetStatus": 1}).ok !== 0;
var rs_state = '';
if(repl_set) {
var status = rs.status();
var members = status.members;
var rs_name = status.set;
for(var i = 0; i<members.length; i++){
if(members[i].self === true){
rs_state = '[' + members[i].stateStr + ':' + rs_name + ']';
}
};
}
var state = isMongos() ? '[mongos]' : rs_state;
return host + '(' + process + '-' + version + ')' + state + ' ' + db + '> ';
};
sh.getRecentMigrations = function () {
var configDB = db.getSiblingDB("config");
var yesterday = new Date( new Date() - 24 * 60 * 60 * 1000 );
var result = [];
result = result.concat(configDB.changelog.aggregate( [
{ $match : { time : { $gt : yesterday }, what : "moveChunk.from", "details.errmsg" : {
"$exists" : false } } },
{ $group : { _id: { msg: "$details.errmsg" }, count : { "$sum":1 } } },
{ $project : { _id : { $ifNull: [ "$_id.msg", "Success" ] }, count : "$count" } }
] ).result);
result = result.concat(configDB.changelog.aggregate( [
{ $match : { time : { $gt : yesterday }, what : "moveChunk.from", "details.errmsg" : {
"$exists" : true } } },
{ $group : { _id: { msg: "$details.errmsg", from : "$details.from", to: "$details.to" },
count : { "$sum":1 } } },
{ $project : { _id : "$_id.msg" , from : "$_id.from", to : "$_id.to" , count : "$count" } }
] ).result);
return result;
};
printShardingStatus = function( configDB , verbose ){
if (configDB === undefined)
configDB = db.getSisterDB('config')
var version = configDB.getCollection( "version" ).findOne();
if ( version == null ){
print( "printShardingStatus: this db does not have sharding enabled. be sure you are",
"connecting to a mongos from the shell and not to a mongod." );
return;
}
var raw = "";
var output = function(s){
raw += s + "\n";
}
output( "--- Sharding Status --- " );
output( " sharding version: " + tojson( configDB.getCollection( "version" ).findOne(), " " ) );
output( " shards:" );
configDB.shards.find().sort( { _id : 1 } ).forEach(
function(z){
output( " " + tojsononeline( z ) );
}
);
// All of the balancer information functions below depend on a connection to a liveDB
// This isn't normally a problem, but can cause issues in testing and running with --nodb
if ( typeof db !== "undefined" ) {
output( " balancer:" );
//Is the balancer currently enabled
output( "\tCurrently enabled: " + ( sh.getBalancerState() ?
colorize("yes", {color: "cyan"}) :
colorize("no", {color: "red"}) ) );
//Is the balancer currently active
output( "\tCurrently running: " +
colorize(( sh.isBalancerRunning() ? "yes" : "no" ), {color: "gray"}) );
//Output details of the current balancer round
var balLock = sh.getBalancerLockDetails();
if ( balLock ) {
output( "\t\tBalancer lock taken at " +
colorize(balLock.when, {color: "gray"}) + " by " +
colorize(balLock.who, {color: "cyan"}) );
}
//Output the balancer window
var balSettings = sh.getBalancerWindow();
if ( balSettings ) {
output( "\t\tBalancer active window is set between " +
colorize(balSettings.start, {color: "gray"}) + " and " +
colorize(balSettings.stop, {color: "gray"}) + " server local time");
}
//Output the list of active migrations
var activeMigrations = sh.getActiveMigrations();
if (activeMigrations.length > 0 ){
output("\tCollections with active migrations: ");
activeMigrations.forEach( function(migration){
output("\t\t" +
colorize(migration._id, {color: "cyan"})+ " started at " +
colorize(migration.when, {color: "gray"}) );
});
}
// Actionlog and version checking only works on 2.7 and greater
var versionHasActionlog = false;
var metaDataVersion = configDB.getCollection("version").findOne().currentVersion;
if ( metaDataVersion > 5 ) {
versionHasActionlog = true;
}
if ( metaDataVersion == 5 ) {
var verArray = db.serverBuildInfo().versionArray;
if (verArray[0] == 2 && verArray[1] > 6){
versionHasActionlog = true;
}
}
if ( versionHasActionlog ) {
//Review config.actionlog for errors
var actionReport = sh.getRecentFailedRounds();
//Always print the number of failed rounds
output( "\tFailed balancer rounds in last 5 attempts: " +
colorize(actionReport.count, {color: "red"}) );
//Only print the errors if there are any
if ( actionReport.count > 0 ){
output( "\tLast reported error: " + actionReport.lastErr );
output( "\tTime of Reported error: " + actionReport.lastTime );
}
output("\tMigration Results for the last 24 hours: ");
var migrations = sh.getRecentMigrations();
if(migrations.length > 0) {
migrations.forEach( function(x) {
if (x._id === "Success"){
output( "\t\t" + colorize(x.count, {color: "gray"}) +
" : "+ colorize(x._id, {color: "cyan"}));
} else {
output( "\t\t" + colorize(x.count, {color: "gray"}) +
" : Failed with error '" + colorize(x._id, {color: "red"}) +
"', from " + x.from + " to " + x.to );
}
});
} else {
output( "\t\tNo recent migrations");
}
}
}
output( " databases:" );
configDB.databases.find().sort( { name : 1 } ).forEach(
function(db){
output( " " + tojsononeline(db,"",true) );
if (db.partitioned){
configDB.collections.find( { _id : new RegExp( "^" +
RegExp.escape(db._id) + "\\." ) } ).
sort( { _id : 1 } ).forEach( function( coll ){
if ( coll.dropped == false ){
output( " " + coll._id );
output( " shard key: " + tojson(coll.key, 0, true) );
output( " chunks:" );
res = configDB.chunks.aggregate(
{ "$match": { ns: coll._id } },
{ "$group": { _id: "$shard", nChunks: { "$sum": 1 } } },
{ "$project" : { _id : 0 , shard : "$_id" , nChunks : "$nChunks" } },
{ "$sort" : { shard : 1 } }
).result
var totalChunks = 0;
res.forEach( function(z){
totalChunks += z.nChunks;
output( " " + z.shard + ": " + z.nChunks );
} )
if ( totalChunks < 20 || verbose ){
configDB.chunks.find( { "ns" : coll._id } ).sort( { min : 1 } ).forEach(
function(chunk){
output( " " +
tojson( chunk.min, 0, true) + " -> " +
tojson( chunk.max, 0, true ) +
" on: " + colorize(chunk.shard, {color: 'cyan'}) + " " + tojson( chunk.lastmod ) + " " +
( chunk.jumbo ? "jumbo " : "" )
);
}
);
}
else {
output( "\t\t\ttoo many chunks to print, use verbose if you want to force print" );
}
configDB.tags.find( { ns : coll._id } ).sort( { min : 1 } ).forEach(
function( tag ) {
output( " tag: " + tag.tag + " " + tojson( tag.min ) + " -> " + tojson( tag.max ) );
}
)
}
}
)
}
}
);
print( raw );
}
// Better show dbs
shellHelper.show = function (what) {
assert(typeof what == "string");
var args = what.split( /\s+/ );
what = args[0]
args = args.splice(1)
if (what == "profile") {
if (db.system.profile.count() == 0) {
print("db.system.profile is empty");
print("Use db.setProfilingLevel(2) will enable profiling");
print("Use db.system.profile.find() to show raw profile entries");
}
else {
print();
db.system.profile.find({ millis: { $gt: 0} }).sort({ $natural: -1 }).limit(5).forEach(
function (x) {
print("" + x.op + "\t" + x.ns + " " + x.millis + "ms " + String(x.ts).substring(0, 24));
var l = "";
for ( var z in x ){
if ( z == "op" || z == "ns" || z == "millis" || z == "ts" )
continue;
var val = x[z];
var mytype = typeof(val);
if ( mytype == "string" ||
mytype == "number" )
l += z + ":" + val + " ";
else if ( mytype == "object" )
l += z + ":" + tojson(val ) + " ";
else if ( mytype == "boolean" )
l += z + " ";
else
l += z + ":" + val + " ";
}
print( l );
print("\n");
}
)
}
return "";
}
if (what == "users") {
db.getUsers().forEach(printjson);
return "";
}
if (what == "roles") {
db.getRoles({showBuiltinRoles: true}).forEach(printjson);
return "";
}
if (what == "collections" || what == "tables") {
var collectionNames = db.getCollectionNames();
var collectionSizes = collectionNames.map(function (name) {
var stats = db.getCollection(name).stats();
var size = (stats.size / 1024 / 1024).toFixed(3);
return (size + "MB");
});
var collectionStorageSizes = collectionNames.map(function (name) {
var stats = db.getCollection(name).stats();
var storageSize = (stats.storageSize / 1024 / 1024).toFixed(3);
return (storageSize + "MB");
});
collectionNames = colorizeAll(collectionNames, mongo_hacker_config['colors']['collectionNames']);
printPaddedColumns(collectionNames, collectionSizes, collectionStorageSizes);
return "";
}
if (what == "dbs" || what == "databases") {
var databases = db.getMongo().getDBs().databases.sort(function(a, b) { return a.name.localeCompare(b.name) });
var databaseNames = databases.map(function(db) {
return db.name;
});
var databaseSizes = databases.map(function(db) {
var sizeInGigaBytes = (db.sizeOnDisk / 1024 / 1024 / 1024).toFixed(3);
return (db.sizeOnDisk > 1) ? (sizeInGigaBytes + "GB") : "(empty)";
});
databaseNames = colorizeAll(databaseNames, mongo_hacker_config['colors']['databaseNames']);
printPaddedColumns(databaseNames, databaseSizes);
return "";
}
if (what == "log" ) {
var n = "global";
if ( args.length > 0 )
n = args[0]
var res = db.adminCommand( { getLog : n } );
if ( ! res.ok ) {
print("Error while trying to show " + n + " log: " + res.errmsg);
return "";
}
for ( var i=0; i<res.log.length; i++){
print( res.log[i] )
}
return ""
}
if (what == "logs" ) {
var res = db.adminCommand( { getLog : "*" } )
if ( ! res.ok ) {
print("Error while trying to show logs: " + res.errmsg);
return "";
}
for ( var i=0; i<res.names.length; i++){
print( res.names[i] )
}
return ""
}
if (what == "startupWarnings" ) {
var dbDeclared, ex;
try {
// !!db essentially casts db to a boolean
// Will throw a reference exception if db hasn't been declared.
dbDeclared = !!db;
} catch (ex) {
dbDeclared = false;
}
if (dbDeclared) {
var res = db.adminCommand( { getLog : "startupWarnings" } );
if ( res.ok ) {
if (res.log.length == 0) {
return "";
}
print( "Server has startup warnings: " );
for ( var i=0; i<res.log.length; i++){
print( res.log[i] )
}
return "";
} else if (res.errmsg == "no such cmd: getLog" ) {
// Don't print if the command is not available
return "";
} else if (res.code == 13 /*unauthorized*/ ||
res.errmsg == "unauthorized" ||
res.errmsg == "need to login") {
// Don't print if startupWarnings command failed due to auth
return "";
} else {
print("Error while trying to show server startup warnings: " + res.errmsg);
return "";
}
} else {
print("Cannot show startupWarnings, \"db\" is not set");
return "";
}
}
throw "don't know how to show [" + what + "]";
}
function base64ToHex(base64) {
var base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var hexDigits = "0123456789abcdef";
var hex = "";
for (var i = 0; i < 24; ) {
var e1 = base64Digits.indexOf(base64[i++]);
var e2 = base64Digits.indexOf(base64[i++]);
var e3 = base64Digits.indexOf(base64[i++]);
var e4 = base64Digits.indexOf(base64[i++]);
var c1 = (e1 << 2) | (e2 >> 4);
var c2 = ((e2 & 15) << 4) | (e3 >> 2);
var c3 = ((e3 & 3) << 6) | e4;
hex += hexDigits[c1 >> 4];
hex += hexDigits[c1 & 15];
if (e3 != 64) {
hex += hexDigits[c2 >> 4];
hex += hexDigits[c2 & 15];
}
if (e4 != 64) {
hex += hexDigits[c3 >> 4];
hex += hexDigits[c3 & 15];
}
}
return hex;
}
function hexToBase64(hex) {
var base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var base64 = "";
var group;
for (var i = 0; i < 30; i += 6) {
group = parseInt(hex.substr(i, 6), 16);
base64 += base64Digits[(group >> 18) & 0x3f];
base64 += base64Digits[(group >> 12) & 0x3f];
base64 += base64Digits[(group >> 6) & 0x3f];
base64 += base64Digits[group & 0x3f];
}
group = parseInt(hex.substr(30, 2), 16);
base64 += base64Digits[(group >> 2) & 0x3f];
base64 += base64Digits[(group << 4) & 0x3f];
base64 += "==";
return base64;
}
var platformSpecificUuidModifications = {
"java": function (hex) {
var msb = hex.substr(0, 16);
var lsb = hex.substr(16, 16);
msb = msb.substr(14, 2) + msb.substr(12, 2) + msb.substr(10, 2) + msb.substr(8, 2)
+ msb.substr(6, 2) + msb.substr(4, 2) + msb.substr(2, 2) + msb.substr(0, 2);
lsb = lsb.substr(14, 2) + lsb.substr(12, 2) + lsb.substr(10, 2) + lsb.substr(8, 2)
+ lsb.substr(6, 2) + lsb.substr(4, 2) + lsb.substr(2, 2) + lsb.substr(0, 2);
return msb + lsb;
},
"c#": function (hex) {
return hex.substr(6, 2) + hex.substr(4, 2) + hex.substr(2, 2) + hex.substr(0, 2)
+ hex.substr(10, 2) + hex.substr(8, 2) + hex.substr(14, 2) + hex.substr(12, 2)
+ hex.substr(16, 16);
},
"python": function (hex) {
return hex;
},
"default": function (hex) {
return hex;
}
};
function UUID(uuid, type) {
var hex = uuid.replace(/[{}-]/g, "");
var typeNum = 4;
if (type != undefined) {
typeNum = 3;
hex = platformSpecificUuidModifications[type](hex);
}
return new BinData(typeNum, hexToBase64(hex));
}
function uuidToString(uuid, uuidType) {
var uuidType = uuidType || mongo_hacker_config['uuid_type'];
var hex = platformSpecificUuidModifications[uuidType](base64ToHex(uuid.base64()));
return hex.substr(0, 8) + '-' + hex.substr(8, 4) + '-' + hex.substr(12, 4)
+ '-' + hex.substr(16, 4) + '-' + hex.substr(20, 12);
}
setVerboseShell(true);
DBQuery.prototype._prettyShell = true
DB.prototype._getExtraInfo = function(action) {
if ( typeof _verboseShell === 'undefined' || !_verboseShell ) {
__callLastError = true;
return;
}
// explicit w:1 so that replset getLastErrorDefaults aren't used here which would be bad.
var startTime = new Date().getTime();
var res = this.getLastErrorCmd(1);
if (res) {
if (res.err !== undefined && res.err !== null) {
// error occurred, display it
print(res.err);
return;
}
var info = action + " ";
// hack for inserted because res.n is 0
info += action != "Inserted" ? res.n : 1;
if (res.n > 0 && res.updatedExisting !== undefined) info += " " + (res.updatedExisting ? "existing" : "new");
info += " record(s) in ";
var time = new Date().getTime() - startTime;
var slowms = getSlowms();
if (time > slowms) {
info += colorize(time + "ms", { color: 'red', bright: true });
} else {
info += colorize(time + "ms", { color: 'green', bright: true });
}
print(info);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment