From 218d50a868577bb744ada87cf9ba7336402b2a2e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 24 May 2024 12:20:10 -0400 Subject: [PATCH] fix(query): shallow clone $or and $and array elements to avoid mutating query filter arguments Fix #14610 --- lib/query.js | 12 ++++++++---- lib/utils.js | 2 ++ test/query.test.js | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/query.js b/lib/query.js index 5db5c0e7ef7..eb6f581f920 100644 --- a/lib/query.js +++ b/lib/query.js @@ -2416,13 +2416,17 @@ Query.prototype.merge = function(source) { } opts.omit = {}; - if (this._conditions && this._conditions.$and && source.$and) { + if (this._conditions && Array.isArray(source.$and)) { opts.omit['$and'] = true; - this._conditions.$and = this._conditions.$and.concat(source.$and); + this._conditions.$and = (this._conditions.$and || []).concat( + source.$and.map(el => utils.isPOJO(el) ? utils.merge({}, el) : el) + ); } - if (this._conditions && this._conditions.$or && source.$or) { + if (this._conditions && Array.isArray(source.$or)) { opts.omit['$or'] = true; - this._conditions.$or = this._conditions.$or.concat(source.$or); + this._conditions.$or = (this._conditions.$or || []).concat( + source.$or.map(el => utils.isPOJO(el) ? utils.merge({}, el) : el) + ); } // plain object diff --git a/lib/utils.js b/lib/utils.js index 512b7728a3e..0172ac3e433 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -294,6 +294,8 @@ exports.merge = function merge(to, from, options, path) { to[key] = from[key]; } } + + return to; }; /** diff --git a/test/query.test.js b/test/query.test.js index 42354434d90..61e70674f44 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -4214,4 +4214,19 @@ describe('Query', function() { assert.strictEqual(doc.account.owner, undefined); assert.strictEqual(doc.account.taxIds, undefined); }); + + it('avoids mutating $or, $and elements when casting (gh-14610)', async function() { + const personSchema = new mongoose.Schema({ + name: String, + age: Number + }); + const Person = db.model('Person', personSchema); + + const filter = [{ name: 'Me', age: '20' }, { name: 'You', age: '50' }]; + await Person.find({ $or: filter }); + assert.deepStrictEqual(filter, [{ name: 'Me', age: '20' }, { name: 'You', age: '50' }]); + + await Person.find({ $and: filter }); + assert.deepStrictEqual(filter, [{ name: 'Me', age: '20' }, { name: 'You', age: '50' }]); + }); });