Skip to content

Commit

Permalink
Merge branch 'master' into 8.0
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Oct 19, 2023
2 parents f6e5a50 + 8831f03 commit eefe935
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 34 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
7.6.3 / 2023-10-17
==================
* fix(populate): handle multiple spaces when specifying paths to populate using space-delimited paths #13984 #13951
* fix(update): avoid applying defaults on query filter when upserting with empty update #13983 #13962
* fix(model): add versionKey to bulkWrite when inserting or upserting #13981 #13944
* docs: fix typo in timestamps docs #13976 [danielcoker](https://github.com/danielcoker)

7.6.2 / 2023-10-13
==================
* perf: avoid storing a separate entry in schema subpaths for every element in an array #13953 #13874
Expand Down
21 changes: 21 additions & 0 deletions lib/helpers/model/castBulkWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const applyTimestampsToChildren = require('../update/applyTimestampsToChildren')
const applyTimestampsToUpdate = require('../update/applyTimestampsToUpdate');
const cast = require('../../cast');
const castUpdate = require('../query/castUpdate');
const decorateUpdateWithVersionKey = require('../update/decorateUpdateWithVersionKey');
const { inspect } = require('util');
const setDefaultsOnInsert = require('../setDefaultsOnInsert');

Expand Down Expand Up @@ -33,6 +34,10 @@ module.exports = function castBulkWrite(originalModel, op, options) {
if (options.session != null) {
doc.$session(options.session);
}
const versionKey = model?.schema?.options?.versionKey;
if (versionKey && doc[versionKey] == null) {
doc[versionKey] = 0;
}
op['insertOne']['document'] = doc;

if (options.skipValidation || op['insertOne'].skipValidation) {
Expand Down Expand Up @@ -81,6 +86,12 @@ module.exports = function castBulkWrite(originalModel, op, options) {
});
}

decorateUpdateWithVersionKey(
op['updateOne']['update'],
op['updateOne'],
model.schema.options.versionKey
);

op['updateOne']['filter'] = cast(model.schema, op['updateOne']['filter'], {
strict: strict,
upsert: op['updateOne'].upsert
Expand Down Expand Up @@ -133,6 +144,12 @@ module.exports = function castBulkWrite(originalModel, op, options) {

_addDiscriminatorToObject(schema, op['updateMany']['filter']);

decorateUpdateWithVersionKey(
op['updateMany']['update'],
op['updateMany'],
model.schema.options.versionKey
);

op['updateMany']['filter'] = cast(model.schema, op['updateMany']['filter'], {
strict: strict,
upsert: op['updateMany'].upsert
Expand Down Expand Up @@ -173,6 +190,10 @@ module.exports = function castBulkWrite(originalModel, op, options) {
if (options.session != null) {
doc.$session(options.session);
}
const versionKey = model?.schema?.options?.versionKey;
if (versionKey && doc[versionKey] == null) {
doc[versionKey] = 0;
}
op['replaceOne']['replacement'] = doc;

if (options.skipValidation || op['replaceOne'].skipValidation) {
Expand Down
3 changes: 2 additions & 1 deletion lib/helpers/query/castUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
Object.keys(filter).length > 0) {
// Trick the driver into allowing empty upserts to work around
// https://github.com/mongodb/node-mongodb-native/pull/2490
return { $setOnInsert: filter };
// Shallow clone to avoid passing defaults in re: gh-13962
return { $setOnInsert: { ...filter } };
}
return ret;
};
Expand Down
26 changes: 26 additions & 0 deletions lib/helpers/update/decorateUpdateWithVersionKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

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

/**
* Decorate the update with a version key, if necessary
* @api private
*/

module.exports = function decorateUpdateWithVersionKey(update, options, versionKey) {
if (!versionKey || !(options && options.upsert || false)) {
return;
}

const updatedPaths = modifiedPaths(update);
if (!updatedPaths[versionKey]) {
if (options.overwrite) {
update[versionKey] = 0;
} else {
if (!update.$setOnInsert) {
update.$setOnInsert = {};
}
update.$setOnInsert[versionKey] = 0;
}
}
};
29 changes: 3 additions & 26 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const assignVals = require('./helpers/populate/assignVals');
const castBulkWrite = require('./helpers/model/castBulkWrite');
const clone = require('./helpers/clone');
const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter');
const decorateUpdateWithVersionKey = require('./helpers/update/decorateUpdateWithVersionKey');
const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue');
const discriminator = require('./helpers/model/discriminator');
Expand All @@ -55,7 +56,6 @@ const isPathExcluded = require('./helpers/projection/isPathExcluded');
const decorateDiscriminatorIndexOptions = require('./helpers/indexes/decorateDiscriminatorIndexOptions');
const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
const leanPopulateMap = require('./helpers/populate/leanPopulateMap');
const modifiedPaths = require('./helpers/update/modifiedPaths');
const parallelLimit = require('./helpers/parallelLimit');
const parentPaths = require('./helpers/path/parentPaths');
const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
Expand Down Expand Up @@ -2433,37 +2433,14 @@ Model.findOneAndUpdate = function(conditions, update, options) {
_isNested: true
});

_decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey);
decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey);

const mq = new this.Query({}, {}, this, this.$__collection);
mq.select(fields);

return mq.findOneAndUpdate(conditions, update, options);
};

/**
* Decorate the update with a version key, if necessary
* @api private
*/

function _decorateUpdateWithVersionKey(update, options, versionKey) {
if (!versionKey || !(options && options.upsert || false)) {
return;
}

const updatedPaths = modifiedPaths(update);
if (!updatedPaths[versionKey]) {
if (options.overwrite) {
update[versionKey] = 0;
} else {
if (!update.$setOnInsert) {
update.$setOnInsert = {};
}
update.$setOnInsert[versionKey] = 0;
}
}
}

/**
* Issues a mongodb findOneAndUpdate command by a document's _id field.
* `findByIdAndUpdate(id, ...)` is equivalent to `findOneAndUpdate({ _id: id }, ...)`.
Expand Down Expand Up @@ -3921,7 +3898,7 @@ function _update(model, op, conditions, doc, options) {
model.schema &&
model.schema.options &&
model.schema.options.versionKey || null;
_decorateUpdateWithVersionKey(doc, options, versionKey);
decorateUpdateWithVersionKey(doc, options, versionKey);

return mq[op](conditions, doc, options);
}
Expand Down
13 changes: 8 additions & 5 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ exports.isMongooseDocumentArray = isMongooseDocumentArray.isMongooseDocumentArra
exports.registerMongooseArray = isMongooseArray.registerMongooseArray;
exports.registerMongooseDocumentArray = isMongooseDocumentArray.registerMongooseDocumentArray;

const oneSpaceRE = /\s/;
const manySpaceRE = /\s+/;

/**
* Produces a collection name from model `name`. By default, just returns
* the model name
Expand Down Expand Up @@ -572,8 +575,8 @@ exports.populate = function populate(path, select, model, match, options, subPop
function makeSingles(arr) {
const ret = [];
arr.forEach(function(obj) {
if (/[\s]/.test(obj.path)) {
const paths = obj.path.split(' ');
if (oneSpaceRE.test(obj.path)) {
const paths = obj.path.split(manySpaceRE);
paths.forEach(function(p) {
const copy = Object.assign({}, obj);
copy.path = p;
Expand All @@ -592,9 +595,9 @@ function _populateObj(obj) {
if (Array.isArray(obj.populate)) {
const ret = [];
obj.populate.forEach(function(obj) {
if (/[\s]/.test(obj.path)) {
if (oneSpaceRE.test(obj.path)) {
const copy = Object.assign({}, obj);
const paths = copy.path.split(' ');
const paths = copy.path.split(manySpaceRE);
paths.forEach(function(p) {
copy.path = p;
ret.push(exports.populate(copy)[0]);
Expand All @@ -609,7 +612,7 @@ function _populateObj(obj) {
}

const ret = [];
const paths = obj.path.split(' ');
const paths = oneSpaceRE.test(obj.path) ? obj.path.split(manySpaceRE) : [obj.path];
if (obj.options != null) {
obj.options = clone(obj.options);
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mongoose",
"description": "Mongoose MongoDB ODM",
"version": "7.6.2",
"version": "7.6.3",
"author": "Guillermo Rauch <[email protected]>",
"keywords": [
"mongodb",
Expand Down
17 changes: 17 additions & 0 deletions test/model.findOneAndUpdate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2183,4 +2183,21 @@ describe('model: findOneAndUpdate:', function() {
assert.ok(document);
assert.equal(document.name, 'test');
});

it('skips adding defaults to filter when passing empty update (gh-13962)', async function() {
const schema = new Schema({
myField: Number,
defaultField: { type: String, default: 'default' }
}, { versionKey: false });
const Test = db.model('Test', schema);

await Test.create({ myField: 1, defaultField: 'some non-default value' });

const updated = await Test.findOneAndUpdate(
{ myField: 1 },
{},
{ upsert: true, returnDocument: 'after' }
);
assert.equal(updated.defaultField, 'some non-default value');
});
});
21 changes: 21 additions & 0 deletions test/model.populate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2819,7 +2819,28 @@ describe('model: populate:', function() {

assert.equal(blogposts[0].user.name, 'Fan 1');
assert.equal(blogposts[0].title, 'Test 1');
});

it('handles multiple spaces in between paths to populate (gh-13951)', async function() {
const BlogPost = db.model('BlogPost', new Schema({
title: String,
user: { type: ObjectId, ref: 'User' },
fans: [{ type: ObjectId, ref: 'User' }]
}));
const User = db.model('User', new Schema({ name: String }));

const fans = await User.create([{ name: 'Fan 1' }]);
const posts = [
{ title: 'Test 1', user: fans[0]._id, fans: [fans[0]._id] }
];
await BlogPost.create(posts);
const blogPost = await BlogPost.
findOne({ title: 'Test 1' }).
populate('user \t fans');

assert.equal(blogPost.user.name, 'Fan 1');
assert.equal(blogPost.fans[0].name, 'Fan 1');
assert.equal(blogPost.title, 'Test 1');
});

it('maps results back to correct document (gh-1444)', async function() {
Expand Down
43 changes: 43 additions & 0 deletions test/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4047,6 +4047,49 @@ describe('Model', function() {

});

it('sets version key (gh-13944)', async function() {
const userSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String }
});
const User = db.model('User', userSchema);

await User.bulkWrite([
{
updateOne: {
filter: { lastName: 'Gibbons' },
update: { firstName: 'Peter' },
upsert: true
}
},
{
insertOne: {
document: {
firstName: 'Michael',
lastName: 'Bolton'
}
}
},
{
replaceOne: {
filter: { lastName: 'Lumbergh' },
replacement: { firstName: 'Bill', lastName: 'Lumbergh' },
upsert: true
}
}
], { ordered: false });

const users = await User.find();
assert.deepStrictEqual(
users.map(user => user.firstName).sort(),
['Bill', 'Michael', 'Peter']
);
assert.deepStrictEqual(
users.map(user => user.__v),
[0, 0, 0]
);
});

it('with single nested and setOnInsert (gh-7534)', function() {
const nested = new Schema({ name: String });
const schema = new Schema({ nested: nested });
Expand Down
2 changes: 1 addition & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ declare module 'mongoose' {
/** Returns a copy of this schema */
clone<T = this>(): T;

discriminator<DisSchema = Schema>(name: string, schema: DisSchema): this;
discriminator<DisSchema = Schema>(name: string | number, schema: DisSchema): this;

/** Returns a new schema that has the picked `paths` from this schema. */
pick<T = this>(paths: string[], options?: SchemaOptions): T;
Expand Down

0 comments on commit eefe935

Please sign in to comment.