summaryrefslogtreecommitdiff
path: root/db/update.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'db/update.cpp')
-rw-r--r--db/update.cpp376
1 files changed, 295 insertions, 81 deletions
diff --git a/db/update.cpp b/db/update.cpp
index 6dcfc78..cbf93ba 100644
--- a/db/update.cpp
+++ b/db/update.cpp
@@ -16,13 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "stdafx.h"
+#include "pch.h"
#include "query.h"
#include "pdfile.h"
#include "jsobjmanipulator.h"
#include "queryoptimizer.h"
#include "repl.h"
#include "update.h"
+#include "btree.h"
//#define DEBUGUPDATE(x) cout << x << endl;
#define DEBUGUPDATE(x)
@@ -236,7 +237,7 @@ namespace mongo {
}
while( i.more() ) {
- bb.appendAs( i.next() , bb.numStr( n - 1 ).c_str() );
+ bb.appendAs( i.next() , bb.numStr( n - 1 ) );
n++;
}
}
@@ -306,8 +307,10 @@ namespace mongo {
}
auto_ptr<ModSetState> ModSet::prepare(const BSONObj &obj) const {
+ DEBUGUPDATE( "\t start prepare" );
ModSetState * mss = new ModSetState( obj );
-
+
+
// 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 );
@@ -407,9 +410,41 @@ namespace mongo {
mss->amIInPlacePossible( false );
}
}
+
+ DEBUGUPDATE( "\t mss\n" << mss->toString() << "\t--" );
return auto_ptr<ModSetState>( mss );
}
+
+ void ModState::appendForOpLog( BSONObjBuilder& b ) const {
+ if ( incType ){
+ DEBUGUPDATE( "\t\t\t\t\t appendForOpLog inc fieldname: " << m->fieldName << " short:" << m->shortFieldName );
+ BSONObjBuilder bb( b.subobjStart( "$set" ) );
+ appendIncValue( bb , true );
+ bb.done();
+ return;
+ }
+
+ const char * name = fixedOpName ? fixedOpName : Mod::modNames[op()];
+
+ DEBUGUPDATE( "\t\t\t\t\t appendForOpLog name:" << name << " fixed: " << fixed << " fn: " << m->fieldName );
+
+ BSONObjBuilder bb( b.subobjStart( name ) );
+ if ( fixed )
+ bb.appendAs( *fixed , m->fieldName );
+ else
+ bb.appendAs( m->elt , m->fieldName );
+ bb.done();
+ }
+
+ string ModState::toString() const {
+ stringstream ss;
+ if ( fixedOpName )
+ ss << " fixedOpName: " << fixedOpName;
+ if ( fixed )
+ ss << " fixed: " << fixed;
+ return ss.str();
+ }
void ModSetState::applyModsInPlace() {
for ( ModStateHolder::iterator i = _mods.begin(); i != _mods.end(); ++i ) {
@@ -492,7 +527,7 @@ namespace mongo {
string field = root + e.fieldName();
FieldCompareResult cmp = compareDottedFieldNames( m->second.m->fieldName , field );
- DEBUGUPDATE( "\t\t\t" << field << "\t" << m->second.m->fieldName << "\t" << cmp );
+ DEBUGUPDATE( "\t\t\t field:" << field << "\t mod:" << m->second.m->fieldName << "\t cmp:" << cmp );
switch ( cmp ){
@@ -518,15 +553,18 @@ namespace mongo {
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 , b , onedownseen );
m++;
continue;
case SAME:
+ DEBUGUPDATE( "\t\t\t\t applying mod on: " << m->second.m->fieldName );
m->second.apply( b , e );
e = es.next();
m++;
continue;
case RIGHT_BEFORE: // field that doesn't have a MOD
+ DEBUGUPDATE( "\t\t\t\t just copying" );
b.append( e ); // if array, ignore field name
e = es.next();
continue;
@@ -540,12 +578,14 @@ namespace mongo {
// finished looping the mods, just adding the rest of the elements
while ( e.type() ){
+ DEBUGUPDATE( "\t\t\t copying: " << e.fieldName() );
b.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 , b , onedownseen );
}
}
@@ -556,6 +596,14 @@ namespace mongo {
return 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();
+ }
+
BSONObj ModSet::createNewFromQuery( const BSONObj& query ){
BSONObj newObj;
@@ -565,6 +613,8 @@ namespace mongo {
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().firstElement().fieldName()[0] == '$' ){
// this means this is a $gt type filter, so don't make part of the new object
@@ -610,6 +660,7 @@ namespace mongo {
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 );
@@ -622,7 +673,7 @@ namespace mongo {
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 conflict mod" , ! haveConflictingMod( 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 ) );
@@ -639,7 +690,7 @@ namespace mongo {
_mods[m.fieldName] = m;
- DEBUGUPDATE( "\t\t " << fieldName << "\t" << _hasDynamicArray );
+ DEBUGUPDATE( "\t\t " << fieldName << "\t" << m.fieldName << "\t" << _hasDynamicArray );
}
}
@@ -677,54 +728,152 @@ namespace mongo {
}
}
- class UpdateOp : public QueryOp {
+ class UpdateOp : public MultiCursor::CursorOp {
public:
- UpdateOp() : _nscanned() {}
- virtual void init() {
- BSONObj pattern = qp().query();
- _c.reset( qp().newCursor().release() );
- if ( ! _c->ok() )
+ UpdateOp( bool hasPositionalField ) : _nscanned(), _hasPositionalField( hasPositionalField ){}
+ virtual void _init() {
+ _c = qp().newCursor();
+ if ( ! _c->ok() ) {
setComplete();
- else
- _matcher.reset( new CoveredIndexMatcher( pattern, qp().indexKey() ) );
+ }
}
+ virtual bool prepareToYield() {
+ if ( ! _cc ) {
+ _cc.reset( new ClientCursor( QueryOption_NoCursorTimeout , _c , qp().ns() ) );
+ }
+ return _cc->prepareToYield( _yieldData );
+ }
+ virtual void recoverFromYield() {
+ if ( !ClientCursor::recoverFromYield( _yieldData ) ) {
+ _c.reset();
+ _cc.reset();
+ massert( 13339, "cursor dropped during update", false );
+ }
+ }
virtual void next() {
if ( ! _c->ok() ) {
setComplete();
return;
}
_nscanned++;
- if ( _matcher->matches(_c->currKey(), _c->currLoc(), &_details ) ) {
+ if ( matcher()->matches(_c->currKey(), _c->currLoc(), &_details ) ) {
setComplete();
return;
}
_c->advance();
}
- bool curMatches(){
- return _matcher->matches(_c->currKey(), _c->currLoc() , &_details );
- }
+
virtual bool mayRecordPlan() const { return false; }
- virtual QueryOp *clone() const {
- return new UpdateOp();
+ virtual QueryOp *_createChild() const {
+ return new UpdateOp( _hasPositionalField );
}
- shared_ptr< Cursor > c() { return _c; }
- long long nscanned() const { return _nscanned; }
- MatchDetails& getMatchDetails(){ return _details; }
+ // already scanned to the first match, so return _c
+ virtual shared_ptr< Cursor > newCursor() const { return _c; }
+ virtual bool alwaysUseRecord() const { return _hasPositionalField; }
private:
shared_ptr< Cursor > _c;
long long _nscanned;
- auto_ptr< CoveredIndexMatcher > _matcher;
+ bool _hasPositionalField;
MatchDetails _details;
+ ClientCursor::CleanupPointer _cc;
+ ClientCursor::YieldData _yieldData;
};
-
- UpdateResult updateObjects(const char *ns, const BSONObj& updateobj, BSONObj patternOrig, bool upsert, bool multi, bool logop , OpDebug& debug ) {
+ static void checkTooLarge(const BSONObj& newObj) {
+ uassert( 12522 , "$ operator made object too large" , newObj.objsize() <= ( 4 * 1024 * 1024 ) );
+ }
+
+ /* 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, int profile, NamespaceDetails *d,
+ NamespaceDetailsTransient *nsdt,
+ bool god, const char *ns,
+ const BSONObj& updateobj, BSONObj patternOrig, bool logop, OpDebug& debug)
+ {
+ DiskLoc loc;
+ {
+ IndexDetails& i = d->idx(idIdxNo);
+ BSONObj key = i.getKeyFromQuery( patternOrig );
+ loc = i.head.btree()->findSingle(i, i.head, key);
+ if( loc.isNull() ) {
+ // no upsert support in _updateById yet, so we are done.
+ return UpdateResult(0, 0, 0);
+ }
+ }
+
+ Record *r = loc.rec();
+
+ /* 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 ) {
+ const BSONObj& onDisk = loc.obj();
+ auto_ptr<ModSetState> mss = mods->prepare( onDisk );
+
+ if( mss->canApplyInPlace() ) {
+ mss->applyModsInPlace();
+ DEBUGUPDATE( "\t\t\t updateById doing in place update" );
+ /*if ( profile )
+ ss << " fastmod "; */
+ }
+ else {
+ BSONObj newObj = mss->createNewFromMods();
+ checkTooLarge(newObj);
+ bool changedId;
+ assert(nsdt);
+ DiskLoc newLoc = theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , newObj.objdata(), newObj.objsize(), debug, changedId);
+ }
+
+ if ( logop ) {
+ DEV assert( mods->size() );
+
+ BSONObj pattern = patternOrig;
+ if ( mss->haveArrayDepMod() ) {
+ BSONObjBuilder patternBuilder;
+ patternBuilder.appendElements( pattern );
+ mss->appendSizeSpecForArrayDepMods( patternBuilder );
+ pattern = patternBuilder.obj();
+ }
+
+ if( mss->needOpLogRewrite() ) {
+ DEBUGUPDATE( "\t rewrite update: " << mss->getOpLogRewrite() );
+ logOp("u", ns, mss->getOpLogRewrite() , &pattern );
+ }
+ else {
+ logOp("u", ns, updateobj, &pattern );
+ }
+ }
+ return UpdateResult( 1 , 1 , 1);
+ } // end $operator update
+
+ // regular update
+ BSONElementManipulator::lookForTimestamps( updateobj );
+ checkNoMods( updateobj );
+ bool changedId = false;
+ assert(nsdt);
+ theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug, changedId);
+ if ( logop ) {
+ if ( !changedId ) {
+ logOp("u", ns, updateobj, &patternOrig );
+ } else {
+ logOp("d", ns, patternOrig );
+ logOp("i", ns, updateobj );
+ }
+ }
+ return UpdateResult( 1 , 0 , 1 );
+ }
+
+ UpdateResult _updateObjects(bool god, const char *ns, const BSONObj& updateobj, BSONObj patternOrig, bool upsert, bool multi, bool logop , OpDebug& debug, RemoveSaver* rs ) {
DEBUGUPDATE( "update: " << ns << " update: " << updateobj << " query: " << patternOrig << " upsert: " << upsert << " multi: " << multi );
- int profile = cc().database()->profile;
+ Client& client = cc();
+ int profile = client.database()->profile;
StringBuilder& ss = debug.str;
if ( logLevel > 2 )
- ss << " update: " << updateobj;
+ ss << " update: " << updateobj.toString();
/* idea with these here it to make them loop invariant for multi updates, and thus be a bit faster for that case */
/* NOTE: when yield() is added herein, these must be refreshed after each call to yield! */
@@ -732,12 +881,6 @@ namespace mongo {
NamespaceDetailsTransient *nsdt = &NamespaceDetailsTransient::get_w(ns);
/* end note */
- 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 , "cannot update system collection", legalClientSystemNS( ns , true ) );
- }
-
auto_ptr<ModSet> mods;
bool isOperatorUpdate = updateobj.firstElement().fieldName()[0] == '$';
int modsIsIndexed = false; // really the # of indexes
@@ -753,31 +896,63 @@ namespace mongo {
modsIsIndexed = mods->isIndexed();
}
+ if( !upsert && !multi && isSimpleIdQuery(patternOrig) && d && !modsIsIndexed ) {
+ int idxNo = d->findIdIndex();
+ if( idxNo >= 0 ) {
+ ss << " byid ";
+ return _updateById(isOperatorUpdate, idxNo, mods.get(), profile, d, nsdt, god, ns, updateobj, patternOrig, logop, debug);
+ }
+ }
+
set<DiskLoc> seenObjects;
- QueryPlanSet qps( ns, patternOrig, BSONObj() );
- UpdateOp original;
- shared_ptr< UpdateOp > u = qps.runOp( original );
- massert( 10401 , u->exceptionMessage(), u->complete() );
- shared_ptr< Cursor > c = u->c();
int numModded = 0;
+ long long nscanned = 0;
+ MatchDetails details;
+ shared_ptr< MultiCursor::CursorOp > opPtr( new UpdateOp( mods.get() && mods->hasDynamicArray() ) );
+ shared_ptr< MultiCursor > c( new MultiCursor( ns, patternOrig, BSONObj(), opPtr, true ) );
+
+ auto_ptr<ClientCursor> cc;
+
while ( c->ok() ) {
- if ( numModded > 0 && ! u->curMatches() ){
+ nscanned++;
+
+ bool atomic = c->matcher()->docMatcher().atomic();
+
+ // May have already matched in UpdateOp, but do again to get details set correctly
+ if ( ! c->matcher()->matches( c->currKey(), c->currLoc(), &details ) ){
c->advance();
+
+ if ( nscanned % 256 == 0 && ! atomic ){
+ if ( cc.get() == 0 ) {
+ shared_ptr< Cursor > cPtr = c;
+ cc.reset( new ClientCursor( QueryOption_NoCursorTimeout , cPtr , ns ) );
+ }
+ if ( ! cc->yield() ){
+ cc.release();
+ // TODO should we assert or something?
+ break;
+ }
+ if ( !c->ok() ) {
+ break;
+ }
+ }
continue;
}
+
Record *r = c->_current();
DiskLoc loc = c->currLoc();
-
+
+ // TODO Maybe this is unnecessary since we have seenObjects
if ( c->getsetdup( loc ) ){
c->advance();
continue;
}
-
+
BSONObj js(r);
-
+
BSONObj pattern = patternOrig;
-
+
if ( logop ) {
BSONObjBuilder idPattern;
BSONElement id;
@@ -793,43 +968,47 @@ namespace mongo {
uassert( 10157 , "multi-update requires all modified objects to have an _id" , ! multi );
}
}
-
+
if ( profile )
- ss << " nscanned:" << u->nscanned();
-
+ ss << " nscanned:" << nscanned;
+
/* 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. */
+ regular ones at the moment. */
if ( isOperatorUpdate ) {
-
+
if ( multi ){
c->advance(); // go to next record in case this one moves
if ( seenObjects.count( loc ) )
continue;
}
-
+
const BSONObj& onDisk = loc.obj();
-
+
ModSet * useMods = mods.get();
bool forceRewrite = false;
-
+
auto_ptr<ModSet> mymodset;
- if ( u->getMatchDetails().elemMatchKey && mods->hasDynamicArray() ){
- useMods = mods->fixDynamicArray( u->getMatchDetails().elemMatchKey );
+ if ( details.elemMatchKey && mods->hasDynamicArray() ){
+ useMods = mods->fixDynamicArray( details.elemMatchKey );
mymodset.reset( useMods );
forceRewrite = true;
}
-
-
+
auto_ptr<ModSetState> mss = useMods->prepare( onDisk );
-
+
bool indexHack = multi && ( modsIsIndexed || ! mss->canApplyInPlace() );
-
- if ( indexHack )
- c->noteLocation();
-
+
+ if ( indexHack ){
+ if ( cc.get() )
+ cc->updateLocation();
+ else
+ c->noteLocation();
+ }
+
if ( modsIsIndexed <= 0 && mss->canApplyInPlace() ){
mss->applyModsInPlace();// const_cast<BSONObj&>(onDisk) );
-
+
+ DEBUGUPDATE( "\t\t\t doing in place update" );
if ( profile )
ss << " fastmod ";
@@ -838,26 +1017,30 @@ namespace mongo {
}
}
else {
+ if ( rs )
+ rs->goingToDelete( onDisk );
+
BSONObj newObj = mss->createNewFromMods();
- uassert( 12522 , "$ operator made object too large" , newObj.objsize() <= ( 4 * 1024 * 1024 ) );
- DiskLoc newLoc = theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , newObj.objdata(), newObj.objsize(), debug);
+ checkTooLarge(newObj);
+ bool changedId;
+ DiskLoc newLoc = theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , newObj.objdata(), newObj.objsize(), debug, changedId);
if ( newLoc != loc || modsIsIndexed ) {
// object moved, need to make sure we don' get again
seenObjects.insert( newLoc );
}
}
-
+
if ( logop ) {
DEV assert( mods->size() );
-
+
if ( mss->haveArrayDepMod() ) {
BSONObjBuilder patternBuilder;
patternBuilder.appendElements( pattern );
mss->appendSizeSpecForArrayDepMods( patternBuilder );
pattern = patternBuilder.obj();
}
-
+
if ( forceRewrite || mss->needOpLogRewrite() ){
DEBUGUPDATE( "\t rewrite update: " << mss->getOpLogRewrite() );
logOp("u", ns, mss->getOpLogRewrite() , &pattern );
@@ -868,19 +1051,42 @@ namespace mongo {
}
numModded++;
if ( ! multi )
- break;
+ return UpdateResult( 1 , 1 , numModded );
if ( indexHack )
c->checkLocation();
+
+ if ( nscanned % 64 == 0 && ! atomic ){
+ if ( cc.get() == 0 ) {
+ shared_ptr< Cursor > cPtr = c;
+ cc.reset( new ClientCursor( QueryOption_NoCursorTimeout , cPtr , ns ) );
+ }
+ if ( ! cc->yield() ){
+ cc.release();
+ break;
+ }
+ if ( !c->ok() ) {
+ break;
+ }
+ }
+
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);
- if ( logop )
- logOp("u", ns, updateobj, &pattern );
+ bool changedId = false;
+ theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug, changedId, god);
+ if ( logop ) {
+ DEV if( god ) log() << "REALLY??" << endl; // god doesn't get logged, this would be bad.
+ if ( !changedId ) {
+ logOp("u", ns, updateobj, &pattern );
+ } else {
+ logOp("d", ns, pattern );
+ logOp("i", ns, updateobj );
+ }
+ }
return UpdateResult( 1 , 0 , 1 );
}
@@ -889,7 +1095,7 @@ namespace mongo {
if ( profile )
- ss << " nscanned:" << u->nscanned();
+ ss << " nscanned:" << nscanned;
if ( upsert ) {
if ( updateobj.firstElement().fieldName()[0] == '$' ) {
@@ -897,24 +1103,32 @@ namespace mongo {
BSONObj newObj = mods->createNewFromQuery( patternOrig );
if ( profile )
ss << " fastmodinsert ";
- theDataFileMgr.insert(ns, newObj);
- if ( profile )
- ss << " fastmodinsert ";
+ theDataFileMgr.insertWithObjMod(ns, newObj, god);
if ( logop )
logOp( "i", ns, newObj );
- return UpdateResult( 0 , 1 , 1 );
+
+ return UpdateResult( 0 , 1 , 1 , newObj );
}
uassert( 10159 , "multi update only works with $ operators" , ! multi );
checkNoMods( updateobj );
if ( profile )
ss << " upsert ";
BSONObj no = updateobj;
- theDataFileMgr.insert(ns, no);
+ theDataFileMgr.insertWithObjMod(ns, no, god);
if ( logop )
logOp( "i", ns, no );
- return UpdateResult( 0 , 0 , 1 );
+ return UpdateResult( 0 , 0 , 1 , no );
}
return UpdateResult( 0 , 0 , 0 );
}
-
+
+ UpdateResult updateObjects(const char *ns, const BSONObj& updateobj, BSONObj patternOrig, bool upsert, bool multi, bool logop , OpDebug& debug ) {
+ 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 , "cannot update system collection", legalClientSystemNS( ns , true ) );
+ }
+ return _updateObjects(false, ns, updateobj, patternOrig, upsert, multi, logop, debug);
+ }
+
}