[SERVER-20203] upsert querying an array field and $push-ing to that same field yields confusing error Created: 30/Aug/15  Updated: 07/Apr/23  Resolved: 16/Sep/15

Status: Closed
Project: Core Server
Component/s: Write Ops
Affects Version/s: 2.6.0
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Evan Altman Assignee: Max Hirschhorn
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related
related to SERVER-34973 Upsert querying an array, then pushin... Closed
related to SERVER-6566 Support conditional updates: $updates Closed
Operating System: ALL
Sprint: Quint 9 09/18/15
Participants:

 Description   

When attempting to query on one field of an array element, the docs make it very clear that $elemMatch is not necessary.

For example, find({"list.field":"value"}) should match the following document: { "list":[ {"field":"value"}, {"otherField":"otherValue"} ] }.

However, doing an upsert using findAndModify on an empty collection:

 
 findAndModify(
  {"list.field":"value"},
  {"$push":{"list":{"field":"value"}}},
  {"upsert":1}
)

causes the following error:
"list" must be an array but is of type Object in document {INVALID-MUTABLE-ELEMENT}



 Comments   
Comment by Max Hirschhorn [ 15/Sep/15 ]

Hi Evan,

Thanks for reporting this.

As you've pointed out, the behavior of {"list.field": value} as a query predicate is different from {list: {$elemMatch: {field: value}}} during an update or findAndModify operation when the document isn't found and upsert=true.

// Success using $elemMatch.
db.coll.update({list: {$elemMatch: {field: "value"}}}, {$push: {list: {field: "value"}}}, true);
db.coll.findAndModify({query: {list: {$elemMatch: {field: "value"}}}, update: {"$push": {list: {field: "value"}}}, upsert: true});
 
// Failure using equality expression.
db.coll.update({"list.field": "value"}, {"$push": {list: {field: "value"}}}, true);
db.coll.findAndModify({query: {"list.field": "value"}, update: {$push: {list: {field: "value"}}}, upsert: true});

This is behavior is described in our documentation for upsert:

The update creates the new document with [...] the fields and values of both the <query> and <update> parameters if the <update> parameter contains update operator expressions. The update creates a base document from the equality clauses in the <query> parameter, and then applies the update expressions from the <update> parameter.

Internally, a base document is constructed and then the specified update modifiers are applied to it. All equality matches of the form {field: value} are assumed to refer to a document, even though nested fields could also refer to an array of subdocuments. Since $elemMatch is not an equality match, no equality match is extracted from the query predicate.

With regard to the previous examples, the base document {list: {field: "value"}} is constructed from the query {"list.field": "value"} and the base document {} is constructed from the query {list: {$elemMatch: {field: "value"}}}. The $push modifier cannot be applied to the list field because it contains a subdocument and is not an array, hence the error message. However, the $push modifier can be applied to an empty document, which adds the field to the document as a singleton array element.

Hope that helps,
Max

Comment by Sam Kleinman (Inactive) [ 03/Sep/15 ]

Thanks for this report and sorry for the confusion. We'll get this evaluated and triaged as soon as possible.

Comment by Evan Altman [ 30/Aug/15 ]

I should note that using $elemMatch for the query (instead of {list.field":"value"}) does work fine.

Generated at Thu Feb 08 03:53:30 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.