diff --git a/client/dbclient.cpp b/client/dbclient.cpp index b84f3eb72db..9e3f64e77a4 100644 --- a/client/dbclient.cpp +++ b/client/dbclient.cpp @@ -560,13 +560,16 @@ namespace mongo { say( toSend ); } - void DBClientBase::update( const string & ns , Query query , BSONObj obj , bool upsert ) { + void DBClientBase::update( const string & ns , Query query , BSONObj obj , bool upsert , bool multi ) { BufBuilder b; b.append( (int)0 ); // reserverd b.append( ns ); - b.append( (int)upsert ); + int flags = 0; + if ( upsert ) flags |= Option_Upsert; + if ( multi ) flags |= Option_Multi; + b.append( flags ); query.obj.appendSelfToBufBuilder( b ); obj.appendSelfToBufBuilder( b ); diff --git a/client/dbclient.h b/client/dbclient.h index 7f0042e2611..655a66e916a 100644 --- a/client/dbclient.h +++ b/client/dbclient.h @@ -48,6 +48,11 @@ namespace mongo { Option_NoCursorTimeout = 1 << 4 }; + enum UpdateOptions { + Option_Upsert = 1 << 0, + Option_Multi = 1 << 1 + }; + class BSONObj; /** Represents a Mongo query expression. Typically one uses the QUERY(...) macro to construct a Query object. @@ -301,7 +306,7 @@ namespace mongo { virtual void remove( const string &ns , Query query, bool justOne = 0 ) = 0; - virtual void update( const string &ns , Query query , BSONObj obj , bool upsert = 0 ) = 0; + virtual void update( const string &ns , Query query , BSONObj obj , bool upsert = 0 , bool multi = 0 ) = 0; virtual ~DBClientInterface() { } }; @@ -625,7 +630,7 @@ namespace mongo { /** updates objects matching query */ - virtual void update( const string &ns , Query query , BSONObj obj , bool upsert = 0 ); + virtual void update( const string &ns , Query query , BSONObj obj , bool upsert = 0 , bool multi = 0 ); /** Create an index if it does not already exist. ensureIndex calls are remembered so it is safe/fast to call this function many @@ -842,8 +847,8 @@ namespace mongo { } /** update */ - virtual void update( const string &ns , Query query , BSONObj obj , bool upsert = 0 ) { - return checkMaster().update(ns, query, obj, upsert); + virtual void update( const string &ns , Query query , BSONObj obj , bool upsert = 0 , bool multi = 0 ) { + return checkMaster().update(ns, query, obj, upsert,multi); } string toString(); diff --git a/db/instance.cpp b/db/instance.cpp index df9e8243b58..1330823bd2a 100644 --- a/db/instance.cpp +++ b/db/instance.cpp @@ -349,8 +349,8 @@ namespace mongo { uassert("update object too large", toupdate.objsize() <= MaxBSONObjectSize); assert( toupdate.objsize() < m.data->dataLen() ); assert( query.objsize() + toupdate.objsize() < m.data->dataLen() ); - bool upsert = flags & 1; - bool multi = flags & 2; + bool upsert = flags & Option_Upsert; + bool multi = flags & Option_Multi; { string s = query.toString(); /* todo: we shouldn't do all this ss stuff when we don't need it, it will slow us down. */ diff --git a/db/update.cpp b/db/update.cpp index 92563582fa5..5864101750b 100644 --- a/db/update.cpp +++ b/db/update.cpp @@ -650,9 +650,7 @@ namespace mongo { }; - UpdateResult updateObjects(const char *ns, BSONObj updateobj, BSONObj pattern, bool upsert, bool multi, stringstream& ss, bool logop ) { - uassert("multi not coded yet", !multi); - + UpdateResult updateObjects(const char *ns, BSONObj updateobjOrig, BSONObj patternOrig, bool upsert, bool multi, stringstream& ss, bool logop ) { int profile = cc().database()->profile; uassert("cannot update reserved $ collection", strchr(ns, '$') == 0 ); @@ -661,15 +659,19 @@ namespace mongo { uassert("cannot update system collection", legalClientSystemNS( ns , true ) ); } - QueryPlanSet qps( ns, pattern, BSONObj() ); + QueryPlanSet qps( ns, patternOrig, BSONObj() ); UpdateOp original; shared_ptr< UpdateOp > u = qps.runOp( original ); massert( u->exceptionMessage(), u->complete() ); auto_ptr< Cursor > c = u->c(); - if ( c->ok() ) { + int numModded = 0; + while ( c->ok() ) { Record *r = c->_current(); BSONObj js(r); + BSONObj pattern = patternOrig; + BSONObj updateobj = updateobjOrig; + if ( logop ) { BSONObjBuilder idPattern; BSONElement id; @@ -681,6 +683,9 @@ namespace mongo { idPattern.append( id ); pattern = idPattern.obj(); } + else { + uassert( "multi-update requires all modified objects to have an _id" , ! multi ); + } } /* note: we only update one row and quit. if you do multiple later, @@ -697,6 +702,9 @@ namespace mongo { const char *firstField = updateobj.firstElement().fieldName(); if ( firstField[0] == '$' ) { + if ( multi ) + updateobj = updateobj.copy(); + ModSet mods; mods.getMods(updateobj); NamespaceDetailsTransient& ndt = NamespaceDetailsTransient::get(ns); @@ -719,10 +727,15 @@ namespace mongo { } logOp("u", ns, updateobj, &pattern ); } - return UpdateResult( 1 , 1 , 1 ); + numModded++; + if ( ! multi ) + break; + c->advance(); + continue; } uassert( "multi update only works with $ operators" , ! multi ); + BSONElementManipulator::lookForTimestamps( updateobj ); checkNoMods( updateobj ); theDataFileMgr.update(ns, r, c->currLoc(), updateobj.objdata(), updateobj.objsize(), ss); @@ -731,15 +744,19 @@ namespace mongo { return UpdateResult( 1 , 0 , 1 ); } + if ( numModded ) + return UpdateResult( 1 , 1 , numModded ); + + if ( profile ) ss << " nscanned:" << u->nscanned(); if ( upsert ) { - if ( updateobj.firstElement().fieldName()[0] == '$' ) { + if ( updateobjOrig.firstElement().fieldName()[0] == '$' ) { /* upsert of an $inc. build a default */ ModSet mods; - mods.getMods(updateobj); - BSONObj newObj = pattern.copy(); + mods.getMods(updateobjOrig); + BSONObj newObj = patternOrig.copy(); if ( mods.applyModsInPlace( newObj ) ) { // } else { @@ -755,12 +772,12 @@ namespace mongo { return UpdateResult( 0 , 1 , 1 ); } uassert( "multi update only works with $ operators" , ! multi ); - checkNoMods( updateobj ); + checkNoMods( updateobjOrig ); if ( profile ) ss << " upsert "; - theDataFileMgr.insert(ns, updateobj); + theDataFileMgr.insert(ns, updateobjOrig); if ( logop ) - logOp( "i", ns, updateobj ); + logOp( "i", ns, updateobjOrig ); return UpdateResult( 0 , 0 , 1 ); } return UpdateResult( 0 , 0 , 0 ); diff --git a/dbtests/repltests.cpp b/dbtests/repltests.cpp index ae801f206cd..86f3820d9d0 100644 --- a/dbtests/repltests.cpp +++ b/dbtests/repltests.cpp @@ -520,7 +520,49 @@ namespace ReplTests { protected: BSONObj o_, q_, u_, ou_; }; + + class MultiInc : public Base { + public: + + string s() const { + stringstream ss; + auto_ptr cc = client()->query( ns() , Query().sort( BSON( "_id" << 1 ) ) ); + bool first = true; + while ( cc->more() ){ + if ( first ) first = false; + else ss << ","; + + BSONObj o = cc->next(); + ss << o["x"].numberInt(); + } + return ss.str(); + } + + void doIt() const { + client()->insert( ns(), BSON( "_id" << 1 << "x" << 1 ) ); + client()->insert( ns(), BSON( "_id" << 2 << "x" << 5 ) ); + + ASSERT_EQUALS( "1,5" , s() ); + + client()->update( ns() , BSON( "_id" << 1 ) , BSON( "$inc" << BSON( "x" << 1 ) ) ); + ASSERT_EQUALS( "2,5" , s() ); + + client()->update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) ); + ASSERT_EQUALS( "3,5" , s() ); + + client()->update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) , false , true ); + check(); + } + void check() const { + ASSERT_EQUALS( "4,6" , s() ); + } + + void reset() const { + deleteAll( ns() ); + } + }; + class UpdateWithoutPreexistingId : public Base { public: UpdateWithoutPreexistingId() : @@ -959,6 +1001,7 @@ namespace ReplTests { add< Idempotence::UpsertInsertIdMod >(); add< Idempotence::UpsertInsertSet >(); add< Idempotence::UpsertInsertInc >(); + add< Idempotence::MultiInc >(); // Don't worry about this until someone wants this functionality. // add< Idempotence::UpdateWithoutPreexistingId >(); add< Idempotence::Remove >(); diff --git a/dbtests/updatetests.cpp b/dbtests/updatetests.cpp index 24326f1b7ee..9d309714f7f 100644 --- a/dbtests/updatetests.cpp +++ b/dbtests/updatetests.cpp @@ -238,6 +238,41 @@ namespace UpdateTests { } }; + class MultiInc : public SetBase { + public: + + string s(){ + stringstream ss; + auto_ptr cc = client().query( ns() , Query().sort( BSON( "_id" << 1 ) ) ); + bool first = true; + while ( cc->more() ){ + if ( first ) first = false; + else ss << ","; + + BSONObj o = cc->next(); + ss << o["x"].numberInt(); + } + return ss.str(); + } + + void run(){ + client().insert( ns(), BSON( "_id" << 1 << "x" << 1 ) ); + client().insert( ns(), BSON( "_id" << 2 << "x" << 5 ) ); + + ASSERT_EQUALS( "1,5" , s() ); + + client().update( ns() , BSON( "_id" << 1 ) , BSON( "$inc" << BSON( "x" << 1 ) ) ); + ASSERT_EQUALS( "2,5" , s() ); + + client().update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) ); + ASSERT_EQUALS( "3,5" , s() ); + + client().update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) , false , true ); + ASSERT_EQUALS( "4,6" , s() ); + + } + }; + class UnorderedNewSet : public SetBase { public: void run() { @@ -508,6 +543,7 @@ namespace UpdateTests { add< SetMissingDotted >(); add< SetAdjacentDotted >(); add< IncMissing >(); + add< MultiInc >(); add< UnorderedNewSet >(); add< UnorderedNewSetAdjacent >(); add< ArrayEmbeddedSet >();