4

Scenario

I've the following document from Chat collection with an array of messages and members in the chat.

And for each message, there will be status field which will store the delivered and read timestamp with respect to users.

{
    "_id" : ObjectId("60679797b4365465745065b2"),
    "members" : [ 
        ObjectId("604e02033f4fc07b6b82771c"), 
        ObjectId("6056ef4630d7b103d8043abd"), 
        ObjectId("6031e3dce8934f11f8c9a79c")
    ],
    "isGroup" : true,
    "createdAt" : 1617401743720.0,
    "updatedAt" : 1617436504453.0,
    "messages" : [ 
        {
            "createdAt" : 1617401743719.0,
            "updatedAt" : 1617401743719.0,
            "_id" : ObjectId("60679797b4365465745065b3"),
            "body" : "This is test message",
            "senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
            "status" : []
        }
    ]
}

So, I want to insert the following data, into messages.status array, to know when the message is received/read by the member.

{
    receiverId: <member of chat>
    deliveredAt: <timestamp>
    readAt: <timestamp>
}

Question

How to write a query to insert the above json for each member (except the sender) in the status array by using the data from existing field?

So that, after query, the document should look like this:

{
    "_id" : ObjectId("60679797b4365465745065b2"),
    "members" : [ 
        ObjectId("604e02033f4fc07b6b82771c"), 
        ObjectId("6056ef4630d7b103d8043abd"), 
        ObjectId("6031e3dce8934f11f8c9a79c")
    ],
    "isGroup" : true,
    "createdAt" : 1617401743720.0,
    "updatedAt" : 1617436504453.0,
    "messages" : [ 
        {
            "createdAt" : 1617401743719.0,
            "updatedAt" : 1617401743719.0,
            "_id" : ObjectId("60679797b4365465745065b3"),
            "body" : "This is test message",
            "senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
            "status" : [{
                "receiverId": ObjectId("604e02033f4fc07b6b82771c")
                "deliveredAt": <timestamp>
                "readAt": <timestamp>
            }, {
                "receiverId": ObjectId("6056ef4630d7b103d8043abd")
                "deliveredAt": <timestamp>
                "readAt": <timestamp>
            }]
        }
    ]
}

Edit

I'm able to do this for static data.

Link: https://mongoplayground.net/p/LgVPfRoXL5p

For easy understanding: I've to map the members array and insert it into the status field of the messages

MongoDB Version: 4.0.5

1
  • Hi Can you elaborate on this point " insert the above json for each member (except the sender)". Like how do you identify the sender in chats collection? Commented Apr 5, 2021 at 10:23

2 Answers 2

1

You can use the $function operator to define custom functions to implement behavior not supported by the MongoDB Query Language. So along with updates-with-aggregate-pipeline and $function you can update messages.status array with only receiver's details as shown below:

NOTE: Works only with MongoDB version >= 4.4.

Try this:

let messageId = ObjectId("60679797b4365465745065b3");

db.chats.update(
  { "messages._id": messageId },
  [
    {
      $set: {
        "messages": {
          $map: {
            input: "$messages",
            as: "message",
            in: {
              $cond: {
                if: { $eq: ["$$message._id", messageId] },
                then: {
                  $function: {
                    body: function (message, members) {
                      message.status = [];
                      for (let i = 0; i < members.length; i++) {
                        if (message.senderId.valueOf() != members[i].valueOf()) {
                          message.status.push({
                            receiverId: members[i],
                            deliveredAt: new Date().getTime(),
                            readAt: new Date().getTime()
                          })
                        }
                      }

                      return message;
                    },
                    args: ["$$message", "$members"],
                    lang: "js"
                  }
                },
                else: "$$message"
              }
            }
          }
        }
      }
    }
  ]
);

Output:

{
    "_id" : ObjectId("60679797b4365465745065b2"),
    "members" : [
        ObjectId("604e02033f4fc07b6b82771c"),
        ObjectId("6056ef4630d7b103d8043abd"),
        ObjectId("6031e3dce8934f11f8c9a79c")
    ],
    "isGroup" : true,
    "createdAt" : 1617401743720,
    "updatedAt" : 1617436504453,
    "messages" : [
        {
            "_id" : ObjectId("60679797b4365465745065b3"),
            "createdAt" : 1617401743719,
            "updatedAt" : 1617401743719,
            "body" : "This is test message",
            "senderId" : ObjectId("6031e3dce8934f11f8c9a79c"),
            "status" : [
                {
                    "receiverId" : ObjectId("604e02033f4fc07b6b82771c"),
                    "deliveredAt" : 1617625735318,
                    "readAt" : 1617625735318
                },
                {
                    "receiverId" : ObjectId("6056ef4630d7b103d8043abd"),
                    "deliveredAt" : 1617625735318,
                    "readAt" : 1617625735318
                }
            ]
        },
        {
            "_id" : ObjectId("60679797b4365465745065b4"),
            "createdAt" : 1617401743719,
            "updatedAt" : 1617401743719,
            "body" : "This is test message",
            "senderId" : ObjectId("6031e3dce8934f11f8c9a79d"),
            "status" : [ ]
        }
    ]
}
Sign up to request clarification or add additional context in comments.

6 Comments

It'll work but I want to execute it for each member of the chat and what if it has 100 members?
No, I mean I just want to map the members array and insert it in the status. Did this make the question clear to you?
What is your MongoDB version?
Mongodb version is 4.0.5
Check the updated answer, If you upgrade your MongoDB.
|
0

Demo - https://mongoplayground.net/p/FoOvxXp6nji

https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/

The filtered positional operator $[] identifies the array elements that match the arrayFilters conditions for an update operation, e.g.

db.collection.update({
  "messages.senderId": "6031e3dce8934f11f8c9a79c" // query 
},
{
  "$push": {
    "messages.$[m].status": [ // push into the matching element of arrayFilters
      {
        "receiverId": ObjectId("604e02033f4fc07b6b82771c")
      },
      {
        "receiverId": ObjectId("6056ef4630d7b103d8043abd")
      }
    ]
  }
},
{
  arrayFilters: [
    {
      "m.senderId": "6031e3dce8934f11f8c9a79c" // matches array element where senderId is 6031e3dce8934f11f8c9a79c
    }
  ]
})

Note- add index to messages.senderId for performance

1 Comment

I'm able to do this without $[]. mongoplayground.net/p/LgVPfRoXL5p. Just need to map the status array directly from members field. That's where I'm stuck. One way is to get the members first then map it on the server side then insert it in the db. But could this be done in one query?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.