Skip to content

Commit 9097ef2

Browse files
committed
Merge branch 'master' into 8.9
2 parents beba447 + 2607904 commit 9097ef2

19 files changed

+347
-79
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
8.8.4 / 2024-12-05
2+
==================
3+
* fix: cast using overwritten embedded discriminator key when set #15076 #15051
4+
* fix: avoid throwing error if saveOptions undefined when invalidating subdoc cache #15062
5+
6+
8.8.3 / 2024-11-26
7+
==================
8+
* fix: disallow using $where in match
9+
* perf: cache results from getAllSubdocs() on saveOptions, only loop through known subdoc properties #15055 #15029
10+
* fix(model+query): support overwriteDiscriminatorKey for bulkWrite updateOne and updateMany, allow inferring discriminator key from update #15046 #15040
11+
12+
8.8.2 / 2024-11-18
13+
==================
14+
* fix(model): handle array filters when casting bulkWrite #15036 #14978
15+
* fix(model): make diffIndexes() avoid trying to drop default timeseries collection index #15035 #14984
16+
* fix: save execution stack in query as string #15039 [durran](https://github.com/durran)
17+
* types(cursor): correct asyncIterator and asyncDispose for TypeScript with lib: 'esnext' #15038
18+
* docs(migrating_to_8): add note about removing findByIdAndRemove #15024 [dragontaek-lee](https://github.com/dragontaek-lee)
19+
120
8.8.1 / 2024-11-08
221
==================
322
* perf: make a few micro-optimizations to help speed up findOne() #15022 #14906

benchmarks/saveSimple.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
3+
const mongoose = require('../');
4+
5+
run().catch(err => {
6+
console.error(err);
7+
process.exit(-1);
8+
});
9+
10+
async function run() {
11+
await mongoose.connect('mongodb://127.0.0.1:27017/mongoose_benchmark');
12+
const FooSchema = new mongoose.Schema({
13+
prop1: String,
14+
prop2: String,
15+
prop3: String,
16+
prop4: String,
17+
prop5: String,
18+
prop6: String,
19+
prop7: String,
20+
prop8: String,
21+
prop9: String,
22+
prop10: String
23+
});
24+
const FooModel = mongoose.model('Foo', FooSchema);
25+
26+
if (!process.env.MONGOOSE_BENCHMARK_SKIP_SETUP) {
27+
await FooModel.deleteMany({});
28+
}
29+
30+
const numIterations = 500;
31+
const saveStart = Date.now();
32+
for (let i = 0; i < numIterations; ++i) {
33+
for (let j = 0; j < 10; ++j) {
34+
const doc = new FooModel({
35+
prop1: `test ${i}`,
36+
prop2: `test ${i}`,
37+
prop3: `test ${i}`,
38+
prop4: `test ${i}`,
39+
prop5: `test ${i}`,
40+
prop6: `test ${i}`,
41+
prop7: `test ${i}`,
42+
prop8: `test ${i}`,
43+
prop9: `test ${i}`,
44+
prop10: `test ${i}`
45+
});
46+
await doc.save();
47+
}
48+
}
49+
const saveEnd = Date.now();
50+
51+
const results = {
52+
'Average save time ms': +((saveEnd - saveStart) / numIterations).toFixed(2)
53+
};
54+
55+
console.log(JSON.stringify(results, null, ' '));
56+
process.exit(0);
57+
}

docs/migrating_to_8.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ In Mongoose 7, `findOneAndRemove()` was an alias for `findOneAndDelete()` that M
8787
Mongoose 8 no longer supports `findOneAndRemove()`.
8888
Use `findOneAndDelete()` instead.
8989

90+
Similarly, Mongoose 8 no longer supports `findByIdAndRemove()`, which was an alias for `findByIdAndDelete()`.
91+
Please use `findByIdAndDelete()` instead.
92+
9093
## Removed `count()` {#removed-count}
9194

9295
`Model.count()` and `Query.prototype.count()` were removed in Mongoose 8. Use `Model.countDocuments()` and `Query.prototype.countDocuments()` instead.

lib/document.js

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2711,7 +2711,7 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
27112711

27122712
if (!isNestedValidate) {
27132713
// If we're validating a subdocument, all this logic will run anyway on the top-level document, so skip for subdocuments
2714-
const subdocs = doc.$getAllSubdocs();
2714+
const subdocs = doc.$getAllSubdocs({ useCache: true });
27152715
const modifiedPaths = doc.modifiedPaths();
27162716
for (const subdoc of subdocs) {
27172717
if (subdoc.$basePath) {
@@ -3482,7 +3482,7 @@ Document.prototype.$__reset = function reset() {
34823482
let _this = this;
34833483

34843484
// Skip for subdocuments
3485-
const subdocs = !this.$isSubdocument ? this.$getAllSubdocs() : null;
3485+
const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null;
34863486
if (subdocs && subdocs.length > 0) {
34873487
for (const subdoc of subdocs) {
34883488
subdoc.$__reset();
@@ -3672,64 +3672,58 @@ Document.prototype.$__getArrayPathsToValidate = function() {
36723672
/**
36733673
* Get all subdocs (by bfs)
36743674
*
3675+
* @param {Object} [options] options. Currently for internal use.
36753676
* @return {Array}
36763677
* @api public
36773678
* @method $getAllSubdocs
36783679
* @memberOf Document
36793680
* @instance
36803681
*/
36813682

3682-
Document.prototype.$getAllSubdocs = function() {
3683+
Document.prototype.$getAllSubdocs = function(options) {
3684+
if (options?.useCache && this.$__.saveOptions?.__subdocs) {
3685+
return this.$__.saveOptions.__subdocs;
3686+
}
3687+
36833688
DocumentArray || (DocumentArray = require('./types/documentArray'));
36843689
Embedded = Embedded || require('./types/arraySubdocument');
36853690

3686-
function docReducer(doc, seed, path) {
3687-
let val = doc;
3688-
let isNested = false;
3689-
if (path) {
3690-
if (doc instanceof Document && doc[documentSchemaSymbol].paths[path]) {
3691-
val = doc._doc[path];
3692-
} else if (doc instanceof Document && doc[documentSchemaSymbol].nested[path]) {
3693-
val = doc._doc[path];
3694-
isNested = true;
3695-
} else {
3696-
val = doc[path];
3691+
const subDocs = [];
3692+
function getSubdocs(doc) {
3693+
const newSubdocs = [];
3694+
for (const { path } of doc.$__schema.childSchemas) {
3695+
const val = doc.$__getValue(path);
3696+
if (val == null) {
3697+
continue;
36973698
}
3698-
}
3699-
if (val instanceof Embedded) {
3700-
seed.push(val);
3701-
} else if (val instanceof Map) {
3702-
seed = Array.from(val.keys()).reduce(function(seed, path) {
3703-
return docReducer(val.get(path), seed, null);
3704-
}, seed);
3705-
} else if (val && !Array.isArray(val) && val.$isSingleNested) {
3706-
seed = Object.keys(val._doc).reduce(function(seed, path) {
3707-
return docReducer(val, seed, path);
3708-
}, seed);
3709-
seed.push(val);
3710-
} else if (val && utils.isMongooseDocumentArray(val)) {
3711-
val.forEach(function _docReduce(doc) {
3712-
if (!doc || !doc._doc) {
3713-
return;
3699+
if (val.$__) {
3700+
newSubdocs.push(val);
3701+
}
3702+
if (Array.isArray(val)) {
3703+
for (const el of val) {
3704+
if (el != null && el.$__) {
3705+
newSubdocs.push(el);
3706+
}
37143707
}
3715-
seed = Object.keys(doc._doc).reduce(function(seed, path) {
3716-
return docReducer(doc._doc, seed, path);
3717-
}, seed);
3718-
if (doc instanceof Embedded) {
3719-
seed.push(doc);
3708+
}
3709+
if (val instanceof Map) {
3710+
for (const el of val.values()) {
3711+
if (el != null && el.$__) {
3712+
newSubdocs.push(el);
3713+
}
37203714
}
3721-
});
3722-
} else if (isNested && val != null) {
3723-
for (const path of Object.keys(val)) {
3724-
docReducer(val, seed, path);
37253715
}
37263716
}
3727-
return seed;
3717+
for (const subdoc of newSubdocs) {
3718+
getSubdocs(subdoc);
3719+
}
3720+
subDocs.push(...newSubdocs);
37283721
}
37293722

3730-
const subDocs = [];
3731-
for (const path of Object.keys(this._doc)) {
3732-
docReducer(this, subDocs, path);
3723+
getSubdocs(this);
3724+
3725+
if (this.$__.saveOptions) {
3726+
this.$__.saveOptions.__subdocs = subDocs;
37333727
}
37343728

37353729
return subDocs;

lib/helpers/model/castBulkWrite.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ module.exports.castUpdateOne = function castUpdateOne(originalModel, updateOne,
149149
updateOne['update'] = castUpdate(model.schema, update, {
150150
strict: strict,
151151
upsert: updateOne.upsert,
152-
arrayFilters: updateOne.arrayFilters
152+
arrayFilters: updateOne.arrayFilters,
153+
overwriteDiscriminatorKey: updateOne.overwriteDiscriminatorKey
153154
}, model, updateOne['filter']);
154155

155156
return updateOne;
@@ -206,7 +207,8 @@ module.exports.castUpdateMany = function castUpdateMany(originalModel, updateMan
206207
updateMany['update'] = castUpdate(model.schema, updateMany['update'], {
207208
strict: strict,
208209
upsert: updateMany.upsert,
209-
arrayFilters: updateMany.arrayFilters
210+
arrayFilters: updateMany.arrayFilters,
211+
overwriteDiscriminatorKey: updateMany.overwriteDiscriminatorKey
210212
}, model, updateMany['filter']);
211213
};
212214

lib/helpers/populate/assignVals.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ function numDocs(v) {
249249

250250
function valueFilter(val, assignmentOpts, populateOptions, allIds) {
251251
const userSpecifiedTransform = typeof populateOptions.transform === 'function';
252-
const transform = userSpecifiedTransform ? populateOptions.transform : noop;
252+
const transform = userSpecifiedTransform ? populateOptions.transform : v => v;
253253
if (Array.isArray(val)) {
254254
// find logic
255255
const ret = [];
@@ -341,7 +341,3 @@ function isPopulatedObject(obj) {
341341
obj.$__ != null ||
342342
leanPopulateMap.has(obj);
343343
}
344-
345-
function noop(v) {
346-
return v;
347-
}

lib/helpers/populate/getModelsMapForPopulate.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
191191
if (hasMatchFunction) {
192192
match = match.call(doc, doc);
193193
}
194+
if (Array.isArray(match)) {
195+
for (const item of match) {
196+
if (item != null && item.$where) {
197+
throw new MongooseError('Cannot use $where filter with populate() match');
198+
}
199+
}
200+
} else if (match != null && match.$where != null) {
201+
throw new MongooseError('Cannot use $where filter with populate() match');
202+
}
194203
data.match = match;
195204
data.hasMatchFunction = hasMatchFunction;
196205
data.isRefPath = isRefPath;
@@ -454,6 +463,16 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
454463
data.match = match;
455464
data.hasMatchFunction = hasMatchFunction;
456465

466+
if (Array.isArray(match)) {
467+
for (const item of match) {
468+
if (item != null && item.$where) {
469+
throw new MongooseError('Cannot use $where filter with populate() match');
470+
}
471+
}
472+
} else if (match != null && match.$where != null) {
473+
throw new MongooseError('Cannot use $where filter with populate() match');
474+
}
475+
457476
// Get local fields
458477
const ret = _getLocalFieldValues(doc, localField, model, options, virtual);
459478

lib/helpers/query/castUpdate.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const ValidationError = require('../../error/validation');
88
const castNumber = require('../../cast/number');
99
const cast = require('../../cast');
1010
const getConstructorName = require('../getConstructorName');
11+
const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
1112
const getEmbeddedDiscriminatorPath = require('./getEmbeddedDiscriminatorPath');
1213
const handleImmutable = require('./handleImmutable');
1314
const moveImmutableProperties = require('../update/moveImmutableProperties');
@@ -62,6 +63,27 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
6263
return obj;
6364
}
6465

66+
if (schema != null &&
67+
filter != null &&
68+
utils.hasUserDefinedProperty(filter, schema.options.discriminatorKey) &&
69+
typeof filter[schema.options.discriminatorKey] !== 'object' &&
70+
schema.discriminators != null) {
71+
const discriminatorValue = filter[schema.options.discriminatorKey];
72+
const byValue = getDiscriminatorByValue(context.model.discriminators, discriminatorValue);
73+
schema = schema.discriminators[discriminatorValue] ||
74+
(byValue && byValue.schema) ||
75+
schema;
76+
} else if (schema != null &&
77+
options.overwriteDiscriminatorKey &&
78+
utils.hasUserDefinedProperty(obj, schema.options.discriminatorKey) &&
79+
schema.discriminators != null) {
80+
const discriminatorValue = obj[schema.options.discriminatorKey];
81+
const byValue = getDiscriminatorByValue(context.model.discriminators, discriminatorValue);
82+
schema = schema.discriminators[discriminatorValue] ||
83+
(byValue && byValue.schema) ||
84+
schema;
85+
}
86+
6587
if (options.upsert) {
6688
moveImmutableProperties(schema, obj, context);
6789
}

lib/helpers/query/getEmbeddedDiscriminatorPath.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p
2828
const updatedPathsByFilter = updatedPathsByArrayFilter(update);
2929

3030
for (let i = 0; i < parts.length; ++i) {
31-
const subpath = cleanPositionalOperators(parts.slice(0, i + 1).join('.'));
31+
const originalSubpath = parts.slice(0, i + 1).join('.');
32+
const subpath = cleanPositionalOperators(originalSubpath);
3233
schematype = schema.path(subpath);
3334
if (schematype == null) {
3435
continue;
@@ -56,6 +57,11 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p
5657
discriminatorKey = filter[wrapperPath].$elemMatch[key];
5758
}
5859

60+
const discriminatorKeyUpdatePath = originalSubpath + '.' + key;
61+
if (discriminatorKeyUpdatePath in update) {
62+
discriminatorKey = update[discriminatorKeyUpdatePath];
63+
}
64+
5965
if (discriminatorValuePath in update) {
6066
discriminatorKey = update[discriminatorValuePath];
6167
}

lib/model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3147,7 +3147,7 @@ function _setIsNew(doc, val) {
31473147
doc.$emit('isNew', val);
31483148
doc.constructor.emit('isNew', val);
31493149

3150-
const subdocs = doc.$getAllSubdocs();
3150+
const subdocs = doc.$getAllSubdocs({ useCache: true });
31513151
for (const subdoc of subdocs) {
31523152
subdoc.$isNew = val;
31533153
subdoc.$emit('isNew', val);

0 commit comments

Comments
 (0)