In 2.0.8, createNewFromQuery performs the following check to see if a
query field represents a value that should be incorporated in the
upsert document, or an operator:
if ( e.type() == Object && e.embeddedObject().firstElementFieldName()[0] == '$' ) { // this means this is a $gt type filter, so don't make part of the new object continue; }
In 2.2.2, that check became the following:
if ( e.type() == Object && e.embeddedObject().firstElementFieldName()[0] == '$' ) { // we have something like { x : { $gt : 5 } } // this can be a query piece // or can be a dbref or something int op = e.embeddedObject().firstElement().getGtLtOp( -1 ); if ( op >= 0 ) { // this means this is a $gt type filter, so don't make part of the new object continue; } }
Now we only consider the object field to represent an operator if
getGtLtOp() can interpret the field name as an operator. However,
this is not a complete check because getGtLtOp() only understands
simple field-value operators. It does not parse $not (or $and, etc.).
This means that the {$not: {$elemMatch:
}} portion of the
query is interpreted by the upsert implementation a value, not an
operator. The upserted document starts with a value of the 'r' field
of {$not: {$elemMatch:
}}. $push cannot operate on this value
because it is not an array, which causes the error the user reported.
Aaron
------------------------------------
In 2.2.x $elemMatch breaks $push when used inside of $not and with an upsert. This is useful is you want to push only when an existing property doesn't exist inside of an array.
An update in 2.0 such as:
db.test.update({_id: 1, r: {$not: {$elemMatch: {"a": 1}}}}, {$push: {r:
}},
{upsert: true});
or even:
db.test.update({_id: 1, r: {$not: {$elemMatch: {$or: [
,
{"e": 1}]}}}}, {$push: {r:
{"a": 1, "e": 0}}},
{upsert: true});
Would successfully create a new document:
{ "_id" : 1, "r" : [
] }
If you called either of those updates again, it would expectantly throw a 11000 error:
E11000 duplicate key error index: test.test.$id dup key: { : "hello" }
Which meant that there already was a value inside "r" with either
or
{"e": 1}.
Now in 2.2 either of those updates fails with error:
Cannot apply $push/$pushAll modifier to non-array
There was a proposed workaround (using $nin): http://stackoverflow.com/questions/12391334/mongodb-upsert-with-push-and-not
But that doesn't actually work. The $nin fails to actually look inside the array, which is expected as it wouldn't seem that it would anyways.