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())
Try just without
ISODate()(Mongoose should parse it properly):