Is there any way to toggle the boolean field of ONE document in MongoDB with atomic operation? Say, (In python)

cl.update({"_id": ...}, {"$toggle": {"field": 1}})

Solution 1

Right now, I don't think it's possible to do this with one operation. The bitwise operators (http://www.mongodb.org/display/DOCS/Updating#Updating-%24bit) don't have a '$xor' yet although I've a patch for it.

Right now the workaround I think think of is by always using '$inc':

cl.update( { "_id": ...}, { '$inc' : { 'field' : 1 } } );

Then instead of checking for true or false, you can do check whether an item is "true":

cl.find( { "_id": ..., 'field' : { '$mod' : [ 2, 1 ] } );

IE, you using the modulo operator to see whether it's even or uneven with even being "unset", and uneven being "set". If you want to have the oppposite behaviour (ie, find all items that don't have the flag set), then use

[ 2, 0 ];

Solution 2

The SERVER-4362 issue is actually resolved now and you have the $bit update operator available. So along with it's xor argument you can now do this in an atomic action:

cl.findOneAndUpdate( 
  { "_id": ...}, 
  { 
     "$bit": { 
         "field": { "xor": NumberInt(1) } 
     } 
  },
  { "returnNewDocument": true, "upsert": true }
);

So as long as the value of field is kept at 0 or 1 then a bitwise "flip" will result that makes the current value the opposite of what it was at the time of the modification.

The .findOneAndUpdate() is not required, but just a way of demonstrating that the resulting value is different on every modification.

Solution 3

You can use update with aggregation pipeline starting from MongoDB v4.2,

1) Option using $not

  • Evaluates a boolean and returns the opposite boolean value; i.e. when passed an expression that evaluates to true, $not returns false; when passed an expression that evaluates to false, $not returns true.
cl.update(
  {"_id": ...}, 
  [
    { "$set": { "field": { "$not": "$field" } } }
  ]
)

Playground

Drawbacks of $not evaluates as false the following: null, 0, and undefined values and other values as true!

2) Option using $eq

  • Will set field to true if it is false, and to false if it is any other value.
cl.update(
  {"_id": ...}, 
  [
    { "$set": { "field": { "$eq": [false, "$field"] } } }
  ]
)

Playground