Skip to content

Commit

Permalink
Merge branch 'master' into 8.7
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Sep 25, 2024
2 parents 45bb194 + c6bd31d commit adb4fb0
Show file tree
Hide file tree
Showing 36 changed files with 537 additions and 200 deletions.
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
7.8.2 / 2024-09-25
==================
* fix(projection): avoid setting projection to unknown exclusive/inclusive if elemMatch on a Date, ObjectId, etc. #14894 #14893

8.6.3 / 2024-09-17
==================
* fix: make getters convert uuid to string when calling toObject() and toJSON() #14890 #14869
* fix: fix missing Aggregate re-exports for ESM #14886 [wongsean](https://github.com/wongsean)
* types(document): add generic param to depopulate() to allow updating properties #14891 #14876

6.13.2 / 2024-09-12
===================
* fix(document): make set() respect merge option on deeply nested objects #14870 #14878

8.6.2 / 2024-09-11
==================
* fix: make set merge deeply nested objects #14870 #14861 [ianHeydoc](https://github.com/ianHeydoc)
Expand Down Expand Up @@ -108,6 +122,7 @@
==================
* feat(model): add throwOnValidationError option for opting into getting MongooseBulkWriteError if all valid operations succeed in bulkWrite() and insertMany() #14599 #14587 #14572 #13410

<<<<<<< HEAD
8.4.3 / 2024-06-17
==================
* fix: remove 0x flamegraph files from release
Expand All @@ -120,6 +135,11 @@
* fix(connection): fix up some inconsistencies in operation-end event and add to docs #14659 #14648
* types: avoid inferring Boolean, Buffer, ObjectId as Date in schema definitions under certain circumstances #14667 #14630
* docs: add note about parallelism in transations #14647 [fiws](https://github.com/fiws)
=======
6.13.1 / 2024-09-06
===================
* fix: remove empty $and, $or, $not that were made empty by scrict mode #14749 #13086 [0x0a0d](https://github.com/0x0a0d)
>>>>>>> 7.x

6.13.0 / 2024-06-06
===================
Expand Down Expand Up @@ -623,7 +643,7 @@
==================
* perf: speed up mapOfSubdocs benchmark by 4x by avoiding unnecessary O(n^2) loop in getPathsToValidate() #13614
* feat: upgrade to MongoDB Node.js driver 5.7.0 #13591
* feat: add `id` setter which allows modifying `_id` by setting `id` (Note this change was reverted in Mongoose 8) #13517
* BREAKING CHANGE: add `id` setter which allows modifying `_id` by setting `id` (Note this change was originally shipped as a `feat`, but later reverted in Mongoose 8 due to compatibility issues) #13517
* feat: support generating custom cast error message with a function #13608 #3162
* feat(query): support MongoDB driver's includeResultMetadata option for findOneAndUpdate #13584 #13539
* feat(connection): add Connection.prototype.removeDb() for removing a related connection #13580 #11821
Expand Down
37 changes: 37 additions & 0 deletions benchmarks/createDeepNestedDocArray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'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 levels = 12;

let schema = new mongoose.Schema({ test: { type: String, required: true } });
let doc = { test: 'gh-14897' };
for (let i = 0; i < levels; ++i) {
schema = new mongoose.Schema({ level: Number, subdocs: [schema] });
doc = { level: (levels - i), subdocs: [{ ...doc }, { ...doc }] };
}
const Test = mongoose.model('Test', schema);

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

const insertStart = Date.now();
await Test.create(doc);
const insertEnd = Date.now();

const results = {
'create() time ms': +(insertEnd - insertStart).toFixed(2)
};

console.log(JSON.stringify(results, null, ' '));
process.exit(0);
}
11 changes: 4 additions & 7 deletions docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ In document middleware functions, `this` refers to the document. To access the m

* [validate](api/document.html#document_Document-validate)
* [save](api/model.html#model_Model-save)
* [remove](api/model.html#model_Model-remove)
* [updateOne](api/document.html#document_Document-updateOne)
* [deleteOne](api/model.html#model_Model-deleteOne)
* [init](api/document.html#document_Document-init) (note: init hooks are [synchronous](#synchronous))
Expand All @@ -50,7 +49,6 @@ In query middleware functions, `this` refers to the query.
* [findOneAndDelete](api/query.html#query_Query-findOneAndDelete)
* [findOneAndReplace](api/query.html#query_Query-findOneAndReplace)
* [findOneAndUpdate](api/query.html#query_Query-findOneAndUpdate)
* [remove](api/model.html#model_Model-remove)
* [replaceOne](api/query.html#query_Query-replaceOne)
* [updateOne](api/query.html#query_Query-updateOne)
* [updateMany](api/query.html#query_Query-updateMany)
Expand Down Expand Up @@ -87,7 +85,6 @@ Here are the possible strings that can be passed to `pre()`
* findOneAndUpdate
* init
* insertMany
* remove
* replaceOne
* save
* update
Expand Down Expand Up @@ -400,11 +397,11 @@ Mongoose has both query and document hooks for `deleteOne()`.
```javascript
schema.pre('deleteOne', function() { console.log('Removing!'); });

// Does **not** print "Removing!". Document middleware for `remove` is not executed by default
// Does **not** print "Removing!". Document middleware for `deleteOne` is not executed by default
await doc.deleteOne();

// Prints "Removing!"
Model.remove();
await Model.deleteOne();
```

You can pass options to [`Schema.pre()`](api.html#schema_Schema-pre)
Expand All @@ -418,8 +415,8 @@ schema.pre('deleteOne', { document: true, query: false }, function() {
console.log('Deleting doc!');
});

// Only query middleware. This will get called when you do `Model.remove()`
// but not `doc.remove()`.
// Only query middleware. This will get called when you do `Model.deleteOne()`
// but not `doc.deleteOne()`.
schema.pre('deleteOne', { query: true, document: false }, function() {
console.log('Deleting!');
});
Expand Down
2 changes: 1 addition & 1 deletion docs/subdocs.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ doc.child;
```

<ul class="toc">
<li><a href="#what-is-a-subdocument-">What is a Subdocument?</a></li>
<li><a href="#what-is-a-subdocument">What is a Subdocument?</a></li>
<li><a href="#subdocuments-versus-nested-paths">Subdocuments versus Nested Paths</a></li>
<li><a href="#subdocument-defaults">Subdocument Defaults</a></li>
<li><a href="#finding-a-subdocument">Finding a Subdocument</a></li>
Expand Down
12 changes: 11 additions & 1 deletion lib/cast.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,27 @@ module.exports = function cast(schema, obj, options, context) {
if (!Array.isArray(val)) {
throw new CastError('Array', val, path);
}
for (let k = 0; k < val.length; ++k) {
for (let k = val.length - 1; k >= 0; k--) {
if (val[k] == null || typeof val[k] !== 'object') {
throw new CastError('Object', val[k], path + '.' + k);
}
const beforeCastKeysLength = Object.keys(val[k]).length;
const discriminatorValue = val[k][schema.options.discriminatorKey];
if (discriminatorValue == null) {
val[k] = cast(schema, val[k], options, context);
} else {
const discriminatorSchema = getSchemaDiscriminatorByValue(context.schema, discriminatorValue);
val[k] = cast(discriminatorSchema ? discriminatorSchema : schema, val[k], options, context);
}

if (Object.keys(val[k]).length === 0 && beforeCastKeysLength !== 0) {
val.splice(k, 1);
}
}

// delete empty: {$or: []} -> {}
if (val.length === 0) {
delete obj[path];
}
} else if (path === '$where') {
type = typeof val;
Expand Down
37 changes: 37 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,40 @@ const queryMiddlewareFunctions = queryOperations.concat([
]);

exports.queryMiddlewareFunctions = queryMiddlewareFunctions;

/*!
* ignore
*/

const aggregateMiddlewareFunctions = [
'aggregate'
];

exports.aggregateMiddlewareFunctions = aggregateMiddlewareFunctions;

/*!
* ignore
*/

const modelMiddlewareFunctions = [
'bulkWrite',
'createCollection',
'insertMany'
];

exports.modelMiddlewareFunctions = modelMiddlewareFunctions;

/*!
* ignore
*/

const documentMiddlewareFunctions = [
'validate',
'save',
'remove',
'updateOne',
'deleteOne',
'init'
];

exports.documentMiddlewareFunctions = documentMiddlewareFunctions;
99 changes: 35 additions & 64 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -2689,7 +2689,7 @@ function _evaluateRequiredFunctions(doc) {
* ignore
*/

function _getPathsToValidate(doc, pathsToValidate, pathsToSkip) {
function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate) {
const doValidateOptions = {};

_evaluateRequiredFunctions(doc);
Expand All @@ -2709,35 +2709,40 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip) {
Object.keys(doc.$__.activePaths.getStatePaths('default')).forEach(addToPaths);
function addToPaths(p) { paths.add(p); }

const subdocs = doc.$getAllSubdocs();
const modifiedPaths = doc.modifiedPaths();
for (const subdoc of subdocs) {
if (subdoc.$basePath) {
const fullPathToSubdoc = subdoc.$isSingleNested ? subdoc.$__pathRelativeToParent() : subdoc.$__fullPathWithIndexes();

// Remove child paths for now, because we'll be validating the whole
// subdoc.
// The following is a faster take on looping through every path in `paths`
// and checking if the path starts with `fullPathToSubdoc` re: gh-13191
for (const modifiedPath of subdoc.modifiedPaths()) {
paths.delete(fullPathToSubdoc + '.' + modifiedPath);
}
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 modifiedPaths = doc.modifiedPaths();
for (const subdoc of subdocs) {
if (subdoc.$basePath) {
const fullPathToSubdoc = subdoc.$isSingleNested ? subdoc.$__pathRelativeToParent() : subdoc.$__fullPathWithIndexes();

// Remove child paths for now, because we'll be validating the whole
// subdoc.
// The following is a faster take on looping through every path in `paths`
// and checking if the path starts with `fullPathToSubdoc` re: gh-13191
for (const modifiedPath of subdoc.modifiedPaths()) {
paths.delete(fullPathToSubdoc + '.' + modifiedPath);
}

if (doc.$isModified(fullPathToSubdoc, null, modifiedPaths) &&
!doc.isDirectModified(fullPathToSubdoc) &&
!doc.$isDefault(fullPathToSubdoc)) {
paths.add(fullPathToSubdoc);
if (doc.$isModified(fullPathToSubdoc, null, modifiedPaths) &&
// Avoid using isDirectModified() here because that does additional checks on whether the parent path
// is direct modified, which can cause performance issues re: gh-14897
!doc.$__.activePaths.getStatePaths('modify').hasOwnProperty(fullPathToSubdoc) &&
!doc.$isDefault(fullPathToSubdoc)) {
paths.add(fullPathToSubdoc);

if (doc.$__.pathsToScopes == null) {
doc.$__.pathsToScopes = {};
}
doc.$__.pathsToScopes[fullPathToSubdoc] = subdoc.$isDocumentArrayElement ?
subdoc.__parentArray :
subdoc.$parent();
if (doc.$__.pathsToScopes == null) {
doc.$__.pathsToScopes = {};
}
doc.$__.pathsToScopes[fullPathToSubdoc] = subdoc.$isDocumentArrayElement ?
subdoc.__parentArray :
subdoc.$parent();

doValidateOptions[fullPathToSubdoc] = { skipSchemaValidators: true };
if (subdoc.$isDocumentArrayElement && subdoc.__index != null) {
doValidateOptions[fullPathToSubdoc].index = subdoc.__index;
doValidateOptions[fullPathToSubdoc] = { skipSchemaValidators: true };
if (subdoc.$isDocumentArrayElement && subdoc.__index != null) {
doValidateOptions[fullPathToSubdoc].index = subdoc.__index;
}
}
}
}
Expand Down Expand Up @@ -2972,7 +2977,7 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
paths = [...paths];
doValidateOptionsByPath = {};
} else {
const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip);
const pathDetails = _getPathsToValidate(this, pathsToValidate, pathsToSkip, options && options._nestedValidate);
paths = shouldValidateModifiedOnly ?
pathDetails[0].filter((path) => this.$isModified(path)) :
pathDetails[0];
Expand Down Expand Up @@ -3059,7 +3064,8 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
const doValidateOptions = {
...doValidateOptionsByPath[path],
path: path,
validateAllPaths
validateAllPaths,
_nestedValidate: true
};

schemaType.doValidate(val, function(err) {
Expand Down Expand Up @@ -3478,44 +3484,9 @@ Document.prototype.$__reset = function reset() {
// Skip for subdocuments
const subdocs = !this.$isSubdocument ? this.$getAllSubdocs() : null;
if (subdocs && subdocs.length > 0) {
const resetArrays = new Set();
for (const subdoc of subdocs) {
const fullPathWithIndexes = subdoc.$__fullPathWithIndexes();
subdoc.$__reset();
if (this.isModified(fullPathWithIndexes) || isParentInit(fullPathWithIndexes)) {
if (subdoc.$isDocumentArrayElement) {
resetArrays.add(subdoc.parentArray());
} else {
const parent = subdoc.$parent();
if (parent === this) {
this.$__.activePaths.clearPath(subdoc.$basePath);
} else if (parent != null && parent.$isSubdocument) {
// If map path underneath subdocument, may end up with a case where
// map path is modified but parent still needs to be reset. See gh-10295
parent.$__reset();
}
}
}
}

for (const array of resetArrays) {
this.$__.activePaths.clearPath(array.$path());
array[arrayAtomicsBackupSymbol] = array[arrayAtomicsSymbol];
array[arrayAtomicsSymbol] = {};
}
}

function isParentInit(path) {
path = path.indexOf('.') === -1 ? [path] : path.split('.');
let cur = '';
for (let i = 0; i < path.length; ++i) {
cur += (cur.length ? '.' : '') + path[i];
if (_this.$__.activePaths[cur] === 'init') {
return true;
}
}

return false;
}

// clear atomics
Expand Down
7 changes: 4 additions & 3 deletions lib/error/browserMissingSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

const MongooseError = require('./mongooseError');

/**
* MissingSchema Error constructor.
*/

class MissingSchemaError extends MongooseError {
/**
* MissingSchema Error constructor.
*/

constructor() {
super('Schema hasn\'t been registered for document.\n'
+ 'Use mongoose.Document(name, schema)');
Expand Down
12 changes: 7 additions & 5 deletions lib/error/divergentArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

const MongooseError = require('./mongooseError');

/**
* DivergentArrayError constructor.
* @param {Array<String>} paths
* @api private
*/

class DivergentArrayError extends MongooseError {
/**
* DivergentArrayError constructor.
* @param {Array<String>} paths
* @api private
*/

constructor(paths) {
const msg = 'For your own good, using `document.save()` to update an array '
+ 'which was selected using an $elemMatch projection OR '
Expand Down
Loading

0 comments on commit adb4fb0

Please sign in to comment.