0

Long story short, I have in my MongoDB database a collection of posts and with node.js, express and mongoose, I'm trying to find all the documents using the $where method.

So I try this and it works perfectly, all the results, if their name includes Jo get returned.

app.get("/posts", (req, res) => {
  const nameSearch = "Jo";
  Post.find({
    $where: function () {
      return this.name.includes("Jo");
    },
  }).then((data) => res.send(data));
});

But if I do something like this, it throws me an error

app.get("/posts", (req, res) => {
  const nameSearch = "Jo";
  Post.find({
    $where: function () {
      return this.name.includes(nameSearch);
    },
  }).then((data) => res.send(data));
});

Also, if I do this, it says that $where requires a string or a function.

function runThis(post) {
      return post.name.includes("Jo");
}
app.get("/posts", (req, res) => {
  Post.find({
    $where: runThis(this),
  }).then((data) => res.send(data));
});

Even weirder, I think, is that if I change the function inside the $where to an arrow function, no matter what I put in the function, it will return all the results

app.get("/posts", (req, res) => {
  const nameSearch = "Jo";
  Post.find({
    $where: () => {
      return this.name.includes("Jo");
    },
  }).then((data) => res.send(data));
});
Lko 3001
  • 3
  • 2
  • 1
    I'm pretty sure that this function runs on the mongo server and it doesn't have access to your local variables. Mongodb uses JavaScript as a query language. – Konrad Oct 10 '22 at 16:27
  • `this` works different in arrow functions – Konrad Oct 10 '22 at 16:28
  • @KonradLinkowski I didnt know that `this` works different in arrow functions, but even if I try to return a simple string, not from a function and without using `this`, it still returns all the results. Anyway do you know if there's a way for me to use a variable inside the function? – Lko 3001 Oct 10 '22 at 16:30
  • @KonradLinkowski - The immediate function `this` is used in isn't an arrow function. – T.J. Crowder Oct 10 '22 at 16:32
  • @T.J.Crowder I meant this part of the code `() => { return this.name.includes("Jo"); }` – Konrad Oct 10 '22 at 16:34
  • *if I try to return a simple string, not from a function and without using this, it still returns all the results* - can you should an example? – Konrad Oct 10 '22 at 16:42
  • @KonradLinkowski ```js Prof.find({ $where: () => { return "something"; }, }).then((data) => res.send(data)); ``` It works completely fine, even if I return false, or true, or anything else – Lko 3001 Oct 10 '22 at 16:46
  • This `$where` function should return boolean. So if you return any string which is truthy it will return all items. – Konrad Oct 10 '22 at 16:47
  • 1
    @KonradLinkowski i didnt think about that, but even if I return false, undefined, null or '', it still returns every result – Lko 3001 Oct 10 '22 at 16:49
  • 1
    Surely there's a query operator that looks for a substring in a field? Having to write a function for this seems wrong... – T.J. Crowder Oct 10 '22 at 16:50
  • @T.J.Crowder [that's the only one I found](https://www.mongodb.com/docs/manual/reference/operator/query/), I know there's $text, but it just searches for part of the word, for example if the name I wanted to find was `Jonathan`, if I write `Jo`, it doesnt return. If I write the entire name or `Jonatha`, it returns. – Lko 3001 Oct 10 '22 at 16:58

2 Answers2

1

Caveat: I don't use Mongoose or MongoDb.

But the documentation says the function you pass for $where isn't executed locally, it's executed on the Mongoose server. So it doesn't have access to the variable.

But a search (1, 2) suggests that the usual way to find a substring within a field is to use $regex. If so, and you have a user-entered string (which might contain characters that have special meaning to $regex), you'll want to escape them. So for instance:

app.get("/posts", (req, res) => {
    const nameSearch = "Jo";
    Post.find({
        name: {
            $regex: new RegExp(escapeRegex(nameSearch)),
        }),
    }).then((data) => res.send(data));
});

...where escapeRegex is from that linked question's answers. (Apparently JavaScript regular expression objects are supported, as well as strings using PCRE syntax instead.)

If for some reason you can't do that, you can also pass a string for $where, so you could create the string dynamically:

app.get("/posts", (req, res) => {
    const nameSearch = "Jo";
    Post.find({
        $where: `this.name.includes(${JSON.stringify(nameSearch)})`,
        //      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    }).then((data) => res.send(data));
});

Normally, writing JavaScript code in strings is a bad idea (even though Mongoose has to convert whatever function you give it to a string to send it to the server anyway), but if you need to do this kind of substitution...

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

Few things from the documentation

$where docs

About scope

Starting in MongoDB 4.4, $where no longer supports the deprecated BSON type JavaScript code with scope (BSON type 15)

You should avoid using $where

$where evaluates JavaScript and cannot take advantage of indexes. Therefore, query performance improves when you express your query using the standard MongoDB operators (e.g., $gt , $in ).

In general, you should use $where only when you cannot express your query using another operator. If you must use $where , try to include at least one other standard query operator to filter the result set. Using $where alone requires a collection scan.

About variables

I have no idea, but you can try using $let

Konrad
  • 21,590
  • 4
  • 28
  • 64