The mongo shell

What it is

The mongo shell is just another client application. There, I said it.

It is not a special node within a MongoDB replica set or cluster. It is an application that connects and communicates with the mongod (or mongos) nodes with the same MongoDB Wire protocol TCP traffic that any other application could. If it was a black box rather than being open source, you could reverse-engineer it even without super-dooper elite hacker skills. It has no special sauce that gives it elevated privilege or better performance compared to what any MongoDB driver-using application can have.

What is unique about the mongo shell compared to the thousands of other MongoDB-connected applications you might install on your computer is that is an interactive CLI (command line interpreter) a.k.a. REPL (read-evaluate-print loop). It's not the only MongoDB CLI that has ever existed, but it is the only popular one to date.

Why use it

Having an interactive shell is a practical requirement for doing administration, so basically everyone will use it for that reason at least. Most people will also use it for learning. The MongoDB documentation uses mongo shell syntax all over too.

Connection examples

On the unix (or windows) shell you can specify connection options, and optionally input (a script file to run or a single string to run).

If you are not already familiar with the command-line arguments the mongo shell accepts please expand the following section.

Expand me to see mongo shell connection examples

Internals

Although it is made with C++ the language that this CLI interprets is Javascript. Apart from a very small number of legacy, imperative-style command expressions such as "show databases", "exit", etc. everything is Javascript.

Shell parsing

The syntax exception: Legacy MySQL-like commands

use <database_name>
show databases
show collections

Apart from "use database_name", which sets the database namespace the client sends in the Wire Protocol requests, these legacy command expressions are all translated internally to a Javascript function. For example "show collections" is really:

//The real code behind "show collections":
if (what == "collections" || what == "tables") {
    db.getCollectionNames().forEach(function(x) {
        print(x);
    });
    return "";
}

To see how these parsing exceptions are achieved you can look at the shellHelper.show function in mongo/shell/utils.js.

Plain Javascript

The mongo shell will process javascript with referring to any database context, if you want to. Below are some client side-only expressions and functions, pretty much identical to those you can do in the native Javascript supported in web browsers etc.

var x = 1;
for (i = 0; i < 100; i++) { print(i); }
function max(a, b) { return a > b ? a : b; }

Javascript that acts with database connection objects

Unless you use the --no-db argument there will be the "db" special global object which can be used to send db command messages over the connection to a MongoDB server.

use <database_name>  //set current database namespace
db.version()    //database namespace doesn't affect this particular command
//Because I did not capture the result into a variable (i.e. I didn't put "var version_result = …" at the front)
//  the shell will capture the return value from db.getVersion() and auto-print it here
3.4.4
db.serverStatus()    ///database namespace doesn't affect this particular command
//As before, the return value from the statement will be auto-printed.
db.serverStatus()
{
    "host" : "myhost.mydomain",
    "version" : "3.4.4",
    "process" : "mongod",
    "pid" : NumberLong(2175),
    ...
    ...
    "ok" : 1
}

var cursor = db.<collection_name>.find({"customer_id": 10034});     //this command is affected by the database namespace
while (cursor.hasNext()) {
  var doc = cursor.next();
  printjson(doc);
}
...

In the example above:

  1. <database_name> is set as the db scope. This will go in command objects put into MongoDB Wire protocol messages sent from here. It won't be changed unless there is another "use xxxxx" statement or something that implies it, like a db.getSiblingDB(...) function.
  2. db.getVersion() will create a buildinfo command as BSON object. Through javascript-interpreter-to-C++-code boundary and then the C++ driver library that is put that in wire protocol message message and send it the db server. The response travels those layers in reverse, finally ending with the buildinfo result in Javascript object, from which the version property is picked and printed.
  3. db.serverStatus() is a helper function that executes _db.adminCommand({serverStatus: 1})) instead. I.e. this time the BSON object being packed and set is {serverStatus: 1} compared to {hostinfo: 1}. At the return the whole object (rather than just one scalar value property) is pretty-printed onto the terminal output.
  4. A similar pattern at first to the last two commands, with a {find: "database_name.collection_name"} BSON object being sent first. The result will contain the found docs, at least if they number 100 or less and fit within the max wire protocol message size. In that simple case it is one request, one response, end. But in the case not all of the documents are delived in one go the result will also contain a (cursor) "exhaust": false value and cursor id value. The driver automatically continues fetching more results from server-side (assuming you bother to iterate the fetched first batch to its end) with a different type of command – the getMore command (or in the legacy way, an OP_GET_MORE wire protocol message).

Ever-present db namespace

The sent commands always includes a database namespace. You can change it at will ("use another_db_name") so it is variable, but it can't be empty/null. Default is "test".

Some commands don't logically require a db namespace – eg. isMaster, addShard, replSetGetStatus – but they won't work unless it is set to "admin". Many a time I've had those fail until I typed "use admin" and tried again. Some like isMaster you don't notice because you're probably never call it except by the a shell helper function (db.isMaster()) that sets it.

Crystal ball gazing: Having said all this it isn't out the question that what is unnecessary will be removed in the future. The OP_MSG message format in particular doesn't require or even permit a db namespace in the network fields, so once older messages formats stop being supported some rationalization is possible. It will be interesting to see if the code base can handle having this trimmed out.

Explicit db connection objects

You don't have to use the "db" global var if you don't want to. You can manually create other live MongoDB connections objects with connect(<conn_uri>), or new Mongo(<conn_uri>) and give those whatever variable name you like. It would be an untypical way to use the mongo shell however.

Recap

To recap the mongo shell:

  • Uses the MongoDB wire protocol to communicate with MongoDB servers the same as any application
  • It is C++ internally
  • Makes use of a javascript engine library and "readline"-style line editor library to provide a live Javascript command line interpreter / REPL.
  • It doesn't handle the wire protocol 'raw' or control TCP primitives itself. It uses the standard C++ MongoDB client driver for that.
  • Can be used to run Javascript code for the sake of Javascript alone, but the purpose is communicate with the database
  • There is one "db" MongoDB connection object created which represents the connection to the standalone mongod or replicaset of mongod nodes or mongos host you specified with the --host argument when you began the shell.
  • The behind-the-scenes flow every time you execute a db.XXX() command:
    1. You create documents as Javascript objects, and execute Javascript functions in the interpreter.
    2. The mongo shell converts the Javascript objects to BSON, and the functions to known MongoDB server commands, which are also serialized in a BSON format. These include the argument values (if any), puts it into the OP_MSG request (or legacy OP_QUERY or the v3.2 experimental OP_COMMAND format requests) and sends it over the network
    3. The server responds with a reply in BSON
    4. The mongo shell shunts the BSON into the TCP payload to C++ object, then cast/marshalled to a javascript object through the Javscript engine.
    5. The converted-to-Javascript-binary-format result is assigned into a javascript variable if you set one, or auto-printed into the shell terminal if you did not.

Q. "But what about server-side Javascript? That's what MongoDB uses right?"

No, that's not what MongoDB uses. Well it can interpret and execute some javascript functions you send to it, but they're only for running within:

  • a MapReduce command, or
  • (Superseded by $expr in v3.6; removed v4.2): if using a $where operator in a find command, or
  • (Deprecated v3.4; removed v4.2): as the "reduce", "keyf" or "finalize" arguments in a group command.

These functions are javascript, but they get packed inside a special BSON datatype (just a string with a different enum value for type) to be sent to the server and the mongod is the only program I know that has ever been programmed to unpack that format. Being javascript it is a lot slower than the native C++ processing in the mongod process.