4

I'm kind of confused on how to use closures in mongodb shell.

I want to create a function that i can use exclusively during development to quickly look up a document by a part of it's _id.

The function should return a $where selector that does the necessary matching.
I wanted to write it like this:

var id = function(pattern, selector) {
    return Object.extend({
        $where: function() {return (this._id + "").indexOf(pattern) != -1;}
    }, selector);
};

But when i try it i get the following error:

db.mycollection.find(id("ab1"));
error: {
        "$err" : "JavaScript execution failed: ReferenceError: pattern is not defined near ').indexOf(pattern) ' ",
        "code" : 16722
}

Invoking $where manually does seem to work oddly enough:

id("ell").$where.call({_id: "hello"}); // true

I could only think of this as a solution to make it work, but this is obviously terrible.
To clarify: This method with new Function works fine, but i don't like it as the above method should work as well.

var id = function(pattern, selector){
    return Object.extend({
        $where: new Function("return (this._id + '').indexOf('" + pattern + "') != -1;")
    }, selector);
};
  • Why is my closure not working, do closures work in mongodb shell ?
  • Bonus question: Can i register this function somewhere in a user profile to load automatically ?
Willem D'Haeseleer
  • 19,661
  • 9
  • 66
  • 99
  • Hmm I am unsure why you think that `find()` expects a closure? In order to run the closure the `find()` function must expect one to be inputted. As for the other question, what do you define as a user profile? Also why would you want to save a `$where` clause globally? – Sammaye Jul 01 '13 at 13:27
  • The problem is not with `find()` but with `$where`. I would have expected for the `pattern` argument from the `id` function to close over the returned `$where` function. But when `$where` is invoked `pattern` is no longer defined. And I would like to have this `id` function available automatically so i don't have to define it each time i start a new shell. I hope this clarifies the problem a bit. – Willem D'Haeseleer Jul 01 '13 at 13:35
  • Ok I think I know why now, and I beleive it is becuase of how JS works. If you were to define your $where value as a string instead of a `Function` I have a funny feeling it will work. Basically I believe it is evaling your `pattern` var outside of the scope of your client function due to how `Function` works. As for getting this to run without defining it, if you are on Linux you can just add it to the `.mongorc.js` and it will do exactly that – Sammaye Jul 01 '13 at 13:49
  • I don't know MongoDB, but your example [looks correct](http://jsfiddle.net/Mjfsj/). This is either a shortcoming in Mogno's JS engine, or it's a quirk of how `$where` is invoked by the system. Could you try using making an example with `$where: function() { print(pattern) }` and *explicitly invoking it* with `id("abc").$where()`? If explicitly invoking `$where` works, then it's probably an oddity of how `$where` is run when performing a query. – apsillers Jul 01 '13 at 13:52
  • @apsillers i added your test to my question, invoking `$where` seems to work. – Willem D'Haeseleer Jul 01 '13 at 14:01
  • That $where will not run in the database but instead in the client side through the mongo shell, the js fiddle is wrong since $where is a database operator which must be evaluated within MongoDBs JS envo – Sammaye Jul 01 '13 at 14:06
  • @Sammaye are you sure `$where` does not run on the server, outside the shell ? It's the only way i can now think off how it would lose the closure. If it stringified the function and sends it to the server i could understand the closure gets lost. – Willem D'Haeseleer Jul 01 '13 at 14:15
  • The one provided in the comment I referenced did not – Sammaye Jul 01 '13 at 14:54

1 Answers1

2

It appears that the MongoDB shell serializes (in this case, string-ifies) $where function objects in order to send them to the server.

A function in JavaScript is much more than its functional code -- it's part of a closure, which includes both the function code itself and the function's variable referencing environment for accessing non-local variables. After the function is serialized, it gets unserialized and executed with different referencing environment. When the function tries to reference pattern, it's asking for pattern in a totally different referencing environment, which does not have a pattern variable.

That's why your literal string function works. That function contains the actual value of pattern, not the variable identifier pattern.

See this MongoDB bug, which explains:

That would work if the function was being evaluated locally, but the closure isn't sent over automatically.

In babble it used to be, but not in the shell.

This suggests that your code may work outside of the shell, but I don't know enough about Mongo internals to say for sure.

Community
  • 1
  • 1
apsillers
  • 112,806
  • 17
  • 235
  • 239