Skip to content

Commit

Permalink
Merge branch 'master' into 8.9
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Dec 5, 2024
2 parents beba447 + 2607904 commit 9097ef2
Show file tree
Hide file tree
Showing 19 changed files with 347 additions and 79 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
8.8.4 / 2024-12-05
==================
* fix: cast using overwritten embedded discriminator key when set #15076 #15051
* fix: avoid throwing error if saveOptions undefined when invalidating subdoc cache #15062

8.8.3 / 2024-11-26
==================
* fix: disallow using $where in match
* perf: cache results from getAllSubdocs() on saveOptions, only loop through known subdoc properties #15055 #15029
* fix(model+query): support overwriteDiscriminatorKey for bulkWrite updateOne and updateMany, allow inferring discriminator key from update #15046 #15040

8.8.2 / 2024-11-18
==================
* fix(model): handle array filters when casting bulkWrite #15036 #14978
* fix(model): make diffIndexes() avoid trying to drop default timeseries collection index #15035 #14984
* fix: save execution stack in query as string #15039 [durran](https://github.com/durran)
* types(cursor): correct asyncIterator and asyncDispose for TypeScript with lib: 'esnext' #15038
* docs(migrating_to_8): add note about removing findByIdAndRemove #15024 [dragontaek-lee](https://github.com/dragontaek-lee)

8.8.1 / 2024-11-08
==================
* perf: make a few micro-optimizations to help speed up findOne() #15022 #14906
Expand Down
57 changes: 57 additions & 0 deletions benchmarks/saveSimple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

const mongoose = require('../');

run().catch(err => {
console.error(err);
process.exit(-1);
});

async function run() {
await mongoose.connect('mongodb://127.0.0.1:27017/mongoose_benchmark');
const FooSchema = new mongoose.Schema({
prop1: String,
prop2: String,
prop3: String,
prop4: String,
prop5: String,
prop6: String,
prop7: String,
prop8: String,
prop9: String,
prop10: String
});
const FooModel = mongoose.model('Foo', FooSchema);

if (!process.env.MONGOOSE_BENCHMARK_SKIP_SETUP) {
await FooModel.deleteMany({});
}

const numIterations = 500;
const saveStart = Date.now();
for (let i = 0; i < numIterations; ++i) {
for (let j = 0; j < 10; ++j) {
const doc = new FooModel({
prop1: `test ${i}`,
prop2: `test ${i}`,
prop3: `test ${i}`,
prop4: `test ${i}`,
prop5: `test ${i}`,
prop6: `test ${i}`,
prop7: `test ${i}`,
prop8: `test ${i}`,
prop9: `test ${i}`,
prop10: `test ${i}`
});
await doc.save();
}
}
const saveEnd = Date.now();

const results = {
'Average save time ms': +((saveEnd - saveStart) / numIterations).toFixed(2)
};

console.log(JSON.stringify(results, null, ' '));
process.exit(0);
}
3 changes: 3 additions & 0 deletions docs/migrating_to_8.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ In Mongoose 7, `findOneAndRemove()` was an alias for `findOneAndDelete()` that M
Mongoose 8 no longer supports `findOneAndRemove()`.
Use `findOneAndDelete()` instead.

Similarly, Mongoose 8 no longer supports `findByIdAndRemove()`, which was an alias for `findByIdAndDelete()`.
Please use `findByIdAndDelete()` instead.

## Removed `count()` {#removed-count}

`Model.count()` and `Query.prototype.count()` were removed in Mongoose 8. Use `Model.countDocuments()` and `Query.prototype.countDocuments()` instead.
Expand Down
80 changes: 37 additions & 43 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -2711,7 +2711,7 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate

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

// Skip for subdocuments
const subdocs = !this.$isSubdocument ? this.$getAllSubdocs() : null;
const subdocs = !this.$isSubdocument ? this.$getAllSubdocs({ useCache: true }) : null;
if (subdocs && subdocs.length > 0) {
for (const subdoc of subdocs) {
subdoc.$__reset();
Expand Down Expand Up @@ -3672,64 +3672,58 @@ Document.prototype.$__getArrayPathsToValidate = function() {
/**
* Get all subdocs (by bfs)
*
* @param {Object} [options] options. Currently for internal use.
* @return {Array}
* @api public
* @method $getAllSubdocs
* @memberOf Document
* @instance
*/

Document.prototype.$getAllSubdocs = function() {
Document.prototype.$getAllSubdocs = function(options) {
if (options?.useCache && this.$__.saveOptions?.__subdocs) {
return this.$__.saveOptions.__subdocs;
}

DocumentArray || (DocumentArray = require('./types/documentArray'));
Embedded = Embedded || require('./types/arraySubdocument');

function docReducer(doc, seed, path) {
let val = doc;
let isNested = false;
if (path) {
if (doc instanceof Document && doc[documentSchemaSymbol].paths[path]) {
val = doc._doc[path];
} else if (doc instanceof Document && doc[documentSchemaSymbol].nested[path]) {
val = doc._doc[path];
isNested = true;
} else {
val = doc[path];
const subDocs = [];
function getSubdocs(doc) {
const newSubdocs = [];
for (const { path } of doc.$__schema.childSchemas) {
const val = doc.$__getValue(path);
if (val == null) {
continue;
}
}
if (val instanceof Embedded) {
seed.push(val);
} else if (val instanceof Map) {
seed = Array.from(val.keys()).reduce(function(seed, path) {
return docReducer(val.get(path), seed, null);
}, seed);
} else if (val && !Array.isArray(val) && val.$isSingleNested) {
seed = Object.keys(val._doc).reduce(function(seed, path) {
return docReducer(val, seed, path);
}, seed);
seed.push(val);
} else if (val && utils.isMongooseDocumentArray(val)) {
val.forEach(function _docReduce(doc) {
if (!doc || !doc._doc) {
return;
if (val.$__) {
newSubdocs.push(val);
}
if (Array.isArray(val)) {
for (const el of val) {
if (el != null && el.$__) {
newSubdocs.push(el);
}
}
seed = Object.keys(doc._doc).reduce(function(seed, path) {
return docReducer(doc._doc, seed, path);
}, seed);
if (doc instanceof Embedded) {
seed.push(doc);
}
if (val instanceof Map) {
for (const el of val.values()) {
if (el != null && el.$__) {
newSubdocs.push(el);
}
}
});
} else if (isNested && val != null) {
for (const path of Object.keys(val)) {
docReducer(val, seed, path);
}
}
return seed;
for (const subdoc of newSubdocs) {
getSubdocs(subdoc);
}
subDocs.push(...newSubdocs);
}

const subDocs = [];
for (const path of Object.keys(this._doc)) {
docReducer(this, subDocs, path);
getSubdocs(this);

if (this.$__.saveOptions) {
this.$__.saveOptions.__subdocs = subDocs;
}

return subDocs;
Expand Down
6 changes: 4 additions & 2 deletions lib/helpers/model/castBulkWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ module.exports.castUpdateOne = function castUpdateOne(originalModel, updateOne,
updateOne['update'] = castUpdate(model.schema, update, {
strict: strict,
upsert: updateOne.upsert,
arrayFilters: updateOne.arrayFilters
arrayFilters: updateOne.arrayFilters,
overwriteDiscriminatorKey: updateOne.overwriteDiscriminatorKey
}, model, updateOne['filter']);

return updateOne;
Expand Down Expand Up @@ -206,7 +207,8 @@ module.exports.castUpdateMany = function castUpdateMany(originalModel, updateMan
updateMany['update'] = castUpdate(model.schema, updateMany['update'], {
strict: strict,
upsert: updateMany.upsert,
arrayFilters: updateMany.arrayFilters
arrayFilters: updateMany.arrayFilters,
overwriteDiscriminatorKey: updateMany.overwriteDiscriminatorKey
}, model, updateMany['filter']);
};

Expand Down
6 changes: 1 addition & 5 deletions lib/helpers/populate/assignVals.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ function numDocs(v) {

function valueFilter(val, assignmentOpts, populateOptions, allIds) {
const userSpecifiedTransform = typeof populateOptions.transform === 'function';
const transform = userSpecifiedTransform ? populateOptions.transform : noop;
const transform = userSpecifiedTransform ? populateOptions.transform : v => v;
if (Array.isArray(val)) {
// find logic
const ret = [];
Expand Down Expand Up @@ -341,7 +341,3 @@ function isPopulatedObject(obj) {
obj.$__ != null ||
leanPopulateMap.has(obj);
}

function noop(v) {
return v;
}
19 changes: 19 additions & 0 deletions lib/helpers/populate/getModelsMapForPopulate.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
if (hasMatchFunction) {
match = match.call(doc, doc);
}
if (Array.isArray(match)) {
for (const item of match) {
if (item != null && item.$where) {
throw new MongooseError('Cannot use $where filter with populate() match');
}
}
} else if (match != null && match.$where != null) {
throw new MongooseError('Cannot use $where filter with populate() match');
}
data.match = match;
data.hasMatchFunction = hasMatchFunction;
data.isRefPath = isRefPath;
Expand Down Expand Up @@ -454,6 +463,16 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
data.match = match;
data.hasMatchFunction = hasMatchFunction;

if (Array.isArray(match)) {
for (const item of match) {
if (item != null && item.$where) {
throw new MongooseError('Cannot use $where filter with populate() match');
}
}
} else if (match != null && match.$where != null) {
throw new MongooseError('Cannot use $where filter with populate() match');
}

// Get local fields
const ret = _getLocalFieldValues(doc, localField, model, options, virtual);

Expand Down
22 changes: 22 additions & 0 deletions lib/helpers/query/castUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const ValidationError = require('../../error/validation');
const castNumber = require('../../cast/number');
const cast = require('../../cast');
const getConstructorName = require('../getConstructorName');
const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
const getEmbeddedDiscriminatorPath = require('./getEmbeddedDiscriminatorPath');
const handleImmutable = require('./handleImmutable');
const moveImmutableProperties = require('../update/moveImmutableProperties');
Expand Down Expand Up @@ -62,6 +63,27 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
return obj;
}

if (schema != null &&
filter != null &&
utils.hasUserDefinedProperty(filter, schema.options.discriminatorKey) &&
typeof filter[schema.options.discriminatorKey] !== 'object' &&
schema.discriminators != null) {
const discriminatorValue = filter[schema.options.discriminatorKey];
const byValue = getDiscriminatorByValue(context.model.discriminators, discriminatorValue);
schema = schema.discriminators[discriminatorValue] ||
(byValue && byValue.schema) ||
schema;
} else if (schema != null &&
options.overwriteDiscriminatorKey &&
utils.hasUserDefinedProperty(obj, schema.options.discriminatorKey) &&
schema.discriminators != null) {
const discriminatorValue = obj[schema.options.discriminatorKey];
const byValue = getDiscriminatorByValue(context.model.discriminators, discriminatorValue);
schema = schema.discriminators[discriminatorValue] ||
(byValue && byValue.schema) ||
schema;
}

if (options.upsert) {
moveImmutableProperties(schema, obj, context);
}
Expand Down
8 changes: 7 additions & 1 deletion lib/helpers/query/getEmbeddedDiscriminatorPath.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p
const updatedPathsByFilter = updatedPathsByArrayFilter(update);

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

const discriminatorKeyUpdatePath = originalSubpath + '.' + key;
if (discriminatorKeyUpdatePath in update) {
discriminatorKey = update[discriminatorKeyUpdatePath];
}

if (discriminatorValuePath in update) {
discriminatorKey = update[discriminatorValuePath];
}
Expand Down
2 changes: 1 addition & 1 deletion lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -3147,7 +3147,7 @@ function _setIsNew(doc, val) {
doc.$emit('isNew', val);
doc.constructor.emit('isNew', val);

const subdocs = doc.$getAllSubdocs();
const subdocs = doc.$getAllSubdocs({ useCache: true });
for (const subdoc of subdocs) {
subdoc.$isNew = val;
subdoc.$emit('isNew', val);
Expand Down
2 changes: 2 additions & 0 deletions lib/options/saveOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ class SaveOptions {
}
}

SaveOptions.prototype.__subdocs = null;

module.exports = SaveOptions;
8 changes: 6 additions & 2 deletions lib/plugins/saveSubdocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = function saveSubdocs(schema) {
}

const _this = this;
const subdocs = this.$getAllSubdocs();
const subdocs = this.$getAllSubdocs({ useCache: true });

if (!subdocs.length) {
next();
Expand All @@ -27,6 +27,10 @@ module.exports = function saveSubdocs(schema) {
cb(err);
});
}, function(error) {
// Invalidate subdocs cache because subdoc pre hooks can add new subdocuments
if (_this.$__.saveOptions) {
_this.$__.saveOptions.__subdocs = null;
}
if (error) {
return _this.$__schema.s.hooks.execPost('save:error', _this, [_this], { error: error }, function(error) {
next(error);
Expand Down Expand Up @@ -64,7 +68,7 @@ module.exports = function saveSubdocs(schema) {
}

const _this = this;
const subdocs = this.$getAllSubdocs();
const subdocs = this.$getAllSubdocs({ useCache: true });

if (!subdocs.length) {
return;
Expand Down
Loading

0 comments on commit 9097ef2

Please sign in to comment.