Wednesday, 28 December 2016

MongoDB Basics V - Database Commands

There are many tasks we need to do with MongoDB that are not covered in the CRUD operations, and these are all performed by issuing Commands.

The available Commands can be shown by running db.listCommands() in the mongo shell.

Commands are run by using db.runCommand(). Some are "admin" commands that need to be run against the admin Database. There is a shell helper that we can use for this: db.adminCommand().
> db.adminCommand
function (obj, extra) {
        if (this._name == "admin")
            return this.runCommand(obj, extra);
        return this.getSiblingDB("admin").runCommand(obj, extra);
    }

As you can see, it simply runs the command against the admin Database unless it is already connected to admin.

Many of the other shell helpers work as functions that call db.runCommand(). For instance, to drop a Collection the helper runs the drop command. We can compare the behavior of the two methods on a successful and unsuccessful drop:
> db.dropMe.insert({a:1})
WriteResult({ "nInserted" : 1 })
> db.dropMe.drop()
true
> db.dropMe.drop()
false

> db.dropMe.insert({a:1})
WriteResult({ "nInserted" : 1 })
> db.runCommand({drop: "dropMe"})
{ "ns" : "next.dropMe", "nIndexesWas" : 1, "ok" : 1 }
> db.runCommand({drop: "dropMe"})
{
        "ok" : 0,
        "errmsg" : "ns not found",
        "code" : 26,
        "codeName" : "NamespaceNotFound"
}


The helper returns true or false whereas db.runCommand() returns a Document containing the key "ok". If "ok" is  0 then there will be an additional field "errmsg" that contains the error message returned by MongoDB. We can look at the code for the db.collection.drop helper itself to see an example of how this can be handled in an application:
> db.collection.drop
function () {
    if (arguments.length > 0)
        throw Error("drop takes no argument");
    var ret = this._db.runCommand({drop: this.getName()});
    if (!ret.ok) {
        if (ret.errmsg == "ns not found")
            return false;
        throw _getErrorWithCode(ret, "drop failed: " + tojson(ret));
    }
    return true;
}



Internally, db.runCommand runs a query against a virtual collection called $cmd. We can do this ourselves, but it is recommended not to
> db.dropMe.insert({a:1})
WriteResult({ "nInserted" : 1 })
> db.$cmd.findOne({"drop" : "dropMe"});
{ "ns" : "next.dropMe", "nIndexesWas" : 1, "ok" : 1 }



An important command is getLastError as it used to get feedback on the previous statement that was run by the current session. The help for it under db.listCommands() shows:
getLastError:  slaveOk
  return error status of the last operation on this connection
  options:
    { fsync:true } - fsync before returning, or wait for journal commit if running with --journal
    { j:true } - wait for journal commit if running with --journal
    { w:n } - await replication to n servers (including self) before returning
    { w:'majority' } - await replication to majority of set
    { wtimeout:m} - timeout for w in m milliseconds

Note that db.runCommand is order sensitive, we need to provide the command as the first field if there are additional options used, as we can do with getLastError. We will look into some command options later.

Here we will run a small update against multiple documents and use getLastError to return the status:
> db.testColl.drop()
true
> // first insert 10 documents
> for (var i = 0; i < 10; i++) { db.testColl.insert({field1 : "a", field2: i}) }
WriteResult({ "nInserted" : 1 })
> // the WriteResult shows just the last insert
> // we can prove it by running a count against the Collection
> db.runCommand({count:"testColl"})
{ "n" : 10, "ok" : 1 }
> // "n" : 10 shows 10 documents counted
> // now we update them all
> db.testColl.update({field1:"a"},{$set:{field2: 1000}},{multi:true})
WriteResult({ "nMatched" : 10, "nUpserted" : 0, "nModified" : 10 })
> db.runCommand({"getLastError" : 1})
{
        "connectionId" : 7,
        "updatedExisting" : true,
        "n" : 10,
        "syncMillis" : 0,
        "writtenTo" : null,
        "err" : null,
        "ok" : 1
}


Our Documents are inserted by multiple insert statements in a loop, and the first writeResult shows only the result of the last statement from the loop as we did not add code to handle it better. But with runCommand we can count the Documents in the Collection and prove that there are 10 as expected.

Note that the "ok" : 1 in the Document returned with getLastError is for the db.runCommand() execution itself and does not mean that the previous statement completed ok. We can show that running an invalid statement:
> db.testColl.update({field1:"a"},{field2: 1},{multi:true})
WriteResult({
        "nMatched" : 0,
        "nUpserted" : 0,
        "nModified" : 0,
        "writeError" : {
                "code" : 9,
                "errmsg" : "multi update only works with $ operators"
        }
})
> db.runCommand({"getLastError" : 1})
{
        "connectionId" : 7,
        "err" : "multi update only works with $ operators",
        "code" : 9,
        "codeName" : "FailedToParse",
        "n" : 0,
        "ok" : 1




We will use db.runCommand a lot as we administer MongoDB and investigate the individual commands as we go.



1 comment:

  1. Thanks for sharing the basic Database Commands of Mongo DB.

    ReplyDelete