Query
- pipeline update requires MongoDB >= 4.2
newProduct is the product you want to add (a js variable)
- check if product already exists => add not-exist? field
- if not-exists add it in the end
- else map to find it and update the quantity
- unset the 2 fields
newProduct and not-exists
*it does 2 array reads, alternative could be to use $reduce but if you have many products $concatArrays is slow to be inside a reduce, so this is faster solutions (even if reduce would read the array 1 time only)
*you need a method to do update with pipeline, i don't know if mongoose is updated to support it, we are MongoDB 5 so i guess it will be(java is), in worst case you can use updateCommand and call it with runCommand(...)
Test code here
update({"_id" : "1"},
[{"$set":
{"newProduct":
{"product":"p1",
"options":{"size":"s1", "color":"c1"},
"quantity":1}}},
{"$set":
{"not-exists?":
{"$eq":
[{"$filter":
{"input":"$items",
"cond":
{"$and":
[{"$eq":["$$this.product", "$newProduct.product"]},
{"$eq":["$$this.options.size", "$newProduct.options.size"]},
{"$eq":["$$this.options.color", "$newProduct.options.color"]}]}}},
[]]}}},
{"$set":
{"items":
{"$cond":
["$not-exists?", {"$concatArrays":["$items", ["$newProduct"]]},
{"$map":
{"input":"$items",
"in":
{"$cond":
[{"$and":
[{"$eq":["$$this.product", "$newProduct.product"]},
{"$eq":["$$this.options.size", "$newProduct.options.size"]},
{"$eq":["$$this.options.color", "$newProduct.options.color"]}]},
{"$mergeObjects":
["$$this", {"quantity":{"$add":["$$this.quantity", 1]}}]},
"$$this"]}}}]}}},
{"$unset":["not-exists?", "newProduct"]}])
Query2
- if you don't want to use update pipeline you can do it with more queries
Check if exists
db.collection.find({
"_id" : "1",
"items": {
"$elemMatch": {
"product": "p1",
"options": {
"size": "s1",
"color": "c1"
}
}
}
})
If not exists
db.collection.update({
"_id": "1"
},
{
"$push": {
"items": "NEWITEM" //put here the new object
}
})
else If exists
db.collection.update({"_id" : "1"},
{
"$inc": {
"items.$[i].quantity": 1
}
},
{
arrayFilters: [
{
"i.product": "p1",
"i.options.size": "s1",
"i.options.color": "c1"
}
]
})