Implementing server-side scripts

In this recipe, we will see how to write server stored JavaScript similar to stored procedures in relational databases. This is a common use case where other pieces of code require access to these common functions and we have them in one central place. To demonstrate server-side scripts, the function will simply add two numbers.

There are two parts to this recipe. First, we see how to load the scripts from the collections on the client-side JavaScript shell and secondly, we will see how to execute these functions on the server.

Note

The documentation specifically mentions that it is not recommended to use server-side scripts. Security is one concern though if the data is not properly audited and hence we need to be careful with what functions are defined. Since Mongo 2.4, the server-side JavaScript engine is V8, which can execute multiple threads in parallel as opposed to the engine prior to version 2.4 of Mongo, which executes only one thread at a time.

Getting ready

Look at the recipe Installing single node MongoDB in Chapter 1, Installing and Starting the Server and start a single instance of Mongo. That is the only prerequisite for this recipe. Start a mongo shell and connect to the started server.

How to do it…

  1. Create a new function called add and save it to the collection db.system.js as follows. The current database should be test:
    > use test
    > db.system.js.save({ _id : 'add', value : function(num1, num2) {return num1 + num2}})
    
  2. Now that this function is defined, load all the functions as follows:
    > db.loadServerScripts()
    
  3. Now, invoke add and see if it works:
    > add(1, 2)
    
  4. We will now use this function and execute this on the server-side instead: Execute the following from the shell:
    > use test
    > db.eval('return add(1, 2)')
    
  5. Execute the following steps (you can execute the preceding command):
    > use test1
    > db.eval('return add(1, 2)')
    

How it works…

The collection system.js is a special MongoDB collection used to store JavaScript code. We add a new server-side JavaScript using the save function in this collection. The save function is just a convenience function that inserts the document if it is not present or updates an existing one. The objective is to add a new document to this collection which you may add even using insert or upsert.

The secret lies in the method loadServerScripts. Let's look at the code of this method: this.system.js.find().forEach(function(u){eval(u._id + " = " + u.value);});

It evaluates a JavaScript using the eval function and assigns the function defined in the value attribute of the document to a variable named with the name given in the _id field of the document for each document present in the collection system.js.

For example, if the following document is present in the collection system.js, { _id : 'add', value : function(num1, num2) {return num1 + num2}}, then the function given in the value field of the document will be assigned to the variable named as add in the current shell. The value add is given in the _id field of the document.

These scripts do not really execute on the server but their definition is stored on the server in a collection. The JavaScript method loadServerScripts, just instantiates some variables in the current shell and make those functions available for invocation. It is the JavaScript interpreter of the shell that executes these functions and not the server. The collection system.js is defined in the scope of the database. Once loaded, these act as JavaScript functions defined in the shell and hence the functions are available throughout the scope of the shell irrespective of the database currently active.

As far as security is concerned, if the shell is connected to the server with security enabled, then the user invoking loadServerScripts must have privileges to read the collections in the database. For more details on enabling security and various roles a user can have, refer to the recipe Setting up users in Mongo in Chapter 4, Administration. As we saw earlier, the function loadServerScripts reads data from the collection system.js and if the user doesn't have privileges to read from the collection, the function invocation will fail. Apart from that, the functions executed from the shell after being loaded should have appropriate privileges. For instance, if a function inserts/updates in any collection, the user should have read and write privileges on that particular collection accessed from the function.

Executing scripts on the server is perhaps what one would expect to be server-side script as opposed to executing in the shell connected. In this case, the functions are evaluated on the server's JavaScript engine and the security checks are more stringent as long running functions can hold locks, having detrimental effects on the performance. The wrapper to invoke the execution of a JavaScript code on the server-side is the db.eval function accepting the code to evaluate on the server-side along with the parameters if any.

Before evaluating the function, the write operation takes a global lock; this can be skipped if the parameter nolock is used. For instance, the preceding add function can be invoked as follows instead of calling db.eval and achieving the same results. We additionally provided the nolock field to instruct the server not to acquire the global lock before evaluating the function. If this function were to perform write operations on a collection, then the nolock field is ignored.

> db.runCommand({eval: function (num1, num2) {return num1 + num2}, args:[1, 2],nolock:true})

If security is enabled on the server, the invoking user needs to have the following four roles: userAdminAnyDatabase, dbAdminAnyDatabase, readWriteAnyDatabase, and clusterAdmin (on the admin database) to successfully invoke the db.eval function.

Programming languages do provide a way for invocation of such server-side scripts using the eval function. For instance, in Java API, the class com.mongodb.DB has the method eval to invoke server-side JavaScript code. Such server-side executions are highly useful when we want to avoid unnecessary network traffic for the data and get the result to the clients. However, too much logic on the database server can quickly make things difficult to maintain and affect the performance of the server badly.

Note

As of MongoDB 3.0.3, the db.eval() method is being deprecated and it is advised that users do not rely on this method but instead use client-side scripts. See https://jira.mongodb.org/browse/SERVER-17453 for more details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset