MongoDB + Mongoose > How to correctly match a set of ISODates in populate()

38 Views Asked by At

I'm pretty new to MongoDB and I encounter a weird behavior when using populate. Here are my Schemas:

let userSchema = new Mongoose.Schema({
  username: { type: String, required: true },
  log: [{ type: Mongoose.Schema.Types.ObjectId, ref: 'Exercise' }],
  count: { type: Number },
})

let exerciseSchema = new Mongoose.Schema({
  id: { type: Mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  description: { type: String, required: true },
  duration: { type: Number, required: true },
  date: { type: Date },
})

This is a typical exercise document in my database :


{"_id":{"$oid":"63a9c694b46f8d2d050f7ffc"},
"id":{"$oid":"63a9c5b7b76534298015f2f2"},
"description":"Push ups",
"duration":{"$numberInt":"59"},
"date":{"$date":{"$numberLong":"1641081600000"}},
"__v":{"$numberInt":"0"}}

Everything is working as expected, except when filtering my populate() function by dates (via the match argument).

Here is the get request I am trying to work around :

app.get('/api/users/:_id/logs', function (req, res) {
  let logId = req.params._id;
  let fromDate = req.query.from ? new Date(req.query.from) : null;
  let toDate = req.query.to ? new Date(req.query.to) : null;
  let limit = req.query.limit ? { limit: req.query.limit } : {};

  let matchQuery;

  // Create From and To date options and store in matchQuery object if needed.
  if (fromDate || toDate) {
    matchQuery = `{ date : { ${fromDate ? "$gte: " + "ISODate(\"" + fromDate.toISOString() + "\")" : ""}${toDate && fromDate ? "," : ""} ${toDate ? "$lte: " + "ISODate(\"" + toDate.toISOString() + "\")" : ""} } }`
  }

  console.log(matchQuery);

  User.find({ _id: logId }).populate({ path: 'log', select: 'description duration name date', match: matchQuery, options: limit }).select('username count _id log').exec(function (err, data) {
    if (err) return console.error(err);
    console.log(data)
    res.json(data);
  })
})

If i type in URL : http://localhost:3000/api/users/63a9c5b7b76534298015f2f2/logs, i get the JSON i'm expecting with all the exercises populated :

[{"log":[{"_id":"63a9c694b46f8d2d050f7ffc","description":"Push ups","duration":59,"date":"2022-01-02T00:00:00.000Z"},{"_id":"63a9c6abb46f8d2d050f7fff","description":"Push ups","duration":45,"date":"2022-01-03T00:00:00.000Z"}],
"_id":"63a9c5b7b76534298015f2f2",
"username":"john",
"count":2}]

But if I start adding filters in the URL like http://localhost:3000/api/users/63a9c5b7b76534298015f2f2/logs?from=2000-01-01&to=2022-02-03 (which should not filter anything and let all results appear) i get:

[{"log":[],"_id":"63a9c5b7b76534298015f2f2","username":"john","count":2}]

So it seems my match request in the populate() function is not working correctly when using date as it just filters out everything whatever the date is. A match applied to other fields works fine (e.g. duration or description).

What am I missing here ? I'm afraid I might not be using ISODate correctly but I cannot be sure. Or maybe the "greater than" and "less than" operators are not formatted correctly ?

I tried all sorts of match expressions but I have to say the documentation on populate() + match + ISODates (and dates in general) is pretty vague so far.

Edit: It seems that the problem was my original query and that what I was passing to the match option was a string which was not parsed by mongodb properly. I made the following changes to pass my populate() query as an object and it works now.

     let populateQuery = {
        path: 'log',
        select: 'description duration name date',
      }
    
      if (isMatchQuery) {
        // finding alternative solution as Date match on populate doesn't seem to work properly
        if (fromDate && toDate) {
          populateQuery.match = { date: { $gte: fromDate, $lte: toDate } }
        } else if (fromDate) {
          populateQuery.match = { date: { $gte: fromDate } }
        } else if (toDate) {
          populateQuery.match = { date: { $gte: toDate } }
        }
        //matchQuery = `${fromDate ? "$gte: " + fromDate.toISOString() : ""}${toDate && fromDate ? " , " : ""}${toDate ? "$lte: " + toDate.toISOString() : ""}`
      }
    
      if (limit != {}) {
        populateQuery.options = limit
      }

that I then place in the populate() function like that :

User.find({ _id: logId }).populate(populateQuery).select('username count _id log').exec(function())

1

There are 1 best solutions below

6
NeNaD On

Try just without ISODate() (Mongoose should parse it properly):

date: { $gte: fromDate.toISOString(), $lte: toDate.toISOSTring() }