IntroductionMongo supports the execution of code inside the database process. $where Clauses and Functions in QueriesIn 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" } );
RestrictionsDo 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. 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
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/ReduceMongoDB supports Javascript-based map/reduce operations on the server. See the map/reduce documentation for more information. Notes on Concurrencyeval() 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(). |

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.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; } } );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.
Dec 10
Anonymous says:
doing an atomic increment is it really atomic? Is there a way to make such ope...is it really atomic?
Is there a way to make such operation really atomic or transaction-style?
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.
Dec 17
Anonymous says:
eval() blocks the entire mongod process while running. Thus, its operations are ...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
Dec 17
Anonymous says:
doh, didn't mean "those 10" but "the other 90" Groeten, Frisodoh, didn't mean "those 10" but "the other 90"
Groeten,
Friso
Dec 18
Eliot Horowitz says:
you are correct. the first 10 will go through, the rest will notyou are correct. the first 10 will go through, the rest will not
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.
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
Feb 04
Mike Dirolf says:
no - $where clauses won't use indexes.no - $where clauses won't use indexes.
Add Comment