[SERVER-18001] Provide $replace modifier Created: 12/Apr/15  Updated: 06/Dec/22  Resolved: 29/Jun/19

Status: Closed
Project: Core Server
Component/s: Write Ops
Affects Version/s: None
Fix Version/s: None

Type: New Feature Priority: Major - P3
Reporter: NOVALUE Mitar Assignee: Backlog - Query Team (Inactive)
Resolution: Won't Do Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related
Assigned Teams:
Query
Participants:

 Description   

Currently the only way to replace the whole document in `update` query is to specify it at the top level:

db.collection.update({_id: ...}, {new: 'document'})

But this makes it problematic because we have to sanitize the new document if it is coming from untrusted source to remove any update operators. It would be really great if MongoDB would provide something a `$replace` update operator, which would work exactly like the query above, but would not allow any update operators:

db.collection.update({_id: ...}, {$replace: {new: 'document'}})



 Comments   
Comment by Mi Tar [ 12/Sep/20 ]

Hm, but why is this not available for regular update and just for aggregation update?

Comment by NOVALUE Mitar [ 30/Jun/19 ]

Awesome! I agree this looks nice now.

Comment by Asya Kamsky [ 30/Jun/19 ]

Sorry, I meant to include $literal specification. I believe this will do exactly what you want:

 
db.c.update({},
            [ {$replaceWith:  {$literal: newDoc }} ]
);

I believe this will give you exactly what you want (an error with or without upsert).

Comment by NOVALUE Mitar [ 29/Jun/19 ]

I disagree. replaceWith does not address the main motivation behind this issue, I will quote:

> this makes it problematic because we have to sanitize the new document if it is coming from untrusted source to remove any update operators.

Sadly, replaceWith support special operators as its content and does not replace the document "raw". This is also visible in its documentation in this example:

db.collection.aggregate([ { $replaceWith: { $mergeObjects: [

{ _id: "$_id", first: "", last: "" }

, "$name" ] } } ])

From my understanding, this would not replace a document with field named "$mergeObjects" nor it would fail, but would succeed with something else being stored.

So there is still no way that I could take raw document and without escaping and sanitizing replace an existing document in the database. I can insert it, but I cannot replace it.

This is problematic because one cannot implement a simple CRUD API.

I would ask for this issue to be reopened.

Comment by Asya Kamsky [ 29/Jun/19 ]

SERVER-40381 implemented support for aggregation expressions to specify update for 4.2. 

 

You can see some examples here.

 

This can be handled using $replaceWith in update pipeline:

 

db.c.update({},
            [ {$replaceWith:  newDoc } ]
); 

edit this comment is missing $literal which should be right hand side with newDoc as its argument.

Comment by David Storch [ 25/Jul/17 ]

Hi mitar, this ticket has a fixVersion of "Backlog". This means that it is not currently prioritized to be worked on for an upcoming release. I'm not sure if you'd have any interest in working on a pull request, but if so, please let us know. Before working on the code, we would want to carry the exact proposed syntax/behavior through design review.

Comment by NOVALUE Mitar [ 24/Jul/17 ]

Any progress on this?

Comment by David Storch [ 26/Feb/16 ]

mitar, understood, thanks for the clarification. I'm re-opening this ticket.

Comment by NOVALUE Mitar [ 26/Feb/16 ]

No, MongoDB is not rejecting all $-prefixed fields. If a document coming from untrusted source contains for example:

{$set: {bla: 1}}

And I want that existing document is updated to new value, then if I take this given document and just call update, MongoDB will not reject it, but will do a field update. Now, I have to sanitize the document manually, check if there are any $-prefixed fields in it, and this is error prone. By providing $replace operator, I could do:

newDoc = {$set: {bla: 1}}
update({_id: id}, {$replace: newDoc})

and MongoDB would complain.

It is just to assure correct program logic without having to think about corner cases.

This is similar to $eq operator. In theory, it is not needed. But then you have to sanitize input yourselfs. MongoDB finally provides $eq operator because now you can say if some value is really equal, not having to think about special case of MongoDB operators.

Comment by David Storch [ 26/Feb/16 ]

Hi mitar,

Thanks for filing this feature request! The server should already validate that a the document resulting from an update is valid for storage before applying the write. In particular, $-prefixed fields are rejected. Therefore, we do not see a use case for the proposed $replace operator. We are closing as Won't Fix. If you can clarify the use case or provide further definition, we can re-open.

Best,
Dave

Generated at Thu Feb 08 03:46:15 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.