Skip to content

Instantly share code, notes, and snippets.

@rogerbinns
Created March 8, 2013 22:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rogerbinns/5120508 to your computer and use it in GitHub Desktop.
Save rogerbinns/5120508 to your computer and use it in GitHub Desktop.
mongo hacker with display hook example
/*
*
* Mongo Hacker
* MongoDB Shell Enhancements for Hackers
*
* Tyler J. Brock - 2013
*
* http://tylerbrock.github.com/mongo-hacker
*
*/
__ansi = {
csi: String.fromCharCode(0x1B) + '[',
reset: '0',
text_prop: 'm',
foreground: '3',
bright: '1',
underline: '4',
colors: {
red: '1',
green: '2',
yellow: '3',
blue: '4',
magenta: '5',
cyan: '6'
}
};
if (_isWindows()) {
print("\nSorry! MongoDB Shell Enhancements for Hackers isn't compatible with Windows.\n");
}
setVerboseShell(true);
setIndexParanoia(true);
DBQuery.prototype._prettyShell = true
__indent = " ";
// Type of function for legacy UUID objects (BinData with subtype = 3) rendering
// Values: "java", "c#", and "default"
uuidType = "java";
function setIndexParanoia( value ) {
if( value === undefined ) value = true;
_indexParanoia = value;
}
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, 'green'), Array(numSpaces).join(" "), "-", help);
}
}
}
};
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 ) {
return controlCode(properties) + String(string) + controlCode();
}
function colorize( string, color, bright, underline ) {
var params = [];
var code = __ansi.foreground + __ansi.colors[color];
params.push(code);
if ( bright === true ) params.push(__ansi.bright);
if ( underline === true ) params.push(__ansi.underline);
return applyColorCode( string, params );
}
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;
}
};
ObjectId.prototype.toString = function() {
return this.str;
};
ObjectId.prototype.tojson = function(indent, nolint) {
return tojson(this);
};
DBCollection.prototype.filter = function( filter ) {
return new DBQuery( this._mongo, this._db, this, this._fullName, this._massageObject( filter ) );
};
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] );
}
}
};
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", "red", true);
} else {
info += colorize(time + "ms", "green", true);
}
print(info);
}
};
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);
};
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.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");
};
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");
};
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");
};
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");
};
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();
}
Date.prototype.tojson = function() {
var UTC = Date.printAsUTC ? 'UTC' : '';
var year = this['get'+UTC+'FullYear']().zeroPad(4);
var month = (this['get'+UTC+'Month']() + 1).zeroPad(2);
var date = this['get'+UTC+'Date']().zeroPad(2);
var hour = this['get'+UTC+'Hours']().zeroPad(2);
var minute = this['get'+UTC+'Minutes']().zeroPad(2);
var sec = this['get'+UTC+'Seconds']().zeroPad(2);
if (this['get'+UTC+'Milliseconds']())
sec += '.' + this['get'+UTC+'Milliseconds']().zeroPad(3);
var ofs = 'Z';
if (!Date.printAsUTC) {
var ofsmin = this.getTimezoneOffset();
if (ofsmin !== 0){
ofs = ofsmin > 0 ? '-' : '+'; // This is correct
ofs += (ofsmin/60).zeroPad(2);
ofs += (ofsmin%60).zeroPad(2);
}
}
var isodate = colorize('"' + [year, month, date].join('-') + 'T' + hour +':' + minute + ':' + sec + ofs + '"', "cyan");
return 'ISODate(' + isodate + ')';
};
Array.tojson = function( a , indent , nolint ){
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 );
if ( i < a.length - 1 ){
s += "," + lineEnding;
}
}
if ( a.length === 0 ) {
s += indent;
}
indent = indent.substring(__indent.length);
s += lineEnding+indent+"]";
return s;
};
NumberLong.prototype.tojson = function() {
return 'NumberLong(' + colorize('"' + this.toString().match(/-?\d+/)[0] + '"', "red") + ')';
};
NumberInt.prototype.tojson = function() {
return 'NumberInt(' + colorize('"' + this.toString().match(/-?\d+/)[0] + '"', "red") + ')';
};
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);
},
"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 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);
}
BinData.prototype.tojson = function(indent , nolint) {
if (this.subtype() === 3) {
return 'UUID(' + colorize('"' + uuidToString(this, uuidType) + '"', "cyan") + ', ' + colorize('"' + uuidType + '"', "cyan") +')'
} else if (this.subtype() === 4) {
return 'UUID(' + colorize('"' + uuidToString(this, "default") + '"', "cyan") + ')'
} else {
return 'BinData(' + colorize(this.subtype(), "red") + ', ' + colorize('"' + this.base64() + '"', "green", true) + ')';
}
};
tojson = function( x, indent , nolint ) {
if ( x === null )
return colorize("null", "red", true);
if ( x === undefined )
return colorize("undefined", "magenta", true);
if ( x.isObjectId ) {
return 'ObjectId(' + colorize('"' + x.str + '"', "green", false, true) + ')';
}
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, "green", true);
}
case "number":
return colorize(x, "red");
case "boolean":
return colorize("" + x, "blue");
case "object": {
s = tojsonObject( x, indent , nolint );
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(), "magenta");
default:
throw "tojson can't handle type " + ( typeof x );
}
};
tojsonObject = function( x, indent, nolint ) {
var lineEnding = nolint ? " " : "\n";
var tabSpace = nolint ? "" : __indent;
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);
}
if ( x.constructor && typeof( x.constructor.tojson ) == "function" && x.constructor.tojson != tojson ) {
return x.constructor.tojson( x, indent , nolint );
}
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;
// sorted keys
var keylist=[];
for(var key in keys)
keylist.push(key);
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 hookoutput = displayHook(x, key, val, indent, nolint);
if (hookoutput === true)
s += indent + colorize("\"" + key + "\"", "yellow") + ": " + tojson( val, indent , nolint );
else if (hookoutput == false) {}
else s += hookoutput;
if (num != total) {
if(hookoutput!==false)
s += ",";
num++;
}
if(hookoutput!==false)
s += lineEnding;
}
// pop one level of indent
indent = indent.substring(__indent.length);
return s + indent + "}";
};
// 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) {
members = rs.status().members;
for(var i = 0; i<members.length; i++){
if(members[i].self === true){
rs_state = '[' + members[i].stateStr + ']';
}
};
}
var state = isMongos() ? '[mongos]' : rs_state;
return host + '(' + process + '-' + version + ')' + state + ' ' + db + '> ';
};
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", "red", true);
} else {
fetched += colorize(time + "ms", "green", true);
}
output.push(fetched);
}
if (typeof _indexParanoia !== 'undefined' && _indexParanoia) {
var explain = this.clone();
explain._ensureSpecial();
explain._query.$explain = true;
explain._limit = Math.abs(n._limit) * -1;
var result = explain.next();
var type = result.cursor;
var index_use = "Index[";
if (type == "BasicCursor") {
index_use += colorize( "none", "red", true);
} else {
index_use += colorize( result.cursor.substring(12), "green", true );
}
index_use += "]";
output.push(index_use);
}
if ( this.hasNext() ) {
___it___ = this;
output.push("More[" + colorize("true", "green", true) + "]");
}
else {
___it___ = null;
output.push("More[" + colorize("false", "red", true) + "]");
}
print(output.join(" -- "));
}
catch ( e ){
print( e );
}
};
// how we signify values changed for display from original
changed_clr=function(s) {
return colorize(s, "cyan");
}
// Return true to display the key: val as normal, false to not display
// it and anything else to provide your own display. See near the end
// of tojsonObject for how a key:val is normally displayed.
_date_fields=["slm_recvtime", "time", "start"];
displayHook=function(o, key, val, indent, nolint) {
// uninteresting values
if (key==="headset" && !val) return false;
if (key==="campaign" && val==="none") return false;
// reformat from list to comma separated string
if (key==="slm_geoip") return indent+colorize("\"" + key + "\"", "yellow") + ": " +
changed_clr(val.join(", "));
// wxh format
if (key === "resolution") return indent+colorize("\"" + key + "\"", "yellow") + ": " +
changed_clr(val.join(" x "));
// Show various dates as local values
for(var i=0; i<_date_fields.length; i++) {
if(key===_date_fields[i]) return indent+colorize("\"" + key + "\"", "yellow") + ": "
+ tojson( val, indent , nolint ) + " # "+changed_clr(new Date(val*1000).toLocaleString());
}
// display everything else
return true;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment