Fred J.
Fred J.

Reputation: 6049

Upsert with $exists not allowed: The dollar ($) prefixed field is not valid for storage

This Meteor server side (Mongo 1.1.18) tries to upsert a document according to the selector as shown but gives the following error:

  myCol.upsert({name: 'sam', job: {$exists: false}}, {$set: {parents: ['jack', 'jacky']}});

MongoError: The dollar ($) prefixed field '$exists' in 'job.$exists' is not valid for storage.

How can I upsert to this selected document? or create it if it does not exist? thx

Upvotes: 0

Views: 3787

Answers (1)

Neil Lunn
Neil Lunn

Reputation: 151200

The reason is because with an "upsert" MongoDB is trying to assign any "query" arguments supplied as a "value" in the newly created object. Since you cannot name a property with a $ the error is thrown because it is trying to create the field "job" as { "job": { "$exists": true } }, just like you supplied in the query arguments.

To avoid this, tell MongoDB which fields to actually use on creation by specifying $setOnInsert. At least that's the case for a normal MongoDB collection. Thus for "meteor" you should access the .rawCollection() instead"

myCol.rawCollection().update(
  {name: 'sam', job: {$exists: false}},
  {
   $setOnInsert: { name: 'sam' },
   $set: {parents: ['jack', 'jacky']}
  },
  { upsert: true }
)

Note that when you do this this does not use various other meteor defaults, such as the value of _id, so you might need to use the alternate approach.

The alternate approach here is to instead use $where which can evaluate a JavaScript condition against the document to see that the "job" field is not present. This would actually not be carried over to to "upsert" even from the meteor collection implementation:

myCol.upsert(
  {name: 'sam', '$where': "!this.hasOwnProperty('job')" },
  {
   $set: {parents: ['jack', 'jacky']}
  }
)

To prove the point, I started up a bare meteor project with no other changes than just adding a collection and executing the the code on server startup. So basically on a new project changing server/main.js as follows:

import { Meteor } from 'meteor/meteor';
import { People } from '../imports/people.js';

Meteor.startup(() => {
  // code to run on server at startup

  People.upsert(
    { name: 'john', '$where': "!this.hasOwnProperty('job')" },
    {
      '$set': { parents: [ 'jack', 'jacky' ] }
    }
  );

  People.rawCollection().update(
    { name: 'bill', job: { '$exists': false }  },
    {
      '$setOnInsert': { name: 'bill' },
      '$set': { parents: [ 'jack', 'jacky' ] }
    },
    { 'upsert': true }
  )

});

And the imports/people.js:

import { Mongo } from 'meteor/mongo';

export const People = new Mongo.Collection('people');

And the documents are created in the collection without error as expected:

{
        "_id" : "irDWFLdACjNKYGEcN",
        "name" : "john",
        "parents" : [
                "jack",
                "jacky"
        ]
}
{
        "_id" : ObjectId("596489d0902edee769372ac6"),
        "name" : "bill",
        "parents" : [
                "jack",
                "jacky"
        ]
}

Upvotes: 1

Related Questions