diff options
author | Andrew Morrow <acm@10gen.com> | 2013-08-13 12:11:06 -0400 |
---|---|---|
committer | Andrew Morrow <acm@10gen.com> | 2013-08-13 18:02:42 -0400 |
commit | dda99507af1eef8368d5d26a226d4d94f50d5a30 (patch) | |
tree | 5f3fe245270ed5619f964cda4ce23904d1ffe408 | |
parent | 072e12a5e5b853435e47046d979b307fa2676951 (diff) | |
download | mongo-dda99507af1eef8368d5d26a226d4d94f50d5a30.tar.gz |
SERVER-10497 Remove legacy update framework
-rwxr-xr-x | buildscripts/smoke.py | 2 | ||||
-rw-r--r-- | src/mongo/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/dbhelpers.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/instance.cpp | 165 | ||||
-rw-r--r-- | src/mongo/db/ops/update.cpp | 561 | ||||
-rw-r--r-- | src/mongo/db/ops/update.h | 48 | ||||
-rw-r--r-- | src/mongo/db/ops/update_internal.cpp | 1496 | ||||
-rw-r--r-- | src/mongo/db/ops/update_internal.h | 781 | ||||
-rw-r--r-- | src/mongo/db/repl/rs_rollback.cpp | 35 | ||||
-rw-r--r-- | src/mongo/dbtests/framework.cpp | 5 | ||||
-rw-r--r-- | src/mongo/dbtests/repltests.cpp | 12 | ||||
-rw-r--r-- | src/mongo/dbtests/updatetests.cpp | 834 |
12 files changed, 168 insertions, 3804 deletions
diff --git a/buildscripts/smoke.py b/buildscripts/smoke.py index 13a7d63af7c..57427f6168a 100755 --- a/buildscripts/smoke.py +++ b/buildscripts/smoke.py @@ -449,8 +449,6 @@ def runTest(test): # Blech. if os.path.basename(path) in ["test", "test.exe", "perftest", "perftest.exe"]: argv = [path] - if "newUpdateFrameworkEnabled" in set_parameters: - argv += ["--testNewUpdateFramework"] # more blech elif os.path.basename(path) in ['mongos', 'mongos.exe']: argv = [path, "--test"] diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 26a4b7c2c63..7d791323f12 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -527,7 +527,6 @@ serverOnlyFiles = [ "db/curop.cpp", "db/ops/delete.cpp", "db/ops/query.cpp", "db/ops/update.cpp", - "db/ops/update_internal.cpp", "db/parsed_query.cpp", "db/query_runner.cpp", "db/dbcommands.cpp", diff --git a/src/mongo/db/dbhelpers.cpp b/src/mongo/db/dbhelpers.cpp index b2aa538fc6a..3448e48e0d8 100644 --- a/src/mongo/db/dbhelpers.cpp +++ b/src/mongo/db/dbhelpers.cpp @@ -214,30 +214,14 @@ namespace mongo { OpDebug debug; Client::Context context(ns); - if (isNewUpdateFrameworkEnabled()) { - - _updateObjectsNEW(/*god=*/true, - ns, - obj, - /*pattern=*/BSONObj(), - /*upsert=*/true, - /*multi=*/false, - logTheOp, - debug ); - - } - else { - - _updateObjects(/*god=*/true, - ns, - obj, - /*pattern=*/BSONObj(), - /*upsert=*/true, - /*multi=*/false, - logTheOp, - debug ); - - } + _updateObjects(/*god=*/true, + ns, + obj, + /*pattern=*/BSONObj(), + /*upsert=*/true, + /*multi=*/false, + logTheOp, + debug ); context.getClient()->curop()->done(); } diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp index 8b78aa424ad..07c67ab49e7 100644 --- a/src/mongo/db/instance.cpp +++ b/src/mongo/db/instance.cpp @@ -601,103 +601,62 @@ namespace mongo { op.debug().query = query; op.setQuery(query); - if ( isNewUpdateFrameworkEnabled() ) { - - // New style. This only works with the new update framework, and moves mod parsing - // out of the write lock. - // - // This code should look quite familiar, since it is basically the prelude code in - // _updateObjectsNEW. We could factor it into a common function, but that would - // require that we heap allocate the driver, which doesn't seem worth it right now, - // especially considering that we will probably rewrite much of this code in the - // near term. - - UpdateDriver::Options options; - options.multi = multi; - options.upsert = upsert; - - // TODO: This is wasteful. We really shouldn't need to generate the oplog entry - // just to throw it away if we are not generating an oplog. - options.logOp = true; - - // Select the right modifier options. We aren't in a replication context here, so - // the only question is whether this update is against the 'config' database, in - // which case we want to disable checks, since config db docs can have field names - // containing a dot ("."). - options.modOptions = ( NamespaceString( ns ).isConfigDB() ) ? - ModifierInterface::Options::unchecked() : - ModifierInterface::Options::normal(); - - UpdateDriver driver( options ); - - Status status = driver.parse( toupdate ); - if ( !status.isOK() ) { - uasserted( 17009, status.reason() ); - } - - PageFaultRetryableSection s; - while ( 1 ) { - try { - Lock::DBWrite lk(ns); + // This code should look quite familiar, since it is basically the prelude code in the + // other overload of _updateObjectsNEW. We could factor it into a common function, but + // that would require that we heap allocate the driver, which doesn't seem worth it + // right now, especially considering that we will probably rewrite much of this code in + // the near term. - // void ReplSetImpl::relinquish() uses big write lock so this is thus - // synchronized given our lock above. - uassert( 17010 , "not master", isMasterNs( ns ) ); - - // if this ever moves to outside of lock, need to adjust check - // Client::Context::_finishInit - if ( ! broadcast && handlePossibleShardedMessage( m , 0 ) ) - return; + UpdateDriver::Options options; + options.multi = multi; + options.upsert = upsert; - Client::Context ctx( ns ); + // TODO: This is wasteful. We really shouldn't need to generate the oplog entry + // just to throw it away if we are not generating an oplog. + options.logOp = true; - UpdateResult res = updateObjects( - &driver, - ns, toupdate, query, - upsert, multi, true, op.debug() ); + // Select the right modifier options. We aren't in a replication context here, so + // the only question is whether this update is against the 'config' database, in + // which case we want to disable checks, since config db docs can have field names + // containing a dot ("."). + options.modOptions = ( NamespaceString( ns ).isConfigDB() ) ? + ModifierInterface::Options::unchecked() : + ModifierInterface::Options::normal(); - // for getlasterror - lastError.getSafe()->recordUpdate( res.existing , res.num , res.upserted ); - break; - } - catch ( PageFaultException& e ) { - e.touch(); - } - } + UpdateDriver driver( options ); + status = driver.parse( toupdate ); + if ( !status.isOK() ) { + uasserted( 17009, status.reason() ); } - else { - // This is the 'old style'. We may or may not call the new update code, but we are - // going to do so under the write lock in all cases. - - PageFaultRetryableSection s; - while ( 1 ) { - try { - Lock::DBWrite lk(ns); + PageFaultRetryableSection s; + while ( 1 ) { + try { + Lock::DBWrite lk(ns); - // void ReplSetImpl::relinquish() uses big write lock so this is thus - // synchronized given our lock above. - uassert( 10054 , "not master", isMasterNs( ns ) ); + // void ReplSetImpl::relinquish() uses big write lock so this is thus + // synchronized given our lock above. + uassert( 17010 , "not master", isMasterNs( ns ) ); - // if this ever moves to outside of lock, need to adjust check - // Client::Context::_finishInit - if ( ! broadcast && handlePossibleShardedMessage( m , 0 ) ) - return; + // if this ever moves to outside of lock, need to adjust check + // Client::Context::_finishInit + if ( ! broadcast && handlePossibleShardedMessage( m , 0 ) ) + return; - Client::Context ctx( ns ); + Client::Context ctx( ns ); - UpdateResult res = updateObjects( - ns, toupdate, query, - upsert, multi, true, op.debug() ); + UpdateResult res = updateObjects( + &driver, + ns, toupdate, query, + upsert, multi, true, op.debug() ); - // for getlasterror - lastError.getSafe()->recordUpdate( res.existing , res.num , res.upserted ); - break; - } - catch ( PageFaultException& e ) { - e.touch(); - } + // for getlasterror + lastError.getSafe()->recordUpdate( res.existing , res.num , res.upserted ); + break; + } + catch ( PageFaultException& e ) { + e.touch(); } } } @@ -871,36 +830,18 @@ namespace mongo { return ok; } - void checkAndInsert(const char *ns, /*modifies*/BSONObj& js) { + void checkAndInsert(const char *ns, /*modifies*/BSONObj& js) { uassert( 10059 , "object to insert too large", js.objsize() <= BSONObjMaxUserSize); - // Do not allow objects to be stored which violate okForStorageAsRoot - if ( isNewUpdateFrameworkEnabled() ) { - NamespaceString nsString(ns); - bool ok = nsString.isConfigDB() || nsString.isSystem() || js.okForStorageAsRoot(); - if (!ok) { - LOG(1) << "ns: " << ns << ", not okForStorageAsRoot: " << js; - } - uassert(17013, - "Cannot insert object with _id field of array/regex or " - "with any field name prefixed with $ or containing a dot. ", - ok); - } - else { - BSONObjIterator i( js ); - while ( i.more() ) { - BSONElement e = i.next(); - - // check no $ modifiers. note we only check top level. - // (scanning deep would be quite expensive) - uassert( 13511, "document to insert can't have $ fields", e.fieldName()[0] != '$' ); - - // check no regexp for _id (SERVER-9502) - if (str::equals(e.fieldName(), "_id")) { - uassert(16824, "can't use a regex for _id", e.type() != RegEx); - } - } + NamespaceString nsString(ns); + bool ok = nsString.isConfigDB() || nsString.isSystem() || js.okForStorageAsRoot(); + if (!ok) { + LOG(1) << "ns: " << ns << ", not okForStorageAsRoot: " << js; } + uassert(17013, + "Cannot insert object with _id field of array/regex or " + "with any field name prefixed with $ or containing a dot. ", + ok); theDataFileMgr.insertWithObjMod(ns, // May be modified in the call to add an _id field. diff --git a/src/mongo/db/ops/update.cpp b/src/mongo/db/ops/update.cpp index fe0d12b89cc..77f0b863019 100644 --- a/src/mongo/db/ops/update.cpp +++ b/src/mongo/db/ops/update.cpp @@ -29,7 +29,6 @@ #include "mongo/db/index_set.h" #include "mongo/db/namespace_details.h" #include "mongo/db/ops/update_driver.h" -#include "mongo/db/ops/update_internal.h" #include "mongo/db/pagefault.h" #include "mongo/db/pdfile.h" #include "mongo/db/query_optimizer.h" @@ -45,16 +44,6 @@ namespace mongo { - MONGO_EXPORT_SERVER_PARAMETER( newUpdateFrameworkEnabled, bool, false ); - - bool isNewUpdateFrameworkEnabled() { - return newUpdateFrameworkEnabled; - } - - bool toggleNewUpdateFrameworkEnabled() { - return newUpdateFrameworkEnabled = !newUpdateFrameworkEnabled; - } - void checkNoMods( BSONObj o ) { BSONObjIterator i( o ); while( i.moreWithEOO() ) { @@ -89,89 +78,16 @@ namespace mongo { } } - /* note: this is only (as-is) called for - - - not multi - - not mods is indexed - - not upsert - */ - static UpdateResult _updateById(bool isOperatorUpdate, - int idIdxNo, - ModSet* mods, - NamespaceDetails* d, - NamespaceDetailsTransient *nsdt, - bool su, - const char* ns, - const BSONObj& updateobj, - BSONObj patternOrig, - bool logop, - OpDebug& debug, - bool fromMigrate = false) { - - DiskLoc loc; - { - IndexDetails& i = d->idx(idIdxNo); - BSONObj key = i.getKeyFromQuery( patternOrig ); - loc = QueryRunner::fastFindSingle(i, key); - if( loc.isNull() ) { - // no upsert support in _updateById yet, so we are done. - return UpdateResult( 0 , 0 , 0 , BSONObj() ); - } - } - Record* r = loc.rec(); - - if ( cc().allowedToThrowPageFaultException() && ! r->likelyInPhysicalMemory() ) { - throw PageFaultException( r ); - } - - /* look for $inc etc. note as listed here, all fields to inc must be this type, you can't set some - regular ones at the moment. */ - BSONObj newObj; - if ( isOperatorUpdate ) { - const BSONObj& onDisk = loc.obj(); - auto_ptr<ModSetState> mss = mods->prepare( onDisk, false /* not an insertion */ ); - - if( mss->canApplyInPlace() ) { - mss->applyModsInPlace(true); - debug.fastmod = true; - DEBUGUPDATE( "\t\t\t updateById doing in place update" ); - - newObj = onDisk; - } - else { - newObj = mss->createNewFromMods(); - checkTooLarge(newObj); - verify(nsdt); - theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , newObj.objdata(), newObj.objsize(), debug); - } - - if ( logop ) { - DEV verify( mods->size() ); - BSONObj pattern = patternOrig; - BSONObj logObj = mss->getOpLogRewrite(); - DEBUGUPDATE( "\t rewrite update: " << logObj ); - - // It is possible that the entire mod set was a no-op over this document. We - // would have an empty log record in that case. If we call logOp, with an empty - // record, that would be replicated as "clear this record", which is not what - // we want. Therefore, to get a no-op in the replica, we simply don't log. - if ( logObj.nFields() ) { - logOp("u", ns, logObj, &pattern, 0, fromMigrate, &newObj ); - } - } - return UpdateResult( 1 , 1 , 1 , BSONObj() ); - - } // end $operator update - - // regular update - BSONElementManipulator::lookForTimestamps( updateobj ); - checkNoMods( updateobj ); - verify(nsdt); - theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug ); - if ( logop ) { - logOp("u", ns, updateobj, &patternOrig, 0, fromMigrate, &updateobj ); + void validateUpdate( const char* ns , const BSONObj& updateobj, const BSONObj& patternOrig ) { + uassert( 10155 , "cannot update reserved $ collection", strchr(ns, '$') == 0 ); + if ( strstr(ns, ".system.") ) { + /* dm: it's very important that system.indexes is never updated as IndexDetails + has pointers into it */ + uassert( 10156, + str::stream() << "cannot update system collection: " + << ns << " q: " << patternOrig << " u: " << updateobj, + legalClientSystemNS( ns , true ) ); } - return UpdateResult( 1 , 0 , 1 , BSONObj() ); } UpdateResult _updateObjects( bool su, @@ -187,321 +103,6 @@ namespace mongo { const QueryPlanSelectionPolicy& planPolicy, bool forReplication ) { - DEBUGUPDATE( "update: " << ns - << " update: " << updateobj - << " query: " << patternOrig - << " upsert: " << upsert << " multi: " << multi ); - - Client& client = cc(); - - debug.updateobj = updateobj; - - // The idea with these here it to make them loop invariant for - // multi updates, and thus be a bit faster for that case. The - // pointers may be left invalid on a failed or terminal yield - // recovery. - NamespaceDetails* d = nsdetails(ns); // can be null if an upsert... - NamespaceDetailsTransient* nsdt = &NamespaceDetailsTransient::get(ns); - - auto_ptr<ModSet> mods; - bool isOperatorUpdate = updateobj.firstElementFieldName()[0] == '$'; - int modsIsIndexed = false; // really the # of indexes - if ( isOperatorUpdate ) { - mods.reset( new ModSet(updateobj, nsdt->indexKeys(), forReplication) ); - modsIsIndexed = mods->maxNumIndexUpdated(); - } - - if( planPolicy.permitOptimalIdPlan() && !multi && isSimpleIdQuery(patternOrig) && d && - !modsIsIndexed ) { - int idxNo = d->findIdIndex(); - if( idxNo >= 0 ) { - debug.idhack = true; - - UpdateResult result = _updateById( isOperatorUpdate, - idxNo, - mods.get(), - d, - nsdt, - su, - ns, - updateobj, - patternOrig, - logop, - debug, - fromMigrate); - if ( result.existing || ! upsert ) { - return result; - } - else if ( upsert && ! isOperatorUpdate ) { - // this handles repl inserts - checkNoMods( updateobj ); - debug.upsert = true; - BSONObj no = updateobj; - theDataFileMgr.insertWithObjMod(ns, no, false, su); - if ( logop ) - logOp( "i", ns, no, 0, 0, fromMigrate, &no ); - - return UpdateResult( 0 , 0 , 1 , no ); - } - } - } - - int numModded = 0; - debug.nscanned = 0; - shared_ptr<Cursor> c = getOptimizedCursor( ns, patternOrig, BSONObj(), planPolicy ); - d = nsdetails(ns); - nsdt = &NamespaceDetailsTransient::get(ns); - bool autoDedup = c->autoDedup(); - - if( c->ok() ) { - set<DiskLoc> seenObjects; - MatchDetails details; - auto_ptr<ClientCursor> cc; - do { - - if ( cc.get() == 0 && - client.allowedToThrowPageFaultException() && - ! c->currLoc().isNull() && - ! c->currLoc().rec()->likelyInPhysicalMemory() ) { - throw PageFaultException( c->currLoc().rec() ); - } - - bool atomic = c->matcher() && c->matcher()->docMatcher().atomic(); - - if ( ! atomic && debug.nscanned > 0 ) { - // we need to use a ClientCursor to yield - if ( cc.get() == 0 ) { - shared_ptr< Cursor > cPtr = c; - cc.reset( new ClientCursor( QueryOption_NoCursorTimeout , cPtr , ns ) ); - } - - bool didYield; - if ( ! cc->yieldSometimes( ClientCursor::WillNeed, &didYield ) ) { - cc.release(); - break; - } - if ( !c->ok() ) { - break; - } - - if ( didYield ) { - d = nsdetails(ns); - if ( ! d ) - break; - nsdt = &NamespaceDetailsTransient::get(ns); - if ( mods.get() ) { - mods->setIndexedStatus( nsdt->indexKeys() ); - modsIsIndexed = mods->maxNumIndexUpdated(); - } - - } - - } // end yielding block - - debug.nscanned++; - - if ( mods.get() && mods->hasDynamicArray() ) { - details.requestElemMatchKey(); - } - - if ( !c->currentMatches( &details ) ) { - c->advance(); - continue; - } - - Record* r = c->_current(); - DiskLoc loc = c->currLoc(); - - if ( c->getsetdup( loc ) && autoDedup ) { - c->advance(); - continue; - } - - BSONObj pattern = patternOrig; - - if ( logop ) { - BSONObj js = BSONObj::make(r); - BSONObj idQuery = makeOplogEntryQuery(js, multi); - pattern = idQuery; - } - - /* look for $inc etc. note as listed here, all fields to inc must be this type, you can't set some - regular ones at the moment. */ - if ( isOperatorUpdate ) { - - if ( multi ) { - // go to next record in case this one moves - c->advance(); - - // Update operations are deduped for cursors that implement their own - // deduplication. In particular, some geo cursors are excluded. - if ( autoDedup ) { - - if ( seenObjects.count( loc ) ) { - continue; - } - - // SERVER-5198 Advance past the document to be modified, provided - // deduplication is enabled, but see SERVER-5725. - while( c->ok() && loc == c->currLoc() ) { - c->advance(); - } - } - } - - const BSONObj& onDisk = loc.obj(); - - ModSet* useMods = mods.get(); - - auto_ptr<ModSet> mymodset; - if ( details.hasElemMatchKey() && mods->hasDynamicArray() ) { - useMods = mods->fixDynamicArray( details.elemMatchKey() ); - mymodset.reset( useMods ); - } - - auto_ptr<ModSetState> mss = useMods->prepare( onDisk, - false /* not an insertion */ ); - - bool willAdvanceCursor = multi && c->ok() && ( modsIsIndexed || ! mss->canApplyInPlace() ); - - if ( willAdvanceCursor ) { - if ( cc.get() ) { - cc->setDoingDeletes( true ); - } - c->prepareToTouchEarlierIterate(); - } - - // If we've made it this far, "ns" must contain a valid collection name, and so - // is of the form "db.collection". Therefore, the following expression must - // always be valid. "system.users" updates must never be done in place, in - // order to ensure that they are validated inside DataFileMgr::updateRecord(.). - bool isSystemUsersMod = nsToCollectionSubstring(ns) == "system.users"; - - BSONObj newObj; - if ( !mss->isUpdateIndexed() && mss->canApplyInPlace() && !isSystemUsersMod ) { - mss->applyModsInPlace( true );// const_cast<BSONObj&>(onDisk) ); - - DEBUGUPDATE( "\t\t\t doing in place update" ); - if ( !multi ) - debug.fastmod = true; - - if ( modsIsIndexed ) { - seenObjects.insert( loc ); - } - newObj = loc.obj(); - d->paddingFits(); - } - else { - newObj = mss->createNewFromMods(); - checkTooLarge(newObj); - DiskLoc newLoc = theDataFileMgr.updateRecord(ns, - d, - nsdt, - r, - loc, - newObj.objdata(), - newObj.objsize(), - debug); - - if ( newLoc != loc || modsIsIndexed ){ - // log() << "Moved obj " << newLoc.obj()["_id"] << " from " << loc << " to " << newLoc << endl; - // object moved, need to make sure we don' get again - seenObjects.insert( newLoc ); - } - - } - - if ( logop ) { - DEV verify( mods->size() ); - BSONObj logObj = mss->getOpLogRewrite(); - DEBUGUPDATE( "\t rewrite update: " << logObj ); - - // It is possible that the entire mod set was a no-op over this - // document. We would have an empty log record in that case. If we - // call logOp, with an empty record, that would be replicated as "clear - // this record", which is not what we want. Therefore, to get a no-op - // in the replica, we simply don't log. - if ( logObj.nFields() ) { - logOp("u", ns, logObj , &pattern, 0, fromMigrate, &newObj ); - } - } - numModded++; - if ( ! multi ) - return UpdateResult( 1 , 1 , numModded , BSONObj() ); - if ( willAdvanceCursor ) - c->recoverFromTouchingEarlierIterate(); - - getDur().commitIfNeeded(); - - continue; - } - - uassert( 10158 , "multi update only works with $ operators" , ! multi ); - - BSONElementManipulator::lookForTimestamps( updateobj ); - checkNoMods( updateobj ); - theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug, su); - if ( logop ) { - DEV wassert( !su ); // super used doesn't get logged, this would be bad. - logOp("u", ns, updateobj, &pattern, 0, fromMigrate, &updateobj ); - } - return UpdateResult( 1 , 0 , 1 , BSONObj() ); - } while ( c->ok() ); - } // endif - - if ( numModded ) - return UpdateResult( 1 , 1 , numModded , BSONObj() ); - - if ( upsert ) { - if ( updateobj.firstElementFieldName()[0] == '$' ) { - // upsert of an $operation. build a default object - BSONObj newObj = mods->createNewFromQuery( patternOrig ); - checkNoMods( newObj ); - debug.fastmodinsert = true; - theDataFileMgr.insertWithObjMod(ns, newObj, false, su); - if ( logop ) - logOp( "i", ns, newObj, 0, 0, fromMigrate, &newObj ); - - return UpdateResult( 0 , 1 , 1 , newObj ); - } - uassert( 10159 , "multi update only works with $ operators" , ! multi ); - checkNoMods( updateobj ); - debug.upsert = true; - BSONObj no = updateobj; - theDataFileMgr.insertWithObjMod(ns, no, false, su); - if ( logop ) - logOp( "i", ns, no, 0, 0, fromMigrate, &no ); - return UpdateResult( 0 , 0 , 1 , no ); - } - - return UpdateResult( 0 , isOperatorUpdate , 0 , BSONObj() ); - } - - void validateUpdate( const char* ns , const BSONObj& updateobj, const BSONObj& patternOrig ) { - uassert( 10155 , "cannot update reserved $ collection", strchr(ns, '$') == 0 ); - if ( strstr(ns, ".system.") ) { - /* dm: it's very important that system.indexes is never updated as IndexDetails - has pointers into it */ - uassert( 10156, - str::stream() << "cannot update system collection: " - << ns << " q: " << patternOrig << " u: " << updateobj, - legalClientSystemNS( ns , true ) ); - } - } - - UpdateResult _updateObjectsNEW( bool su, - const char* ns, - const BSONObj& updateobj, - const BSONObj& patternOrig, - bool upsert, - bool multi, - bool logop , - OpDebug& debug, - RemoveSaver* rs, - bool fromMigrate, - const QueryPlanSelectionPolicy& planPolicy, - bool forReplication ) { - // TODO: Put this logic someplace central and check based on constants (maybe using the // list of actually excluded config collections, and not global for the config db). NamespaceString nsStr( ns ); @@ -524,24 +125,24 @@ namespace mongo { uasserted( 16840, status.reason() ); } - return _updateObjectsNEW( &driver, su, ns, updateobj, patternOrig, - upsert, multi, logop, debug, rs, fromMigrate, - planPolicy, forReplication); + return _updateObjects( &driver, su, ns, updateobj, patternOrig, + upsert, multi, logop, debug, rs, fromMigrate, + planPolicy, forReplication); } - UpdateResult _updateObjectsNEW( UpdateDriver* driver, - bool su, - const char* ns, - const BSONObj& updateobj, - const BSONObj& patternOrig, - bool upsert, - bool multi, - bool logop , - OpDebug& debug, - RemoveSaver* rs, - bool fromMigrate, - const QueryPlanSelectionPolicy& planPolicy, - bool forReplication ) { + UpdateResult _updateObjects( UpdateDriver* driver, + bool su, + const char* ns, + const BSONObj& updateobj, + const BSONObj& patternOrig, + bool upsert, + bool multi, + bool logop , + OpDebug& debug, + RemoveSaver* rs, + bool fromMigrate, + const QueryPlanSelectionPolicy& planPolicy, + bool forReplication ) { NamespaceDetails* d = nsdetails( ns ); NamespaceDetailsTransient* nsdt = &NamespaceDetailsTransient::get( ns ); @@ -895,22 +496,11 @@ namespace mongo { validateUpdate( ns , updateobj , patternOrig ); - if ( isNewUpdateFrameworkEnabled() ) { - - UpdateResult ur = _updateObjectsNEW(false, ns, updateobj, patternOrig, - upsert, multi, logop, - debug, NULL, fromMigrate, planPolicy ); - debug.nupdated = ur.num; - return ur; - } - else { - - UpdateResult ur = _updateObjects(false, ns, updateobj, patternOrig, - upsert, multi, logop, - debug, NULL, fromMigrate, planPolicy ); - debug.nupdated = ur.num; - return ur; - } + UpdateResult ur = _updateObjects(false, ns, updateobj, patternOrig, + upsert, multi, logop, + debug, NULL, fromMigrate, planPolicy ); + debug.nupdated = ur.num; + return ur; } UpdateResult updateObjects( UpdateDriver* driver, @@ -926,11 +516,9 @@ namespace mongo { validateUpdate( ns , updateobj , patternOrig ); - verify( isNewUpdateFrameworkEnabled() ); - - UpdateResult ur = _updateObjectsNEW(driver, false, ns, updateobj, patternOrig, - upsert, multi, logop, - debug, NULL, fromMigrate, planPolicy ); + UpdateResult ur = _updateObjects(driver, false, ns, updateobj, patternOrig, + upsert, multi, logop, + debug, NULL, fromMigrate, planPolicy ); debug.nupdated = ur.num; return ur; } @@ -947,67 +535,40 @@ namespace mongo { validateUpdate( ns , updateobj , patternOrig ); - if ( isNewUpdateFrameworkEnabled() ) { - - UpdateResult ur = _updateObjectsNEW(false, - ns, - updateobj, - patternOrig, - upsert, - multi, - logop, - debug, - NULL /* no remove saver */, - fromMigrate, - planPolicy, - true /* for replication */ ); - debug.nupdated = ur.num; - return ur; - - } - else { - - UpdateResult ur = _updateObjects(false, - ns, - updateobj, - patternOrig, - upsert, - multi, - logop, - debug, - NULL /* no remove saver */, - fromMigrate, - planPolicy, - true /* for replication */ ); - debug.nupdated = ur.num; - return ur; + UpdateResult ur = _updateObjects(false, + ns, + updateobj, + patternOrig, + upsert, + multi, + logop, + debug, + NULL /* no remove saver */, + fromMigrate, + planPolicy, + true /* for replication */ ); + debug.nupdated = ur.num; + return ur; - } } BSONObj applyUpdateOperators( const BSONObj& from, const BSONObj& operators ) { - if ( isNewUpdateFrameworkEnabled() ) { - UpdateDriver::Options opts; - opts.multi = false; - opts.upsert = false; - UpdateDriver driver( opts ); - Status status = driver.parse( operators ); - if ( !status.isOK() ) { - uasserted( 16838, status.reason() ); - } - - mutablebson::Document doc( from, mutablebson::Document::kInPlaceDisabled ); - status = driver.update( StringData(), &doc, NULL /* not oplogging */ ); - if ( !status.isOK() ) { - uasserted( 16839, status.reason() ); - } - - return doc.getObject(); + UpdateDriver::Options opts; + opts.multi = false; + opts.upsert = false; + UpdateDriver driver( opts ); + Status status = driver.parse( operators ); + if ( !status.isOK() ) { + uasserted( 16838, status.reason() ); } - else { - ModSet mods( operators ); - return mods.prepare( from, false /* not an insertion */ )->createNewFromMods(); + + mutablebson::Document doc( from, mutablebson::Document::kInPlaceDisabled ); + status = driver.update( StringData(), &doc, NULL /* not oplogging */ ); + if ( !status.isOK() ) { + uasserted( 16839, status.reason() ); } + + return doc.getObject(); } } // namespace mongo diff --git a/src/mongo/db/ops/update.h b/src/mongo/db/ops/update.h index 049dcc43cfa..f4088c90bc4 100644 --- a/src/mongo/db/ops/update.h +++ b/src/mongo/db/ops/update.h @@ -46,12 +46,6 @@ namespace mongo { class RemoveSaver; - /** Returns true if updates are supposed to be handle by the new update framework */ - bool isNewUpdateFrameworkEnabled(); - - /** switches state from enabled/disabled; returns new state */ - bool toggleNewUpdateFrameworkEnabled(); - /* returns true if an existing object was updated, false if no existing object was found. multi - update multiple objects - mostly useful with things like $set su - allow access to system namespaces (super user) @@ -111,34 +105,20 @@ namespace mongo { = QueryPlanSelectionPolicy::any(), bool forReplication = false); - UpdateResult _updateObjectsNEW(bool su, - const char* ns, - const BSONObj& updateobj, - const BSONObj& pattern, - bool upsert, - bool multi, - bool logop, - OpDebug& debug, - RemoveSaver* rs = 0, - bool fromMigrate = false, - const QueryPlanSelectionPolicy& planPolicy - = QueryPlanSelectionPolicy::any(), - bool forReplication = false); - - UpdateResult _updateObjectsNEW(UpdateDriver* driver, - bool su, - const char* ns, - const BSONObj& updateobj, - const BSONObj& pattern, - bool upsert, - bool multi, - bool logop, - OpDebug& debug, - RemoveSaver* rs = 0, - bool fromMigrate = false, - const QueryPlanSelectionPolicy& planPolicy - = QueryPlanSelectionPolicy::any(), - bool forReplication = false); + UpdateResult _updateObjects(UpdateDriver* driver, + bool su, + const char* ns, + const BSONObj& updateobj, + const BSONObj& pattern, + bool upsert, + bool multi, + bool logop, + OpDebug& debug, + RemoveSaver* rs = 0, + bool fromMigrate = false, + const QueryPlanSelectionPolicy& planPolicy + = QueryPlanSelectionPolicy::any(), + bool forReplication = false); /** diff --git a/src/mongo/db/ops/update_internal.cpp b/src/mongo/db/ops/update_internal.cpp deleted file mode 100644 index bb5856921c4..00000000000 --- a/src/mongo/db/ops/update_internal.cpp +++ /dev/null @@ -1,1496 +0,0 @@ -//@file update_internal.cpp - -/** - * Copyright (C) 2012 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "mongo/pch.h" - -#include "mongo/db/ops/update_internal.h" - -#include <algorithm> // for max - -#include "mongo/db/field_ref.h" -#include "mongo/db/jsobjmanipulator.h" -#include "mongo/db/pdfile.h" -#include "mongo/db/repl/oplog.h" -#include "mongo/util/mongoutils/str.h" - -//#define DEBUGUPDATE(x) cout << x << endl; -#define DEBUGUPDATE(x) - -namespace mongo { - - const char* Mod::modNames[] = { "$inc", "$set", "$push", "$pushAll", "$pull", "$pullAll" , "$pop", "$unset" , - "$bitand" , "$bitor" , "$bit" , "$addToSet", "$rename", "$rename" , - "$setOnInsert" - }; - unsigned Mod::modNamesNum = sizeof(Mod::modNames)/sizeof(char*); - - bool Mod::_pullElementMatch( BSONElement& toMatch ) const { - - if ( elt.type() != Object ) { - // if elt isn't an object, then comparison will work - return toMatch.woCompare( elt , false ) == 0; - } - - if ( matcherOnPrimitive ) - return matcher->matches( toMatch.wrap( "" ) ); - - if ( toMatch.type() != Object ) { - // looking for an object, so this can't match - return false; - } - - // now we have an object on both sides - return matcher->matches( toMatch.embeddedObject() ); - } - - void Mod::appendIncremented( BSONBuilderBase& builder , const BSONElement& in, ModState& ms ) const { - BSONType a = in.type(); - BSONType b = elt.type(); - - if ( a == NumberDouble || b == NumberDouble ) { - ms.incType = NumberDouble; - ms.incdouble = elt.numberDouble() + in.numberDouble(); - } - else if ( a == NumberLong || b == NumberLong ) { - ms.incType = NumberLong; - ms.inclong = elt.numberLong() + in.numberLong(); - } - else { - int x = elt.numberInt() + in.numberInt(); - if ( x < 0 && elt.numberInt() > 0 && in.numberInt() > 0 ) { - // overflow - ms.incType = NumberLong; - ms.inclong = elt.numberLong() + in.numberLong(); - } - else { - ms.incType = NumberInt; - ms.incint = elt.numberInt() + in.numberInt(); - } - } - - ms.appendIncValue( builder , false ); - } - - void appendUnset( BSONBuilderBase& builder ) { - if ( builder.isArray() ) { - builder.appendNull(); - } - } - - void Mod::apply( BSONBuilderBase& builder , BSONElement in , ModState& ms ) const { - if ( ms.dontApply ) { - // Pass the original element through unchanged. - builder << in; - return; - } - - switch ( op ) { - - case INC: { - appendIncremented( builder , in , ms ); - // We don't need to "fix" this operation into a $set, for oplog purposes, - // here. ModState::appendForOpLog will do that for us. It relies on the new value - // being in inc{int,long,double} inside the ModState that wraps around this Mod. - break; - } - - case SET_ON_INSERT: - // There is a corner case that would land us here (making a change to an existing - // field with $setOnInsert). If we're in an upsert, and the query portion of the - // update creates a field, we can modify it with $setOnInsert. This degenerates - // into a $set, so we fall through to the next case. - ms.fixedOpName = "$set"; - // Fall through. - - case SET: { - _checkForAppending( elt ); - builder.appendAs( elt , shortFieldName ); - break; - } - - case UNSET: { - appendUnset( builder ); - break; - } - - case PUSH: { - uassert( 10131 , "$push can only be applied to an array" , in.type() == Array ); - - // - // We can be in a single element push case, a "push all" case, or a "push all" case - // with a slice requirement (ie, a "push to size"). In each of these, we decide - // differently how much of the existing- and of the parameter-array to copy to the - // final object. - // - - // Start the resulting array's builder. - BSONArrayBuilder bb( builder.subarrayStart( shortFieldName ) ); - - // If in the single element push case, we'll copy all elements of the existing - // array and add the new one. - if ( ! isEach() ) { - BSONObjIterator i( in.embeddedObject() ); - while ( i.more() ) { - bb.append( i.next() ); - } - bb.append( elt ); - - // We don't want to log a positional $set for which the '_checkForAppending' test - // won't pass. If we're in that case, fall back to non-optimized logging. - if ( (elt.type() == Object && elt.embeddedObject().okForStorage()) || - (elt.type() != Object) ) { - ms.fixedOpName = "$set"; - ms.forcePositional = true; - ms.position = bb.arrSize() - 1; - bb.done(); - } - else { - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray( bb.done().getOwned() ); - } - } - - // If we're in the "push all" case, we'll copy all element of both the existing and - // parameter arrays. - else if ( isEach() && ! isSliceOnly() && ! isSliceAndSort() ) { - BSONObjIterator i( in.embeddedObject() ); - while ( i.more() ) { - bb.append( i.next() ); - } - BSONObjIterator j( getEach() ); - while ( j.more() ) { - bb.append( j.next() ); - } - - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray( bb.done().getOwned() ); - } - - // If we're in the "push with a $each" case with slice, we have to decide how much - // of each of the existing and parameter arrays to copy to the final object. - else if ( isSliceOnly() ) { - long long slice = getSlice(); - BSONObj eachArray = getEach(); - long long arraySize = in.embeddedObject().nFields(); - long long eachArraySize = eachArray.nFields(); - - // Zero slice is equivalent to resetting the array in the final object, so - // we won't copy anything. - if (slice == 0) { - // no-op - } - - // If the parameter array alone is larger than the slice, then only copy - // object from that array. - else if (slice <= eachArraySize) { - long long skip = eachArraySize - slice; - BSONObjIterator j( getEach() ); - while ( j.more() ) { - if ( skip-- > 0 ) { - j.next(); - continue; - } - bb.append( j.next() ); - } - } - - // If the parameter array is not sufficient to fill the slice, then some (or all) - // the elements from the existing array will be copied too. - else { - long long skip = std::max(0LL, arraySize - (slice - eachArraySize) ); - BSONObjIterator i( in.embeddedObject() ); - while ( i.more() ) { - if (skip-- > 0) { - i.next(); - continue; - } - bb.append( i.next() ); - } - BSONObjIterator j( getEach() ); - while ( j.more() ) { - bb.append( j.next() ); - } - } - - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray( bb.done().getOwned() ); - } - - // If we're in the "push all" case ($push with a $each) with sort, we have to - // concatenate the existing array with the $each array, sort the result, and then - // decide how much of each of the resulting work area to copy to the final object. - else { - long long slice = getSlice(); - - // Zero slice is equivalent to resetting the array in the final object, so - // we only go into sorting if there is anything to sort. - if ( slice > 0 ) { - vector<BSONObj> workArea; - BSONObjIterator i( in.embeddedObject() ); - while ( i.more() ) { - workArea.push_back( i.next().Obj() ); - } - BSONObjIterator j( getEach() ); - while ( j.more() ) { - workArea.push_back( j.next().Obj() ); - } - ProjectKeyCmp cmp( getSort() ); - sort( workArea.begin(), workArea.end(), cmp ); - - long long skip = std::max( 0LL, - (long long)workArea.size() - slice ); - for ( vector<BSONObj>::iterator it = workArea.begin(); - it != workArea.end(); - ++it ) { - if ( skip-- > 0 ) { - continue; - } - bb.append( *it ); - } - } - - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray( bb.done().getOwned() ); - } - - break; - } - - case ADDTOSET: { - uassert( 12592 , "$addToSet can only be applied to an array" , in.type() == Array ); - BSONArrayBuilder bb( builder.subarrayStart( shortFieldName ) ); - BSONObjIterator i( in.embeddedObject() ); - - if ( isEach() ) { - - BSONElementSet toadd; - parseEach( toadd ); - - while ( i.more() ) { - BSONElement cur = i.next(); - bb.append( cur ); - toadd.erase( cur ); - } - - { - BSONObjIterator i( getEach() ); - while ( i.more() ) { - BSONElement e = i.next(); - if ( toadd.count(e) ) { - bb.append( e ); - toadd.erase( e ); - } - } - } - - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray(bb.done().getOwned()); - } - else { - - bool found = false; - int pos = 0; - int count = 0; - while ( i.more() ) { - BSONElement cur = i.next(); - bb.append( cur ); - if ( elt.woCompare( cur , false ) == 0 ) { - found = true; - pos = count; - } - count++; - } - - if ( !found ) { - bb.append( elt ); - } - - // We don't want to log a positional $set for which the '_checkForAppending' - // test won't pass. If we're in that case, fall back to non-optimized logging. - if ( (elt.type() == Object && elt.embeddedObject().okForStorage()) || - (elt.type() != Object) ) { - ms.fixedOpName = "$set"; - ms.forcePositional = true; - ms.position = found ? pos : bb.arrSize() - 1; - bb.done(); - } - else { - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray(bb.done().getOwned()); - } - } - - break; - } - - case PUSH_ALL: { - uassert( 10132 , "$pushAll can only be applied to an array" , in.type() == Array ); - uassert( 10133 , "$pushAll has to be passed an array" , elt.type() ); - - BSONArrayBuilder bb( builder.subarrayStart( shortFieldName ) ); - - BSONObjIterator i( in.embeddedObject() ); - while ( i.more() ) { - bb.append( i.next() ); - } - - i = BSONObjIterator( elt.embeddedObject() ); - while ( i.more() ) { - bb.append( i.next() ); - } - - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray(bb.done().getOwned()); - break; - } - - case PULL: - case PULL_ALL: { - uassert( 10134 , "$pull/$pullAll can only be applied to an array" , in.type() == Array ); - BSONArrayBuilder bb( builder.subarrayStart( shortFieldName ) ); - - //temporarily record the things to pull. only use this set while 'elt' in scope. - BSONElementSet toPull; - if ( op == PULL_ALL ) { - BSONObjIterator j( elt.embeddedObject() ); - while ( j.more() ) { - toPull.insert( j.next() ); - } - } - - BSONObjIterator i( in.embeddedObject() ); - while ( i.more() ) { - BSONElement e = i.next(); - bool allowed = true; - - if ( op == PULL ) { - allowed = ! _pullElementMatch( e ); - } - else { - allowed = ( toPull.find( e ) == toPull.end() ); - } - - if ( allowed ) - bb.append( e ); - } - - // If this is the last element of the array, then we want to write the empty array to the - // oplog. - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray(bb.done().getOwned()); - break; - } - - case POP: { - uassert( 10135 , "$pop can only be applied to an array" , in.type() == Array ); - BSONArrayBuilder bb( builder.subarrayStart( shortFieldName ) ); - - - BSONObjIterator i( in.embeddedObject() ); - if ( elt.isNumber() && elt.number() < 0 ) { - // pop from front - if ( i.more() ) { - i.next(); - } - - while( i.more() ) { - bb.append( i.next() ); - } - } - else { - // pop from back - while( i.more() ) { - BSONElement arrI = i.next(); - if ( i.more() ) { - bb.append( arrI ); - } - } - } - - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray(bb.done().getOwned()); - break; - } - - case BIT: { - uassert( 10136 , "$bit needs an object" , elt.type() == Object ); - uassert( 10137 , "$bit can only be applied to numbers" , in.isNumber() ); - uassert( 10138 , "$bit cannot update a value of type double" , in.type() != NumberDouble ); - - int x = in.numberInt(); - long long y = in.numberLong(); - - BSONObjIterator it( elt.embeddedObject() ); - while ( it.more() ) { - BSONElement e = it.next(); - uassert( 10139 , "$bit field must be number" , e.isNumber() ); - if ( str::equals(e.fieldName(), "and") ) { - switch( in.type() ) { - case NumberInt: x = x&e.numberInt(); break; - case NumberLong: y = y&e.numberLong(); break; - default: verify( 0 ); - } - } - else if ( str::equals(e.fieldName(), "or") ) { - switch( in.type() ) { - case NumberInt: x = x|e.numberInt(); break; - case NumberLong: y = y|e.numberLong(); break; - default: verify( 0 ); - } - } - else { - uasserted(9016, str::stream() << "unknown $bit operation: " << e.fieldName()); - } - } - - switch( in.type() ) { - - case NumberInt: - builder.append( shortFieldName , x ); - // By recording the result of the bit manipulation into the ModSet, we'll be - // set up so that this $bit operation be "fixed" as a $set of the final result - // in the oplog. This will happen in appendForOpLog and what triggers it is - // setting the incType in the ModSet that is around this Mod. - ms.incType = NumberInt; - ms.incint = x; - break; - - case NumberLong: - // Please see comment on fixing this $bit into a $set for logging purposes in - // the NumberInt case. - builder.append( shortFieldName , y ); - ms.incType = NumberLong; - ms.inclong = y; - break; - - default: verify( 0 ); - } - - break; - } - - case RENAME_FROM: { - // We don't need to "fix" this operation into a $set here. ModState::appendForOpLog - // will do that for us. It relies on the field name being stored on this Mod. - break; - } - - case RENAME_TO: { - // We don't need to "fix" this operation into a $set here, for the same reason we - // didn't either with RENAME_FROM. - ms.handleRename( builder, shortFieldName ); - break; - } - - default: - uasserted( 9017 , str::stream() << "Mod::apply can't handle type: " << op ); - } - } - - // -1 inside a non-object (non-object could be array) - // 0 missing - // 1 found - int validRenamePath( BSONObj obj, const char* path ) { - while( const char* p = strchr( path, '.' ) ) { - string left( path, p - path ); - BSONElement e = obj.getField( left ); - if ( e.eoo() ) { - return 0; - } - if ( e.type() != Object ) { - return -1; - } - obj = e.embeddedObject(); - path = p + 1; - } - return !obj.getField( path ).eoo(); - } - - auto_ptr<ModSetState> ModSet::prepare(const BSONObj& obj, bool insertion) const { - DEBUGUPDATE( "\t start prepare" ); - auto_ptr<ModSetState> mss( new ModSetState( obj, - _numIndexAlwaysUpdated, - _numIndexMaybeUpdated ) ); - - - // Perform this check first, so that we don't leave a partially modified object on uassert. - for ( ModHolder::const_iterator i = _mods.begin(); i != _mods.end(); ++i ) { - DEBUGUPDATE( "\t\t prepare : " << i->first ); - mss->_mods[i->first].reset( new ModState() ); - ModState& ms = *mss->_mods[i->first]; - - const Mod& m = i->second; - - // Check for any positional operators that have not been replaced with a numeric field - // name (from a query match element). - // Only perform this positional operator validation in 'strictApply' mode. When - // replicating from a legacy primary that does not implement this validation, the - // secondary bypasses validation and remains consistent with the primary. - if ( m.strictApply ) { - FieldRef fieldRef; - fieldRef.parse( m.fieldName ); - StringData positionalOpField( "$" ); - for( size_t i = 0; i < fieldRef.numParts(); ++i ) { - uassert( 16650, - "Cannot apply the positional operator without a corresponding query " - "field containing an array.", - fieldRef.getPart( i ).compare( positionalOpField ) != 0 ); - } - } - - BSONElement e = obj.getFieldDotted(m.fieldName); - - ms.m = &m; - ms.old = e; - - if ( m.op == Mod::RENAME_FROM ) { - int source = validRenamePath( obj, m.fieldName ); - uassert( 13489, "$rename source field invalid", source != -1 ); - if ( source != 1 ) { - ms.dontApply = true; - } - continue; - } - - if ( m.op == Mod::RENAME_TO ) { - int source = validRenamePath( obj, m.renameFrom() ); - if ( source == 1 ) { - int target = validRenamePath( obj, m.fieldName ); - uassert( 13490, "$rename target field invalid", target != -1 ); - ms.newVal = obj.getFieldDotted( m.renameFrom() ); - mss->amIInPlacePossible( false ); - } - else { - ms.dontApply = true; - } - continue; - } - - if ( m.op != Mod::SET_ON_INSERT && e.eoo() ) { - mss->amIInPlacePossible( m.op == Mod::UNSET ); - continue; - } - - switch( m.op ) { - case Mod::INC: - uassert( 10140 , "Cannot apply $inc modifier to non-number", e.isNumber() || e.eoo() ); - if ( mss->amIInPlacePossible( e.isNumber() ) ) { - // check more typing info here - if ( m.elt.type() != e.type() ) { - // if i'm incrementing with a double, then the storage has to be a double - mss->amIInPlacePossible( m.elt.type() != NumberDouble ); - } - - // check for overflow - if ( e.type() == NumberInt && e.numberLong() + m.elt.numberLong() > numeric_limits<int>::max() ) { - mss->amIInPlacePossible( false ); - } - } - break; - - case Mod::SET: - mss->amIInPlacePossible( m.elt.type() == e.type() && - m.elt.valuesize() == e.valuesize() ); - break; - - case Mod::SET_ON_INSERT: - // If the document exist (i.e this is an update, not an insert) $setOnInsert - // becomes a no-op. - if ( !insertion ) { - ms.dontApply = true; - mss->amIInPlacePossible( true ); - } - else { - mss->amIInPlacePossible( false ); - } - break; - - case Mod::PUSH: - case Mod::PUSH_ALL: - uassert( 10141, - "Cannot apply $push/$pushAll modifier to non-array", - e.type() == Array || e.eoo() ); - - // Currently, we require the base array of a $sort to be made of - // objects (as opposed to base types). - if ( !e.eoo() && m.isEach() && m.isSliceAndSort() ) { - BSONObjIterator i( e.embeddedObject() ); - while ( i.more() ) { - BSONElement arrayItem = i.next(); - uassert( 16638, - "$sort can only be applied to an array of objects", - arrayItem.type() == Object ); - } - } - mss->amIInPlacePossible( false ); - break; - - case Mod::PULL: - case Mod::PULL_ALL: { - uassert( 10142, - "Cannot apply $pull/$pullAll modifier to non-array", - e.type() == Array || e.eoo() ); - - //temporarily record the things to pull. only use this set while 'm.elt' in scope. - BSONElementSet toPull; - if ( m.op == Mod::PULL_ALL ) { - BSONObjIterator j( m.elt.embeddedObject() ); - while ( j.more() ) { - toPull.insert( j.next() ); - } - } - - BSONObjIterator i( e.embeddedObject() ); - while( mss->_inPlacePossible && i.more() ) { - BSONElement arrI = i.next(); - if ( m.op == Mod::PULL ) { - mss->amIInPlacePossible( ! m._pullElementMatch( arrI ) ); - } - else if ( m.op == Mod::PULL_ALL ) { - mss->amIInPlacePossible( toPull.find( arrI ) == toPull.end() ); - } - } - break; - } - - case Mod::POP: { - uassert( 10143, - "Cannot apply $pop modifier to non-array", - e.type() == Array || e.eoo() ); - mss->amIInPlacePossible( e.embeddedObject().isEmpty() ); - break; - } - - case Mod::ADDTOSET: { - uassert( 12591, - "Cannot apply $addToSet modifier to non-array", - e.type() == Array || e.eoo() ); - - BSONObjIterator i( e.embeddedObject() ); - if ( m.isEach() ) { - BSONElementSet toadd; - m.parseEach( toadd ); - while( i.more() ) { - BSONElement arrI = i.next(); - toadd.erase( arrI ); - } - mss->amIInPlacePossible( toadd.size() == 0 ); - } - else { - bool found = false; - while( i.more() ) { - BSONElement arrI = i.next(); - if ( arrI.woCompare( m.elt , false ) == 0 ) { - found = true; - break; - } - } - mss->amIInPlacePossible( found ); - } - break; - } - - default: - // mods we don't know about shouldn't be done in place - mss->amIInPlacePossible( false ); - } - } - - DEBUGUPDATE( "\t mss\n" << mss->toString() << "\t--" ); - - return mss; - } - - const char* ModState::getOpLogName() const { - if ( dontApply ) { - return NULL; - } - - if ( incType ) { - return "$set"; - } - - if ( m->op == Mod::RENAME_FROM ) { - return "$unset"; - } - - if ( m->op == Mod::RENAME_TO ) { - return "$set"; - } - - return fixedOpName ? fixedOpName : Mod::modNames[op()]; - } - - - void ModState::appendForOpLog( BSONObjBuilder& bb ) const { - // dontApply logic is deprecated for all but $rename. - if ( dontApply ) { - return; - } - - if ( incType ) { - DEBUGUPDATE( "\t\t\t\t\t appendForOpLog inc fieldname: " << m->fieldName - << " short:" << m->shortFieldName ); - appendIncValue( bb , true ); - return; - } - - if ( m->op == Mod::RENAME_FROM ) { - DEBUGUPDATE( "\t\t\t\t\t appendForOpLog RENAME_FROM fieldName:" << m->fieldName ); - bb.append( m->fieldName, 1 ); - return; - } - - if ( m->op == Mod::RENAME_TO ) { - DEBUGUPDATE( "\t\t\t\t\t appendForOpLog RENAME_TO fieldName:" << m->fieldName ); - bb.appendAs( newVal, m->fieldName ); - return; - } - - const char* name = fixedOpName ? fixedOpName : Mod::modNames[op()]; - - DEBUGUPDATE( "\t\t\t\t\t appendForOpLog name:" << name << " fixed: " << fixed - << " fn: " << m->fieldName ); - - if (strcmp(name, "$unset") == 0) { - bb.append(m->fieldName, 1); - return; - } - - if ( fixed ) { - bb.appendAs( *fixed , m->fieldName ); - } - else if ( ! fixedArray.isEmpty() || forceEmptyArray ) { - bb.append( m->fieldName, fixedArray ); - } - else if ( forcePositional ) { - string positionalField = str::stream() << m->fieldName << "." << position; - bb.appendAs( m->elt, positionalField.c_str() ); - } - else { - bb.appendAs( m->elt , m->fieldName ); - } - - } - - typedef map<string, vector<ModState*> > NamedModMap; - - BSONObj ModSetState::getOpLogRewrite() const { - NamedModMap names; - for ( ModStateHolder::const_iterator i = _mods.begin(); i != _mods.end(); ++i ) { - const char* name = i->second->getOpLogName(); - if ( ! name ) - continue; - names[name].push_back( i->second.get() ); - } - - BSONObjBuilder b; - for ( NamedModMap::const_iterator i = names.begin(); - i != names.end(); - ++i ) { - BSONObjBuilder bb( b.subobjStart( i->first ) ); - const vector<ModState*>& mods = i->second; - for ( unsigned j = 0; j < mods.size(); j++ ) { - mods[j]->appendForOpLog( bb ); - } - bb.doneFast(); - } - return b.obj(); - } - - string ModState::toString() const { - stringstream ss; - if ( fixedOpName ) - ss << " fixedOpName: " << fixedOpName; - if ( fixed ) - ss << " fixed: " << fixed; - return ss.str(); - } - - void ModState::handleRename( BSONBuilderBase& newObjBuilder, const char* shortFieldName ) { - newObjBuilder.appendAs( newVal , shortFieldName ); - BSONObjBuilder b; - b.appendAs( newVal, shortFieldName ); - verify( _objData.isEmpty() ); - _objData = b.obj(); - newVal = _objData.firstElement(); - } - - void ModSetState::applyModsInPlace( bool isOnDisk ) { - // TODO i think this assert means that we can get rid of the isOnDisk param - // and just use isOwned as the determination - DEV verify( isOnDisk == ! _obj.isOwned() ); - - for ( ModStateHolder::iterator i = _mods.begin(); i != _mods.end(); ++i ) { - ModState& m = *i->second; - - if ( m.dontApply ) { - continue; - } - - switch ( m.m->op ) { - case Mod::UNSET: - m.fixedOpName = "$unset"; - break; - - case Mod::ADDTOSET: - m.fixedOpName = "$set"; - m.fixed = &(m.old); - break; - - case Mod::RENAME_FROM: - case Mod::RENAME_TO: - // this should have been handled by prepare - break; - - case Mod::PULL: - case Mod::PULL_ALL: - // this should have been handled by prepare - m.fixedOpName = "$set"; - m.fixed = &(m.old); - break; - - case Mod::POP: - verify( m.old.isABSONObj() && m.old.Obj().isEmpty() ); - m.fixedOpName = "$set"; - m.fixed = &(m.old); - break; - // [dm] the BSONElementManipulator statements below are for replication (correct?) - - case Mod::INC: - if ( isOnDisk ) - m.m->IncrementMe( m.old ); - else - m.m->incrementMe( m.old ); - m.fixedOpName = "$set"; - m.fixed = &(m.old); - break; - - case Mod::SET: - if ( isOnDisk ) - BSONElementManipulator( m.old ).ReplaceTypeAndValue( m.m->elt ); - else - BSONElementManipulator( m.old ).replaceTypeAndValue( m.m->elt ); - break; - - case Mod::SET_ON_INSERT: - // this should have been handled by prepare - break; - - default: - uassert( 13478 , "can't apply mod in place - shouldn't have gotten here" , 0 ); - } - } - } - - void ModSetState::_appendNewFromMods( const string& root, - ModState& modState, - BSONBuilderBase& builder, - set<string>& onedownseen ) { - Mod& m = *((Mod*)(modState.m)); // HACK - switch (m.op) { - // unset/pull/pullAll on nothing does nothing, so don't append anything. Still, - // explicitly log that the target array was reset. - case Mod::POP: - case Mod::PULL: - case Mod::PULL_ALL: - case Mod::UNSET: - modState.fixedOpName = "$unset"; - return; - - // $rename/$setOnInsert may involve dotted path creation, so we want to make sure we're - // not creating a path here for a rename that's a no-op. In other words if we're - // issuing a {$rename: {a.b : c.d} } that's a no-op, we don't want to create the a and - // c paths here. See test NestedNoName in the 'repl' suite. - case Mod::RENAME_FROM: - case Mod::RENAME_TO: - case Mod::SET_ON_INSERT: - if (modState.dontApply) { - return; - } - - default: - ;// fall through - } - const char* temp = modState.fieldName(); - temp += root.size(); - const char* dot = strchr( temp , '.' ); - if ( dot ) { - string nr( modState.fieldName() , 0 , 1 + ( dot - modState.fieldName() ) ); - string nf( temp , 0 , dot - temp ); - if ( onedownseen.count( nf ) ) - return; - onedownseen.insert( nf ); - BSONObjBuilder bb ( builder.subobjStart( nf ) ); - // Always insert an object, even if the field name is numeric. - createNewObjFromMods( nr , bb , BSONObj() ); - bb.done(); - } - else { - appendNewFromMod( modState , builder ); - } - } - - bool ModSetState::duplicateFieldName( const BSONElement& a, const BSONElement& b ) { - return - !a.eoo() && - !b.eoo() && - ( a.rawdata() != b.rawdata() ) && - str::equals( a.fieldName(), b.fieldName() ); - } - - ModSetState::ModStateRange ModSetState::modsForRoot( const string& root ) { - ModStateHolder::iterator mstart = _mods.lower_bound( root ); - StringBuilder buf; - buf << root << (char)255; - ModStateHolder::iterator mend = _mods.lower_bound( buf.str() ); - return make_pair( mstart, mend ); - } - - void ModSetState::createNewObjFromMods( const string& root, - BSONObjBuilder& builder, - const BSONObj& obj ) { - BSONObjIteratorSorted es( obj ); - createNewFromMods( root, builder, es, modsForRoot( root ), LexNumCmp( true ) ); - } - - void ModSetState::createNewArrayFromMods( const string& root, - BSONArrayBuilder& builder, - const BSONArray& arr ) { - BSONArrayIteratorSorted es( arr ); - ModStateRange objectOrderedRange = modsForRoot( root ); - ModStateHolder arrayOrderedMods( LexNumCmp( false ) ); - arrayOrderedMods.insert( objectOrderedRange.first, objectOrderedRange.second ); - ModStateRange arrayOrderedRange( arrayOrderedMods.begin(), arrayOrderedMods.end() ); - createNewFromMods( root, builder, es, arrayOrderedRange, LexNumCmp( false ) ); - } - - void ModSetState::createNewFromMods( const string& root, - BSONBuilderBase& builder, - BSONIteratorSorted& es, - const ModStateRange& modRange, - const LexNumCmp& lexNumCmp ) { - - DEBUGUPDATE( "\t\t createNewFromMods root: " << root ); - ModStateHolder::iterator m = modRange.first; - const ModStateHolder::const_iterator mend = modRange.second; - BSONElement e = es.next(); - - set<string> onedownseen; - BSONElement prevE; - while ( !e.eoo() && m != mend ) { - - if ( duplicateFieldName( prevE, e ) ) { - // Just copy through an element with a duplicate field name. - builder.append( e ); - prevE = e; - e = es.next(); - continue; - } - prevE = e; - - string field = root + e.fieldName(); - FieldCompareResult cmp = compareDottedFieldNames( m->second->m->fieldName , field , - lexNumCmp ); - - DEBUGUPDATE( "\t\t\t field:" << field << "\t mod:" - << m->second->m->fieldName << "\t cmp:" << cmp - << "\t short: " << e.fieldName() ); - - switch ( cmp ) { - - case LEFT_SUBFIELD: { // Mod is embedded under this element - - // SERVER-4781 - bool isObjOrArr = e.type() == Object || e.type() == Array; - if ( ! isObjOrArr ) { - if (m->second->m->strictApply) { - uasserted( 10145, - str::stream() << "LEFT_SUBFIELD only supports Object: " << field - << " not: " << e.type() ); - } - else { - // Since we're not applying the mod, we keep what was there before - builder.append( e ); - - // Skip both as we're not applying this mod. Note that we'll advance - // the iterator on the mod side for all the mods that are under the - // root we are now. - e = es.next(); - m++; - while ( m != mend && - ( compareDottedFieldNames( m->second->m->fieldName, - field, - lexNumCmp ) == LEFT_SUBFIELD ) ) { - m++; - } - continue; - } - } - - if ( onedownseen.count( e.fieldName() ) == 0 ) { - onedownseen.insert( e.fieldName() ); - if ( e.type() == Object ) { - BSONObjBuilder bb( builder.subobjStart( e.fieldName() ) ); - stringstream nr; nr << root << e.fieldName() << "."; - createNewObjFromMods( nr.str() , bb , e.Obj() ); - bb.done(); - } - else { - BSONArrayBuilder ba( builder.subarrayStart( e.fieldName() ) ); - stringstream nr; nr << root << e.fieldName() << "."; - createNewArrayFromMods( nr.str() , ba , BSONArray( e.embeddedObject() ) ); - ba.done(); - } - // inc both as we handled both - e = es.next(); - m++; - while ( m != mend && - ( compareDottedFieldNames( m->second->m->fieldName , field , lexNumCmp ) == - LEFT_SUBFIELD ) ) { - m++; - } - } - else { - massert( 16069 , "ModSet::createNewFromMods - " - "SERVER-4777 unhandled duplicate field" , 0 ); - } - continue; - } - case LEFT_BEFORE: // Mod on a field that doesn't exist - DEBUGUPDATE( "\t\t\t\t creating new field for: " << m->second->m->fieldName ); - _appendNewFromMods( root , *m->second , builder , onedownseen ); - m++; - continue; - case SAME: - DEBUGUPDATE( "\t\t\t\t applying mod on: " << m->second->m->fieldName ); - m->second->apply( builder , e ); - e = es.next(); - m++; - continue; - case RIGHT_BEFORE: // field that doesn't have a MOD - DEBUGUPDATE( "\t\t\t\t just copying" ); - builder.append( e ); // if array, ignore field name - e = es.next(); - continue; - case RIGHT_SUBFIELD: - massert( 10399 , "ModSet::createNewFromMods - RIGHT_SUBFIELD should be impossible" , 0 ); - break; - default: - massert( 10400 , "unhandled case" , 0 ); - } - } - - // finished looping the mods, just adding the rest of the elements - while ( !e.eoo() ) { - DEBUGUPDATE( "\t\t\t copying: " << e.fieldName() ); - builder.append( e ); // if array, ignore field name - e = es.next(); - } - - // do mods that don't have fields already - for ( ; m != mend; m++ ) { - DEBUGUPDATE( "\t\t\t\t appending from mod at end: " << m->second->m->fieldName ); - _appendNewFromMods( root , *m->second , builder , onedownseen ); - } - } - - BSONObj ModSetState::createNewFromMods() { - BSONObjBuilder b( (int)(_obj.objsize() * 1.1) ); - createNewObjFromMods( "" , b , _obj ); - return _newFromMods = b.obj(); - } - - string ModSetState::toString() const { - stringstream ss; - for ( ModStateHolder::const_iterator i=_mods.begin(); i!=_mods.end(); ++i ) { - ss << "\t\t" << i->first << "\t" << i->second->toString() << "\n"; - } - return ss.str(); - } - - bool ModSetState::isUpdateIndexedSlow() const { - // There may be indices over fields for which Mods are no-ops. In other words, if a - // Mod touches an index field but that Mod is a no-op, this update may be - // considered one that does not update indices. - if ( _numIndexMaybeUpdated == 0 ) { - return false; - } - else { - for ( ModStateHolder::const_iterator it = _mods.begin(); - it != _mods.end(); - ++it ) { - const Mod* m = it->second->m; - shared_ptr<ModState> ms = it->second; - - switch ( m->op ) { - case Mod::SET_ON_INSERT: - case Mod::RENAME_FROM: - case Mod::RENAME_TO: - if ( m->isIndexed && !ms->dontApply ) { - return true; - } - break; - - default: - // no-op - break; - } - } - - return false; - } - } - - - BSONObj ModSet::createNewFromQuery( const BSONObj& query ) { - BSONObj newObj; - - { - BSONObjBuilder bb; - EmbeddedBuilder eb( &bb ); - BSONObjIteratorSorted i( query ); - while ( i.more() ) { - BSONElement e = i.next(); - if ( e.fieldName()[0] == '$' ) // for $atomic and anything else we add - continue; - - if ( e.type() == Object && e.embeddedObject().firstElementFieldName()[0] == '$' ) { - // we have something like { x : { $gt : 5 } } - // this can be a query piece - // or can be a dbref or something - - int op = e.embeddedObject().firstElement().getGtLtOp(); - if ( op > 0 ) { - // This means this is a $gt type filter, so don't make it part of the new - // object. - continue; - } - - if ( str::equals( e.embeddedObject().firstElement().fieldName(), "$not" ) ) { - // A $not filter operator is not detected in getGtLtOp() and should not - // become part of the new object. - continue; - } - } - - eb.appendAs( e , e.fieldName() ); - } - eb.done(); - newObj = bb.obj(); - } - - auto_ptr<ModSetState> mss = prepare( newObj, true /* this is an insertion */ ); - - if ( mss->canApplyInPlace() ) - mss->applyModsInPlace( false ); - else - newObj = mss->createNewFromMods(); - - return newObj; - } - - /* get special operations like $inc - { $inc: { a:1, b:1 } } - { $set: { a:77 } } - { $push: { a:55 } } - { $pushAll: { a:[77,88] } } - { $pull: { a:66 } } - { $pullAll : { a:[99,1010] } } - NOTE: MODIFIES source from object! - */ - ModSet::ModSet( const BSONObj& from , - const IndexPathSet& idxKeys, - bool forReplication ) - : _numIndexMaybeUpdated( 0 ) - , _numIndexAlwaysUpdated( 0 ) - , _hasDynamicArray( false ) { - - BSONObjIterator it(from); - - while ( it.more() ) { - BSONElement e = it.next(); - const char* fn = e.fieldName(); - - uassert( 10147 , "Invalid modifier specified: " + string( fn ), e.type() == Object ); - BSONObj j = e.embeddedObject(); - DEBUGUPDATE( "\t" << j ); - - BSONObjIterator jt(j); - Mod::Op op = opFromStr( fn ); - - while ( jt.more() ) { - BSONElement f = jt.next(); // x:44 - - const char* fieldName = f.fieldName(); - - // Allow remove of invalid field name in case it was inserted before this check - // was added (~ version 2.1). - uassert( 15896, - "Modified field name may not start with $", - fieldName[0] != '$' || op == Mod::UNSET ); - uassert( 10148, - "Mod on _id not allowed", - strcmp( fieldName, "_id" ) != 0 ); - uassert( 10149, - "Invalid mod field name, may not end in a period", - fieldName[ strlen( fieldName ) - 1 ] != '.' ); - uassert( 10150, - "Field name duplication not allowed with modifiers", - ! haveModForField( fieldName ) ); - uassert( 10151, - "have conflicting mods in update", - ! haveConflictingMod( fieldName ) ); - uassert( 10152, - "Modifier $inc allowed for numbers only", - f.isNumber() || op != Mod::INC ); - uassert( 10153, - "Modifier $pushAll/pullAll allowed for arrays only", - f.type() == Array || ( op != Mod::PUSH_ALL && op != Mod::PULL_ALL ) ); - - // Check whether $each, $slice, and $sort syntax for $push is correct. - if ( ( op == Mod::PUSH ) && ( f.type() == Object ) ) { - BSONObj pushObj = f.embeddedObject(); - if ( pushObj.nFields() > 0 && - strcmp(pushObj.firstElement().fieldName(), "$each") == 0 ) { - uassert( 16564, - "$each term needs to occur alone (or with $slice/$sort)", - pushObj.nFields() <= 3 ); - uassert( 16565, - "$each requires an array value", - pushObj.firstElement().type() == Array ); - - // If both $slice and $sort are present, they may be switched. - if ( pushObj.nFields() > 1 ) { - BSONObjIterator i( pushObj ); - i.next(); - - bool seenSlice = false; - bool seenSort = false; - while ( i.more() ) { - BSONElement nextElem = i.next(); - - if ( str::equals( nextElem.fieldName(), "$slice" ) ) { - uassert( 16567, "$slice appeared twice", !seenSlice); - seenSlice = true; - uassert( 16568, - "$slice value must be a numeric integer", - nextElem.type() == NumberInt || - nextElem.type() == NumberLong || - (nextElem.type() == NumberDouble && - nextElem.numberDouble() == - (long long)nextElem.numberDouble() ) ); - uassert( 16640, - "$slice value must be negative or zero", - nextElem.number() <= 0 ); - } - else if ( str::equals( nextElem.fieldName(), "$sort" ) ) { - uassert( 16647, "$sort appeared twice", !seenSort ); - seenSort = true; - uassert( 16648, - "$sort component of $push must be an object", - nextElem.type() == Object ); - - BSONObjIterator j( nextElem.embeddedObject() ); - while ( j.more() ) { - BSONElement fieldSortElem = j.next(); - uassert( 16641, - "$sort elements' values must either 1 or -1", - ( fieldSortElem.type() == NumberInt || - fieldSortElem.type() == NumberLong || - ( fieldSortElem.type() == NumberDouble && - fieldSortElem.numberDouble() == - (long long) fieldSortElem.numberDouble() ) ) && - ( fieldSortElem.Number() == 1 || - fieldSortElem.Number() == -1 ) ); - - FieldRef sortField; - sortField.parse( fieldSortElem.fieldName() ); - uassert( 16690, - "$sort field cannot be empty", - sortField.numParts() > 0 ); - - for ( size_t i = 0; i < sortField.numParts(); i++ ) { - uassert( 16691, - "empty field in dotted sort pattern", - sortField.getPart( i ).size() > 0 ); - } - } - - // Finally, check if the $each is made of objects (as opposed - // to basic types). Currently, $sort only supports operating - // on arrays of objects. - BSONObj eachArray = pushObj.firstElement().embeddedObject(); - BSONObjIterator k( eachArray ); - while ( k.more() ) { - BSONElement eachItem = k.next(); - uassert( 16642, - "$sort requires $each to be an array of objects", - eachItem.type() == Object ); - } - - } - else { - uasserted( 16643, - "$each term takes only $slice (and optionally " - "$sort) as complements" ); - } - } - - uassert( 16644, "cannot have a $sort without a $slice", seenSlice ); - } - } - } - - if ( op == Mod::RENAME_TO ) { - uassert( 13494, "$rename target must be a string", f.type() == String ); - const char* target = f.valuestr(); - uassert( 13495, - "$rename source must differ from target", - strcmp( fieldName, target ) != 0 ); - uassert( 13496, - "invalid mod field name, source may not be empty", - fieldName[0] ); - uassert( 13479, - "invalid mod field name, target may not be empty", - target[0] ); - uassert( 13480, - "invalid mod field name, source may not begin or end in period", - fieldName[0] != '.' && fieldName[ strlen( fieldName ) - 1 ] != '.' ); - uassert( 13481, - "invalid mod field name, target may not begin or end in period", - target[0] != '.' && target[ strlen( target ) - 1 ] != '.' ); - uassert( 13482, - "$rename affecting _id not allowed", - !( fieldName[0] == '_' && fieldName[1] == 'i' && fieldName[2] == 'd' - && ( !fieldName[3] || fieldName[3] == '.' ) ) ); - uassert( 13483, - "$rename affecting _id not allowed", - !( target[0] == '_' && target[1] == 'i' && target[2] == 'd' - && ( !target[3] || target[3] == '.' ) ) ); - uassert( 13484, - "field name duplication not allowed with $rename target", - !haveModForField( target ) ); - uassert( 13485, - "conflicting mods not allowed with $rename target", - !haveConflictingMod( target ) ); - uassert( 13486, - "$rename target may not be a parent of source", - !( strncmp( fieldName, target, strlen( target ) ) == 0 - && fieldName[ strlen( target ) ] == '.' ) ); - uassert( 13487, - "$rename source may not be dynamic array", - strstr( fieldName , ".$" ) == 0 ); - uassert( 13488, - "$rename target may not be dynamic array", - strstr( target , ".$" ) == 0 ); - - Mod from; - from.init( Mod::RENAME_FROM, f , forReplication ); - from.setFieldName( fieldName ); - setIndexedStatus( from, idxKeys ); - _mods[ from.fieldName ] = from; - - Mod to; - to.init( Mod::RENAME_TO, f , forReplication ); - to.setFieldName( target ); - setIndexedStatus( to, idxKeys ); - _mods[ to.fieldName ] = to; - - DEBUGUPDATE( "\t\t " << fieldName << "\t" << from.fieldName << "\t" << to.fieldName ); - continue; - } - - _hasDynamicArray = _hasDynamicArray || strstr( fieldName , ".$" ) > 0; - - Mod m; - m.init( op , f , forReplication ); - m.setFieldName( f.fieldName() ); - setIndexedStatus( m, idxKeys ); - _mods[m.fieldName] = m; - - DEBUGUPDATE( "\t\t " << fieldName << "\t" << m.fieldName << "\t" << _hasDynamicArray ); - } - } - - } - - ModSet* ModSet::fixDynamicArray( const string& elemMatchKey ) const { - ModSet* n = new ModSet(); - n->_numIndexMaybeUpdated = _numIndexMaybeUpdated; - n->_numIndexAlwaysUpdated = _numIndexAlwaysUpdated; - n->_hasDynamicArray = _hasDynamicArray; - for ( ModHolder::const_iterator i=_mods.begin(); i!=_mods.end(); i++ ) { - string s = i->first; - size_t idx = s.find( ".$" ); - if ( idx == string::npos ) { - n->_mods[s] = i->second; - continue; - } - StringBuilder buf; - buf << s.substr(0,idx+1) << elemMatchKey << s.substr(idx+2); - string fixed = buf.str(); - DEBUGUPDATE( "fixed dynamic: " << s << " -->> " << fixed ); - n->_mods[fixed] = i->second; - ModHolder::iterator temp = n->_mods.find( fixed ); - temp->second.setFieldName( temp->first.c_str() ); - } - return n; - } - - void ModSet::setIndexedStatus( const IndexPathSet& idxKeys ) { - for ( ModHolder::iterator i = _mods.begin(); i != _mods.end(); ++i ) - setIndexedStatus( i->second, idxKeys ); - } - - void ModSet::setIndexedStatus( Mod& m, const IndexPathSet& idxKeys ) { - if ( idxKeys.mightBeIndexed( m.fieldName ) ) { - m.isIndexed = true; - - // Some mods may be no-ops depending on the document they are applied - // on. Determining how many indices will actually be used can only be - // determined for sure after looking at that target document. - switch ( m.op ) { - - case Mod::SET_ON_INSERT: - case Mod::RENAME_FROM: - case Mod::RENAME_TO: - _numIndexMaybeUpdated++; - break; - - default: - _numIndexAlwaysUpdated++; - - } - } - else { - m.isIndexed = false; - } - } - - -} // namespace mongo diff --git a/src/mongo/db/ops/update_internal.h b/src/mongo/db/ops/update_internal.h deleted file mode 100644 index 620f5d4889c..00000000000 --- a/src/mongo/db/ops/update_internal.h +++ /dev/null @@ -1,781 +0,0 @@ -//@file update_internal.h - -/** - * Copyright (C) 2012 10gen Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "mongo/db/index_set.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/jsobjmanipulator.h" -#include "mongo/db/matcher.h" -#include "mongo/util/embedded_builder.h" -#include "mongo/util/mongoutils/str.h" -#include "mongo/util/stringutils.h" - -using namespace mongoutils; - -namespace mongo { - - class ModState; - class ModSetState; - - /* Used for modifiers such as $inc, $set, $push, ... - * stores the info about a single operation - * once created should never be modified - */ - struct Mod { - // See opFromStr below - // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 - enum Op { INC, SET, PUSH, PUSH_ALL, PULL, PULL_ALL , POP, UNSET, BITAND, BITOR , BIT , ADDTOSET, RENAME_FROM, RENAME_TO, SET_ON_INSERT } op; - - static const char* modNames[]; - static unsigned modNamesNum; - - const char* fieldName; - const char* shortFieldName; - - // Determines if this mod must absoluetly be applied. In some replication scenarios, a - // failed apply of a mod does not constitute an error. In those cases, setting strict - // to off would not throw errors. - bool strictApply; - - // Determines if an index is going to be updated as part of the application of this - // mod. - bool isIndexed; - - BSONElement elt; // x:5 note: this is the actual element from the updateobj - boost::shared_ptr<Matcher> matcher; - bool matcherOnPrimitive; - - void init( Op o , BSONElement& e , bool forReplication ) { - op = o; - elt = e; - strictApply = !forReplication; - isIndexed = false; - if ( op == PULL && e.type() == Object ) { - BSONObj t = e.embeddedObject(); - if ( t.firstElement().getGtLtOp() == 0 ) { - matcher.reset( new Matcher( t ) ); - matcherOnPrimitive = false; - } - else { - matcher.reset( new Matcher( BSON( "" << t ) ) ); - matcherOnPrimitive = true; - } - } - } - - void setFieldName( const char* s ) { - fieldName = s; - shortFieldName = strrchr( fieldName , '.' ); - if ( shortFieldName ) - shortFieldName++; - else - shortFieldName = fieldName; - } - - /** - * @param in increments the actual value inside in - */ - void incrementMe( BSONElement& in ) const { - BSONElementManipulator manip( in ); - switch ( in.type() ) { - case NumberDouble: - manip.setNumber( elt.numberDouble() + in.numberDouble() ); - break; - case NumberLong: - manip.setLong( elt.numberLong() + in.numberLong() ); - break; - case NumberInt: - manip.setInt( elt.numberInt() + in.numberInt() ); - break; - default: - verify(0); - } - } - void IncrementMe( BSONElement& in ) const { - BSONElementManipulator manip( in ); - switch ( in.type() ) { - case NumberDouble: - manip.SetNumber( elt.numberDouble() + in.numberDouble() ); - break; - case NumberLong: - manip.SetLong( elt.numberLong() + in.numberLong() ); - break; - case NumberInt: - manip.SetInt( elt.numberInt() + in.numberInt() ); - break; - default: - verify(0); - } - } - - void appendIncremented( BSONBuilderBase& bb , const BSONElement& in, ModState& ms ) const; - - bool operator<( const Mod& other ) const { - return strcmp( fieldName, other.fieldName ) < 0; - } - - bool arrayDep() const { - switch (op) { - case PUSH: - case PUSH_ALL: - case POP: - return true; - default: - return false; - } - } - - void apply( BSONBuilderBase& b , BSONElement in , ModState& ms ) const; - - /** - * @return true iff toMatch should be removed from the array - */ - bool _pullElementMatch( BSONElement& toMatch ) const; - - void _checkForAppending( const BSONElement& e ) const { - if ( e.type() == Object ) { - // this is a tiny bit slow, but rare and important - // only when setting something TO an object, not setting something in an object - // and it checks for { $set : { x : { 'a.b' : 1 } } } - // which is feel has been common - uassert( 12527 , "not okForStorage" , e.embeddedObject().okForStorage() ); - } - } - - bool isEach() const { - if ( elt.type() != Object ) - return false; - BSONElement e = elt.embeddedObject().firstElement(); - if ( e.type() != Array ) - return false; - return strcmp( e.fieldName() , "$each" ) == 0; - } - - BSONObj getEach() const { - return elt.embeddedObjectUserCheck().firstElement().embeddedObjectUserCheck(); - } - - void parseEach( BSONElementSet& s ) const { - BSONObjIterator i(getEach()); - while ( i.more() ) { - s.insert( i.next() ); - } - } - - bool isSliceOnly() const { - if ( elt.type() != Object ) - return false; - BSONObj obj = elt.embeddedObject(); - if ( obj.nFields() != 2 ) - return false; - BSONObjIterator i( obj ); - i.next(); - BSONElement elemSlice = i.next(); - return strcmp( elemSlice.fieldName(), "$slice" ) == 0; - } - - long long getSlice() const { - // The $slice may be the second or the third element in the field object. - // { <field name>: { $each: [<each array>], $slice: -N, $sort: <pattern> } } - // 'elt' here is the BSONElement above. - BSONObj obj = elt.embeddedObject(); - BSONObjIterator i( obj ); - i.next(); - BSONElement elem = i.next(); - if ( ! str::equals( elem.fieldName(), "$slice" ) ) { - elem = i.next(); - } - dassert( elem.isNumber() ); - - // For now, we're only supporting slicing from the back of the array, i.e. - // negative slice. But the code now is wired in the opposite way: trimming from the - // back of the array is positive. - // TODO: fix this. - return -elem.numberLong(); - } - - bool isSliceAndSort() const { - if ( elt.type() != Object ) - return false; - BSONObj obj = elt.embeddedObject(); - if ( obj.nFields() != 3 ) - return false; - BSONObjIterator i( obj ); - i.next(); - - // Slice and sort may be switched. - bool seenSlice = false; - bool seenSort = false; - while ( i.more() ) { - BSONElement elem = i.next(); - if ( str::equals( elem.fieldName(), "$slice" ) ) { - if ( seenSlice ) return false; - seenSlice = true; - } - else if ( str::equals( elem.fieldName(), "$sort" ) ) { - if ( seenSort ) return false; - seenSort = true; - if ( elem.type() != Object ) return false; - } - else { - return false; - } - } - - // If present, the $sort element would have been checked during ModSet construction. - return seenSlice && seenSort; - } - - BSONObj getSort() const { - // The $sort may be the second or the third element in the field object. - // { <field name>: { $each: [<each array>], $slice: -N, $sort: <pattern> } } - // 'elt' here is the BSONElement above. - BSONObj obj = elt.embeddedObject(); - BSONObjIterator i( obj ); - i.next(); - BSONElement elem = i.next(); - if ( ! str::equals( elem.fieldName(), "$sort" ) ) { - elem = i.next(); - } - return elem.embeddedObject(); - } - - const char* renameFrom() const { - massert( 13492, "mod must be RENAME_TO type", op == Mod::RENAME_TO ); - return elt.fieldName(); - } - }; - - /** - * stores a set of Mods - * once created, should never be changed - */ - class ModSet : boost::noncopyable { - typedef map<string,Mod> ModHolder; - ModHolder _mods; - int _numIndexMaybeUpdated; - int _numIndexAlwaysUpdated; - bool _hasDynamicArray; - - static Mod::Op opFromStr( const char* fn ) { - verify( fn[0] == '$' ); - switch( fn[1] ) { - case 'i': { - if ( fn[2] == 'n' && fn[3] == 'c' && fn[4] == 0 ) - return Mod::INC; - break; - } - case 's': { - if ( fn[2] == 'e' && fn[3] == 't' ) { - if ( fn[4] == 0 ) { - return Mod::SET; - } - else if ( fn[4] == 'O' && fn[5] == 'n' && fn[6] == 'I' && fn[7] == 'n' && - fn[8] == 's' && fn[9] == 'e' && fn[10] == 'r' && fn[11] == 't' && - fn[12] == 0 ) { - return Mod::SET_ON_INSERT; - } - } - break; - } - case 'p': { - if ( fn[2] == 'u' ) { - if ( fn[3] == 's' && fn[4] == 'h' ) { - if ( fn[5] == 0 ) - return Mod::PUSH; - if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' && fn[8] == 0 ) - return Mod::PUSH_ALL; - } - else if ( fn[3] == 'l' && fn[4] == 'l' ) { - if ( fn[5] == 0 ) - return Mod::PULL; - if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' && fn[8] == 0 ) - return Mod::PULL_ALL; - } - } - else if ( fn[2] == 'o' && fn[3] == 'p' && fn[4] == 0 ) - return Mod::POP; - break; - } - case 'u': { - if ( fn[2] == 'n' && fn[3] == 's' && fn[4] == 'e' && fn[5] == 't' && fn[6] == 0 ) - return Mod::UNSET; - break; - } - case 'b': { - if ( fn[2] == 'i' && fn[3] == 't' ) { - if ( fn[4] == 0 ) - return Mod::BIT; - if ( fn[4] == 'a' && fn[5] == 'n' && fn[6] == 'd' && fn[7] == 0 ) - return Mod::BITAND; - if ( fn[4] == 'o' && fn[5] == 'r' && fn[6] == 0 ) - return Mod::BITOR; - } - break; - } - case 'a': { - if ( fn[2] == 'd' && fn[3] == 'd' ) { - // add - if ( fn[4] == 'T' && fn[5] == 'o' && fn[6] == 'S' && fn[7] == 'e' && fn[8] == 't' && fn[9] == 0 ) - return Mod::ADDTOSET; - - } - break; - } - case 'r': { - if ( fn[2] == 'e' && fn[3] == 'n' && fn[4] == 'a' && fn[5] == 'm' && fn[6] =='e' ) { - return Mod::RENAME_TO; // with this return code we handle both RENAME_TO and RENAME_FROM - } - break; - } - default: break; - } - uassert( 10161 , "Invalid modifier specified " + string( fn ), false ); - return Mod::INC; - } - - ModSet() {} - - /** - * if if applying this mod would require updating an index, set such condition in 'm', - * and update the number of indices touched in 'this' ModSet. - */ - void setIndexedStatus( Mod& m, const IndexPathSet& idxKeys ); - - public: - - ModSet( const BSONObj& from, - const IndexPathSet& idxKeys = IndexPathSet(), - bool forReplication = false ); - - /** - * re-check if this mod is impacted by indexes - */ - void setIndexedStatus( const IndexPathSet& idxKeys ); - - // TODO: this is inefficient - should probably just handle when iterating - ModSet * fixDynamicArray( const string& elemMatchKey ) const; - - bool hasDynamicArray() const { return _hasDynamicArray; } - - /** - * creates a ModSetState suitable for operation on obj - * doesn't change or modify this ModSet or any underlying Mod - * - * flag 'insertion' differentiates between obj existing prior to this update. - */ - auto_ptr<ModSetState> prepare( const BSONObj& obj, bool insertion = false ) const; - - /** - * given a query pattern, builds an object suitable for an upsert - * will take the query spec and combine all $ operators - */ - BSONObj createNewFromQuery( const BSONObj& query ); - - int maxNumIndexUpdated() const { return _numIndexMaybeUpdated + _numIndexAlwaysUpdated; } - - unsigned size() const { return _mods.size(); } - - bool haveModForField( const char* fieldName ) const { - return _mods.find( fieldName ) != _mods.end(); - } - - bool haveConflictingMod( const string& fieldName ) { - size_t idx = fieldName.find( '.' ); - if ( idx == string::npos ) - idx = fieldName.size(); - - ModHolder::const_iterator start = _mods.lower_bound(fieldName.substr(0,idx)); - for ( ; start != _mods.end(); start++ ) { - FieldCompareResult r = compareDottedFieldNames( fieldName , start->first , - LexNumCmp( true ) ); - switch ( r ) { - case LEFT_SUBFIELD: return true; - case LEFT_BEFORE: return false; - case SAME: return true; - case RIGHT_BEFORE: return false; - case RIGHT_SUBFIELD: return true; - } - } - return false; - } - - }; - - /** - * Comparator between two BSONObjects that takes in consideration only the keys and - * direction described in the sort pattern. - */ - struct ProjectKeyCmp { - BSONObj sortPattern; - - ProjectKeyCmp( BSONObj pattern ) : sortPattern( pattern) {} - - int operator()( const BSONObj& left, const BSONObj& right ) const { - BSONObj keyLeft = left.extractFields( sortPattern, true ); - BSONObj keyRight = right.extractFields( sortPattern, true ); - return keyLeft.woCompare( keyRight, sortPattern ) < 0; - } - }; - - /** - * stores any information about a single Mod operating on a single Object - */ - class ModState : boost::noncopyable { - public: - const Mod* m; - BSONElement old; - BSONElement newVal; - BSONObj _objData; - - const char* fixedOpName; - BSONElement* fixed; - BSONArray fixedArray; - bool forceEmptyArray; - bool forcePositional; - int position; - int DEPRECATED_pushStartSize; - - BSONType incType; - int incint; - double incdouble; - long long inclong; - - bool dontApply; - - ModState() { - fixedOpName = 0; - fixed = 0; - forceEmptyArray = false; - forcePositional = false; - position = 0; - DEPRECATED_pushStartSize = -1; - incType = EOO; - dontApply = false; - } - - Mod::Op op() const { - return m->op; - } - - const char* fieldName() const { - return m->fieldName; - } - - bool DEPRECATED_needOpLogRewrite() const { - if ( dontApply ) - return false; - - if ( fixed || fixedOpName || incType ) - return true; - - switch( op() ) { - case Mod::RENAME_FROM: - case Mod::RENAME_TO: - return true; - case Mod::BIT: - case Mod::BITAND: - case Mod::BITOR: - return true; - default: - return false; - } - } - - const char* getOpLogName() const; - void appendForOpLog( BSONObjBuilder& b ) const; - - void apply( BSONBuilderBase& b , BSONElement in ) { - m->apply( b , in , *this ); - } - - void appendIncValue( BSONBuilderBase& b , bool useFullName ) const { - const char* n = useFullName ? m->fieldName : m->shortFieldName; - - switch ( incType ) { - case NumberDouble: - b.append( n , incdouble ); break; - case NumberLong: - b.append( n , inclong ); break; - case NumberInt: - b.append( n , incint ); break; - default: - verify(0); - } - } - - string toString() const; - - void handleRename( BSONBuilderBase& newObjBuilder, const char* shortFieldName ); - }; - - /** - * this is used to hold state, meta data while applying a ModSet to a BSONObj - * the goal is to make ModSet const so its re-usable - */ - class ModSetState : boost::noncopyable { - typedef map<string,shared_ptr<ModState>,LexNumCmp> ModStateHolder; - typedef pair<const ModStateHolder::iterator,const ModStateHolder::iterator> ModStateRange; - const BSONObj& _obj; - ModStateHolder _mods; - bool _inPlacePossible; - BSONObj _newFromMods; // keep this data alive, as oplog generation may depend on it - int _numIndexAlwaysUpdated; - int _numIndexMaybeUpdated; - - ModSetState( const BSONObj& obj , int numIndexAlwaysUpdated , int numIndexMaybeUpdated ) - : _obj( obj ) - , _mods( LexNumCmp( true ) ) - , _inPlacePossible(true) - , _numIndexAlwaysUpdated( numIndexAlwaysUpdated ) - , _numIndexMaybeUpdated( numIndexMaybeUpdated ) { - } - - /** - * @return if in place is still possible - */ - bool amIInPlacePossible( bool inPlacePossible ) { - if ( ! inPlacePossible ) - _inPlacePossible = false; - return _inPlacePossible; - } - - ModStateRange modsForRoot( const string& root ); - - void createNewObjFromMods( const string& root, BSONObjBuilder& b, const BSONObj& obj ); - void createNewArrayFromMods( const string& root, BSONArrayBuilder& b, - const BSONArray& arr ); - - void createNewFromMods( const string& root , BSONBuilderBase& b , BSONIteratorSorted& es , - const ModStateRange& modRange , const LexNumCmp& lexNumCmp ); - - void _appendNewFromMods( const string& root , ModState& m , BSONBuilderBase& b , set<string>& onedownseen ); - - void appendNewFromMod( ModState& ms , BSONBuilderBase& b ) { - if ( ms.dontApply ) { - return; - } - - //const Mod& m = *(ms.m); // HACK - Mod& m = *((Mod*)(ms.m)); // HACK - - switch ( m.op ) { - - case Mod::PUSH: { - ms.fixedOpName = "$set"; - if ( m.isEach() ) { - BSONObj arr = m.getEach(); - if ( !m.isSliceOnly() && !m.isSliceAndSort() ) { - b.appendArray( m.shortFieldName, arr ); - - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray( arr.getOwned() ); - } - else if ( m.isSliceOnly() && ( m.getSlice() >= arr.nFields() ) ) { - b.appendArray( m.shortFieldName, arr ); - - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray( arr.getOwned() ); - } - else if ( m.isSliceOnly() ) { - BSONArrayBuilder arrBuilder( b.subarrayStart( m.shortFieldName ) ); - long long skip = arr.nFields() - m.getSlice(); - BSONObjIterator j( arr ); - while ( j.more() ) { - if ( skip-- > 0 ) { - j.next(); - continue; - } - arrBuilder.append( j.next() ); - } - - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray( arrBuilder.done().getOwned() ); - } - else if ( m.isSliceAndSort() ) { - long long slice = m.getSlice(); - - // Sort the $each array over sortPattern. - vector<BSONObj> workArea; - BSONObjIterator j( arr ); - while ( j.more() ) { - workArea.push_back( j.next().Obj() ); - } - ProjectKeyCmp cmp( m.getSort() ); - sort( workArea.begin(), workArea.end(), cmp ); - - // Slice to the appropriate size. If slice is zero, that's equivalent - // to resetting the array, ie, a no-op. - BSONArrayBuilder arrBuilder( b.subarrayStart( m.shortFieldName ) ); - if (slice > 0) { - long long skip = std::max( 0LL, - (long long)workArea.size() - slice ); - for (vector<BSONObj>::iterator it = workArea.begin(); - it != workArea.end(); - ++it ) { - if ( skip-- > 0 ) { - continue; - } - arrBuilder.append( *it ); - } - } - - // Log the full resulting array. - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray( arrBuilder.done().getOwned() ); - } - } - else { - BSONObjBuilder arr( b.subarrayStart( m.shortFieldName ) ); - arr.appendAs( m.elt, "0" ); - - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray(arr.done().getOwned()); - } - break; - } - - case Mod::ADDTOSET: { - ms.fixedOpName = "$set"; - if ( m.isEach() ) { - // Remove any duplicates in given array - BSONArrayBuilder arr( b.subarrayStart( m.shortFieldName ) ); - BSONElementSet toadd; - m.parseEach( toadd ); - BSONObjIterator i( m.getEach() ); - // int n = 0; - while ( i.more() ) { - BSONElement e = i.next(); - if ( toadd.count(e) ) { - arr.append( e ); - toadd.erase( e ); - } - } - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray(arr.done().getOwned()); - } - else { - BSONArrayBuilder arr( b.subarrayStart( m.shortFieldName ) ); - arr.append( m.elt ); - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray(arr.done().getOwned()); - } - break; - } - - case Mod::PUSH_ALL: { - b.appendAs( m.elt, m.shortFieldName ); - ms.fixedOpName = "$set"; - ms.forceEmptyArray = true; - ms.fixedArray = BSONArray(m.elt.Obj()); - break; - } - - case Mod::POP: - case Mod::PULL: - case Mod::PULL_ALL: - case Mod::UNSET: - // No-op b/c unset/pull of nothing does nothing. Still, explicilty log that - // the target array was reset. - ms.fixedOpName = "$unset"; - break; - - case Mod::INC: - case Mod::SET_ON_INSERT: - ms.fixedOpName = "$set"; - case Mod::SET: { - m._checkForAppending( m.elt ); - b.appendAs( m.elt, m.shortFieldName ); - break; - } - - // shouldn't see RENAME_FROM here - case Mod::RENAME_TO: - ms.handleRename( b, m.shortFieldName ); - break; - - default: - stringstream ss; - ss << "unknown mod in appendNewFromMod: " << m.op; - throw UserException( 9015, ss.str() ); - } - - } - - /** @return true iff the elements aren't eoo(), are distinct, and share a field name. */ - static bool duplicateFieldName( const BSONElement& a, const BSONElement& b ); - - public: - - bool canApplyInPlace() const { - return _inPlacePossible; - } - - bool isUpdateIndexed() const { - if ( _numIndexAlwaysUpdated != 0 ) { - return true; - } - - return isUpdateIndexedSlow(); - } - - bool isUpdateIndexedSlow() const; - - /** - * modified underlying _obj - * @param isOnDisk - true means this is an on disk object, and this update needs to be made durable - */ - void applyModsInPlace( bool isOnDisk ); - - BSONObj createNewFromMods(); - - // re-writing for oplog - - bool DEPRECATED_needOpLogRewrite() const { - for ( ModStateHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ ) - if ( i->second->DEPRECATED_needOpLogRewrite() ) - return true; - return false; - } - - BSONObj getOpLogRewrite() const; - - bool DEPRECATED_haveArrayDepMod() const { - for ( ModStateHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ ) - if ( i->second->m->arrayDep() ) - return true; - return false; - } - - void DEPRECATED_appendSizeSpecForArrayDepMods( BSONObjBuilder& b ) const { - for ( ModStateHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ ) { - const ModState& m = *i->second; - if ( m.m->arrayDep() ) { - if ( m.DEPRECATED_pushStartSize == -1 ) - b.appendNull( m.fieldName() ); - else - b << m.fieldName() << BSON( "$size" << m.DEPRECATED_pushStartSize ); - } - } - } - - string toString() const; - - friend class ModSet; - }; - -} // namespace mongo diff --git a/src/mongo/db/repl/rs_rollback.cpp b/src/mongo/db/repl/rs_rollback.cpp index 5e86e4f404b..ce69fd8dc82 100644 --- a/src/mongo/db/repl/rs_rollback.cpp +++ b/src/mongo/db/repl/rs_rollback.cpp @@ -549,32 +549,15 @@ namespace mongo { // todo faster... OpDebug debug; updates++; - if (isNewUpdateFrameworkEnabled()) { - - _updateObjectsNEW(/*god*/true, - d.ns, - i->second, - pattern, - /*upsert=*/true, - /*multi=*/false, - /*logtheop=*/false, - debug, - rs.get()); - - } - else { - - _updateObjects(/*god*/true, - d.ns, - i->second, - pattern, - /*upsert=*/true, - /*multi=*/false, - /*logtheop=*/false, - debug, - rs.get()); - - } + _updateObjects(/*god*/true, + d.ns, + i->second, + pattern, + /*upsert=*/true, + /*multi=*/false, + /*logtheop=*/false, + debug, + rs.get()); } } catch(DBException& e) { diff --git a/src/mongo/dbtests/framework.cpp b/src/mongo/dbtests/framework.cpp index a2b04df67b0..af0b125e5be 100644 --- a/src/mongo/dbtests/framework.cpp +++ b/src/mongo/dbtests/framework.cpp @@ -116,7 +116,6 @@ namespace mongo { ("bigfiles", "use big datafiles instead of smallfiles which is the default") ("filter,f" , po::value<string>() , "string substring filter on test name" ) ("verbose,v", "verbose") - ("testNewUpdateFramework", "test the new update framework") ("useNewQueryFramework", "use the new query framework") ("dur", "enable journaling (currently the default)") ("nodur", "disable journaling") @@ -157,10 +156,6 @@ namespace mongo { return EXIT_CLEAN; } - if (params.count("testNewUpdateFramework") && !mongo::isNewUpdateFrameworkEnabled()) { - mongo::toggleNewUpdateFrameworkEnabled(); - } - if (params.count("useNewQueryFramework")) { mongo::enableNewQueryFramework(); } diff --git a/src/mongo/dbtests/repltests.cpp b/src/mongo/dbtests/repltests.cpp index b4e89d2e53f..b900461d749 100644 --- a/src/mongo/dbtests/repltests.cpp +++ b/src/mongo/dbtests/repltests.cpp @@ -1632,12 +1632,6 @@ namespace ReplTests { add< Idempotence::EmptyPush >(); add< Idempotence::EmptyPushSparseIndex >(); add< Idempotence::PushAll >(); - - // The new update framework does not allow field names with a leading '$' to be - // pushed. - if (!isNewUpdateFrameworkEnabled()) - add< Idempotence::PushWithDollarSigns >(); - add< Idempotence::PushSlice >(); add< Idempotence::PushSliceInitiallyInexistent >(); add< Idempotence::PushSliceToZero >(); @@ -1657,12 +1651,6 @@ namespace ReplTests { add< Idempotence::SingletonNoRename >(); add< Idempotence::IndexedSingletonNoRename >(); add< Idempotence::AddToSetEmptyMissing >(); - - // The new update framework does not allow field names with a leading '$' to be - // added to a set. - if (!isNewUpdateFrameworkEnabled()) - add< Idempotence::AddToSetWithDollarSigns >(); - add< Idempotence::ReplaySetPreexistingNoOpPull >(); add< Idempotence::ReplayArrayFieldNotAppended >(); add< DeleteOpIsIdBased >(); diff --git a/src/mongo/dbtests/updatetests.cpp b/src/mongo/dbtests/updatetests.cpp index 008bf5b46e7..7ef59c9d11c 100644 --- a/src/mongo/dbtests/updatetests.cpp +++ b/src/mongo/dbtests/updatetests.cpp @@ -26,7 +26,6 @@ #include "mongo/db/json.h" #include "mongo/db/lasterror.h" #include "mongo/db/ops/update.h" -#include "mongo/db/ops/update_internal.h" #include "mongo/dbtests/dbtests.h" namespace UpdateTests { @@ -1268,6 +1267,29 @@ namespace UpdateTests { } }; + namespace { + + /** + * Comparator between two BSONObjects that takes in consideration only the keys and + * direction described in the sort pattern. + * + * TODO: This was pulled from update_internal.h, we should verify that these tests work + * with the new update framework $push sorter. + */ + struct ProjectKeyCmp { + BSONObj sortPattern; + + ProjectKeyCmp( BSONObj pattern ) : sortPattern( pattern) {} + + int operator()( const BSONObj& left, const BSONObj& right ) const { + BSONObj keyLeft = left.extractFields( sortPattern, true ); + BSONObj keyRight = right.extractFields( sortPattern, true ); + return keyLeft.woCompare( keyRight, sortPattern ) < 0; + } + }; + + } // namespace + class PushSortSortMixed { public: void run() { @@ -1637,18 +1659,6 @@ namespace UpdateTests { } }; - class IndexModSet : public SetBase { - public: - void run() { - client().ensureIndex( ns(), BSON( "a.b" << 1 ) ); - client().insert( ns(), fromjson( "{'_id':0,a:{b:3}}" ) ); - client().update( ns(), Query(), fromjson( "{$set:{'a.b':4}}" ) ); - ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), Query() ) ); - ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), fromjson( "{'a.b':4}" ) ) ); // make sure the index works - } - }; - - class PreserveIdWithIndex : public SetBase { // Not using $set, but base class is still useful public: void run() { @@ -1718,748 +1728,6 @@ namespace UpdateTests { mutablebson::unordered( client().findOne( ns(), BSONObj() ) ) ); } }; - - namespace ModSetTests { - - class internal1 { - public: - void run() { - BSONObj b = BSON( "$inc" << BSON( "x" << 1 << "a.b" << 1 ) ); - ModSet m(b); - - ASSERT( m.haveModForField( "x" ) ); - ASSERT( m.haveModForField( "a.b" ) ); - ASSERT( ! m.haveModForField( "y" ) ); - ASSERT( ! m.haveModForField( "a.c" ) ); - ASSERT( ! m.haveModForField( "a" ) ); - - ASSERT( m.haveConflictingMod( "x" ) ); - ASSERT( m.haveConflictingMod( "a" ) ); - ASSERT( m.haveConflictingMod( "a.b" ) ); - ASSERT( ! m.haveConflictingMod( "a.bc" ) ); - ASSERT( ! m.haveConflictingMod( "a.c" ) ); - ASSERT( ! m.haveConflictingMod( "a.a" ) ); - } - }; - - class Base { - public: - - virtual ~Base() {} - - - void test( BSONObj morig , BSONObj in , BSONObj wanted ) { - BSONObj m = morig.copy(); - ModSet set(m); - - BSONObj out = set.prepare(in)->createNewFromMods(); - ASSERT_EQUALS( wanted , out ); - } - }; - - class inc1 : public Base { - public: - void run() { - BSONObj m = BSON( "$inc" << BSON( "x" << 1 ) ); - test( m , BSON( "x" << 5 ) , BSON( "x" << 6 ) ); - test( m , BSON( "a" << 5 ) , BSON( "a" << 5 << "x" << 1 ) ); - test( m , BSON( "z" << 5 ) , BSON( "x" << 1 << "z" << 5 ) ); - } - }; - - class inc2 : public Base { - public: - void run() { - BSONObj m = BSON( "$inc" << BSON( "a.b" << 1 ) ); - test( m , BSONObj() , BSON( "a" << BSON( "b" << 1 ) ) ); - test( m , BSON( "a" << BSON( "b" << 2 ) ) , BSON( "a" << BSON( "b" << 3 ) ) ); - - m = BSON( "$inc" << BSON( "a.b" << 1 << "a.c" << 1 ) ); - test( m , BSONObj() , BSON( "a" << BSON( "b" << 1 << "c" << 1 ) ) ); - - - } - }; - - class set1 : public Base { - public: - void run() { - test( BSON( "$set" << BSON( "x" << 17 ) ) , BSONObj() , BSON( "x" << 17 ) ); - test( BSON( "$set" << BSON( "x" << 17 ) ) , BSON( "x" << 5 ) , BSON( "x" << 17 ) ); - - test( BSON( "$set" << BSON( "x.a" << 17 ) ) , BSON( "z" << 5 ) , BSON( "x" << BSON( "a" << 17 )<< "z" << 5 ) ); - } - }; - - class push1 : public Base { - public: - void run() { - test( BSON( "$push" << BSON( "a" << 5 ) ) , fromjson( "{a:[1]}" ) , fromjson( "{a:[1,5]}" ) ); - } - }; - - - class IncRewriteInPlace { - public: - void run() { - BSONObj obj = BSON( "a" << 2 ); - BSONObj mod = BSON( "$inc" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_TRUE( modSetState->canApplyInPlace() ); - modSetState->applyModsInPlace(false); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 3 ) ), modSetState->getOpLogRewrite() ); - } - }; - - // Check if not applying in place changes anything. - class InRewriteForceNotInPlace { - public: - void run() { - BSONObj obj = BSON( "a" << 2 ); - BSONObj mod = BSON( "$inc" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 3 ) ), modSetState->getOpLogRewrite() ); - } - }; - - class IncRewriteNestedArray { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 2 ) ); - BSONObj mod = BSON( "$inc" << BSON( "a.0" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_TRUE( modSetState->canApplyInPlace() ); - modSetState->applyModsInPlace(false); - ASSERT_EQUALS( BSON( "$set" << BSON( "a.0" << 3 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class IncRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << 2 ); - BSONObj mod = BSON( "$inc" << BSON( "a" << 1 ) << "$set" << BSON( "b" << 2) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 3 << "b" << 2)), - modSetState->getOpLogRewrite() ); - } - }; - - class IncRewriteNonExistingField { - public: - void run() { - BSONObj obj = BSON( "c" << 1 ); - BSONObj mod = BSON( "$inc" << BSON( "a" << 1 ) << "$set" << BSON( "b" << 2) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 1 << "b" << 2)), - modSetState->getOpLogRewrite() ); - } - }; - - // A no-op $setOnInsert would not interfere with in-placeness and won't log. - class SetOnInsertRewriteInPlace { - public: - void run() { - BSONObj obj = BSON( "a" << 2 ); - BSONObj mod = BSON( "$setOnInsert" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_TRUE( modSetState->canApplyInPlace() ); - modSetState->applyModsInPlace(false); - ASSERT_EQUALS( BSONObj(), modSetState->getOpLogRewrite() ); - } - }; - - // A no-op $setOnInsert that was forced not in-place doesn't log. - class SetOnInsertRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << 2 ); - BSONObj mod = BSON( "$setOnInsert" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - // force not in place - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSONObj(), modSetState->getOpLogRewrite() ); - } - }; - - // Push is never applied in place - class PushRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); - BSONObj mod = BSON( "$push" << BSON( "a" << 3 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a.2" << 3 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PushSliceRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); - // { $push : { a : { $each : [ 3 ] , $slice :-2 } } } - BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 3 ) << "$slice" << -2 ); - BSONObj mod = BSON( "$push" << BSON( "a" << pushObj ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 2 << 3 ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PushSortRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "x" << BSON_ARRAY( BSON( "a" << 1 ) << - BSON( "a" << 2 ) ) ); - // { $push : { a : { $each : [ {a:3} ], $slice:-2, $sort : {a:1} } } } - BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << - "$slice" << -2 << - "$sort" << BSON( "a" << 1 ) ); - BSONObj mod = BSON( "$push" << BSON( "x" << pushObj ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "x" << - BSON_ARRAY( BSON( "a" << 2 ) << - BSON( "a" << 3 ) ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PushRewriteNonExistingField { - public: - void run() { - BSONObj obj = BSON( "b" << 1 ); - BSONObj mod = BSON( "$push" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PushSliceRewriteNonExistingField { - public: - void run() { - BSONObj obj = BSON( "b" << 1 ); - // { $push : { a : { $each : [ 1 , 2 ] , $slice:-2 } } } - BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 1 << 2) << "$slice" << -2 ); - BSONObj mod = BSON( "$push" << BSON( "a" << pushObj ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PushSliceRewriteNested { - public: - void run() { - BSONObj obj = fromjson( "{ a:{ b:[ 1, 2 ] } }" ); - BSONObj mod = fromjson( "{ $push: { 'a.b': { $each: [3] , $slice:-2 } } }" ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a.b" << BSON_ARRAY( 2 << 3 ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PushSortRewriteNonExistingField { - public: - void run() { - BSONObj obj = BSON( "b" << 1 ); - // { $push : { x : { $each : [ {a:1},{a:2} ], $slice:-2, $sort : {a:1} } } } - BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 1 ) << - BSON( "a" << 2 ) ) << - "$slice" << -2 << - "$sort" << BSON( "a" << 1 ) ); - BSONObj mod = BSON( "$push" << BSON( "x" << pushObj ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "x" << BSON_ARRAY( BSON( "a" << 1 ) << - BSON( "a" << 2 ) ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PushAllRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); - BSONObj modAll = BSON( "$pushAll" << BSON( "a" << BSON_ARRAY( 3 << 4 << 5 ) ) ); - ModSet modSetAll( modAll ); - auto_ptr<ModSetState> modSetStateAll = modSetAll.prepare( obj ); - ASSERT_FALSE( modSetStateAll->canApplyInPlace() ); - modSetStateAll->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2 << 3 << 4 << 5) ) ), - modSetStateAll->getOpLogRewrite() ); - } - }; - - class PushAllRewriteNonExistingField { - public: - void run() { - BSONObj obj = BSON( "b" << 1 ); - BSONObj modAll = BSON( "$pushAll" << BSON( "a" << BSON_ARRAY( 1 << 2 << 3) ) ); - ModSet modSetAll( modAll ); - auto_ptr<ModSetState> modSetStateAll = modSetAll.prepare( obj ); - ASSERT_FALSE( modSetStateAll->canApplyInPlace() ); - modSetStateAll->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2 << 3 ) ) ), - modSetStateAll->getOpLogRewrite() ); - } - }; - - // Pull is only in place if it's a no-op. - class PullRewriteInPlace { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); - BSONObj modMatcher = BSON( "$pull" << BSON( "a" << BSON( "$gt" << 3 ) ) ); - ModSet modSetMatcher( modMatcher ); - auto_ptr<ModSetState> modSetStateMatcher = modSetMatcher.prepare( obj ); - ASSERT_TRUE( modSetStateMatcher->canApplyInPlace() ); - modSetStateMatcher->applyModsInPlace(false); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2) ) ), - modSetStateMatcher->getOpLogRewrite() ); - } - }; - - class PullRewriteForceNotInPlace { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); - BSONObj modMatcher = BSON( "$pull" << BSON( "a" << BSON( "$gt" << 3 ) ) ); - ModSet modSetMatcher( modMatcher ); - auto_ptr<ModSetState> modSetStateMatcher = modSetMatcher.prepare( obj ); - modSetStateMatcher->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2) ) ), - modSetStateMatcher->getOpLogRewrite() ); - } - }; - - class PullRewriteNonExistingUnsets { - public: - void run() { - BSONObj obj; - BSONObj modMatcher = BSON( "$pull" << BSON( "a" << BSON( "$gt" << 3 ) ) ); - ModSet modSetMatcher( modMatcher ); - auto_ptr<ModSetState> modSetStateMatcher = modSetMatcher.prepare( obj ); - ASSERT_FALSE( modSetStateMatcher->canApplyInPlace() ); - modSetStateMatcher->createNewFromMods(); - ASSERT_EQUALS( BSON( "$unset" << BSON( "a" << 1 ) ), - modSetStateMatcher->getOpLogRewrite() ); - } - }; - - class PullRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); - BSONObj mod = BSON( "$pull" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 2 ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PullRewriteLastExistingField { - public: - void run() { - // check last pull corner case - BSONObj obj = BSON( "a" << BSON_ARRAY( 2 ) ); - BSONObj mod = BSON( "$pull" << BSON( "a" << 2 ) ); - ModSet modSetLast( mod ); - auto_ptr<ModSetState> modSetStateLast = modSetLast.prepare( obj ); - ASSERT_FALSE( modSetStateLast->canApplyInPlace() ); - modSetStateLast->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSONArray() ) ), - modSetStateLast->getOpLogRewrite() ); - } - }; - - class PullRewriteNonExistingField { - public: - void run() { - BSONObj obj = BSON( "b" << 1 ); - BSONObj mod = BSON( "$pull" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$unset" << BSON( "a" << 1 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class TwoNestedPulls { - public: - void run() { - BSONObj obj = fromjson( "{ a:{ b:[ 1, 2 ], c:[ 1, 2 ] } }" ); - BSONObj mod = fromjson( "{ $pull:{ 'a.b':2, 'a.c':2 } }" ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( fromjson( "{ $set:{ 'a.b':[ 1 ] , 'a.c':[ 1 ] } }" ), - modSetState->getOpLogRewrite() ); - } - }; - - // Pop is only applied in place if the target array remains the same size (i.e. if - // it is empty already. - class PopRewriteEmptyArray { - public: - void run() { - BSONObj obj = BSON( "a" << BSONArray() ); - BSONObj mod = BSON( "$pop" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_TRUE( modSetState->canApplyInPlace() ); - modSetState->applyModsInPlace(false); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSONArray() ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PopRewriteLastElement { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 ) ); - BSONObj mod = BSON( "$pop" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSONArray() ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PopRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2) ); - BSONObj mod = BSON( "$pop" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class PopRewriteNonExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 ) ); - BSONObj mod = BSON( "$pop" << BSON( "b" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$unset" << BSON( "b" << 1 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - // AddToSet is in place if it is a no-op. - class AddToSetRewriteInPlace { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); - BSONObj mod = BSON( "$addToSet" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_TRUE( modSetState->canApplyInPlace() ); - modSetState->applyModsInPlace(false); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class AddToSetRewriteForceNotInPlace { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); - BSONObj mod = BSON( "$addToSet" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a.0" << 1 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class AddToSetRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 ) ); - BSONObj mod = BSON( "$addToSet" << BSON( "a" << 2 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a.1" << 2 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class AddToSetRewriteNonExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << BSON_ARRAY( 1 ) ); - BSONObj mod = BSON( "$addToSet" << BSON( "b" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "b" << BSON_ARRAY( 1 ) ) ), - modSetState->getOpLogRewrite() ); - } - }; - - // Rename doesn't log if both fields are not present. - class RenameRewriteBothNonExistent { - public: - void run() { - BSONObj obj = BSON( "a" << 1 ); - BSONObj mod = BSON( "$rename" << BSON( "b" << "c" ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_TRUE( modSetState->canApplyInPlace() ); - modSetState->applyModsInPlace(false); - ASSERT_EQUALS( BSONObj(), modSetState->getOpLogRewrite() ); - } - }; - - class RenameRewriteExistingToField { - public: - void run() { - BSONObj obj = BSON( "b" << 100 ); - BSONObj mod = BSON( "$rename" << BSON( "a" << "b" ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_TRUE( modSetState->canApplyInPlace() ); - modSetState->applyModsInPlace(false); - ASSERT_EQUALS( BSONObj(), modSetState->getOpLogRewrite() ); - } - }; - - class RenameRewriteExistingFromField { - public: - void run() { - BSONObj obj = BSON( "a" << 100 ); - BSONObj mod = BSON( "$rename" << BSON( "a" << "b" ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "b" << 100 ) << "$unset" << BSON ( "a" << 1 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class RenameRewriteBothExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << 100 << "b" << 200); - BSONObj mod = BSON( "$rename" << BSON( "a" << "b" ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "b" << 100 ) << "$unset" << BSON ( "a" << 1 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - // $bit is never applied in place currently - class BitRewriteExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << 0 ); - BSONObj mod = BSON( "$bit" << BSON( "a" << BSON( "or" << 1 ) ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 1 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class BitRewriteNonExistingField { - public: - void run() { - BSONObj obj = BSON( "a" << 0 ); - BSONObj mod = BSON( "$bit" << BSON( "b" << BSON( "or" << 1 ) ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "b" << 1 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class SetIsNotRewritten { - public: - void run() { - BSONObj obj = BSON( "a" << 0 ); - BSONObj mod = BSON( "$set" << BSON( "b" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$set" << BSON( "b" << 1 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class UnsetIsNotRewritten { - public: - void run() { - BSONObj obj = BSON( "a" << 0 ); - BSONObj mod = BSON( "$unset" << BSON( "a" << 1 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_FALSE( modSetState->canApplyInPlace() ); - modSetState->createNewFromMods(); - ASSERT_EQUALS( BSON( "$unset" << BSON( "a" << 1 ) ), - modSetState->getOpLogRewrite() ); - } - }; - - class MultiSets { - public: - void run() { - BSONObj obj = BSON( "_id" << 1 << "a" << 1 << "b" << 1 ); - BSONObj mod = BSON( "$set" << BSON( "a" << 2 << "b" << 2 ) ); - ModSet modSet( mod ); - auto_ptr<ModSetState> modSetState = modSet.prepare( obj ); - ASSERT_TRUE( modSetState->canApplyInPlace() ); - ASSERT_EQUALS( mod, modSetState->getOpLogRewrite() ); - } - }; - - class PositionalWithoutElemMatchKey { - public: - void run() { - BSONObj querySpec = BSONObj(); - BSONObj modSpec = BSON( "$set" << BSON( "a.$" << 1 ) ); - ModSet modSet( modSpec ); - - // A positional operator must be replaced with an array index before calling - // prepare(). - ASSERT_THROWS( modSet.prepare( querySpec ), UserException ); - } - }; - - class PositionalWithoutNestedElemMatchKey { - public: - void run() { - BSONObj querySpec = BSONObj(); - BSONObj modSpec = BSON( "$set" << BSON( "a.b.c.$.e.f" << 1 ) ); - ModSet modSet( modSpec ); - - // A positional operator must be replaced with an array index before calling - // prepare(). - ASSERT_THROWS( modSet.prepare( querySpec ), UserException ); - } - }; - - class DbrefPassesPositionalValidation { - public: - void run() { - BSONObj querySpec = BSONObj(); - BSONObj modSpec = BSON( "$set" << BSON( "a.$ref" << "foo" << "a.$id" << 0 ) ); - ModSet modSet( modSpec ); - - // A positional operator must be replaced with an array index before calling - // prepare(), but $ prefixed fields encoding dbrefs are allowed. - modSet.prepare( querySpec ); // Does not throw. - } - }; - - class NoPositionalValidationOnReplication { - public: - void run() { - BSONObj querySpec = BSONObj(); - BSONObj modSpec = BSON( "$set" << BSON( "a.$" << 1 ) ); - ModSet modSet( modSpec, IndexPathSet(), true ); - - // No positional operator validation is performed if a ModSet is 'forReplication'. - modSet.prepare( querySpec ); // Does not throw. - } - }; - - class NoPositionalValidationOnPartialFixedArrayReplication { - public: - void run() { - BSONObj querySpec = BSONObj( BSON( "a.b" << 1 ) ); - BSONObj modSpec = BSON( "$set" << BSON( "a.$.b.$" << 1 ) ); - ModSet modSet( modSpec, IndexPathSet(), true ); - - // Attempt to fix the positional operator fields. - scoped_ptr<ModSet> fixedMods( modSet.fixDynamicArray( "0" ) ); - - // The first positional field is replaced, but the second is not (until SERVER-831 - // is implemented). - ASSERT( fixedMods->haveModForField( "a.0.b.$" ) ); - - // No positional operator validation is performed if a ModSet is 'forReplication', - // even after an attempt to fix the positional operator fields. - fixedMods->prepare( querySpec ); // Does not throw. - } - }; - - class CreateNewFromQueryExcludeNot { - public: - void run() { - BSONObj querySpec = BSON( "a" << BSON( "$not" << BSON( "$lt" << 1 ) ) ); - BSONObj modSpec = BSON( "$set" << BSON( "b" << 1 ) ); - ModSet modSet( modSpec ); - - // Because a $not operator is applied to the 'a' field, the 'a' field is excluded - // from the resulting document. - ASSERT_EQUALS( BSON( "b" << 1 ), modSet.createNewFromQuery( querySpec ) ); - } - }; - }; namespace basic { class Base : public ClientBase { @@ -2768,68 +2036,12 @@ namespace UpdateTests { add< DontDropEmpty >(); add< InsertInEmpty >(); add< IndexParentOfMod >(); - add< IndexModSet >(); add< PreserveIdWithIndex >(); add< CheckNoMods >(); add< UpdateMissingToNull >(); add< TwoModsWithinDuplicatedField >(); add< ThreeModsWithinDuplicatedField >(); add< TwoModsBeforeExistingField >(); - - add< ModSetTests::internal1 >(); - add< ModSetTests::inc1 >(); - add< ModSetTests::inc2 >(); - add< ModSetTests::set1 >(); - add< ModSetTests::push1 >(); - - add< ModSetTests::IncRewriteInPlace >(); - add< ModSetTests::InRewriteForceNotInPlace >(); - add< ModSetTests::IncRewriteNestedArray >(); - add< ModSetTests::IncRewriteExistingField >(); - add< ModSetTests::IncRewriteNonExistingField >(); - add< ModSetTests::SetOnInsertRewriteInPlace >(); - add< ModSetTests::SetOnInsertRewriteExistingField >(); - add< ModSetTests::PushRewriteExistingField >(); - add< ModSetTests::PushSliceRewriteExistingField >(); - add< ModSetTests::PushSortRewriteExistingField >(); - add< ModSetTests::PushRewriteNonExistingField >(); - add< ModSetTests::PushSliceRewriteNonExistingField >(); - add< ModSetTests::PushSliceRewriteNested >(); - add< ModSetTests::PushSortRewriteNonExistingField >(); - add< ModSetTests::PushAllRewriteExistingField >(); - add< ModSetTests::PushAllRewriteNonExistingField >(); - add< ModSetTests::PullRewriteInPlace >(); - add< ModSetTests::PullRewriteForceNotInPlace >(); - add< ModSetTests::PullRewriteNonExistingUnsets >(); - add< ModSetTests::PullRewriteExistingField >(); - add< ModSetTests::PullRewriteLastExistingField >(); - add< ModSetTests::PullRewriteNonExistingField >(); - add< ModSetTests::TwoNestedPulls >(); - add< ModSetTests::PopRewriteEmptyArray >(); - add< ModSetTests::PopRewriteLastElement >(); - add< ModSetTests::PopRewriteExistingField >(); - add< ModSetTests::PopRewriteNonExistingField >(); - add< ModSetTests::AddToSetRewriteInPlace >(); - add< ModSetTests::AddToSetRewriteForceNotInPlace >(); - add< ModSetTests::AddToSetRewriteExistingField >(); - add< ModSetTests::AddToSetRewriteNonExistingField >(); - add< ModSetTests::RenameRewriteBothNonExistent >(); - add< ModSetTests::RenameRewriteExistingToField >(); - add< ModSetTests::RenameRewriteExistingFromField >(); - add< ModSetTests::RenameRewriteBothExistingField >(); - add< ModSetTests::BitRewriteExistingField >(); - // XXX $bit over non-existing field is missing. Probably out of scope to fix it here. - // add< ModSetTests::BitRewriteNonExistingField >(); - add< ModSetTests::SetIsNotRewritten >(); - add< ModSetTests::UnsetIsNotRewritten >(); - add< ModSetTests::MultiSets >(); - add< ModSetTests::PositionalWithoutElemMatchKey >(); - add< ModSetTests::PositionalWithoutNestedElemMatchKey >(); - add< ModSetTests::DbrefPassesPositionalValidation >(); - add< ModSetTests::NoPositionalValidationOnReplication >(); - add< ModSetTests::NoPositionalValidationOnPartialFixedArrayReplication >(); - add< ModSetTests::CreateNewFromQueryExcludeNot >(); - add< basic::inc1 >(); add< basic::inc2 >(); add< basic::inc3 >(); |