From ae3736254c94ebee0404d1d33aaab316c7cdd860 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 10 Apr 2024 16:40:12 -0400 Subject: [PATCH] fix(populate): avoid match function filtering out `null` values in populate result Fix #14494 --- lib/helpers/populate/assignVals.js | 4 +-- test/model.populate.test.js | 53 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/helpers/populate/assignVals.js b/lib/helpers/populate/assignVals.js index eeeb6c622c9..9aff29fd538 100644 --- a/lib/helpers/populate/assignVals.js +++ b/lib/helpers/populate/assignVals.js @@ -101,8 +101,8 @@ module.exports = function assignVals(o) { valueToSet = numDocs(rawIds[i]); } else if (Array.isArray(o.match)) { valueToSet = Array.isArray(rawIds[i]) ? - rawIds[i].filter(sift(o.match[i])) : - [rawIds[i]].filter(sift(o.match[i]))[0]; + rawIds[i].filter(v => v == null || sift(o.match[i])(v)) : + [rawIds[i]].filter(v => v == null || sift(o.match[i])(v))[0]; } else { valueToSet = rawIds[i]; } diff --git a/test/model.populate.test.js b/test/model.populate.test.js index 65b3b36b2cc..a987bd6d4cc 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -10968,4 +10968,57 @@ describe('model: populate:', function() { assert.equal(res.children[0].subdoc.name, 'A'); assert.equal(res.children[1].subdoc.name, 'B'); }); + + it('avoids filtering out `null` values when applying match function (gh-14494)', async function() { + const gradeSchema = new mongoose.Schema({ + studentId: mongoose.Types.ObjectId, + classId: mongoose.Types.ObjectId, + grade: String + }); + + const Grade = db.model('Test', gradeSchema); + + const studentSchema = new mongoose.Schema({ + name: String + }); + + studentSchema.virtual('grade', { + ref: Grade, + localField: '_id', + foreignField: 'studentId', + match: (doc) => ({ + classId: doc._id + }), + justOne: true + }); + + const classSchema = new mongoose.Schema({ + name: String, + students: [studentSchema] + }); + + const Class = db.model('Test2', classSchema); + + const newClass = await Class.create({ + name: 'History', + students: [{ name: 'Henry' }, { name: 'Robert' }] + }); + + const studentRobert = newClass.students.find( + ({ name }) => name === 'Robert' + ); + + await Grade.create({ + studentId: studentRobert._id, + classId: newClass._id, + grade: 'B' + }); + + const latestClass = await Class.findOne({ name: 'History' }).populate('students.grade'); + + assert.equal(latestClass.students[0].name, 'Henry'); + assert.equal(latestClass.students[0].grade, null); + assert.equal(latestClass.students[1].name, 'Robert'); + assert.equal(latestClass.students[1].grade.grade, 'B'); + }); });