summaryrefslogtreecommitdiff
path: root/db/update.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'db/update.cpp')
-rw-r--r--db/update.cpp736
1 files changed, 736 insertions, 0 deletions
diff --git a/db/update.cpp b/db/update.cpp
new file mode 100644
index 0000000..0639a99
--- /dev/null
+++ b/db/update.cpp
@@ -0,0 +1,736 @@
+// update.cpp
+
+/**
+ * Copyright (C) 2008 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 "stdafx.h"
+#include "query.h"
+#include "pdfile.h"
+#include "jsobjmanipulator.h"
+#include "queryoptimizer.h"
+#include "repl.h"
+#include "update.h"
+
+namespace mongo {
+
+ const char* Mod::modNames[] = { "$inc", "$set", "$push", "$pushAll", "$pull", "$pullAll" , "$pop", "$unset" ,
+ "$bitand" , "$bitor" , "$bit" };
+ 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 ( 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::apply( BSONObjBuilder& b , BSONElement in ){
+ switch ( op ){
+
+ case INC: {
+ // TODO: this is horrible
+ inc( in );
+ b.appendAs( elt , shortFieldName );
+ break;
+ }
+
+ case SET: {
+ _checkForAppending( elt );
+ b.appendAs( elt , shortFieldName );
+ break;
+ }
+
+ case UNSET: {
+ //Explicit NOOP
+ 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() ){
+ bb.append( i.next() );
+ n++;
+ }
+
+ pushStartSize = n;
+
+ 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() ){
+ bb.append( i.next() );
+ n++;
+ }
+
+ pushStartSize = n;
+
+ i = BSONObjIterator( elt.embeddedObject() );
+ 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() ){
+ BSONElement e = i.next();
+ bool allowed = true;
+
+ if ( op == PULL ){
+ allowed = ! _pullElementMatch( e );
+ }
+ else {
+ BSONObjIterator j( elt.embeddedObject() );
+ while( j.more() ) {
+ BSONElement arrJ = j.next();
+ if ( e.woCompare( arrJ, false ) == 0 ){
+ allowed = false;
+ break;
+ }
+ }
+ }
+
+ if ( allowed )
+ bb.appendAs( e , bb.numStr( n++ ) );
+ }
+
+ bb.done();
+ break;
+ }
+
+ 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 ){
+ // pop from front
+ if ( i.more() ){
+ i.next();
+ n++;
+ }
+
+ while( i.more() ) {
+ bb.appendAs( i.next() , bb.numStr( n - 1 ).c_str() );
+ n++;
+ }
+ }
+ else {
+ // pop from back
+ while( i.more() ) {
+ n++;
+ BSONElement arrI = i.next();
+ if ( i.more() ){
+ bb.append( arrI );
+ }
+ }
+ }
+
+ pushStartSize = n;
+ assert( pushStartSize == in.embeddedObject().nFields() );
+ bb.done();
+ break;
+ }
+
+ case BIT: {
+ 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() ){
+ BSONElement e = it.next();
+ uassert( 10139 , "$bit field must be number" , e.isNumber() );
+ 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() ){
+ case NumberInt: x = x|e.numberInt(); break;
+ case NumberLong: y = y|e.numberLong(); break;
+ default: assert( 0 );
+ }
+ }
+
+ else {
+ throw UserException( 9016, (string)"unknown bit mod:" + e.fieldName() );
+ }
+ }
+
+ switch( in.type() ){
+ case NumberInt: b.append( shortFieldName , x ); break;
+ case NumberLong: b.append( shortFieldName , y ); break;
+ default: assert( 0 );
+ }
+
+ break;
+ }
+
+ default:
+ stringstream ss;
+ ss << "Mod::apply can't handle type: " << op;
+ throw UserException( 9017, ss.str() );
+ }
+ }
+
+ bool ModSet::canApplyInPlaceAndVerify(const BSONObj &obj) const {
+ bool inPlacePossible = true;
+
+ // 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 ) {
+ const Mod& m = i->second;
+ BSONElement e = obj.getFieldDotted(m.fieldName);
+
+ if ( e.eoo() ) {
+ inPlacePossible = (m.op == Mod::UNSET);
+ }
+ else {
+ switch( m.op ) {
+ case Mod::INC:
+ uassert( 10140 , "Cannot apply $inc modifier to non-number", e.isNumber() || e.eoo() );
+ if ( !e.isNumber() )
+ inPlacePossible = false;
+ break;
+ case Mod::SET:
+ inPlacePossible =
+ 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() );
+ inPlacePossible = false;
+ break;
+ case Mod::PULL:
+ case Mod::PULL_ALL: {
+ uassert( 10142 , "Cannot apply $pull/$pullAll modifier to non-array", e.type() == Array || e.eoo() );
+ BSONObjIterator i( e.embeddedObject() );
+ while( inPlacePossible && i.more() ) {
+ BSONElement arrI = i.next();
+ if ( m.op == Mod::PULL ) {
+ if ( m._pullElementMatch( arrI ) )
+ inPlacePossible = false;
+ }
+ else if ( m.op == Mod::PULL_ALL ) {
+ BSONObjIterator j( m.elt.embeddedObject() );
+ while( inPlacePossible && j.moreWithEOO() ) {
+ BSONElement arrJ = j.next();
+ if ( arrJ.eoo() )
+ break;
+ if ( arrI.woCompare( arrJ, false ) == 0 ) {
+ inPlacePossible = false;
+ }
+ }
+ }
+ }
+ break;
+ }
+ case Mod::POP: {
+ uassert( 10143 , "Cannot apply $pop modifier to non-array", e.type() == Array || e.eoo() );
+ if ( ! e.embeddedObject().isEmpty() )
+ inPlacePossible = false;
+ break;
+ }
+ default:
+ // mods we don't know about shouldn't be done in place
+ inPlacePossible = false;
+ }
+ }
+ }
+ return inPlacePossible;
+ }
+
+ void ModSet::applyModsInPlace(const BSONObj &obj) const {
+ for ( ModHolder::const_iterator i = _mods.begin(); i != _mods.end(); ++i ) {
+ const Mod& m = i->second;
+ BSONElement e = obj.getFieldDotted(m.fieldName);
+
+ switch ( m.op ){
+ case Mod::UNSET:
+ case Mod::PULL:
+ case Mod::PULL_ALL:
+ break;
+
+ // [dm] the BSONElementManipulator statements below are for replication (correct?)
+ case Mod::INC:
+ m.inc(e);
+ m.setElementToOurNumericValue(e);
+ break;
+ case Mod::SET:
+ if ( e.isNumber() && m.elt.isNumber() ) {
+ // todo: handle NumberLong:
+ m.setElementToOurNumericValue(e);
+ }
+ else {
+ BSONElementManipulator( e ).replaceTypeAndValue( m.elt );
+ }
+ break;
+ default:
+ uassert( 10144 , "can't apply mod in place - shouldn't have gotten here" , 0 );
+ }
+ }
+ }
+
+ void ModSet::extractFields( map< string, BSONElement > &fields, const BSONElement &top, const string &base ) {
+ if ( top.type() != Object ) {
+ fields[ base + top.fieldName() ] = top;
+ return;
+ }
+ BSONObjIterator i( top.embeddedObject() );
+ bool empty = true;
+ while( i.moreWithEOO() ) {
+ BSONElement e = i.next();
+ if ( e.eoo() )
+ break;
+ extractFields( fields, e, base + top.fieldName() + "." );
+ empty = false;
+ }
+ if ( empty )
+ fields[ base + top.fieldName() ] = top;
+ }
+
+ void ModSet::_appendNewFromMods( const string& root , Mod& m , BSONObjBuilder& b , set<string>& onedownseen ){
+ const char * temp = m.fieldName;
+ temp += root.size();
+ const char * dot = strchr( temp , '.' );
+ 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() ) );
+ createNewFromMods( nr , bb , BSONObj() );
+ bb.done();
+ }
+ else {
+ appendNewFromMod( m , b );
+ }
+
+ }
+
+ void ModSet::createNewFromMods( const string& root , BSONObjBuilder& b , const BSONObj &obj ){
+ BSONObjIteratorSorted es( obj );
+ BSONElement e = es.next();
+
+ ModHolder::iterator m = _mods.lower_bound( root );
+ ModHolder::iterator mend = _mods.lower_bound( root + "{" );
+
+ set<string> onedownseen;
+
+ while ( e.type() && m != mend ){
+ string field = root + e.fieldName();
+ FieldCompareResult cmp = compareDottedFieldNames( m->second.fieldName , field );
+
+ 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 ){
+ onedownseen.insert( e.fieldName() );
+ BSONObjBuilder bb ( e.type() == Object ? b.subobjStart( e.fieldName() ) : b.subarrayStart( e.fieldName() ) );
+ stringstream nr; nr << root << e.fieldName() << ".";
+ createNewFromMods( nr.str() , bb , e.embeddedObject() );
+ bb.done();
+ // inc both as we handled both
+ e = es.next();
+ m++;
+ }
+ continue;
+ }
+ case LEFT_BEFORE: // Mod on a field that doesn't exist
+ _appendNewFromMods( root , m->second , b , onedownseen );
+ m++;
+ continue;
+ case SAME:
+ m->second.apply( b , e );
+ e = es.next();
+ m++;
+ continue;
+ case RIGHT_BEFORE: // field that doesn't have a MOD
+ b.append( e );
+ 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.type() ){
+ b.append( e );
+ e = es.next();
+ }
+
+ // do mods that don't have fields already
+ for ( ; m != mend; m++ ){
+ _appendNewFromMods( root , m->second , b , onedownseen );
+ }
+ }
+
+ BSONObj ModSet::createNewFromMods( const BSONObj &obj ) {
+ BSONObjBuilder b( (int)(obj.objsize() * 1.1) );
+ createNewFromMods( "" , b , obj );
+ return b.obj();
+ }
+
+ 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.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;
+ }
+
+ eb.appendAs( e , e.fieldName() );
+ }
+ eb.done();
+ newObj = bb.obj();
+ }
+
+ if ( canApplyInPlaceAndVerify( newObj ) )
+ applyModsInPlace( newObj );
+ else
+ newObj = createNewFromMods( newObj );
+
+ 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!
+ */
+ void ModSet::getMods(const BSONObj &from) {
+ 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();
+ BSONObjIterator jt(j);
+ Mod::Op op = opFromStr( fn );
+ if ( op == Mod::INC )
+ strcpy((char *) fn, "$set"); // rewrite for op log
+ while ( jt.more() ) {
+ BSONElement f = jt.next(); // x:44
+
+ const char * fieldName = f.fieldName();
+
+ 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( 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 ) );
+
+ Mod m;
+ m.init( op , f );
+ m.setFieldName( f.fieldName() );
+
+ // horrible - to be cleaned up
+ if ( f.type() == NumberDouble ) {
+ m.ndouble = (double *) f.value();
+ m.nint = 0;
+ } else if ( f.type() == NumberInt ) {
+ m.ndouble = 0;
+ m.nint = (int *) f.value();
+ }
+ else if( f.type() == NumberLong ) {
+ m.ndouble = 0;
+ m.nint = 0;
+ m.nlong = (long long *) f.value();
+ }
+
+ _mods[m.fieldName] = m;
+ }
+ }
+ }
+
+ void checkNoMods( BSONObj o ) {
+ BSONObjIterator i( o );
+ while( i.moreWithEOO() ) {
+ BSONElement e = i.next();
+ if ( e.eoo() )
+ break;
+ uassert( 10154 , "Modifiers and non-modifiers cannot be mixed", e.fieldName()[ 0 ] != '$' );
+ }
+ }
+
+ class UpdateOp : public QueryOp {
+ public:
+ UpdateOp() : nscanned_() {}
+ virtual void init() {
+ BSONObj pattern = qp().query();
+ c_.reset( qp().newCursor().release() );
+ if ( !c_->ok() )
+ setComplete();
+ else
+ matcher_.reset( new CoveredIndexMatcher( pattern, qp().indexKey() ) );
+ }
+ virtual void next() {
+ if ( !c_->ok() ) {
+ setComplete();
+ return;
+ }
+ nscanned_++;
+ if ( matcher_->matches(c_->currKey(), c_->currLoc()) ) {
+ setComplete();
+ return;
+ }
+ c_->advance();
+ }
+ bool curMatches(){
+ return matcher_->matches(c_->currKey(), c_->currLoc() );
+ }
+ virtual bool mayRecordPlan() const { return false; }
+ virtual QueryOp *clone() const {
+ return new UpdateOp();
+ }
+ shared_ptr< Cursor > c() { return c_; }
+ long long nscanned() const { return nscanned_; }
+ private:
+ shared_ptr< Cursor > c_;
+ long long nscanned_;
+ auto_ptr< CoveredIndexMatcher > matcher_;
+ };
+
+
+ UpdateResult updateObjects(const char *ns, BSONObj updateobjOrig, BSONObj patternOrig, bool upsert, bool multi, bool logop , OpDebug& debug ) {
+ int profile = cc().database()->profile;
+ StringBuilder& ss = debug.str;
+
+ 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 ) );
+ }
+
+ 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;
+ while ( c->ok() ) {
+ if ( numModded > 0 && ! u->curMatches() ){
+ c->advance();
+ continue;
+ }
+ Record *r = c->_current();
+ DiskLoc loc = c->currLoc();
+
+ if ( c->getsetdup( loc ) ){
+ c->advance();
+ continue;
+ }
+
+ BSONObj js(r);
+
+ BSONObj pattern = patternOrig;
+ BSONObj updateobj = updateobjOrig;
+
+ if ( logop ) {
+ BSONObjBuilder idPattern;
+ BSONElement id;
+ // NOTE: If the matching object lacks an id, we'll log
+ // with the original pattern. This isn't replay-safe.
+ // It might make sense to suppress the log instead
+ // if there's no id.
+ if ( js.getObjectID( id ) ) {
+ idPattern.append( id );
+ pattern = idPattern.obj();
+ }
+ else {
+ uassert( 10157 , "multi-update requires all modified objects to have an _id" , ! multi );
+ }
+ }
+
+ if ( profile )
+ ss << " nscanned:" << u->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. */
+
+ const char *firstField = updateobj.firstElement().fieldName();
+
+ if ( firstField[0] == '$' ) {
+
+ if ( multi ){
+ c->advance(); // go to next record in case this one moves
+ if ( seenObjects.count( loc ) )
+ continue;
+ updateobj = updateobj.copy();
+ }
+
+ ModSet mods;
+ mods.getMods(updateobj);
+ NamespaceDetailsTransient& ndt = NamespaceDetailsTransient::get_w(ns);
+ set<string>& idxKeys = ndt.indexKeys();
+ int isIndexed = mods.isIndexed( idxKeys );
+
+ if ( isIndexed && multi ){
+ c->noteLocation();
+ }
+
+ if ( isIndexed <= 0 && mods.canApplyInPlaceAndVerify( loc.obj() ) ) {
+ mods.applyModsInPlace( loc.obj() );
+ //seenObjects.insert( loc );
+ if ( profile )
+ ss << " fastmod ";
+
+ if ( isIndexed ){
+ seenObjects.insert( loc );
+ }
+ }
+ else {
+ BSONObj newObj = mods.createNewFromMods( loc.obj() );
+ uassert( 12522 , "$ operator made objcet too large" , newObj.isValid() );
+ DiskLoc newLoc = theDataFileMgr.update(ns, r, loc , newObj.objdata(), newObj.objsize(), debug);
+ if ( newLoc != loc || isIndexed ){
+ // object moved, need to make sure we don' get again
+ seenObjects.insert( newLoc );
+ }
+
+ }
+
+ if ( logop ) {
+
+ assert( mods.size() );
+
+ if ( mods.haveArrayDepMod() ) {
+ BSONObjBuilder patternBuilder;
+ patternBuilder.appendElements( pattern );
+ mods.appendSizeSpecForArrayDepMods( patternBuilder );
+ pattern = patternBuilder.obj();
+ }
+
+ if ( mods.needOpLogRewrite() )
+ updateobj = mods.getOpLogRewrite();
+
+ logOp("u", ns, updateobj, &pattern );
+ }
+ numModded++;
+ if ( ! multi )
+ break;
+ if ( multi && isIndexed )
+ c->checkLocation();
+ continue;
+ }
+
+ uassert( 10158 , "multi update only works with $ operators" , ! multi );
+
+ BSONElementManipulator::lookForTimestamps( updateobj );
+ checkNoMods( updateobj );
+ theDataFileMgr.update(ns, r, loc , updateobj.objdata(), updateobj.objsize(), debug);
+ if ( logop )
+ logOp("u", ns, updateobj, &pattern );
+ return UpdateResult( 1 , 0 , 1 );
+ }
+
+ if ( numModded )
+ return UpdateResult( 1 , 1 , numModded );
+
+
+ if ( profile )
+ ss << " nscanned:" << u->nscanned();
+
+ if ( upsert ) {
+ if ( updateobjOrig.firstElement().fieldName()[0] == '$' ) {
+ /* upsert of an $inc. build a default */
+ ModSet mods;
+ mods.getMods(updateobjOrig);
+
+ BSONObj newObj = mods.createNewFromQuery( patternOrig );
+
+ if ( profile )
+ ss << " fastmodinsert ";
+ theDataFileMgr.insert(ns, newObj);
+ if ( profile )
+ ss << " fastmodinsert ";
+ if ( logop )
+ logOp( "i", ns, newObj );
+ return UpdateResult( 0 , 1 , 1 );
+ }
+ uassert( 10159 , "multi update only works with $ operators" , ! multi );
+ checkNoMods( updateobjOrig );
+ if ( profile )
+ ss << " upsert ";
+ theDataFileMgr.insert(ns, updateobjOrig);
+ if ( logop )
+ logOp( "i", ns, updateobjOrig );
+ return UpdateResult( 0 , 0 , 1 );
+ }
+ return UpdateResult( 0 , 0 , 0 );
+ }
+
+}