Positional operator not working in MongoDB with array elements FastAPI

Question:

I have a document that looks like this:

{
  "_id": "cc3a8d7f-5962-47e9-a3eb-09b0a57c9fdb",
  "isDeleted": false,
  "user": {
    "timestamp": "2023-02-12",
    "name": "john",
    "surname": "doe",
    "email": "[email protected]",
    "phone": "+012345678912",
    "age": 25,
    "gender": "female",
    "nationality": "smth",
    "universityMajor": "ENGINEERING",
    "preferences": null,
    "highPrivacy": false,
  },
  "postings": [
    {
      "id": "f61b103d-8118-4054-8b24-b26e2f4febc4",
      "isDeleted": false,
      "timestamp": "2023-02-12",
      "houseType": "apartment",
      "totalNumOfRoommates": 5,
      "location": {
        "neighborhood": "Oran",
        "district": "Çankaya",
        "city": "Adana"
      },
      "startDate": "2022-11-10",
      "endDate": "2022-11-15",
      "postingType": "House Sharer",
      "title": "House sharer post 1",
      "description": "This is house sharer post 1",
      "price": 2500,
      "houseSize": "2 + 0"
    },
    {
      "id": "b7d34113-1b13-4265-ba9b-766accecd267",
      "isDeleted": false,
      "timestamp": "2023-02-12",
      "houseType": "apartment",
      "totalNumOfRoommates": 5,
      "location": {
        "neighborhood": "Dikmen",
        "district": "Çankaya",
        "city": "Adana"
      },
      "startDate": "2022-09-13",
      "endDate": "2023-12-24",
      "postingType": "House Seeker",
      "startPrice": 2002,
      "endPrice": 2500
    }
  ],
}

Each posting object has an ID. I am trying to "delete" (setting the property isDeleted to True, rather than actual deletion) the post whose ID is specified in the code below:

@router.delete('/{id}', response_description='Deletes a single posting')
async def deletePost(id: str):       

    update_result = await dbConnection.update_one({"postings.id": id, "postings.isDeleted" : False}, 
    {"$set" : {"postings.$.isDeleted" : True} })
    if update_result.modified_count == 1:
        return Response(status_code=status.HTTP_204_NO_CONTENT)
    else:
        raise HTTPException(status_code=404, detail=f"Post {id} not found or has already been deleted")

The issue is that the first document (the one with ID f61b103d-8118-4054-8b24-b26e2f4febc4) is being "deleted" even when I supply the ID b7d34113-1b13-4265-ba9b-766accecd267 to the function. If I hit the endpoint again with the same ID, it "deletes" the array elements in order regardless of which ID I supply. Even though I am using the positional operator to set the specific element’s property isDeleted to True.

What exactly could be the problem here?

Here is a link with the earlier setup in Mongo Playground: https://mongoplayground.net/p/03HSkwDUPUE

P.S although @ray’s answer does work in MongoPlayground, I had to change a couple of things in the query with FastAPI, for anyone interested, the working query is below:

update_result = await dbConnection.update_one(
        {"postings.id": id},
        {
            "$set": {
                "postings.$[p].isDeleted": True
            }
        },
        upsert=True,
        array_filters=[
            {
                "p.id": id,
                "p.isDeleted": False
            }]
    )
Asked By: Ahmet-Salman

||

Answers:

Your query now is kind of like searching documents through 2 criteria in a "or" behaviour.

  1. "postings.id": "b7d34113-1b13-4265-ba9b-766accecd267" – find document with any postings element with id b7d34113-1b13-4265-ba9b-766accecd267
  2. "postings.isDeleted": false – find document with any postings element with deleted is false

Note the "any". That means the 2 criteria are not required to be happened on the same array element. So, that is kind of "or" behaviour.

You can use arrayFilters to achieve what you want.

db.collection.update({},
{
  "$set": {
    "postings.$[p].isDeleted": true
  }
},
{
  arrayFilters: [
    {
      "p.id": "b7d34113-1b13-4265-ba9b-766accecd267",
      "p.isDeleted": false
    }
  ]
})

Mongo Playground

Answered By: ray
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.