Skip to content

Commit 94b2873

Browse files
authored
Merge pull request #14963 from Automattic/vkarpov15/gh-14952
fix(document): recursively clear modified subpaths when setting deeply nested subdoc to null
2 parents 562aabd + e6c9dd3 commit 94b2873

File tree

2 files changed

+91
-3
lines changed

2 files changed

+91
-3
lines changed

lib/helpers/document/cleanModifiedSubpaths.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,21 @@ module.exports = function cleanModifiedSubpaths(doc, path, options) {
2525
++deleted;
2626

2727
if (doc.$isSubdocument) {
28-
const owner = doc.ownerDocument();
29-
const fullPath = doc.$__fullPath(modifiedPath);
30-
owner.$__.activePaths.clearPath(fullPath);
28+
cleanParent(doc, modifiedPath);
3129
}
3230
}
3331
}
3432
return deleted;
3533
};
34+
35+
function cleanParent(doc, path, seen = new Set()) {
36+
if (seen.has(doc)) {
37+
throw new Error('Infinite subdocument loop: subdoc with _id ' + doc._id + ' is a parent of itself');
38+
}
39+
const parent = doc.$parent();
40+
const newPath = doc.$__pathRelativeToParent(void 0, false) + '.' + path;
41+
parent.$__.activePaths.clearPath(newPath);
42+
if (parent.$isSubdocument) {
43+
cleanParent(parent, newPath, seen);
44+
}
45+
}

test/document.test.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13978,6 +13978,84 @@ describe('document', function() {
1397813978
assert.ok(Buffer.isBuffer(reloaded.pdfSettings.fileContent));
1397913979
assert.strictEqual(reloaded.pdfSettings.fileContent.toString('utf8'), 'hello');
1398013980
});
13981+
13982+
it('clears modified subpaths when setting deeply nested subdoc to null (gh-14952)', async function() {
13983+
const currentMilestoneSchema = new Schema(
13984+
{
13985+
id: { type: String, required: true }
13986+
},
13987+
{
13988+
_id: false
13989+
}
13990+
);
13991+
13992+
const milestoneSchema = new Schema(
13993+
{
13994+
current: {
13995+
type: currentMilestoneSchema,
13996+
required: true
13997+
}
13998+
},
13999+
{
14000+
_id: false
14001+
}
14002+
);
14003+
14004+
const campaignSchema = new Schema(
14005+
{
14006+
milestones: {
14007+
type: milestoneSchema,
14008+
required: false
14009+
}
14010+
},
14011+
{
14012+
_id: false
14013+
}
14014+
);
14015+
const questSchema = new Schema(
14016+
{
14017+
campaign: { type: campaignSchema, required: false }
14018+
},
14019+
{
14020+
_id: false
14021+
}
14022+
);
14023+
14024+
const parentSchema = new Schema({
14025+
quests: [questSchema]
14026+
});
14027+
14028+
const ParentModel = db.model('Parent', parentSchema);
14029+
const doc = new ParentModel({
14030+
quests: [
14031+
{
14032+
campaign: {
14033+
milestones: {
14034+
current: {
14035+
id: 'milestone1'
14036+
}
14037+
}
14038+
}
14039+
}
14040+
]
14041+
});
14042+
14043+
await doc.save();
14044+
14045+
// Set the nested schema to null
14046+
doc.quests[0].campaign.milestones.current = {
14047+
id: 'milestone1'
14048+
};
14049+
doc.quests[0].campaign.milestones.current = {
14050+
id: ''
14051+
};
14052+
14053+
doc.quests[0].campaign.milestones = null;
14054+
await doc.save();
14055+
14056+
const fromDb = await ParentModel.findById(doc._id).orFail();
14057+
assert.strictEqual(fromDb.quests[0].campaign.milestones, null);
14058+
});
1398114059
});
1398214060

1398314061
describe('Check if instance function that is supplied in schema option is available', function() {

0 commit comments

Comments
 (0)