Skip to content

Commit ecc4ab7

Browse files
committed
Merge branch 'master' into 8.11
2 parents b502e8c + d64f481 commit ecc4ab7

File tree

6 files changed

+93
-3
lines changed

6 files changed

+93
-3
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
8.10.1 / 2025-02-14
2+
===================
3+
* perf(document): only call undoReset() 1x/document #15257 #15255
4+
* perf(schema): clear childSchemas when overwriting existing path to avoid performance degradations #15256 #15253
5+
* perf: some more micro optimizations for find() and findOne() #14906 #15250
6+
* fix(model): avoid adding timeout on Model.init() buffering to avoid unintentional dangling open handles #15251 #15241
7+
* fix: avoid connection buffering on init if autoCreate: false #15247 #15241
8+
* fix: infer discriminator key if set in $set with overwriteDiscriminatorKey #15243 #15218
9+
* types(middleware): make this in document middleware the hydrated doc type, not raw doc type #15246 #15242
10+
* types(schema): support options parameter to Schema.prototype.discriminator() #15249 #15244
11+
* types(schema): allow calling Schema.prototype.number() with no message arg #15237 #15236
12+
* docs(typescript): recommend using HydratedSingleSubdocument over Types.Subdocument #15240 #15211
13+
114
8.10.0 / 2025-02-05
215
===================
316
* feat(schema+schematype): add toJSONSchema() method to convert schemas and schematypes to JSON schema #15184 #11162

lib/document.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3553,8 +3553,10 @@ Document.prototype.$__undoReset = function $__undoReset() {
35533553
}
35543554
}
35553555

3556-
for (const subdoc of this.$getAllSubdocs()) {
3557-
subdoc.$__undoReset();
3556+
if (!this.$isSubdocument) {
3557+
for (const subdoc of this.$getAllSubdocs()) {
3558+
subdoc.$__undoReset();
3559+
}
35583560
}
35593561
};
35603562

lib/schema.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,9 @@ Schema.prototype.path = function(path, obj) {
11181118
this.paths[path] = this.interpretAsType(path, obj, this.options);
11191119
const schemaType = this.paths[path];
11201120

1121+
// If overwriting an existing path, make sure to clear the childSchemas
1122+
this.childSchemas = this.childSchemas.filter(childSchema => childSchema.path !== path);
1123+
11211124
if (schemaType.$isSchemaMap) {
11221125
// Maps can have arbitrary keys, so `$*` is internal shorthand for "any key"
11231126
// The '$' is to imply this path should never be stored in MongoDB so we

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "mongoose",
33
"description": "Mongoose MongoDB ODM",
4-
"version": "8.10.0",
4+
"version": "8.10.1",
55
"author": "Guillermo Rauch <[email protected]>",
66
"keywords": [
77
"mongodb",

test/document.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const ArraySubdocument = require('../lib/types/arraySubdocument');
1212
const Query = require('../lib/query');
1313
const assert = require('assert');
1414
const idGetter = require('../lib/helpers/schema/idGetter');
15+
const sinon = require('sinon');
1516
const util = require('./util');
1617
const utils = require('../lib/utils');
1718

@@ -14296,6 +14297,47 @@ describe('document', function() {
1429614297

1429714298
delete mongoose.Schema.Types.CustomType;
1429814299
});
14300+
14301+
it('handles undoReset() on deep recursive subdocuments (gh-15255)', async function() {
14302+
const RecursiveSchema = new mongoose.Schema({});
14303+
14304+
const s = [RecursiveSchema];
14305+
RecursiveSchema.path('nested', s);
14306+
14307+
const generateRecursiveDocument = (depth, curr = 0) => {
14308+
return {
14309+
name: `Document of depth ${curr}`,
14310+
nested: depth > 0 ? new Array(2).fill().map(() => generateRecursiveDocument(depth - 1, curr + 1)) : [],
14311+
__v: 5
14312+
};
14313+
};
14314+
const TestModel = db.model('Test', RecursiveSchema);
14315+
const data = generateRecursiveDocument(10);
14316+
const doc = new TestModel(data);
14317+
await doc.save();
14318+
14319+
sinon.spy(Document.prototype, '$__undoReset');
14320+
14321+
try {
14322+
const d = await TestModel.findById(doc._id);
14323+
d.increment();
14324+
d.data = 'asd';
14325+
// Force a version error by updating the document directly
14326+
await TestModel.collection.updateOne({ _id: doc._id }, { $inc: { __v: 1 } });
14327+
const err = await d.save().then(() => null, err => err);
14328+
assert.ok(err);
14329+
assert.equal(err.name, 'VersionError');
14330+
// `$__undoReset()` should be called 1x per subdoc, plus 1x for top-level doc. Without fix for gh-15255,
14331+
// this would fail because `$__undoReset()` is called nearly 700k times for only 2046 subdocs
14332+
assert.strictEqual(Document.prototype.$__undoReset.getCalls().length, d.$getAllSubdocs().length + 1);
14333+
assert.ok(Document.prototype.$__undoReset.getCalls().find(call => call.thisValue === d), 'top level doc was not reset');
14334+
for (const subdoc of d.$getAllSubdocs()) {
14335+
assert.ok(Document.prototype.$__undoReset.getCalls().find(call => call.thisValue === subdoc), `${subdoc.name} was not reset`);
14336+
}
14337+
} finally {
14338+
sinon.restore();
14339+
}
14340+
});
1429914341
});
1430014342

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

test/schema.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3889,4 +3889,34 @@ describe('schema', function() {
38893889
assert.throws(() => schema.toJSONSchema(), /unsupported SchemaType to JSON Schema: Mixed/);
38903890
});
38913891
});
3892+
3893+
it('path() clears existing child schemas (gh-15253)', async function() {
3894+
const RecursiveSchema = new mongoose.Schema({
3895+
data: String
3896+
});
3897+
3898+
const s = [RecursiveSchema];
3899+
RecursiveSchema.path('nested', s);
3900+
assert.strictEqual(RecursiveSchema.childSchemas.length, 1);
3901+
RecursiveSchema.path('nested', s);
3902+
assert.strictEqual(RecursiveSchema.childSchemas.length, 1);
3903+
RecursiveSchema.path('nested', s);
3904+
assert.strictEqual(RecursiveSchema.childSchemas.length, 1);
3905+
RecursiveSchema.path('nested', s);
3906+
assert.strictEqual(RecursiveSchema.childSchemas.length, 1);
3907+
3908+
const generateRecursiveDocument = (depth, curr = 0) => {
3909+
return {
3910+
name: `Document of depth ${curr}`,
3911+
nested: depth > 0 ? new Array(3).fill().map(() => generateRecursiveDocument(depth - 1, curr + 1)) : [],
3912+
data: Math.random()
3913+
};
3914+
};
3915+
3916+
const TestModel = db.model('Test', RecursiveSchema);
3917+
const data = generateRecursiveDocument(6);
3918+
const doc = new TestModel(data);
3919+
await doc.save();
3920+
3921+
});
38923922
});

0 commit comments

Comments
 (0)