Skip to content

Instantly share code, notes, and snippets.

@terakilobyte
Created November 2, 2017 16:06
Show Gist options
  • Save terakilobyte/6a850c0b9d2dbb5bb65d864a48891f1b to your computer and use it in GitHub Desktop.
Save terakilobyte/6a850c0b9d2dbb5bb65d864a48891f1b to your computer and use it in GitHub Desktop.
/*
*
* Mongo-Hacker
* MongoDB Shell Enhancements for Hackers
*
* Tyler J. Brock - 2013 - 2016
*
* http://tylerbrock.github.com/mongo-hacker
*
*
*
* Edits made to make it play well with changes to the aggregation framework in MongoDB 3.6
* Remove all aggregation additions. They're no longer needed and now get in the way.
*
*/
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.14', // 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");
}
}
// Helper method for determining if parameter has dollar signs
function hasDollar(fields){
for (k in fields){
if(k.indexOf('$') !== -1){
return true;
};
};
return false;
}
//----------------------------------------------------------------------------
// 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);
}
};
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 ( 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;
};
function runOnDbs(regexp, callback) {
var originalDb = db.getName();
db.getMongo().getDBs().databases.filter(function(db) {
return db.name.match(regexp); }
).forEach(function(dbEntry) {
db = db.getSiblingDB(dbEntry.name);
callback(db);
});
db = db.getSiblingDB(originalDb);
}
// 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 enterpriseModulePresent = db.serverBuildInfo().modules.indexOf("enterprise");
var enterprise = enterpriseModulePresent !== -1 ? "ENTERPRISE" : "";
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 + ' ' + enterprise + ')' + state + ' ' + db + '> ';
};
// Begin PS Hack
// Copyright (c) 2016 Andrew Young
// SPDX-License-Identifier: MIT
function printTableRow(row, columnWidths) {
var pad = function(x, i) {
return x.pad(i).slice(-i).toString();
};
var line = "";
for(c in columnWidths) {
line = line + pad(row[c], columnWidths[c]) + " ";
}
print(line);
}
function printTable(headers, rows) {
// printTable(headers: Array[String], rows: Array[Array[String]])
// Find column widths
var columnWidths = [];
for(c in headers) {
columnWidths[c] = headers[c].length;
}
for(r in rows) {
var row = rows[r];
for (c in headers) {
row[c] = row[c].toString();
columnWidths[c] = Math.max(columnWidths[c], row[c].length);
}
}
// Print Table
var hr = function(l) {
return new Array(l + 1).join("-");
};
var hrs = [];
for(c in columnWidths) {
hrs.push(hr(columnWidths[c]));
}
printTableRow(headers, columnWidths);
printTableRow(hrs, columnWidths);
rows.forEach(function(row) { printTableRow(row, columnWidths); });
}
function getConnections() {
return db.currentOp(true).inprog.filter(function(x) { return x.connectionId; } );
}
shellHelper.ps = function() {
var headers = [
"Connection",
"ID",
"Client",
"S",
"Active",
"Time",
"WaitLock",
"Operation",
"Plan",
"Namespace"
];
var rows = [];
var connections = getConnections();
connections.forEach(function(op) {
var connectionId = op.connectionId;
var opId = op.opid || "";
var client = op.client || op.client_s || "";
var isMongos = op.client_s ? "S" : "";
var active = op.active ? "Active" : "Idle";
var time = op.secs_running || "";
var waitingForLock = op.waitingForLock ? "Yes" : "No";
var opName = op.op || "";
var plan = op.planSummary || "";
var ns = op.ns || "";
rows.push([
connectionId,
opId,
client,
isMongos,
active,
time,
waitingForLock,
opName,
plan,
ns
]);
});
printTable(headers, rows);
};
shellHelper.kill = function(opId) {
return db.killOp(opId);
}
// End PS Hack
//----------------------------------------------------------------------------
// Randomise API
//----------------------------------------------------------------------------
function randomWord(length, words, seed){
/* Return a random word(s).
length: length of each word (default is 5 letters).
words: number of words (default is 1 word).
seed: a word to be planted randomly amongst the word(s), good for search. (optional)
*/
words = typeof words !== 'undefined' ? words : 1;
length = typeof length !== 'undefined' ? length : 5;
var seedOn = typeof seed !== 'undefined';
var text = "";
var possible ="abcdefghijklmnopqrstuvwxyz";
var firstword = true;
for (var j=0; j < words; j++){
var word = "";
for (var i=0; i < length; i++){
word += possible.charAt(Random.randInt(possible.length));
}
/* Plant a seeded word */
if (seedOn == true){
var randomBool = Random.rand() >= 0.8;
if (randomBool == true){
if (firstword == true){ text = seed; firstword = false;}
else {text += " " + seed;}
seedOn = false;
}
}
if (firstword == true){ text = word; firstword = false;}
else {text += " " + word;}
}
return text;
};
function randomNumber(max){
/* Return a random number
max: highest random number (default is 100).
*/
max = typeof max !== 'undefined' ? max : 100;
return Random.randInt(max);
};
function randomDate(start, end){
/* Return a random date between start and end values.
start: Date(), default 2 years ago.
end: Date(), default today.
*/
end = typeof end !== 'undefined' ? end : new Date();
if (typeof start === 'undefined') {
start = new Date(end.getTime());
start.setYear(start.getFullYear() - 2);
}
return new Date(start.getTime() + Random.randInt(end.getTime() - start.getTime()));
};
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