An upsert operation updates an existing document or inserts a new one. But, using array update operators such as $addToSet or $push may lead to errors, when the query contains the corresponding field predicate and the insert-case is activated. I will show you the case that I encountered and how to fix it.
1. Problem
MongoDB version: 4.2.8
Let’s say you have the following upsert command:
db.example.updateOne(
{ numbers: 69 },
{ $addToSet: { numbers: 69 } },
{ upsert: true }
);
You expect the command will insert a new document if no document matches the query, right? In fact, no document has been added and you will see the following error:
Cannot apply $addToSet to non-array field. Field named 'numbers' has non-array type double
Oops!
The bad news is not only $addToSet
but also other array update operators also suffer the same fate!
But do not worry, the good news is that I will show you how to fix it soon.
2. Why?
An upsert operation has two cases: an update-case and an insert-case. With the update-case, if the document exists and its numbers
field is an array, the command will work as expected. Trouble comes with the insert-case only.
Let’s dig deeper!
The root of the problem is the query predicate {numbers: 69}
. I tried a few alternatives but none worked:
{ numbers: { $eq: 69 } }
{ numbers: { $in: [69] } }
{ numbers: { $in: [69, 69] } }
Let me explain.
Imagine the update-case has 3 steps: prepare a new document, update it, and then add it to the database.
Step #1: Preparing a new document
The query engine converts those query predicates to the same as
{numbers: 69}
. This equality comparison lets MongoDB create a new document{numbers: 69}
because it thinks you want to find a document with the value of thenumbers
is 69.Step #2: Updating the document
It is not too hard to realize the reason why the error occurred, right? The
numbers
field is not an array (it is a number in this case), so$addToSet
cannot be used. The process failed right here, and step 3 never happened.
To fix this problem, you must make sure that MongoDB does not create a new document with a non-array numbers
field. The next section will show the solution I have applied.
3. Solution
The $elemMatch, an array update operator, to the rescue! By using it, MongoDB implies that the numbers
field is an array and that error will disappear completely.
db.example.updateOne(
{ numbers: { $elemMatch: { $eq: 69 } } },
{ $addToSet: { numbers: 69 } },
{ upsert: true }
);