From 16bec60ea0b52aa5a55b810a2f1ef3df2a51a1df Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 1 Aug 2022 22:19:53 -0400 Subject: [PATCH] fix(model+query): handle populate with lean transform that deletes `_id` Fix #12143 --- lib/model.js | 19 +++++++++++++++++++ test/query.test.js | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/model.js b/lib/model.js index bbbf4febf43..b6633f9d2fc 100644 --- a/lib/model.js +++ b/lib/model.js @@ -4599,6 +4599,17 @@ function populate(model, docs, options, callback) { mod.options.options.sort || void 0; assignmentOpts.excludeId = excludeIdReg.test(select) || (select && select._id === 0); + // Lean transform may delete `_id`, which would cause assignment + // to fail. So delay running lean transform until _after_ + // `_assign()` + if (mod.options && + mod.options.options && + mod.options.options.lean && + mod.options.options.lean.transform) { + mod.options.options._leanTransform = mod.options.options.lean.transform; + mod.options.options.lean = true; + } + if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) { // Ensure that we set to 0 or empty array even // if we don't actually execute a query to make sure there's a value @@ -4673,6 +4684,14 @@ function populate(model, docs, options, callback) { for (const arr of params) { removeDeselectedForeignField(arr[0].foreignField, arr[0].options, vals); } + for (const arr of params) { + const mod = arr[0]; + if (mod.options && mod.options.options && mod.options.options._leanTransform) { + for (const doc of vals) { + mod.options.options._leanTransform(doc); + } + } + } callback(); } } diff --git a/test/query.test.js b/test/query.test.js index 1ebc5320aa4..4387c336642 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -3992,7 +3992,7 @@ describe('Query', function() { }); }); - it('allows a transform option for lean on a query gh-10423', async function() { + it('allows a transform option for lean on a query (gh-10423)', async function() { const arraySchema = new mongoose.Schema({ sub: String }); @@ -4004,7 +4004,7 @@ describe('Query', function() { foo: [arraySchema], otherName: subDoc }); - const Test = db.model('gh10423', testSchema); + const Test = db.model('Test', testSchema); await Test.create({ name: 'foo', foo: [{ sub: 'Test' }, { sub: 'Testerson' }], otherName: { nickName: 'Bar' } }); const result = await Test.find().lean({ @@ -4030,6 +4030,40 @@ describe('Query', function() { assert.strictEqual(single.foo[0]._id, undefined); }); + it('handles a lean transform that deletes _id with populate (gh-12143) (gh-10423)', async function() { + const testSchema = Schema({ + name: String, + user: { + type: mongoose.Types.ObjectId, + ref: 'User' + } + }); + + const userSchema = Schema({ + name: String + }); + + const Test = db.model('Test', testSchema); + const User = db.model('User', userSchema); + + const user = await User.create({ name: 'John Smith' }); + let test = await Test.create({ name: 'test', user }); + + test = await Test.findById(test).populate('user').lean({ + transform: (doc) => { + delete doc._id; + delete doc.__v; + return doc; + } + }); + + assert.ok(test); + assert.deepStrictEqual(test, { + name: 'test', + user: { name: 'John Smith' } + }); + }); + it('skips applying default projections over slice projections (gh-11940)', async function() { const commentSchema = new mongoose.Schema({ comment: String