debitoor/mongopatch

Use positional operator in update and avoid race-conditions

Closed this issue · 2 comments

Beeing able to do the following patch would be nice

patch.update('tableTennis', {players: { $elemMatch: {firstName: 'Max'}}}, function (document, callback) {
        return callback(null, {$set:{'players.$.score': 21}});
    });

This can be done by extending the query-part of the findAndModify doing the statement, that currently only has the _id property, with the criteria for updating a document ({players: { $elemMatch: {firstName: 'Max'}}). This would also get rid of some race conditions, where the document has been modified to not fit the criteria for updating, while calculating the patch-modifier.

Using the whole document as criteria would prevent all race conditions where the document has been updated externally. The diff would also only show changes done by the patch (there is still a small chance for an external update occurring, but it is greatly reduced). The algorithm would be something like.

var updatedDocument = collection.findAndModify({ query: patch.before, update: patch.modifier, new: true })
if updatedDocument is null
    // The document has been updated externally. Fetch the updated document using id
    // and the original query (to make sure it still meets the criteria)

    var query = extend(patch.query, { _id: patch.before._id })
    updatedDocument = collection.findAndModify({ query: query, update: patch.modifier, new: true })

    if updatedDocument is null
        // The document doesn't meet the criteria any more, ignore document

I'm thinking

var updatedDocument = collection.findAndModify({ query: {$and:[patch.query, patch.before]}, update: patch.modifier, new: true }) //use patch.query to be able to use positional operator in modifier on $set (not sure if this works)
if updatedDocument is null
    // The document has been updated externally. Fetch the updated document using id
    // and the original query (to make sure it still meets the criteria)
    var query = extend(patch.query, { _id: patch.before._id })
    doc = collection.find({ query: query })
    if(!doc){
      // The document doesn't meet the criteria any more, ignore document
      return;
    }
    //rerun user-defined patch function to get potential new modifier
   // start from top