-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Fix: Add strictFilter option to findOneAndUpdate (#14913) #15402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Fix: Add strictFilter option to findOneAndUpdate (#14913) #15402
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new strictFilter option to findOneAndUpdate to prevent accidental updates when an empty filter is provided. Key changes include:
- Adding strictFilter logic and JSDoc updates in lib/query.js.
- Introducing four new test cases in test/query.test.js to validate the behavior with empty, non-empty, undefined filters, and the default case.
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
File | Description |
---|---|
lib/query.js | Added strictFilter check that rejects empty filters and updated the JSDoc. |
test/query.test.js | Added test cases covering strictFilter behavior. |
Comments suppressed due to low confidence (1)
test/query.test.js:4501
- [nitpick] Consider adding additional tests for cases where the filter is null or a non-object value, to ensure that the strictFilter option behaves as intended.
it('handles undefined filter with strictFilter true', async function() {
lib/query.js
Outdated
@@ -3353,6 +3355,11 @@ Query.prototype.findOneAndUpdate = function(filter, doc, options) { | |||
throw new MongooseError('Query.prototype.findOneAndUpdate() no longer accepts a callback'); | |||
} | |||
|
|||
// Check for empty filter with strictFilter option | |||
if (options && options.strictFilter && filter && Object.keys(filter).length === 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider verifying that 'filter' is a plain object before checking its keys (e.g. using typeof filter === 'object' and filter !== null) to ensure that non-object inputs do not bypass the strictFilter check.
if (options && options.strictFilter && filter && Object.keys(filter).length === 0) { | |
if (options && options.strictFilter && typeof filter === 'object' && filter !== null && !Array.isArray(filter) && Object.keys(filter).length === 0) { |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of suggestions. Also, this option should ideally behave similarly for other update and delete functions:
findOneAndDelete
findOneAndReplace
updateOne
updateMany
deleteOne
deleteMany
Overall I like the idea of this option, throwing an error if the final filter is empty is useful. But the implementation needs some work.
lib/query.js
Outdated
@@ -3353,6 +3355,11 @@ Query.prototype.findOneAndUpdate = function(filter, doc, options) { | |||
throw new MongooseError('Query.prototype.findOneAndUpdate() no longer accepts a callback'); | |||
} | |||
|
|||
// Check for empty filter with strictFilter option |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is the right place for this check because of query chaining. For example, await TestModel.findOneAndUpdate({}, { isPublic: true }).setQuery({ isPublic: false })
will execute with a non-empty filter even though the original findOneAndUpdate()
call had an empty filter.
lib/query.js
Outdated
@@ -3337,6 +3338,7 @@ function prepareDiscriminatorCriteria(query) { | |||
* @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. | |||
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key | |||
* @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. | |||
* @param {Boolean} [options.strictFilter=false] If true, throws an error if the filter is empty (`{}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think strictFilter
is the right name for this option. strictFilter
could misleadingly imply this option has similar semantics to strict
or strictQuery
, which are about schema-enforced structure, not whether filter exists.
I'd recommend renaming to requireFilter
.
@vkarpov15 Thank you for the detailed feedback and for supporting the I’ll address the following in the next 1-2 days:
I’ll verify the updates with tests and my |
@vkarpov15 The
Please let me know if any further changes are needed. Thanks for the guidance! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a couple of minor comments but looking good overall
lib/query.js
Outdated
function checkRequireFilter(filter, options) { | ||
if (options && options.requireFilter && | ||
(filter == null || | ||
(typeof filter === 'object' && filter !== null && !Array.isArray(filter) && Object.keys(filter).length === 0))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think filter
can be an array, so I would get rid of this Array.isArray(filter)
check.
Also, it would likely be worthwhile to check for empty objects in $and
, $or
, and $nor
as well. For example, deleteOne({ $and: [{}] })
and deleteOne({ $or: [{}] })
is equivalent to deleteOne({})
. That would make requireFilter
more valuable.
test/query.test.js
Outdated
} | ||
|
||
const schema = new Schema({ name: String, email: String }); | ||
Person = mongoose.model('Person', schema, null, { cache: false }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Person = db.model()
instead of mongoose.model()
in these tests please, that's how the rest of our test cases work
test/query.test.js
Outdated
try { | ||
|
||
if (mongoose.connection.readyState !== 1) { | ||
await mongoose.connect('mongodb://localhost:27017/testdb'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't call mongoose.connect()
in tests. We use db.model()
in tests throughout.
…m/muazahmed-dev/mongoose into fix-findOneAndUpdate-empty-filter
@vkarpov15 Thanks for the feedback! I’ve addressed both points:
Please let me know if there’s anything else to address or refine. Thanks for the guidance! |
Summary
This PR addresses Issue #14913 by adding a
strictFilter
option tofindOneAndUpdate
. When enabled (strictFilter: true
), it throws an error if the filter is empty ({}
), preventing unintended updates to the first document in the collection. The default behavior (strictFilter: false
or unset) remains unchanged to avoid breaking existing applications.Motivation: In applications where data integrity is critical (e.g., financial or user data systems), an empty filter in
findOneAndUpdate
can accidentally update the wrong document. ThestrictFilter
option enhances safety by requiring an explicit filter, reducing the risk of such errors. This change aligns with Mongoose’s goal of providing robust tools for MongoDB interactions.Changes:
strictFilter
option tofindOneAndUpdate
inlib/query.js
with updated JSDoc.test/query.test.js
covering empty filter, non-empty filter, undefined filter, and default behavior.reproduce_error.js
) using local Mongoose changes.Examples
The following example demonstrates the
strictFilter
behavior: