From c57651a6df393ef1e658d1370cb6d7d444f2add0 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Mon, 15 Jul 2024 12:51:00 +0200 Subject: [PATCH 01/15] chore(workflows/test): update mongodb versions to their latest patch --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d6ff049078a..2b5a189cb55 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 }} @@ -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: From 0f249a9530a952144af4b158643822156cfd2dd0 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Mon, 15 Jul 2024 12:56:43 +0200 Subject: [PATCH 02/15] chore(workflows/tsd): update node-version to 16 for types test --- .github/workflows/tsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tsd.yml b/.github/workflows/tsd.yml index cc3d045454d..0960b7d1de2 100644 --- a/.github/workflows/tsd.yml +++ b/.github/workflows/tsd.yml @@ -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 From bb85c54e9d2fc665eef32b68ad251ee2ebaf94be Mon Sep 17 00:00:00 2001 From: hasezoey Date: Mon, 15 Jul 2024 12:53:02 +0200 Subject: [PATCH 03/15] chore(deps-dev): bump mongodb-memory-server from 9.4.0 to 10.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 477448ac4f6..56b2df8ffa9 100644 --- a/package.json +++ b/package.json @@ -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", From 693398f520a6f3fb83e694c097e0e82607cc7a75 Mon Sep 17 00:00:00 2001 From: Blake Sager Date: Thu, 18 Jul 2024 12:50:59 -0500 Subject: [PATCH 04/15] Correct shardkey access in buildBulkWriteOps buildBulkWriteOperations adds the shard key to the filter condition, but it sets it to the schema's value, not the document's value. It also updates a problematic test, which passed because the update value it uses was the same schema's shard key value. --- lib/model.js | 2 +- test/model.test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/model.js b/lib/model.js index 507ec49ff1d..808a595811c 100644 --- a/lib/model.js +++ b/lib/model.js @@ -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]]; } } diff --git a/test/model.test.js b/test/model.test.js index bc386146eee..b73757e4721 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -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); @@ -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( From bb2853c9686caec1a45446d1ecafeca69b5d4606 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 21 Jul 2024 13:54:15 -0400 Subject: [PATCH 05/15] fix(query): handle casting $switch in $expr Fix #14751 --- lib/helpers/query/cast$expr.js | 4 ++-- test/helpers/query.cast$expr.test.js | 29 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/helpers/query/cast$expr.js b/lib/helpers/query/cast$expr.js index 9889d47ada1..d91110049f4 100644 --- a/lib/helpers/query/cast$expr.js +++ b/lib/helpers/query/cast$expr.js @@ -93,8 +93,8 @@ 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); + val.$switch.branches = val.$switch.branches.map(v => _castExpression(v, schema, strictQuery)); + val.$switch.default = _castExpression(val.$switch.default, schema, strictQuery); } const keys = Object.keys(val); diff --git a/test/helpers/query.cast$expr.test.js b/test/helpers/query.cast$expr.test.js index 416db9af5c6..fd3f7cd5bfe 100644 --- a/test/helpers/query.cast$expr.test.js +++ b/test/helpers/query.cast$expr.test.js @@ -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 + ] + }); + }); }); From 08e65f179c9da34109db5d41e63d3a08e44dd3df Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 21 Jul 2024 13:57:06 -0400 Subject: [PATCH 06/15] fix: better edge case handling --- lib/helpers/query/cast$expr.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/helpers/query/cast$expr.js b/lib/helpers/query/cast$expr.js index d91110049f4..8e84011b2c3 100644 --- a/lib/helpers/query/cast$expr.js +++ b/lib/helpers/query/cast$expr.js @@ -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.$switch.branches = val.$switch.branches.map(v => _castExpression(v, schema, strictQuery)); - val.$switch.default = _castExpression(val.$switch.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); From 195103cec3aad94830f7f341387538d11e1dd06b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 21 Jul 2024 14:32:43 -0400 Subject: [PATCH 07/15] types: allow calling `SchemaType.cast()` without `parent` and `init` parameters Fix #14718 Re: #9076 --- test/schema.test.js | 20 ++++++++++++++++++++ test/types/schema.test.ts | 15 +++++++++++++++ types/schematypes.d.ts | 3 ++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/test/schema.test.js b/test/schema.test.js index 8cd58ba7b9f..3076c9df62a 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -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); + }); }); diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 9dab60d2022..e3771e0dd64 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1,5 +1,6 @@ import { DefaultSchemaOptions, + HydratedArraySubdocument, HydratedSingleSubdocument, Schema, Document, @@ -1555,3 +1556,17 @@ function gh14696() { }); } + +function gh14748() { + const nestedSchema = new Schema({ name: String }); + + const schema = new Schema({ + arr: [nestedSchema], + singleNested: nestedSchema + }); + + // Cast to single nested subdoc + const subdoc = schema.path('singleNested') + .cast>({ name: 'bar' }); + expectAssignable<{ name: string }>(subdoc); +} \ No newline at end of file diff --git a/types/schematypes.d.ts b/types/schematypes.d.ts index b7f1fc733b9..26eab700a81 100644 --- a/types/schematypes.d.ts +++ b/types/schematypes.d.ts @@ -220,7 +220,8 @@ declare module 'mongoose' { OptionsConstructor: SchemaTypeOptions; /** Cast `val` to this schema type. Each class that inherits from schema type should implement this function. */ - cast(val: any, doc: Document, init: boolean, prev?: any, options?: any): any; + cast(val: any, doc?: Document, init?: boolean, prev?: any, options?: any): any; + cast(val: any, doc?: Document, init?: boolean, prev?: any, options?: any): ResultType; /** Sets a default value for this SchemaType. */ default(val: any): any; From b48801b276db71cad17eb9f9f6177b25413c9dba Mon Sep 17 00:00:00 2001 From: Abdelrahman Elkady Date: Tue, 23 Jul 2024 16:45:14 +0300 Subject: [PATCH 08/15] docs: fix a wrong example in v6 migration guide fixes a wrong example with `typePojoToMixed` behavior in mongoose 5 --- docs/migrating_to_6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrating_to_6.md b/docs/migrating_to_6.md index 36d670fc343..f0b91a5f9b8 100644 --- a/docs/migrating_to_6.md +++ b/docs/migrating_to_6.md @@ -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 } } }); From 7c2cfa898f04edd566220c04b1c46bffe9864b3a Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 23 Jul 2024 12:15:19 -0400 Subject: [PATCH 09/15] fix(model+document): avoid depopulating manually populated doc as getter value Fix #14759 --- lib/document.js | 6 +++--- lib/model.js | 4 ++-- lib/schemaType.js | 4 ++-- lib/types/map.js | 4 ++-- test/model.populate.test.js | 31 +++++++++++++++++++++++++++++++ 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/lib/document.js b/lib/document.js index c4afeb763a7..015dee2a9f5 100644 --- a/lib/document.js +++ b/lib/document.js @@ -1394,7 +1394,7 @@ 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; + 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; @@ -1412,7 +1412,7 @@ Document.prototype.$set = function $set(path, val, type, options) { this.$populated(path, val.map(function(v) { return v._id; }), popOpts); for (const doc of val) { - doc.$__.wasPopulated = { value: doc._id }; + doc.$__.wasPopulated = { value: doc._doc._id }; } didPopulate = true; } @@ -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. diff --git a/lib/model.js b/lib/model.js index 808a595811c..b3497669be5 100644 --- a/lib/model.js +++ b/lib/model.js @@ -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]) { @@ -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 diff --git a/lib/schemaType.js b/lib/schemaType.js index b54e83fd6a8..f95ecbb3226 100644 --- a/lib/schemaType.js +++ b/lib/schemaType.js @@ -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; } @@ -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; diff --git a/lib/types/map.js b/lib/types/map.js index 4b8607df311..882fc117a4d 100644 --- a/lib/types/map.js +++ b/lib/types/map.js @@ -116,7 +116,7 @@ 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) { @@ -124,7 +124,7 @@ class MongooseMap extends Map { 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 { diff --git a/test/model.populate.test.js b/test/model.populate.test.js index 3583249d6eb..ce47c637b1f 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -11014,4 +11014,35 @@ 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 = new Owner({ + _id: ownerId, + name: 'Alice' + }); + await owner.save(); + const pet = new Pet({ name: 'Kitty', owner: owner }); + await pet.save(); + + const fromDb = await Pet.findOne({ owner: ownerId }).lean().orFail(); + assert.ok(fromDb.owner instanceof mongoose.Types.ObjectId); + }); }); From 9da1804fed1102a709c820186c3946cd9acaa80b Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 23 Jul 2024 12:29:00 -0400 Subject: [PATCH 10/15] Update test/types/schema.test.ts Co-authored-by: hasezoey --- test/types/schema.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index e3771e0dd64..b672d14e27e 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1569,4 +1569,4 @@ function gh14748() { const subdoc = schema.path('singleNested') .cast>({ name: 'bar' }); expectAssignable<{ name: string }>(subdoc); -} \ No newline at end of file +} From 351b28957871cb371da10204b0986efa2f49d915 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 23 Jul 2024 12:37:55 -0400 Subject: [PATCH 11/15] types: improve return value for Types.Subdocument.cast() --- test/types/schema.test.ts | 7 ++++++- types/schematypes.d.ts | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index b672d14e27e..a9679c082b8 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1565,8 +1565,13 @@ function gh14748() { singleNested: nestedSchema }); - // Cast to single nested subdoc const subdoc = schema.path('singleNested') .cast>({ name: 'bar' }); expectAssignable<{ name: string }>(subdoc); + + const subdoc2 = schema.path('singleNested').cast({ name: 'bar' }); + expectAssignable<{ name: string }>(subdoc2); + + const subdoc3 = schema.path>('singleNested').cast({ name: 'bar' }); + expectAssignable<{ name: string }>(subdoc3); } diff --git a/types/schematypes.d.ts b/types/schematypes.d.ts index 26eab700a81..e8a0ecffdf0 100644 --- a/types/schematypes.d.ts +++ b/types/schematypes.d.ts @@ -444,7 +444,7 @@ declare module 'mongoose' { defaultOptions: Record; } - class Subdocument extends SchemaType implements AcceptsDiscriminator { + class Subdocument extends SchemaType implements AcceptsDiscriminator { /** This schema type's name, to defend against minifiers that mangle function names. */ static schemaName: string; @@ -456,6 +456,8 @@ declare module 'mongoose' { discriminator(name: string | number, schema: Schema, value?: string): U; discriminator(name: string | number, schema: Schema, value?: string): Model; + + cast(val: any, doc?: Document, init?: boolean, prev?: any, options?: any): HydratedSingleSubdocument; } class String extends SchemaType { From ad75131f80d557ad2e1dbaef6a4eafadd3884082 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 23 Jul 2024 13:37:39 -0400 Subject: [PATCH 12/15] perf(clone): avoid further unnecessary checks if cloning a primitive value --- lib/helpers/clone.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/helpers/clone.js b/lib/helpers/clone.js index fafb2f3063b..b0e37f6cfba 100644 --- a/lib/helpers/clone.js +++ b/lib/helpers/clone.js @@ -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 @@ -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); @@ -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; } From 1ad7531bb7b7bacd506f909ff4d3363cca5ed3b2 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 26 Jul 2024 17:34:50 -0400 Subject: [PATCH 13/15] address code review comments and fix a couple of other spots where we get _id through getter --- lib/document.js | 8 ++++---- test/model.populate.test.js | 11 +++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/document.js b/lib/document.js index 015dee2a9f5..f4bc5e3e53e 100644 --- a/lib/document.js +++ b/lib/document.js @@ -1393,7 +1393,7 @@ 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))) { + 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 }; @@ -1409,7 +1409,7 @@ 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._doc._id }; @@ -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); } } } @@ -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; } diff --git a/test/model.populate.test.js b/test/model.populate.test.js index ce47c637b1f..48427bf6ae4 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -11034,15 +11034,18 @@ describe('model: populate:', function() { const Pet = db.model('Pet', petSchema); const ownerId = new mongoose.Types.ObjectId(); - const owner = new Owner({ + const owner = await Owner.create({ _id: ownerId, name: 'Alice' }); - await owner.save(); - const pet = new Pet({ name: 'Kitty', owner: owner }); - await pet.save(); + 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'); }); }); From 9a2462c81f6fe74d8e3a05a28f5e9fd7227ea48e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 29 Jul 2024 13:54:25 -0400 Subject: [PATCH 14/15] fix: allow setting document array default to `null` Fix #6691 Re: #14717 --- lib/schema/documentArray.js | 2 +- test/document.test.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/schema/documentArray.js b/lib/schema/documentArray.js index a10d2ec76b1..aa0c0d7984a 100644 --- a/lib/schema/documentArray.js +++ b/lib/schema/documentArray.js @@ -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)) { diff --git a/test/document.test.js b/test/document.test.js index c11f72753ee..5957556a867 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -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() { From 986f5eb3077120de530d5baea450ce95428f3266 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 30 Jul 2024 14:04:47 -0400 Subject: [PATCH 15/15] chore: release 8.5.2 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 241620b2b87..d087c5af90b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/package.json b/package.json index 56b2df8ffa9..402945e1ed5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "8.5.1", + "version": "8.5.2", "author": "Guillermo Rauch ", "keywords": [ "mongodb",