diff --git a/lib/helpers/projection/isExclusive.js b/lib/helpers/projection/isExclusive.js index a232857d601..b55cf468458 100644 --- a/lib/helpers/projection/isExclusive.js +++ b/lib/helpers/projection/isExclusive.js @@ -12,13 +12,12 @@ module.exports = function isExclusive(projection) { } const keys = Object.keys(projection); - let ki = keys.length; let exclude = null; - if (ki === 1 && keys[0] === '_id') { + if (keys.length === 1 && keys[0] === '_id') { exclude = !projection._id; } else { - while (ki--) { + for (let ki = 0; ki < keys.length; ++ki) { // Does this projection explicitly define inclusion/exclusion? // Explicitly avoid `$meta` and `$slice` const key = keys[ki]; diff --git a/lib/model.js b/lib/model.js index 8b0a99e05b6..e97407429aa 100644 --- a/lib/model.js +++ b/lib/model.js @@ -4408,7 +4408,7 @@ function populate(model, docs, options, callback) { select = select.replace(excludeIdRegGlobal, ' '); } else { // preserve original select conditions by copying - select = utils.object.shallowCopy(select); + select = { ...select }; delete select._id; } } diff --git a/lib/query.js b/lib/query.js index 98112653776..a6063cc5fc4 100644 --- a/lib/query.js +++ b/lib/query.js @@ -4932,16 +4932,14 @@ Query.prototype._castFields = function _castFields(fields) { elemMatchKeys, keys, key, - out, - i; + out; if (fields) { keys = Object.keys(fields); elemMatchKeys = []; - i = keys.length; // collect $elemMatch args - while (i--) { + for (let i = 0; i < keys.length; ++i) { key = keys[i]; if (fields[key].$elemMatch) { selected || (selected = {}); @@ -4960,8 +4958,7 @@ Query.prototype._castFields = function _castFields(fields) { } // apply the casted field args - i = elemMatchKeys.length; - while (i--) { + for (let i = 0; i < elemMatchKeys.length; ++i) { key = elemMatchKeys[i]; fields[key] = out[key]; } diff --git a/lib/queryhelpers.js b/lib/queryhelpers.js index 8d5f3aea0c0..90d8739ef8b 100644 --- a/lib/queryhelpers.js +++ b/lib/queryhelpers.js @@ -180,11 +180,12 @@ exports.applyPaths = function applyPaths(fields, schema) { if (!isDefiningProjection(field)) { continue; } - // `_id: 1, name: 0` is a mixed inclusive/exclusive projection in - // MongoDB 4.0 and earlier, but not in later versions. if (keys[keyIndex] === '_id' && keys.length > 1) { continue; } + if (keys[keyIndex] === schema.options.discriminatorKey && keys.length > 1 && field != null && !field) { + continue; + } exclude = !field; break; } diff --git a/lib/utils.js b/lib/utils.js index 4fca9502d56..fa17f2f2d48 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -687,12 +687,6 @@ exports.object.vals = function vals(o) { return ret; }; -/** - * @see exports.options - */ - -exports.object.shallowCopy = exports.options; - const hop = Object.prototype.hasOwnProperty; /** diff --git a/test/model.populate.test.js b/test/model.populate.test.js index 9f629f28844..1c6029853de 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -10834,4 +10834,29 @@ describe('model: populate:', function() { [id1.toString(), id2.toString(), id3.toString(), id4.toString()] ); }); + + it('allows deselecting discriminator key when populating (gh-3230) (gh-13760) (gh-13679)', async function() { + const Test = db.model( + 'Test', + Schema({ name: String, arr: [{ testRef: { type: 'ObjectId', ref: 'Test2' } }] }) + ); + + const schema = Schema({ name: String }); + const Test2 = db.model('Test2', schema); + const D = Test2.discriminator('D', Schema({ prop: String })); + + + await Test.deleteMany({}); + await Test2.deleteMany({}); + const { _id } = await D.create({ name: 'foo', prop: 'bar' }); + const test = await Test.create({ name: 'test', arr: [{ testRef: _id }] }); + + const doc = await Test + .findById(test._id) + .populate('arr.testRef', { name: 1, prop: 1, _id: 0, __t: 0 }); + assert.deepStrictEqual( + doc.toObject().arr[0].testRef, + { name: 'foo', prop: 'bar' } + ); + }); });