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 the numbers 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 }
);