[SERVER-49156] Update Aggregation Pipeline values that start with $ resolve to undefined Created: 26/Jun/20  Updated: 27/Oct/23  Resolved: 07/Jul/20

Status: Closed
Project: Core Server
Component/s: Aggregation Framework
Affects Version/s: 4.3.6
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Thomas Reggi (Inactive) Assignee: Asya Kamsky
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Operating System: ALL
Participants:

 Description   

Hey, this is Thomas with the Node.js driver team!

I had a user report this case and I'm passing it up. This seems like odd behavior to me, perhaps there's a way of escaping that I'm not aware of.

For some reason when updateOne is passed a pipeline that `$sets` and the values in that document start with a `$` an update does not take place, it actually removes the property if it exists. If not it will do nothing. I've tried updating strings like `a$a` and they work as expected. 

Here's an example from the shell (4.3.6)

> db.meow.insert({ name: 'tom' })
WriteResult({ "nInserted" : 1 })
> const cat = db.meow.findOne()
> db.meow.updateOne({ _id: cat._id }, [{ $set: { password: '$anything' }}])
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 0 }
> db.meow.findOne()
{ "_id" : ObjectId("5ef65b23f330c5e7e431c975"), "name" : "tom" }
> db.meow.updateOne({ _id: cat._id }, [{ $set: { password: 'anything' }}])
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.meow.findOne()
{
 "_id" : ObjectId("5ef65b23f330c5e7e431c975"),
 "name" : "tom",
 "password" : "anything"
}



 Comments   
Comment by Thomas Reggi (Inactive) [ 07/Jul/20 ]

asya Thank you! Had no clue about $literal.

Comment by Asya Kamsky [ 07/Jul/20 ]

Note that traditional update command (no pipeline) will update the field as intended by the user...

db.meow.updateOne({ name:'tom' }, { $set: { password: '$name' }})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
 
db.meow.find()
{ "_id" : ObjectId("5f0498c9dd343e8f4b305488"), "name" : "tom", "password" : "$name" }

Comment by Asya Kamsky [ 07/Jul/20 ]

This is actually working as described in the docs - the brackets [ ] mean that this is an update that's using an aggregation pipeline syntax. In agg $set is just an alias for $addFields aggregation stage and values that start with $ are references to another field in the same document. Since setting password to value of non-existent field is "nothing", it is a no-op (you can see it in the "modifiedCount":0). Luckily, aggregation provides a way to use special value by wrapping it in the $literal expression.

db.meow.updateOne({ name:'tom' }, [{ $set: { password: '$anything' }}])
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 0 }
db.meow.updateOne({ name:'tom' }, [{ $set: { password: '$name' }}])
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
db.meow.find()
{ "_id" : ObjectId("5f0498c9dd343e8f4b305488"), "name" : "tom", "password" : "tom" }
db.meow.updateOne({ name:'tom' }, [{ $set: { password: {$literal: '$anything'} }}])
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
db.meow.find()
{ "_id" : ObjectId("5f0498c9dd343e8f4b305488"), "name" : "tom", "password" : "$anything" }

Generated at Thu Feb 08 05:19:05 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.