Skip to content

Commit

Permalink
Merge branch '7.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Mar 15, 2024
2 parents 5ea6e38 + 9afba5f commit 7c726c2
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 41 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,8 @@
7.6.10 / 2024-03-13
===================
* docs(model): add extra note about lean option for insertMany() skipping casting #14415
* docs(mongoose): add options.overwriteModel details to mongoose.model() docs #14422

8.2.1 / 2024-03-04
==================
* fix(document): make $clone avoid converting subdocs into POJOs #14395 #14353
Expand All @@ -7,13 +12,21 @@
* types: missing typescript details on options params of updateMany, updateOne, etc. #14382 #14379 #14378 [FaizBShah](https://github.com/FaizBShah) [sderrow](https://github.com/sderrow)
* types: allow Record<string, string> as valid query select argument #14371 [sderrow](https://github.com/sderrow)

6.12.7 / 2024-03-01
===================
* perf(model): make insertMany() lean option skip hydrating Mongoose docs #14376 #14372
* perf(document+schema): small optimizations to make init() faster #14383 #14113
* fix(connection): don't modify passed options object to `openUri()` #14370 #13376 #13335
* fix(ChangeStream): bubble up resumeTokenChanged changeStream event #14355 #14349 [3150](https://github.com/3150)

7.6.9 / 2024-02-26
==================
* fix(document): handle embedded recursive discriminators on nested path defined using Schema.prototype.discriminator #14256 #14245
* types(model): correct return type for findByIdAndDelete() #14233 #14190
* docs(connections): add note about using asPromise() with createConnection() for error handling #14364 #14266
* docs(model+query+findoneandupdate): add more details about overwriteDiscriminatorKey option to docs #14264 #14246

<<<<<<< HEAD
8.2.0 / 2024-02-22
==================
* feat(model): add recompileSchema() function to models to allow applying schema changes after compiling #14306 #14296
Expand Down Expand Up @@ -71,6 +84,12 @@
* docs: update TLS/SSL guide for Mongoose v8 - MongoDB v6 driver deprecations #14170 [andylwelch](https://github.com/andylwelch)
* docs: update findOneAndUpdate tutorial to use includeResultMetadata #14208 #14207
* docs: clarify disabling _id on subdocs #14195 #14194
=======
6.12.6 / 2024-01-22
===================
* fix(collection): correctly handle buffer timeouts with find() #14277
* fix(document): allow calling push() with different $position arguments #14254
>>>>>>> 7.x

7.6.8 / 2024-01-08
==================
Expand Down
28 changes: 15 additions & 13 deletions lib/document.js
Expand Up @@ -742,7 +742,7 @@ function init(self, obj, doc, opts, prefix) {
if (i === '__proto__' || i === 'constructor') {
return;
}
path = prefix + i;
path = prefix ? prefix + i : i;
schemaType = docSchema.path(path);
// Should still work if not a model-level discriminator, but should not be
// necessary. This is *only* to catch the case where we queried using the
Expand All @@ -751,37 +751,39 @@ function init(self, obj, doc, opts, prefix) {
return;
}

if (!schemaType && utils.isPOJO(obj[i])) {
const value = obj[i];
if (!schemaType && utils.isPOJO(value)) {
// assume nested object
if (!doc[i]) {
doc[i] = {};
if (!strict && !(i in docSchema.tree) && !(i in docSchema.methods) && !(i in docSchema.virtuals)) {
self[i] = doc[i];
}
}
init(self, obj[i], doc[i], opts, path + '.');
init(self, value, doc[i], opts, path + '.');
} else if (!schemaType) {
doc[i] = obj[i];
doc[i] = value;
if (!strict && !prefix) {
self[i] = obj[i];
self[i] = value;
}
} else {
// Retain order when overwriting defaults
if (doc.hasOwnProperty(i) && obj[i] !== void 0 && !opts.hydratedPopulatedDocs) {
if (doc.hasOwnProperty(i) && value !== void 0 && !opts.hydratedPopulatedDocs) {
delete doc[i];
}
if (obj[i] === null) {
if (value === null) {
doc[i] = schemaType._castNullish(null);
} else if (obj[i] !== undefined) {
const wasPopulated = obj[i].$__ == null ? null : obj[i].$__.wasPopulated;
if ((schemaType && !wasPopulated) && !opts.hydratedPopulatedDocs) {
} else if (value !== undefined) {
const wasPopulated = value.$__ == null ? null : value.$__.wasPopulated;

if (schemaType && !wasPopulated && !opts.hydratedPopulatedDocs) {
try {
if (opts && opts.setters) {
// Call applySetters with `init = false` because otherwise setters are a noop
const overrideInit = false;
doc[i] = schemaType.applySetters(obj[i], self, overrideInit);
doc[i] = schemaType.applySetters(value, self, overrideInit);
} else {
doc[i] = schemaType.cast(obj[i], self, true);
doc[i] = schemaType.cast(value, self, true);
}
} catch (e) {
self.invalidate(e.path, new ValidatorError({
Expand All @@ -793,7 +795,7 @@ function init(self, obj, doc, opts, prefix) {
}));
}
} else {
doc[i] = obj[i];
doc[i] = value;
}
}
// mark as hydrated
Expand Down
7 changes: 3 additions & 4 deletions lib/drivers/node-mongodb-native/collection.js
Expand Up @@ -136,11 +136,10 @@ function iter(i) {
let promise = null;
let timeout = null;
if (syncCollectionMethods[i] && typeof lastArg === 'function') {
this.addQueue(() => {
lastArg.call(this, null, this[i].apply(this, _args.slice(0, _args.length - 1)));
}, []);
this.addQueue(i, _args);
callback = lastArg;
} else if (syncCollectionMethods[i]) {
promise = new Promise((resolve, reject) => {
promise = new this.Promise((resolve, reject) => {
callback = function collectionOperationCallback(err, res) {
if (timeout != null) {
clearTimeout(timeout);
Expand Down
22 changes: 17 additions & 5 deletions lib/model.js
Expand Up @@ -3085,7 +3085,7 @@ Model.startSession = function() {
* @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany)
* @param {Boolean} [options.ordered=true] if true, will fail fast on the first error encountered. If false, will insert all the documents it can and report errors later. An `insertMany()` with `ordered = false` is called an "unordered" `insertMany()`.
* @param {Boolean} [options.rawResult=false] if false, the returned promise resolves to the documents that passed mongoose document validation. If `true`, will return the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/InsertManyResult.html) with a `mongoose` property that contains `validationErrors` and `results` if this is an unordered `insertMany`.
* @param {Boolean} [options.lean=false] if `true`, skips hydrating and validating the documents. This option is useful if you need the extra performance, but Mongoose won't validate the documents before inserting.
* @param {Boolean} [options.lean=false] if `true`, skips hydrating the documents. This means Mongoose will **not** cast or validate any of the documents passed to `insertMany()`. This option is useful if you need the extra performance, but comes with data integrity risk. Consider using with [`castObject()`](https://mongoosejs.com/docs/api/model.html#Model.castObject()).
* @param {Number} [options.limit=null] this limits the number of documents being processed (validation/casting) by mongoose in parallel, this does **NOT** send the documents in batches to MongoDB. Use this option if you're processing a large number of documents and your app is running out of memory.
* @param {String|Object|Array} [options.populate=null] populates the result documents. This option is a no-op if `rawResult` is set.
* @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully.
Expand Down Expand Up @@ -3146,6 +3146,13 @@ Model.$__insertMany = function(arr, options, callback) {
const results = ordered ? null : new Array(arr.length);
const toExecute = arr.map((doc, index) =>
callback => {
// If option `lean` is set to true bypass validation and hydration
if (lean) {
// we have to execute callback at the nextTick to be compatible
// with parallelLimit, as `results` variable has TDZ issue if we
// execute the callback synchronously
return immediate(() => callback(null, doc));
}
if (!(doc instanceof _this)) {
if (doc != null && typeof doc !== 'object') {
return callback(new ObjectParameterError(doc, 'arr.' + index, 'insertMany'));
Expand Down Expand Up @@ -3226,7 +3233,7 @@ Model.$__insertMany = function(arr, options, callback) {
callback(null, []);
return;
}
const docObjects = docAttributes.map(function(doc) {
const docObjects = lean ? docAttributes : docAttributes.map(function(doc) {
if (doc.$__schema.options.versionKey) {
doc[doc.$__schema.options.versionKey] = 0;
}
Expand All @@ -3239,9 +3246,11 @@ Model.$__insertMany = function(arr, options, callback) {

_this.$__collection.insertMany(docObjects, options).then(
res => {
for (const attribute of docAttributes) {
attribute.$__reset();
_setIsNew(attribute, false);
if (!lean) {
for (const attribute of docAttributes) {
attribute.$__reset();
_setIsNew(attribute, false);
}
}

if (ordered === false && throwOnValidationError && validationErrors.length > 0) {
Expand Down Expand Up @@ -3343,6 +3352,9 @@ Model.$__insertMany = function(arr, options, callback) {
return !isErrored;
}).
map(function setIsNewForInsertedDoc(doc) {
if (lean) {
return doc;
}
doc.$__reset();
_setIsNew(doc, false);
return doc;
Expand Down
6 changes: 4 additions & 2 deletions lib/mongoose.js
Expand Up @@ -506,12 +506,14 @@ Mongoose.prototype.pluralize = function(fn) {
*
* // or
*
* const collectionName = 'actor'
* const M = mongoose.model('Actor', schema, collectionName)
* const collectionName = 'actor';
* const M = mongoose.model('Actor', schema, collectionName);
*
* @param {String|Function} name model name or class extending Model
* @param {Schema} [schema] the schema to use.
* @param {String} [collection] name (optional, inferred from model name)
* @param {Object} [options]
* @param {Boolean} [options.overwriteModels=false] If true, overwrite existing models with the same name to avoid `OverwriteModelError`
* @return {Model} The model associated with `name`. Mongoose will create the model if it doesn't already exist.
* @api public
*/
Expand Down
3 changes: 3 additions & 0 deletions lib/schema.js
Expand Up @@ -1014,6 +1014,9 @@ reserved.collection = 1;

Schema.prototype.path = function(path, obj) {
if (obj === undefined) {
if (this.paths[path] != null) {
return this.paths[path];
}
// Convert to '.$' to check subpaths re: gh-6405
const cleanPath = _pathToPositionalSyntax(path);
let schematype = _getPath(this, path, cleanPath);
Expand Down
17 changes: 8 additions & 9 deletions lib/types/array/methods/index.js
Expand Up @@ -698,22 +698,21 @@ const methods = {

if ((atomics.$push && atomics.$push.$each && atomics.$push.$each.length || 0) !== 0 &&
atomics.$push.$position != atomic.$position) {
throw new MongooseError('Cannot call `Array#push()` multiple times ' +
'with different `$position`');
}
if (atomic.$position != null) {
[].splice.apply(arr, [atomic.$position, 0].concat(values));
ret = arr.length;
} else {
ret = [].push.apply(arr, values);
}

if (atomic.$position != null) {
this._registerAtomic('$set', this);
} else if (atomic.$position != null) {
[].splice.apply(arr, [atomic.$position, 0].concat(values));
ret = this.length;
} else {
ret = [].push.apply(arr, values);
}
} else {
if ((atomics.$push && atomics.$push.$each && atomics.$push.$each.length || 0) !== 0 &&
atomics.$push.$position != null) {
throw new MongooseError('Cannot call `Array#push()` multiple times ' +
'with different `$position`');
}
atomic = values;
ret = _basePush.apply(arr, values);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/types/arraySubdocument.js
Expand Up @@ -145,7 +145,7 @@ ArraySubdocument.prototype.$__fullPath = function(path, skipIndex) {
*/

ArraySubdocument.prototype.$__pathRelativeToParent = function(path, skipIndex) {
if (this.__index == null) {
if (this.__index == null || (!this.__parentArray || !this.__parentArray.$path)) {
return null;
}
if (skipIndex) {
Expand Down
13 changes: 13 additions & 0 deletions test/collection.test.js
Expand Up @@ -51,6 +51,19 @@ describe('collections:', function() {
await db.close();
});

it('returns a promise if buffering and callback with find() (gh-14184)', function(done) {
db = mongoose.createConnection();
const collection = db.collection('gh14184');
collection.opts.bufferTimeoutMS = 100;

collection.find({ foo: 'bar' }, {}, (err, docs) => {
assert.ok(err);
assert.ok(err.message.includes('buffering timed out after 100ms'));
assert.equal(docs, undefined);
done();
});
});

it('methods should that throw (unimplemented)', function() {
const collection = new Collection('test', mongoose.connection);
let thrown = false;
Expand Down
15 changes: 8 additions & 7 deletions test/document.test.js
Expand Up @@ -8172,7 +8172,7 @@ describe('document', function() {
assert.deepEqual(Object.keys(err.errors), ['age']);
});

it('array push with $position (gh-4322)', async function() {
it('array push with $position (gh-14244) (gh-4322)', async function() {
const schema = Schema({
nums: [Number]
});
Expand All @@ -8196,12 +8196,13 @@ describe('document', function() {
$each: [0],
$position: 0
});
assert.throws(() => {
doc.nums.push({ $each: [5] });
}, /Cannot call.*multiple times/);
assert.throws(() => {
doc.nums.push(5);
}, /Cannot call.*multiple times/);
assert.deepStrictEqual(doc.nums.$__getAtomics(), [['$push', { $each: [0], $position: 0 }]]);

doc.nums.push({ $each: [5] });
assert.deepStrictEqual(doc.nums.$__getAtomics(), [['$set', [0, 1, 2, 3, 4, 5]]]);

doc.nums.push({ $each: [0.5], $position: 1 });
assert.deepStrictEqual(doc.nums.$__getAtomics(), [['$set', [0, 0.5, 1, 2, 3, 4, 5]]]);
});

it('setting a path to a single nested document should update the single nested doc parent (gh-8400)', function() {
Expand Down
14 changes: 14 additions & 0 deletions test/model.test.js
Expand Up @@ -3592,6 +3592,20 @@ describe('Model', function() {

await db.close();
});

it('bubbles up resumeTokenChanged events (gh-14349)', async function() {
const MyModel = db.model('Test', new Schema({ name: String }));

const resumeTokenChangedEvent = new Promise(resolve => {
changeStream = MyModel.watch();
listener = data => resolve(data);
changeStream.once('resumeTokenChanged', listener);
});

await MyModel.create({ name: 'test' });
const { _data } = await resumeTokenChangedEvent;
assert.ok(_data);
});
});

describe('sessions (gh-6362)', function() {
Expand Down

0 comments on commit 7c726c2

Please sign in to comment.