Server-side Code Execution

Introduction

Mongo supports the execution of code inside the database process.

$where Clauses and Functions in Queries

In addition to the regular document-style query specification for find() operations, you can also express the query either as a string containing a SQL-style WHERE predicate clause, or a full JavaScript function.

When using this mode of query, the database will call your function, or evaluate your predicate clause, for each object in the collection.

In the case of the string, you must represent the object as "this" (see example below). In the case of a full JavaScript function, you use the normal JavaScript function syntax.

The following four statements in mongo - The Interactive Shell are equivalent:

db.myCollection.find( { a : { $gt: 3 } } );
db.myCollection.find( { $where: "this.a > 3" });
db.myCollection.find( "this.a > 3" );
db.myCollection.find( { $where: function() { return this.a > 3;}});

The first statement is the preferred form. It will be at least slightly faster to execute because the query optimizer can easily interpret that query and choose an index to use.

You may mix data-style find conditions and a function. This can be advantageous for performance because the data-style expression will be evaluated first, and if not matched, no further evaluation is required. Additionally, the database can then consider using an index for that condition's field. To mix forms, pass your evaluation function as the $where field of the query object. For example:

db.myCollection.find( { active: true, $where: function() { return obj.credits - obj.debits < 0; } } );

You may mix data-style find conditions and a function. This can be advantageous for performance because the data-style expression will be evaluated first, and if not matched, no further evaluation is required. Additionally, the database can then consider using an index for that condition's field. For example:

db.myCollection.find( { active: true, $where: "this.credits - this.debits < 0" } );

Restrictions

Do not write to the collection being inspected from the $where expression.

Using db.eval()

db.eval() is used to evaluate a function (written in JavaScript) at the database server.
This is useful if you need to touch a lot of data lightly. In that scenario, network transfer of the data could be a bottleneck.

db.eval() returns the return value of the function that was invoked at the server. If invocation fails an exception is thrown.

For a trivial example, we can get the server to add 3 to 3:

> db.eval( function() { return 3+3; } );
6
>

Let's consider an example where we wish to erase a given field, foo, in every single document in a collection. A naive client-side approach would be something like

function my_erase() {
  db.things.find().forEach( function(obj) {
                       delete obj.foo;
                       db.things.save(obj);
                     } );
}

my_erase();

Calling my_erase() on the client will require the entire contents of the collection to be transmitted from server to client and back again.

Instead, we can pass the function to eval(), and it will be called in the runtime environment of the server. On the server, the db variable is set to the current database:

db.eval(my_erase);

Examples

> myfunc = function(x){ return x; };

> db.eval( myfunc, {k:"asdf"} );
{ k : "asdf"  }

> db.eval( myfunc, "asdf" );
"asdf"

> db.eval( function(x){ return x; }, 2 );
2.0

If an error occurs on the evaluation (say, a null pointer exception at the server), an exception will be thrown of the form:

{ dbEvalException: { errno : -3.0 , errmsg : "invoke failed" , ok : 0.0  } }

Example of using eval() to do equivalent of the Mongo count() function:

function mycount(collection) {
  return db.eval( function(){return db[collection].find({},{_id:ObjId()}).length();} );
}

Example of using db.eval() for doing an atomic increment, plus some calculations:

function inc( name , howMuch ){
    return db.eval(
        function(){
            var t = db.things.findOne( { name : name } );
            t = t || { name : name , num : 0 , total : 0 , avg : 0 };
            t.num++;
            t.total += howMuch;
            t.avg = t.total / t.num;
            db.things.save( t );
            return t;
        }
    );
}

db.things.remove( {} );
print( tojson( inc( "eliot" , 2 )) );
print( tojson( inc( "eliot" , 3 )) );

Storing functions server-side

in version 1.1.1 and above

There is a special system collection called system.js that can store JavaScript function to be re-used. To store a function, you would do:

db.system.js.save( { _id : "foo" , value : function( x , y ){ return x + y; } } );

_id is the name of the function, and is unique per database.

Once you do that, you can use foo from any JavaScript context (db.eval, $where, map/reduce)

See http://github.com/mongodb/mongo/tree/master/jstests/storefunc.js for a full example

Map/Reduce

MongoDB supports Javascript-based map/reduce operations on the server. See the map/reduce documentation for more information.

Notes on Concurrency

eval() blocks the entire mongod process while running. Thus, its operations are atomic but prevent other operations from processing.

When more concurrency is needed consider using map/reduce instead of eval().


Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

Comments (10)

IF YOU HAVE A QUESTION, POST IT TO THE USER GROUP.

These pages are fine for comments, but for questions, your best bet will always be the MongoDB User Group.

 
  1. Oct 22

    Anonymous says:

    Does the example above need to replace save w/insert instead? db.system.js.in...

    Does the example above need to replace save w/insert instead?

    db.system.js.insert( { _id : "foo" , value : function( x, y ) { return x + y; } } );
    
    1. Oct 22

      Kristina Chodorow says:

      If there is already a document where _id is "foo", then save will replace the ex...

      If there is already a document where _id is "foo", then save will replace the existing document with this new one. If we used insert in such a case, the database would give a "duplicate key" error, because you can't have two documents in a collection with the same _id.

  2. Dec 10

    Anonymous says:

    doing an atomic increment is it really atomic? Is there a way to make such ope...

    doing an atomic increment

    is it really atomic?

    Is there a way to make such operation really atomic or transaction-style?

    1. Dec 11

      Eliot Horowitz says:

      Yes - it is really atomic. It locks the whole db while running. It doesn't supp...

      Yes - it is really atomic. It locks the whole db while running.
      It doesn't support rollback or anything like that, so you'd have to do that yourself.

  3. Dec 17

    Anonymous says:

    eval() blocks the entire mongod process while running. Thus, its operations are ...

    eval() blocks the entire mongod process while running. Thus, its operations are atomic but prevent other operations from processing.

    I've got a couple of questions about this as well. First off, the blocking bit. I did a little test

     
    > db.system.js.save({_id:"foo", value: function() { for (i = 0; i < 100000; i++) { print(i); }}});
    > db.system.js.findOne().value();
    

    does not seem to be blocking my db. So my assumption is that blocking only occurs when using the eval function. Is this correct?

    Than about the atomicy: it blocks the entire database, but what happens if you write a function that is supposed to update 100 documents, but it fails after 10. Will those 10 be updated? I take it from Eliot's last comment that the answer will be "no", but I'd like to be sure.

    Groeten,

    Friso

    1. Dec 17

      Anonymous says:

      doh, didn't mean "those 10" but "the other 90" Groeten, Friso

      doh, didn't mean "those 10" but "the other 90"

      Groeten,

      Friso

      1. Dec 18

        Eliot Horowitz says:

        you are correct. the first 10 will go through, the rest will not

        you are correct. the first 10 will go through, the rest will not

  4. Jan 28

    Anonymous says:

    can you confirm that the java driver api support the $where clause construct? t...

    can you confirm that the java driver api support the $where clause construct?

    thanks.

  5. Feb 04

    Anonymous says:

    Consider the following statements: 1: db.mycoll.find({name : 'Bill'}) 2: db.myc...

    Consider the following statements:

    1: db.mycoll.find({name : 'Bill'})
    2: db.mycoll.find({$where : function() {return this.name == 'Bill';}})

    Does Mongo uses indexes for the property name in the 2nd case?

    Thanks
    Matteo

    1. Feb 04

      Mike Dirolf says:

      no - $where clauses won't use indexes.

      no - $where clauses won't use indexes.

Add Comment