Implementing server-side scripts

In this recipe, we will see how to write server-stored JavaScript that is 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. The function for demo purpose is simple; we will add two numbers. There are two parts to this recipe. First, we'll see how to load the scripts from the collections on the client-side JavaScript shell and then, 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, need to be careful about what functions are defined. Since the launch of 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

Refer to the Single node installation of MongoDB recipe in Chapter 1, Installing and Starting the MongoDB Server, and start a single instance of Mongo. This 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 db.system.js collection 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. Invoke add and see if it works:
    > add(1, 2)
    
  4. We will use the add function and execute this on the server side instead. Execute the following commands from the shell:
    > use test
    > db.eval('return add(1, 2)')
    
  5. Execute the following commands:
    > use test1
    > db.eval('return add(1, 2)')
    

How it works…

The system.js collection is a plain old collection just like any other collection. 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 using insert or upsert.

The secret lies in the loadServerScripts method. The method executes the following line of code:

this.system.js.find().forEach(function(u){eval(u._id + " = " + u.value);});

This evaluates JavaScript using the eval function, and it 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 system.js collection.

For example, if the { _id : 'add', value : function(num1, num2) {return num1 + num2}} document is present in the system.js collection, the function given in the value field of the document will be assigned to the variable named as add in the current shell. The add value 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 loadServerScripts method just instantiates some variables in the current shell and makes those functions available for invocation. It is the JavaScript interpreter of the shell that executes these functions and not the server. The system.js collection is defined in the scope of the database, but once loaded, these are 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, 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 Setting up users in MongoDB recipe in Chapter 4, Administration. As we saw earlier, the loadServerScripts function reads data from the system.js collection and thus, 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 the server-side script. as against executing in the connected shell. 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 the 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 nolock parameter is used. For instance, the preceding add function can be invoked as follows, instead of calling db.eval, and will achieve the same results. Additionally, we provided the nolock field to instruct the server not to acquire the global lock before evaluating the function. If the function performs any read/write operations on the collection, it will acquire locks as usual, and this field doesn't affect this behavior.

> 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 four roles, namely, userAdminAnyDatabase, dbAdminAnyDatabase, readWriteAnyDatabase, and clusterAdmin (on the admin database), to successfully invoke the db.eval function.

Programming languages do provide a way for the invocation of such server-side scripts as well using the eval function. For instance, in Java API, the com.mongodb.DB class has the eval method 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 can affect the performance of the server badly.

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

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