[SERVER-7315] getLastError updatedExisting field should be set true when $set is used to add a new field. Created: 10/Oct/12  Updated: 09/Jul/16  Resolved: 08/Mar/14

Status: Closed
Project: Core Server
Component/s: Write Ops
Affects Version/s: 2.2.0, 2.4.8, 2.6.0-rc0
Fix Version/s: None

Type: Bug Priority: Critical - P2
Reporter: Scott Hernandez (Inactive) Assignee: Scott Hernandez (Inactive)
Resolution: Done Votes: 1
Labels: getlasterror, gle, n, nh-240, updated
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Depends
Related
Operating System: ALL
Participants:

 Description   

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
}



 Comments   
Comment by Scott Hernandez (Inactive) [ 08/Mar/14 ]

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.

Comment by Andy Schwerin [ 06/Mar/14 ]

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.

Comment by Gianfranco Palumbo [ 24/Dec/13 ]

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
}

Comment by Scott Hernandez (Inactive) [ 19/Nov/12 ]

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.

Comment by Scott Hernandez (Inactive) [ 10/Oct/12 ]

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.