Skip to content

Commit

Permalink
Merge branch 'master' into 8.6
Browse files Browse the repository at this point in the history
  • Loading branch information
vkarpov15 committed Aug 1, 2024
2 parents 5db83ec + 986f5eb commit 220dd50
Show file tree
Hide file tree
Showing 19 changed files with 165 additions and 36 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ jobs:
matrix:
node: [16, 18, 20]
os: [ubuntu-20.04, ubuntu-22.04]
mongodb: [4.4.28, 5.0.25, 6.0.14, 7.0.7]
mongodb: [4.4.29, 5.0.26, 6.0.15, 7.0.12]
include:
- os: ubuntu-20.04 # customize on which matrix the coverage will be collected on
mongodb: 5.0.25
mongodb: 5.0.26
node: 16
coverage: true
exclude:
- os: ubuntu-22.04 # exclude because there are no 4.x mongodb builds for 2204
mongodb: 4.4.28
mongodb: 4.4.29
- os: ubuntu-22.04 # exclude because there are no 5.x mongodb builds for 2204
mongodb: 5.0.25
mongodb: 5.0.26
name: Node ${{ matrix.node }} MongoDB ${{ matrix.mongodb }} OS ${{ matrix.os }}
env:
MONGOMS_VERSION: ${{ matrix.mongodb }}
Expand Down Expand Up @@ -90,7 +90,7 @@ jobs:
runs-on: ubuntu-20.04
name: Deno tests
env:
MONGOMS_VERSION: 6.0.14
MONGOMS_VERSION: 6.0.15
MONGOMS_PREFER_GLOBAL_PATH: 1
FORCE_COLOR: true
steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tsd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 14
node-version: 16

- run: npm install

Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
8.5.2 / 2024-07-30
==================
* perf(clone): avoid further unnecessary checks if cloning a primitive value #14762 #14394
* fix: allow setting document array default to null #14769 #14717 #6691
* fix(model): support session: null option for save() to opt out of automatic session option with transactionAsyncLocalStorage #14744 #14736
* fix(model+document): avoid depopulating manually populated doc as getter value #14760 #14759
* fix: correct shardkey access in buildBulkWriteOps #14753 #14752 [adf0nt3s](https://github.com/adf0nt3s)
* fix(query): handle casting $switch in $expr #14755 #14751
* types: allow calling SchemaType.cast() without parent and init parameters #14756 #14748 #9076
* docs: fix a wrong example in v6 migration guide #14758 [abdelrahman-elkady](https://github.com/abdelrahman-elkady)

8.5.1 / 2024-07-12
==================
* perf(model): performance improvements for insertMany() #14724
Expand Down
2 changes: 1 addition & 1 deletion docs/migrating_to_6.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ Schema paths declared with `type: { name: String }` become single nested subdocs

```javascript
// In Mongoose 6, the below makes `foo` into a subdocument with a `name` property.
// In Mongoose 5, the below would make `foo` a `Mixed` type, _unless_ you set `typePojoToMixed: true`.
// In Mongoose 5, the below would make `foo` a `Mixed` type, _unless_ you set `typePojoToMixed: false`.
const schema = new Schema({
foo: { type: { name: String } }
});
Expand Down
14 changes: 7 additions & 7 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -1393,8 +1393,8 @@ Document.prototype.$set = function $set(path, val, type, options) {
})();

let didPopulate = false;
if (refMatches && val instanceof Document && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._id))) {
const unpopulatedValue = (schema && schema.$isSingleNested) ? schema.cast(val, this) : val._id;
if (refMatches && val instanceof Document && (!val.$__.wasPopulated || utils.deepEqual(val.$__.wasPopulated.value, val._doc._id))) {
const unpopulatedValue = (schema && schema.$isSingleNested) ? schema.cast(val, this) : val._doc._id;
this.$populated(path, unpopulatedValue, { [populateModelSymbol]: val.constructor });
val.$__.wasPopulated = { value: unpopulatedValue };
didPopulate = true;
Expand All @@ -1409,10 +1409,10 @@ Document.prototype.$set = function $set(path, val, type, options) {
schema.options[typeKey][0].ref &&
_isManuallyPopulatedArray(val, schema.options[typeKey][0].ref)) {
popOpts = { [populateModelSymbol]: val[0].constructor };
this.$populated(path, val.map(function(v) { return v._id; }), popOpts);
this.$populated(path, val.map(function(v) { return v._doc._id; }), popOpts);

for (const doc of val) {
doc.$__.wasPopulated = { value: doc._id };
doc.$__.wasPopulated = { value: doc._doc._id };
}
didPopulate = true;
}
Expand Down Expand Up @@ -1455,7 +1455,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
if (Array.isArray(val) && this.$__.populated[path]) {
for (let i = 0; i < val.length; ++i) {
if (val[i] instanceof Document) {
val.set(i, val[i]._id, true);
val.set(i, val[i]._doc._id, true);
}
}
}
Expand Down Expand Up @@ -1628,7 +1628,7 @@ Document.prototype.$__shouldModify = function(pathToMark, path, options, constru
// if they have the same _id
if (this.$populated(path) &&
val instanceof Document &&
deepEqual(val._id, priorVal)) {
deepEqual(val._doc._id, priorVal)) {
return false;
}

Expand Down Expand Up @@ -3840,7 +3840,7 @@ Document.prototype.$toObject = function(options, json) {
// _isNested will only be true if this is not the top level document, we
// should never depopulate the top-level document
if (depopulate && options._isNested && this.$__.wasPopulated) {
return clone(this.$__.wasPopulated.value || this._id, options);
return clone(this.$__.wasPopulated.value || this._doc._id, options);
}

// merge default options with input options.
Expand Down
12 changes: 7 additions & 5 deletions lib/helpers/clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const trustedSymbol = require('./query/trusted').trustedSymbol;
*
* If options.minimize is true, creates a minimal data object. Empty objects and undefined values will not be cloned. This makes the data payload sent to MongoDB as small as possible.
*
* Functions are never cloned.
* Functions and primitives are never cloned.
*
* @param {Object} obj the object to clone
* @param {Object} options
Expand All @@ -30,6 +30,9 @@ function clone(obj, options, isArrayChild) {
if (obj == null) {
return obj;
}
if (typeof obj === 'number' || typeof obj === 'string' || typeof obj === 'boolean' || typeof obj === 'bigint') {
return obj;
}

if (Array.isArray(obj)) {
return cloneArray(isMongooseArray(obj) ? obj.__array : obj, options);
Expand Down Expand Up @@ -148,13 +151,12 @@ function cloneObject(obj, options, isArrayChild) {
ret[trustedSymbol] = obj[trustedSymbol];
}

let i = 0;
let key = '';
const keys = Object.keys(obj);
const len = keys.length;

for (i = 0; i < len; ++i) {
if (specialProperties.has(key = keys[i])) {
for (let i = 0; i < len; ++i) {
const key = keys[i];
if (specialProperties.has(key)) {
continue;
}

Expand Down
8 changes: 6 additions & 2 deletions lib/helpers/query/cast$expr.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,12 @@ function _castExpression(val, schema, strictQuery) {
} else if (val.$ifNull != null) {
val.$ifNull.map(v => _castExpression(v, schema, strictQuery));
} else if (val.$switch != null) {
val.branches.map(v => _castExpression(v, schema, strictQuery));
val.default = _castExpression(val.default, schema, strictQuery);
if (Array.isArray(val.$switch.branches)) {
val.$switch.branches = val.$switch.branches.map(v => _castExpression(v, schema, strictQuery));
}
if ('default' in val.$switch) {
val.$switch.default = _castExpression(val.$switch.default, schema, strictQuery);
}
}

const keys = Object.keys(val);
Expand Down
6 changes: 3 additions & 3 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -3631,7 +3631,7 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
const len = paths.length;

for (let i = 0; i < len; ++i) {
where[paths[i]] = shardKey[paths[i]];
where[paths[i]] = document[paths[i]];
}
}

Expand Down Expand Up @@ -4459,7 +4459,7 @@ function _assign(model, vals, mod, assignmentOpts) {
}
} else {
if (_val instanceof Document) {
_val = _val._id;
_val = _val._doc._id;
}
key = String(_val);
if (rawDocs[key]) {
Expand All @@ -4468,7 +4468,7 @@ function _assign(model, vals, mod, assignmentOpts) {
rawOrder[key].push(i);
} else if (isVirtual ||
rawDocs[key].constructor !== val.constructor ||
String(rawDocs[key]._id) !== String(val._id)) {
String(rawDocs[key]._doc._id) !== String(val._doc._id)) {
// May need to store multiple docs with the same id if there's multiple models
// if we have discriminators or a ref function. But avoid converting to an array
// if we have multiple queries on the same model because of `perDocumentLimit` re: gh-9906
Expand Down
2 changes: 1 addition & 1 deletion lib/schema/documentArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function SchemaDocumentArray(key, schema, options, schemaOptions) {

const fn = this.defaultValue;

if (!('defaultValue' in this) || fn !== void 0) {
if (!('defaultValue' in this) || fn != null) {
this.default(function() {
let arr = fn.call(this);
if (arr != null && !Array.isArray(arr)) {
Expand Down
4 changes: 2 additions & 2 deletions lib/schemaType.js
Original file line number Diff line number Diff line change
Expand Up @@ -1542,7 +1542,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init) {
}

if (value.$__ != null) {
value.$__.wasPopulated = value.$__.wasPopulated || { value: value._id };
value.$__.wasPopulated = value.$__.wasPopulated || { value: value._doc._id };
return value;
}

Expand All @@ -1568,7 +1568,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init) {
!doc.$__.populated[path].options.options ||
!doc.$__.populated[path].options.options.lean) {
ret = new pop.options[populateModelSymbol](value);
ret.$__.wasPopulated = { value: ret._id };
ret.$__.wasPopulated = { value: ret._doc._id };
}

return ret;
Expand Down
4 changes: 2 additions & 2 deletions lib/types/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ class MongooseMap extends Map {
v = new populated.options[populateModelSymbol](v);
}
// Doesn't support single nested "in-place" populate
v.$__.wasPopulated = { value: v._id };
v.$__.wasPopulated = { value: v._doc._id };
return v;
});
} else if (value != null) {
if (value.$__ == null) {
value = new populated.options[populateModelSymbol](value);
}
// Doesn't support single nested "in-place" populate
value.$__.wasPopulated = { value: value._id };
value.$__.wasPopulated = { value: value._doc._id };
}
} else {
try {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "mongoose",
"description": "Mongoose MongoDB ODM",
"version": "8.5.1",
"version": "8.5.2",
"author": "Guillermo Rauch <guillermo@learnboost.com>",
"keywords": [
"mongodb",
Expand Down Expand Up @@ -57,7 +57,7 @@
"mkdirp": "^3.0.1",
"mocha": "10.6.0",
"moment": "2.30.1",
"mongodb-memory-server": "9.4.0",
"mongodb-memory-server": "10.0.0",
"ncp": "^2.0.0",
"nyc": "15.1.0",
"pug": "3.0.3",
Expand Down
6 changes: 6 additions & 0 deletions test/document.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3203,16 +3203,22 @@ describe('document', function() {
names: {
type: [String],
default: null
},
tags: {
type: [{ tag: String }],
default: null
}
});

const Model = db.model('Test', schema);
const m = new Model();
assert.strictEqual(m.names, null);
assert.strictEqual(m.tags, null);
await m.save();

const doc = await Model.collection.findOne({ _id: m._id });
assert.strictEqual(doc.names, null);
assert.strictEqual(doc.tags, null);
});

it('validation works when setting array index (gh-3816)', async function() {
Expand Down
29 changes: 29 additions & 0 deletions test/helpers/query.cast$expr.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,33 @@ describe('castexpr', function() {
res = cast$expr({ $eq: [{ $round: ['$value'] }, 2] }, testSchema);
assert.deepStrictEqual(res, { $eq: [{ $round: ['$value'] }, 2] });
});

it('casts $switch (gh-14751)', function() {
const testSchema = new Schema({
name: String,
scores: [Number]
});
const res = cast$expr({
$eq: [
{
$switch: {
branches: [{ case: { $eq: ['$$NOW', '$$NOW'] }, then: true }],
default: false
}
},
true
]
}, testSchema);
assert.deepStrictEqual(res, {
$eq: [
{
$switch: {
branches: [{ case: { $eq: ['$$NOW', '$$NOW'] }, then: true }],
default: false
}
},
true
]
});
});
});
34 changes: 34 additions & 0 deletions test/model.populate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11014,4 +11014,38 @@ describe('model: populate:', function() {
assert.equal(latestClass.students[1].name, 'Robert');
assert.equal(latestClass.students[1].grade.grade, 'B');
});

it('avoids depopulating manually populated doc as getter value (gh-14759)', async function() {
const ownerSchema = new mongoose.Schema({
_id: {
type: 'ObjectId',
get(value) {
return value == null ? value : value.toString();
}
},
name: 'String'
});
const petSchema = new mongoose.Schema({
name: 'String',
owner: { type: 'ObjectId', ref: 'Owner' }
});

const Owner = db.model('Owner', ownerSchema);
const Pet = db.model('Pet', petSchema);

const ownerId = new mongoose.Types.ObjectId();
const owner = await Owner.create({
_id: ownerId,
name: 'Alice'
});
await Pet.create({ name: 'Kitty', owner: owner });

const fromDb = await Pet.findOne({ owner: ownerId }).lean().orFail();
assert.ok(fromDb.owner instanceof mongoose.Types.ObjectId);

const pet1 = new Pet({ name: 'Kitty1', owner: owner });
const pet2 = new Pet({ name: 'Kitty2', owner: owner });
assert.equal(pet1.owner.name, 'Alice');
assert.equal(pet2.owner.name, 'Alice');
});
});
6 changes: 3 additions & 3 deletions test/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6365,9 +6365,9 @@ describe('Model', function() {
describe('buildBulkWriteOperations() (gh-9673)', () => {
it('builds write operations', async() => {


const userSchema = new Schema({
name: { type: String }
name: { type: String },
a: { type: Number }
}, { shardKey: { a: 1 } });

const User = db.model('User', userSchema);
Expand All @@ -6386,7 +6386,7 @@ describe('Model', function() {
const desiredWriteOperations = [
{ insertOne: { document: users[0] } },
{ insertOne: { document: users[1] } },
{ updateOne: { filter: { _id: users[2]._id, a: 1 }, update: { $set: { name: 'I am the updated third name' } } } }
{ updateOne: { filter: { _id: users[2]._id, a: 3 }, update: { $set: { name: 'I am the updated third name' } } } }
];

assert.deepEqual(
Expand Down
20 changes: 20 additions & 0 deletions test/schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3258,4 +3258,24 @@ describe('schema', function() {

await q;
});

it('supports casting object to subdocument (gh-14748) (gh-9076)', function() {
const nestedSchema = new Schema({ name: String });
nestedSchema.methods.getAnswer = () => 42;

const schema = new Schema({
arr: [nestedSchema],
singleNested: nestedSchema
});

// Cast to doc array
let subdoc = schema.path('arr').cast([{ name: 'foo' }])[0];
assert.ok(subdoc instanceof mongoose.Document);
assert.equal(subdoc.getAnswer(), 42);

// Cast to single nested subdoc
subdoc = schema.path('singleNested').cast({ name: 'bar' });
assert.ok(subdoc instanceof mongoose.Document);
assert.equal(subdoc.getAnswer(), 42);
});
});
Loading

0 comments on commit 220dd50

Please sign in to comment.