[SERVER-22102] New update operator for compare-and-set pattern Created: 08/Jan/16  Updated: 06/Dec/22  Resolved: 29/Jun/19

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

Type: New Feature Priority: Major - P3
Reporter: JianXiong Zhou [X] Assignee: Backlog - Query Team (Inactive)
Resolution: Won't Do Votes: 4
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related
related to SERVER-6566 Support conditional updates: $updates Closed
related to SERVER-14609 Add $setOnMissing update operator Closed
Assigned Teams:
Query
Participants:

 Description   

Would mongodb provide a new update operator to make compare-and-set updates? Something like:

    { "$cond" : { "foo": [ oldValue, newValue ] } }

mongodb set `newValue` to the "foo" field only when the original value of "foo" is equal to `oldValue`.

Further more, the `oldValue` could be a boolean expression with the original value, and mongodb updates if the condition is true:

    // if (doc.foo != 1) { doc.foo = 2; }
    { "$cond" : { "foo": [ {"$ne": 1}, 2 ] } }

There are already two similar operators: $min and $max, so I think this enhancement should be possible as well.



 Comments   
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 done now by using various aggregation expressions.

Comment by Ramon Fernandez Marina [ 27/Jan/16 ]

We're sending this request to the Query Team for evaluation.

Comment by Mark Bigler [ 19/Jan/16 ]

+1

This would also allow to set, but not overwrite values if existing. See SERVER-14609

{"$cond": {"field": [ $exists: false, "newValue"]}}

Comment by JianXiong Zhou [X] [ 12/Jan/16 ]

A small declaration: It may be confusing, but my use case is not dealing with multi-documents. My previous comments just for answering Ramón's questions.

I have a collection named "User" for storing users' profile, and each profile needs to be reviewed by the administrator. There is a "status" field and its value could be one of "PENDING", "APPROVED" and "REJECTED". The "PENDING" status means it is pending for reviewing, the administrator can change the status from "PENDING" to "APPROVED" or "REJECTED" after reviewing. When in the "APPROVED" status, if the user updates his/her profile, the "status" will become to "PENDING" to inform the administrator to review the profile again. However, the "REJECTED" status can only be changed by the administrator. So I have to update the "status" field conditionally when updating user profiles.

    db.User.update({"id": 123}, {"$set" : {"nickname": "foo"}, "$cond": {"status": ["APPROVED", "PENDING"]}})
 
    // Please notice that I still can't specify the condition to the query part, because I don't exactly know the value of "status" until it is loaded from database.

I had implemented this need by: get the value of "status" by findAndModify and then update it if needed.

In my opinion, no matter in what use case, using two update commands is not ideal. The extra update command leads to more overload and concurrency issues.

Also, the proposed new operator is an enhanced version of $min/$max:

    // {"$min": {"score": 100}}
    {"$cond": {"score": [{"$gt": 100}, 100]}}

Can I ask why $min and $max were added?

Comment by Kelsey Schubert [ 11/Jan/16 ]

Hi X,

Can you elaborate on your use case?

For example, if you want to set the "bar" field into all documents, and conditionally update the "foo" field. Why not use the following two commands?

db.col.update({},{$set : {"bar": "whatever"}}, {multi : true})
db.col.update({"foo" : "oldValue"}, {$set : {"foo" : "newValue"}}, {multi : true})

Thank you,
Thomas

Comment by JianXiong Zhou [X] [ 09/Jan/16 ]

It is the latter, but the update and $set trick doesn't help. Sometimes we can't specify the condition to the query part, please think about this example:

    // the "bar" field will be set into all documents, and the "foo" field will be updated conditionally
    db.col.update({}, {$cond: {"foo": [oldValue, newValue]}, $set : {"bar": whatever}}, {multi : true})
 
    // not all documents will be touched, so it doesn't work as expected
    db.col.update({"foo" : oldValue}, {$set : {"foo" : newValue, "bar": whatever}}, {multi : true})

I believe that was the reason for inventing $min and $max.

Comment by Ramon Fernandez Marina [ 09/Jan/16 ]

If the idea is for the $cond operator to provide multi-document atomicity then I'd say this is the same functionality as described in SERVER-11500.

If no multi-document atomicity is required, you can do this today with update and $set. Reusing your examples above:

// set `newValue` to the "foo" field only when the original value of "foo" is equal to `oldValue`
db.col.update({"foo" : oldValue}, {$set : {"foo" : newValue}}, {multi : true})
 
// if (doc.foo != 1) { doc.foo = 2; }
db.col.update({"foo" : {$ne: 1}}, {$set : {"foo" : 2}}, {multi : true})

With the {multi:true} argument the update operation affects all documents in the collection that satisfy the query argument.

Does this meet your requirements?

Comment by JianXiong Zhou [X] [ 09/Jan/16 ]

The sightly difference between findAndModify and the $cond operator (precisely is the update command) is that findAndModify updates one document at a time, while the $cond operator could effect many documents. In my understanding, findAndModify provides the atomicity in the document level, and the $cond operator just cares about a single field. Please consider it, thanks.

Comment by Ramon Fernandez Marina [ 08/Jan/16 ]

zhoujianxiong2@gmail.com, if I understand correctly you're describing functionality already available via findAndModify() – can you please review the semantics of findAndModify and elaborate on how a new operator would perform differently?

Thanks,
Ramón

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