diff options
Diffstat (limited to 'db/update.cpp')
-rw-r--r-- | db/update.cpp | 617 |
1 files changed, 381 insertions, 236 deletions
diff --git a/db/update.cpp b/db/update.cpp index e178e0f..7de9bb1 100644 --- a/db/update.cpp +++ b/db/update.cpp @@ -31,21 +31,25 @@ namespace mongo { const char* Mod::modNames[] = { "$inc", "$set", "$push", "$pushAll", "$pull", "$pullAll" , "$pop", "$unset" , - "$bitand" , "$bitor" , "$bit" , "$addToSet" }; + "$bitand" , "$bitor" , "$bit" , "$addToSet", "$rename", "$rename" + }; unsigned Mod::modNamesNum = sizeof(Mod::modNames)/sizeof(char*); bool Mod::_pullElementMatch( BSONElement& toMatch ) const { - - if ( elt.type() != Object ){ + + if ( elt.type() != Object ) { // if elt isn't an object, then comparison will work return toMatch.woCompare( elt , false ) == 0; } - if ( toMatch.type() != Object ){ + 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() ); } @@ -54,41 +58,53 @@ namespace mongo { void Mod::appendIncremented( Builder& bb , const BSONElement& in, ModState& ms ) const { BSONType a = in.type(); BSONType b = elt.type(); - - if ( a == NumberDouble || b == NumberDouble ){ + + if ( a == NumberDouble || b == NumberDouble ) { ms.incType = NumberDouble; ms.incdouble = elt.numberDouble() + in.numberDouble(); } - else if ( a == NumberLong || b == NumberLong ){ + else if ( a == NumberLong || b == NumberLong ) { ms.incType = NumberLong; ms.inclong = elt.numberLong() + in.numberLong(); } else { - ms.incType = NumberInt; - ms.incint = elt.numberInt() + in.numberInt(); + 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( bb , false ); } template< class Builder > void appendUnset( Builder &b ) { } - + template<> void appendUnset( BSONArrayBuilder &b ) { b.appendNull(); } - + template< class Builder > void Mod::apply( Builder& b , BSONElement in , ModState& ms ) const { - switch ( op ){ - + if ( ms.dontApply ) { + return; + } + + switch ( op ) { + case INC: { appendIncremented( b , in , ms ); break; } - + case SET: { _checkForAppending( elt ); b.appendAs( elt , shortFieldName ); @@ -99,13 +115,13 @@ namespace mongo { appendUnset( b ); break; } - + case PUSH: { uassert( 10131 , "$push can only be applied to an array" , in.type() == Array ); BSONObjBuilder bb( b.subarrayStart( shortFieldName ) ); BSONObjIterator i( in.embeddedObject() ); int n=0; - while ( i.more() ){ + while ( i.more() ) { bb.append( i.next() ); n++; } @@ -116,28 +132,35 @@ namespace mongo { bb.done(); break; } - + case ADDTOSET: { uassert( 12592 , "$addToSet can only be applied to an array" , in.type() == Array ); BSONObjBuilder bb( b.subarrayStart( shortFieldName ) ); - + BSONObjIterator i( in.embeddedObject() ); - int n=0; + int n=0; + + if ( isEach() ) { - if ( isEach() ){ - BSONElementSet toadd; parseEach( toadd ); - - while ( i.more() ){ + + while ( i.more() ) { BSONElement cur = i.next(); bb.append( cur ); - n++; + n++; toadd.erase( cur ); } - - for ( BSONElementSet::iterator j=toadd.begin(); j!=toadd.end(); j++ ){ - bb.appendAs( *j , BSONObjBuilder::numStr( n++ ) ); + + { + BSONObjIterator i( getEach() ); + while ( i.more() ) { + BSONElement e = i.next(); + if ( toadd.count(e) ) { + bb.appendAs( e , BSONObjBuilder::numStr( n++ ) ); + toadd.erase( e ); + } + } } } @@ -145,34 +168,34 @@ namespace mongo { bool found = false; - while ( i.more() ){ + while ( i.more() ) { BSONElement cur = i.next(); bb.append( cur ); n++; if ( elt.woCompare( cur , false ) == 0 ) found = true; } - + if ( ! found ) bb.appendAs( elt , bb.numStr( n ) ); - + } - + bb.done(); 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() ); BSONObjBuilder bb( b.subarrayStart( shortFieldName ) ); - + BSONObjIterator i( in.embeddedObject() ); int n=0; - while ( i.more() ){ + while ( i.more() ) { bb.append( i.next() ); n++; } @@ -180,34 +203,34 @@ namespace mongo { ms.pushStartSize = n; i = BSONObjIterator( elt.embeddedObject() ); - while ( i.more() ){ + while ( i.more() ) { bb.appendAs( i.next() , bb.numStr( n++ ) ); } bb.done(); break; } - + case PULL: case PULL_ALL: { uassert( 10134 , "$pull/$pullAll can only be applied to an array" , in.type() == Array ); BSONObjBuilder bb( b.subarrayStart( shortFieldName ) ); - + int n = 0; BSONObjIterator i( in.embeddedObject() ); - while ( i.more() ){ + while ( i.more() ) { BSONElement e = i.next(); bool allowed = true; - if ( op == PULL ){ + if ( op == PULL ) { allowed = ! _pullElementMatch( e ); } else { BSONObjIterator j( elt.embeddedObject() ); while( j.more() ) { BSONElement arrJ = j.next(); - if ( e.woCompare( arrJ, false ) == 0 ){ + if ( e.woCompare( arrJ, false ) == 0 ) { allowed = false; break; } @@ -217,7 +240,7 @@ namespace mongo { if ( allowed ) bb.appendAs( e , bb.numStr( n++ ) ); } - + bb.done(); break; } @@ -225,13 +248,13 @@ namespace mongo { case POP: { uassert( 10135 , "$pop can only be applied to an array" , in.type() == Array ); BSONObjBuilder bb( b.subarrayStart( shortFieldName ) ); - + int n = 0; BSONObjIterator i( in.embeddedObject() ); - if ( elt.isNumber() && elt.number() < 0 ){ + if ( elt.isNumber() && elt.number() < 0 ) { // pop from front - if ( i.more() ){ + if ( i.more() ) { i.next(); n++; } @@ -246,7 +269,7 @@ namespace mongo { while( i.more() ) { n++; BSONElement arrI = i.next(); - if ( i.more() ){ + if ( i.more() ) { bb.append( arrI ); } } @@ -262,23 +285,23 @@ namespace mongo { uassert( 10136 , "$bit needs an array" , elt.type() == Object ); uassert( 10137 , "$bit can only be applied to numbers" , in.isNumber() ); uassert( 10138 , "$bit can't use a double" , in.type() != NumberDouble ); - + int x = in.numberInt(); long long y = in.numberLong(); BSONObjIterator it( elt.embeddedObject() ); - while ( it.more() ){ + while ( it.more() ) { BSONElement e = it.next(); uassert( 10139 , "$bit field must be number" , e.isNumber() ); - if ( strcmp( e.fieldName() , "and" ) == 0 ){ - switch( in.type() ){ + if ( strcmp( e.fieldName() , "and" ) == 0 ) { + switch( in.type() ) { case NumberInt: x = x&e.numberInt(); break; case NumberLong: y = y&e.numberLong(); break; default: assert( 0 ); } } - else if ( strcmp( e.fieldName() , "or" ) == 0 ){ - switch( in.type() ){ + else if ( strcmp( e.fieldName() , "or" ) == 0 ) { + switch( in.type() ) { case NumberInt: x = x|e.numberInt(); break; case NumberLong: y = y|e.numberLong(); break; default: assert( 0 ); @@ -289,8 +312,8 @@ namespace mongo { throw UserException( 9016, (string)"unknown bit mod:" + e.fieldName() ); } } - - switch( in.type() ){ + + switch( in.type() ) { case NumberInt: b.append( shortFieldName , x ); break; case NumberLong: b.append( shortFieldName , y ); break; default: assert( 0 ); @@ -299,6 +322,15 @@ namespace mongo { break; } + case RENAME_FROM: { + break; + } + + case RENAME_TO: { + ms.handleRename( b, shortFieldName ); + break; + } + default: stringstream ss; ss << "Mod::apply can't handle type: " << op; @@ -306,11 +338,30 @@ namespace mongo { } } + // -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) const { DEBUGUPDATE( "\t start prepare" ); - ModSetState * mss = new ModSetState( obj ); - - + auto_ptr<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 ); @@ -318,23 +369,51 @@ namespace mongo { const Mod& m = i->second; 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 ( 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() ) ){ + if ( mss->amIInPlacePossible( e.isNumber() ) ) { // check more typing info here - if ( m.elt.type() != e.type() ){ + if ( m.elt.type() != e.type() ) { // if i'm incrememnting with a double, then the storage has to be a double - mss->amIInPlacePossible( m.elt.type() != NumberDouble ); + 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; @@ -343,7 +422,7 @@ namespace mongo { mss->amIInPlacePossible( m.elt.type() == e.type() && m.elt.valuesize() == e.valuesize() ); break; - + case Mod::PUSH: case Mod::PUSH_ALL: uassert( 10141 , "Cannot apply $push/$pushAll modifier to non-array", e.type() == Array || e.eoo() ); @@ -358,7 +437,7 @@ namespace mongo { BSONElement arrI = i.next(); if ( m.op == Mod::PULL ) { mss->amIInPlacePossible( ! m._pullElementMatch( arrI ) ); - } + } else if ( m.op == Mod::PULL_ALL ) { BSONObjIterator j( m.elt.embeddedObject() ); while( mss->_inPlacePossible && j.moreWithEOO() ) { @@ -377,12 +456,12 @@ namespace mongo { 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() ){ + if ( m.isEach() ) { BSONElementSet toadd; m.parseEach( toadd ); while( i.more() ) { @@ -395,7 +474,7 @@ namespace mongo { bool found = false; while( i.more() ) { BSONElement arrI = i.next(); - if ( arrI.woCompare( m.elt , false ) == 0 ){ + if ( arrI.woCompare( m.elt , false ) == 0 ) { found = true; break; } @@ -404,7 +483,7 @@ namespace mongo { } break; } - + default: // mods we don't know about shouldn't be done in place mss->amIInPlacePossible( false ); @@ -412,28 +491,49 @@ namespace mongo { } DEBUGUPDATE( "\t mss\n" << mss->toString() << "\t--" ); - - return auto_ptr<ModSetState>( mss ); + + return mss; } void ModState::appendForOpLog( BSONObjBuilder& b ) const { - if ( incType ){ + if ( dontApply ) { + return; + } + + 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; } - + + if ( m->op == Mod::RENAME_FROM ) { + DEBUGUPDATE( "\t\t\t\t\t appendForOpLog RENAME_FROM fielName:" << m->fieldName ); + BSONObjBuilder bb( b.subobjStart( "$unset" ) ); + bb.append( m->fieldName, 1 ); + bb.done(); + return; + } + + if ( m->op == Mod::RENAME_TO ) { + DEBUGUPDATE( "\t\t\t\t\t appendForOpLog RENAME_TO fielName:" << m->fieldName ); + BSONObjBuilder bb( b.subobjStart( "$set" ) ); + 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 ); BSONObjBuilder bb( b.subobjStart( name ) ); - if ( fixed ) + if ( fixed ) { bb.appendAs( *fixed , m->fieldName ); - else + } + else { bb.appendAs( m->elt , m->fieldName ); + } bb.done(); } @@ -445,30 +545,55 @@ namespace mongo { ss << " fixed: " << fixed; return ss.str(); } - - void ModSetState::applyModsInPlace() { + + template< class Builder > + void ModState::handleRename( Builder &newObjBuilder, const char *shortFieldName ) { + newObjBuilder.appendAs( newVal , shortFieldName ); + BSONObjBuilder b; + b.appendAs( newVal, shortFieldName ); + assert( _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 assert( isOnDisk == ! _obj.isOwned() ); + for ( ModStateHolder::iterator i = _mods.begin(); i != _mods.end(); ++i ) { ModState& m = i->second; - - switch ( m.m->op ){ + + if ( m.dontApply ) { + continue; + } + + switch ( m.m->op ) { case Mod::UNSET: case Mod::PULL: case Mod::PULL_ALL: case Mod::ADDTOSET: + case Mod::RENAME_FROM: + case Mod::RENAME_TO: // this should have been handled by prepare break; - - // [dm] the BSONElementManipulator statements below are for replication (correct?) + // [dm] the BSONElementManipulator statements below are for replication (correct?) case Mod::INC: - m.m->incrementMe( m.old ); + if ( isOnDisk ) + m.m->IncrementMe( m.old ); + else + m.m->incrementMe( m.old ); m.fixedOpName = "$set"; m.fixed = &(m.old); break; case Mod::SET: - BSONElementManipulator( m.old ).replaceTypeAndValue( m.m->elt ); + if ( isOnDisk ) + BSONElementManipulator( m.old ).ReplaceTypeAndValue( m.m->elt ); + else + BSONElementManipulator( m.old ).replaceTypeAndValue( m.m->elt ); break; default: - uassert( 10144 , "can't apply mod in place - shouldn't have gotten here" , 0 ); + uassert( 13478 , "can't apply mod in place - shouldn't have gotten here" , 0 ); } } } @@ -488,61 +613,62 @@ namespace mongo { empty = false; } if ( empty ) - fields[ base + top.fieldName() ] = top; + fields[ base + top.fieldName() ] = top; } - + template< class Builder > - void ModSetState::_appendNewFromMods( const string& root , ModState& m , Builder& b , set<string>& onedownseen ){ + void ModSetState::_appendNewFromMods( const string& root , ModState& m , Builder& b , set<string>& onedownseen ) { const char * temp = m.fieldName(); temp += root.size(); const char * dot = strchr( temp , '.' ); - if ( dot ){ + if ( dot ) { string nr( m.fieldName() , 0 , 1 + ( dot - m.fieldName() ) ); string nf( temp , 0 , dot - temp ); if ( onedownseen.count( nf ) ) return; onedownseen.insert( nf ); - BSONObjBuilder bb ( b.subobjStart( nf.c_str() ) ); + BSONObjBuilder bb ( b.subobjStart( nf ) ); createNewFromMods( nr , bb , BSONObj() ); // don't infer an array from name bb.done(); } else { appendNewFromMod( m , b ); } - + } - + template< class Builder > - void ModSetState::createNewFromMods( const string& root , Builder& b , const BSONObj &obj ){ + void ModSetState::createNewFromMods( const string& root , Builder& b , const BSONObj &obj ) { DEBUGUPDATE( "\t\t createNewFromMods root: " << root ); BSONObjIteratorSorted es( obj ); BSONElement e = es.next(); - + ModStateHolder::iterator m = _mods.lower_bound( root ); StringBuilder buf(root.size() + 2 ); buf << root << (char)255; ModStateHolder::iterator mend = _mods.lower_bound( buf.str() ); - + set<string> onedownseen; - - while ( e.type() && m != mend ){ + + while ( e.type() && m != mend ) { string field = root + e.fieldName(); FieldCompareResult cmp = compareDottedFieldNames( m->second.m->fieldName , field ); DEBUGUPDATE( "\t\t\t field:" << field << "\t mod:" << m->second.m->fieldName << "\t cmp:" << cmp << "\t short: " << e.fieldName() ); - - switch ( cmp ){ - + + switch ( cmp ) { + case LEFT_SUBFIELD: { // Mod is embeddeed under this element - uassert( 10145 , "LEFT_SUBFIELD only supports Object" , e.type() == Object || e.type() == Array ); - if ( onedownseen.count( e.fieldName() ) == 0 ){ + uassert( 10145 , str::stream() << "LEFT_SUBFIELD only supports Object: " << field << " not: " << e.type() , e.type() == Object || e.type() == Array ); + if ( onedownseen.count( e.fieldName() ) == 0 ) { onedownseen.insert( e.fieldName() ); if ( e.type() == Object ) { BSONObjBuilder bb( b.subobjStart( e.fieldName() ) ); stringstream nr; nr << root << e.fieldName() << "."; createNewFromMods( nr.str() , bb , e.embeddedObject() ); - bb.done(); - } else { + bb.done(); + } + else { BSONArrayBuilder ba( b.subarrayStart( e.fieldName() ) ); stringstream nr; nr << root << e.fieldName() << "."; createNewFromMods( nr.str() , ba , e.embeddedObject() ); @@ -578,22 +704,22 @@ namespace mongo { e = es.next(); continue; case RIGHT_SUBFIELD: - massert( 10399 , "ModSet::createNewFromMods - RIGHT_SUBFIELD should be impossible" , 0 ); + 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.type() ){ + 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++ ){ + for ( ; m != mend; m++ ) { DEBUGUPDATE( "\t\t\t\t appending from mod at end: " << m->second.m->fieldName ); _appendNewFromMods( root , m->second , b , onedownseen ); } @@ -602,30 +728,30 @@ namespace mongo { BSONObj ModSetState::createNewFromMods() { BSONObjBuilder b( (int)(_obj.objsize() * 1.1) ); createNewFromMods( "" , b , _obj ); - return b.obj(); + return _newFromMods = b.obj(); } string ModSetState::toString() const { stringstream ss; - for ( ModStateHolder::const_iterator i=_mods.begin(); i!=_mods.end(); ++i ){ + 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 ModSet::createNewFromQuery( const BSONObj& query ) { BSONObj newObj; { BSONObjBuilder bb; EmbeddedBuilder eb( &bb ); BSONObjIteratorSorted i( query ); - while ( i.more() ){ + 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] == '$' ){ + 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 continue; } @@ -635,17 +761,17 @@ namespace mongo { eb.done(); newObj = bb.obj(); } - + auto_ptr<ModSetState> mss = prepare( newObj ); if ( mss->canApplyInPlace() ) - mss->applyModsInPlace(); + mss->applyModsInPlace( false ); else newObj = mss->createNewFromMods(); - + return newObj; } - + /* get special operations like $inc { $inc: { a:1, b:1 } } { $set: { a:77 } } @@ -656,21 +782,21 @@ namespace mongo { NOTE: MODIFIES source from object! */ ModSet::ModSet( - const BSONObj &from , + const BSONObj &from , const set<string>& idxKeys, const set<string> *backgroundKeys) : _isIndexed(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 ); @@ -685,18 +811,45 @@ namespace mongo { 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 ) ); - + + 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 ); + from.setFieldName( fieldName ); + updateIsIndexed( from, idxKeys, backgroundKeys ); + _mods[ from.fieldName ] = from; + + Mod to; + to.init( Mod::RENAME_TO, f ); + to.setFieldName( target ); + updateIsIndexed( to, idxKeys, backgroundKeys ); + _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 ); m.setFieldName( f.fieldName() ); - - if ( m.isIndexed( idxKeys ) || - (backgroundKeys && m.isIndexed(*backgroundKeys)) ) { - _isIndexed++; - } - + updateIsIndexed( m, idxKeys, backgroundKeys ); _mods[m.fieldName] = m; DEBUGUPDATE( "\t\t " << fieldName << "\t" << m.fieldName << "\t" << _hasDynamicArray ); @@ -709,10 +862,10 @@ namespace mongo { ModSet * n = new ModSet(); n->_isIndexed = _isIndexed; n->_hasDynamicArray = _hasDynamicArray; - for ( ModHolder::const_iterator i=_mods.begin(); i!=_mods.end(); i++ ){ + for ( ModHolder::const_iterator i=_mods.begin(); i!=_mods.end(); i++ ) { string s = i->first; size_t idx = s.find( ".$" ); - if ( idx == string::npos ){ + if ( idx == string::npos ) { n->_mods[s] = i->second; continue; } @@ -726,7 +879,7 @@ namespace mongo { } return n; } - + void checkNoMods( BSONObj o ) { BSONObjIterator i( o ); while( i.moreWithEOO() ) { @@ -736,10 +889,10 @@ namespace mongo { uassert( 10154 , "Modifiers and non-modifiers cannot be mixed", e.fieldName()[ 0 ] != '$' ); } } - + class UpdateOp : public MultiCursor::CursorOp { public: - UpdateOp( bool hasPositionalField ) : _nscanned(), _hasPositionalField( hasPositionalField ){} + UpdateOp( bool hasPositionalField ) : _nscanned(), _hasPositionalField( hasPositionalField ) {} virtual void _init() { _c = qp().newCursor(); if ( ! _c->ok() ) { @@ -751,14 +904,18 @@ namespace mongo { _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 long long nscanned() { + assert( _c.get() ); + return _c->nscanned(); + } virtual void next() { if ( ! _c->ok() ) { setComplete(); @@ -789,64 +946,62 @@ namespace mongo { }; static void checkTooLarge(const BSONObj& newObj) { - uassert( 12522 , "$ operator made object too large" , newObj.objsize() <= ( 4 * 1024 * 1024 ) ); + uassert( 12522 , "$ operator made object too large" , newObj.objsize() <= BSONObjMaxUserSize ); } - /* note: this is only (as-is) called for + /* 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, + 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) - { + 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() ) { + 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(); + if ( isOperatorUpdate ) { + const BSONObj& onDisk = loc.obj(); auto_ptr<ModSetState> mss = mods->prepare( onDisk ); - + if( mss->canApplyInPlace() ) { - mss->applyModsInPlace(); + mss->applyModsInPlace(true); 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); + DiskLoc newLoc = theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , newObj.objdata(), newObj.objsize(), debug); } - + if ( logop ) { DEV assert( mods->size() ); - + BSONObj pattern = patternOrig; if ( mss->haveArrayDepMod() ) { BSONObjBuilder patternBuilder; patternBuilder.appendElements( pattern ); mss->appendSizeSpecForArrayDepMods( patternBuilder ); - pattern = patternBuilder.obj(); + pattern = patternBuilder.obj(); } - + if( mss->needOpLogRewrite() ) { DEBUGUPDATE( "\t rewrite update: " << mss->getOpLogRewrite() ); logOp("u", ns, mss->getOpLogRewrite() , &pattern ); @@ -857,24 +1012,18 @@ namespace mongo { } 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); + theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug ); if ( logop ) { - if ( !changedId ) { - logOp("u", ns, updateobj, &patternOrig ); - } else { - logOp("d", ns, patternOrig ); - logOp("i", ns, updateobj ); - } + logOp("u", ns, updateobj, &patternOrig ); } 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 ); Client& client = cc(); @@ -883,20 +1032,20 @@ namespace mongo { if ( logLevel > 2 ) 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! */ NamespaceDetails *d = nsdetails(ns); // can be null if an upsert... NamespaceDetailsTransient *nsdt = &NamespaceDetailsTransient::get_w(ns); /* end note */ - + auto_ptr<ModSet> mods; bool isOperatorUpdate = updateobj.firstElement().fieldName()[0] == '$'; int modsIsIndexed = false; // really the # of indexes - if ( isOperatorUpdate ){ - if( d && d->backgroundIndexBuildInProgress ) { + if ( isOperatorUpdate ) { + if( d && d->indexBuildInProgress ) { set<string> bgKeys; - d->backgroundIdx().keyPattern().getFieldNames(bgKeys); + d->inProgIdx().keyPattern().getFieldNames(bgKeys); mods.reset( new ModSet(updateobj, nsdt->indexKeys(), &bgKeys) ); } else { @@ -914,30 +1063,30 @@ namespace mongo { } set<DiskLoc> seenObjects; - + 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() ) { 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 ) ){ + if ( ! c->matcher()->matches( c->currKey(), c->currLoc(), &details ) ) { c->advance(); - - if ( nscanned % 256 == 0 && ! atomic ){ + + 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() ){ + if ( ! cc->yield() ) { cc.release(); // TODO should we assert or something? break; @@ -948,20 +1097,20 @@ namespace mongo { } continue; } - + Record *r = c->_current(); DiskLoc loc = c->currLoc(); - + // TODO Maybe this is unnecessary since we have seenObjects - if ( c->getsetdup( loc ) ){ + if ( c->getsetdup( loc ) ) { c->advance(); continue; } - + BSONObj js(r); - + BSONObj pattern = patternOrig; - + if ( logop ) { BSONObjBuilder idPattern; BSONElement id; @@ -977,80 +1126,79 @@ namespace mongo { uassert( 10157 , "multi-update requires all modified objects to have an _id" , ! multi ); } } - + if ( profile ) 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. */ if ( isOperatorUpdate ) { - - if ( multi ){ + + 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 ( details.elemMatchKey && mods->hasDynamicArray() ){ + 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 ){ + + if ( indexHack ) { if ( cc.get() ) cc->updateLocation(); else c->noteLocation(); } - - if ( modsIsIndexed <= 0 && mss->canApplyInPlace() ){ - mss->applyModsInPlace();// const_cast<BSONObj&>(onDisk) ); - + + if ( modsIsIndexed <= 0 && mss->canApplyInPlace() ) { + mss->applyModsInPlace( true );// const_cast<BSONObj&>(onDisk) ); + DEBUGUPDATE( "\t\t\t doing in place update" ); if ( profile ) ss << " fastmod "; - - if ( modsIsIndexed ){ + + if ( modsIsIndexed ) { seenObjects.insert( loc ); } - } + } else { if ( rs ) rs->goingToDelete( onDisk ); BSONObj newObj = mss->createNewFromMods(); checkTooLarge(newObj); - bool changedId; - DiskLoc newLoc = theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , newObj.objdata(), newObj.objsize(), debug, changedId); + DiskLoc newLoc = theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , newObj.objdata(), newObj.objsize(), debug); 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(); + pattern = patternBuilder.obj(); } - - if ( forceRewrite || mss->needOpLogRewrite() ){ + + if ( forceRewrite || mss->needOpLogRewrite() ) { DEBUGUPDATE( "\t rewrite update: " << mss->getOpLogRewrite() ); logOp("u", ns, mss->getOpLogRewrite() , &pattern ); } @@ -1063,13 +1211,13 @@ namespace mongo { return UpdateResult( 1 , 1 , numModded ); if ( indexHack ) c->checkLocation(); - - if ( nscanned % 64 == 0 && ! atomic ){ + + 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() ){ + if ( ! cc->yield() ) { cc.release(); break; } @@ -1077,35 +1225,32 @@ namespace mongo { break; } } - + + if (atomic) + getDur().commitIfNeeded(); + continue; - } - + } + uassert( 10158 , "multi update only works with $ operators" , ! multi ); - + BSONElementManipulator::lookForTimestamps( updateobj ); checkNoMods( updateobj ); - bool changedId = false; - theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug, changedId, god); + theDataFileMgr.updateRecord(ns, d, nsdt, r, loc , updateobj.objdata(), updateobj.objsize(), debug, 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 ); - } + logOp("u", ns, updateobj, &pattern ); } return UpdateResult( 1 , 0 , 1 ); } - + if ( numModded ) return UpdateResult( 1 , 1 , numModded ); - + if ( profile ) ss << " nscanned:" << nscanned; - + if ( upsert ) { if ( updateobj.firstElement().fieldName()[0] == '$' ) { /* upsert of an $inc. build a default */ @@ -1115,7 +1260,7 @@ namespace mongo { theDataFileMgr.insertWithObjMod(ns, newObj, god); if ( logop ) logOp( "i", ns, newObj ); - + return UpdateResult( 0 , 1 , 1 , newObj ); } uassert( 10159 , "multi update only works with $ operators" , ! multi ); @@ -1130,14 +1275,14 @@ namespace mongo { } 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 ) ); + uassert( 10156 , str::stream() << "cannot update system collection: " << ns << " q: " << patternOrig << " u: " << updateobj , legalClientSystemNS( ns , true ) ); } return _updateObjects(false, ns, updateobj, patternOrig, upsert, multi, logop, debug); } - + } |