Skip to content

Commit

Permalink
fix(populate): fix a couple of other places where we get the document…
Browse files Browse the repository at this point in the history
…'s _id with getters

Fix #14827
Re: #14759
  • Loading branch information
vkarpov15 committed Aug 27, 2024
1 parent 5dc2e90 commit 62c6a20
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 16 deletions.
6 changes: 3 additions & 3 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ function init(self, obj, doc, opts, prefix) {
*/

Document.prototype.updateOne = function updateOne(doc, options, callback) {
const query = this.constructor.updateOne({ _id: this._id }, doc, options);
const query = this.constructor.updateOne({ _id: this._doc._id }, doc, options);
const self = this;
query.pre(function queryPreUpdateOne(cb) {
self.constructor._middleware.execPre('updateOne', self, [self], cb);
Expand Down Expand Up @@ -883,7 +883,7 @@ Document.prototype.updateOne = function updateOne(doc, options, callback) {

Document.prototype.replaceOne = function replaceOne() {
const args = [...arguments];
args.unshift({ _id: this._id });
args.unshift({ _id: this._doc._id });
return this.constructor.replaceOne.apply(this.constructor, args);
};

Expand Down Expand Up @@ -3050,7 +3050,7 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
} else if (val != null && val.$__ != null && val.$__.wasPopulated) {
// Array paths, like `somearray.1`, do not show up as populated with `$populated()`,
// so in that case pull out the document's id
val = val._id;
val = val._doc._id;
}
const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ?
_this.$__.pathsToScopes[path] :
Expand Down
2 changes: 1 addition & 1 deletion lib/error/parallelSave.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ParallelSaveError extends MongooseError {
*/
constructor(doc) {
const msg = 'Can\'t save() the same doc multiple times in parallel. Document: ';
super(msg + doc._id);
super(msg + doc._doc._id);
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/error/parallelValidate.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ParallelValidateError extends MongooseError {
*/
constructor(doc) {
const msg = 'Can\'t validate() the same doc multiple times in parallel. Document: ';
super(msg + doc._id);
super(msg + doc._doc._id);
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/error/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class VersionError extends MongooseError {
*/
constructor(doc, currentVersion, modifiedPaths) {
const modifiedPathsStr = modifiedPaths.join(', ');
super('No matching document found for id "' + doc._id +
super('No matching document found for id "' + doc._doc._id +
'" version ' + currentVersion + ' modifiedPaths "' + modifiedPathsStr + '"');
this.version = currentVersion;
this.modifiedPaths = modifiedPaths;
Expand Down
4 changes: 2 additions & 2 deletions lib/helpers/populate/getModelsMapForPopulate.js
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ function _getLocalFieldValues(doc, localField, model, options, virtual, schema)

function convertTo_id(val, schema) {
if (val != null && val.$__ != null) {
return val._id;
return val._doc._id;
}
if (val != null && val._id != null && (schema == null || !schema.$isSchemaMap)) {
return val._id;
Expand All @@ -636,7 +636,7 @@ function convertTo_id(val, schema) {
const rawVal = val.__array != null ? val.__array : val;
for (let i = 0; i < rawVal.length; ++i) {
if (rawVal[i] != null && rawVal[i].$__ != null) {
rawVal[i] = rawVal[i]._id;
rawVal[i] = rawVal[i]._doc._id;
}
}
if (utils.isMongooseArray(val) && val.$schema()) {
Expand Down
4 changes: 2 additions & 2 deletions lib/helpers/populate/markArraySubdocsPopulated.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ const utils = require('../../utils');
*/

module.exports = function markArraySubdocsPopulated(doc, populated) {
if (doc._id == null || populated == null || populated.length === 0) {
if (doc._doc._id == null || populated == null || populated.length === 0) {
return;
}

const id = String(doc._id);
const id = String(doc._doc._id);
for (const item of populated) {
if (item.isVirtual) {
continue;
Expand Down
6 changes: 3 additions & 3 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2345,7 +2345,7 @@ Model.findByIdAndUpdate = function(id, update, options) {

// if a model is passed in instead of an id
if (id instanceof Document) {
id = id._id;
id = id._doc._id;
}

return this.findOneAndUpdate.call(this, { _id: id }, update, options);
Expand Down Expand Up @@ -3408,7 +3408,7 @@ Model.bulkSave = async function bulkSave(documents, options) {
documents.map(async(document) => {
const documentError = bulkWriteError && bulkWriteError.writeErrors.find(writeError => {
const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
return writeErrorDocumentId.toString() === document._id.toString();
return writeErrorDocumentId.toString() === document._doc._id.toString();
});

if (documentError == null) {
Expand Down Expand Up @@ -4436,7 +4436,7 @@ function _assign(model, vals, mod, assignmentOpts) {

for (let __val of _val) {
if (__val instanceof Document) {
__val = __val._id;
__val = __val._doc._id;
}
key = String(__val);
if (rawDocs[key]) {
Expand Down
6 changes: 3 additions & 3 deletions lib/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -3380,9 +3380,9 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
if (!this._update || Object.keys(this._update).length === 0) {
if (options.upsert) {
// still need to do the upsert to empty doc
const doc = clone(this._update);
delete doc._id;
this._update = { $set: doc };
const $set = clone(this._update);
delete $set._id;
this._update = { $set };
} else {
this._executionStack = null;
const res = await this._findOne();
Expand Down
35 changes: 35 additions & 0 deletions test/model.populate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11049,6 +11049,41 @@ describe('model: populate:', function() {
assert.equal(pet2.owner.name, 'Alice');
});

it('avoids populating manually populated doc as getter value (gh-14827)', async function() {
const ownerSchema = new mongoose.Schema({
_id: {
type: 'ObjectId',
get(value) {
return value == null ? value : value.toString();
}
},
name: 'String'
});
const petSchema = new mongoose.Schema({
name: 'String',
owner: { type: 'ObjectId', ref: 'Owner' }
});

const Owner = db.model('Owner', ownerSchema);
const Pet = db.model('Pet', petSchema);

const _id = new mongoose.Types.ObjectId();
const owner = new Owner({ _id, name: 'Alice' });
const pet = new Pet({ name: 'Kitty', owner: owner });

await owner.save();

assert.equal(typeof pet._doc.owner.$__.wasPopulated.value, 'object'); // object 😀
await pet.populate('owner'); // This breaks it 💥💥💥💥💥💥💥💥💥💥
assert.equal(typeof pet._doc.owner.$__.wasPopulated.value, 'object'); // string 🙁

await pet.save();


const fromDb = await Pet.findOne({ owner: _id }).lean().orFail();
assert.ok(fromDb.owner instanceof mongoose.Types.ObjectId);
});

it('makes sure that populate works correctly with duplicate foreignField with lean(); (gh-14794)', async function() {
const authorSchema = new mongoose.Schema({
group: String,
Expand Down

0 comments on commit 62c6a20

Please sign in to comment.