-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Filter is mutated in place by Query modifiers #14567
Labels
confirmed-bug
We've confirmed this is a bug in Mongoose and will fix it.
Milestone
Comments
vkarpov15
added
the
has repro script
There is a repro script, the Mongoose devs need to confirm that it reproduces the issue
label
May 6, 2024
IslandRhythms
added
confirmed-bug
We've confirmed this is a bug in Mongoose and will fix it.
and removed
has repro script
There is a repro script, the Mongoose devs need to confirm that it reproduces the issue
labels
May 7, 2024
const mongoose = require('mongoose');
const assert = require('assert');
const schema = new mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, required: true },
active: { type: Boolean, required: true, default: true },
createdAt: { type: Number },
updatedAt: { type: Number },
});
const M = mongoose.model("Person", schema);
async function run() {
await mongoose.connect('mongodb://localhost:27017');
await mongoose.connection.dropDatabase();
await M.create([
{ name: "Alice", age: 30 },
{ name: "Bob", age: 27 },
{ name: "Charlie", age: 25 },
{ name: "David", age: 8 },
{ name: "Eve", age: 20, active: false },
]);
/********************************* EXAMPLE #1 *********************************/
const activeQuery = { $and: [{ active: true }] };
const activeAdults = await M.countDocuments(activeQuery)
.and([{ age: { $gte: 18 } }])
// EXPECTED: activeAdults === 3
// ACTUAL: activeAdults === 3
assert.equal(activeAdults, 3)
const allActive = await M.countDocuments(activeQuery).exec();
// EXPECTED: allActive === 4
// ACTUAL: allActive === 3
assert.equal(allActive, 4)
/********************************* EXAMPLE #2 *********************************/
const adultQuery = { age: { $gte: 18 } };
const youngAdults = await M.countDocuments(adultQuery).where("age").lte(25);
// EXPECTED: youngAdults === 2
// ACTUAL: youngAdults === 2
assert.equal(youngAdults, 2)
const allAdults = await M.countDocuments(adultQuery);
// EXPECTED: allAdults === 4
// ACTUAL: allAdults === 2
assert.equal(allAdults, 4)
}
run(); |
vkarpov15
added a commit
that referenced
this issue
May 8, 2024
vkarpov15
added a commit
that referenced
this issue
May 8, 2024
@vkarpov15 Nice! Thanks for tackling quickly. I left a couple comments on the PR. |
vkarpov15
added a commit
that referenced
this issue
May 11, 2024
fix(query): shallow clone $or, $and if merging onto empty query filter
2 tasks
This was referenced Jun 5, 2024
This was referenced Jun 22, 2024
This was referenced Jun 24, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Prerequisites
Mongoose version
8.3.3
Node.js version
18.8.0
MongoDB server version
7.0
Typescript version (if applicable)
4.5.2
Description
Mongoose doesn't clone the
source
when usingmerge
, so filter query inputs that have objects for values at the top level are modified in place by query modifiers like.and
,.or
,.where
, etc., and those changes persist because of JS's call-by-sharing.This is such core behavior that maybe we're doing it on purpose, but I find it unintuitive that Mongoose is taking my filter input and mutating it so it might end up as something else when I go to use the same input.
Is there a reason we don't just do
source = clone(source)
or something at the top ofQuery.prototype.merge
to protect against errant mutations? Is it a performance thing?Fundamentally, this is the specific problem line. When a key in
from
doesn't exist into
, we just copy over the entire value fromfrom
intoto
. When that value is an object itself, it's just an address in memory, so it's literally the same object that gets copied. Later on, when the query is mutating its own conditions (rightfully so), this means that the initial externally-provided query also gets changed.Steps to Reproduce
Expected Behavior
See comments in the code block above for two different examples with expectation vs actual.
The text was updated successfully, but these errors were encountered: