summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbuildscripts/smoke.py2
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/db/dbhelpers.cpp32
-rw-r--r--src/mongo/db/instance.cpp165
-rw-r--r--src/mongo/db/ops/update.cpp561
-rw-r--r--src/mongo/db/ops/update.h48
-rw-r--r--src/mongo/db/ops/update_internal.cpp1496
-rw-r--r--src/mongo/db/ops/update_internal.h781
-rw-r--r--src/mongo/db/repl/rs_rollback.cpp35
-rw-r--r--src/mongo/dbtests/framework.cpp5
-rw-r--r--src/mongo/dbtests/repltests.cpp12
-rw-r--r--src/mongo/dbtests/updatetests.cpp834
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 >();