Search code examples
mongodbmongoosechangestream

How to filter the update not to include a parent field when using change stream in Mongodb


My document example:

{
id: 139234234,
count: 12,
type: apple,
buyer: [
    {name: "peter", price: 1.23, max: 129}, 
    {name: "alex", price: 1.4, "max": 12146}
    ]
}

My update:

db.updateMany({ id: 139234234 }, [
    {
      $set: {
        buyer: {
          $concatArrays: [
            {
              $filter: {
                input: "$buyer",
                cond: {
                    { $ne: ["$$this.name", "john"] },
                },
              },
            },
            [
              {
                name: "john",
                price: 12,
                max: 234,
              },
            ],
          ],
        },
      },
    },
  ]);

I frequently do this kind of update in buyer field, what should I write for the change stream pipeline (db.collection.watch(changeStreamPipeline)) if I want any update not include buyer. For example: The current update description

 updateDescription: {
    updatedFields: {
      'buyer.2.name': "alex",
      'buyer.2.price': 1.4,
      'buyer.2.max': 12146,
      }}

I don't want to receive any message of the updateFields that start with buyer, for exmaple: buyer.2.name, buyer.2.price. I tried the following pipeline, but it only works for the update field that exactly is buyer:

db.collection.watch(
    [
        {
            $match: {
                operationType: "update",
                "updateDescription.updatedFields.buyer": {
                    $exists: false
                }
            }
        }
    ]
)

Solution

  • What you can do is add an $addFields stage prior to your $match stage, in which we'll remove all "buyer" keys from the object before performing the match stage to see if any other fields were updated,

    Here is how I did this using $objectToArray and $regexMatch:

    const pipeline = [
        {
            $addFields: {
                "updateDescription.updatedFields": {
                    $arrayToObject: {
                        $filter: {
                            input: {
                                $objectToArray: { $ifNull: ["$updateDescription.updatedFields", {}]}
                            },
                            cond: {
                                $not: { $regexMatch: {input: "$$this.k", regex: /^buyer\..*/,} }
                            }
                        }
                    }
                }
            }
        },
        {
            $match: {
                $or: [
                    {
                        operationType: {$in: ["update", 'replace']},
                        $expr: {
                            $gt: [
                                {$size: {$objectToArray: { $ifNull: ["$updateDescription.updatedFields", {}]}}},
                                0
                            ]
                        }
                    },
                    {
                        operationType: {$in: ["delete", 'insert']},
                    }
                ]
            }
        }
    ];
    
    const changeStream = collection.watch(pipeline);