Reputation: 6627
I'm trying to update a user-document in a MongoDB collection. If the user doesn't exist it should be created, but if it already exist it should only be updated given the query parses certain checks.
This is what I first tried:
var ts = new Date('2015-01-01T00:00:00.000Z');
var query = { name: 'bob', updatedAt: { $lt: ts } };
var update = { $set: { age: 42, updatedAt: ts }, $inc: { updates: 1 } };
db.users.update(query, update, { upsert: true });
The problem with that solution is that it will try to create the user twice if its updatedAt
property is less than the given ts
date.
How can I make sure it's created only if the name
part of the query doesn't match any documents, but don't do anything if the updatedAt
part doesn't?
Upvotes: 0
Views: 566
Reputation: 52000
Basically:
upset
and $setOnInsert
;update
.Simply said, you need two update statements. But, you may wrap them in the same update command:
db.runCommand({
update: 'users',
updates: [
{ q: { name: 'bob' },
u: { $setOnInsert: { updatedAt: now, updates: 0 }},
upsert: true },
{ q: { name: 'bob', updatedAt: { $lt: now } },
u: { $set: { updatedAt: now }, $inc: { updates: 1 } },
upsert: false },
]
})
Producing on the first run (empty collection):
> var now = new Date()
> db.runCommand({ update: 'users', updates: [ { q: { name: 'bob' }, u: { $setOnInsert: { updatedAt: now, updates: 0 }}, upsert: true }, { q: { name: 'bob', updatedAt: { $lt: now } }, u: { $set: { updatedAt: now }, $inc: { updates: 1 } }, upsert: false }, ] })
{
"ok" : 1,
"nModified" : 0,
"n" : 1,
"upserted" : [
{
"index" : 0,
"_id" : ObjectId("556f672cd418ed1506eb2ca3")
}
]
}
Then, when run again:
> var now = new Date()
> db.runCommand({ update: 'users', updates: [ { q: { name: 'bob' }, u: { $setOnInsert: { updatedAt: now, updates: 0 }}, upsert: true }, { q: { name: 'bob', updatedAt: { $lt: now } }, u: { $set: { updatedAt: now }, $inc: { updates: 1 } }, upsert: false }, ] })
{ "ok" : 1, "nModified" : 1, "n" : 2 }
Please note that the update command is not atomic. But there is no way for an other client to see a partially created or updated user document as either the first update statement create the fully populated document, or it already exists (and is left untouched) until the second statement that updates it completely.
It is even safe if the user is concurrently created between the two statements -- it will then be transparently updated if needed.
Upvotes: 3