Skip to content

Commit 919592d

Browse files
authored
Merge pull request #15402 from muazahmed-dev/fix-findOneAndUpdate-empty-filter
Fix: Add strictFilter option to findOneAndUpdate (#14913)
2 parents 9702ac2 + 4ab2e4b commit 919592d

File tree

2 files changed

+537
-1
lines changed

2 files changed

+537
-1
lines changed

lib/query.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,32 @@ function Query(conditions, options, model, collection) {
165165
}
166166
}
167167

168+
// Helper function to check if an object is empty or contains only empty objects/arrays
169+
function isEmptyFilter(obj) {
170+
if (obj == null) return true;
171+
if (typeof obj !== 'object') return true;
172+
if (Object.keys(obj).length === 0) return true;
173+
174+
// Check $and, $or, $nor arrays
175+
for (const key of ['$and', '$or', '$nor']) {
176+
if (Array.isArray(obj[key])) {
177+
// If array is empty or all elements are empty objects, consider it empty
178+
if (obj[key].length === 0 || obj[key].every(item => isEmptyFilter(item))) {
179+
return true;
180+
}
181+
}
182+
}
183+
184+
return false;
185+
}
186+
187+
// Helper function to check for empty/invalid filter
188+
function checkRequireFilter(filter, options) {
189+
if (options && options.requireFilter && isEmptyFilter(filter)) {
190+
throw new Error('Empty or invalid filter not allowed with requireFilter enabled');
191+
}
192+
}
193+
168194
/*!
169195
* inherit mquery
170196
*/
@@ -3111,6 +3137,7 @@ function _handleSortValue(val, key) {
31113137
*
31123138
* @param {Object|Query} [filter] mongodb selector
31133139
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3140+
* @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
31143141
* @return {Query} this
31153142
* @see DeleteResult https://mongodb.github.io/node-mongodb-native/6.15/interfaces/DeleteResult.html
31163143
* @see deleteOne https://mongodb.github.io/node-mongodb-native/6.15/classes/Collection.html#deleteOne
@@ -3148,6 +3175,9 @@ Query.prototype._deleteOne = async function _deleteOne() {
31483175
this._applyTranslateAliases();
31493176
this._castConditions();
31503177

3178+
// Check for empty/invalid filter with requireFilter option
3179+
checkRequireFilter(this._conditions, this.options);
3180+
31513181
if (this.error() != null) {
31523182
throw this.error();
31533183
}
@@ -3183,6 +3213,7 @@ Query.prototype._deleteOne = async function _deleteOne() {
31833213
*
31843214
* @param {Object|Query} [filter] mongodb selector
31853215
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3216+
* @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
31863217
* @return {Query} this
31873218
* @see DeleteResult https://mongodb.github.io/node-mongodb-native/6.15/interfaces/DeleteResult.html
31883219
* @see deleteMany https://mongodb.github.io/node-mongodb-native/6.15/classes/Collection.html#deleteMany
@@ -3220,6 +3251,9 @@ Query.prototype._deleteMany = async function _deleteMany() {
32203251
this._applyTranslateAliases();
32213252
this._castConditions();
32223253

3254+
// Check for empty/invalid filter with requireFilter option
3255+
checkRequireFilter(this._conditions, this.options);
3256+
32233257
if (this.error() != null) {
32243258
throw this.error();
32253259
}
@@ -3311,6 +3345,7 @@ function prepareDiscriminatorCriteria(query) {
33113345
* - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
33123346
* - `runValidators`: if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
33133347
* - `setDefaultsOnInsert`: `true` by default. If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created.
3348+
* - `requireFilter`: bool - if true, throws an error if the filter is empty (`{}`). Defaults to false.
33143349
*
33153350
* #### Example:
33163351
*
@@ -3337,6 +3372,7 @@ function prepareDiscriminatorCriteria(query) {
33373372
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
33383373
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
33393374
* @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
3375+
* @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
33403376
* @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
33413377
* @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/
33423378
* @see ModifyResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html
@@ -3417,6 +3453,9 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
34173453
this._applyTranslateAliases();
34183454
this._castConditions();
34193455

3456+
// Check for empty/invalid filter with requireFilter option
3457+
checkRequireFilter(this._conditions, this.options);
3458+
34203459
_castArrayFilters(this);
34213460

34223461
if (this.error()) {
@@ -3500,6 +3539,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
35003539
*
35013540
* - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
35023541
* - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
3542+
* - `requireFilter`: bool - if true, throws an error if the filter is empty (`{}`). Defaults to false.
35033543
*
35043544
* #### Example:
35053545
*
@@ -3512,6 +3552,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
35123552
* @param {Object} [filter]
35133553
* @param {Object} [options]
35143554
* @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
3555+
* @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
35153556
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
35163557
* @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
35173558
* @return {Query} this
@@ -3551,6 +3592,9 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
35513592
this._applyTranslateAliases();
35523593
this._castConditions();
35533594

3595+
// Check for empty/invalid filter with requireFilter option
3596+
checkRequireFilter(this._conditions, this.options);
3597+
35543598
if (this.error() != null) {
35553599
throw this.error();
35563600
}
@@ -3590,6 +3634,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
35903634
* - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
35913635
* - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
35923636
* - `includeResultMetadata`: if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
3637+
* - `requireFilter`: bool - if true, throws an error if the filter is empty (`{}`). Defaults to false.
35933638
*
35943639
* #### Example:
35953640
*
@@ -3612,6 +3657,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
36123657
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
36133658
* @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`.
36143659
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
3660+
* @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
36153661
* @return {Query} this
36163662
* @api public
36173663
*/
@@ -3667,6 +3713,10 @@ Query.prototype.findOneAndReplace = function(filter, replacement, options) {
36673713
Query.prototype._findOneAndReplace = async function _findOneAndReplace() {
36683714
this._applyTranslateAliases();
36693715
this._castConditions();
3716+
3717+
// Check for empty/invalid filter with requireFilter option
3718+
checkRequireFilter(this._conditions, this.options);
3719+
36703720
if (this.error() != null) {
36713721
throw this.error();
36723722
}
@@ -3969,6 +4019,9 @@ async function _updateThunk(op) {
39694019

39704020
this._castConditions();
39714021

4022+
// Check for empty/invalid filter with requireFilter option
4023+
checkRequireFilter(this._conditions, this.options);
4024+
39724025
_castArrayFilters(this);
39734026

39744027
if (this.error() != null) {
@@ -4119,6 +4172,7 @@ Query.prototype._replaceOne = async function _replaceOne() {
41194172
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
41204173
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
41214174
* @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
4175+
* @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
41224176
* @return {Query} this
41234177
* @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
41244178
* @see Query docs https://mongoosejs.com/docs/queries.html
@@ -4193,6 +4247,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
41934247
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
41944248
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
41954249
* @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
4250+
* @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
41964251
* @return {Query} this
41974252
* @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
41984253
* @see Query docs https://mongoosejs.com/docs/queries.html
@@ -4259,6 +4314,7 @@ Query.prototype.updateOne = function(conditions, doc, options, callback) {
42594314
* @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
42604315
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
42614316
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
4317+
* @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
42624318
* @return {Query} this
42634319
* @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
42644320
* @see Query docs https://mongoosejs.com/docs/queries.html

0 commit comments

Comments
 (0)