Bulk upsert - issue with Mongo id [solved]

I’m trying to implement a bulk upsert. I almost got things to work, but the resulting document ids are weird:

import { Mongo } from 'meteor/mongo';
const Things = new Mongo.Collection('things');
const thingsCollection = Things._collection.rawCollection();
const bulk = thingsCollection.initializeUnorderedBulkOp();
documents.forEach(doc => {
  bulk.find(doc).upsert().updateOne(doc);
})
bulk.execute().then(console.log).catch(console.log);

Things work as excepted regarding the upserting and the bulk, but then documents have an _id that looks like

_id: { [String: '583e0dcc39ff3ba3228523cc'] _str: '583e0dcc39ff3ba3228523cc'

instead of just

_id: '583e0dcc39ff3ba3228523cc'

Any idea of where the issue is coming from and how to fix it? Thanks!

2 Likes

Because you are using the Mongodb _collection object, the idGeneration is in ‘MONGO’ default ObjectID format instead of the ‘STRING’ format that Meteor defaults to (see idGeneration option in http://docs.meteor.com/api/collections.html). If you need your bulk loaded docs to use the Meteor ‘STRING’ format you can generate the _id yourself for each doc using Random.id (see http://docs.meteor.com/packages/random.html#Random-id) and include it in doc object before using it.

Although, I don’t get the upsert… isn’t it an insert all the time, since you are generating new _id?

The upsert is an insert if it doesn’t find a matching document, else an update. So I can’t pass a new id in the find filter since it would return nothing, and I can’t pass an id in the update since you can’t update ids.

The docs state:

Then, if the update operation with the Bulk.find.upsert() option performs an insert, the update operation inserts a single document with the fields and values from the query document of the Bulk.find() method and then applies the specified update from the update document. If neither the update document nor the query document specifies an _id field, MongoDB adds the _id field

So, the way I read it, if you specify in your find(doc1) the doc without the _id, so that it updates instead of inserting a new one when matching, and in the updateOne(doc2) part you include the a Random.id generated _id field, that should be used when there is no match in doc1.

Ok so there is a $setOnInsert parameter I didn’t know about. It’s a good place to set an _id because it’s not possible to set the _id in $set, which is what updateOne defaults to. So a working solution is:

import { Mongo } from 'meteor/mongo';
const Things = new Mongo.Collection('things');
const thingsCollection = Things._collection.rawCollection();
const bulk = thingsCollection.initializeUnorderedBulkOp();
documents.forEach(doc => {
  bulk.find(doc).upsert().updateOne({ $setOnInsert: { _id: Random.id() } });
})
bulk.execute().then(console.log).catch(console.log);
3 Likes

I’m getting a function-not-found error in a server side method. Works well with insert or update, but, for some reason, not updateOne. Must one use some sort of package to use it?

Thanks

Can you share the code that’s not working?

Thanks! Here it is… the “collection.update” statement works like a charm, but won’t return the id of the inserted document. Changing that, simply to “collection.updateOne”, throws an error on the server console, as per below.

I’m using Meteor 1.4.2.3 and a separate instance of Mongo v3.2.11

(1) ERROR MESSAGE
I20170118-02:05:16.839(-5)? Exception while invoking method ‘method_name’ TypeError: collection.updateOne is not a function
I20170118-02:05:16.839(-5)? at [object Object].verseTagAdd (server/methods/method_name.js:40:14)
I20170118-02:05:16.839(-5)? at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1712:12)
I20170118-02:05:16.840(-5)? at packages/ddp-server/livedata_server.js:711:19
I20170118-02:05:16.840(-5)? at [object Object]..extend.withValue (packages/meteor.js:1122:17)
I20170118-02:05:16.840(-5)? at packages/ddp-server/livedata_server.js:709:40
I20170118-02:05:16.840(-5)? at [object Object].
.extend.withValue (packages/meteor.js:1122:17)
I20170118-02:05:16.840(-5)? at packages/ddp-server/livedata_server.js:707:46
I20170118-02:05:16.840(-5)? at Session.method (packages/ddp-server/livedata_server.js:681:23)
I20170118-02:05:16.840(-5)? at packages/ddp-server/livedata_server.js:551:43

(2) CODE
collection.update(. //trying to change to updateOne

// Query
{
    "field_1" : parameter_1
}

// Update
, {
    // Set
    $set:{
        "soft_deleted" : null
    }

    // Set only on insert
    , $setOnInsert:{
        "field_2" : languageId
        // other fields, etc
    }
}

// Options
, {
    upsert: true
}

// Callback
, function(err, docId) {
    if(!docId) return null;               
    // Insert the docId into another collection, etc.
}

);

There is no updateOne method in minimongo. If you want to use it you’ll need to use the underlying library, which you can do with collection.rawCollection().updateOne(...). Note that rawCollection is only available to server code.

You’ll also need to ensure you structure the query according to the library documentation.

1 Like