|
If a client uses $set to add a field to an existing document during an update, it should set updatedExisting to true in the last-error document, but only so sets updatedExisting when $set replaces an existing field.
Expected behavior:
connecting to: test
|
> db.a.drop()
|
> db.a.insert({_id: 1})
|
> db.a.update({_id:1}, {a:2})
|
> db.getLastErrorObj()
|
{
|
"updatedExisting" : true,
|
"n" : 1,
|
"connectionId" : 6,
|
"err" : null,
|
"ok" : 1
|
}
|
> db.a.update({_id:1}, {a:3})
|
> db.getLastErrorObj()
|
{
|
"updatedExisting" : true,
|
"n" : 1,
|
"connectionId" : 6,
|
"err" : null,
|
"ok" : 1
|
}
|
Actual behavior:
connecting to: test
|
> db.a.drop()
|
> db.a.insert({_id: 1})
|
> db.a.update({_id:1}, {a:2})
|
> db.getLastErrorObj()
|
{ "n" : 0, "connectionId" : 6, "err" : null, "ok" : 1 }
|
> db.a.update({_id:1}, {a:3})
|
> db.getLastErrorObj()
|
{
|
"updatedExisting" : true,
|
"n" : 1,
|
"connectionId" : 6,
|
"err" : null,
|
"ok" : 1
|
}
|
|
|
The new write commands correctly return nModified as you can see here:
> db.foo.insert({_id:1})
|
WriteResult({ "nInserted" : 1 })
|
> db.foo.update({_id:1}, {a:1})
|
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
|
> db.foo.update({_id:1}, {a:1}) // since the replacement doesn't include _id (all existing fields) it is *not* a no-op
|
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
|
> db.foo.update({_id:1}, {_id:1, a:2}) // no-op cause it matches existing doc
|
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
|
> db.foo.update({_id:1}, {a:2})
|
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
|
As getLastError does not return nModified, you can still use updatedExisting as you can see here in legacy shell write mode:
> db.foo.save({_id:1});db.getLastErrorObj()
|
{
|
"connectionId" : 1,
|
"updatedExisting" : true,
|
"n" : 1,
|
"syncMillis" : 0,
|
"writtenTo" : null,
|
"err" : null,
|
"ok" : 1
|
}
|
> db.foo.update({_id:1}, {b:2}); db.getLastErrorObj()
|
{
|
"connectionId" : 1,
|
"updatedExisting" : true,
|
"n" : 1,
|
"syncMillis" : 0,
|
"writtenTo" : null,
|
"err" : null,
|
"ok" : 1
|
}
|
Because this was fixed via the new update framework this behavior cannot be backported with the existing code.
|
|
This bug has nothing to do with auth. If you start the system up without auth, you get the following behvior.
> db.foo.drop()
|
true
|
> db.foo.insert({_id: 1})
|
> db.foo.update({_id: 1}, { $set: { a: 2 } })
|
> db.getLastErrorObj()
|
{ "n" : 0, "connectionId" : 1, "err" : null, "ok" : 1 }
|
> db.foo.update({_id: 1}, { $set: { a: 3 } })
|
> db.getLastErrorObj()
|
{
|
"updatedExisting" : true,
|
"n" : 1,
|
"connectionId" : 1,
|
"err" : null,
|
"ok" : 1
|
}
|
The actual bug being reported here is that in 2.2 and 2.4, updatedExisting is not set to true for a $set if it doesn't replace a pre-existing field in the updated object. That seems like a bug, but it has nothing to do with auth.
|
|
I could repro this in 2.2.6 and 2.4.8
2.2.6
start mongod with --auth (without users)
$ mongo --norc
|
MongoDB shell version: 2.2.6
|
connecting to: test
|
> db.addUser( "test", "test" );
|
{
|
"user" : "test",
|
"readOnly" : false,
|
"pwd" : "a6de521abefc2fed4f5876855a3484f5",
|
"_id" : ObjectId("52b96fcc9c643b18a35d5678")
|
}
|
> db.getSisterDB( "admin" ).addUser( "admin" , "admin" );
|
{
|
"user" : "admin",
|
"readOnly" : false,
|
"pwd" : "7c67ef13bbd4cae106d959320af3f704",
|
"_id" : ObjectId("52b96fd19c643b18a35d5679")
|
}
|
addUser succeeded, but cannot wait for replication since we no longer have auth
|
|
> db.logout()
|
{ "errmsg" : "need to login", "ok" : 0 }
|
> db.auth( "test", "test" );
|
1
|
> db.foo.insert( { _id : 1 } );
|
> db.foo.update( { _id : 1 } , { $set : { a : 2 } } );
|
> printjson( db.getLastErrorObj() );
|
{ "n" : 0, "connectionId" : 1, "err" : null, "ok" : 1 }
|
|
|
|
> db.logout()
|
{ "ok" : 1 }
|
> use admin
|
switched to db admin
|
> db.auth( "admin", "admin" );
|
1
|
> use test
|
switched to db test
|
> db.foo.update( { _id : 1 } , { $set : { a : 3 } } );
|
> printjson( db.getLastErrorObj() );
|
{
|
"updatedExisting" : true,
|
"n" : 1,
|
"connectionId" : 1,
|
"err" : null,
|
"ok" : 1
|
}
|
2.4.8
start mongod with --auth (without users)
$ mongo
|
MongoDB shell version: 2.4.8
|
connecting to: test
|
> db.addUser( "test", "test" );
|
{
|
"user" : "test",
|
"readOnly" : false,
|
"pwd" : "a6de521abefc2fed4f5876855a3484f5",
|
"_id" : ObjectId("52b96e9d9fd7bfa3aee3ef8f")
|
}
|
> db.getSisterDB( "admin" ).addUser( "admin" , "admin" );
|
{
|
"user" : "admin",
|
"readOnly" : false,
|
"pwd" : "7c67ef13bbd4cae106d959320af3f704",
|
"_id" : ObjectId("52b96eab9fd7bfa3aee3ef90")
|
}
|
> db.logout()
|
{ "ok" : 1 }
|
> db.auth( "test", "test" );
|
1
|
> db.foo.insert( { _id : 1 } );
|
> db.foo.update( { _id : 1 } , { $set : { a : 2 } } );
|
> printjson( db.getLastErrorObj() );
|
{ "n" : 0, "connectionId" : 1, "err" : null, "ok" : 1 }
|
|
|
|
> db.logout()
|
{ "ok" : 1 }
|
> use admin
|
switched to db admin
|
> db.auth( "admin", "admin" );
|
1
|
> use test
|
switched to db test
|
> db.foo.update( { _id : 1 } , { $set : { a : 3 } } );
|
> printjson( db.getLastErrorObj() );
|
{
|
"updatedExisting" : true,
|
"n" : 1,
|
"connectionId" : 1,
|
"err" : null,
|
"ok" : 1
|
}
|
|
|
This is critical for people doing optimistic locking and checking if documents get updated. This seriously breaks morphia and other frameworks implementing optimistic locking.
It breaks existing applications badly.
|
|
Missing the field updatedExisting and n is always 0.
|
Generated at Thu Feb 08 03:14:10 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.