You were close at the end, but there are a few missing things. You cannot $rename
when using the positioning operator, instead you need $set
new name and $unset
old. But there is one more limitation here, since both of them will belong to the "checkout" as the parent path, since you cannot simultaneously perform both actions.
Another key line in your question is “cross element”, and this is the only thing you cannot do when updating “all” elements of an array at once. Well, not safe and not rewriting new data, anyway.
What you need to do is "iterate" each document and a similar iteration of each member of the array for a "safe" update. You cannot really iterate over only a document and "save" the entire array with the changes. Of course, not when anything else is actively using the data.
I would personally run such an operation in the MongoDB shell, if possible, since this is a “one-way” (hopefully) thing, and it saves the overhead of writing other API code. We also use the Bulk Operations API to make this as efficient as possible. With a mongoose, a little more digging is required to implement, but still can be done. But here is a list of shells:
var bulk = db.collection.initializeOrderedBulkOp(), count = 0; db.collection.find({ "checkouts.techId1": { "$exists": true } }).forEach(function(doc) { doc.checkouts.forEach(function(checkout) { if ( checkout.hasOwnProperty("techId1") ) { bulk.find({ "_id": doc._id, "checkouts._id": checkout._id }).updateOne({ "$set": { "checkouts.$.techId": checkout.techId1 } }); bulk.find({ "_id": doc._id, "checkouts._id": checkout._id }).updateOne({ "$unset": { "checkouts.$.techId1": 1 } }); count += 2; if ( count % 500 == 0 ) { bulk.execute(); bulk = db.collection.initializeOrderedBulkOp(); } } }); }); if ( count % 500 !== 0 ) bulk.execute();
Since the operations of $set
and $unset
are performed in pairs, we save the total batch size up to 1000 operations to execute in order to save memory usage on the client.
The loop simply looks for documents in which the field to be renamed "exists", and then iterates over each element of the array of each document and makes two changes. As bulk operations, they are not sent to the server until .execute()
is .execute()
, which also returns one response for each call. This saves a lot of traffic.
If you insist on coding with mongoose. Keep in mind that a .collection
acessor is required to access Bulk API methods from the main driver, for example:
var bulk = Model.collection.inititializeOrderedBulkOp();
And the only thing that is sent to the server is the .execute()
method, so this is your only execution callback:
bulk.exectute(function(err,response) {
And use .forEach()
instead asynchronous flow control like async.each .
Also, if you do this, then keep in mind that as a raw driver method that is not controlled by mongoose, you are not getting the same understanding of the database connection, as is the case with mongoose methods. If you do not know for sure that the database connection has already been established, then safter puts this code in the event callback to connect to the server:
mongoose.connection.on("connect",function(err) {
But otherwise, these are the only real ones (besides the call syntax) that you really need.